Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
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 | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 | 11 | |
ce9781c5 TO |
12 | use MJS\TopSort\CircularDependencyException; |
13 | use MJS\TopSort\ElementNotFoundException; | |
14 | ||
6a488035 TO |
15 | /** |
16 | * This class stores logic for managing schema upgrades in CiviCRM extensions. | |
17 | * | |
18 | * @package CRM | |
ca5cec67 | 19 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
20 | */ |
21 | class CRM_Extension_Upgrades { | |
22 | ||
23 | const QUEUE_NAME = 'ext-upgrade'; | |
24 | ||
25 | /** | |
fe482240 | 26 | * Determine whether any extensions have pending upgrades. |
6a488035 TO |
27 | * |
28 | * @return bool | |
29 | */ | |
00be9182 | 30 | public static function hasPending() { |
ce9781c5 TO |
31 | $hasTrue = function($checks) { |
32 | if (is_array($checks)) { | |
33 | foreach ($checks as $check) { | |
34 | if ($check) { | |
35 | return TRUE; | |
36 | } | |
6a488035 TO |
37 | } |
38 | } | |
ce9781c5 TO |
39 | return FALSE; |
40 | }; | |
41 | ||
42 | foreach (self::getActiveUpgraders() as $upgrader) { | |
43 | /** @var \CRM_Extension_Upgrader_Interface $upgrader */ | |
44 | if ($hasTrue($upgrader->notify('upgrade', ['check']))) { | |
45 | return TRUE; | |
46 | } | |
6a488035 TO |
47 | } |
48 | ||
ce9781c5 TO |
49 | $checks = CRM_Utils_Hook::upgrade('check'); |
50 | return $hasTrue($checks); | |
6a488035 TO |
51 | } |
52 | ||
53 | /** | |
fe482240 | 54 | * Fill a queue with upgrade tasks. |
6a488035 TO |
55 | * |
56 | * @return CRM_Queue_Queue | |
57 | */ | |
00be9182 | 58 | public static function createQueue() { |
be2fb01f | 59 | $queue = CRM_Queue_Service::singleton()->create([ |
6a488035 TO |
60 | 'type' => 'Sql', |
61 | 'name' => self::QUEUE_NAME, | |
62 | 'reset' => TRUE, | |
be2fb01f | 63 | ]); |
9416187d TO |
64 | return static::fillQueue($queue); |
65 | } | |
6a488035 | 66 | |
9416187d TO |
67 | /** |
68 | * @param \CRM_Queue_Queue $queue | |
69 | * | |
70 | * @return \CRM_Queue_Queue | |
71 | */ | |
72 | public static function fillQueue(CRM_Queue_Queue $queue): CRM_Queue_Queue { | |
ce9781c5 TO |
73 | foreach (self::getActiveUpgraders() as $upgrader) { |
74 | /** @var \CRM_Extension_Upgrader_Interface $upgrader */ | |
75 | $upgrader->notify('upgrade', ['enqueue', $queue]); | |
76 | } | |
77 | ||
6a488035 TO |
78 | CRM_Utils_Hook::upgrade('enqueue', $queue); |
79 | ||
2bbbd9b2 SL |
80 | // dev/core#1618 When Extension Upgrades are run reconcile log tables |
81 | $task = new CRM_Queue_Task( | |
82 | [__CLASS__, 'upgradeLogTables'], | |
83 | [], | |
84 | ts('Update log tables') | |
85 | ); | |
86 | // Set weight low so that it will be run last. | |
87 | $queue->createItem($task, -2); | |
88 | ||
6a488035 TO |
89 | return $queue; |
90 | } | |
91 | ||
2bbbd9b2 SL |
92 | /** |
93 | * Update log tables following execution of extension upgrades | |
94 | */ | |
95 | public static function upgradeLogTables() { | |
96 | $logging = new CRM_Logging_Schema(); | |
97 | $logging->fixSchemaDifferences(); | |
98 | return TRUE; | |
99 | } | |
100 | ||
ce9781c5 TO |
101 | /** |
102 | * @return array | |
103 | * Array(string $extKey => CRM_Extension_Upgrader_Interface $upgrader) | |
104 | */ | |
105 | protected static function getActiveUpgraders() { | |
106 | $mapper = \CRM_Extension_System::singleton()->getMapper(); | |
107 | $keys = self::getActiveKeys(); | |
108 | ||
109 | $upgraders = []; | |
110 | foreach ($keys as $key) { | |
111 | $upgrader = $mapper->getUpgrader($key); | |
112 | if ($upgrader !== NULL) { | |
113 | $upgraders[$key] = $upgrader; | |
114 | } | |
115 | } | |
116 | return $upgraders; | |
117 | } | |
118 | ||
119 | /** | |
120 | * @return string[] | |
121 | */ | |
122 | protected static function getActiveKeys() { | |
123 | $mapper = \CRM_Extension_System::singleton()->getMapper(); | |
124 | try { | |
125 | return self::sortKeys(array_column($mapper->getActiveModuleFiles(), 'fullName')); | |
126 | } | |
127 | catch (CircularDependencyException $e) { | |
128 | CRM_Core_Error::debug_log_message("Failed to identify extensions. Circular dependency. " . $e->getMessage()); | |
129 | return []; | |
130 | } | |
131 | catch (ElementNotFoundException $e) { | |
132 | CRM_Core_Error::debug_log_message("Failed to identify extensions. Unrecognized dependency. " . $e->getMessage()); | |
133 | return []; | |
134 | } | |
135 | } | |
136 | ||
137 | /** | |
13182e01 CW |
138 | * Sorts active extensions according to their dependencies |
139 | * | |
ce9781c5 | 140 | * @param string[] $keys |
13182e01 | 141 | * Names of all active modules |
ce9781c5 TO |
142 | * |
143 | * @return string[] | |
144 | * @throws \CRM_Extension_Exception | |
145 | * @throws \MJS\TopSort\CircularDependencyException | |
146 | * @throws \MJS\TopSort\ElementNotFoundException | |
147 | */ | |
148 | protected static function sortKeys($keys) { | |
149 | $infos = CRM_Extension_System::singleton()->getMapper()->getAllInfos(); | |
150 | ||
13182e01 | 151 | // Ensure a stable starting order. |
ce9781c5 TO |
152 | $todoKeys = array_unique($keys); |
153 | sort($todoKeys); | |
154 | ||
ce9781c5 TO |
155 | $sorter = new \MJS\TopSort\Implementations\FixedArraySort(); |
156 | ||
13182e01 | 157 | foreach ($todoKeys as $key) { |
ce9781c5 | 158 | /** @var CRM_Extension_Info $info */ |
13182e01 | 159 | $info = $infos[$key] ?? NULL; |
ce9781c5 | 160 | |
13182e01 CW |
161 | // Add dependencies |
162 | if ($info) { | |
163 | // Filter out missing dependencies; missing modules cannot be upgraded | |
164 | $requires = array_intersect($info->requires ?? [], $keys); | |
165 | $sorter->add($key, $requires); | |
ce9781c5 | 166 | } |
13182e01 | 167 | // This shouldn't ever happen if this function is being passed a list of active extensions. |
ce9781c5 | 168 | else { |
13182e01 | 169 | throw new CRM_Extension_Exception('Invalid extension key: "' . $key . '"'); |
ce9781c5 TO |
170 | } |
171 | } | |
172 | return $sorter->sort(); | |
173 | } | |
174 | ||
6a488035 | 175 | } |