Support Joomla!

Packages

Package: phpGACL

License

Content on this site is copyright © 2005 - 2008 Open Source Matters Inc and can be used in accordance with the Joomla! Electronic Documentation License. Some parts of this website may be subject to other licenses.
Source code for file /phpgacl/gacl.php

Documentation is available at gacl.php

  1. <?php
  2. // $Id: gacl.php 10381 2008-06-01 03:35:53Z pasamio $
  3.  
  4. /**
  5.  * phpGACL - Generic Access Control List
  6.  * Copyright (C) 2002,2003 Mike Benoit
  7.  *
  8.  * This library is free software; you can redistribute it and/or
  9.  * modify it under the terms of the GNU Lesser General Public
  10.  * License as published by the Free Software Foundation; either
  11.  * version 2.1 of the License, or (at your option) any later version.
  12.  *
  13.  * This library is distributed in the hope that it will be useful,
  14.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16.  * Lesser General Public License for more details.
  17.  *
  18.  * You should have received a copy of the GNU Lesser General Public
  19.  * License along with this library; if not, write to the Free Software
  20.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21.  *
  22.  * For questions, help, comments, discussion, etc., please join the
  23.  * phpGACL mailing list. http://sourceforge.net/mail/?group_id=57103
  24.  *
  25.  * You may contact the author of phpGACL by e-mail at:
  26.  * ipso@snappymail.ca
  27.  *
  28.  * The latest version of phpGACL can be obtained from:
  29.  * http://phpgacl.sourceforge.net/
  30.  *
  31.  * @package phpGACL
  32.  */
  33.  
  34. /*
  35.  * Path to ADODB.
  36.  */
  37. //if ( !defined('ADODB_DIR') ) {
  38. //    define('ADODB_DIR', dirname(__FILE__).'/adodb');
  39. //}
  40. // Causing conflicts with 3rd party apps using adodb
  41.  
  42. /**
  43. * phpGACL main class
  44. *
  45. * Class gacl should be used in applications where only querying the phpGACL
  46. * database is required.
  47. *
  48. @package phpGACL
  49. @author Mike Benoit <ipso@snappymail.ca>
  50. */
  51. class gacl {
  52.     /*
  53.     --- Private properties ---
  54.     */
  55.     /** @var boolean Enables Debug output if true */
  56.     var $_debug = FALSE;
  57.  
  58.     /**
  59.      * Joomla usage
  60.      */
  61.     var $_debugLog = null;
  62.  
  63.     /*
  64.     --- Database configuration. ---
  65.     */
  66.     /** @var string Prefix for all the phpgacl tables in the database */
  67.     var $_db_table_prefix = '';
  68.  
  69.     /** @var string The database type, based on available ADODB connectors - mysql, postgres7, sybase, oci8po See here for more: http://php.weblogs.com/adodb_manual#driverguide */
  70.     var $_db_type = 'mysql';
  71.  
  72.     /** @var string The database server */
  73.     var $_db_host = 'localhost';
  74.  
  75.     /** @var string The database user name */
  76.     var $_db_user = 'root';
  77.  
  78.     /** @var string The database user password */
  79.     var $_db_password = '';
  80.  
  81.     /** @var string The database name */
  82.     var $_db_name = 'gacl';
  83.  
  84.     /** @var object An ADODB database connector object */
  85.     var $_db = '';
  86.  
  87.     /*
  88.      * NOTE:     This cache must be manually cleaned each time ACL's are modified.
  89.      *         Alternatively you could wait for the cache to expire.
  90.      */
  91.  
  92.     /** @var boolean Caches queries if true */
  93.     var $_caching = FALSE;
  94.  
  95.     /** @var boolean Force cache to expire */
  96.     var $_force_cache_expire = TRUE;
  97.  
  98.     /** @var string The directory for cache file to eb written (ensure write permission are set) */
  99.     var $_cache_dir = '/tmp/phpgacl_cache'// NO trailing slash
  100.  
  101.     
  102.     /** @var int The time for the cache to expire in seconds - 600 == Ten Minutes */
  103.     var $_cache_expire_time=600;
  104.  
  105.     /** @var string A switch to put acl_check into '_group_' mode */
  106.     var $_group_switch = '_group_';
  107.  
  108.     /**
  109.      * Constructor
  110.      * @param array An arry of options to oeverride the class defaults
  111.      */
  112.     function gacl($options NULL{
  113.  
  114.         $available_options array('db','debug','items_per_page','max_select_box_items','max_search_return_items','db_table_prefix','db_type','db_host','db_user','db_password','db_name','caching','force_cache_expire','cache_dir','cache_expire_time');
  115.         if (is_array($options)) {
  116.             foreach ($options as $key => $value{
  117.                 $this->debug_text("Option: $key");
  118.  
  119.                 if (in_array($key$available_options) ) {
  120.                     $this->debug_text("Valid Config options: $key");
  121.                     $property '_'.$key;
  122.                     $this->$property $value;
  123.                 else {
  124.                     $this->debug_text("ERROR: Config option: $key is not a valid option");
  125.                 }
  126.             }
  127.         }
  128.  
  129.         //Use NUM for slight performance/memory reasons.
  130.         //Leave this in for backwards compatibility with older ADODB installations.
  131.         //If your using ADODB v3.5+ feel free to comment out the following line if its giving you problems.
  132.         //$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
  133.  
  134.         if (is_object($this->_db)) {
  135.             $this->db &$this->_db;
  136.         else {
  137.             require_onceADODB_DIR .'/adodb.inc.php');
  138.             require_onceADODB_DIR .'/adodb-pager.inc.php');
  139.  
  140.             $this->db ADONewConnection($this->_db_type);
  141.             $this->db->SetFetchMode(ADODB_FETCH_NUM);
  142.             $this->db->PConnect($this->_db_host$this->_db_user$this->_db_password$this->_db_name);
  143.         }
  144.         $this->db->debug $this->_debug;
  145.  
  146.         if $this->_caching == TRUE {
  147.             if (!class_exists('Hashed_Cache_Lite')) {
  148.                 require_once(dirname(__FILE__.'/Cache_Lite/Hashed_Cache_Lite.php');
  149.             }
  150.  
  151.             /*
  152.              * Cache options. We default to the highest performance. If you run in to cache corruption problems,
  153.              * Change all the 'false' to 'true', this will slow things down slightly however.
  154.              */
  155.  
  156.             $cache_options array(
  157.                 'caching' => $this->_caching,
  158.                 'cacheDir' => $this->_cache_dir.'/',
  159.                 'lifeTime' => $this->_cache_expire_time,
  160.                 'fileLocking' => TRUE,
  161.                 'writeControl' => FALSE,
  162.                 'readControl' => FALSE,
  163.                 'memoryCaching' => TRUE,
  164.                 'automaticSerialization' => FALSE
  165.             );
  166.             $this->Cache_Lite new Hashed_Cache_Lite($cache_options);
  167.         }
  168.  
  169.         return true;
  170.     }
  171.  
  172.     /**
  173.     * Prints debug text if debug is enabled.
  174.     * @param string THe text to output
  175.     * @return boolean Always returns true
  176.     */
  177.     function debug_text($text{
  178.         if (!$this->_debugLog)
  179.         {
  180.             $this->_debugLog = array();
  181.         }
  182.  
  183.         $this->_debugLog[$text;
  184.         if ($this->_debug{
  185.             echo "$text<br>\n";
  186.         }
  187.  
  188.         return true;
  189.     }
  190.  
  191.     /**
  192.     * Prints database debug text if debug is enabled.
  193.     * @param string The name of the function calling this method
  194.     * @return string Returns an error message
  195.     */
  196.     function debug_db($function_name ''{
  197.         if ($function_name != ''{
  198.             $function_name .= ' (): ';
  199.         }
  200.  
  201.         return $this->debug_text ($function_name .'database error: '$this->db->ErrorMsg(.' ('$this->db->ErrorNo(.')');
  202.     }
  203.  
  204.     /**
  205.     * Wraps the actual acl_query() function.
  206.     *
  207.     * It is simply here to return TRUE/FALSE accordingly.
  208.     * @param string The ACO section value
  209.     * @param string The ACO value
  210.     * @param string The ARO section value
  211.     * @param string The ARO section
  212.     * @param string The AXO section value (optional)
  213.     * @param string The AXO section value (optional)
  214.     * @param integer The group id of the ARO ??Mike?? (optional)
  215.     * @param integer The group id of the AXO ??Mike?? (optional)
  216.     * @return boolean TRUE if the check succeeds, false if not.
  217.     */
  218.     function acl_check($aco_section_value$aco_value$aro_section_value$aro_value$axo_section_value=NULL$axo_value=NULL$root_aro_group=NULL$root_axo_group=NULL{
  219.         $acl_result $this->acl_query($aco_section_value$aco_value$aro_section_value$aro_value$axo_section_value$axo_value$root_aro_group$root_axo_group);
  220.  
  221.         return $acl_result['allow'];
  222.     }
  223.  
  224.     /**
  225.     * Wraps the actual acl_query() function.
  226.     *
  227.     * Quick access to the return value of an ACL.
  228.     * @param string The ACO section value
  229.     * @param string The ACO value
  230.     * @param string The ARO section value
  231.     * @param string The ARO section
  232.     * @param string The AXO section value (optional)
  233.     * @param string The AXO section value (optional)
  234.     * @param integer The group id of the ARO (optional)
  235.     * @param integer The group id of the AXO (optional)
  236.     * @return string The return value of the ACL
  237.     */
  238.     function acl_return_value($aco_section_value$aco_value$aro_section_value$aro_value$axo_section_value=NULL$axo_value=NULL$root_aro_group=NULL$root_axo_group=NULL{
  239.         $acl_result $this->acl_query($aco_section_value$aco_value$aro_section_value$aro_value$axo_section_value$axo_value$root_aro_group$root_axo_group);
  240.  
  241.         return $acl_result['return_value'];
  242.     }
  243.  
  244.     /**
  245.      * Handles ACL lookups over arrays of AROs
  246.      * @param string The ACO section value
  247.      * @param string The ACO value
  248.     * @param array An named array of arrays, each element in the format aro_section_value=>array(aro_value1,aro_value1,...)
  249.      * @return mixed The same data format as inputted.
  250.      */
  251.     function acl_check_array($aco_section_value$aco_value$aro_array{
  252.         /*
  253.             Input Array:
  254.                 Section => array(Value, Value, Value),
  255.                 Section => array(Value, Value, Value)
  256.  
  257.          */
  258.  
  259.         if (!is_array($aro_array)) {
  260.             $this->debug_text("acl_query_array(): ARO Array must be passed");
  261.             return false;
  262.         }
  263.  
  264.         foreach($aro_array as $aro_section_value => $aro_value_array{
  265.             foreach ($aro_value_array as $aro_value{
  266.                 $this->debug_text("acl_query_array(): ARO Section Value: $aro_section_value ARO VALUE: $aro_value");
  267.  
  268.                 if$this->acl_check($aco_section_value$aco_value$aro_section_value$aro_value) ) {
  269.                     $this->debug_text("acl_query_array(): ACL_CHECK True");
  270.                     $retarr[$aro_section_value][$aro_value;
  271.                 else {
  272.                     $this->debug_text("acl_query_array(): ACL_CHECK False");
  273.                 }
  274.             }
  275.         }
  276.  
  277.         return $retarr;
  278.  
  279.     }
  280.  
  281.     /**
  282.     * The Main function that does the actual ACL lookup.
  283.     * @param string The ACO section value
  284.     * @param string The ACO value
  285.     * @param string The ARO section value
  286.     * @param string The ARO section
  287.     * @param string The AXO section value (optional)
  288.     * @param string The AXO section value (optional)
  289.     * @param string The value of the ARO group (optional)
  290.     * @param string The value of the AXO group (optional)
  291.     * @param boolean Debug the operation if true (optional)
  292.     * @return array Returns as much information as possible about the ACL so other functions can trim it down and omit unwanted data.
  293.     */
  294.     function acl_query($aco_section_value$aco_value$aro_section_value$aro_value$axo_section_value=NULL$axo_value=NULL$root_aro_group=NULL$root_axo_group=NULL$debug=NULL{
  295.  
  296.         $cache_id 'acl_query_'.$aco_section_value.'-'.$aco_value.'-'.$aro_section_value.'-'.$aro_value.'-'.$axo_section_value.'-'.$axo_value.'-'.$root_aro_group.'-'.$root_axo_group.'-'.$debug;
  297.  
  298.         $retarr $this->get_cache($cache_id);
  299.  
  300.         if (!$retarr{
  301.             /*
  302.              * Grab all groups mapped to this ARO/AXO
  303.              */
  304.             $aro_group_ids $this->acl_get_groups($aro_section_value$aro_value$root_aro_group'ARO');
  305.  
  306.             if (is_array($aro_group_idsAND !empty($aro_group_ids)) {
  307.                 $sql_aro_group_ids implode(','$aro_group_ids);
  308.             }
  309.  
  310.             if ($axo_section_value !== '' AND $axo_value !== ''{
  311.                 $axo_group_ids $this->acl_get_groups($axo_section_value$axo_value$root_axo_group'AXO');
  312.  
  313.                 if (is_array($axo_group_idsAND !empty($axo_group_ids)) {
  314.                     $sql_axo_group_ids implode(','$axo_group_ids);
  315.                 }
  316.             }
  317.  
  318.             /*
  319.              * This query is where all the magic happens.
  320.              * The ordering is very important here, as well very tricky to get correct.
  321.              * Currently there can be  duplicate ACLs, or ones that step on each other toes. In this case, the ACL that was last updated/created
  322.              * is used.
  323.              *
  324.              * This is probably where the most optimizations can be made.
  325.              */
  326.  
  327.             $order_by array();
  328.  
  329.             $query '
  330.                     SELECT        a.id,a.allow,a.return_value
  331.                     FROM        '$this->_db_table_prefix .'acl a
  332.                     LEFT JOIN     '$this->_db_table_prefix .'aco_map ac ON ac.acl_id=a.id';
  333.  
  334.             if ($aro_section_value != $this->_group_switch{
  335.                 $query .= '
  336.                     LEFT JOIN    '$this->_db_table_prefix .'aro_map ar ON ar.acl_id=a.id';
  337.             }
  338.  
  339.             if ($axo_section_value != $this->_group_switch{
  340.                 $query .= '
  341.                     LEFT JOIN    '$this->_db_table_prefix .'axo_map ax ON ax.acl_id=a.id';
  342.             }
  343.  
  344.             /*
  345.              * if there are no aro groups, don't bother doing the join.
  346.              */
  347.             if (isset($sql_aro_group_ids)) {
  348.                 $query .= '
  349.                     LEFT JOIN    '$this->_db_table_prefix .'aro_groups_map arg ON arg.acl_id=a.id
  350.                     LEFT JOIN    '$this->_db_table_prefix .'aro_groups rg ON rg.id=arg.group_id';
  351.             }
  352.  
  353.             // this join is necessary to weed out rules associated with axo groups
  354.             $query .= '
  355.                     LEFT JOIN    '$this->_db_table_prefix .'axo_groups_map axg ON axg.acl_id=a.id';
  356.  
  357.             /*
  358.              * if there are no axo groups, don't bother doing the join.
  359.              * it is only used to rank by the level of the group.
  360.              */
  361.             if (isset($sql_axo_group_ids)) {
  362.                 $query .= '
  363.                     LEFT JOIN    '$this->_db_table_prefix .'axo_groups xg ON xg.id=axg.group_id';
  364.             }
  365.  
  366.             //Move the below line to the LEFT JOIN above for PostgreSQL's sake.
  367.             //AND    ac.acl_id=a.id
  368.             $query .= '
  369.                     WHERE        a.enabled=1
  370.                         AND        (ac.section_value='$this->db->quote($aco_section_value.' AND ac.value='$this->db->quote($aco_value.')';
  371.  
  372.             // if we are querying an aro group
  373.             if ($aro_section_value == $this->_group_switch{
  374.                 // if acl_get_groups did not return an array
  375.                 if !isset ($sql_aro_group_ids) ) {
  376.                     $this->debug_text ('acl_query(): Invalid ARO Group: '$aro_value);
  377.                     return FALSE;
  378.                 }
  379.  
  380.                 $query .= '
  381.                         AND        rg.id IN ('$sql_aro_group_ids .')';
  382.  
  383.                 $order_by['(rg.rgt-rg.lft) ASC';
  384.             else {
  385.                 $query .= '
  386.                         AND        ((ar.section_value='$this->db->quote($aro_section_value.' AND ar.value='$this->db->quote($aro_value.')';
  387.  
  388.                 if isset ($sql_aro_group_ids) ) {
  389.                     $query .= ' OR rg.id IN ('$sql_aro_group_ids .')';
  390.  
  391.                     $order_by['(CASE WHEN ar.value IS NULL THEN 0 ELSE 1 END) DESC';
  392.                     $order_by['(rg.rgt-rg.lft) ASC';
  393.                 }
  394.  
  395.                 $query .= ')';
  396.             }
  397.  
  398.  
  399.             // if we are querying an axo group
  400.             if ($axo_section_value == $this->_group_switch{
  401.                 // if acl_get_groups did not return an array
  402.                 if !isset ($sql_axo_group_ids) ) {
  403.                     $this->debug_text ('acl_query(): Invalid AXO Group: '$axo_value);
  404.                     return FALSE;
  405.                 }
  406.  
  407.                 $query .= '
  408.                         AND        xg.id IN ('$sql_axo_group_ids .')';
  409.  
  410.                 $order_by['(xg.rgt-xg.lft) ASC';
  411.             else {
  412.                 $query .= '
  413.                         AND        (';
  414.  
  415.                 if ($axo_section_value == '' AND $axo_value == ''{
  416.                     $query .= '(ax.section_value IS NULL AND ax.value IS NULL)';
  417.                 else {
  418.                     $query .= '(ax.section_value='$this->db->quote($axo_section_value.' AND ax.value='$this->db->quote($axo_value.')';
  419.                 }
  420.  
  421.                 if (isset($sql_axo_group_ids)) {
  422.                     $query .= ' OR xg.id IN ('$sql_axo_group_ids .')';
  423.  
  424.                     $order_by['(CASE WHEN ax.value IS NULL THEN 0 ELSE 1 END) DESC';
  425.                     $order_by['(xg.rgt-xg.lft) ASC';
  426.                 else {
  427.                     $query .= ' AND axg.group_id IS NULL';
  428.                 }
  429.  
  430.                 $query .= ')';
  431.             }
  432.  
  433.             /*
  434.              * The ordering is always very tricky and makes all the difference in the world.
  435.              * Order (ar.value IS NOT NULL) DESC should put ACLs given to specific AROs
  436.              * ahead of any ACLs given to groups. This works well for exceptions to groups.
  437.              */
  438.  
  439.             $order_by['a.updated_date DESC';
  440.  
  441.             $query .= '
  442.                     ORDER BY    'implode (','$order_by'
  443.                     ';
  444.  
  445.             // we are only interested in the first row
  446.             $rs $this->db->SelectLimit($query1);
  447.  
  448.             if (!is_object($rs)) {
  449.                 $this->debug_db('acl_query');
  450.                 return FALSE;
  451.             }
  452.  
  453.             $row =$rs->FetchRow();
  454.  
  455.             /*
  456.              * Return ACL ID. This is the key to "hooking" extras like pricing assigned to ACLs etc... Very useful.
  457.         &nb