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 | ]); |
6a488035 | 64 | |
ce9781c5 TO |
65 | foreach (self::getActiveUpgraders() as $upgrader) { |
66 | /** @var \CRM_Extension_Upgrader_Interface $upgrader */ | |
67 | $upgrader->notify('upgrade', ['enqueue', $queue]); | |
68 | } | |
69 | ||
6a488035 TO |
70 | CRM_Utils_Hook::upgrade('enqueue', $queue); |
71 | ||
2bbbd9b2 SL |
72 | // dev/core#1618 When Extension Upgrades are run reconcile log tables |
73 | $task = new CRM_Queue_Task( | |
74 | [__CLASS__, 'upgradeLogTables'], | |
75 | [], | |
76 | ts('Update log tables') | |
77 | ); | |
78 | // Set weight low so that it will be run last. | |
79 | $queue->createItem($task, -2); | |
80 | ||
6a488035 TO |
81 | return $queue; |
82 | } | |
83 | ||
2bbbd9b2 SL |
84 | /** |
85 | * Update log tables following execution of extension upgrades | |
86 | */ | |
87 | public static function upgradeLogTables() { | |
88 | $logging = new CRM_Logging_Schema(); | |
89 | $logging->fixSchemaDifferences(); | |
90 | return TRUE; | |
91 | } | |
92 | ||
ce9781c5 TO |
93 | /** |
94 | * @return array | |
95 | * Array(string $extKey => CRM_Extension_Upgrader_Interface $upgrader) | |
96 | */ | |
97 | protected static function getActiveUpgraders() { | |
98 | $mapper = \CRM_Extension_System::singleton()->getMapper(); | |
99 | $keys = self::getActiveKeys(); | |
100 | ||
101 | $upgraders = []; | |
102 | foreach ($keys as $key) { | |
103 | $upgrader = $mapper->getUpgrader($key); | |
104 | if ($upgrader !== NULL) { | |
105 | $upgraders[$key] = $upgrader; | |
106 | } | |
107 | } | |
108 | return $upgraders; | |
109 | } | |
110 | ||
111 | /** | |
112 | * @return string[] | |
113 | */ | |
114 | protected static function getActiveKeys() { | |
115 | $mapper = \CRM_Extension_System::singleton()->getMapper(); | |
116 | try { | |
117 | return self::sortKeys(array_column($mapper->getActiveModuleFiles(), 'fullName')); | |
118 | } | |
119 | catch (CircularDependencyException $e) { | |
120 | CRM_Core_Error::debug_log_message("Failed to identify extensions. Circular dependency. " . $e->getMessage()); | |
121 | return []; | |
122 | } | |
123 | catch (ElementNotFoundException $e) { | |
124 | CRM_Core_Error::debug_log_message("Failed to identify extensions. Unrecognized dependency. " . $e->getMessage()); | |
125 | return []; | |
126 | } | |
127 | } | |
128 | ||
129 | /** | |
13182e01 CW |
130 | * Sorts active extensions according to their dependencies |
131 | * | |
ce9781c5 | 132 | * @param string[] $keys |
13182e01 | 133 | * Names of all active modules |
ce9781c5 TO |
134 | * |
135 | * @return string[] | |
136 | * @throws \CRM_Extension_Exception | |
137 | * @throws \MJS\TopSort\CircularDependencyException | |
138 | * @throws \MJS\TopSort\ElementNotFoundException | |
139 | */ | |
140 | protected static function sortKeys($keys) { | |
141 | $infos = CRM_Extension_System::singleton()->getMapper()->getAllInfos(); | |
142 | ||
13182e01 | 143 | // Ensure a stable starting order. |
ce9781c5 TO |
144 | $todoKeys = array_unique($keys); |
145 | sort($todoKeys); | |
146 | ||
ce9781c5 TO |
147 | $sorter = new \MJS\TopSort\Implementations\FixedArraySort(); |
148 | ||
13182e01 | 149 | foreach ($todoKeys as $key) { |
ce9781c5 | 150 | /** @var CRM_Extension_Info $info */ |
13182e01 | 151 | $info = $infos[$key] ?? NULL; |
ce9781c5 | 152 | |
13182e01 CW |
153 | // Add dependencies |
154 | if ($info) { | |
155 | // Filter out missing dependencies; missing modules cannot be upgraded | |
156 | $requires = array_intersect($info->requires ?? [], $keys); | |
157 | $sorter->add($key, $requires); | |
ce9781c5 | 158 | } |
13182e01 | 159 | // This shouldn't ever happen if this function is being passed a list of active extensions. |
ce9781c5 | 160 | else { |
13182e01 | 161 | throw new CRM_Extension_Exception('Invalid extension key: "' . $key . '"'); |
ce9781c5 TO |
162 | } |
163 | } | |
164 | return $sorter->sort(); | |
165 | } | |
166 | ||
6a488035 | 167 | } |