3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
12 use MJS\TopSort\CircularDependencyException
;
13 use MJS\TopSort\ElementNotFoundException
;
16 * This class stores logic for managing schema upgrades in CiviCRM extensions.
19 * @copyright CiviCRM LLC https://civicrm.org/licensing
21 class CRM_Extension_Upgrades
{
23 const QUEUE_NAME
= 'ext-upgrade';
26 * Determine whether any extensions have pending upgrades.
30 public static function hasPending() {
31 $hasTrue = function($checks) {
32 if (is_array($checks)) {
33 foreach ($checks as $check) {
42 foreach (self
::getActiveUpgraders() as $upgrader) {
43 /** @var \CRM_Extension_Upgrader_Interface $upgrader */
44 if ($hasTrue($upgrader->notify('upgrade', ['check']))) {
49 $checks = CRM_Utils_Hook
::upgrade('check');
50 return $hasTrue($checks);
54 * Fill a queue with upgrade tasks.
56 * @return CRM_Queue_Queue
58 public static function createQueue() {
59 $queue = CRM_Queue_Service
::singleton()->create([
61 'name' => self
::QUEUE_NAME
,
65 foreach (self
::getActiveUpgraders() as $upgrader) {
66 /** @var \CRM_Extension_Upgrader_Interface $upgrader */
67 $upgrader->notify('upgrade', ['enqueue', $queue]);
70 CRM_Utils_Hook
::upgrade('enqueue', $queue);
77 * Array(string $extKey => CRM_Extension_Upgrader_Interface $upgrader)
79 protected static function getActiveUpgraders() {
80 $mapper = \CRM_Extension_System
::singleton()->getMapper();
81 $keys = self
::getActiveKeys();
84 foreach ($keys as $key) {
85 $upgrader = $mapper->getUpgrader($key);
86 if ($upgrader !== NULL) {
87 $upgraders[$key] = $upgrader;
96 protected static function getActiveKeys() {
97 $mapper = \CRM_Extension_System
::singleton()->getMapper();
99 return self
::sortKeys(array_column($mapper->getActiveModuleFiles(), 'fullName'));
101 catch (CircularDependencyException
$e) {
102 CRM_Core_Error
::debug_log_message("Failed to identify extensions. Circular dependency. " . $e->getMessage());
105 catch (ElementNotFoundException
$e) {
106 CRM_Core_Error
::debug_log_message("Failed to identify extensions. Unrecognized dependency. " . $e->getMessage());
112 * @param string[] $keys
115 * @throws \CRM_Extension_Exception
116 * @throws \MJS\TopSort\CircularDependencyException
117 * @throws \MJS\TopSort\ElementNotFoundException
119 protected static function sortKeys($keys) {
120 $infos = CRM_Extension_System
::singleton()->getMapper()->getAllInfos();
122 // Start with our inputs in a normalized form.
123 $todoKeys = array_unique($keys);
126 // Goal: Add all active items to $sorter and flag $doneKeys['org.example.foobar']=1.
128 $sorter = new \MJS\TopSort\Implementations\
FixedArraySort();
130 while (!empty($todoKeys)) {
131 $key = array_shift($todoKeys);
132 if (isset($doneKeys[$key])) {
137 /** @var CRM_Extension_Info $info */
138 $info = @$infos[$key];
140 if ($info && $info->requires
) {
141 $sorter->add($key, $info->requires
);
142 $todoKeys = array_merge($todoKeys, $info->requires
);
145 $sorter->add($key, []);
148 return $sorter->sort();