From 31cb18983883417aef133804ccd81278a00c907b Mon Sep 17 00:00:00 2001 From: Anthony Nemirovsky Date: Tue, 8 Sep 2015 19:17:17 +1200 Subject: [PATCH] CRM-17176 Add report menu rebuild function & api This is code extracted from a bunch of improvements by Giant Rabbit. It adds a function to rebuild the report menu I have given this an api interface (& moved the functions around a little) wmf CRM-17176 enhance navigation-reset to accept domain as a parameter CRM-17176 navigation reset api - fix type --- CRM/Core/BAO/Navigation.php | 204 +++++++++++++++++- api/v3/Navigation.php | 37 +++- tests/phpunit/CRM/Core/BAO/NavigationTest.php | 112 ++++++++++ 3 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 tests/phpunit/CRM/Core/BAO/NavigationTest.php diff --git a/CRM/Core/BAO/Navigation.php b/CRM/Core/BAO/Navigation.php index 2708f737f7..7e22a8635e 100644 --- a/CRM/Core/BAO/Navigation.php +++ b/CRM/Core/BAO/Navigation.php @@ -29,8 +29,6 @@ * * @package CRM * @copyright CiviCRM LLC (c) 2004-2015 - * $Id$ - * */ class CRM_Core_BAO_Navigation extends CRM_Core_DAO_Navigation { @@ -827,6 +825,208 @@ ORDER BY parent_id, weight"; } } + /** + * Rebuild reports menu. + * + * All Contact reports will become sub-items of 'Contact Reports' and so on. + * + * @param int $domain_id + */ + public static function rebuildReportsNavigation($domain_id) { + $component_to_nav_name = array( + 'CiviContact' => 'Contact Reports', + 'CiviContribute' => 'Contribution Reports', + 'CiviMember' => 'Membership Reports', + 'CiviEvent' => 'Event Reports', + 'CiviPledge' => 'Pledge Reports', + 'CiviGrant' => 'Grant Reports', + 'CiviMail' => 'Mailing Reports', + 'CiviCampaign' => 'Campaign Reports', + ); + + // Create or update the top level Reports link. + $reports_nav = self::createOrUpdateTopLevelReportsNavItem($domain_id); + + // Get all active report instances grouped by component. + $components = self::getAllActiveReportsByComponent($domain_id); + foreach ($components as $component_id => $component) { + // Create or update the per component reports links. + $component_nav_name = $component['name']; + if (isset($component_to_nav_name[$component_nav_name])) { + $component_nav_name = $component_to_nav_name[$component_nav_name]; + } + $permission = "access {$component['name']}"; + if ($component['name'] === 'CiviContact') { + $permission = "administer CiviCRM"; + } + elseif ($component['name'] === 'CiviCampaign') { + $permission = "access CiviReport"; + } + $component_nav = self::createOrUpdateReportNavItem($component_nav_name, 'civicrm/report/list', "compid={$component_id}&reset=1", $reports_nav->id, $permission, $domain_id); + foreach ($component['reports'] as $report_id => $report) { + // Create or update the report instance links. + $report_nav = self::createOrUpdateReportNavItem($report['title'], $report['url'], 'reset=1', $component_nav->id, $report['permission'], $domain_id); + // Update the report instance to include the navigation id. + $query = "UPDATE civicrm_report_instance SET navigation_id = %1 WHERE id = %2"; + $params = array( + 1 => array($report_nav->id, 'Integer'), + 2 => array($report_id, 'Integer'), + ); + CRM_Core_DAO::executeQuery($query, $params); + } + } + + // Create or update the All Reports link. + self::createOrUpdateReportNavItem('All Reports', 'civicrm/report/list', 'reset=1', $reports_nav->id, 'access CiviReport', $domain_id); + } + + /** + * Create the top level 'Reports' item in the navigation tree. + * + * @param int $domain_id + * + * @return bool|\CRM_Core_DAO + */ + static public function createOrUpdateTopLevelReportsNavItem($domain_id) { + $id = NULL; + + $dao = new CRM_Core_BAO_Navigation(); + $dao->name = 'Reports'; + $dao->domain_id = $domain_id; + // The first selectAdd clears it - so that we only retrieve the one field. + $dao->selectAdd(); + $dao->selectAdd('id'); + if ($dao->find(TRUE)) { + $id = $dao->id; + } + + $nav = self::createReportNavItem('Reports', NULL, NULL, NULL, 'access CiviReport', $id, $domain_id); + return $nav; + } + + /** + * Retrieve a navigation item using it's url. + * + * @param string $url + * @param array $url_params + * + * @return bool|\CRM_Core_DAO + */ + public static function getNavItemByUrl($url, $url_params) { + $query = "SELECT * FROM civicrm_navigation WHERE url = %1"; + $params = array( + 1 => array("{$url}?{$url_params}", 'String'), + ); + $dao = CRM_Core_DAO::executeQuery($query, $params, TRUE, 'CRM_Core_DAO_Navigation'); + $dao->fetch(); + if (isset($dao->id)) { + return $dao; + } + return FALSE; + } + + /** + * Get all active reports, organised by component. + * + * @param int $domain_id + * + * @return array + */ + public static function getAllActiveReportsByComponent($domain_id) { + $sql = " + SELECT + civicrm_report_instance.id, civicrm_report_instance.title, civicrm_report_instance.permission, civicrm_component.name, civicrm_component.id AS component_id + FROM + civicrm_option_group + LEFT JOIN + civicrm_option_value ON civicrm_option_value.option_group_id = civicrm_option_group.id AND civicrm_option_group.name = 'report_template' + LEFT JOIN + civicrm_report_instance ON civicrm_option_value.value = civicrm_report_instance.report_id + LEFT JOIN + civicrm_component ON civicrm_option_value.component_id = civicrm_component.id + WHERE + civicrm_option_value.is_active = 1 + AND + civicrm_report_instance.domain_id = %1 + ORDER BY civicrm_option_value.weight"; + + $dao = CRM_Core_DAO::executeQuery($sql, array( + 1 => array($domain_id, 'Integer'), + )); + $rows = array(); + while ($dao->fetch()) { + $component_name = is_null($dao->name) ? 'CiviContact' : $dao->name; + $component_id = is_null($dao->component_id) ? 99 : $dao->component_id; + $rows[$component_id]['name'] = $component_name; + $rows[$component_id]['reports'][$dao->id] = array( + 'title' => $dao->title, + 'url' => "civicrm/report/instance/{$dao->id}", + 'permission' => $dao->permission, + ); + } + return $rows; + } + + /** + * Create or update a navigation item for a report instance. + * + * The function will check whether create or update is required. + * + * @param string $name + * @param string $url + * @param string $url_params + * @param int $parent_id + * @param string $permission + * @param int $domain_id + * + * @return \CRM_Core_DAO_Navigation + */ + protected static function createOrUpdateReportNavItem($name, $url, $url_params, $parent_id, $permission, $domain_id) { + $id = NULL; + $existing_nav = CRM_Core_BAO_Navigation::getNavItemByUrl($url, $url_params); + if ($existing_nav) { + $id = $existing_nav->id; + } + $nav = self::createReportNavItem($name, $url, $url_params, $parent_id, $permission, $id, $domain_id); + return $nav; + } + + /** + * Create a navigation item for a report instance. + * + * @param string $name + * @param string $url + * @param string $url_params + * @param int $parent_id + * @param string $permission + * @param int $id + * @param int $domain_id + * ID of domain to create item in. + * + * @return \CRM_Core_DAO_Navigation + */ + public static function createReportNavItem($name, $url, $url_params, $parent_id, $permission, $id, $domain_id) { + if ($url !== NULL) { + $url = "{$url}?{$url_params}"; + } + $params = array( + 'name' => $name, + 'label' => ts($name), + 'url' => $url, + 'parent_id' => $parent_id, + 'is_active' => TRUE, + 'permission' => array( + $permission, + ), + 'domain_id' => $domain_id, + ); + if ($id !== NULL) { + $params['id'] = $id; + } + $nav = CRM_Core_BAO_Navigation::add($params); + return $nav; + } + /** * Get cache key. * diff --git a/api/v3/Navigation.php b/api/v3/Navigation.php index f6f2ff34d1..f8daf86bff 100644 --- a/api/v3/Navigation.php +++ b/api/v3/Navigation.php @@ -36,6 +36,41 @@ * * @param array $params */ +function _civicrm_api3_navigation_reset_spec(&$params) { + $params['for']['api.required'] = TRUE; + $params['for']['title'] = "Is this reset for all navigation or reports"; + $params['for']['type'] = CRM_Utils_Type::T_STRING; + $params['for']['options'] = array( + 'all' => 'General Navigation rebuild from xml', + 'report' => 'Reset report menu to default structure', + ); + $params['domain_id']['api.default'] = CRM_Core_Config::domainID(); + $params['domain_id']['type'] = CRM_Utils_Type::T_INT; + $params['domain_id']['title'] = 'Domain ID'; +} + +/** + * Reset navigation. + * + * @param array $params + * Array of name/value pairs. + * + * @return array + * API result array. + */ +function civicrm_api3_navigation_reset($params) { + if ($params['for'] == 'report') { + CRM_Core_BAO_Navigation::resetNavigation(); + } + CRM_Core_BAO_Navigation::rebuildReportsNavigation($params['domain_id']); + return civicrm_api3_create_success(1, $params, 'navigation', 'reset'); +} + +/** + * Adjust metadata for navigation get action. + * + * @param array $params + */ function _civicrm_api3_navigation_get_spec(&$params) { } @@ -82,7 +117,7 @@ function _civicrm_api3_navigation_delete_spec(&$params) { } /** - * Create navigation item. + * Delete navigation item. * * @param array $params * Array of name/value pairs. diff --git a/tests/phpunit/CRM/Core/BAO/NavigationTest.php b/tests/phpunit/CRM/Core/BAO/NavigationTest.php new file mode 100644 index 0000000000..6097a1e33b --- /dev/null +++ b/tests/phpunit/CRM/Core/BAO/NavigationTest.php @@ -0,0 +1,112 @@ +getCountReportInstances(); + CRM_Core_DAO::executeQuery("DELETE FROM civicrm_navigation WHERE url = 'civicrm/report/instance/1?reset=1'"); + $this->assertEquals($reportCount - 1, $this->getCountReportInstances()); + CRM_Core_BAO_Navigation::rebuildReportsNavigation(); + + $this->assertEquals($reportCount, $this->getCountReportInstances()); + $url = 'civicrm/report/instance/1'; + $url_params = 'reset=1'; + $new_nav = CRM_Core_BAO_Navigation::getNavItemByUrl($url, $url_params); + $this->assertObjectHasAttribute('id', $new_nav); + $this->assertNotNull($new_nav->id); + } + + /** + * Test that a missing report menu link is added by rebuildReportsNavigation. + */ + public function testCreateMissingReportMenuItemLinkViaAPI() { + $reportCount = $this->getCountReportInstances(); + CRM_Core_DAO::executeQuery("DELETE FROM civicrm_navigation WHERE url = 'civicrm/report/instance/1?reset=1'"); + $this->assertEquals($reportCount - 1, $this->getCountReportInstances()); + $this->callAPISuccess('Navigation', 'reset', array('for' => 'report')); + + $this->assertEquals($reportCount, $this->getCountReportInstances()); + $url = 'civicrm/report/instance/1'; + $url_params = 'reset=1'; + $new_nav = CRM_Core_BAO_Navigation::getNavItemByUrl($url, $url_params); + $this->assertObjectHasAttribute('id', $new_nav); + $this->assertNotNull($new_nav->id); + } + + /** + * Test that an existing report link is rebuilt under is't parent. + * + * Function tests CRM_Core_BAO_Navigation::rebuildReportsNavigation. + */ + public function testUpdateExistingReportMenuLink() { + $url = 'civicrm/report/instance/1'; + $url_params = 'reset=1'; + $existing_nav = CRM_Core_BAO_Navigation::getNavItemByUrl($url, $url_params); + $this->assertNotEquals(FALSE, $existing_nav); + $existing_nav->parent_id = 1; + $existing_nav->save(); + CRM_Core_BAO_Navigation::rebuildReportsNavigation(); + $parent_url = 'civicrm/report/list'; + $parent_url_params = 'compid=99&reset=1'; + $parent_nav = CRM_Core_BAO_Navigation::getNavItemByUrl($parent_url, $parent_url_params); + $this->assertNotEquals($parent_nav->id, 1); + $changed_existing_nav = new CRM_Core_BAO_Navigation(); + $changed_existing_nav->id = $existing_nav->id; + $changed_existing_nav->find(TRUE); + $this->assertEquals($changed_existing_nav->parent_id, $parent_nav->id); + } + + + /** + * Test that a navigation item can be retrieved by it's url. + */ + public function testGetNavItemByUrl() { + $random_string = substr(sha1(rand()), 0, 7); + $name = "Test Menu Link {$random_string}"; + $url = "civicrm/test/{$random_string}"; + $url_params = "reset=1"; + $params = array( + 'name' => $name, + 'label' => ts($name), + 'url' => "{$url}?{$url_params}", + 'parent_id' => NULL, + 'is_active' => TRUE, + 'permission' => array( + 'access CiviCRM', + ), + ); + CRM_Core_BAO_Navigation::add($params); + $new_nav = CRM_Core_BAO_Navigation::getNavItemByUrl($url, $url_params); + $this->assertObjectHasAttribute('id', $new_nav); + $this->assertNotNull($new_nav->id); + $new_nav->delete(); + } + + /** + * Get a count of report instances. + * + * @return int + */ + protected function getCountReportInstances() { + return CRM_Core_DAO::singleValueQuery( + "SELECT count(*) FROM civicrm_navigation WHERE url LIKE 'civicrm/report/instance/%'"); + } + +} -- 2.25.1