Merge pull request #19095 from nishant-bhorodia/Issue#537-owner-notification-email...
[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 = $item['operator'] ?? NULL;
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 Civi::contactSettings($contactID)->set('navigation', $newKey);
472 }
473 }
474
475 return $newKey;
476 }
477
478 /**
479 * Process navigation.
480 *
481 * @param array $params
482 * Associated array, $_GET.
483 */
484 public static function processNavigation(&$params) {
485 $nodeID = (int) str_replace("node_", "", $params['id']);
486 $referenceID = (int) str_replace("node_", "", $params['ref_id']);
487 $position = $params['ps'];
488 $type = $params['type'];
489 $label = $params['data'] ?? NULL;
490
491 switch ($type) {
492 case "move":
493 self::processMove($nodeID, $referenceID, $position);
494 break;
495
496 case "rename":
497 self::processRename($nodeID, $label);
498 break;
499
500 case "delete":
501 self::processDelete($nodeID);
502 break;
503 }
504
505 //reset navigation menus
506 self::resetNavigation();
507 CRM_Utils_System::civiExit();
508 }
509
510 /**
511 * Process move action.
512 *
513 * @param $nodeID
514 * Node that is being moved.
515 * @param $referenceID
516 * Parent id where node is moved. 0 mean no parent.
517 * @param $position
518 * New position of the nod, it starts with 0 - n.
519 */
520 public static function processMove($nodeID, $referenceID, $position) {
521 // based on the new position we need to get the weight of the node after moved node
522 // 1. update the weight of $position + 1 nodes to weight + 1
523 // 2. weight of the ( $position -1 ) node - 1 is the new weight of the node being moved
524
525 // check if there is parent id, which means node is moved inside existing parent container, so use parent id
526 // to find the correct position else use NULL to get the weights of parent ( $position - 1 )
527 // accordingly set the new parent_id
528 if ($referenceID) {
529 $newParentID = $referenceID;
530 $parentClause = "parent_id = {$referenceID} ";
531 }
532 else {
533 $newParentID = 'NULL';
534 $parentClause = 'parent_id IS NULL';
535 }
536
537 $incrementOtherNodes = TRUE;
538 $sql = "SELECT weight from civicrm_navigation WHERE {$parentClause} ORDER BY weight LIMIT %1, 1";
539 $params = [1 => [$position, 'Positive']];
540 $newWeight = CRM_Core_DAO::singleValueQuery($sql, $params);
541
542 // this means node is moved to last position, so you need to get the weight of last element + 1
543 if (!$newWeight) {
544 // If this is not the first item being added to a parent
545 if ($position) {
546 $lastPosition = $position - 1;
547 $sql = "SELECT weight from civicrm_navigation WHERE {$parentClause} ORDER BY weight LIMIT %1, 1";
548 $params = [1 => [$lastPosition, 'Positive']];
549 $newWeight = CRM_Core_DAO::singleValueQuery($sql, $params);
550
551 // since last node increment + 1
552 $newWeight = $newWeight + 1;
553 }
554 else {
555 $newWeight = '0';
556 }
557
558 // since this is a last node we don't need to increment other nodes
559 $incrementOtherNodes = FALSE;
560 }
561
562 $transaction = new CRM_Core_Transaction();
563
564 // now update the existing nodes to weight + 1, if required.
565 if ($incrementOtherNodes) {
566 $query = "UPDATE civicrm_navigation SET weight = weight + 1
567 WHERE {$parentClause} AND weight >= {$newWeight}";
568
569 CRM_Core_DAO::executeQuery($query);
570 }
571
572 // finally set the weight of current node
573 $query = "UPDATE civicrm_navigation SET weight = {$newWeight}, parent_id = {$newParentID} WHERE id = {$nodeID}";
574 CRM_Core_DAO::executeQuery($query);
575
576 $transaction->commit();
577 }
578
579 /**
580 * Function to process rename action for tree.
581 *
582 * @param int $nodeID
583 * @param $label
584 */
585 public static function processRename($nodeID, $label) {
586 CRM_Core_DAO::setFieldValue('CRM_Core_DAO_Navigation', $nodeID, 'label', $label);
587 }
588
589 /**
590 * Process delete action for tree.
591 *
592 * @param int $nodeID
593 */
594 public static function processDelete($nodeID) {
595 $query = "DELETE FROM civicrm_navigation WHERE id = {$nodeID}";
596 CRM_Core_DAO::executeQuery($query);
597 }
598
599 /**
600 * Update menu.
601 *
602 * @param array $params
603 * @param array $newParams
604 * New value of params.
605 */
606 public static function processUpdate($params, $newParams) {
607 $dao = new CRM_Core_DAO_Navigation();
608 $dao->copyValues($params);
609 if ($dao->find(TRUE)) {
610 $dao->copyValues($newParams);
611 $dao->save();
612 }
613 }
614
615 /**
616 * Rebuild reports menu.
617 *
618 * All Contact reports will become sub-items of 'Contact Reports' and so on.
619 *
620 * @param int $domain_id
621 */
622 public static function rebuildReportsNavigation($domain_id) {
623 $component_to_nav_name = [
624 'CiviContact' => 'Contact Reports',
625 'CiviContribute' => 'Contribution Reports',
626 'CiviMember' => 'Membership Reports',
627 'CiviEvent' => 'Event Reports',
628 'CiviPledge' => 'Pledge Reports',
629 'CiviGrant' => 'Grant Reports',
630 'CiviMail' => 'Mailing Reports',
631 'CiviCampaign' => 'Campaign Reports',
632 ];
633
634 // Create or update the top level Reports link.
635 $reports_nav = self::createOrUpdateTopLevelReportsNavItem($domain_id);
636
637 // Get all active report instances grouped by component.
638 $components = self::getAllActiveReportsByComponent($domain_id);
639 foreach ($components as $component_id => $component) {
640 // Create or update the per component reports links.
641 $component_nav_name = $component['name'];
642 if (isset($component_to_nav_name[$component_nav_name])) {
643 $component_nav_name = $component_to_nav_name[$component_nav_name];
644 }
645 $permission = "access {$component['name']}";
646 if ($component['name'] === 'CiviContact') {
647 $permission = "administer CiviCRM";
648 }
649 elseif ($component['name'] === 'CiviCampaign') {
650 $permission = "access CiviReport";
651 }
652 $component_nav = self::createOrUpdateReportNavItem($component_nav_name, 'civicrm/report/list',
653 "compid={$component_id}&reset=1", $reports_nav->id, $permission, $domain_id, TRUE);
654 foreach ($component['reports'] as $report_id => $report) {
655 // Create or update the report instance links.
656 $report_nav = self::createOrUpdateReportNavItem($report['title'], $report['url'], 'reset=1', $component_nav->id, $report['permission'], $domain_id, FALSE, TRUE);
657 // Update the report instance to include the navigation id.
658 $query = "UPDATE civicrm_report_instance SET navigation_id = %1 WHERE id = %2";
659 $params = [
660 1 => [$report_nav->id, 'Integer'],
661 2 => [$report_id, 'Integer'],
662 ];
663 CRM_Core_DAO::executeQuery($query, $params);
664 }
665 }
666
667 // Create or update the All Reports link.
668 self::createOrUpdateReportNavItem('All Reports', 'civicrm/report/list', 'reset=1', $reports_nav->id, 'access CiviReport', $domain_id, TRUE);
669 // Create or update the My Reports link.
670 self::createOrUpdateReportNavItem('My Reports', 'civicrm/report/list', 'myreports=1&reset=1', $reports_nav->id, 'access CiviReport', $domain_id, TRUE);
671
672 }
673
674 /**
675 * Create the top level 'Reports' item in the navigation tree.
676 *
677 * @param int $domain_id
678 *
679 * @return bool|\CRM_Core_DAO
680 */
681 public static function createOrUpdateTopLevelReportsNavItem($domain_id) {
682 $id = NULL;
683
684 $dao = new CRM_Core_BAO_Navigation();
685 $dao->name = 'Reports';
686 $dao->domain_id = $domain_id;
687 // The first selectAdd clears it - so that we only retrieve the one field.
688 $dao->selectAdd();
689 $dao->selectAdd('id');
690 if ($dao->find(TRUE)) {
691 $id = $dao->id;
692 }
693
694 $nav = self::createReportNavItem('Reports', NULL, NULL, NULL, 'access CiviReport', $id, $domain_id);
695 return $nav;
696 }
697
698 /**
699 * Retrieve a navigation item using it's url.
700 *
701 * Note that we use LIKE to permit a wildcard as the calling code likely doesn't
702 * care about output params appended.
703 *
704 * @param string $url
705 * @param array $url_params
706 *
707 * @param int|null $parent_id
708 * Optionally restrict to one parent.
709 *
710 * @return bool|\CRM_Core_BAO_Navigation
711 */
712 public static function getNavItemByUrl($url, $url_params, $parent_id = NULL) {
713 $nav = new CRM_Core_BAO_Navigation();
714 $nav->parent_id = $parent_id;
715 $nav->whereAdd("url LIKE '{$url}?{$url_params}'");
716
717 if ($nav->find(TRUE)) {
718 return $nav;
719 }
720 return FALSE;
721 }
722
723 /**
724 * Get all active reports, organised by component.
725 *
726 * @param int $domain_id
727 *
728 * @return array
729 */
730 public static function getAllActiveReportsByComponent($domain_id) {
731 $sql = "
732 SELECT
733 civicrm_report_instance.id, civicrm_report_instance.title, civicrm_report_instance.permission, civicrm_component.name, civicrm_component.id AS component_id
734 FROM
735 civicrm_option_group
736 LEFT JOIN
737 civicrm_option_value ON civicrm_option_value.option_group_id = civicrm_option_group.id AND civicrm_option_group.name = 'report_template'
738 LEFT JOIN
739 civicrm_report_instance ON civicrm_option_value.value = civicrm_report_instance.report_id
740 LEFT JOIN
741 civicrm_component ON civicrm_option_value.component_id = civicrm_component.id
742 WHERE
743 civicrm_option_value.is_active = 1
744 AND
745 civicrm_report_instance.domain_id = %1
746 ORDER BY civicrm_option_value.weight";
747
748 $dao = CRM_Core_DAO::executeQuery($sql, [
749 1 => [$domain_id, 'Integer'],
750 ]);
751 $rows = [];
752 while ($dao->fetch()) {
753 $component_name = is_null($dao->name) ? 'CiviContact' : $dao->name;
754 $component_id = is_null($dao->component_id) ? 99 : $dao->component_id;
755 $rows[$component_id]['name'] = $component_name;
756 $rows[$component_id]['reports'][$dao->id] = [
757 'title' => $dao->title,
758 'url' => "civicrm/report/instance/{$dao->id}",
759 'permission' => $dao->permission,
760 ];
761 }
762 return $rows;
763 }
764
765 /**
766 * Create or update a navigation item for a report instance.
767 *
768 * The function will check whether create or update is required.
769 *
770 * @param string $name
771 * @param string $url
772 * @param string $url_params
773 * @param int $parent_id
774 * @param string $permission
775 * @param int $domain_id
776 *
777 * @param bool $onlyMatchParentID
778 * If True then do not match with a url that has a different parent
779 * (This is because for top level items there is a risk of 'stealing' rows that normally
780 * live under 'Contact' and intentionally duplicate the report examples.)
781 * @param bool $useWildcard
782 * @return \CRM_Core_DAO_Navigation
783 */
784 protected static function createOrUpdateReportNavItem($name, $url, $url_params, $parent_id, $permission,
785 $domain_id, $onlyMatchParentID = FALSE, $useWildcard = TRUE) {
786 $id = NULL;
787 $existing_url_params = $useWildcard ? $url_params . '%' : $url_params;
788 $existing_nav = CRM_Core_BAO_Navigation::getNavItemByUrl($url, $existing_url_params, ($onlyMatchParentID ? $parent_id : NULL));
789 if ($existing_nav) {
790 $id = $existing_nav->id;
791 }
792
793 $nav = self::createReportNavItem($name, $url, $url_params, $parent_id, $permission, $id, $domain_id);
794 return $nav;
795 }
796
797 /**
798 * Create a navigation item for a report instance.
799 *
800 * @param string $name
801 * @param string $url
802 * @param string $url_params
803 * @param int $parent_id
804 * @param string $permission
805 * @param int $id
806 * @param int $domain_id
807 * ID of domain to create item in.
808 *
809 * @return \CRM_Core_DAO_Navigation
810 */
811 public static function createReportNavItem($name, $url, $url_params, $parent_id, $permission, $id, $domain_id) {
812 if ($url !== NULL) {
813 $url = "{$url}?{$url_params}";
814 }
815 $params = [
816 'name' => $name,
817 'label' => ts($name),
818 'url' => $url,
819 'parent_id' => $parent_id,
820 'is_active' => TRUE,
821 'permission' => [
822 $permission,
823 ],
824 'domain_id' => $domain_id,
825 ];
826 if ($id) {
827 $params['id'] = $id;
828 }
829 return CRM_Core_BAO_Navigation::add($params);
830 }
831
832 /**
833 * Get cache key.
834 *
835 * @param int $cid
836 *
837 * @return object|string
838 */
839 public static function getCacheKey($cid) {
840 $key = Civi::service('settings_manager')
841 ->getBagByContact(NULL, $cid)
842 ->get('navigation');
843 if (strlen($key) !== self::CACHE_KEY_STRLEN) {
844 $key = self::resetNavigation($cid);
845 }
846 return $key;
847 }
848
849 /**
850 * Unset menu items for disabled components and non-permissioned users
851 *
852 * @param $menu
853 */
854 public static function filterByPermission(&$menu) {
855 foreach ($menu as $key => $item) {
856 if (
857 (array_key_exists('active', $item['attributes']) && !$item['attributes']['active']) ||
858 !CRM_Core_BAO_Navigation::checkPermission($item['attributes'])
859 ) {
860 unset($menu[$key]);
861 continue;
862 }
863 if (!empty($item['child'])) {
864 self::filterByPermission($menu[$key]['child']);
865 }
866 }
867 }
868
869 /**
870 * @param array $menu
871 */
872 public static function buildHomeMenu(&$menu) {
873 foreach ($menu as &$item) {
874 if (CRM_Utils_Array::value('name', $item['attributes']) === 'Home') {
875 unset($item['attributes']['label'], $item['attributes']['url']);
876 $item['attributes']['icon'] = 'crm-logo-sm';
877 $item['attributes']['attr']['accesskey'] = 'm';
878 $item['child'] = [
879 [
880 'attributes' => [
881 'label' => 'CiviCRM Home',
882 'name' => 'CiviCRM Home',
883 'url' => 'civicrm/dashboard?reset=1',
884 'weight' => 1,
885 ],
886 ],
887 [
888 'attributes' => [
889 'label' => 'Hide Menu',
890 'name' => 'Hide Menu',
891 'url' => '#hidemenu',
892 'weight' => 2,
893 ],
894 ],
895 [
896 'attributes' => [
897 'label' => 'Log out',
898 'name' => 'Log out',
899 'url' => 'civicrm/logout?reset=1',
900 'weight' => 3,
901 ],
902 ],
903 ];
904 return;
905 }
906 }
907 }
908
909 }