[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Access/ -> Access.php (source)

   1  <?php
   2  
   3  /**
   4   * Joomla! Content Management System
   5   *
   6   * @copyright  (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
   7   * @license    GNU General Public License version 2 or later; see LICENSE.txt
   8   */
   9  
  10  namespace Joomla\CMS\Access;
  11  
  12  use Joomla\CMS\Component\ComponentHelper;
  13  use Joomla\CMS\Factory;
  14  use Joomla\CMS\Helper\UserGroupsHelper;
  15  use Joomla\CMS\Log\Log;
  16  use Joomla\CMS\Profiler\Profiler;
  17  use Joomla\CMS\Table\Asset;
  18  use Joomla\CMS\User\User;
  19  use Joomla\Database\ParameterType;
  20  use Joomla\Utilities\ArrayHelper;
  21  
  22  // phpcs:disable PSR1.Files.SideEffects
  23  \defined('JPATH_PLATFORM') or die;
  24  // phpcs:enable PSR1.Files.SideEffects
  25  
  26  /**
  27   * Class that handles all access authorisation routines.
  28   *
  29   * @since  1.7.0
  30   */
  31  class Access
  32  {
  33      /**
  34       * Array of view levels
  35       *
  36       * @var    array
  37       * @since  1.7.0
  38       */
  39      protected static $viewLevels = array();
  40  
  41      /**
  42       * Array of rules for the asset
  43       *
  44       * @var    array
  45       * @since  1.7.0
  46       */
  47      protected static $assetRules = array();
  48  
  49      /**
  50       * Array of identities for asset rules
  51       *
  52       * @var    array
  53       * @since  1.7.0
  54       */
  55      protected static $assetRulesIdentities = array();
  56  
  57      /**
  58       * Array of the permission parent ID mappings
  59       *
  60       * @var    array
  61       * @since  1.7.0
  62       */
  63      protected static $assetPermissionsParentIdMapping = array();
  64  
  65      /**
  66       * Array of asset types that have been preloaded
  67       *
  68       * @var    array
  69       * @since  1.7.0
  70       */
  71      protected static $preloadedAssetTypes = array();
  72  
  73      /**
  74       * Array of loaded user identities
  75       *
  76       * @var    array
  77       * @since  1.7.0
  78       */
  79      protected static $identities = array();
  80  
  81      /**
  82       * Array of user groups.
  83       *
  84       * @var    array
  85       * @since  1.7.0
  86       */
  87      protected static $userGroups = array();
  88  
  89      /**
  90       * Array of user group paths.
  91       *
  92       * @var    array
  93       * @since  1.7.0
  94       */
  95      protected static $userGroupPaths = array();
  96  
  97      /**
  98       * Array of cached groups by user.
  99       *
 100       * @var    array
 101       * @since  1.7.0
 102       */
 103      protected static $groupsByUser = array();
 104  
 105      /**
 106       * Array of preloaded asset names and ids (key is the asset id).
 107       *
 108       * @var    array
 109       * @since  3.7.0
 110       */
 111      protected static $preloadedAssets = array();
 112  
 113      /**
 114       * The root asset id.
 115       *
 116       * @var    integer
 117       * @since  3.7.0
 118       */
 119      protected static $rootAssetId = null;
 120  
 121      /**
 122       * Method for clearing static caches.
 123       *
 124       * @return  void
 125       *
 126       * @since   1.7.3
 127       */
 128      public static function clearStatics()
 129      {
 130          self::$viewLevels                      = array();
 131          self::$assetRules                      = array();
 132          self::$assetRulesIdentities            = array();
 133          self::$assetPermissionsParentIdMapping = array();
 134          self::$preloadedAssetTypes             = array();
 135          self::$identities                      = array();
 136          self::$userGroups                      = array();
 137          self::$userGroupPaths                  = array();
 138          self::$groupsByUser                    = array();
 139          self::$preloadedAssets                 = array();
 140          self::$rootAssetId                     = null;
 141      }
 142  
 143      /**
 144       * Method to check if a user is authorised to perform an action, optionally on an asset.
 145       *
 146       * @param   integer         $userId    Id of the user for which to check authorisation.
 147       * @param   string          $action    The name of the action to authorise.
 148       * @param   integer|string  $assetKey  The asset key (asset id or asset name). null fallback to root asset.
 149       * @param   boolean         $preload   Indicates whether preloading should be used.
 150       *
 151       * @return  boolean|null  True if allowed, false for an explicit deny, null for an implicit deny.
 152       *
 153       * @since   1.7.0
 154       */
 155      public static function check($userId, $action, $assetKey = null, $preload = true)
 156      {
 157          // Sanitise inputs.
 158          $userId = (int) $userId;
 159          $action = strtolower(preg_replace('#[\s\-]+#', '.', trim($action)));
 160  
 161          if (!isset(self::$identities[$userId])) {
 162              // Get all groups against which the user is mapped.
 163              self::$identities[$userId] = self::getGroupsByUser($userId);
 164              array_unshift(self::$identities[$userId], $userId * -1);
 165          }
 166  
 167          return self::getAssetRules($assetKey, true, true, $preload)->allow($action, self::$identities[$userId]);
 168      }
 169  
 170      /**
 171       * Method to preload the Rules object for the given asset type.
 172       *
 173       * @param   integer|string|array  $assetTypes  The type or name of the asset (e.g. 'com_content.article', 'com_menus.menu.2').
 174       *                                             Also accepts the asset id. An array of asset type or a special
 175       *                                             'components' string to load all component assets.
 176       * @param   boolean               $reload      Set to true to reload from database.
 177       *
 178       * @return  boolean  True on success.
 179       *
 180       * @since   1.6
 181       * @note    This method will return void in 4.0.
 182       */
 183      public static function preload($assetTypes = 'components', $reload = false)
 184      {
 185          // If sent an asset id, we first get the asset type for that asset id.
 186          if (is_numeric($assetTypes)) {
 187              $assetTypes = self::getAssetType($assetTypes);
 188          }
 189  
 190          // Check for default case:
 191          $isDefault = \is_string($assetTypes) && \in_array($assetTypes, array('components', 'component'));
 192  
 193          // Preload the rules for all of the components.
 194          if ($isDefault) {
 195              self::preloadComponents();
 196  
 197              return true;
 198          }
 199  
 200          // If we get to this point, this is a regular asset type and we'll proceed with the preloading process.
 201          if (!\is_array($assetTypes)) {
 202              $assetTypes = (array) $assetTypes;
 203          }
 204  
 205          foreach ($assetTypes as $assetType) {
 206              self::preloadPermissions($assetType, $reload);
 207          }
 208  
 209          return true;
 210      }
 211  
 212      /**
 213       * Method to recursively retrieve the list of parent Asset IDs
 214       * for a particular Asset.
 215       *
 216       * @param   string   $assetType  The asset type, or the asset name, or the extension of the asset
 217       *                               (e.g. 'com_content.article', 'com_menus.menu.2', 'com_contact').
 218       * @param   integer  $assetId    The numeric asset id.
 219       *
 220       * @return  array  List of ancestor ids (includes original $assetId).
 221       *
 222       * @since   1.6
 223       */
 224      protected static function getAssetAncestors($assetType, $assetId)
 225      {
 226          // Get the extension name from the $assetType provided
 227          $extensionName = self::getExtensionNameFromAsset($assetType);
 228  
 229          // Holds the list of ancestors for the Asset ID:
 230          $ancestors = array();
 231  
 232          // Add in our starting Asset ID:
 233          $ancestors[] = (int) $assetId;
 234  
 235          // Initialize the variable we'll use in the loop:
 236          $id = (int) $assetId;
 237  
 238          while ($id !== 0) {
 239              if (isset(self::$assetPermissionsParentIdMapping[$extensionName][$id])) {
 240                  $id = (int) self::$assetPermissionsParentIdMapping[$extensionName][$id]->parent_id;
 241  
 242                  if ($id !== 0) {
 243                      $ancestors[] = $id;
 244                  }
 245              } else {
 246                  // Add additional case to break out of the while loop automatically in
 247                  // the case that the ID is non-existent in our mapping variable above.
 248                  break;
 249              }
 250          }
 251  
 252          return $ancestors;
 253      }
 254  
 255      /**
 256       * Method to retrieve the Asset Rule strings for this particular
 257       * Asset Type and stores them for later usage in getAssetRules().
 258       * Stores 2 arrays: one where the list has the Asset ID as the key
 259       * and a second one where the Asset Name is the key.
 260       *
 261       * @param   string   $assetType  The asset type, or the asset name, or the extension of the asset
 262       *                               (e.g. 'com_content.article', 'com_menus.menu.2', 'com_contact').
 263       * @param   boolean  $reload     Reload the preloaded assets.
 264       *
 265       * @return  void
 266       *
 267       * @since   1.6
 268       */
 269      protected static function preloadPermissions($assetType, $reload = false)
 270      {
 271          // Get the extension name from the $assetType provided
 272          $extensionName = self::getExtensionNameFromAsset($assetType);
 273  
 274          // If asset is a component, make sure that all the component assets are preloaded.
 275          if ((isset(self::$preloadedAssetTypes[$extensionName]) || isset(self::$preloadedAssetTypes[$assetType])) && !$reload) {
 276              return;
 277          }
 278  
 279          !JDEBUG ?: Profiler::getInstance('Application')->mark('Before Access::preloadPermissions (' . $extensionName . ')');
 280  
 281          // Get the database connection object.
 282          $db       = Factory::getDbo();
 283          $assetKey = $extensionName . '.%';
 284  
 285          // Get a fresh query object.
 286          $query = $db->getQuery(true)
 287              ->select($db->quoteName(array('id', 'name', 'rules', 'parent_id')))
 288              ->from($db->quoteName('#__assets'))
 289              ->where(
 290                  [
 291                      $db->quoteName('name') . ' LIKE :asset',
 292                      $db->quoteName('name') . ' = :extension',
 293                      $db->quoteName('parent_id') . ' = 0',
 294                  ],
 295                  'OR'
 296              )
 297              ->bind(':extension', $extensionName)
 298              ->bind(':asset', $assetKey);
 299  
 300          // Get the permission map for all assets in the asset extension.
 301          $assets = $db->setQuery($query)->loadObjectList();
 302  
 303          self::$assetPermissionsParentIdMapping[$extensionName] = array();
 304  
 305          foreach ($assets as $asset) {
 306              self::$assetPermissionsParentIdMapping[$extensionName][$asset->id] = $asset;
 307              self::$preloadedAssets[$asset->id]                                 = $asset->name;
 308          }
 309  
 310          // Mark asset type and it's extension name as preloaded.
 311          self::$preloadedAssetTypes[$assetType]     = true;
 312          self::$preloadedAssetTypes[$extensionName] = true;
 313  
 314          !JDEBUG ?: Profiler::getInstance('Application')->mark('After Access::preloadPermissions (' . $extensionName . ')');
 315      }
 316  
 317      /**
 318       * Method to preload the Rules objects for all components.
 319       *
 320       * Note: This will only get the base permissions for the component.
 321       * e.g. it will get 'com_content', but not 'com_content.article.1' or
 322       * any more specific asset type rules.
 323       *
 324       * @return   array  Array of component names that were preloaded.
 325       *
 326       * @since    1.6
 327       */
 328      protected static function preloadComponents()
 329      {
 330          // If the components already been preloaded do nothing.
 331          if (isset(self::$preloadedAssetTypes['components'])) {
 332              return array();
 333          }
 334  
 335          !JDEBUG ?: Profiler::getInstance('Application')->mark('Before Access::preloadComponents (all components)');
 336  
 337          // Add root to asset names list.
 338          $components = array('root.1');
 339  
 340          // Add enabled components to asset names list.
 341          foreach (ComponentHelper::getComponents() as $component) {
 342              if ($component->enabled) {
 343                  $components[] = $component->option;
 344              }
 345          }
 346  
 347          // Get the database connection object.
 348          $db = Factory::getDbo();
 349  
 350          // Get the asset info for all assets in asset names list.
 351          $query = $db->getQuery(true)
 352              ->select($db->quoteName(array('id', 'name', 'rules', 'parent_id')))
 353              ->from($db->quoteName('#__assets'))
 354              ->whereIn($db->quoteName('name'), $components, ParameterType::STRING);
 355  
 356          // Get the Name Permission Map List
 357          $assets = $db->setQuery($query)->loadObjectList();
 358  
 359          $rootAsset = null;
 360  
 361          // First add the root asset and save it to preload memory and mark it as preloaded.
 362          foreach ($assets as &$asset) {
 363              if ((int) $asset->parent_id === 0) {
 364                  $rootAsset                                                       = $asset;
 365                  self::$rootAssetId                                               = $asset->id;
 366                  self::$preloadedAssetTypes[$asset->name]                         = true;
 367                  self::$preloadedAssets[$asset->id]                               = $asset->name;
 368                  self::$assetPermissionsParentIdMapping[$asset->name][$asset->id] = $asset;
 369  
 370                  unset($asset);
 371                  break;
 372              }
 373          }
 374  
 375          // Now create save the components asset tree to preload memory.
 376          foreach ($assets as $asset) {
 377              if (!isset(self::$assetPermissionsParentIdMapping[$asset->name])) {
 378                  self::$assetPermissionsParentIdMapping[$asset->name] = array($rootAsset->id => $rootAsset, $asset->id => $asset);
 379                  self::$preloadedAssets[$asset->id]                   = $asset->name;
 380              }
 381          }
 382  
 383          // Mark all components asset type as preloaded.
 384          self::$preloadedAssetTypes['components'] = true;
 385  
 386          !JDEBUG ?: Profiler::getInstance('Application')->mark('After Access::preloadComponents (all components)');
 387  
 388          return $components;
 389      }
 390  
 391      /**
 392       * Method to check if a group is authorised to perform an action, optionally on an asset.
 393       *
 394       * @param   integer         $groupId   The path to the group for which to check authorisation.
 395       * @param   string          $action    The name of the action to authorise.
 396       * @param   integer|string  $assetKey  The asset key (asset id or asset name). null fallback to root asset.
 397       * @param   boolean         $preload   Indicates whether preloading should be used.
 398       *
 399       * @return  boolean  True if authorised.
 400       *
 401       * @since   1.7.0
 402       */
 403      public static function checkGroup($groupId, $action, $assetKey = null, $preload = true)
 404      {
 405          // Sanitize input.
 406          $groupId = (int) $groupId;
 407          $action  = strtolower(preg_replace('#[\s\-]+#', '.', trim($action)));
 408  
 409          return self::getAssetRules($assetKey, true, true, $preload)->allow($action, self::getGroupPath($groupId));
 410      }
 411  
 412      /**
 413       * Gets the parent groups that a leaf group belongs to in its branch back to the root of the tree
 414       * (including the leaf group id).
 415       *
 416       * @param   mixed  $groupId  An integer or array of integers representing the identities to check.
 417       *
 418       * @return  mixed  True if allowed, false for an explicit deny, null for an implicit deny.
 419       *
 420       * @since   1.7.0
 421       */
 422      protected static function getGroupPath($groupId)
 423      {
 424          // Load all the groups to improve performance on intensive groups checks
 425          $groups = UserGroupsHelper::getInstance()->getAll();
 426  
 427          if (!isset($groups[$groupId])) {
 428              return array();
 429          }
 430  
 431          return $groups[$groupId]->path;
 432      }
 433  
 434      /**
 435       * Method to return the Rules object for an asset. The returned object can optionally hold
 436       * only the rules explicitly set for the asset or the summation of all inherited rules from
 437       * parent assets and explicit rules.
 438       *
 439       * @param   integer|string  $assetKey              The asset key (asset id or asset name). null fallback to root asset.
 440       * @param   boolean         $recursive             True to return the rules object with inherited rules.
 441       * @param   boolean         $recursiveParentAsset  True to calculate the rule also based on inherited component/extension rules.
 442       * @param   boolean         $preload               Indicates whether preloading should be used.
 443       *
 444       * @return  Rules  Rules object for the asset.
 445       *
 446       * @since   1.7.0
 447       * @note    The non preloading code will be removed in 4.0. All asset rules should use asset preloading.
 448       */
 449      public static function getAssetRules($assetKey, $recursive = false, $recursiveParentAsset = true, $preload = true)
 450      {
 451          // Auto preloads the components assets and root asset (if chosen).
 452          if ($preload) {
 453              self::preload('components');
 454          }
 455  
 456          // When asset key is null fallback to root asset.
 457          $assetKey = self::cleanAssetKey($assetKey);
 458  
 459          // Auto preloads assets for the asset type (if chosen).
 460          if ($preload) {
 461              self::preload(self::getAssetType($assetKey));
 462          }
 463  
 464          // Get the asset id and name.
 465          $assetId = self::getAssetId($assetKey);
 466  
 467          // If asset rules already cached em memory return it (only in full recursive mode).
 468          if ($recursive && $recursiveParentAsset && $assetId && isset(self::$assetRules[$assetId])) {
 469              return self::$assetRules[$assetId];
 470          }
 471  
 472          // Get the asset name and the extension name.
 473          $assetName     = self::getAssetName($assetKey);
 474          $extensionName = self::getExtensionNameFromAsset($assetName);
 475  
 476          // If asset id does not exist fallback to extension asset, then root asset.
 477          if (!$assetId) {
 478              if ($extensionName && $assetName !== $extensionName) {
 479                  Log::add('No asset found for ' . $assetName . ', falling back to ' . $extensionName, Log::WARNING, 'assets');
 480  
 481                  return self::getAssetRules($extensionName, $recursive, $recursiveParentAsset, $preload);
 482              }
 483  
 484              if (self::$rootAssetId !== null && $assetName !== self::$preloadedAssets[self::$rootAssetId]) {
 485                  Log::add('No asset found for ' . $assetName . ', falling back to ' . self::$preloadedAssets[self::$rootAssetId], Log::WARNING, 'assets');
 486  
 487                  return self::getAssetRules(self::$preloadedAssets[self::$rootAssetId], $recursive, $recursiveParentAsset, $preload);
 488              }
 489          }
 490  
 491          // Almost all calls can take advantage of preloading.
 492          if ($assetId && isset(self::$preloadedAssets[$assetId])) {
 493              !JDEBUG ?: Profiler::getInstance('Application')->mark('Before Access::getAssetRules (id:' . $assetId . ' name:' . $assetName . ')');
 494  
 495              // Collects permissions for each asset
 496              $collected = array();
 497  
 498              // If not in any recursive mode. We only want the asset rules.
 499              if (!$recursive && !$recursiveParentAsset) {
 500                  $collected = array(self::$assetPermissionsParentIdMapping[$extensionName][$assetId]->rules);
 501              } else {
 502                  // If there is any type of recursive mode.
 503                  $ancestors = array_reverse(self::getAssetAncestors($extensionName, $assetId));
 504  
 505                  foreach ($ancestors as $id) {
 506                      // There are no rules for this ancestor
 507                      if (!isset(self::$assetPermissionsParentIdMapping[$extensionName][$id])) {
 508                          continue;
 509                      }
 510  
 511                      // If full recursive mode, but not recursive parent mode, do not add the extension asset rules.
 512                      if ($recursive && !$recursiveParentAsset && self::$assetPermissionsParentIdMapping[$extensionName][$id]->name === $extensionName) {
 513                          continue;
 514                      }
 515  
 516                      // If not full recursive mode, but recursive parent mode, do not add other recursion rules.
 517                      if (
 518                          !$recursive && $recursiveParentAsset && self::$assetPermissionsParentIdMapping[$extensionName][$id]->name !== $extensionName
 519                          && (int) self::$assetPermissionsParentIdMapping[$extensionName][$id]->id !== $assetId
 520                      ) {
 521                          continue;
 522                      }
 523  
 524                      // If empty asset to not add to rules.
 525                      if (self::$assetPermissionsParentIdMapping[$extensionName][$id]->rules === '{}') {
 526                          continue;
 527                      }
 528  
 529                      $collected[] = self::$assetPermissionsParentIdMapping[$extensionName][$id]->rules;
 530                  }
 531              }
 532  
 533              /**
 534              * Hashing the collected rules allows us to store
 535              * only one instance of the Rules object for
 536              * Assets that have the same exact permissions...
 537              * it's a great way to save some memory.
 538              */
 539              $hash = md5(implode(',', $collected));
 540  
 541              if (!isset(self::$assetRulesIdentities[$hash])) {
 542                  $rules = new Rules();
 543                  $rules->mergeCollection($collected);
 544  
 545                  self::$assetRulesIdentities[$hash] = $rules;
 546              }
 547  
 548              // Save asset rules to memory cache(only in full recursive mode).
 549              if ($recursive && $recursiveParentAsset) {
 550                  self::$assetRules[$assetId] = self::$assetRulesIdentities[$hash];
 551              }
 552  
 553              !JDEBUG ?: Profiler::getInstance('Application')->mark('After Access::getAssetRules (id:' . $assetId . ' name:' . $assetName . ')');
 554  
 555              return self::$assetRulesIdentities[$hash];
 556          }
 557  
 558          // Non preloading code. Use old slower method, slower. Only used in rare cases (if any) or without preloading chosen.
 559          Log::add('Asset ' . $assetKey . ' permissions fetch without preloading (slower method).', Log::INFO, 'assets');
 560  
 561          !JDEBUG ?: Profiler::getInstance('Application')->mark('Before Access::getAssetRules (assetKey:' . $assetKey . ')');
 562  
 563          // There's no need to process it with the recursive method for the Root Asset ID.
 564          if ((int) $assetKey === 1) {
 565              $recursive = false;
 566          }
 567  
 568          // Get the database connection object.
 569          $db = Factory::getDbo();
 570  
 571          // Build the database query to get the rules for the asset.
 572          $query = $db->getQuery(true)
 573              ->select($db->quoteName($recursive ? 'b.rules' : 'a.rules', 'rules'))
 574              ->from($db->quoteName('#__assets', 'a'));
 575  
 576          // If the asset identifier is numeric assume it is a primary key, else lookup by name.
 577          if (is_numeric($assetKey)) {
 578              $query->where($db->quoteName('a.id') . ' = :asset', 'OR')
 579                  ->bind(':asset', $assetKey, ParameterType::INTEGER);
 580          } else {
 581              $query->where($db->quoteName('a.name') . ' = :asset', 'OR')
 582                  ->bind(':asset', $assetKey);
 583          }
 584  
 585          if ($recursiveParentAsset && ($extensionName !== $assetKey || is_numeric($assetKey))) {
 586              $query->where($db->quoteName('a.name') . ' = :extension')
 587                  ->bind(':extension', $extensionName);
 588          }
 589  
 590          // If we want the rules cascading up to the global asset node we need a self-join.
 591          if ($recursive) {
 592              $query->where($db->quoteName('a.parent_id') . ' = 0')
 593                  ->join(
 594                      'LEFT',
 595                      $db->quoteName('#__assets', 'b'),
 596                      $db->quoteName('b.lft') . ' <= ' . $db->quoteName('a.lft') . ' AND ' . $db->quoteName('b.rgt') . ' >= ' . $db->quoteName('a.rgt')
 597                  )
 598                  ->order($db->quoteName('b.lft'));
 599          }
 600  
 601          // Execute the query and load the rules from the result.
 602          $result = $db->setQuery($query)->loadColumn();
 603  
 604          // Get the root even if the asset is not found and in recursive mode
 605          if (empty($result)) {
 606              $rootId = (new Asset($db))->getRootId();
 607  
 608              $query->clear()
 609                  ->select($db->quoteName('rules'))
 610                  ->from($db->quoteName('#__assets'))
 611                  ->where($db->quoteName('id') . ' = :rootId')
 612                  ->bind(':rootId', $rootId, ParameterType::INTEGER);
 613  
 614              $result = $db->setQuery($query)->loadColumn();
 615          }
 616  
 617          // Instantiate and return the Rules object for the asset rules.
 618          $rules = new Rules();
 619          $rules->mergeCollection($result);
 620  
 621          !JDEBUG ?: Profiler::getInstance('Application')->mark('Before Access::getAssetRules <strong>Slower</strong> (assetKey:' . $assetKey . ')');
 622  
 623          return $rules;
 624      }
 625  
 626      /**
 627       * Method to clean the asset key to make sure we always have something.
 628       *
 629       * @param   integer|string  $assetKey  The asset key (asset id or asset name). null fallback to root asset.
 630       *
 631       * @return  integer|string  Asset id or asset name.
 632       *
 633       * @since   3.7.0
 634       */
 635      protected static function cleanAssetKey($assetKey = null)
 636      {
 637          // If it's a valid asset key, clean it and return it.
 638          if ($assetKey) {
 639              return strtolower(preg_replace('#[\s\-]+#', '.', trim($assetKey)));
 640          }
 641  
 642          // Return root asset id if already preloaded.
 643          if (self::$rootAssetId !== null) {
 644              return self::$rootAssetId;
 645          }
 646  
 647          // No preload. Return root asset id from Assets.
 648          $assets = new Asset(Factory::getDbo());
 649  
 650          return $assets->getRootId();
 651      }
 652  
 653      /**
 654       * Method to get the asset id from the asset key.
 655       *
 656       * @param   integer|string  $assetKey  The asset key (asset id or asset name).
 657       *
 658       * @return  integer  The asset id.
 659       *
 660       * @since   3.7.0
 661       */
 662      protected static function getAssetId($assetKey)
 663      {
 664          static $loaded = array();
 665  
 666          // If the asset is already an id return it.
 667          if (is_numeric($assetKey)) {
 668              return (int) $assetKey;
 669          }
 670  
 671          if (!isset($loaded[$assetKey])) {
 672              // It's the root asset.
 673              if (self::$rootAssetId !== null && $assetKey === self::$preloadedAssets[self::$rootAssetId]) {
 674                  $loaded[$assetKey] = self::$rootAssetId;
 675              } else {
 676                  $preloadedAssetsByName = array_flip(self::$preloadedAssets);
 677  
 678                  // If we already have the asset name stored in preloading, example, a component, no need to fetch it from table.
 679                  if (isset($preloadedAssetsByName[$assetKey])) {
 680                      $loaded[$assetKey] = $preloadedAssetsByName[$assetKey];
 681                  } else {
 682                      // Else we have to do an extra db query to fetch it from the table fetch it from table.
 683                      $table = new Asset(Factory::getDbo());
 684                      $table->load(array('name' => $assetKey));
 685                      $loaded[$assetKey] = $table->id;
 686                  }
 687              }
 688          }
 689  
 690          return (int) $loaded[$assetKey];
 691      }
 692  
 693      /**
 694       * Method to get the asset name from the asset key.
 695       *
 696       * @param   integer|string  $assetKey  The asset key (asset id or asset name).
 697       *
 698       * @return  string  The asset name (ex: com_content.article.8).
 699       *
 700       * @since   3.7.0
 701       */
 702      protected static function getAssetName($assetKey)
 703      {
 704          static $loaded = array();
 705  
 706          // If the asset is already a string return it.
 707          if (!is_numeric($assetKey)) {
 708              return $assetKey;
 709          }
 710  
 711          if (!isset($loaded[$assetKey])) {
 712              // It's the root asset.
 713              if (self::$rootAssetId !== null && $assetKey === self::$rootAssetId) {
 714                  $loaded[$assetKey] = self::$preloadedAssets[self::$rootAssetId];
 715              } elseif (isset(self::$preloadedAssets[$assetKey])) {
 716                  // If we already have the asset name stored in preloading, example, a component, no need to fetch it from table.
 717                  $loaded[$assetKey] = self::$preloadedAssets[$assetKey];
 718              } else {
 719                  // Else we have to do an extra db query to fetch it from the table fetch it from table.
 720                  $table = new Asset(Factory::getDbo());
 721                  $table->load($assetKey);
 722                  $loaded[$assetKey] = $table->name;
 723              }
 724          }
 725  
 726          return $loaded[$assetKey];
 727      }
 728  
 729      /**
 730       * Method to get the extension name from the asset name.
 731       *
 732       * @param   integer|string  $assetKey  The asset key (asset id or asset name).
 733       *
 734       * @return  string  The extension name (ex: com_content).
 735       *
 736       * @since    1.6
 737       */
 738      public static function getExtensionNameFromAsset($assetKey)
 739      {
 740          static $loaded = array();
 741  
 742          if (!isset($loaded[$assetKey])) {
 743              $assetName = self::getAssetName($assetKey);
 744              $firstDot  = strpos($assetName, '.');
 745  
 746              if ($assetName !== 'root.1' && $firstDot !== false) {
 747                  $assetName = substr($assetName, 0, $firstDot);
 748              }
 749  
 750              $loaded[$assetKey] = $assetName;
 751          }
 752  
 753          return $loaded[$assetKey];
 754      }
 755  
 756      /**
 757       * Method to get the asset type from the asset name.
 758       *
 759       * For top level components this returns "components":
 760       * 'com_content' returns 'components'
 761       *
 762       * For other types:
 763       * 'com_content.article.1' returns 'com_content.article'
 764       * 'com_content.category.1' returns 'com_content.category'
 765       *
 766       * @param   integer|string  $assetKey  The asset key (asset id or asset name).
 767       *
 768       * @return  string  The asset type (ex: com_content.article).
 769       *
 770       * @since    1.6
 771       */
 772      public static function getAssetType($assetKey)
 773      {
 774          // If the asset is already a string return it.
 775          $assetName = self::getAssetName($assetKey);
 776          $lastDot   = strrpos($assetName, '.');
 777  
 778          if ($assetName !== 'root.1' && $lastDot !== false) {
 779              return substr($assetName, 0, $lastDot);
 780          }
 781  
 782          return 'components';
 783      }
 784  
 785      /**
 786       * Method to return the title of a user group
 787       *
 788       * @param   integer  $groupId  Id of the group for which to get the title of.
 789       *
 790       * @return  string  The title of the group
 791       *
 792       * @since   3.5
 793       */
 794      public static function getGroupTitle($groupId)
 795      {
 796          // Cast as integer until method is typehinted.
 797          $groupId = (int) $groupId;
 798  
 799          // Fetch the group title from the database
 800          $db    = Factory::getDbo();
 801          $query = $db->getQuery(true);
 802          $query->select($db->quoteName('title'))
 803              ->from($db->quoteName('#__usergroups'))
 804              ->where($db->quoteName('id') . ' = :groupId')
 805              ->bind(':groupId', $groupId, ParameterType::INTEGER);
 806          $db->setQuery($query);
 807  
 808          return $db->loadResult();
 809      }
 810  
 811      /**
 812       * Method to return a list of user groups mapped to a user. The returned list can optionally hold
 813       * only the groups explicitly mapped to the user or all groups both explicitly mapped and inherited
 814       * by the user.
 815       *
 816       * @param   integer  $userId     Id of the user for which to get the list of groups.
 817       * @param   boolean  $recursive  True to include inherited user groups.
 818       *
 819       * @return  array    List of user group ids to which the user is mapped.
 820       *
 821       * @since   1.7.0
 822       */
 823      public static function getGroupsByUser($userId, $recursive = true)
 824      {
 825          // Cast as integer until method is typehinted.
 826          $userId = (int) $userId;
 827  
 828          // Creates a simple unique string for each parameter combination:
 829          $storeId = $userId . ':' . (int) $recursive;
 830  
 831          if (!isset(self::$groupsByUser[$storeId])) {
 832              // @todo: Uncouple this from ComponentHelper and allow for a configuration setting or value injection.
 833              $guestUsergroup = (int) ComponentHelper::getParams('com_users')->get('guest_usergroup', 1);
 834  
 835              // Guest user (if only the actually assigned group is requested)
 836              if (empty($userId) && !$recursive) {
 837                  $result = array($guestUsergroup);
 838              } else {
 839                  // Registered user and guest if all groups are requested
 840                  $db = Factory::getDbo();
 841  
 842                  // Build the database query to get the rules for the asset.
 843                  $query = $db->getQuery(true)
 844                      ->select($db->quoteName($recursive ? 'b.id' : 'a.id'));
 845  
 846                  if (empty($userId)) {
 847                      $query->from($db->quoteName('#__usergroups', 'a'))
 848                          ->where($db->quoteName('a.id') . ' = :guest')
 849                          ->bind(':guest', $guestUsergroup, ParameterType::INTEGER);
 850                  } else {
 851                      $query->from($db->quoteName('#__user_usergroup_map', 'map'))
 852                          ->where($db->quoteName('map.user_id') . ' = :userId')
 853                          ->join('LEFT', $db->quoteName('#__usergroups', 'a'), $db->quoteName('a.id') . ' = ' . $db->quoteName('map.group_id'))
 854                          ->bind(':userId', $userId, ParameterType::INTEGER);
 855                  }
 856  
 857                  // If we want the rules cascading up to the global asset node we need a self-join.
 858                  if ($recursive) {
 859                      $query->join(
 860                          'LEFT',
 861                          $db->quoteName('#__usergroups', 'b'),
 862                          $db->quoteName('b.lft') . ' <= ' . $db->quoteName('a.lft') . ' AND ' . $db->quoteName('b.rgt') . ' >= ' . $db->quoteName('a.rgt')
 863                      );
 864                  }
 865  
 866                  // Execute the query and load the rules from the result.
 867                  $db->setQuery($query);
 868                  $result = $db->loadColumn();
 869  
 870                  // Clean up any NULL or duplicate values, just in case
 871                  $result = ArrayHelper::toInteger($result);
 872  
 873                  if (empty($result)) {
 874                      $result = array(1);
 875                  } else {
 876                      $result = array_unique($result);
 877                  }
 878              }
 879  
 880              self::$groupsByUser[$storeId] = $result;
 881          }
 882  
 883          return self::$groupsByUser[$storeId];
 884      }
 885  
 886      /**
 887       * Method to return a list of user Ids contained in a Group
 888       *
 889       * @param   integer  $groupId    The group Id
 890       * @param   boolean  $recursive  Recursively include all child groups (optional)
 891       *
 892       * @return  array
 893       *
 894       * @since   1.7.0
 895       * @todo    This method should move somewhere else
 896       */
 897      public static function getUsersByGroup($groupId, $recursive = false)
 898      {
 899          // Cast as integer until method is typehinted.
 900          $groupId = (int) $groupId;
 901  
 902          // Get a database object.
 903          $db = Factory::getDbo();
 904  
 905          $test = $recursive ? ' >= ' : ' = ';
 906  
 907          // First find the users contained in the group
 908          $query = $db->getQuery(true)
 909              ->select('DISTINCT(' . $db->quoteName('user_id') . ')')
 910              ->from($db->quoteName('#__usergroups', 'ug1'))
 911              ->join(
 912                  'INNER',
 913                  $db->quoteName('#__usergroups', 'ug2'),
 914                  $db->quoteName('ug2.lft') . $test . $db->quoteName('ug1.lft') . ' AND ' . $db->quoteName('ug1.rgt') . $test . $db->quoteName('ug2.rgt')
 915              )
 916              ->join('INNER', $db->quoteName('#__user_usergroup_map', 'm'), $db->quoteName('ug2.id') . ' = ' . $db->quoteName('m.group_id'))
 917              ->where($db->quoteName('ug1.id') . ' = :groupId')
 918              ->bind(':groupId', $groupId, ParameterType::INTEGER);
 919  
 920          $db->setQuery($query);
 921  
 922          $result = $db->loadColumn();
 923  
 924          // Clean up any NULL values, just in case
 925          $result = ArrayHelper::toInteger($result);
 926  
 927          return $result;
 928      }
 929  
 930      /**
 931       * Method to return a list of view levels for which the user is authorised.
 932       *
 933       * @param   integer  $userId  Id of the user for which to get the list of authorised view levels.
 934       *
 935       * @return  array    List of view levels for which the user is authorised.
 936       *
 937       * @since   1.7.0
 938       */
 939      public static function getAuthorisedViewLevels($userId)
 940      {
 941          // Only load the view levels once.
 942          if (empty(self::$viewLevels)) {
 943              // Get a database object.
 944              $db = Factory::getDbo();
 945  
 946              // Build the base query.
 947              $query = $db->getQuery(true)
 948                  ->select($db->quoteName(['id', 'rules']))
 949                  ->from($db->quoteName('#__viewlevels'));
 950  
 951              // Set the query for execution.
 952              $db->setQuery($query);
 953  
 954              // Build the view levels array.
 955              foreach ($db->loadAssocList() as $level) {
 956                  self::$viewLevels[$level['id']] = (array) json_decode($level['rules']);
 957              }
 958          }
 959  
 960          // Initialise the authorised array.
 961          $authorised = array(1);
 962  
 963          // Check for the recovery mode setting and return early.
 964          $user      = User::getInstance($userId);
 965          $root_user = Factory::getApplication()->get('root_user');
 966  
 967          if (($user->username && $user->username == $root_user) || (is_numeric($root_user) && $user->id > 0 && $user->id == $root_user)) {
 968              // Find the super user levels.
 969              foreach (self::$viewLevels as $level => $rule) {
 970                  foreach ($rule as $id) {
 971                      if ($id > 0 && self::checkGroup($id, 'core.admin')) {
 972                          $authorised[] = $level;
 973                          break;
 974                      }
 975                  }
 976              }
 977  
 978              return array_values(array_unique($authorised));
 979          }
 980  
 981          // Get all groups that the user is mapped to recursively.
 982          $groups = self::getGroupsByUser($userId);
 983  
 984          // Find the authorised levels.
 985          foreach (self::$viewLevels as $level => $rule) {
 986              foreach ($rule as $id) {
 987                  if (($id < 0) && (($id * -1) == $userId)) {
 988                      $authorised[] = $level;
 989                      break;
 990                  } elseif (($id >= 0) && \in_array($id, $groups)) {
 991                      // Check to see if the group is mapped to the level.
 992                      $authorised[] = $level;
 993                      break;
 994                  }
 995              }
 996          }
 997  
 998          return array_values(array_unique($authorised));
 999      }
1000  
1001      /**
1002       * Method to return a list of actions from a file for which permissions can be set.
1003       *
1004       * @param   string  $file   The path to the XML file.
1005       * @param   string  $xpath  An optional xpath to search for the fields.
1006       *
1007       * @return  boolean|array   False if case of error or the list of actions available.
1008       *
1009       * @since   3.0.0
1010       */
1011      public static function getActionsFromFile($file, $xpath = "/access/section[@name='component']/")
1012      {
1013          if (!is_file($file) || !is_readable($file)) {
1014              // If unable to find the file return false.
1015              return false;
1016          } else {
1017              // Else return the actions from the xml.
1018              $xml = simplexml_load_file($file);
1019  
1020              return self::getActionsFromData($xml, $xpath);
1021          }
1022      }
1023  
1024      /**
1025       * Method to return a list of actions from a string or from an xml for which permissions can be set.
1026       *
1027       * @param   string|\SimpleXMLElement  $data   The XML string or an XML element.
1028       * @param   string                    $xpath  An optional xpath to search for the fields.
1029       *
1030       * @return  boolean|array   False if case of error or the list of actions available.
1031       *
1032       * @since   3.0.0
1033       */
1034      public static function getActionsFromData($data, $xpath = "/access/section[@name='component']/")
1035      {
1036          // If the data to load isn't already an XML element or string return false.
1037          if ((!($data instanceof \SimpleXMLElement)) && (!\is_string($data))) {
1038              return false;
1039          }
1040  
1041          // Attempt to load the XML if a string.
1042          if (\is_string($data)) {
1043              try {
1044                  $data = new \SimpleXMLElement($data);
1045              } catch (\Exception $e) {
1046                  return false;
1047              }
1048  
1049              // Make sure the XML loaded correctly.
1050              if (!$data) {
1051                  return false;
1052              }
1053          }
1054  
1055          // Initialise the actions array
1056          $actions = array();
1057  
1058          // Get the elements from the xpath
1059          $elements = $data->xpath($xpath . 'action[@name][@title]');
1060  
1061          // If there some elements, analyse them
1062          if (!empty($elements)) {
1063              foreach ($elements as $element) {
1064                  // Add the action to the actions array
1065                  $action = array(
1066                      'name' => (string) $element['name'],
1067                      'title' => (string) $element['title'],
1068                  );
1069  
1070                  if (isset($element['description'])) {
1071                      $action['description'] = (string) $element['description'];
1072                  }
1073  
1074                  $actions[] = (object) $action;
1075              }
1076          }
1077  
1078          // Finally return the actions array
1079          return $actions;
1080      }
1081  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer