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