[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |