CRM-17176 Add report menu rebuild function & api
authorAnthony Nemirovsky <anemirovsky@giantrabbit.com>
Tue, 8 Sep 2015 07:17:17 +0000 (19:17 +1200)
committereileenmcnaugton <eileen@fuzion.co.nz>
Wed, 30 Sep 2015 01:23:15 +0000 (14:23 +1300)
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
api/v3/Navigation.php
tests/phpunit/CRM/Core/BAO/NavigationTest.php [new file with mode: 0644]

index 2708f737f7099095524be1db284fe80edaaa465f..7e22a8635ea161369a1e56c3237dec971ef42c09 100644 (file)
@@ -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.
    *
index f6f2ff34d14dc704d33b135acf9d0669a2fd5d80..f8daf86bff399f6224d3c0881bcef47235df4374 100644 (file)
  *
  * @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 (file)
index 0000000..6097a1e
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+require_once 'CiviTest/CiviUnitTestCase.php';
+
+/**
+ * Class CRM_Core_BAO_NavigationTest.
+ */
+class CRM_Core_BAO_NavigationTest extends CiviUnitTestCase {
+
+  /**
+   * Set up data for the test run.
+   *
+   * Here we ensure we are starting from a default report navigation.
+   */
+  public function setUp() {
+    parent::setUp();
+    CRM_Core_BAO_Navigation::rebuildReportsNavigation();
+  }
+
+  /**
+   * Test that a missing report menu link is added by rebuildReportsNavigation.
+   */
+  public function testCreateMissingReportMenuItemLink() {
+    $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());
+    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/%'");
+  }
+
+}