Merge pull request #15526 from civicrm/5.19
[civicrm-core.git] / CRM / Utils / Hook.php
index c4c8cd0ae98b3891eecb20265a7998b13d51b90f..3993c4349ae8b1da642b6be076f1bd695ae4002c 100644 (file)
@@ -44,16 +44,26 @@ abstract class CRM_Utils_Hook {
   const SUMMARY_BELOW = 1;
   // place hook content above
   const SUMMARY_ABOVE = 2;
-  // create your own summaries
+  /**
+   *create your own summaries
+   */
   const SUMMARY_REPLACE = 3;
 
-  static $_nullObject = NULL;
+  /**
+   * Object to pass when an object is required to be passed by params.
+   *
+   * This is supposed to be a convenience but note that it is a bad
+   * pattern as it can get contaminated & result in hard-to-diagnose bugs.
+   *
+   * @var null
+   */
+  public static $_nullObject = NULL;
 
   /**
    * We only need one instance of this object. So we use the singleton
    * pattern and cache the instance in this variable
    *
-   * @var object
+   * @var CRM_Utils_Hook
    */
   static private $_singleton = NULL;
 
@@ -77,7 +87,7 @@ abstract class CRM_Utils_Hook {
    *
    * @param bool $fresh
    *
-   * @return self
+   * @return CRM_Utils_Hook
    *   An instance of $config->userHookClass
    */
   public static function singleton($fresh = FALSE) {
@@ -91,6 +101,8 @@ abstract class CRM_Utils_Hook {
 
   /**
    * CRM_Utils_Hook constructor.
+   *
+   * @throws \CRM_Core_Exception
    */
   public function __construct() {
     $this->cache = CRM_Utils_Cache::create([
@@ -123,7 +135,7 @@ abstract class CRM_Utils_Hook {
    *
    * @return mixed
    */
-  public abstract function invokeViaUF(
+  abstract public function invokeViaUF(
     $numParams,
     &$arg1, &$arg2, &$arg3, &$arg4, &$arg5, &$arg6,
     $fnSuffix
@@ -156,6 +168,17 @@ abstract class CRM_Utils_Hook {
     &$arg1, &$arg2, &$arg3, &$arg4, &$arg5, &$arg6,
     $fnSuffix
   ) {
+    // Per https://github.com/civicrm/civicrm-core/pull/13551 we have had ongoing significant problems where hooks from modules are
+    // invoked during upgrade but not those from extensions. The issues are both that an incorrect module list & settings are cached and that
+    // some hooks REALLY need to run during upgrade - the loss of triggers during upgrade causes significant loss of data
+    // whereas loss of logTable hooks means that log tables are created for tables specifically excluded - e.g due to large
+    // quantities of low-importance data or the table not having an id field, which could cause a fatal error.
+    // Instead of not calling any hooks we only call those we know to be frequently important - if a particular extension wanted
+    // to avoid this they could do an early return on CRM_Core_Config::singleton()->isUpgradeMode
+    $upgradeFriendlyHooks = ['civicrm_alterSettingsFolders', 'civicrm_alterSettingsMetaData', 'civicrm_triggerInfo', 'civicrm_alterLogTables', 'civicrm_container'];
+    if (CRM_Core_Config::singleton()->isUpgradeMode() && !in_array($fnSuffix, $upgradeFriendlyHooks)) {
+      return;
+    }
     if (is_array($names) && !defined('CIVICRM_FORCE_LEGACY_HOOK') && \Civi\Core\Container::isContainerBooted()) {
       $event = \Civi\Core\Event\GenericHookEvent::createOrdered(
         $names,
@@ -165,6 +188,11 @@ abstract class CRM_Utils_Hook {
       return $event->getReturnValues();
     }
     else {
+      // We need to ensure tht we will still run known bootstrap related hooks even if the container is not booted.
+      $prebootContainerHooks = array_merge($upgradeFriendlyHooks, ['civicrm_entityTypes', 'civicrm_config']);
+      if (!\Civi\Core\Container::isContainerBooted() && !in_array($fnSuffix, $prebootContainerHooks)) {
+        return;
+      }
       $count = is_array($names) ? count($names) : $names;
       return $this->invokeViaUF($count, $arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $fnSuffix);
     }
@@ -223,17 +251,20 @@ abstract class CRM_Utils_Hook {
   }
 
   /**
-   * @param $civiModules
-   * @param $fnSuffix
-   * @param array $numParams
-   * @param $arg1
-   * @param $arg2
-   * @param $arg3
-   * @param $arg4
-   * @param $arg5
-   * @param $arg6
+   * Run hooks.
+   *
+   * @param array $civiModules
+   * @param string $fnSuffix
+   * @param int $numParams
+   * @param mixed $arg1
+   * @param mixed $arg2
+   * @param mixed $arg3
+   * @param mixed $arg4
+   * @param mixed $arg5
+   * @param mixed $arg6
    *
    * @return array|bool
+   * @throws \Exception
    */
   public function runHooks(
     $civiModules, $fnSuffix, $numParams,
@@ -576,6 +607,63 @@ abstract class CRM_Utils_Hook {
     );
   }
 
+  /**
+   * A theme is a set of CSS files which are loaded on CiviCRM pages. To register a new
+   * theme, add it to the $themes array. Use these properties:
+   *
+   *  - ext: string (required)
+   *         The full name of the extension which defines the theme.
+   *         Ex: "org.civicrm.themes.greenwich".
+   *  - title: string (required)
+   *         Visible title.
+   *  - help: string (optional)
+   *         Description of the theme's appearance.
+   *  - url_callback: mixed (optional)
+   *         A function ($themes, $themeKey, $cssExt, $cssFile) which returns the URL(s) for a CSS resource.
+   *         Returns either an array of URLs or PASSTHRU.
+   *         Ex: \Civi\Core\Themes\Resolvers::simple (default)
+   *         Ex: \Civi\Core\Themes\Resolvers::none
+   *  - prefix: string (optional)
+   *         A prefix within the extension folder to prepend to the file name.
+   *  - search_order: array (optional)
+   *         A list of themes to search.
+   *         Generally, the last theme should be "*fallback*" (Civi\Core\Themes::FALLBACK).
+   *  - excludes: array (optional)
+   *         A list of files (eg "civicrm:css/bootstrap.css" or "$ext:$file") which should never
+   *         be returned (they are excluded from display).
+   *
+   * @param array $themes
+   *   List of themes, keyed by name.
+   * @return null
+   *   the return value is ignored
+   */
+  public static function themes(&$themes) {
+    return self::singleton()->invoke(1, $themes,
+      self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
+      'civicrm_themes'
+    );
+  }
+
+  /**
+   * The activeTheme hook determines which theme is active.
+   *
+   * @param string $theme
+   *   The identifier for the theme. Alterable.
+   *   Ex: 'greenwich'.
+   * @param array $context
+   *   Information about the current page-request. Includes some mix of:
+   *   - page: the relative path of the current Civi page (Ex: 'civicrm/dashboard').
+   *   - themes: an instance of the Civi\Core\Themes service.
+   * @return null
+   *   the return value is ignored
+   */
+  public static function activeTheme(&$theme, $context) {
+    return self::singleton()->invoke(array('theme', 'context'), $theme, $context,
+      self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
+      'civicrm_activeTheme'
+    );
+  }
+
   /**
    * This hook is called for declaring managed entities via API.
    *
@@ -752,6 +840,21 @@ abstract class CRM_Utils_Hook {
     );
   }
 
+  /**
+   * This hook allows modification of the admin panels
+   *
+   * @param array $panels
+   *   Associated array of admin panels
+   *
+   * @return mixed
+   */
+  public static function alterAdminPanel(&$panels) {
+    return self::singleton()->invoke(array('panels'), $panels,
+      self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
+      'civicrm_alterAdminPanel'
+    );
+  }
+
   /**
    * This hook is called when sending an email / printing labels to get the values for all the
    * tokens returned by the 'tokens' hook
@@ -1270,7 +1373,7 @@ abstract class CRM_Utils_Hook {
    *
    * @return mixed
    */
-  public static function export(&$exportTempTable, &$headerRows, &$sqlColumns, &$exportMode, &$componentTable, &$ids) {
+  public static function export(&$exportTempTable, &$headerRows, &$sqlColumns, $exportMode, $componentTable, $ids) {
     return self::singleton()->invoke(['exportTempTable', 'headerRows', 'sqlColumns', 'exportMode', 'componentTable', 'ids'],
       $exportTempTable, $headerRows, $sqlColumns,
       $exportMode, $componentTable, $ids,
@@ -1596,6 +1699,7 @@ abstract class CRM_Utils_Hook {
       'civicrm_triggerInfo'
     );
   }
+
   /**
    * This hook allows changes to the spec of which tables to log.
    *
@@ -1862,7 +1966,7 @@ abstract class CRM_Utils_Hook {
   }
 
   /**
-   * @param CRM_Core_Exception Exception $exception
+   * @param CRM_Core_Exception $exception
    * @param mixed $request
    *   Reserved for future use.
    */
@@ -1874,7 +1978,7 @@ abstract class CRM_Utils_Hook {
   /**
    * This hook is called for declaring managed entities via API.
    *
-   * Note: This is a preboot hook. It will dispatch via the extension/module
+   * Note: This is a pre-boot hook. It will dispatch via the extension/module
    * subsystem but *not* the Symfony EventDispatcher.
    *
    * @param array[] $entityTypes
@@ -1981,7 +2085,6 @@ abstract class CRM_Utils_Hook {
       ->invoke(['labelName', 'label', 'format', 'participant'], $labelName, $label, $format, $participant, self::$_nullObject, self::$_nullObject, 'civicrm_alterBadge');
   }
 
-
   /**
    * This hook is called before encoding data in barcode.
    *
@@ -2201,6 +2304,24 @@ abstract class CRM_Utils_Hook {
     Civi::dispatcher()->dispatch('hook_civicrm_alterAngular', $event);
   }
 
+  /**
+   * This hook is called when building a link to a semi-static asset.
+   *
+   * @param string $asset
+   *   The name of the asset.
+   *   Ex: 'angular.json'
+   * @param array $params
+   *   List of optional arguments which influence the content.
+   * @return null
+   *   the return value is ignored
+   */
+  public static function getAssetUrl(&$asset, &$params) {
+    return self::singleton()->invoke(['asset', 'params'],
+      $asset, $params, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
+      'civicrm_getAssetUrl'
+    );
+  }
+
   /**
    * This hook is called whenever the system builds a new copy of
    * semi-static asset.
@@ -2290,7 +2411,7 @@ abstract class CRM_Utils_Hook {
   }
 
   /**
-   * @param array <CRM_Core_FileSearchInterface> $fileSearches
+   * @param array $fileSearches CRM_Core_FileSearchInterface
    * @return mixed
    */
   public static function fileSearches(&$fileSearches) {
@@ -2370,9 +2491,6 @@ abstract class CRM_Utils_Hook {
    * @param string $region
    */
   public static function coreResourceList(&$list, $region) {
-    // First allow the cms integration to add to the list
-    CRM_Core_Config::singleton()->userSystem->appendCoreResources($list);
-
     self::singleton()->invoke(['list', 'region'], $list, $region,
       self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
       'civicrm_coreResourceList'
@@ -2385,9 +2503,10 @@ abstract class CRM_Utils_Hook {
    * @see CRM_Core_Resources::entityRefFilters
    *
    * @param array $filters
+   * @param array $links
    */
-  public static function entityRefFilters(&$filters) {
-    self::singleton()->invoke(['filters'], $filters, self::$_nullObject, self::$_nullObject,
+  public static function entityRefFilters(&$filters, &$links = NULL) {
+    self::singleton()->invoke(['filters', 'links'], $filters, $links, self::$_nullObject,
       self::$_nullObject, self::$_nullObject, self::$_nullObject,
       'civicrm_entityRefFilters'
     );
@@ -2426,8 +2545,8 @@ abstract class CRM_Utils_Hook {
   /**
    * This hook is called before an inbound SMS is processed.
    *
-   * @param CRM_SMS_Message Object $message
-   *   An SMS message recieved
+   * @param \CRM_SMS_Message $message
+   *   An SMS message received
    * @return mixed
    */
   public static function inboundSMS(&$message) {
@@ -2438,7 +2557,7 @@ abstract class CRM_Utils_Hook {
    * This hook is called to modify api params of EntityRef form field
    *
    * @param array $params
-   *
+   * @param string $formName
    * @return mixed
    */
   public static function alterEntityRefParams(&$params, $formName) {