1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @package toolkit |
5
|
|
|
*/ |
6
|
|
|
/** |
7
|
|
|
* The ExtensionManager class is responsible for managing all extensions |
8
|
|
|
* in Symphony. Extensions are stored on the file system in the `EXTENSIONS` |
9
|
|
|
* folder. They are autodiscovered where the Extension class name is the same |
10
|
|
|
* as it's folder name (excluding the extension prefix). |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
include_once FACE . '/interface.fileresource.php'; |
14
|
|
|
include_once TOOLKIT . '/class.extension.php'; |
15
|
|
|
|
16
|
|
|
class ExtensionManager implements FileResource |
|
|
|
|
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* An array of all the objects that the Manager is responsible for. |
20
|
|
|
* Defaults to an empty array. |
21
|
|
|
* @var array |
22
|
|
|
*/ |
23
|
|
|
protected static $_pool = array(); |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* An array of all extensions whose status is enabled |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
private static $_enabled_extensions = array(); |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* An array of all the subscriptions to Symphony delegates made by extensions. |
33
|
|
|
* @var array |
34
|
|
|
*/ |
35
|
|
|
private static $_subscriptions = array(); |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* An associative array of all the extensions in `tbl_extensions` where |
39
|
|
|
* the key is the extension name and the value is an array |
40
|
|
|
* representation of it's accompanying database row. |
41
|
|
|
* @var array |
42
|
|
|
*/ |
43
|
|
|
private static $_extensions = array(); |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* An associative array of all the providers from the enabled extensions. |
47
|
|
|
* The key is the type of object, with the value being an associative array |
48
|
|
|
* with the name, classname and path to the object |
49
|
|
|
* |
50
|
|
|
* @since Symphony 2.3 |
51
|
|
|
* @var array |
52
|
|
|
*/ |
53
|
|
|
private static $_providers = array(); |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* The constructor will populate the `$_subscriptions` variable from |
57
|
|
|
* the `tbl_extension` and `tbl_extensions_delegates` tables. |
58
|
|
|
*/ |
59
|
|
|
public function __construct() |
60
|
|
|
{ |
61
|
|
|
if (empty(self::$_subscriptions) && Symphony::Database()->isConnected()) { |
62
|
|
|
$subscriptions = Symphony::Database()->fetch( |
63
|
|
|
"SELECT t1.name, t2.page, t2.delegate, t2.callback |
64
|
|
|
FROM `tbl_extensions` as t1 INNER JOIN `tbl_extensions_delegates` as t2 ON t1.id = t2.extension_id |
65
|
|
|
WHERE t1.status = 'enabled' |
66
|
|
|
ORDER BY t2.delegate, t1.name" |
67
|
|
|
); |
68
|
|
|
|
69
|
|
|
foreach ($subscriptions as $subscription) { |
70
|
|
|
self::$_subscriptions[$subscription['delegate']][] = $subscription; |
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
public static function __getHandleFromFilename($filename) |
76
|
|
|
{ |
77
|
|
|
return false; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Given a name, returns the full class name of an Extension. |
82
|
|
|
* Extension use an 'extension' prefix. |
83
|
|
|
* |
84
|
|
|
* @param string $name |
85
|
|
|
* The extension handle |
86
|
|
|
* @return string |
87
|
|
|
*/ |
88
|
|
|
public static function __getClassName($name) |
89
|
|
|
{ |
90
|
|
|
return 'extension_' . $name; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Finds an Extension by name by searching the `EXTENSIONS` folder and |
95
|
|
|
* returns the path to the folder. |
96
|
|
|
* |
97
|
|
|
* @param string $name |
98
|
|
|
* The extension folder |
99
|
|
|
* @return string |
100
|
|
|
*/ |
101
|
|
|
public static function __getClassPath($name) |
102
|
|
|
{ |
103
|
|
|
return EXTENSIONS . strtolower("/$name"); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Given a name, return the path to the driver of the Extension. |
108
|
|
|
* |
109
|
|
|
* @see toolkit.ExtensionManager#__getClassPath() |
110
|
|
|
* @param string $name |
111
|
|
|
* The extension folder |
112
|
|
|
* @return string |
113
|
|
|
*/ |
114
|
|
|
public static function __getDriverPath($name) |
115
|
|
|
{ |
116
|
|
|
return self::__getClassPath($name) . '/extension.driver.php'; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* This function returns an instance of an extension from it's name |
121
|
|
|
* |
122
|
|
|
* @param string $name |
123
|
|
|
* The name of the Extension Class minus the extension prefix. |
124
|
|
|
* @throws SymphonyErrorPage |
125
|
|
|
* @throws Exception |
126
|
|
|
* @return Extension |
127
|
|
|
*/ |
128
|
|
|
public static function getInstance($name) |
129
|
|
|
{ |
130
|
|
|
return (isset(self::$_pool[$name]) ? self::$_pool[$name] : self::create($name)); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Populates the `ExtensionManager::$_extensions` array with all the |
135
|
|
|
* extensions stored in `tbl_extensions`. If `ExtensionManager::$_extensions` |
136
|
|
|
* isn't empty, passing true as a parameter will force the array to update |
137
|
|
|
* |
138
|
|
|
* @param boolean $update |
139
|
|
|
* Updates the `ExtensionManager::$_extensions` array even if it was |
140
|
|
|
* populated, defaults to false. |
141
|
|
|
* @throws DatabaseException |
142
|
|
|
*/ |
143
|
|
|
private static function __buildExtensionList($update = false) |
144
|
|
|
{ |
145
|
|
|
if (empty(self::$_extensions) || $update) { |
146
|
|
|
self::$_extensions = Symphony::Database()->fetch("SELECT * FROM `tbl_extensions`", 'name'); |
147
|
|
|
} |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Returns the status of an Extension given an associative array containing |
152
|
|
|
* the Extension `handle` and `version` where the `version` is the file |
153
|
|
|
* version, not the installed version. This function returns an array |
154
|
|
|
* which may include a maximum of two statuses. |
155
|
|
|
* |
156
|
|
|
* @param array $about |
157
|
|
|
* An associative array of the extension meta data, typically returned |
158
|
|
|
* by `ExtensionManager::about()`. At the very least this array needs |
159
|
|
|
* `handle` and `version` keys. |
160
|
|
|
* @return array |
161
|
|
|
* An array of extension statuses, with the possible values being |
162
|
|
|
* `EXTENSION_ENABLED`, `EXTENSION_DISABLED`, `EXTENSION_REQUIRES_UPDATE` |
163
|
|
|
* or `EXTENSION_NOT_INSTALLED`. If an extension doesn't exist, |
164
|
|
|
* `EXTENSION_NOT_INSTALLED` will be returned. |
165
|
|
|
*/ |
166
|
|
|
public static function fetchStatus($about) |
167
|
|
|
{ |
168
|
|
|
$return = array(); |
169
|
|
|
self::__buildExtensionList(); |
170
|
|
|
|
171
|
|
|
if (isset($about['handle']) && array_key_exists($about['handle'], self::$_extensions)) { |
172
|
|
|
if (self::$_extensions[$about['handle']]['status'] == 'enabled') { |
173
|
|
|
$return[] = Extension::EXTENSION_ENABLED; |
174
|
|
|
} else { |
175
|
|
|
$return[] = Extension::EXTENSION_DISABLED; |
176
|
|
|
} |
177
|
|
|
} else { |
178
|
|
|
$return[] = Extension::EXTENSION_NOT_INSTALLED; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
if (isset($about['handle'], $about['version']) && self::__requiresUpdate($about['handle'], $about['version'])) { |
|
|
|
|
182
|
|
|
$return[] = Extension::EXTENSION_REQUIRES_UPDATE; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return $return; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* A convenience method that returns an extension version from it's name. |
190
|
|
|
* |
191
|
|
|
* @param string $name |
192
|
|
|
* The name of the Extension Class minus the extension prefix. |
193
|
|
|
* @return string |
194
|
|
|
*/ |
195
|
|
|
public static function fetchInstalledVersion($name) |
196
|
|
|
{ |
197
|
|
|
self::__buildExtensionList(); |
198
|
|
|
|
199
|
|
|
return (isset(self::$_extensions[$name]) ? self::$_extensions[$name]['version'] : null); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* A convenience method that returns an extension ID from it's name. |
204
|
|
|
* |
205
|
|
|
* @param string $name |
206
|
|
|
* The name of the Extension Class minus the extension prefix. |
207
|
|
|
* @return integer |
208
|
|
|
*/ |
209
|
|
|
public static function fetchExtensionID($name) |
210
|
|
|
{ |
211
|
|
|
self::__buildExtensionList(); |
212
|
|
|
|
213
|
|
|
return self::$_extensions[$name]['id']; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Return an array all the Provider objects supplied by extensions, |
218
|
|
|
* optionally filtered by a given `$type`. |
219
|
|
|
* |
220
|
|
|
* @since Symphony 2.3 |
221
|
|
|
* @todo Add information about the possible types |
222
|
|
|
* @param string $type |
223
|
|
|
* This will only return Providers of this type. If null, which is |
224
|
|
|
* default, all providers will be returned. |
225
|
|
|
* @throws Exception |
226
|
|
|
* @throws SymphonyErrorPage |
227
|
|
|
* @return array |
228
|
|
|
* An array of objects |
229
|
|
|
*/ |
230
|
|
|
public static function getProvidersOf($type = null) |
231
|
|
|
{ |
232
|
|
|
// Loop over all extensions and build an array of providable objects |
233
|
|
|
if (empty(self::$_providers)) { |
234
|
|
|
self::$_providers = array(); |
235
|
|
|
|
236
|
|
|
foreach (self::listInstalledHandles() as $handle) { |
237
|
|
|
$obj = self::getInstance($handle); |
238
|
|
|
|
239
|
|
|
if (!method_exists($obj, 'providerOf')) { |
240
|
|
|
continue; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
$providers = $obj->providerOf(); |
244
|
|
|
|
245
|
|
|
if (empty($providers)) { |
246
|
|
|
continue; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
// For each of the matching objects (by $type), resolve the object path |
250
|
|
|
self::$_providers = array_merge_recursive(self::$_providers, $obj->providerOf()); |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
// Return an array of objects |
255
|
|
|
if (is_null($type)) { |
256
|
|
|
return self::$_providers; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
if (!isset(self::$_providers[$type])) { |
260
|
|
|
return array(); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
return self::$_providers[$type]; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* This function will return the `Cacheable` object with the appropriate |
268
|
|
|
* caching layer for the given `$key`. This `$key` should be stored in |
269
|
|
|
* the Symphony configuration in the caching group with a reference |
270
|
|
|
* to the class of the caching object. If the key is not found, this |
271
|
|
|
* will return a default `Cacheable` object created with the MySQL driver. |
272
|
|
|
* |
273
|
|
|
* @since Symphony 2.4 |
274
|
|
|
* @param string $key |
275
|
|
|
* Should be a reference in the Configuration file to the Caching class |
276
|
|
|
* @param boolean $reuse |
277
|
|
|
* By default true, which will reuse an existing Cacheable object of `$key` |
278
|
|
|
* if it exists. If false, a new instance will be generated. |
279
|
|
|
* @return Cacheable |
280
|
|
|
*/ |
281
|
|
|
public static function getCacheProvider($key = null, $reuse = true) |
282
|
|
|
{ |
283
|
|
|
$cacheDriver = Symphony::Configuration()->get($key, 'caching'); |
284
|
|
|
|
285
|
|
|
if (in_array($cacheDriver, array_keys(Symphony::ExtensionManager()->getProvidersOf('cache')))) { |
286
|
|
|
$cacheable = new $cacheDriver; |
287
|
|
|
} else { |
288
|
|
|
$cacheable = Symphony::Database(); |
289
|
|
|
$cacheDriver = 'CacheDatabase'; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
if ($reuse === false) { |
293
|
|
|
return new Cacheable($cacheable); |
294
|
|
|
} elseif (!isset(self::$_pool[$cacheDriver])) { |
295
|
|
|
self::$_pool[$cacheDriver] = new Cacheable($cacheable); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
return self::$_pool[$cacheDriver]; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Determines whether the current extension is installed or not by checking |
303
|
|
|
* for an id in `tbl_extensions` |
304
|
|
|
* |
305
|
|
|
* @param string $name |
306
|
|
|
* The name of the Extension Class minus the extension prefix. |
307
|
|
|
* @return boolean |
308
|
|
|
*/ |
309
|
|
|
private static function __requiresInstallation($name) |
310
|
|
|
{ |
311
|
|
|
self::__buildExtensionList(); |
312
|
|
|
$id = self::$_extensions[$name]['id']; |
313
|
|
|
|
314
|
|
|
return (is_numeric($id) ? false : true); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Determines whether an extension needs to be updated or not using |
319
|
|
|
* PHP's `version_compare` function. This function will return the |
320
|
|
|
* installed version if the extension requires an update, or |
321
|
|
|
* false otherwise. |
322
|
|
|
* |
323
|
|
|
* @param string $name |
324
|
|
|
* The name of the Extension Class minus the extension prefix. |
325
|
|
|
* @param string $file_version |
326
|
|
|
* The version of the extension from the **file**, not the Database. |
327
|
|
|
* @return string|boolean |
328
|
|
|
* If the given extension (by $name) requires updating, the installed |
329
|
|
|
* version is returned, otherwise, if the extension doesn't require |
330
|
|
|
* updating, false. |
331
|
|
|
*/ |
332
|
|
|
private static function __requiresUpdate($name, $file_version) |
333
|
|
|
{ |
334
|
|
|
$installed_version = self::fetchInstalledVersion($name); |
335
|
|
|
|
336
|
|
|
if (is_null($installed_version)) { |
337
|
|
|
return false; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
return (version_compare($installed_version, $file_version, '<') ? $installed_version : false); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Enabling an extension will re-register all it's delegates with Symphony. |
345
|
|
|
* It will also install or update the extension if needs be by calling the |
346
|
|
|
* extensions respective install and update methods. The enable method is |
347
|
|
|
* of the extension object is finally called. |
348
|
|
|
* |
349
|
|
|
* @see toolkit.ExtensionManager#registerDelegates() |
350
|
|
|
* @see toolkit.ExtensionManager#__canUninstallOrDisable() |
351
|
|
|
* @param string $name |
352
|
|
|
* The name of the Extension Class minus the extension prefix. |
353
|
|
|
* @throws SymphonyErrorPage |
354
|
|
|
* @throws Exception |
355
|
|
|
* @return boolean |
356
|
|
|
*/ |
357
|
|
|
public static function enable($name) |
358
|
|
|
{ |
359
|
|
|
$obj = self::getInstance($name); |
360
|
|
|
|
361
|
|
|
// If not installed, install it |
362
|
|
|
if (self::__requiresInstallation($name) && $obj->install() === false) { |
363
|
|
|
// If the installation failed, run the uninstall method which |
364
|
|
|
// should rollback the install method. #1326 |
365
|
|
|
$obj->uninstall(); |
|
|
|
|
366
|
|
|
return false; |
367
|
|
|
|
368
|
|
|
// If the extension requires updating before enabling, then update it |
369
|
|
|
} elseif (($about = self::about($name)) && ($previousVersion = self::__requiresUpdate($name, $about['version'])) !== false) { |
370
|
|
|
$obj->update($previousVersion); |
|
|
|
|
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
if (!isset($about)) { |
374
|
|
|
$about = self::about($name); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
$id = self::fetchExtensionID($name); |
378
|
|
|
|
379
|
|
|
$fields = array( |
380
|
|
|
'name' => $name, |
381
|
|
|
'status' => 'enabled', |
382
|
|
|
'version' => $about['version'] |
383
|
|
|
); |
384
|
|
|
|
385
|
|
|
// If there's no $id, the extension needs to be installed |
386
|
|
|
if (is_null($id)) { |
387
|
|
|
Symphony::Database()->insert($fields, 'tbl_extensions'); |
388
|
|
|
self::__buildExtensionList(true); |
389
|
|
|
|
390
|
|
|
// Extension is installed, so update! |
391
|
|
|
} else { |
392
|
|
|
Symphony::Database()->update($fields, 'tbl_extensions', sprintf(" `id` = %d ", $id)); |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
self::registerDelegates($name); |
396
|
|
|
|
397
|
|
|
// Now enable the extension |
398
|
|
|
$obj->enable(); |
|
|
|
|
399
|
|
|
|
400
|
|
|
return true; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Disabling an extension will prevent it from executing but retain all it's |
405
|
|
|
* settings in the relevant tables. Symphony checks that an extension can |
406
|
|
|
* be disabled using the `canUninstallorDisable()` before removing |
407
|
|
|
* all delegate subscriptions from the database and calling the extension's |
408
|
|
|
* `disable()` function. |
409
|
|
|
* |
410
|
|
|
* @see toolkit.ExtensionManager#removeDelegates() |
411
|
|
|
* @see toolkit.ExtensionManager#__canUninstallOrDisable() |
412
|
|
|
* @param string $name |
413
|
|
|
* The name of the Extension Class minus the extension prefix. |
414
|
|
|
* @throws DatabaseException |
415
|
|
|
* @throws SymphonyErrorPage |
416
|
|
|
* @throws Exception |
417
|
|
|
* @return boolean |
418
|
|
|
*/ |
419
|
|
|
public static function disable($name) |
420
|
|
|
{ |
421
|
|
|
$obj = self::getInstance($name); |
422
|
|
|
|
423
|
|
|
self::__canUninstallOrDisable($obj); |
424
|
|
|
|
425
|
|
|
$info = self::about($name); |
426
|
|
|
$id = self::fetchExtensionID($name); |
427
|
|
|
|
428
|
|
|
Symphony::Database()->update( |
429
|
|
|
array( |
430
|
|
|
'name' => $name, |
431
|
|
|
'status' => 'disabled', |
432
|
|
|
'version' => $info['version'] |
433
|
|
|
), |
434
|
|
|
'tbl_extensions', |
435
|
|
|
sprintf(" `id` = %d ", $id) |
436
|
|
|
); |
437
|
|
|
|
438
|
|
|
$obj->disable(); |
|
|
|
|
439
|
|
|
|
440
|
|
|
self::removeDelegates($name); |
441
|
|
|
|
442
|
|
|
return true; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
/** |
446
|
|
|
* Uninstalling an extension will unregister all delegate subscriptions and |
447
|
|
|
* remove all extension settings. Symphony checks that an extension can |
448
|
|
|
* be uninstalled using the `canUninstallorDisable()` before calling |
449
|
|
|
* the extension's `uninstall()` function. Alternatively, if this function |
450
|
|
|
* is called because the extension described by `$name` cannot be found |
451
|
|
|
* it's delegates and extension meta information will just be removed from the |
452
|
|
|
* database. |
453
|
|
|
* |
454
|
|
|
* @see toolkit.ExtensionManager#removeDelegates() |
455
|
|
|
* @see toolkit.ExtensionManager#__canUninstallOrDisable() |
456
|
|
|
* @param string $name |
457
|
|
|
* The name of the Extension Class minus the extension prefix. |
458
|
|
|
* @throws Exception |
459
|
|
|
* @throws SymphonyErrorPage |
460
|
|
|
* @throws DatabaseException |
461
|
|
|
* @throws Exception |
462
|
|
|
* @return boolean |
463
|
|
|
*/ |
464
|
|
|
public static function uninstall($name) |
465
|
|
|
{ |
466
|
|
|
// If this function is called because the extension doesn't exist, |
467
|
|
|
// then catch the error and just remove from the database. This |
468
|
|
|
// means that the uninstall() function will not run on the extension, |
469
|
|
|
// which may be a blessing in disguise as no entry data will be removed |
470
|
|
|
try { |
471
|
|
|
$obj = self::getInstance($name); |
472
|
|
|
self::__canUninstallOrDisable($obj); |
473
|
|
|
$obj->uninstall(); |
|
|
|
|
474
|
|
|
} catch (SymphonyErrorPage $ex) { |
475
|
|
|
// Create a consistant key |
476
|
|
|
$key = str_replace('-', '_', $ex->getTemplateName()); |
477
|
|
|
|
478
|
|
|
if ($key !== 'missing_extension') { |
479
|
|
|
throw $ex; |
480
|
|
|
} |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
self::removeDelegates($name); |
484
|
|
|
Symphony::Database()->delete('tbl_extensions', sprintf(" `name` = '%s' ", $name)); |
485
|
|
|
|
486
|
|
|
return true; |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
/** |
490
|
|
|
* This functions registers an extensions delegates in `tbl_extensions_delegates`. |
491
|
|
|
* |
492
|
|
|
* @param string $name |
493
|
|
|
* The name of the Extension Class minus the extension prefix. |
494
|
|
|
* @throws Exception |
495
|
|
|
* @throws SymphonyErrorPage |
496
|
|
|
* @return integer |
497
|
|
|
* The Extension ID |
498
|
|
|
*/ |
499
|
|
|
public static function registerDelegates($name) |
500
|
|
|
{ |
501
|
|
|
$obj = self::getInstance($name); |
502
|
|
|
$id = self::fetchExtensionID($name); |
503
|
|
|
|
504
|
|
|
if (!$id) { |
505
|
|
|
return false; |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
Symphony::Database()->delete('tbl_extensions_delegates', sprintf(" |
509
|
|
|
`extension_id` = %d ", $id |
510
|
|
|
)); |
511
|
|
|
|
512
|
|
|
$delegates = $obj->getSubscribedDelegates(); |
513
|
|
|
|
514
|
|
|
if (is_array($delegates) && !empty($delegates)) { |
515
|
|
|
foreach ($delegates as $delegate) { |
516
|
|
|
Symphony::Database()->insert( |
517
|
|
|
array( |
518
|
|
|
'extension_id' => $id, |
519
|
|
|
'page' => $delegate['page'], |
520
|
|
|
'delegate' => $delegate['delegate'], |
521
|
|
|
'callback' => $delegate['callback'] |
522
|
|
|
), |
523
|
|
|
'tbl_extensions_delegates' |
524
|
|
|
); |
525
|
|
|
} |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
// Remove the unused DB records |
529
|
|
|
self::cleanupDatabase(); |
530
|
|
|
|
531
|
|
|
return $id; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* This function will remove all delegate subscriptions for an extension |
536
|
|
|
* given an extension's name. This triggers `cleanupDatabase()` |
537
|
|
|
* |
538
|
|
|
* @see toolkit.ExtensionManager#cleanupDatabase() |
539
|
|
|
* @param string $name |
540
|
|
|
* The name of the Extension Class minus the extension prefix. |
541
|
|
|
* @return boolean |
542
|
|
|
*/ |
543
|
|
|
public static function removeDelegates($name) |
544
|
|
|
{ |
545
|
|
|
$delegates = Symphony::Database()->fetchCol('id', sprintf(" |
546
|
|
|
SELECT tbl_extensions_delegates.`id` |
547
|
|
|
FROM `tbl_extensions_delegates` |
548
|
|
|
LEFT JOIN `tbl_extensions` |
549
|
|
|
ON (`tbl_extensions`.id = `tbl_extensions_delegates`.extension_id) |
550
|
|
|
WHERE `tbl_extensions`.name = '%s'", |
551
|
|
|
$name |
552
|
|
|
)); |
553
|
|
|
|
554
|
|
|
if (!empty($delegates)) { |
555
|
|
|
Symphony::Database()->delete('tbl_extensions_delegates', " `id` IN ('". implode("', '", $delegates). "') "); |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
// Remove the unused DB records |
559
|
|
|
self::cleanupDatabase(); |
560
|
|
|
|
561
|
|
|
return true; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
/** |
565
|
|
|
* This function checks that if the given extension has provided Fields, |
566
|
|
|
* Data Sources or Events, that they aren't in use before the extension |
567
|
|
|
* is uninstalled or disabled. This prevents exceptions from occurring when |
568
|
|
|
* accessing an object that was using something provided by this Extension |
569
|
|
|
* can't anymore because it has been removed. |
570
|
|
|
* |
571
|
|
|
* @param Extension $obj |
572
|
|
|
* An extension object |
573
|
|
|
* @throws SymphonyErrorPage |
574
|
|
|
* @throws Exception |
575
|
|
|
* @return boolean |
576
|
|
|
*/ |
577
|
|
|
private static function __canUninstallOrDisable(Extension $obj) |
578
|
|
|
{ |
579
|
|
|
$extension_handle = strtolower(preg_replace('/^extension_/i', null, get_class($obj))); |
580
|
|
|
$about = self::about($extension_handle); |
581
|
|
|
|
582
|
|
|
// Fields: |
583
|
|
View Code Duplication |
if (is_dir(EXTENSIONS . "/{$extension_handle}/fields")) { |
|
|
|
|
584
|
|
|
foreach (glob(EXTENSIONS . "/{$extension_handle}/fields/field.*.php") as $file) { |
585
|
|
|
$type = preg_replace(array('/^field\./i', '/\.php$/i'), null, basename($file)); |
586
|
|
|
|
587
|
|
|
if (FieldManager::isFieldUsed($type)) { |
588
|
|
|
throw new Exception( |
589
|
|
|
__('The field ‘%s’, provided by the Extension ‘%s’, is currently in use.', array(basename($file), $about['name'])) |
590
|
|
|
. ' ' . __("Please remove it from your sections prior to uninstalling or disabling.") |
591
|
|
|
); |
592
|
|
|
} |
593
|
|
|
} |
594
|
|
|
} |
595
|
|
|
|
596
|
|
|
// Data Sources: |
597
|
|
View Code Duplication |
if (is_dir(EXTENSIONS . "/{$extension_handle}/data-sources")) { |
|
|
|
|
598
|
|
|
foreach (glob(EXTENSIONS . "/{$extension_handle}/data-sources/data.*.php") as $file) { |
599
|
|
|
$handle = preg_replace(array('/^data\./i', '/\.php$/i'), null, basename($file)); |
600
|
|
|
|
601
|
|
|
if (PageManager::isDataSourceUsed($handle)) { |
602
|
|
|
throw new Exception( |
603
|
|
|
__('The Data Source ‘%s’, provided by the Extension ‘%s’, is currently in use.', array(basename($file), $about['name'])) |
604
|
|
|
. ' ' . __("Please remove it from your pages prior to uninstalling or disabling.") |
605
|
|
|
); |
606
|
|
|
} |
607
|
|
|
} |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
// Events |
611
|
|
View Code Duplication |
if (is_dir(EXTENSIONS . "/{$extension_handle}/events")) { |
|
|
|
|
612
|
|
|
foreach (glob(EXTENSIONS . "/{$extension_handle}/events/event.*.php") as $file) { |
613
|
|
|
$handle = preg_replace(array('/^event\./i', '/\.php$/i'), null, basename($file)); |
614
|
|
|
|
615
|
|
|
if (PageManager::isEventUsed($handle)) { |
616
|
|
|
throw new Exception( |
617
|
|
|
__('The Event ‘%s’, provided by the Extension ‘%s’, is currently in use.', array(basename($file), $about['name'])) |
618
|
|
|
. ' ' . __("Please remove it from your pages prior to uninstalling or disabling.") |
619
|
|
|
); |
620
|
|
|
} |
621
|
|
|
} |
622
|
|
|
} |
623
|
|
|
|
624
|
|
|
// Text Formatters |
625
|
|
View Code Duplication |
if (is_dir(EXTENSIONS . "/{$extension_handle}/text-formatters")) { |
|
|
|
|
626
|
|
|
foreach (glob(EXTENSIONS . "/{$extension_handle}/text-formatters/formatter.*.php") as $file) { |
627
|
|
|
$handle = preg_replace(array('/^formatter\./i', '/\.php$/i'), null, basename($file)); |
628
|
|
|
|
629
|
|
|
if (FieldManager::isTextFormatterUsed($handle)) { |
630
|
|
|
throw new Exception( |
631
|
|
|
__('The Text Formatter ‘%s’, provided by the Extension ‘%s’, is currently in use.', array(basename($file), $about['name'])) |
632
|
|
|
. ' ' . __("Please remove it from your fields prior to uninstalling or disabling.") |
633
|
|
|
); |
634
|
|
|
} |
635
|
|
|
} |
636
|
|
|
} |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* Given a delegate name, notify all extensions that have registered to that |
641
|
|
|
* delegate to executing their callbacks with a `$context` array parameter |
642
|
|
|
* that contains information about the current Symphony state. |
643
|
|
|
* |
644
|
|
|
* @param string $delegate |
645
|
|
|
* The delegate name |
646
|
|
|
* @param string $page |
647
|
|
|
* The current page namespace that this delegate operates in |
648
|
|
|
* @param array $context |
649
|
|
|
* The `$context` param is an associative array that at minimum will contain |
650
|
|
|
* the current Administration class, the current page object and the delegate |
651
|
|
|
* name. Other context information may be passed to this function when it is |
652
|
|
|
* called. eg. |
653
|
|
|
* |
654
|
|
|
* array( |
655
|
|
|
* 'parent' =>& $this->Parent, |
656
|
|
|
* 'page' => $page, |
657
|
|
|
* 'delegate' => $delegate |
658
|
|
|
* ); |
659
|
|
|
* @throws Exception |
660
|
|
|
* @throws SymphonyErrorPage |
661
|
|
|
* @return null|void |
662
|
|
|
*/ |
663
|
|
|
public static function notifyMembers($delegate, $page, array $context = array()) |
664
|
|
|
{ |
665
|
|
|
// Make sure $page is an array |
666
|
|
|
if (!is_array($page)) { |
667
|
|
|
$page = array($page); |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
// Support for global delegate subscription |
671
|
|
|
if (!in_array('*', $page)) { |
672
|
|
|
$page[] = '*'; |
673
|
|
|
} |
674
|
|
|
|
675
|
|
|
$services = array(); |
676
|
|
|
|
677
|
|
|
if (isset(self::$_subscriptions[$delegate])) { |
678
|
|
|
foreach (self::$_subscriptions[$delegate] as $subscription) { |
679
|
|
|
if (!in_array($subscription['page'], $page)) { |
680
|
|
|
continue; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
$services[] = $subscription; |
684
|
|
|
} |
685
|
|
|
} |
686
|
|
|
|
687
|
|
|
if (empty($services)) { |
688
|
|
|
return null; |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
$context += array('page' => $page, 'delegate' => $delegate); |
692
|
|
|
$profiling = Symphony::Profiler() instanceof Profiler; |
693
|
|
|
|
694
|
|
|
foreach ($services as $s) { |
695
|
|
|
if ($profiling) { |
696
|
|
|
// Initial seeding and query count |
697
|
|
|
Symphony::Profiler()->seed(); |
698
|
|
|
$queries = Symphony::Database()->queryCount(); |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
// Get instance of extension and execute the callback passing |
702
|
|
|
// the `$context` along |
|
|
|
|
703
|
|
|
$obj = self::getInstance($s['name']); |
704
|
|
|
|
705
|
|
|
if (is_object($obj) && method_exists($obj, $s['callback'])) { |
706
|
|
|
$obj->{$s['callback']}($context); |
707
|
|
|
} |
708
|
|
|
|
709
|
|
|
// Complete the Profiling sample |
710
|
|
|
if ($profiling) { |
711
|
|
|
$queries = Symphony::Database()->queryCount() - $queries; |
|
|
|
|
712
|
|
|
Symphony::Profiler()->sample($delegate . '|' . $s['name'], PROFILE_LAP, 'Delegate', $queries); |
713
|
|
|
} |
714
|
|
|
} |
715
|
|
|
} |
716
|
|
|
|
717
|
|
|
/** |
718
|
|
|
* Returns an array of all the enabled extensions available |
719
|
|
|
* |
720
|
|
|
* @return array |
721
|
|
|
*/ |
722
|
|
|
public static function listInstalledHandles() |
723
|
|
|
{ |
724
|
|
|
if (empty(self::$_enabled_extensions) && Symphony::Database()->isConnected()) { |
725
|
|
|
self::$_enabled_extensions = Symphony::Database()->fetchCol( |
726
|
|
|
'name', |
727
|
|
|
"SELECT `name` FROM `tbl_extensions` WHERE `status` = 'enabled'" |
728
|
|
|
); |
729
|
|
|
} |
730
|
|
|
return self::$_enabled_extensions; |
731
|
|
|
} |
732
|
|
|
|
733
|
|
|
/** |
734
|
|
|
* Will return an associative array of all extensions and their about information |
735
|
|
|
* |
736
|
|
|
* @param string $filter |
737
|
|
|
* Allows a regular expression to be passed to return only extensions whose |
738
|
|
|
* folders match the filter. |
739
|
|
|
* @throws SymphonyErrorPage |
740
|
|
|
* @throws Exception |
741
|
|
|
* @return array |
742
|
|
|
* An associative array with the key being the extension folder and the value |
743
|
|
|
* being the extension's about information |
744
|
|
|
*/ |
745
|
|
|
public static function listAll($filter = '/^((?![-^?%:*|"<>]).)*$/') |
746
|
|
|
{ |
747
|
|
|
$result = array(); |
748
|
|
|
$extensions = General::listDirStructure(EXTENSIONS, $filter, false, EXTENSIONS); |
749
|
|
|
|
750
|
|
|
if (is_array($extensions) && !empty($extensions)) { |
751
|
|
|
foreach ($extensions as $extension) { |
752
|
|
|
$e = trim($extension, '/'); |
753
|
|
|
|
754
|
|
|
if ($about = self::about($e)) { |
755
|
|
|
$result[$e] = $about; |
756
|
|
|
} |
757
|
|
|
} |
758
|
|
|
} |
759
|
|
|
|
760
|
|
|
return $result; |
761
|
|
|
} |
762
|
|
|
|
763
|
|
|
/** |
764
|
|
|
* Custom user sorting function used inside `fetch` to recursively sort authors |
765
|
|
|
* by their names. |
766
|
|
|
* |
767
|
|
|
* @param array $a |
768
|
|
|
* @param array $b |
769
|
|
|
* @param integer $i |
770
|
|
|
* @return integer |
771
|
|
|
*/ |
772
|
|
|
private static function sortByAuthor($a, $b, $i = 0) |
|
|
|
|
773
|
|
|
{ |
774
|
|
|
$first = $a; |
775
|
|
|
$second = $b; |
776
|
|
|
|
777
|
|
|
if (isset($a[$i])) { |
778
|
|
|
$first = $a[$i]; |
779
|
|
|
} |
780
|
|
|
|
781
|
|
|
if (isset($b[$i])) { |
782
|
|
|
$second = $b[$i]; |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
if ($first == $a && $second == $b && $first['name'] == $second['name']) { |
786
|
|
|
return 1; |
787
|
|
|
} elseif ($first['name'] == $second['name']) { |
788
|
|
|
return self::sortByAuthor($a, $b, $i + 1); |
789
|
|
|
} else { |
790
|
|
|
return ($first['name'] < $second['name']) ? -1 : 1; |
791
|
|
|
} |
792
|
|
|
} |
793
|
|
|
|
794
|
|
|
/** |
795
|
|
|
* This function will return an associative array of Extension information. The |
796
|
|
|
* information returned is defined by the `$select` parameter, which will allow |
797
|
|
|
* a developer to restrict what information is returned about the Extension. |
798
|
|
|
* Optionally, `$where` (not implemented) and `$order_by` parameters allow a developer to |
799
|
|
|
* further refine their query. |
800
|
|
|
* |
801
|
|
|
* @param array $select (optional) |
802
|
|
|
* Accepts an array of keys to return from the listAll() method. If omitted, all keys |
803
|
|
|
* will be returned. |
804
|
|
|
* @param array $where (optional) |
805
|
|
|
* Not implemented. |
806
|
|
|
* @param string $order_by (optional) |
807
|
|
|
* Allows a developer to return the extensions in a particular order. The syntax is the |
808
|
|
|
* same as other `fetch` methods. If omitted this will return resources ordered by `name`. |
809
|
|
|
* @throws Exception |
810
|
|
|
* @throws SymphonyErrorPage |
811
|
|
|
* @return array |
812
|
|
|
* An associative array of Extension information, formatted in the same way as the |
813
|
|
|
* listAll() method. |
814
|
|
|
*/ |
815
|
|
|
public static function fetch(array $select = array(), array $where = array(), $order_by = null) |
816
|
|
|
{ |
817
|
|
|
$extensions = self::listAll(); |
818
|
|
|
$data = array(); |
819
|
|
|
|
820
|
|
|
if (empty($select) && empty($where) && is_null($order_by)) { |
821
|
|
|
return $extensions; |
822
|
|
|
} |
823
|
|
|
|
824
|
|
|
if (empty($extensions)) { |
825
|
|
|
return array(); |
826
|
|
|
} |
827
|
|
|
|
828
|
|
|
if (!is_null($order_by)) { |
829
|
|
|
$author = $name = $label = array(); |
830
|
|
|
$order_by = array_map('strtolower', explode(' ', $order_by)); |
831
|
|
|
$order = ($order_by[1] == 'desc') ? SORT_DESC : SORT_ASC; |
832
|
|
|
$sort = $order_by[0]; |
833
|
|
|
|
834
|
|
|
if ($sort == 'author') { |
835
|
|
|
foreach ($extensions as $key => $about) { |
836
|
|
|
$author[$key] = $about['author']; |
837
|
|
|
} |
838
|
|
|
|
839
|
|
|
uasort($author, array('self', 'sortByAuthor')); |
840
|
|
|
|
841
|
|
|
if ($order == SORT_DESC) { |
842
|
|
|
$author = array_reverse($author); |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
foreach ($author as $key => $value) { |
846
|
|
|
$data[$key] = $extensions[$key]; |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
$extensions = $data; |
850
|
|
|
} elseif ($sort == 'name') { |
851
|
|
|
foreach ($extensions as $key => $about) { |
852
|
|
|
$name[$key] = strtolower($about['name']); |
853
|
|
|
$label[$key] = $key; |
854
|
|
|
} |
855
|
|
|
|
856
|
|
|
array_multisort($name, $order, $label, $order, $extensions); |
857
|
|
|
} |
858
|
|
|
} |
859
|
|
|
|
860
|
|
View Code Duplication |
foreach ($extensions as $i => $e) { |
861
|
|
|
$data[$i] = array(); |
862
|
|
|
foreach ($e as $key => $value) { |
863
|
|
|
// If $select is empty, we assume every field is requested |
864
|
|
|
if (in_array($key, $select) || empty($select)) { |
865
|
|
|
$data[$i][$key] = $value; |
866
|
|
|
} |
867
|
|
|
} |
868
|
|
|
} |
869
|
|
|
|
870
|
|
|
return $data; |
871
|
|
|
} |
872
|
|
|
|
873
|
|
|
/** |
874
|
|
|
* This function will load an extension's meta information given the extension |
875
|
|
|
* `$name`. Since Symphony 2.3, this function will look for an `extension.meta.xml` |
876
|
|
|
* file inside the extension's folder. If this is not found, it will initialise |
877
|
|
|
* the extension and invoke the `about()` function. By default this extension will |
878
|
|
|
* return an associative array display the basic meta data about the given extension. |
879
|
|
|
* If the `$rawXML` parameter is passed true, and the extension has a `extension.meta.xml` |
880
|
|
|
* file, this function will return `DOMDocument` of the file. |
881
|
|
|
* |
882
|
|
|
* @param string $name |
883
|
|
|
* The name of the Extension Class minus the extension prefix. |
884
|
|
|
* @param boolean $rawXML |
885
|
|
|
* If passed as true, and is available, this function will return the |
886
|
|
|
* DOMDocument of representation of the given extension's `extension.meta.xml` |
887
|
|
|
* file. If the file is not available, the extension will return the normal |
888
|
|
|
* `about()` results. By default this is false. |
889
|
|
|
* @throws Exception |
890
|
|
|
* @throws SymphonyErrorPage |
891
|
|
|
* @return array |
892
|
|
|
* An associative array describing this extension |
893
|
|
|
*/ |
894
|
|
|
public static function about($name, $rawXML = false) |
895
|
|
|
{ |
896
|
|
|
// See if the extension has the new meta format |
897
|
|
|
if (file_exists(self::__getClassPath($name) . '/extension.meta.xml')) { |
898
|
|
|
try { |
899
|
|
|
$meta = new DOMDocument; |
900
|
|
|
$meta->load(self::__getClassPath($name) . '/extension.meta.xml'); |
901
|
|
|
$xpath = new DOMXPath($meta); |
902
|
|
|
$rootNamespace = $meta->lookupNamespaceUri($meta->namespaceURI); |
903
|
|
|
|
904
|
|
|
if (is_null($rootNamespace)) { |
905
|
|
|
throw new Exception(__('Missing default namespace definition.')); |
906
|
|
|
} else { |
907
|
|
|
$xpath->registerNamespace('ext', $rootNamespace); |
908
|
|
|
} |
909
|
|
|
} catch (Exception $ex) { |
910
|
|
|
Symphony::Engine()->throwCustomError( |
911
|
|
|
__('The %1$s file for the %2$s extension is not valid XML: %3$s', array( |
912
|
|
|
'<code>extension.meta.xml</code>', |
913
|
|
|
'<code>' . $name . '</code>', |
914
|
|
|
'<br /><code>' . $ex->getMessage() . '</code>' |
915
|
|
|
)) |
916
|
|
|
); |
917
|
|
|
} |
918
|
|
|
|
919
|
|
|
// Load <extension> |
920
|
|
|
$extension = $xpath->query('/ext:extension')->item(0); |
|
|
|
|
921
|
|
|
|
922
|
|
|
// Check to see that the extension is named correctly, if it is |
923
|
|
|
// not, then return nothing |
924
|
|
|
if (self::__getClassName($name) !== self::__getClassName($xpath->evaluate('string(@id)', $extension))) { |
925
|
|
|
return array(); |
926
|
|
|
} |
927
|
|
|
|
928
|
|
|
// If `$rawXML` is set, just return our DOMDocument instance |
929
|
|
|
if ($rawXML) { |
930
|
|
|
return $meta; |
931
|
|
|
} |
932
|
|
|
|
933
|
|
|
$about = array( |
934
|
|
|
'name' => $xpath->evaluate('string(ext:name)', $extension), |
935
|
|
|
'handle' => $name, |
936
|
|
|
'github' => $xpath->evaluate('string(ext:repo)', $extension), |
937
|
|
|
'discuss' => $xpath->evaluate('string(ext:url[@type="discuss"])', $extension), |
938
|
|
|
'homepage' => $xpath->evaluate('string(ext:url[@type="homepage"])', $extension), |
939
|
|
|
'wiki' => $xpath->evaluate('string(ext:url[@type="wiki"])', $extension), |
940
|
|
|
'issues' => $xpath->evaluate('string(ext:url[@type="issues"])', $extension), |
941
|
|
|
'status' => array() |
942
|
|
|
); |
943
|
|
|
|
944
|
|
|
// find the latest <release> (largest version number) |
945
|
|
|
$latest_release_version = '0.0.0'; |
946
|
|
|
foreach ($xpath->query('//ext:release', $extension) as $release) { |
947
|
|
|
$version = $xpath->evaluate('string(@version)', $release); |
948
|
|
|
|
949
|
|
|
if (version_compare($version, $latest_release_version, '>')) { |
950
|
|
|
$latest_release_version = $version; |
951
|
|
|
} |
952
|
|
|
} |
953
|
|
|
|
954
|
|
|
// Load the latest <release> information |
955
|
|
|
if ($release = $xpath->query("//ext:release[@version='$latest_release_version']", $extension)->item(0)) { |
956
|
|
|
$about += array( |
957
|
|
|
'version' => $xpath->evaluate('string(@version)', $release), |
958
|
|
|
'release-date' => $xpath->evaluate('string(@date)', $release) |
959
|
|
|
); |
960
|
|
|
|
961
|
|
|
// If it exists, load in the 'min/max' version data for this release |
962
|
|
|
$required_min_version = $xpath->evaluate('string(@min)', $release); |
963
|
|
|
$required_max_version = $xpath->evaluate('string(@max)', $release); |
964
|
|
|
$current_symphony_version = Symphony::Configuration()->get('version', 'symphony'); |
965
|
|
|
|
966
|
|
|
// Remove pre-release notes from the current Symphony version so that |
967
|
|
|
// we don't get false erros in the backend |
968
|
|
|
$current_symphony_version = preg_replace(array('/dev/i', '/-?beta\.?\d/i', '/-?rc\.?\d/i', '/.0/i'), '', $current_symphony_version); |
969
|
|
|
|
970
|
|
|
// Munge the version number so that it makes sense in the backend. |
971
|
|
|
// Consider, 2.3.x. As the min version, this means 2.3 onwards, |
972
|
|
|
// for the max it implies any 2.3 release. RE: #1019 |
973
|
|
|
// Also remove any .0 when doing the comparison to prevent extensions |
974
|
|
|
// that don't use semver yet. RE: #2146 |
975
|
|
|
$required_min_version = preg_replace(array('/\.x/', '/\.0$/'), '', $required_min_version); |
976
|
|
|
$required_max_version = preg_replace(array('/\.x/', '/\.0$/'), 'p', $required_max_version); |
977
|
|
|
|
978
|
|
|
// Min version |
979
|
|
|
if (!empty($required_min_version) && version_compare($current_symphony_version, $required_min_version, '<')) { |
980
|
|
|
$about['status'][] = Extension::EXTENSION_NOT_COMPATIBLE; |
981
|
|
|
$about['required_version'] = $required_min_version; |
982
|
|
|
|
983
|
|
|
// Max version |
984
|
|
|
} elseif (!empty($required_max_version) && version_compare($current_symphony_version, $required_max_version, '>')) { |
985
|
|
|
$about['status'][] = Extension::EXTENSION_NOT_COMPATIBLE; |
986
|
|
|
$about['required_version'] = str_replace('p', '.x', $required_max_version); |
987
|
|
|
} |
988
|
|
|
} |
989
|
|
|
|
990
|
|
|
// Add the <author> information |
991
|
|
|
foreach ($xpath->query('//ext:author', $extension) as $author) { |
992
|
|
|
$a = array( |
993
|
|
|
'name' => $xpath->evaluate('string(ext:name)', $author), |
994
|
|
|
'website' => $xpath->evaluate('string(ext:website)', $author), |
995
|
|
|
'github' => $xpath->evaluate('string(ext:name/@github)', $author), |
996
|
|
|
'email' => $xpath->evaluate('string(ext:email)', $author) |
997
|
|
|
); |
998
|
|
|
|
999
|
|
|
$about['author'][] = array_filter($a); |
1000
|
|
|
} |
1001
|
|
|
|
1002
|
|
|
$about['status'] = array_merge($about['status'], self::fetchStatus($about)); |
1003
|
|
|
return $about; |
1004
|
|
|
} else { |
1005
|
|
|
Symphony::Log()->pushToLog(sprintf('%s does not have an extension.meta.xml file', $name), E_DEPRECATED, true); |
1006
|
|
|
|
1007
|
|
|
return array(); |
1008
|
|
|
} |
1009
|
|
|
} |
1010
|
|
|
|
1011
|
|
|
/** |
1012
|
|
|
* Creates an instance of a given class and returns it |
1013
|
|
|
* |
1014
|
|
|
* @param string $name |
1015
|
|
|
* The name of the Extension Class minus the extension prefix. |
1016
|
|
|
* @throws Exception |
1017
|
|
|
* @throws SymphonyErrorPage |
1018
|
|
|
* @return Extension |
1019
|
|
|
*/ |
1020
|
|
|
public static function create($name) |
1021
|
|
|
{ |
1022
|
|
|
if (!isset(self::$_pool[$name])) { |
1023
|
|
|
$classname = self::__getClassName($name); |
1024
|
|
|
$path = self::__getDriverPath($name); |
1025
|
|
|
|
1026
|
|
|
if (!is_file($path)) { |
1027
|
|
|
Symphony::Engine()->throwCustomError( |
1028
|
|
|
__('Could not find extension %s at location %s.', array( |
1029
|
|
|
'<code>' . $name . '</code>', |
1030
|
|
|
'<code>' . $path . '</code>' |
1031
|
|
|
)), |
1032
|
|
|
__('Symphony Extension Missing Error'), |
1033
|
|
|
Page::HTTP_STATUS_ERROR, |
1034
|
|
|
'missing_extension', |
1035
|
|
|
array( |
1036
|
|
|
'name' => $name, |
1037
|
|
|
'path' => $path |
1038
|
|
|
) |
1039
|
|
|
); |
1040
|
|
|
} |
1041
|
|
|
|
1042
|
|
|
if (!class_exists($classname)) { |
1043
|
|
|
require_once($path); |
1044
|
|
|
} |
1045
|
|
|
|
1046
|
|
|
// Create the extension object |
1047
|
|
|
self::$_pool[$name] = new $classname(array()); |
1048
|
|
|
} |
1049
|
|
|
|
1050
|
|
|
return self::$_pool[$name]; |
1051
|
|
|
} |
1052
|
|
|
|
1053
|
|
|
/** |
1054
|
|
|
* A utility function that is used by the ExtensionManager to ensure |
1055
|
|
|
* stray delegates are not in `tbl_extensions_delegates`. It is called when |
1056
|
|
|
* a new Delegate is added or removed. |
1057
|
|
|
*/ |
1058
|
|
|
public static function cleanupDatabase() |
1059
|
|
|
{ |
1060
|
|
|
// Grab any extensions sitting in the database |
1061
|
|
|
$rows = Symphony::Database()->fetch("SELECT `name` FROM `tbl_extensions`"); |
1062
|
|
|
|
1063
|
|
|
// Iterate over each row |
1064
|
|
|
if (is_array($rows) && !empty($rows)) { |
1065
|
|
|
foreach ($rows as $r) { |
1066
|
|
|
$name = $r['name']; |
1067
|
|
|
$status = isset($r['status']) ? $r['status'] : null; |
1068
|
|
|
|
1069
|
|
|
// Grab the install location |
1070
|
|
|
$path = self::__getClassPath($name); |
1071
|
|
|
$existing_id = self::fetchExtensionID($name); |
1072
|
|
|
|
1073
|
|
|
// If it doesnt exist, remove the DB rows |
1074
|
|
|
if (!@is_dir($path)) { |
1075
|
|
|
Symphony::Database()->delete("tbl_extensions_delegates", sprintf(" `extension_id` = %d ", $existing_id)); |
1076
|
|
|
Symphony::Database()->delete('tbl_extensions', sprintf(" `id` = %d LIMIT 1", $existing_id)); |
1077
|
|
|
} elseif ($status == 'disabled') { |
1078
|
|
|
Symphony::Database()->delete("tbl_extensions_delegates", sprintf(" `extension_id` = %d ", $existing_id)); |
1079
|
|
|
} |
1080
|
|
|
} |
1081
|
|
|
} |
1082
|
|
|
} |
1083
|
|
|
} |
1084
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.