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);
72 // dev/core#1618 When Extension Upgrades are run reconcile log tables
73 $task = new CRM_Queue_Task(
74 [__CLASS__
, 'upgradeLogTables'],
76 ts('Update log tables')
78 // Set weight low so that it will be run last.
79 $queue->createItem($task, -2);
85 * Update log tables following execution of extension upgrades
87 public static function upgradeLogTables() {
88 $logging = new CRM_Logging_Schema();
89 $logging->fixSchemaDifferences();
95 * Array(string $extKey => CRM_Extension_Upgrader_Interface $upgrader)
97 protected static function getActiveUpgraders() {
98 $mapper = \CRM_Extension_System
::singleton()->getMapper();
99 $keys = self
::getActiveKeys();
102 foreach ($keys as $key) {
103 $upgrader = $mapper->getUpgrader($key);
104 if ($upgrader !== NULL) {
105 $upgraders[$key] = $upgrader;
114 protected static function getActiveKeys() {
115 $mapper = \CRM_Extension_System
::singleton()->getMapper();
117 return self
::sortKeys(array_column($mapper->getActiveModuleFiles(), 'fullName'));
119 catch (CircularDependencyException
$e) {
120 CRM_Core_Error
::debug_log_message("Failed to identify extensions. Circular dependency. " . $e->getMessage());
123 catch (ElementNotFoundException
$e) {
124 CRM_Core_Error
::debug_log_message("Failed to identify extensions. Unrecognized dependency. " . $e->getMessage());
130 * @param string[] $keys
133 * @throws \CRM_Extension_Exception
134 * @throws \MJS\TopSort\CircularDependencyException
135 * @throws \MJS\TopSort\ElementNotFoundException
137 protected static function sortKeys($keys) {
138 $infos = CRM_Extension_System
::singleton()->getMapper()->getAllInfos();
140 // Start with our inputs in a normalized form.
141 $todoKeys = array_unique($keys);
144 // Goal: Add all active items to $sorter and flag $doneKeys['org.example.foobar']=1.
146 $sorter = new \MJS\TopSort\Implementations\
FixedArraySort();
148 while (!empty($todoKeys)) {
149 $key = array_shift($todoKeys);
150 if (isset($doneKeys[$key])) {
155 /** @var CRM_Extension_Info $info */
156 $info = @$infos[$key];
158 if ($info && $info->requires
) {
159 $sorter->add($key, $info->requires
);
160 $todoKeys = array_merge($todoKeys, $info->requires
);
163 $sorter->add($key, []);
166 return $sorter->sort();