Commit | Line | Data |
---|---|---|
4bcd4c62 TO |
1 | <?php |
2 | namespace Civi; | |
3 | ||
4 | use Civi\Setup\Event\CheckAuthorizedEvent; | |
5 | use Civi\Setup\Event\CheckRequirementsEvent; | |
6 | use Civi\Setup\Event\CheckInstalledEvent; | |
7 | use Civi\Setup\UI\Event\UIConstructEvent; | |
8 | use Civi\Setup\Event\InitEvent; | |
9 | use Civi\Setup\Event\InstallDatabaseEvent; | |
10 | use Civi\Setup\Event\InstallFilesEvent; | |
11 | use Civi\Setup\Event\UninstallDatabaseEvent; | |
12 | use Civi\Setup\Event\UninstallFilesEvent; | |
13 | use Civi\Setup\Exception\InitException; | |
14 | use Psr\Log\NullLogger; | |
15 | use Symfony\Component\EventDispatcher\EventDispatcher; | |
16 | ||
17 | class Setup { | |
18 | ||
30bef769 | 19 | const PROTOCOL = '1.1'; |
4bcd4c62 TO |
20 | |
21 | const PRIORITY_START = 2000; | |
22 | const PRIORITY_PREPARE = 1000; | |
23 | const PRIORITY_MAIN = 0; | |
24 | const PRIORITY_LATE = -1000; | |
25 | const PRIORITY_END = -2000; | |
26 | ||
27 | private static $instance; | |
28 | ||
29 | /** | |
30 | * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface | |
31 | */ | |
32 | protected $dispatcher; | |
33 | ||
34 | /** | |
35 | * @var \Civi\Setup\Model | |
36 | */ | |
37 | protected $model; | |
38 | ||
39 | /** | |
40 | * @var \Psr\Log\LoggerInterface | |
41 | */ | |
42 | protected $log; | |
43 | ||
589e1936 TO |
44 | /** |
45 | * @var string|null | |
46 | */ | |
47 | protected $pendingAction = NULL; | |
48 | ||
4bcd4c62 TO |
49 | // ----- Static initialization ----- |
50 | ||
51 | /** | |
52 | * The initialization process loads any `*.civi-setup.php` files and | |
53 | * fires the `civi.setup.init` event. | |
54 | * | |
55 | * @param array $modelValues | |
56 | * List of default configuration options. | |
57 | * Recommended fields: 'srcPath', 'cms' | |
58 | * @param callable $pluginCallback | |
59 | * Function which manipulates the list of plugin files. | |
60 | * Use this to add, remove, or re-order callbacks. | |
61 | * function(array $files) => array | |
62 | * Ex: ['hello' => '/var/www/plugins/hello.civi-setup.php'] | |
63 | * @param \Psr\Log\LoggerInterface $log | |
64 | */ | |
65 | public static function init($modelValues = array(), $pluginCallback = NULL, $log = NULL) { | |
66 | if (!defined('CIVI_SETUP')) { | |
67 | define('CIVI_SETUP', 1); | |
68 | } | |
69 | ||
70 | self::$instance = new Setup(); | |
71 | self::$instance->model = new \Civi\Setup\Model(); | |
72 | self::$instance->model->setValues($modelValues); | |
73 | self::$instance->dispatcher = new EventDispatcher(); | |
74 | self::$instance->log = $log ? $log : new NullLogger(); | |
75 | ||
76 | $pluginDir = dirname(__DIR__) . '/plugins'; | |
77 | $pluginFiles = array(); | |
78 | foreach (['*.civi-setup.php', '*/*.civi-setup.php'] as $pattern) { | |
79 | foreach ((array) glob("$pluginDir/$pattern") as $file) { | |
80 | $key = substr($file, strlen($pluginDir) + 1); | |
81 | $key = preg_replace('/\.civi-setup\.php$/', '', $key); | |
82 | $pluginFiles[$key] = $file; | |
83 | } | |
84 | } | |
85 | ksort($pluginFiles); | |
86 | ||
87 | if ($pluginCallback) { | |
88 | $pluginFiles = $pluginCallback($pluginFiles); | |
89 | } | |
90 | ||
91 | foreach ($pluginFiles as $pluginFile) { | |
92 | self::$instance->log->debug('[Setup.php] Load plugin {file}', array( | |
93 | 'file' => $pluginFile, | |
94 | )); | |
95 | require $pluginFile; | |
96 | } | |
97 | ||
98 | $event = new InitEvent(self::$instance->getModel()); | |
99 | self::$instance->getDispatcher()->dispatch('civi.setup.init', $event); | |
100 | // return $event; ...or... return self::$instance; | |
101 | } | |
102 | ||
103 | /** | |
104 | * Assert that this copy of civicrm-setup is compatible with the client. | |
105 | * | |
106 | * @param string $expectedVersion | |
107 | * @throws \Exception | |
108 | */ | |
109 | public static function assertProtocolCompatibility($expectedVersion) { | |
110 | if (version_compare(self::PROTOCOL, $expectedVersion, '<')) { | |
111 | throw new InitException(sprintf("civicrm-setup is running protocol v%s. This application expects civicrm-setup to support protocol v%s.", self::PROTOCOL, $expectedVersion)); | |
112 | } | |
113 | list ($actualFirst) = explode('.', self::PROTOCOL); | |
114 | list ($expectedFirst) = explode('.', $expectedVersion); | |
115 | if ($actualFirst > $expectedFirst) { | |
116 | throw new InitException(sprintf("civicrm-setup is running protocol v%s. This application expects civicrm-setup to support protocol v%s.", self::PROTOCOL, $expectedVersion)); | |
117 | } | |
118 | } | |
119 | ||
120 | /** | |
121 | * Assert that the "Setup" subsystem is running. | |
122 | * | |
123 | * This function is mostly just a placeholder -- in practice, if | |
124 | * someone makes a failed call to `assertRunning()`, it will probably | |
125 | * manifest as an unknown class/function. But this gives us a pretty, | |
126 | * one-line, syntactically-valid way to make the assertion. | |
127 | */ | |
128 | public static function assertRunning() { | |
129 | if (!defined('CIVI_SETUP')) { | |
130 | exit("Installation plugins must only be loaded by the installer.\n"); | |
131 | } | |
132 | } | |
133 | ||
134 | /** | |
135 | * @return Setup | |
136 | */ | |
137 | public static function instance() { | |
138 | if (self::$instance === NULL) { | |
139 | throw new InitException('\Civi\Setup has not been initialized.'); | |
140 | } | |
141 | return self::$instance; | |
142 | } | |
143 | ||
144 | /** | |
145 | * @return \Psr\Log\LoggerInterface | |
146 | */ | |
147 | public static function log() { | |
148 | return self::instance()->getLog(); | |
149 | } | |
150 | ||
151 | /** | |
152 | * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface | |
153 | */ | |
154 | public static function dispatcher() { | |
155 | return self::instance()->getDispatcher(); | |
156 | } | |
157 | ||
158 | // ----- Logic ---- | |
159 | ||
160 | /** | |
161 | * Determine whether the current CMS user is authorized to perform | |
162 | * installation. | |
163 | * | |
164 | * @return \Civi\Setup\Event\CheckAuthorizedEvent | |
165 | */ | |
166 | public function checkAuthorized() { | |
167 | $event = new CheckAuthorizedEvent($this->getModel()); | |
168 | return $this->getDispatcher()->dispatch('civi.setup.checkAuthorized', $event); | |
169 | } | |
170 | ||
171 | /** | |
172 | * Determine whether the local environment meets system requirements. | |
173 | * | |
174 | * @return \Civi\Setup\Event\CheckRequirementsEvent | |
175 | */ | |
176 | public function checkRequirements() { | |
177 | $event = new CheckRequirementsEvent($this->getModel()); | |
178 | return $this->getDispatcher()->dispatch('civi.setup.checkRequirements', $event); | |
179 | } | |
180 | ||
181 | /** | |
182 | * Determine whether the setting and/or schema are already installed. | |
183 | * | |
184 | * @return \Civi\Setup\Event\CheckInstalledEvent | |
185 | */ | |
186 | public function checkInstalled() { | |
187 | $event = new CheckInstalledEvent($this->getModel()); | |
188 | return $this->getDispatcher()->dispatch('civi.setup.checkInstalled', $event); | |
189 | } | |
190 | ||
191 | /** | |
192 | * Create the settings file. | |
193 | * | |
194 | * @return \Civi\Setup\Event\InstallFilesEvent | |
195 | */ | |
196 | public function installFiles() { | |
589e1936 TO |
197 | if ($this->pendingAction !== NULL) { |
198 | throw new InitException(sprintf("Cannot begin action %s. Already executing %s.", __FUNCTION__, $this->pendingAction)); | |
199 | } | |
200 | $this->pendingAction = __FUNCTION__; | |
201 | ||
202 | try { | |
203 | $event = new InstallFilesEvent($this->getModel()); | |
204 | return $this->getDispatcher()->dispatch('civi.setup.installFiles', $event); | |
205 | } | |
206 | finally { | |
207 | $this->pendingAction = NULL; | |
208 | } | |
4bcd4c62 TO |
209 | } |
210 | ||
211 | /** | |
212 | * Create the database schema. | |
213 | * | |
214 | * @return \Civi\Setup\Event\InstallDatabaseEvent | |
215 | */ | |
216 | public function installDatabase() { | |
589e1936 TO |
217 | if ($this->pendingAction !== NULL) { |
218 | throw new InitException(sprintf("Cannot begin action %s. Already executing %s.", __FUNCTION__, $this->pendingAction)); | |
219 | } | |
220 | $this->pendingAction = __FUNCTION__; | |
221 | ||
222 | try { | |
223 | $event = new InstallDatabaseEvent($this->getModel()); | |
224 | return $this->getDispatcher()->dispatch('civi.setup.installDatabase', $event); | |
225 | } | |
226 | finally { | |
227 | $this->pendingAction = NULL; | |
228 | } | |
4bcd4c62 TO |
229 | } |
230 | ||
231 | /** | |
232 | * Remove the settings file. | |
233 | * | |
234 | * @return \Civi\Setup\Event\UninstallFilesEvent | |
235 | */ | |
236 | public function uninstallFiles() { | |
589e1936 TO |
237 | if ($this->pendingAction !== NULL) { |
238 | throw new InitException(sprintf("Cannot begin action %s. Already executing %s.", __FUNCTION__, $this->pendingAction)); | |
239 | } | |
240 | $this->pendingAction = __FUNCTION__; | |
241 | ||
242 | try { | |
243 | $event = new UninstallFilesEvent($this->getModel()); | |
244 | return $this->getDispatcher()->dispatch('civi.setup.uninstallFiles', $event); | |
245 | } | |
246 | finally { | |
247 | $this->pendingAction = NULL; | |
248 | } | |
4bcd4c62 TO |
249 | } |
250 | ||
251 | /** | |
252 | * Remove the database schema. | |
253 | * | |
254 | * @return \Civi\Setup\Event\UninstallDatabaseEvent | |
255 | */ | |
256 | public function uninstallDatabase() { | |
589e1936 TO |
257 | if ($this->pendingAction !== NULL) { |
258 | throw new InitException(sprintf("Cannot begin action %s. Already executing %s.", __FUNCTION__, $this->pendingAction)); | |
259 | } | |
260 | $this->pendingAction = __FUNCTION__; | |
261 | ||
262 | try { | |
263 | $event = new UninstallDatabaseEvent($this->getModel()); | |
264 | return $this->getDispatcher()->dispatch('civi.setup.uninstallDatabase', $event); | |
265 | } | |
266 | finally { | |
267 | $this->pendingAction = NULL; | |
268 | } | |
4bcd4c62 TO |
269 | } |
270 | ||
271 | /** | |
272 | * Create a page-controller for a web-based installation form. | |
273 | * | |
274 | * @return \Civi\Setup\UI\Event\UIConstructEvent | |
275 | */ | |
276 | public function createController() { | |
277 | $event = new UIConstructEvent($this->getModel()); | |
278 | return $this->getDispatcher()->dispatch('civi.setupui.construct', $event); | |
279 | } | |
280 | ||
281 | // ----- Accessors ----- | |
282 | ||
283 | /** | |
284 | * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface | |
285 | */ | |
286 | public function getDispatcher() { | |
287 | return $this->dispatcher; | |
288 | } | |
289 | ||
290 | /** | |
291 | * @return \Civi\Setup\Model | |
292 | */ | |
293 | public function getModel() { | |
294 | return $this->model; | |
295 | } | |
296 | ||
297 | /** | |
298 | * @return \Psr\Log\LoggerInterface | |
299 | */ | |
300 | public function getLog() { | |
301 | return $this->log; | |
302 | } | |
303 | ||
589e1936 TO |
304 | /** |
305 | * @return NULL|string | |
306 | * The name of a pending installation action, or NULL if none are active. | |
307 | * Ex: 'installDatabase', 'uninstallFiles' | |
308 | */ | |
309 | public function getPendingAction() { | |
310 | return $this->pendingAction; | |
311 | } | |
312 | ||
4bcd4c62 | 313 | } |