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