CRM-15306: Adding hook to alter mail templates
[civicrm-core.git] / CRM / Utils / Hook.php
index e96d7c473febd63610f72840abe371445da57fd9..af9744cb55a64955840aa23539a61e6cfa992fbb 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 4.6                                                |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2015                                |
  +--------------------------------------------------------------------+
@@ -29,8 +29,6 @@
  *
  * @package CiviCRM_Hook
  * @copyright CiviCRM LLC (c) 2004-2015
- * $Id: $
- *
  */
 abstract class CRM_Utils_Hook {
 
@@ -44,9 +42,9 @@ abstract class CRM_Utils_Hook {
 
   // by default - place content below existing content
   const SUMMARY_BELOW = 1;
-  // pace hook content above
+  // place hook content above
   const SUMMARY_ABOVE = 2;
-  // create your own summarys
+  // create your own summaries
   const SUMMARY_REPLACE = 3;
 
   static $_nullObject = NULL;
@@ -69,6 +67,11 @@ abstract class CRM_Utils_Hook {
    */
   private $commonCiviModules = array();
 
+  /**
+   * @var CRM_Utils_Cache_Interface
+   */
+  protected $cache;
+
   /**
    * Constructor and getter for the singleton instance.
    *
@@ -81,12 +84,19 @@ abstract class CRM_Utils_Hook {
     if (self::$_singleton == NULL || $fresh) {
       $config = CRM_Core_Config::singleton();
       $class = $config->userHookClass;
-      require_once str_replace('_', DIRECTORY_SEPARATOR, $config->userHookClass) . '.php';
       self::$_singleton = new $class();
     }
     return self::$_singleton;
   }
 
+  public function __construct() {
+    $this->cache = CRM_Utils_Cache::create(array(
+      'name' => 'hooks',
+      'type' => array('ArrayCache'),
+      'prefetch' => 1,
+    ));
+  }
+
   /**
    * Invoke hooks.
    *
@@ -189,51 +199,60 @@ abstract class CRM_Utils_Hook {
     // to reproduce the issue are pretty intricate.
     $result = array();
 
-    if ($civiModules !== NULL) {
-      foreach ($civiModules as $module) {
-        $fnName = "{$module}_{$fnSuffix}";
-        if (function_exists($fnName)) {
-          $fResult = array();
-          switch ($numParams) {
-            case 0:
-              $fResult = $fnName();
-              break;
-
-            case 1:
-              $fResult = $fnName($arg1);
-              break;
-
-            case 2:
-              $fResult = $fnName($arg1, $arg2);
-              break;
-
-            case 3:
-              $fResult = $fnName($arg1, $arg2, $arg3);
-              break;
-
-            case 4:
-              $fResult = $fnName($arg1, $arg2, $arg3, $arg4);
-              break;
-
-            case 5:
-              $fResult = $fnName($arg1, $arg2, $arg3, $arg4, $arg5);
-              break;
-
-            case 6:
-              $fResult = $fnName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6);
-              break;
-
-            default:
-              CRM_Core_Error::fatal(ts('Invalid hook invocation'));
-              break;
-          }
-
-          if (!empty($fResult) &&
-            is_array($fResult)
-          ) {
-            $result = array_merge($result, $fResult);
+    $fnNames = $this->cache->get($fnSuffix);
+    if (!is_array($fnNames)) {
+      $fnNames = array();
+      if ($civiModules !== NULL) {
+        foreach ($civiModules as $module) {
+          $fnName = "{$module}_{$fnSuffix}";
+          if (function_exists($fnName)) {
+            $fnNames[] = $fnName;
           }
         }
+        $this->cache->set($fnSuffix, $fnNames);
+      }
+    }
+
+    foreach ($fnNames as $fnName) {
+      $fResult = array();
+      switch ($numParams) {
+        case 0:
+          $fResult = $fnName();
+          break;
+
+        case 1:
+          $fResult = $fnName($arg1);
+          break;
+
+        case 2:
+          $fResult = $fnName($arg1, $arg2);
+          break;
+
+        case 3:
+          $fResult = $fnName($arg1, $arg2, $arg3);
+          break;
+
+        case 4:
+          $fResult = $fnName($arg1, $arg2, $arg3, $arg4);
+          break;
+
+        case 5:
+          $fResult = $fnName($arg1, $arg2, $arg3, $arg4, $arg5);
+          break;
+
+        case 6:
+          $fResult = $fnName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6);
+          break;
+
+        default:
+          CRM_Core_Error::fatal(ts('Invalid hook invocation'));
+          break;
+      }
+
+      if (!empty($fResult) &&
+        is_array($fResult)
+      ) {
+        $result = array_merge($result, $fResult);
       }
     }
 
@@ -276,8 +295,8 @@ abstract class CRM_Utils_Hook {
    */
   public static function pre($op, $objectName, $id, &$params) {
     $event = new \Civi\Core\Event\PreEvent($op, $objectName, $id, $params);
-    \Civi\Core\Container::singleton()->get('dispatcher')->dispatch("hook_civicrm_pre", $event);
-    \Civi\Core\Container::singleton()->get('dispatcher')->dispatch("hook_civicrm_pre::$objectName", $event);
+    \Civi::service('dispatcher')->dispatch("hook_civicrm_pre", $event);
+    \Civi::service('dispatcher')->dispatch("hook_civicrm_pre::$objectName", $event);
     return self::singleton()
       ->invoke(4, $op, $objectName, $id, $params, self::$_nullObject, self::$_nullObject, 'civicrm_pre');
   }
@@ -300,8 +319,8 @@ abstract class CRM_Utils_Hook {
    */
   public static function post($op, $objectName, $objectId, &$objectRef) {
     $event = new \Civi\Core\Event\PostEvent($op, $objectName, $objectId, $objectRef);
-    \Civi\Core\Container::singleton()->get('dispatcher')->dispatch("hook_civicrm_post", $event);
-    \Civi\Core\Container::singleton()->get('dispatcher')->dispatch("hook_civicrm_post::$objectName", $event);
+    \Civi::service('dispatcher')->dispatch("hook_civicrm_post", $event);
+    \Civi::service('dispatcher')->dispatch("hook_civicrm_post::$objectName", $event);
     return self::singleton()
       ->invoke(4, $op, $objectName, $objectId, $objectRef, self::$_nullObject, self::$_nullObject, 'civicrm_post');
   }
@@ -384,25 +403,6 @@ abstract class CRM_Utils_Hook {
     );
   }
 
-  /**
-   * This hook is invoked during all CiviCRM form validation. An array of errors
-   * detected is returned. Else we assume validation succeeded.
-   *
-   * @param string $formName
-   *   The name of the form.
-   * @param array &$fields the POST parameters as filtered by QF
-   * @param array &$files the FILES parameters as sent in by POST
-   * @param array &$form the form object
-   *
-   * @return mixed
-   *   formRule hooks return a boolean or
-   *                           an array of error messages which display a QF Error
-   */
-  public static function validate($formName, &$fields, &$files, &$form) {
-    return self::singleton()
-      ->invoke(4, $formName, $fields, $files, $form, self::$_nullObject, self::$_nullObject, 'civicrm_validate');
-  }
-
   /**
    * This hook is invoked during all CiviCRM form validation. An array of errors
    * detected is returned. Else we assume validation succeeded.
@@ -803,7 +803,7 @@ abstract class CRM_Utils_Hook {
    * @param array $tasks
    *   The current set of tasks for that custom field.
    *   You can add/remove existing tasks.
-   *   Each task needs to have a title (eg 'title'  => ts( 'Add Contacts to Group')) and a class
+   *   Each task needs to have a title (eg 'title'  => ts( 'Group - add contacts')) and a class
    *   (eg 'class'  => 'CRM_Contact_Form_Task_AddToGroup').
    *   Optional result (boolean) may also be provided. Class can be an array of classes (not sure what that does :( ).
    *   The key for new Task(s) should not conflict with the keys for core tasks of that $objectType, which can be
@@ -998,6 +998,21 @@ abstract class CRM_Utils_Hook {
     );
   }
 
+  /**
+   * This hook is called after getting the content of the mail and before tokenizing it.
+   *
+   * @param array $templates
+   *   Array fields include: html, text, subject
+   *
+   * @return mixed
+   */
+  public static function alterMailTemplates(&$templates) {
+    return self::singleton()->invoke(1, $templates,
+      self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
+      'civicrm_alterMailTemplates'
+    );
+  }
+
   /**
    * This hook is called when rendering the Manage Case screen.
    *
@@ -1188,7 +1203,7 @@ abstract class CRM_Utils_Hook {
    *                  fieldHeaders    - field headers
    *                  fields          - import fields
    *
-   * @return void
+   * @return mixed
    */
   public static function import($object, $usage, &$objectRef, &$params) {
     return self::singleton()->invoke(4, $object, $usage, $objectRef, $params,
@@ -1265,8 +1280,8 @@ abstract class CRM_Utils_Hook {
    * @param array $selector
    *   the selector object. Allows you access to the context of the search
    *
-   * @return void
-   *   modify the header and values object to pass the data u need
+   * @return mixed
+   *   modify the header and values object to pass the data you need
    */
   public static function searchColumns($objectName, &$headers, &$rows, &$selector) {
     return self::singleton()->invoke(4, $objectName, $headers, $rows, $selector,
@@ -1340,7 +1355,7 @@ abstract class CRM_Utils_Hook {
    *   float $x x position in user units
    *   float $y y position in user units
    *   boolean $reseth if true reset the last cell height (default true).
-   *   int $stretch stretch carachter mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if
+   *   int $stretch stretch character mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if
    *                necessary</li><li>2 = forced horizontal scaling</li><li>3 = character spacing only if
    *                necessary</li><li>4 = forced character spacing</li></ul>
    *   boolean $ishtml set to true if $txt is HTML content (default = false).
@@ -1627,13 +1642,13 @@ abstract class CRM_Utils_Hook {
     self::singleton()
       ->invoke(2, $exception, $request, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, 'civicrm_unhandled_exception');
     // == 4.4 ==
-    //$event = new stdClass();
-    //$event->exception = $exception;
-    //CRM_Core_LegacyErrorHandler::handleException($event);
+    // $event = new stdClass();
+    // $event->exception = $exception;
+    // CRM_Core_LegacyErrorHandler::handleException($event);
 
     // == 4.5+ ==
     $event = new \Civi\Core\Event\UnhandledExceptionEvent($exception, self::$_nullObject);
-    \Civi\Core\Container::singleton()->get('dispatcher')->dispatch("hook_civicrm_unhandled_exception", $event);
+    \Civi::service('dispatcher')->dispatch("hook_civicrm_unhandled_exception", $event);
   }
 
   /**
@@ -1803,7 +1818,7 @@ abstract class CRM_Utils_Hook {
    * @param int $otherCaseId
    * @param bool $changeClient
    *
-   * @return void
+   * @return mixed
    */
   public static function pre_case_merge($mainContactId, $mainCaseId = NULL, $otherContactId = NULL, $otherCaseId = NULL, $changeClient = FALSE) {
     return self::singleton()
@@ -1819,7 +1834,7 @@ abstract class CRM_Utils_Hook {
    * @param int $otherCaseId
    * @param bool $changeClient
    *
-   * @return void
+   * @return mixed
    */
   public static function post_case_merge($mainContactId, $mainCaseId = NULL, $otherContactId = NULL, $otherCaseId = NULL, $changeClient = FALSE) {
     return self::singleton()
@@ -1884,7 +1899,7 @@ abstract class CRM_Utils_Hook {
    */
   public static function caseChange(\Civi\CCase\Analyzer $analyzer) {
     $event = new \Civi\CCase\Event\CaseChangeEvent($analyzer);
-    \Civi\Core\Container::singleton()->get('dispatcher')->dispatch("hook_civicrm_caseChange", $event);
+    \Civi::service('dispatcher')->dispatch("hook_civicrm_caseChange", $event);
 
     self::singleton()->invoke(1, $analyzer,
       self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
@@ -1917,6 +1932,31 @@ abstract class CRM_Utils_Hook {
     );
   }
 
+  /**
+   * Modify the CiviCRM container - add new services, parameters, extensions, etc.
+   *
+   * @code
+   * use Symfony\Component\Config\Resource\FileResource;
+   * use Symfony\Component\DependencyInjection\Definition;
+   *
+   * function mymodule_civicrm_container($container) {
+   *   $container->addResource(new FileResource(__FILE__));
+   *   $container->setDefinition('mysvc', new Definition('My\Class', array()));
+   * }
+   * @endcode
+   *
+   * Tip: The container configuration will be compiled/cached. The default cache
+   * behavior is aggressive. When you first implement the hook, be sure to
+   * flush the cache. Additionally, you should relax caching during development.
+   * In `civicrm.settings.php`, set define('CIVICRM_CONTAINER_CACHE', 'auto').
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   * @see http://symfony.com/doc/current/components/dependency_injection/index.html
+   */
+  public static function container(\Symfony\Component\DependencyInjection\ContainerBuilder $container) {
+    self::singleton()->invoke(1, $container, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, 'civicrm_container');
+  }
+
   /**
    * @param array <CRM_Core_FileSearchInterface> $fileSearches
    * @return mixed
@@ -1928,8 +1968,24 @@ abstract class CRM_Utils_Hook {
     );
   }
 
+  /**
+   * Check system status.
+   *
+   * @param array $messages
+   *   Array<CRM_Utils_Check_Message>. A list of messages regarding system status.
+   * @return mixed
+   */
+  public static function check(&$messages) {
+    return self::singleton()
+      ->invoke(1, $messages, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, 'civicrm_check');
+  }
+
   /**
    * This hook is called when a query string of the CSV Batch export is generated.
+   *
+   * @param string $query
+   *
+   * @return mixed
    */
   public static function batchQuery(&$query) {
     return self::singleton()->invoke(1, $query, self::$_nullObject,
@@ -1940,6 +1996,11 @@ abstract class CRM_Utils_Hook {
 
   /**
    * This hook is called when the entries of the CSV Batch export are mapped.
+   *
+   * @param array $results
+   * @param array $items
+   *
+   * @return mixed
    */
   public static function batchItems(&$results, &$items) {
     return self::singleton()->invoke(2, $results, $items,