From ce9781c51cd49082ed097b29979d74cee858b316 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Sat, 17 Apr 2021 04:28:29 -0700 Subject: [PATCH] Extensions - If there is an ``, then send it lifecycle notifications (w/top-sort) --- CRM/Extension/Manager/Module.php | 3 + CRM/Extension/Mapper.php | 38 +++++++++++ CRM/Extension/Upgrades.php | 106 +++++++++++++++++++++++++++++-- 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/CRM/Extension/Manager/Module.php b/CRM/Extension/Manager/Module.php index 1bc20576fc..b66f633c31 100644 --- a/CRM/Extension/Manager/Module.php +++ b/CRM/Extension/Manager/Module.php @@ -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); + } } /** diff --git a/CRM/Extension/Mapper.php b/CRM/Extension/Mapper.php index 59caf165e0..3e0c9dd14e 100644 --- a/CRM/Extension/Mapper.php +++ b/CRM/Extension/Mapper.php @@ -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]; + } + } diff --git a/CRM/Extension/Upgrades.php b/CRM/Extension/Upgrades.php index 5a008e9fbc..d26a266ff6 100644 --- a/CRM/Extension/Upgrades.php +++ b/CRM/Extension/Upgrades.php @@ -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(); + } + } -- 2.25.1