diff --git a/libraries/joomla/access/access.php b/libraries/joomla/access/access.php index 8e25eb1af242f..d89ac207832ad 100644 --- a/libraries/joomla/access/access.php +++ b/libraries/joomla/access/access.php @@ -61,6 +61,14 @@ class JAccess */ protected static $assetPermissionsByName = array(); + /** + * Array of the permission parent ID mappings + * + * @var array + * @since 11.1 + */ + protected static $assetPermissionsParentIdMapping = array(); + /** * Array of asset types that have been preloaded * @@ -101,14 +109,6 @@ class JAccess */ protected static $groupsByUser = array(); - /** - * Flag to indicate if components assets have been preloaded. - * - * @var boolean - * @since __DEPLOY_VERSION__ - */ - protected static $componentsPreloaded = false; - /** * Method for clearing static caches. * @@ -118,16 +118,16 @@ class JAccess */ public static function clearStatics() { - self::$componentsPreloaded = false; - self::$viewLevels = array(); - self::$assetPermissionsById = array(); + self::$viewLevels = array(); + self::$assetPermissionsById = array(); self::$assetPermissionsByName = array(); - self::$preloadedAssetTypes = array(); - self::$identities = array(); - self::$assetRules = array(); - self::$userGroups = array(); - self::$userGroupPaths = array(); - self::$groupsByUser = array(); + self::$assetPermissionsParentIdMapping = array(); + self::$preloadedAssetTypes = array(); + self::$identities = array(); + self::$assetRules = array(); + self::$userGroups = array(); + self::$userGroupPaths = array(); + self::$groupsByUser = array(); } /** @@ -146,15 +146,16 @@ public static function check($userId, $action, $asset = null, $preload = true) { // Sanitise inputs. $userId = (int) $userId; + $action = strtolower(preg_replace('#[\s\-]+#', '.', trim($action))); - $asset = strtolower(preg_replace('#[\s\-]+#', '.', trim($asset))); + $asset = strtolower(preg_replace('#[\s\-]+#', '.', trim($asset))); // Default to the root asset node. if (empty($asset)) { $db = JFactory::getDbo(); $assets = JTable::getInstance('Asset', 'JTable', array('dbo' => $db)); - $asset = $assets->getRootId(); + $asset = $assets->getRootId(); } // Auto preloads assets for the asset type: @@ -165,6 +166,7 @@ public static function check($userId, $action, $asset = null, $preload = true) if (!isset(self::$preloadedAssetTypes[$assetType])) { self::preload($assetType); + self::$preloadedAssetTypes[$assetType] = true; } } @@ -196,26 +198,29 @@ public static function check($userId, $action, $asset = null, $preload = true) */ public static function preload($assetTypes = 'components', $reload = false) { + // Get instance of the Profiler: + $profiler = JProfiler::getInstance('Application'); + // Check for default case: - $isDefault = is_string($assetTypes) && in_array($assetTypes, array('components', 'component')); + $isDefault = (is_string($assetTypes) && in_array($assetTypes, array('components', 'component'))); // Preload the rules for all of the components: - if ($isDefault && !self::$componentsPreloaded) + if ($isDefault) { // Mark in the profiler. - !JDEBUG ?: JProfiler::getInstance('Application')->mark('Before JAccess::preload (all components)'); + JDEBUG ? $profiler->mark('Start JAccess::preload(components)') : null; - self::preloadComponents(); - self::$componentsPreloaded = true; + $components = self::preloadComponents(); + self::$preloadedAssetTypes = array_merge(self::$preloadedAssetTypes, array_flip($components)); // Mark in the profiler. - !JDEBUG ?: JProfiler::getInstance('Application')->mark('After JAccess::preload (all components)'); - } + JDEBUG ? $profiler->mark('Finish JAccess::preload(components)') : null; - // Quick short circuit for default case - if ($isDefault) - { - return true; + // Quick short circuit for default case: + if ($isDefault) + { + return true; + } } // If we get to this point, this is a regular asset type @@ -230,12 +235,17 @@ public static function preload($assetTypes = 'components', $reload = false) { if (!isset(self::$preloadedAssetTypes[$assetType]) || $reload) { - !JDEBUG ?: JProfiler::getInstance('Application')->mark('Before JAccess::preload (' . $assetType . ')'); + JDEBUG ? $profiler->mark('New JAccess Preloading Process(' . $assetType . ')') : null; + + self::preloadPermissionsParentIdMapping($assetType); + JDEBUG ? $profiler->mark('After preloadPermissionsParentIdMapping (' . $assetType . ')') : null; self::preloadPermissions($assetType); - self::$preloadedAssetTypes[$assetType] = true; + JDEBUG ? $profiler->mark('After preloadPermissions (' . $assetType . ')') : null; + + JDEBUG ? $profiler->mark('End New JAccess Preloading Process(' . $assetType . ')') : null; - !JDEBUG ?: JProfiler::getInstance('Application')->mark('After JAccess::preload (' . $assetType . ')'); + self::$preloadedAssetTypes[$assetType] = true; } } @@ -246,15 +256,18 @@ public static function preload($assetTypes = 'components', $reload = false) * Method to recursively retrieve the list of parent Asset IDs * for a particular Asset. * - * @param string $extensionName e.g. 'com_content.article' - * @param string|int $assetId numeric Asset ID + * @param string $assetType e.g. 'com_content.article' + * @param string|int $assetId numeric Asset ID * * @return array List of Ancestor IDs (includes original $assetId) * * @since 1.6 */ - protected static function getAssetAncestors($extensionName, $assetId) + protected static function getAssetAncestors($assetType, $assetId) { + // Get the extension name from the $assetType provided + $extensionName = self::getExtensionNameFromAsset($assetType); + // Holds the list of ancestors for the Asset ID: $ancestors = array(); @@ -266,9 +279,9 @@ protected static function getAssetAncestors($extensionName, $assetId) while ($id !== 0) { - if (isset(self::$assetPermissionsById[$extensionName][$id])) + if (isset(self::$assetPermissionsParentIdMapping[$extensionName][$id])) { - $id = (int) self::$assetPermissionsById[$extensionName][$id]->parent_id; + $id = (int) self::$assetPermissionsParentIdMapping[$extensionName][$id]->parent_id; if ($id !== 0) { @@ -286,6 +299,44 @@ protected static function getAssetAncestors($extensionName, $assetId) return $ancestors; } + /** + * Method to retrieve the list of Asset IDs and their Parent Asset IDs + * and store them for later usage in getAssetRules(). + * + * @param string $assetType e.g. 'com_content.article' + * + * @return array List of Asset IDs (includes Parent Asset ID Info) + * + * @since 1.6 + */ + protected static function &preloadPermissionsParentIdMapping($assetType) + { + // Get the extension name from the $assetType provided + $extensionName = self::getExtensionNameFromAsset($assetType); + + if (!isset(self::$assetPermissionsParentIdMapping[$extensionName])) + { + // Get the database connection object. + $db = JFactory::getDbo(); + + // Get a fresh query object: + $query = $db->getQuery(true); + + // Build the database query: + $query->select('a.id, a.parent_id'); + $query->from('#__assets AS a'); + $query->where('(a.name LIKE ' . $db->quote($extensionName . '.%') . ' OR a.name = ' . $db->quote($extensionName) . ' OR a.id = 1)'); + + // Get the Name Permission Map List + $db->setQuery($query); + $parentIdMapping = $db->loadObjectList('id'); + + self::$assetPermissionsParentIdMapping[$extensionName] = &$parentIdMapping; + } + + return self::$assetPermissionsParentIdMapping[$extensionName]; + } + /** * Method to retrieve the Asset Rule strings for this particular * Asset Type and stores them for later usage in getAssetRules(). @@ -303,27 +354,32 @@ protected static function preloadPermissions($assetType) // Get the extension name from the $assetType provided $extensionName = self::getExtensionNameFromAsset($assetType); - // Get the database connection object. - $db = JFactory::getDbo(); + if (!isset(self::$assetPermissionsById[$extensionName]) && !isset(self::$assetPermissionsByName[$extensionName])) + { + // Get the database connection object. + $db = JFactory::getDbo(); - $parents = implode(',', $db->q(array($extensionName, 'root.1'))); + // Get a fresh query object: + $query = $db->getQuery(true); - // Get a fresh query object: - $query = $db->getQuery(true) - ->select($db->qn(array('id', 'name', 'rules', 'parent_id'))) - ->from($db->qn('#__assets')) - ->where($db->qn('name') . ' LIKE ' . $db->q($extensionName . '.%') . ' OR ' . $db->qn('name') . ' IN (' . $parents . ')'); + // Build the database query: + $query->select('a.id, a.name, a.rules'); + $query->from('#__assets AS a'); + $query->where('(a.name LIKE ' . $db->quote($extensionName . '.%') . ' OR a.name = ' . $db->quote($extensionName) . ' OR a.id = 1 )'); - // Get the Name Permission Map List - $assets = $db->setQuery($query)->loadObjectList(); + // Get the Name Permission Map List + $db->setQuery($query); - self::$assetPermissionsById[$extensionName] = array(); - self::$assetPermissionsByName[$extensionName] = array(); + $iterator = $db->getIterator(); - foreach ($assets as $asset) - { - self::$assetPermissionsById[$extensionName][$asset->id] = $asset; - self::$assetPermissionsByName[$extensionName][$asset->name] = $asset; + self::$assetPermissionsById[$extensionName] = array(); + self::$assetPermissionsByName[$extensionName] = array(); + + foreach ($iterator as $row) + { + self::$assetPermissionsById[$extensionName][$row->id] = $row; + self::$assetPermissionsByName[$extensionName][$row->name] = $row; + } } return true; @@ -342,61 +398,79 @@ protected static function preloadPermissions($assetType) */ protected static function preloadComponents() { - // Add root to asset names list. - $components = array(); + // Get the database connection object. + $db = JFactory::getDbo(); + + // Build the database query: + $query = $db->getQuery(true); + $query->select('element'); + $query->from('#__extensions'); + $query->where('type = ' . $db->quote('component')); + $query->where('enabled = ' . $db->quote(1)); - // Add enabled components to asset names list. - foreach (JComponentHelper::getComponents() as $component) + // Set the query and get the list of active components: + $db->setQuery($query); + $components = $db->loadColumn(); + + // Get a fresh query object: + $query = $db->getQuery(true); + + // Build the in clause for the queries: + $inClause = ''; + $last = end($components); + + foreach ($components as $component) { - if ($component->enabled) + if ($component === $last) + { + $inClause .= $db->quote($component); + } + else { - $components[] = $component->option; + $inClause .= $db->quote($component) . ','; } } - // Get the database connection object. - $db = JFactory::getDbo(); - - // Get the asset info for all assets in asset names list. - $query = $db->getQuery(true) - ->select($db->qn(array('id', 'name', 'rules', 'parent_id'))) - ->from($db->qn('#__assets')) - ->where($db->qn('name') . ' IN (' . implode(',', $db->quote($components)) . ', ' . $db->quote('root.1') . ')'); + // Build the database query: + $query->select('a.name, a.rules'); + $query->from('#__assets AS a'); + $query->where('(a.name IN (' . $inClause . ') OR a.name = ' . $db->quote('root.1') . ')'); // Get the Name Permission Map List - $assets = $db->setQuery($query)->loadObjectList('name'); + $db->setQuery($query); + $namePermissionMap = $db->loadAssocList('name'); - // Add the root asset as parent of all components. - $assetsTree = array(); - $rootName = 'root.1'; + $root = array(); + $root['rules'] = ''; - foreach ($assets as $extensionName => $asset) + if (isset($namePermissionMap['root.1'])) { - $assetsTree[$extensionName][] = $assets[$rootName]; - - if ($extensionName !== $rootName) - { - $assetsTree[$extensionName][] = $assets[$extensionName]; - } + $root = $namePermissionMap['root.1']; + unset($namePermissionMap['root.1']); } - // Save the permissions for the components asset. - foreach ($assetsTree as $extensionName => $assets) - { - if (!isset(self::$preloadedAssetTypes[$extensionName])) - { - self::$assetPermissionsById[$extensionName] = array(); - self::$assetPermissionsByName[$extensionName] = array(); + // Container for all of the JAccessRules for this $assetType + $rulesList = array(); - foreach ($assets as $asset) - { - self::$assetPermissionsById[$extensionName][$asset->id] = $asset; - self::$assetPermissionsByName[$extensionName][$asset->name] = $asset; - } + // Collects permissions for each $assetName and adds + // into the $assetRules class variable. + foreach ($namePermissionMap as $assetName => &$permissions) + { + // Instantiate and return the JAccessRules object for the asset rules. + $rules = new JAccessRules; + $rules->mergeCollection(array($root['rules'], $permissions['rules'])); - self::$preloadedAssetTypes[$extensionName] = true; - } + $rulesList[$assetName] = $rules; } + + unset($assetName); + unset($permissions); + + // Merge our rules list with self::$assetRules + self::$assetRules = self::$assetRules + $rulesList; + unset($rulesList); + + return $components; } /** @@ -475,33 +549,23 @@ protected static function getGroupPath($groupId) */ public static function getAssetRules($asset, $recursive = false, $recursiveParentAsset = true) { - $method = ''; - // Get instance of the Profiler: - $extensionName = self::getExtensionNameFromAsset($asset); - $assetType = self::getAssetType($asset); + $profiler = JProfiler::getInstance('Application'); - // Make sure the components assets are preloaded. - if (!self::$componentsPreloaded) - { - self::preload('components'); - } - - !JDEBUG ?: JProfiler::getInstance('Application')->mark('Before JAccess::getAssetRules (' . $asset . ')'); + $extensionName = self::getExtensionNameFromAsset($asset); - // Almost all calls should have recursive set to true so we'll get to take advantage of preloading. - if ($recursive && $recursiveParentAsset && (isset(self::$preloadedAssetTypes[$assetType]) || isset(self::$preloadedAssetTypes[$extensionName]))) + // Almost all calls should have recursive set to true + // so we'll get to take advantage of preloading: + if ($recursive && $recursiveParentAsset && isset(self::$assetPermissionsByName[$extensionName]) + && isset(self::$assetPermissionsByName[$extensionName][$asset])) { - // The asset type (ex: com_modules.module) as been preloaded, but the asset does not exist (ex: com_modules.module.37). - // In this case we fallback to extension name asset. - if (!isset(self::$assetPermissionsByName[$extensionName][$asset])) - { - self::$assetPermissionsByName[$extensionName][$asset] = self::$assetPermissionsByName[$extensionName][$extensionName]; - } + // Mark in the profiler. + JDEBUG ? $profiler->mark('Start JAccess::getAssetRules New (' . $asset . ')') : null; + $assetType = self::getAssetType($asset); $assetId = self::$assetPermissionsByName[$extensionName][$asset]->id; - $ancestors = array_reverse(self::getAssetAncestors($extensionName, $assetId)); + $ancestors = array_reverse(self::getAssetAncestors($assetType, $assetId)); // Collects permissions for each $asset $collected = array(); @@ -521,17 +585,21 @@ public static function getAssetRules($asset, $recursive = false, $recursiveParen if (!isset(self::$assetRulesIdentities[$hash])) { - $rules = new JAccessRules; + $rules = new JAccessRules; $rules->mergeCollection($collected); self::$assetRulesIdentities[$hash] = $rules; } - $rules = self::$assetRulesIdentities[$hash]; + // Mark in the profiler. + JDEBUG ? $profiler->mark('Finish JAccess::getAssetRules New (' . $asset . ')') : null; + + return self::$assetRulesIdentities[$hash]; } else { - $method = ' Slower, no preloading, method used.'; + // Mark in the profiler. + JDEBUG ? $profiler->mark('Start JAccess::getAssetRules Old (' . $asset . ')') : null; if ($asset === "1") { @@ -545,7 +613,7 @@ public static function getAssetRules($asset, $recursive = false, $recursiveParen // Build the database query to get the rules for the asset. $query = $db->getQuery(true) - ->select($recursive ? 'DISTINCT(b.rules)' : 'a.rules') + ->select($recursive ? 'b.rules' : 'a.rules') ->from('#__assets AS a'); $extensionString = ''; @@ -601,11 +669,11 @@ public static function getAssetRules($asset, $recursive = false, $recursiveParen // Instantiate and return the JAccessRules object for the asset rules. $rules = new JAccessRules; $rules->mergeCollection($result); - } - !JDEBUG ?: JProfiler::getInstance('Application')->mark('After JAccess::getAssetRules (' . $asset . ')' . $method); + JDEBUG ? $profiler->mark('Finish JAccess::getAssetRules Old (' . $asset . ')') : null; - return $rules; + return $rules; + } } /**