"Varchar", "Arg" => "Int", ); static $has_one = array( "Group" => "Group", ); static $indexes = array( "Code" => true, ); /** * Permissions declared as belonging to the system. This is used to provide * permission matrices. */ static $declared_permissions = null; protected static $declared_permissions_list = null; /** * @var $strict_checking Boolean Method to globally disable "strict" checking, * which means a permission will be granted if the key does not exist at all. */ static $strict_checking = true; /** * If this setting is set, then permissions can imply other permissions */ static $implied_permissions = false; /** * Set to false to prevent the 'ADMIN' permission from implying all permissions in the system */ static $admin_implies_all = true; /** * Check that the current member has the given permission * * @param $code string|array Either a list of codes or a single code * @param $arg string * @param $memberID integer * @param $strict Boolean * @return Integer */ static function check($code, $arg = "any", $memberID = null, $strict = true) { if(!$memberID) { if(!Member::currentUser()) { return false; } $memberID = Member::currentUserID(); } return self::checkMember($memberID, $code, $arg); } /** * Check that the given member has the given permission * * @param memberID The ID of the member to check. Leave blank for the current member * @param $code string * @param $arg string * @param $strict Boolean * @return Integer The ID of the Permission record if the permission exists; null otherwise */ static function checkMember($memberID, $code, $arg = "any", $strict = true) { // Group component $perms_list = self::get_declared_permissions_list(); if(self::$declared_permissions && is_array($perms_list) && !in_array($code, $perms_list)) { // user_error("Permission '$code' has not been declared. Use Permission::declare_permissions() to add this permission", E_USER_WARNING); } $groupList = self::groupList($memberID); if($groupList) { $groupCSV = implode(", ", $groupList); // Arg component switch($arg) { case "any": $argClause = "";break; case "all": $argClause = " AND Arg = -1"; break; default: if(is_numeric($arg)) $argClause = "AND Arg IN (-1, $arg) "; else use_error("Permission::checkMember: bad arg '$arg'", E_USER_ERROR); } if(is_array($code)) $SQL_codeList = "'" . implode("', '", Convert::raw2sql($code)) . "'"; else $SQL_codeList = "'" . Convert::raw2sql($code) . "'"; if(!self::$strict_checking || !$strict) { $hasPermission = DB::query(" SELECT COUNT(*) FROM Permission WHERE Code IN ('$SQL_codeList') ")->value(); if(!$hasPermission) return true; } // Raw SQL for efficiency return DB::query("SELECT ID FROM Permission WHERE Code IN ($SQL_codeList, 'ADMIN') AND GroupID IN ($groupCSV) $argClause")->value(); } } /** * Get the list of groups that the given member belongs to. * Call without an argument to get the groups that the current member belongs to. In this case, the results will be session-cached */ static function groupList($memberID = null) { // Default to current member, with session-caching if(!$memberID) { $member = Member::currentUser(); if($member && isset($_SESSION['Permission_groupList'][$member->ID])) return $_SESSION['Permission_groupList'][$member->ID]; } else { $member = DataObject::get_by_id("Member", $memberID); } if($member) { // Build a list of the IDs of the groups. Most of the heavy lifting is done by Member::Groups // NOTE: This isn't effecient; but it's called once per session so it's a low priority to fix. $groups = $member->Groups(); $groupList = array(); if($groups) { foreach($groups as $group) $groupList[] = $group->ID; } // Session caching if(!$memberID) { $_SESSION['Permission_groupList'][$member->ID] = $groupList; } return isset($groupList) ? $groupList : null; } } /** * Grant the given permission code/arg to the given group * @returns The new permission object */ static function grant($groupID, $code, $arg = "any") { $perm = new Permission(); $perm->GroupID = $groupID; $perm->Code = $code; // Arg component switch($arg) { case "any": break; case "all": $perm->Arg = -1; default: if(is_numeric($arg)) $perm->Arg = $arg; else use_error("Permission::checkMember: bad arg '$arg'", E_USER_ERROR); } $perm->write(); return $perm; } function requireDefaultRecords() { parent::requireDefaultRecords(); // Add default content if blank if(!DB::query("SELECT ID FROM Permission")->value()) { $admins = DB::query("SELECT ID FROM `Group` WHERE CanCMSAdmin = 1")->column(); if(isset($admins)) { foreach($admins as $admin) Permission::grant($admin, "ADMIN"); } $authors = DB::query("SELECT ID FROM `Group` WHERE CanCMS = 1")->column(); if(isset($authors)) { foreach($authors as $author) { Permission::grant($author, "CMS_ACCESS_CMSMain"); Permission::grant($author, "CMS_ACCESS_AssetAdmin"); Permission::grant($author, "CMS_ACCESS_NewsletterAdmin"); Permission::grant($author, "CMS_ACCESS_ReportAdmin"); } } } } /** * Returns all members for a specific permission. * * @param $code String|array Either a single permission code, or a list of permission codes * @return DataObjectSet */ static function get_members_by_permission($code) { $groupIDs = array(); if(is_array($code)) $SQL_filter = "Permission.Code IN ('" . implode("','", Convert::raw2sql($code)) . "')"; else $SQL_filter = "Permission.Code = '" . Convert::raw2sql($code) . "'"; $toplevelGroups = DataObject::get( 'Group', $SQL_filter, // filter null, // limit "LEFT JOIN `Permission` ON `Group`.`ID` = `Permission`.`GroupID`" // join ); if(!$toplevelGroups) return false; foreach($toplevelGroups as $group) { $familyIDs = $group->collateFamilyIDs(); if(is_array($familyIDs)) { $groupIDs = array_merge($groupIDs, array_values($familyIDs)); } } if(!count($groupIDs)) return false; $members = DataObject::get( Object::getCustomClass('Member'), $_filter = "`Group`.ID IN (" . implode(",",$groupIDs) . ")", $_sort = "", $_join = "LEFT JOIN `Group_Members` ON `Member`.`ID` = `Group_Members`.`MemberID` " . "LEFT JOIN `Group` ON `Group_Members`.`GroupID` = `Group`.`ID` " ); return $members; } /** * Return all of the groups that have one of the given permission codes * @param $codes array|string Either a single permission code, or an array of permission codes * @return DataObjectSet The matching group objects */ static function get_groups_by_permission($codes) { if(!is_array($codes)) $codes = array($codes); $SQLa_codes = Convert::raw2sql($codes); $SQL_codes = join("','", $SQLa_codes); return DataObject::get( 'Group', "Permission.Code IN ('$SQL_codes')", "", "LEFT JOIN Permission ON Group.ID = Permission.GroupID" ); } static function get_codes($blankItemText = null) { $classes = ClassInfo::implementorsOf('PermissionProvider'); $allCodes = array(); if($blankItemText) $allCodes[''] = ($blankItemText === true) ? '(select)' : $blankItemText; $allCodes['ADMIN'] = 'Full administrative rights'; foreach($classes as $class) { $SNG = singleton($class); $someCodes = $SNG->providePermissions(); if($someCodes) foreach($someCodes as $k => $v) { $allCodes[$k] = $v; } } $otherPerms = DB::query("SELECT DISTINCT Code From Permission")->column(); foreach($otherPerms as $otherPerm) { if(!array_key_exists($otherPerm, $allCodes)) $allCodes[$otherPerm] = $otherPerm; } asort($allCodes); return $allCodes; } /* * Controller action to list the codes available */ function listcodes() { if(!Permission::check('ADMIN')) Security::permissionFailure(); echo "
"; print_r($codes); } /** * Declare an array of permissions for the system. Permissions can be grouped by nesting arrays * scalar values are always treated as permissions. * * @param $permArray A (possibly nested) array of permissions to declare for the system. */ static function declare_permissions($permArray) { if(is_array(self::$declared_permissions)) self::$declared_permissions = array_merge_recursive(self::$declared_permissions, $permArray); else self::$declared_permissions = $permArray; } /** * Get a linear list of the permissions in the system. */ static function get_declared_permissions_list() { if(!self::$declared_permissions) return null; if(self::$declared_permissions_list) return self::$declared_permissions_list; self::$declared_permissions_list = array(); self::traverse_declared_permissions(self::$declared_permissions, self::$declared_permissions_list); return self::$declared_permissions_list; } /** * Recursively traverse the nested list of declared permissions and create a linear list. * * @param $declared Nested structure of permissions. * @param $list List of permissions in the structure */ protected static function traverse_declared_permissions($declared, &$list) { if(!is_array($declared)) return; foreach($declared as $perm => $value) { if($value instanceof Permission_Group) { $list[] = $value->getName(); self::traverse_declared_permissions($value->getPermissions(), &$list); } else $list[$perm] = $value; } } } class Permission_Group { protected $name; protected $permissions = array(); function __construct($name, $permissions) { $this->name = $name; $this->permissions = $permissions; } function getName() { return $this->name; } function getPermissions() { return $this->permissions; } } ?>