CRM-12337 - Convert navigation script to ajax with browser caching
authorColeman Watts <coleman@civicrm.org>
Tue, 26 Nov 2013 04:49:09 +0000 (20:49 -0800)
committerColeman Watts <coleman@civicrm.org>
Tue, 26 Nov 2013 04:49:32 +0000 (20:49 -0800)
CRM/Admin/Page/AJAX.php
CRM/Core/BAO/Navigation.php
CRM/Core/Smarty/plugins/function.crmNavigationMenu.php
CRM/Core/xml/Menu/Admin.xml
templates/CRM/common/Navigation.tpl

index 0f0427c164f634a7d77204934b953fc359a60686..4588d0c8a9789f969e66903bf3f2b4da6c577e93 100644 (file)
 class CRM_Admin_Page_AJAX {
 
   /**
-   * Function to build menu tree
+   * CRM-12337 Output navigation menu as executable javascript
+   * @see smarty_function_crmNavigationMenu
+   */
+  static function getNavigationMenu() {
+    $session = CRM_Core_Session::singleton();
+    $contactID = $session->get('userID');
+    if ($contactID) {
+      // Set headers to encourage browsers to cache for a long time
+      // If we want to refresh the menu we will send a different url
+      // @see smarty_function_crmNavigationMenu()
+      $year = 60*60*24*364;
+      header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + $year));
+      header('Content-Type:    application/javascript');
+      header("Cache-Control: max-age=$year, public");
+
+      // Render template as a javascript file
+      $smarty = CRM_Core_Smarty::singleton();
+      $navigation = CRM_Core_BAO_Navigation::createNavigation($contactID);
+      $smarty->assign('navigation', $navigation);
+      print $smarty->fetch('CRM/common/Navigation.tpl');
+    }
+    exit();
+  }
+
+  /**
+   * Return menu tree as json data for editing
    */
   static function getNavigationList() {
     echo CRM_Core_BAO_Navigation::buildNavigation(TRUE, FALSE);
@@ -50,8 +75,7 @@ class CRM_Admin_Page_AJAX {
    * Function to process drag/move action for menu tree
    */
   static function menuTree() {
-    echo CRM_Core_BAO_Navigation::processNavigation($_GET);
-    CRM_Utils_System::civiExit();
+    CRM_Core_BAO_Navigation::processNavigation($_GET);
   }
 
   /**
index 3ba44506d0a33de8ed800b95f0ed74f6bd6fd8f4..361bef7f8a0311cf5bb35d5e3dd69f65bf041fdd 100644 (file)
@@ -34,6 +34,9 @@
  */
 class CRM_Core_BAO_Navigation extends CRM_Core_DAO_Navigation {
 
+  // Number of characters in the menu js cache key
+  const CACHE_KEY_STRLEN = 8;
+
   /**
    * class constructor
    */
@@ -568,29 +571,9 @@ ORDER BY parent_id, weight";
   static function createNavigation($contactID) {
     $config = CRM_Core_Config::singleton();
 
-    // if on frontend, do not create navigation menu items, CRM-5349
-    if ($config->userFrameworkFrontend) {
-      return "<!-- $config->lcMessages -->";
-    }
+    $navigation = self::buildNavigation();
 
-    $navParams = array('contact_id' => $contactID);
-
-    $navigation = CRM_Core_BAO_Setting::getItem(
-      CRM_Core_BAO_Setting::PERSONAL_PREFERENCES_NAME,
-      'navigation',
-      NULL,
-      NULL,
-      $contactID
-    );
-
-    // FIXME: hack for CRM-5027: we need to prepend the navigation string with
-    // (HTML-commented-out) locale info so that we rebuild menu on locale changes
-    if (
-      !$navigation ||
-      substr($navigation, 0, 14) != "<!-- $config->lcMessages -->"
-    ) {
-      //retrieve navigation if it's not cached.
-      $navigation = self::buildNavigation();
+    if ($navigation) {
 
       //add additional navigation items
       $logoutURL = CRM_Utils_System::url('civicrm/logout', 'reset=1');
@@ -629,16 +612,32 @@ ORDER BY parent_id, weight";
         $prepandString = "<li class=\"menumain crm-link-home\"><a href=\"{$homeURL}\" title=\"" . $homeLabel . "\">" . $homeLabel . "</a></li>";
       }
 
-      // prepend the navigation with locale info for CRM-5027
-      $navigation = "<!-- $config->lcMessages -->" . $prepandString . $navigation . $appendSring;
+      $navigation = $prepandString . $navigation . $appendSring;
+    }
+    return $navigation;
+  }
 
+  /**
+   * Reset navigation for all contacts or a specified contact
+   *
+   * @param integer $contactID - reset only entries belonging to that contact ID
+   * @return string
+   */
+  static function resetNavigation($contactID = NULL) {
+    $newKey = CRM_Utils_String::createRandom(self::CACHE_KEY_STRLEN, CRM_Utils_String::ALPHANUMERIC);
+    if (!$contactID) {
+      $query = "UPDATE civicrm_setting SET value = '$newKey' WHERE name='navigation' AND contact_id IS NOT NULL";
+      CRM_Core_DAO::executeQuery($query);
+      CRM_Core_BAO_Cache::deleteGroup('navigation');
+    }
+    else {
       // before inserting check if contact id exists in db
-      // this is to handle wierd case when contact id is in session but not in db
+      // this is to handle weird case when contact id is in session but not in db
       $contact = new CRM_Contact_DAO_Contact();
       $contact->id = $contactID;
       if ($contact->find(TRUE)) {
         CRM_Core_BAO_Setting::setItem(
-          $navigation,
+          $newKey,
           CRM_Core_BAO_Setting::PERSONAL_PREFERENCES_NAME,
           'navigation',
           NULL,
@@ -647,30 +646,11 @@ ORDER BY parent_id, weight";
         );
       }
     }
-    return $navigation;
-  }
-
-  /**
-   * Reset navigation for all contacts
-   *
-   * @param integer $contactID - reset only entries belonging to that contact ID
-   */
-  static function resetNavigation($contactID = NULL) {
-    $params = array();
-    $query = "UPDATE civicrm_setting SET value = NULL WHERE name='navigation'";
-    if ($contactID) {
-      $query .= " AND contact_id = %1";
-      $params[1] = array($contactID, 'Integer');
-    }
-    else {
-      $query .= " AND contact_id IS NOT NULL";
-    }
-
-    CRM_Core_DAO::executeQuery($query, $params);
-    CRM_Core_BAO_Cache::deleteGroup('navigation');
-
     // also reset the dashlet cache in case permissions have changed etc
+    // FIXME: decouple this
     CRM_Core_BAO_Dashboard::resetDashletCache($contactID);
+
+    return $newKey;
   }
 
   /**
@@ -824,5 +804,19 @@ ORDER BY parent_id, weight";
       $dao->save();
     }
   }
+
+  static function getCacheKey($cid) {
+    $key = CRM_Core_BAO_Setting::getItem(
+      CRM_Core_BAO_Setting::PERSONAL_PREFERENCES_NAME,
+      'navigation',
+      NULL,
+      '',
+      $cid
+    );
+    if (strlen($key) !== self::CACHE_KEY_STRLEN) {
+      $key = self::resetNavigation($cid);
+    }
+    return $key;
+  }
 }
 
index 0f02e19cf6566ea644dd7c6cafb642cf4cbf8134..e38d8f0a34b50ba3962b75d045d7d1bd87e4a015 100644 (file)
@@ -34,7 +34,7 @@
  */
 
 /**
- * Generate the nav menu
+ * Output navigation script tag
  *
  * @param array $params
  *   - is_default: bool, true if this is normal/default instance of the menu (which may be subject to CIVICRM_DISABLE_DEFAULT_MENU)
  * @return string HTML
  */
 function smarty_function_crmNavigationMenu($params, &$smarty) {
+  $config = CRM_Core_Config::singleton();
   //check if logged in user has access CiviCRM permission and build menu
   $buildNavigation = !CRM_Core_Config::isUpgradeMode() && CRM_Core_Permission::check('access CiviCRM');
   if (defined('CIVICRM_DISABLE_DEFAULT_MENU') && CRM_Utils_Array::value('is_default', $params, FALSE)) {
     $buildNavigation = FALSE;
   }
+  if ($config->userFrameworkFrontend) {
+    $buildNavigation = FALSE;
+  }
   if ($buildNavigation) {
     $session = CRM_Core_Session::singleton();
     $contactID = $session->get('userID');
     if ($contactID) {
-      $navigation = CRM_Core_BAO_Navigation::createNavigation($contactID);
-      $smarty->assign('navigation', $navigation);
-      return $smarty->fetch('CRM/common/Navigation.tpl');
+      // These params force the browser to refresh the js file when switching user, domain, or language
+      // We don't put them as a query string because some browsers will refuse to cache a page with a ? in the url
+      // We end the string with .js to trick apache mods into sending pro-caching headers
+      // @see CRM_Admin_Page_AJAX::getNavigationMenu
+      $lang = $config->lcMessages;
+      $domain = CRM_Core_Config::domainID();
+      $key = CRM_Core_BAO_Navigation::getCacheKey($contactID);
+      $src = CRM_Utils_System::url("civicrm/ajax/menujs/$contactID/$lang/$domain/$key.js");
+      return '<script type="text/javascript" src="' . $src . '"></script>';
     }
   }
   return '';
index f56b9d8302f48bd8bc0632942e55c8b58a9e16fa..187a8796f555669a73b677230c4c658e0541f99c 100644 (file)
      <title>Option Values</title>
      <page_callback>CRM_Admin_Page_OptionValue</page_callback>
   </item>
+  <item>
+     <path>civicrm/ajax/menujs</path>
+     <page_callback>CRM_Admin_Page_AJAX::getNavigationMenu</page_callback>
+     <access_arguments>access CiviCRM</access_arguments>
+  </item>
   <item>
      <path>civicrm/ajax/menu</path>
      <page_callback>CRM_Admin_Page_AJAX::getNavigationList</page_callback>
index 5e981612df3c3d18e45c392c876f097eadef4ce1..92d7cdce007217ff4a98554f83954de8a87fdc03 100644 (file)
@@ -53,9 +53,7 @@
     {/if}
     {$navigation}
   </ul>
-{/strip}{/capture}
-{literal}
-<script type="text/javascript">
+{/strip}{/capture}{literal}
   (function($) {
     var menuMarkup = {/literal}{$menuMarkup|@json_encode}{literal};
     $(function() {
     });
     {/literal}{/if}{literal}
     $('#civicrm-menu').menu({arrowSrc: CRM.config.resourceBase + 'packages/jquery/css/images/arrow.png'});
-  })(cj);
-</script>
-{/literal}
+  })(cj);{/literal}