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 | ||
72 | return $queue; | |
73 | } | |
74 | ||
ce9781c5 TO |
75 | /** |
76 | * @return array | |
77 | * Array(string $extKey => CRM_Extension_Upgrader_Interface $upgrader) | |
78 | */ | |
79 | protected static function getActiveUpgraders() { | |
80 | $mapper = \CRM_Extension_System::singleton()->getMapper(); | |
81 | $keys = self::getActiveKeys(); | |
82 | ||
83 | $upgraders = []; | |
84 | foreach ($keys as $key) { | |
85 | $upgrader = $mapper->getUpgrader($key); | |
86 | if ($upgrader !== NULL) { | |
87 | $upgraders[$key] = $upgrader; | |
88 | } | |
89 | } | |
90 | return $upgraders; | |
91 | } | |
92 | ||
93 | /** | |
94 | * @return string[] | |
95 | */ | |
96 | protected static function getActiveKeys() { | |
97 | $mapper = \CRM_Extension_System::singleton()->getMapper(); | |
98 | try { | |
99 | return self::sortKeys(array_column($mapper->getActiveModuleFiles(), 'fullName')); | |
100 | } | |
101 | catch (CircularDependencyException $e) { | |
102 | CRM_Core_Error::debug_log_message("Failed to identify extensions. Circular dependency. " . $e->getMessage()); | |
103 | return []; | |
104 | } | |
105 | catch (ElementNotFoundException $e) { | |
106 | CRM_Core_Error::debug_log_message("Failed to identify extensions. Unrecognized dependency. " . $e->getMessage()); | |
107 | return []; | |
108 | } | |
109 | } | |
110 | ||
111 | /** | |
112 | * @param string[] $keys | |
113 | * | |
114 | * @return string[] | |
115 | * @throws \CRM_Extension_Exception | |
116 | * @throws \MJS\TopSort\CircularDependencyException | |
117 | * @throws \MJS\TopSort\ElementNotFoundException | |
118 | */ | |
119 | protected static function sortKeys($keys) { | |
120 | $infos = CRM_Extension_System::singleton()->getMapper()->getAllInfos(); | |
121 | ||
122 | // Start with our inputs in a normalized form. | |
123 | $todoKeys = array_unique($keys); | |
124 | sort($todoKeys); | |
125 | ||
126 | // Goal: Add all active items to $sorter and flag $doneKeys['org.example.foobar']=1. | |
127 | $doneKeys = []; | |
128 | $sorter = new \MJS\TopSort\Implementations\FixedArraySort(); | |
129 | ||
130 | while (!empty($todoKeys)) { | |
131 | $key = array_shift($todoKeys); | |
132 | if (isset($doneKeys[$key])) { | |
133 | continue; | |
134 | } | |
135 | $doneKeys[$key] = 1; | |
136 | ||
137 | /** @var CRM_Extension_Info $info */ | |
138 | $info = @$infos[$key]; | |
139 | ||
140 | if ($info && $info->requires) { | |
141 | $sorter->add($key, $info->requires); | |
142 | $todoKeys = array_merge($todoKeys, $info->requires); | |
143 | } | |
144 | else { | |
145 | $sorter->add($key, []); | |
146 | } | |
147 | } | |
148 | return $sorter->sort(); | |
149 | } | |
150 | ||
6a488035 | 151 | } |