Merge pull request #24115 from kcristiano/5.52-token
[civicrm-core.git] / CRM / Core / BAO / Navigation.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17 class CRM_Core_BAO_Navigation extends CRM_Core_DAO_Navigation {
18
19 // Number of characters in the menu js cache key
20 const CACHE_KEY_STRLEN = 8;
21
22 /**
23 * Update the is_active flag in the db.
24 *
25 * @param int $id
26 * Id of the database record.
27 * @param bool $is_active
28 * Value we want to set the is_active field.
29 *
30 * @return bool
31 * true if we found and updated the object, else false
32 */
33 public static function setIsActive($id, $is_active) {
34 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_Navigation', $id, 'is_active', $is_active);
35 }
36
37 /**
38 * Deprecated in favor of APIv4
39 *
40 * @deprecated
41 * @param array $params Submitted values
42 * @return CRM_Core_DAO_Navigation
43 */
44 public static function add(&$params) {
45 $navigation = new CRM_Core_DAO_Navigation();
46 if (empty($params['id'])) {
47 $params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE);
48 $params['has_separator'] = CRM_Utils_Array::value('has_separator', $params, FALSE);
49 $params['domain_id'] = CRM_Utils_Array::value('domain_id', $params, CRM_Core_Config::domainID());
50 }
51
52 if (!isset($params['id']) ||
53 (CRM_Utils_Array::value('parent_id', $params) != CRM_Utils_Array::value('current_parent_id', $params))
54 ) {
55 /* re/calculate the weight, if the Parent ID changed OR create new menu */
56
57 if ($navName = CRM_Utils_Array::value('name', $params)) {
58 $params['name'] = $navName;
59 }
60 elseif ($navLabel = CRM_Utils_Array::value('label', $params)) {
61 $params['name'] = $navLabel;
62 }
63
64 $params['weight'] = self::calculateWeight(CRM_Utils_Array::value('parent_id', $params));
65 }
66
67 if (array_key_exists('permission', $params) && is_array($params['permission'])) {
68 $params['permission'] = implode(',', $params['permission']);
69 }
70
71 $navigation->copyValues($params);
72
73 $navigation->save();
74 return $navigation;
75 }
76
77 /**
78 * Retrieve DB object and copy to defaults array.
79 *
80 * @param array $params
81 * Array of criteria values.
82 * @param array $defaults
83 * Array to be populated with found values.
84 *
85 * @return self|null
86 * The DAO object, if found.
87 *
88 * @deprecated
89 */
90 public static function retrieve($params, &$defaults) {
91 $params['domain_id'] = CRM_Core_Config::domainID();
92 return self::commonRetrieve(self::class, $params, $defaults);
93 }
94
95 /**
96 * Calculate navigation weight.
97 *
98 * @param int $parentID
99 * Parent_id of a menu.
100 * @param int $menuID
101 * Menu id.
102 *
103 * @return int
104 * $weight string
105 */
106 public static function calculateWeight($parentID = NULL, $menuID = NULL) {
107 $domainID = CRM_Core_Config::domainID();
108
109 $weight = 1;
110 // we reset weight for each parent, i.e we start from 1 to n
111 // calculate max weight for top level menus, if parent id is absent
112 if (!$parentID) {
113 $query = "SELECT max(weight) as weight FROM civicrm_navigation WHERE parent_id IS NULL AND domain_id = $domainID";
114 }
115 else {
116 // if parent is passed, we need to get max weight for that particular parent
117 $query = "SELECT max(weight) as weight FROM civicrm_navigation WHERE parent_id = {$parentID} AND domain_id = $domainID";
118 }
119
120 $dao = CRM_Core_DAO::executeQuery($query);
121 $dao->fetch();
122 return $weight = $weight + $dao->weight;
123 }
124
125 /**
126 * Get formatted menu list.
127 *
128 * @return array
129 * returns associated array
130 */
131 public static function getNavigationList() {
132 $cacheKeyString = "navigationList_" . CRM_Core_Config::domainID();
133 $whereClause = '';
134
135 $config = CRM_Core_Config::singleton();
136
137 // check if we can retrieve from database cache
138 $navigations = Civi::cache('navigation')->get($cacheKeyString);
139
140 if (!$navigations) {
141 $domainID = CRM_Core_Config::domainID();
142 $query = "
143 SELECT id, label, parent_id, weight, is_active, name
144 FROM civicrm_navigation WHERE domain_id = $domainID";
145 $result = CRM_Core_DAO::executeQuery($query);
146
147 $pidGroups = [];
148 while ($result->fetch()) {
149 $pidGroups[$result->parent_id][$result->label] = $result->id;
150 }
151
152 foreach ($pidGroups[''] as $label => $val) {
153 $pidGroups[''][$label] = self::_getNavigationValue($val, $pidGroups);
154 }
155
156 $navigations = [];
157 self::_getNavigationLabel($pidGroups[''], $navigations);
158
159 Civi::cache('navigation')->set($cacheKeyString, $navigations);
160 }
161 return $navigations;
162 }
163
164 /**
165 * Helper function for getNavigationList().
166 *
167 * @param array $list
168 * Menu info.
169 * @param array $navigations
170 * Navigation menus.
171 * @param string $separator
172 * Menu separator.
173 */
174 public static function _getNavigationLabel($list, &$navigations, $separator = '') {
175 $i18n = CRM_Core_I18n::singleton();
176 foreach ($list as $label => $val) {
177 if ($label == 'navigation_id') {
178 continue;
179 }
180 $translatedLabel = $i18n->crm_translate($label, ['context' => 'menu']);
181 $navigations[is_array($val) ? $val['navigation_id'] : $val] = "{$separator}{$translatedLabel}";
182 if (is_array($val)) {
183 self::_getNavigationLabel($val, $navigations, $separator . '&nbsp;&nbsp;&nbsp;&nbsp;');
184 }
185 }
186 }
187
188 /**
189 * Helper function for getNavigationList().
190 *
191 * @param string $val
192 * Menu name.
193 * @param array $pidGroups
194 * Parent menus.
195 *
196 * @return array
197 */
198 public static function _getNavigationValue($val, &$pidGroups) {
199 if (array_key_exists($val, $pidGroups)) {
200 $list = ['navigation_id' => $val];
201 foreach ($pidGroups[$val] as $label => $id) {
202 $list[$label] = self::_getNavigationValue($id, $pidGroups);
203 }
204 unset($pidGroups[$val]);
205 return $list;
206 }
207 else {
208 return $val;
209 }
210 }
211
212 /**
213 * Build navigation tree.
214 *
215 * @return array
216 * nested array of menus
217 */
218 public static function buildNavigationTree() {
219 $domainID = CRM_Core_Config::domainID();
220 $navigationTree = [];
221
222 $navigationMenu = new self();
223 $navigationMenu->domain_id = $domainID;
224 $navigationMenu->orderBy('parent_id, weight');
225 $navigationMenu->find();
226
227 while ($navigationMenu->fetch()) {
228 $navigationTree[$navigationMenu->id] = [
229 'attributes' => [
230 'label' => $navigationMenu->label,
231 'name' => $navigationMenu->name,
232 'url' => $navigationMenu->url,
233 'icon' => $navigationMenu->icon,
234 'weight' => $navigationMenu->weight,
235 'permission' => $navigationMenu->permission,
236 'operator' => $navigationMenu->permission_operator,
237 'separator' => $navigationMenu->has_separator,
238 'parentID' => $navigationMenu->parent_id,
239 'navID' => $navigationMenu->id,
240 'active' => $navigationMenu->is_active,
241 ],
242 ];
243 }
244
245 return self::buildTree($navigationTree);
246 }
247
248 /**
249 * Convert flat array to nested.
250 *
251 * @param array $elements
252 * @param int|null $parentId
253 *
254 * @return array
255 */
256 private static function buildTree($elements, $parentId = NULL) {
257 $branch = [];
258
259 foreach ($elements as $id => $element) {
260 if ($element['attributes']['parentID'] == $parentId) {
261 $children = self::buildTree($elements, $id);
262 if ($children) {
263 $element['child'] = $children;
264 }
265 $branch[$id] = $element;
266 }
267 }
268
269 return $branch;
270 }
271
272 /**
273 * buildNavigationTree retreives items in order. We call this function to
274 * ensure that any items added by the hook are also in the correct order.
275 */
276 public static function orderByWeight(&$navigations) {
277 // sort each item in navigations by weight
278 usort($navigations, function($a, $b) {
279
280 // If no weight have been defined for an item put it at the end of the list
281 if (!isset($a['attributes']['weight'])) {
282 $a['attributes']['weight'] = 1000;
283 }
284 if (!isset($b['attributes']['weight'])) {
285 $b['attributes']['weight'] = 1000;
286 }
287 return $a['attributes']['weight'] - $b['attributes']['weight'];
288 });
289
290 // If any of the $navigations have children, recurse
291 foreach ($navigations as &$navigation) {
292 if (isset($navigation['child'])) {
293 self::orderByWeight($navigation['child']);
294 }
295 }
296 }
297
298 /**
299 * Given a navigation menu, generate navIDs for any items which are
300 * missing them.
301 *
302 * @param array $nodes
303 * Each key is a numeral; each value is a node in
304 * the menu tree (with keys "child" and "attributes").
305 */
306 public static function fixNavigationMenu(&$nodes) {
307 $maxNavID = 1;
308 array_walk_recursive($nodes, function($item, $key) use (&$maxNavID) {
309 if ($key === 'navID') {
310 $maxNavID = max($maxNavID, $item);
311 }
312 });
313 self::_fixNavigationMenu($nodes, $maxNavID, NULL);
314 }
315
316 /**
317 * @param array $nodes
318 * Each key is a numeral; each value is a node in
319 * the menu tree (with keys "child" and "attributes").
320 * @param int $maxNavID
321 * @param int $parentID
322 */
323 private static function _fixNavigationMenu(&$nodes, &$maxNavID, $parentID) {
324 $origKeys = array_keys($nodes);
325 foreach ($origKeys as $origKey) {
326 if (!isset($nodes[$origKey]['attributes']['parentID']) && $parentID !== NULL) {
327 $nodes[$origKey]['attributes']['parentID'] = $parentID;
328 }
329 // If no navID, then assign navID and fix key.
330 if (!isset($nodes[$origKey]['attributes']['navID'])) {
331 $newKey = ++$maxNavID;
332 $nodes[$origKey]['attributes']['navID'] = $newKey;
333 if ($origKey != $newKey) {
334 // If the keys are different, reset the array index to match.
335 $nodes[$newKey] = $nodes[$origKey];
336 unset($nodes[$origKey]);
337 $origKey = $newKey;
338 }
339 }
340 if (isset($nodes[$origKey]['child']) && is_array($nodes[$origKey]['child'])) {
341 self::_fixNavigationMenu($nodes[$origKey]['child'], $maxNavID, $nodes[$origKey]['attributes']['navID']);
342 }
343 }
344 }
345
346 /**
347 * Check if a menu item should be visible based on permissions and component.
348 *
349 * @param $item
350 * @return bool
351 */
352 public static function checkPermission($item) {
353 if (!empty($item['permission'])) {
354 $permissions = explode(',', $item['permission']);
355 $operator = $item['operator'] ?? NULL;
356 $hasPermission = FALSE;
357 foreach ($permissions as $key) {
358 $key = trim($key);
359 $showItem = TRUE;
360
361 //get the component name from permission.
362 $componentName = CRM_Core_Permission::getComponentName($key);
363
364 if ($componentName) {
365 if (!CRM_Core_Component::isEnabled($componentName) || !CRM_Core_Permission::check($key)) {
366 $showItem = FALSE;
367 if ($operator == 'AND') {
368 return FALSE;
369 }
370 }
371 else {
372 $hasPermission = TRUE;
373 }
374 }
375 elseif (!CRM_Core_Permission::check($key)) {
376 $showItem = FALSE;
377 if ($operator == 'AND') {
378 return FALSE;
379 }
380 }
381 else {
382 $hasPermission = TRUE;
383 }
384 }
385
386 if (empty($showItem) && !$hasPermission) {
387 return FALSE;
388 }
389 }
390 return TRUE;
391 }
392
393 /**
394 * Turns relative URLs (like civicrm/foo/bar) into fully-formed
395 * ones (i.e. example.com/wp-admin?q=civicrm/dashboard).
396 *
397 * If the URL is already fully-formed, nothing will be done.
398 *
399 * @param string $url
400 *
401 * @return string
402 */
403 public static function makeFullyFormedUrl($url) {
404 if (self::isNotFullyFormedUrl($url)) {
405 //CRM-7656 --make sure to separate out url path from url params,
406 //as we'r going to validate url path across cross-site scripting.
407 $path = parse_url($url, PHP_URL_PATH);
408 $q = parse_url($url, PHP_URL_QUERY);
409 $fragment = parse_url($url, PHP_URL_FRAGMENT);
410 return CRM_Utils_System::url($path, $q, FALSE, $fragment);
411 }
412
413 if (strpos($url, '&amp;') === FALSE) {
414 return htmlspecialchars($url);
415 }
416
417 return $url;
418 }
419
420 /**
421 * Checks if the given URL is not fully-formed
422 *
423 * @param string $url
424 *
425 * @return bool
426 */
427 private static function isNotFullyFormedUrl($url) {
428 return substr($url, 0, 4) !== 'http' && $url[0] !== '/' && $url[0] !== '#';
429 }
430
431 /**
432 * Reset navigation for all contacts or a specified contact.
433 *
434 * @param int $contactID
435 * Reset only entries belonging to that contact ID.
436 *
437 * @return string
438 */
439 public static function resetNavigation($contactID = NULL) {
440 $newKey = CRM_Utils_String::createRandom(self::CACHE_KEY_STRLEN, CRM_Utils_String::ALPHANUMERIC);
441 if (!$contactID) {
442 $ser = serialize($newKey);
443 $query = "UPDATE civicrm_setting SET value = '$ser' WHERE name='navigation' AND contact_id IS NOT NULL";
444 CRM_Core_DAO::executeQuery($query);
445 Civi::cache('navigation')->flush();
446 // reset ACL and System caches
447 CRM_Core_BAO_Cache::resetCaches();
448 }
449 else {
450 // before inserting check if contact id exists in db
451 // this is to handle weird case when contact id is in session but not in db
452 $contact = new CRM_Contact_DAO_Contact();
453 $contact->id = $contactID;
454 if ($contact->find(TRUE)) {
455 Civi::contactSettings($contactID)->set('navigation', $newKey);
456 }
457 }
458
459 return $newKey;
460 }
461
462 /**
463 * Process navigation.
464 *
465 * @param array $params
466 * Associated array, $_GET.
467 */
468 public static function processNavigation(&$params) {
469 $nodeID = (int) str_replace("node_", "", $params['id']);
470 $referenceID = (int) str_replace("node_", "", $params['ref_id']);
471 $position = $params['ps'];
472 $type = $params['type'];
473 $label = $params['data'] ?? NULL;
474
475 switch ($type) {
476 case "move":
477 self::processMove($nodeID, $referenceID, $position);
478 break;
479
480 case "rename":
481 self::processRename($nodeID, $label);
482 break;
483
484 case "delete":
485 self::processDelete($nodeID);
486 break;
487 }
488
489 //reset navigation menus
490 self::resetNavigation();
491 CRM_Utils_System::civiExit();
492 }
493
494 /**
495 * Process move action.
496 *
497 * @param $nodeID
498 * Node that is being moved.
499 * @param $referenceID
500 * Parent id where node is moved. 0 mean no parent.
501 * @param $position
502 * New position of the nod, it starts with 0 - n.
503 */
504 public static function processMove($nodeID, $referenceID, $position) {
505 // based on the new position we need to get the weight of the node after moved node
506 // 1. update the weight of $position + 1 nodes to weight + 1
507 // 2. weight of the ( $position -1 ) node - 1 is the new weight of the node being moved
508
509 // check if there is parent id, which means node is moved inside existing parent container, so use parent id
510 // to find the correct position else use NULL to get the weights of parent ( $position - 1 )
511 // accordingly set the new parent_id
512 if ($referenceID) {
513 $newParentID = $referenceID;
514 $parentClause = "parent_id = {$referenceID} ";
515 }
516 else {
517 $newParentID = 'NULL';
518 $parentClause = 'parent_id IS NULL';
519 }
520
521 $incrementOtherNodes = TRUE;
522 $sql = "SELECT weight from civicrm_navigation WHERE {$parentClause} ORDER BY weight LIMIT %1, 1";
523 $params = [1 => [$position, 'Positive']];
524 $newWeight = CRM_Core_DAO::singleValueQuery($sql, $params);
525
526 // this means node is moved to last position, so you need to get the weight of last element + 1
527 if (!$newWeight) {
528 // If this is not the first item being added to a parent
529 if ($position) {
530 $lastPosition = $position - 1;
531 $sql = "SELECT weight from civicrm_navigation WHERE {$parentClause} ORDER BY weight LIMIT %1, 1";
532 $params = [1 => [$lastPosition, 'Positive']];
533 $newWeight = CRM_Core_DAO::singleValueQuery($sql, $params);
534
535 // since last node increment + 1
536 $newWeight = $newWeight + 1;
537 }
538 else {
539 $newWeight = '0';
540 }
541
542 // since this is a last node we don't need to increment other nodes
543 $incrementOtherNodes = FALSE;
544 }
545
546 $transaction = new CRM_Core_Transaction();
547
548 // now update the existing nodes to weight + 1, if required.
549 if ($incrementOtherNodes) {
550 $query = "UPDATE civicrm_navigation SET weight = weight + 1
551 WHERE {$parentClause} AND weight >= {$newWeight}";
552
553 CRM_Core_DAO::executeQuery($query);
554 }
555
556 // finally set the weight of current node
557 $query = "UPDATE civicrm_navigation SET weight = {$newWeight}, parent_id = {$newParentID} WHERE id = {$nodeID}";
558 CRM_Core_DAO::executeQuery($query);
559
560 $transaction->commit();
561 }
562
563 /**
564 * Function to process rename action for tree.
565 *
566 * @param int $nodeID
567 * @param $label
568 */
569 public static function processRename($nodeID, $label) {
570 CRM_Core_DAO::setFieldValue('CRM_Core_DAO_Navigation', $nodeID, 'label', $label);
571 }
572
573 /**
574 * Process delete action for tree.
575 *
576 * @param int $nodeID
577 */
578 public static function processDelete($nodeID) {
579 $query = "DELETE FROM civicrm_navigation WHERE id = {$nodeID}";
580 CRM_Core_DAO::executeQuery($query);
581 }
582
583 /**
584 * Update menu.
585 *
586 * @param array $params
587 * @param array $newParams
588 * New value of params.
589 */
590 public static function processUpdate($params, $newParams) {
591 $dao = new CRM_Core_DAO_Navigation();
592 $dao->copyValues($params);
593 if ($dao->find(TRUE)) {
594 $dao->copyValues($newParams);
595 $dao->save();
596 }
597 }
598
599 /**
600 * Rebuild reports menu.
601 *
602 * All Contact reports will become sub-items of 'Contact Reports' and so on.
603 *
604 * @param int $domain_id
605 */
606 public static function rebuildReportsNavigation($domain_id) {
607 $component_to_nav_name = [
608 'CiviContact' => 'Contact Reports',
609 'CiviContribute' => 'Contribution Reports',
610 'CiviMember' => 'Membership Reports',
611 'CiviEvent' => 'Event Reports',
612 'CiviPledge' => 'Pledge Reports',
613 'CiviGrant' => 'Grant Reports',
614 'CiviMail' => 'Mailing Reports',
615 'CiviCampaign' => 'Campaign Reports',
616 ];
617
618 // Create or update the top level Reports link.
619 $reports_nav = self::createOrUpdateTopLevelReportsNavItem($domain_id);
620
621 // Get all active report instances grouped by component.
622 $components = self::getAllActiveReportsByComponent($domain_id);
623 foreach ($components as $component_id => $component) {
624 // Create or update the per component reports links.
625 $component_nav_name = $component['name'];
626 if (isset($component_to_nav_name[$component_nav_name])) {
627 $component_nav_name = $component_to_nav_name[$component_nav_name];
628 }
629 $permission = "access {$component['name']}";
630 if ($component['name'] === 'CiviContact') {
631 $permission = "administer CiviCRM";
632 }
633 elseif ($component['name'] === 'CiviCampaign') {
634 $permission = "access CiviReport";
635 }
636 $component_nav = self::createOrUpdateReportNavItem($component_nav_name, 'civicrm/report/list',
637 "compid={$component_id}&reset=1", $reports_nav->id, $permission, $domain_id, TRUE);
638 foreach ($component['reports'] as $report_id => $report) {
639 // Create or update the report instance links.
640 $report_nav = self::createOrUpdateReportNavItem($report['title'], $report['url'], 'reset=1', $component_nav->id, $report['permission'], $domain_id, FALSE, TRUE);
641 // Update the report instance to include the navigation id.
642 $query = "UPDATE civicrm_report_instance SET navigation_id = %1 WHERE id = %2";
643 $params = [
644 1 => [$report_nav->id, 'Integer'],
645 2 => [$report_id, 'Integer'],
646 ];
647 CRM_Core_DAO::executeQuery($query, $params);
648 }
649 }
650
651 // Create or update the All Reports link.
652 self::createOrUpdateReportNavItem('All Reports', 'civicrm/report/list', 'reset=1', $reports_nav->id, 'access CiviReport', $domain_id, TRUE);
653 // Create or update the My Reports link.
654 self::createOrUpdateReportNavItem('My Reports', 'civicrm/report/list', 'myreports=1&reset=1', $reports_nav->id, 'access CiviReport', $domain_id, TRUE);
655
656 }
657
658 /**
659 * Create the top level 'Reports' item in the navigation tree.
660 *
661 * @param int $domain_id
662 *
663 * @return bool|\CRM_Core_DAO
664 */
665 public static function createOrUpdateTopLevelReportsNavItem($domain_id) {
666 $id = NULL;
667
668 $dao = new CRM_Core_BAO_Navigation();
669 $dao->name = 'Reports';
670 $dao->domain_id = $domain_id;
671 // The first selectAdd clears it - so that we only retrieve the one field.
672 $dao->selectAdd();
673 $dao->selectAdd('id');
674 if ($dao->find(TRUE)) {
675 $id = $dao->id;
676 }
677
678 $nav = self::createReportNavItem('Reports', NULL, NULL, NULL, 'access CiviReport', $id, $domain_id);
679 return $nav;
680 }
681
682 /**
683 * Retrieve a navigation item using it's url.
684 *
685 * Note that we use LIKE to permit a wildcard as the calling code likely doesn't
686 * care about output params appended.
687 *
688 * @param string $url
689 * @param string $url_params
690 *
691 * @param int|null $parent_id
692 * Optionally restrict to one parent.
693 *
694 * @return bool|\CRM_Core_BAO_Navigation
695 */
696 public static function getNavItemByUrl($url, $url_params, $parent_id = NULL) {
697 $nav = new CRM_Core_BAO_Navigation();
698 $nav->parent_id = $parent_id;
699 $nav->whereAdd("url LIKE '{$url}?{$url_params}'");
700
701 if ($nav->find(TRUE)) {
702 return $nav;
703 }
704 return FALSE;
705 }
706
707 /**
708 * Get all active reports, organised by component.
709 *
710 * @param int $domain_id
711 *
712 * @return array
713 */
714 public static function getAllActiveReportsByComponent($domain_id) {
715 $sql = "
716 SELECT
717 civicrm_report_instance.id, civicrm_report_instance.title, civicrm_report_instance.permission, civicrm_component.name, civicrm_component.id AS component_id
718 FROM
719 civicrm_option_group
720 LEFT JOIN
721 civicrm_option_value ON civicrm_option_value.option_group_id = civicrm_option_group.id AND civicrm_option_group.name = 'report_template'
722 LEFT JOIN
723 civicrm_report_instance ON civicrm_option_value.value = civicrm_report_instance.report_id
724 LEFT JOIN
725 civicrm_component ON civicrm_option_value.component_id = civicrm_component.id
726 WHERE
727 civicrm_option_value.is_active = 1
728 AND
729 civicrm_report_instance.domain_id = %1
730 ORDER BY civicrm_option_value.weight";
731
732 $dao = CRM_Core_DAO::executeQuery($sql, [
733 1 => [$domain_id, 'Integer'],
734 ]);
735 $rows = [];
736 while ($dao->fetch()) {
737 $component_name = is_null($dao->name) ? 'CiviContact' : $dao->name;
738 $component_id = is_null($dao->component_id) ? 99 : $dao->component_id;
739 $rows[$component_id]['name'] = $component_name;
740 $rows[$component_id]['reports'][$dao->id] = [
741 'title' => $dao->title,
742 'url' => "civicrm/report/instance/{$dao->id}",
743 'permission' => $dao->permission,
744 ];
745 }
746 return $rows;
747 }
748
749 /**
750 * Create or update a navigation item for a report instance.
751 *
752 * The function will check whether create or update is required.
753 *
754 * @param string $name
755 * @param string $url
756 * @param string $url_params
757 * @param int $parent_id
758 * @param string $permission
759 * @param int $domain_id
760 *
761 * @param bool $onlyMatchParentID
762 * If True then do not match with a url that has a different parent
763 * (This is because for top level items there is a risk of 'stealing' rows that normally
764 * live under 'Contact' and intentionally duplicate the report examples.)
765 * @param bool $useWildcard
766 * @return \CRM_Core_DAO_Navigation
767 */
768 protected static function createOrUpdateReportNavItem($name, $url, $url_params, $parent_id, $permission,
769 $domain_id, $onlyMatchParentID = FALSE, $useWildcard = TRUE) {
770 $id = NULL;
771 $existing_url_params = $useWildcard ? $url_params . '%' : $url_params;
772 $existing_nav = CRM_Core_BAO_Navigation::getNavItemByUrl($url, $existing_url_params, ($onlyMatchParentID ? $parent_id : NULL));
773 if ($existing_nav) {
774 $id = $existing_nav->id;
775 }
776
777 $nav = self::createReportNavItem($name, $url, $url_params, $parent_id, $permission, $id, $domain_id);
778 return $nav;
779 }
780
781 /**
782 * Create a navigation item for a report instance.
783 *
784 * @param string $name
785 * @param string $url
786 * @param string $url_params
787 * @param int $parent_id
788 * @param string $permission
789 * @param int $id
790 * @param int $domain_id
791 * ID of domain to create item in.
792 *
793 * @return \CRM_Core_DAO_Navigation
794 */
795 public static function createReportNavItem($name, $url, $url_params, $parent_id, $permission, $id, $domain_id) {
796 if ($url !== NULL) {
797 $url = "{$url}?{$url_params}";
798 }
799 $params = [
800 'name' => $name,
801 'label' => ts($name),
802 'url' => $url,
803 'parent_id' => $parent_id,
804 'is_active' => TRUE,
805 'permission' => [
806 $permission,
807 ],
808 'domain_id' => $domain_id,
809 ];
810 if ($id) {
811 $params['id'] = $id;
812 }
813 return CRM_Core_BAO_Navigation::add($params);
814 }
815
816 /**
817 * Get cache key.
818 *
819 * @param int $cid
820 *
821 * @return object|string
822 */
823 public static function getCacheKey($cid) {
824 $key = Civi::service('settings_manager')
825 ->getBagByContact(NULL, $cid)
826 ->get('navigation');
827 if (strlen($key) !== self::CACHE_KEY_STRLEN) {
828 $key = self::resetNavigation($cid);
829 }
830 return $key;
831 }
832
833 /**
834 * Unset menu items for disabled components and non-permissioned users
835 *
836 * @param $menu
837 */
838 public static function filterByPermission(&$menu) {
839 foreach ($menu as $key => $item) {
840 if (
841 (array_key_exists('active', $item['attributes']) && !$item['attributes']['active']) ||
842 !CRM_Core_BAO_Navigation::checkPermission($item['attributes'])
843 ) {
844 unset($menu[$key]);
845 continue;
846 }
847 if (!empty($item['child'])) {
848 self::filterByPermission($menu[$key]['child']);
849 }
850 }
851 }
852
853 /**
854 * @param array $menu
855 */
856 public static function buildHomeMenu(&$menu) {
857 foreach ($menu as &$item) {
858 if (CRM_Utils_Array::value('name', $item['attributes']) === 'Home') {
859 unset($item['attributes']['label'], $item['attributes']['url']);
860 $item['attributes']['icon'] = 'crm-logo-sm';
861 $item['attributes']['attr']['accesskey'] = 'm';
862 $item['child'] = [
863 [
864 'attributes' => [
865 'label' => 'CiviCRM Home',
866 'name' => 'CiviCRM Home',
867 'url' => 'civicrm/dashboard?reset=1',
868 'weight' => 1,
869 ],
870 ],
871 [
872 'attributes' => [
873 'label' => 'Hide Menu',
874 'name' => 'Hide Menu',
875 'url' => '#hidemenu',
876 'weight' => 2,
877 ],
878 ],
879 [
880 'attributes' => [
881 'label' => 'Log out',
882 'name' => 'Log out',
883 'url' => 'civicrm/logout?reset=1',
884 'weight' => 3,
885 ],
886 ],
887 ];
888 return;
889 }
890 }
891 }
892
893 }