[REF] Replace the deprecated system_rebuild_module_data function with equivilant...
[civicrm-core.git] / setup / src / Setup.php
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
19 const PROTOCOL = '1.0';
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
44 /**
45 * @var string|null
46 */
47 protected $pendingAction = NULL;
48
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() {
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 }
209 }
210
211 /**
212 * Create the database schema.
213 *
214 * @return \Civi\Setup\Event\InstallDatabaseEvent
215 */
216 public function installDatabase() {
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 }
229 }
230
231 /**
232 * Remove the settings file.
233 *
234 * @return \Civi\Setup\Event\UninstallFilesEvent
235 */
236 public function uninstallFiles() {
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 }
249 }
250
251 /**
252 * Remove the database schema.
253 *
254 * @return \Civi\Setup\Event\UninstallDatabaseEvent
255 */
256 public function uninstallDatabase() {
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 }
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
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
313 }