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; | |
c95fbf9b | 15 | use Civi\Core\CiviEventDispatcher; |
4bcd4c62 TO |
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); | |
c95fbf9b | 73 | self::$instance->dispatcher = new CiviEventDispatcher(); |
fd795073 SL |
74 | self::$instance->dispatcher->setDispatchPolicy([ |
75 | '/^civi\.setup\./' => 'run', | |
76 | '/^civi\.setupui\./' => 'run', | |
77 | '/./' => 'fail', | |
78 | ]); | |
4bcd4c62 TO |
79 | self::$instance->log = $log ? $log : new NullLogger(); |
80 | ||
81 | $pluginDir = dirname(__DIR__) . '/plugins'; | |
82 | $pluginFiles = array(); | |
83 | foreach (['*.civi-setup.php', '*/*.civi-setup.php'] as $pattern) { | |
84 | foreach ((array) glob("$pluginDir/$pattern") as $file) { | |
85 | $key = substr($file, strlen($pluginDir) + 1); | |
86 | $key = preg_replace('/\.civi-setup\.php$/', '', $key); | |
87 | $pluginFiles[$key] = $file; | |
88 | } | |
89 | } | |
90 | ksort($pluginFiles); | |
91 | ||
92 | if ($pluginCallback) { | |
93 | $pluginFiles = $pluginCallback($pluginFiles); | |
94 | } | |
95 | ||
96 | foreach ($pluginFiles as $pluginFile) { | |
97 | self::$instance->log->debug('[Setup.php] Load plugin {file}', array( | |
98 | 'file' => $pluginFile, | |
99 | )); | |
100 | require $pluginFile; | |
101 | } | |
102 | ||
103 | $event = new InitEvent(self::$instance->getModel()); | |
104 | self::$instance->getDispatcher()->dispatch('civi.setup.init', $event); | |
105 | // return $event; ...or... return self::$instance; | |
106 | } | |
107 | ||
108 | /** | |
109 | * Assert that this copy of civicrm-setup is compatible with the client. | |
110 | * | |
111 | * @param string $expectedVersion | |
112 | * @throws \Exception | |
113 | */ | |
114 | public static function assertProtocolCompatibility($expectedVersion) { | |
115 | if (version_compare(self::PROTOCOL, $expectedVersion, '<')) { | |
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 | list ($actualFirst) = explode('.', self::PROTOCOL); | |
119 | list ($expectedFirst) = explode('.', $expectedVersion); | |
120 | if ($actualFirst > $expectedFirst) { | |
121 | throw new InitException(sprintf("civicrm-setup is running protocol v%s. This application expects civicrm-setup to support protocol v%s.", self::PROTOCOL, $expectedVersion)); | |
122 | } | |
123 | } | |
124 | ||
125 | /** | |
126 | * Assert that the "Setup" subsystem is running. | |
127 | * | |
128 | * This function is mostly just a placeholder -- in practice, if | |
129 | * someone makes a failed call to `assertRunning()`, it will probably | |
130 | * manifest as an unknown class/function. But this gives us a pretty, | |
131 | * one-line, syntactically-valid way to make the assertion. | |
132 | */ | |
133 | public static function assertRunning() { | |
134 | if (!defined('CIVI_SETUP')) { | |
135 | exit("Installation plugins must only be loaded by the installer.\n"); | |
136 | } | |
137 | } | |
138 | ||
139 | /** | |
140 | * @return Setup | |
141 | */ | |
142 | public static function instance() { | |
143 | if (self::$instance === NULL) { | |
144 | throw new InitException('\Civi\Setup has not been initialized.'); | |
145 | } | |
146 | return self::$instance; | |
147 | } | |
148 | ||
149 | /** | |
150 | * @return \Psr\Log\LoggerInterface | |
151 | */ | |
152 | public static function log() { | |
153 | return self::instance()->getLog(); | |
154 | } | |
155 | ||
156 | /** | |
157 | * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface | |
158 | */ | |
159 | public static function dispatcher() { | |
160 | return self::instance()->getDispatcher(); | |
161 | } | |
162 | ||
163 | // ----- Logic ---- | |
164 | ||
165 | /** | |
166 | * Determine whether the current CMS user is authorized to perform | |
167 | * installation. | |
168 | * | |
169 | * @return \Civi\Setup\Event\CheckAuthorizedEvent | |
170 | */ | |
171 | public function checkAuthorized() { | |
172 | $event = new CheckAuthorizedEvent($this->getModel()); | |
173 | return $this->getDispatcher()->dispatch('civi.setup.checkAuthorized', $event); | |
174 | } | |
175 | ||
176 | /** | |
177 | * Determine whether the local environment meets system requirements. | |
178 | * | |
179 | * @return \Civi\Setup\Event\CheckRequirementsEvent | |
180 | */ | |
181 | public function checkRequirements() { | |
182 | $event = new CheckRequirementsEvent($this->getModel()); | |
183 | return $this->getDispatcher()->dispatch('civi.setup.checkRequirements', $event); | |
184 | } | |
185 | ||
186 | /** | |
187 | * Determine whether the setting and/or schema are already installed. | |
188 | * | |
189 | * @return \Civi\Setup\Event\CheckInstalledEvent | |
190 | */ | |
191 | public function checkInstalled() { | |
192 | $event = new CheckInstalledEvent($this->getModel()); | |
193 | return $this->getDispatcher()->dispatch('civi.setup.checkInstalled', $event); | |
194 | } | |
195 | ||
196 | /** | |
197 | * Create the settings file. | |
198 | * | |
199 | * @return \Civi\Setup\Event\InstallFilesEvent | |
200 | */ | |
201 | public function installFiles() { | |
589e1936 TO |
202 | if ($this->pendingAction !== NULL) { |
203 | throw new InitException(sprintf("Cannot begin action %s. Already executing %s.", __FUNCTION__, $this->pendingAction)); | |
204 | } | |
205 | $this->pendingAction = __FUNCTION__; | |
206 | ||
207 | try { | |
208 | $event = new InstallFilesEvent($this->getModel()); | |
209 | return $this->getDispatcher()->dispatch('civi.setup.installFiles', $event); | |
210 | } | |
211 | finally { | |
212 | $this->pendingAction = NULL; | |
213 | } | |
4bcd4c62 TO |
214 | } |
215 | ||
216 | /** | |
217 | * Create the database schema. | |
218 | * | |
219 | * @return \Civi\Setup\Event\InstallDatabaseEvent | |
220 | */ | |
221 | public function installDatabase() { | |
589e1936 TO |
222 | if ($this->pendingAction !== NULL) { |
223 | throw new InitException(sprintf("Cannot begin action %s. Already executing %s.", __FUNCTION__, $this->pendingAction)); | |
224 | } | |
225 | $this->pendingAction = __FUNCTION__; | |
226 | ||
227 | try { | |
228 | $event = new InstallDatabaseEvent($this->getModel()); | |
229 | return $this->getDispatcher()->dispatch('civi.setup.installDatabase', $event); | |
230 | } | |
231 | finally { | |
232 | $this->pendingAction = NULL; | |
233 | } | |
4bcd4c62 TO |
234 | } |
235 | ||
236 | /** | |
237 | * Remove the settings file. | |
238 | * | |
239 | * @return \Civi\Setup\Event\UninstallFilesEvent | |
240 | */ | |
241 | public function uninstallFiles() { | |
589e1936 TO |
242 | if ($this->pendingAction !== NULL) { |
243 | throw new InitException(sprintf("Cannot begin action %s. Already executing %s.", __FUNCTION__, $this->pendingAction)); | |
244 | } | |
245 | $this->pendingAction = __FUNCTION__; | |
246 | ||
247 | try { | |
248 | $event = new UninstallFilesEvent($this->getModel()); | |
249 | return $this->getDispatcher()->dispatch('civi.setup.uninstallFiles', $event); | |
250 | } | |
251 | finally { | |
252 | $this->pendingAction = NULL; | |
253 | } | |
4bcd4c62 TO |
254 | } |
255 | ||
256 | /** | |
257 | * Remove the database schema. | |
258 | * | |
259 | * @return \Civi\Setup\Event\UninstallDatabaseEvent | |
260 | */ | |
261 | public function uninstallDatabase() { | |
589e1936 TO |
262 | if ($this->pendingAction !== NULL) { |
263 | throw new InitException(sprintf("Cannot begin action %s. Already executing %s.", __FUNCTION__, $this->pendingAction)); | |
264 | } | |
265 | $this->pendingAction = __FUNCTION__; | |
266 | ||
267 | try { | |
268 | $event = new UninstallDatabaseEvent($this->getModel()); | |
269 | return $this->getDispatcher()->dispatch('civi.setup.uninstallDatabase', $event); | |
270 | } | |
271 | finally { | |
272 | $this->pendingAction = NULL; | |
273 | } | |
4bcd4c62 TO |
274 | } |
275 | ||
276 | /** | |
277 | * Create a page-controller for a web-based installation form. | |
278 | * | |
279 | * @return \Civi\Setup\UI\Event\UIConstructEvent | |
280 | */ | |
281 | public function createController() { | |
282 | $event = new UIConstructEvent($this->getModel()); | |
283 | return $this->getDispatcher()->dispatch('civi.setupui.construct', $event); | |
284 | } | |
285 | ||
286 | // ----- Accessors ----- | |
287 | ||
288 | /** | |
289 | * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface | |
290 | */ | |
291 | public function getDispatcher() { | |
292 | return $this->dispatcher; | |
293 | } | |
294 | ||
295 | /** | |
296 | * @return \Civi\Setup\Model | |
297 | */ | |
298 | public function getModel() { | |
299 | return $this->model; | |
300 | } | |
301 | ||
302 | /** | |
303 | * @return \Psr\Log\LoggerInterface | |
304 | */ | |
305 | public function getLog() { | |
306 | return $this->log; | |
307 | } | |
308 | ||
589e1936 TO |
309 | /** |
310 | * @return NULL|string | |
311 | * The name of a pending installation action, or NULL if none are active. | |
312 | * Ex: 'installDatabase', 'uninstallFiles' | |
313 | */ | |
314 | public function getPendingAction() { | |
315 | return $this->pendingAction; | |
316 | } | |
317 | ||
4bcd4c62 | 318 | } |