Extensions - If there is an `<upgrader>`, then send it lifecycle notifications (w...
authorTim Otten <totten@civicrm.org>
Sat, 17 Apr 2021 11:28:29 +0000 (04:28 -0700)
committerTim Otten <totten@civicrm.org>
Wed, 21 Apr 2021 06:28:25 +0000 (23:28 -0700)
CRM/Extension/Manager/Module.php
CRM/Extension/Mapper.php
CRM/Extension/Upgrades.php

index 1bc20576fc0bfc725ac30c83969a5d1647cabe94..b66f633c31a247bf738556b9ab9ebb9e093620e7 100644 (file)
@@ -60,6 +60,9 @@ class CRM_Extension_Manager_Module extends CRM_Extension_Manager_Base {
     if (function_exists($fnName)) {
       $fnName();
     }
+    if ($info->upgrader) {
+      $this->mapper->getUpgrader($info->key)->notify($hookName);
+    }
   }
 
   /**
index 59caf165e00925f38ee63606941722198ef46f64..3e0c9dd14e4b7dc190becc1a83d8124a807aa9b9 100644 (file)
@@ -66,6 +66,12 @@ class CRM_Extension_Mapper {
 
   protected $civicrmUrl;
 
+  /**
+   * @var array
+   *   Array(string $extKey => CRM_Extension_Upgrader_Interface $upgrader)
+   */
+  protected $upgraders = [];
+
   /**
    * @param CRM_Extension_Container_Interface $container
    * @param CRM_Utils_Cache_Interface $cache
@@ -555,4 +561,36 @@ class CRM_Extension_Mapper {
     }
   }
 
+  /**
+   * @param string $key
+   *   Long name of the extension.
+   *   Ex: 'org.example.myext'
+   *
+   * @return \CRM_Extension_Upgrader_Interface
+   */
+  public function getUpgrader(string $key) {
+    if (!array_key_exists($key, $this->upgraders)) {
+      $this->upgraders[$key] = NULL;
+
+      try {
+        $info = $this->keyToInfo($key);
+      }
+      catch (CRM_Extension_Exception_ParseException $e) {
+        CRM_Core_Session::setStatus(ts('Parse error in extension: %1', [
+          1 => $e->getMessage(),
+        ]), '', 'error');
+        CRM_Core_Error::debug_log_message("Parse error in extension: " . $e->getMessage());
+        return NULL;
+      }
+
+      if (!empty($info->upgrader)) {
+        $class = $info->upgrader;
+        $u = new $class();
+        $u->init(['key' => $key]);
+        $this->upgraders[$key] = $u;
+      }
+    }
+    return $this->upgraders[$key];
+  }
+
 }
index 5a008e9fbcce87778211030a59f5c8ca68b0069f..d26a266ff6e9d9fa254a5b9165c720708a60f95a 100644 (file)
@@ -9,6 +9,9 @@
  +--------------------------------------------------------------------+
  */
 
+use MJS\TopSort\CircularDependencyException;
+use MJS\TopSort\ElementNotFoundException;
+
 /**
  * This class stores logic for managing schema upgrades in CiviCRM extensions.
  *
@@ -25,16 +28,26 @@ class CRM_Extension_Upgrades {
    * @return bool
    */
   public static function hasPending() {
-    $checks = CRM_Utils_Hook::upgrade('check');
-    if (is_array($checks)) {
-      foreach ($checks as $check) {
-        if ($check) {
-          return TRUE;
+    $hasTrue = function($checks) {
+      if (is_array($checks)) {
+        foreach ($checks as $check) {
+          if ($check) {
+            return TRUE;
+          }
         }
       }
+      return FALSE;
+    };
+
+    foreach (self::getActiveUpgraders() as $upgrader) {
+      /** @var \CRM_Extension_Upgrader_Interface $upgrader */
+      if ($hasTrue($upgrader->notify('upgrade', ['check']))) {
+        return TRUE;
+      }
     }
 
-    return FALSE;
+    $checks = CRM_Utils_Hook::upgrade('check');
+    return $hasTrue($checks);
   }
 
   /**
@@ -49,9 +62,90 @@ class CRM_Extension_Upgrades {
       'reset' => TRUE,
     ]);
 
+    foreach (self::getActiveUpgraders() as $upgrader) {
+      /** @var \CRM_Extension_Upgrader_Interface $upgrader */
+      $upgrader->notify('upgrade', ['enqueue', $queue]);
+    }
+
     CRM_Utils_Hook::upgrade('enqueue', $queue);
 
     return $queue;
   }
 
+  /**
+   * @return array
+   *   Array(string $extKey => CRM_Extension_Upgrader_Interface $upgrader)
+   */
+  protected static function getActiveUpgraders() {
+    $mapper = \CRM_Extension_System::singleton()->getMapper();
+    $keys = self::getActiveKeys();
+
+    $upgraders = [];
+    foreach ($keys as $key) {
+      $upgrader = $mapper->getUpgrader($key);
+      if ($upgrader !== NULL) {
+        $upgraders[$key] = $upgrader;
+      }
+    }
+    return $upgraders;
+  }
+
+  /**
+   * @return string[]
+   */
+  protected static function getActiveKeys() {
+    $mapper = \CRM_Extension_System::singleton()->getMapper();
+    try {
+      return self::sortKeys(array_column($mapper->getActiveModuleFiles(), 'fullName'));
+    }
+    catch (CircularDependencyException $e) {
+      CRM_Core_Error::debug_log_message("Failed to identify extensions. Circular dependency. " . $e->getMessage());
+      return [];
+    }
+    catch (ElementNotFoundException $e) {
+      CRM_Core_Error::debug_log_message("Failed to identify extensions. Unrecognized dependency. " . $e->getMessage());
+      return [];
+    }
+  }
+
+  /**
+   * @param string[] $keys
+   *
+   * @return string[]
+   * @throws \CRM_Extension_Exception
+   * @throws \MJS\TopSort\CircularDependencyException
+   * @throws \MJS\TopSort\ElementNotFoundException
+   */
+  protected static function sortKeys($keys) {
+    $infos = CRM_Extension_System::singleton()->getMapper()->getAllInfos();
+
+    // Start with our inputs in a normalized form.
+    $todoKeys = array_unique($keys);
+    sort($todoKeys);
+
+    // Goal: Add all active items to $sorter and flag $doneKeys['org.example.foobar']=1.
+    $doneKeys = [];
+    $sorter = new \MJS\TopSort\Implementations\FixedArraySort();
+
+    while (!empty($todoKeys)) {
+      $key = array_shift($todoKeys);
+      if (isset($doneKeys[$key])) {
+        continue;
+      }
+      $doneKeys[$key] = 1;
+
+      /** @var CRM_Extension_Info $info */
+      $info = @$infos[$key];
+
+      if ($info && $info->requires) {
+        $sorter->add($key, $info->requires);
+        $todoKeys = array_merge($todoKeys, $info->requires);
+      }
+      else {
+        $sorter->add($key, []);
+      }
+    }
+    return $sorter->sort();
+  }
+
 }