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