GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

ExtensionManager   F
last analyzed

Complexity

Total Complexity 140

Size/Duplication

Total Lines 1067
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 347
c 3
b 1
f 0
dl 0
loc 1067
rs 2
wmc 140

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __getClassName() 0 3 1
A __buildExtensionList() 0 4 3
A __getHandleFromFilename() 0 3 1
A getInstance() 0 3 2
A __getDriverPath() 0 3 1
A __getClassPath() 0 3 1
C notifyMembers() 0 50 12
A fetchInstalledVersion() 0 5 2
B sortByAuthor() 0 19 8
A __requiresUpdate() 0 9 3
A getCacheProvider() 0 18 4
A fetchExtensionID() 0 5 1
A __requiresInstallation() 0 6 2
A listAll() 0 16 5
C fetch() 0 56 17
A uninstall() 0 23 3
A __construct() 0 12 4
A listInstalledHandles() 0 9 3
A registerDelegates() 0 33 5
B getProvidersOf() 0 34 7
A removeDelegates() 0 19 2
B enable() 0 44 7
A disable() 0 24 1
C about() 0 114 14
B cleanupDatabase() 0 21 7
A create() 0 36 5
C __canUninstallOrDisable() 0 56 13
A fetchStatus() 0 20 6

How to fix   Complexity   

Complex Class

Complex classes like ExtensionManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExtensionManager, and based on these observations, apply Extract Interface, too.

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 auto-discovered where the Extension class name is the same
10
 * as it's folder name (excluding the extension prefix).
11
 */
12
13
class ExtensionManager implements FileResource
14
{
15
    /**
16
     * An array of all the objects that the Manager is responsible for.
17
     * Defaults to an empty array.
18
     * @var array
19
     */
20
    protected static $_pool = array();
21
22
    /**
23
     * An array of all extensions whose status is enabled
24
     * @var array
25
     */
26
    private static $_enabled_extensions = array();
27
28
    /**
29
     * An array of all the subscriptions to Symphony delegates made by extensions.
30
     * @var array
31
     */
32
    private static $_subscriptions = array();
33
34
    /**
35
     * An associative array of all the extensions in `tbl_extensions` where
36
     * the key is the extension name and the value is an array
37
     * representation of it's accompanying database row.
38
     * @var array
39
     */
40
    private static $_extensions = array();
41
42
    /**
43
     * An associative array of all the providers from the enabled extensions.
44
     * The key is the type of object, with the value being an associative array
45
     * with the name, classname and path to the object
46
     *
47
     * @since Symphony 2.3
48
     * @var array
49
     */
50
    private static $_providers = array();
51
52
    /**
53
     * The constructor will populate the `$_subscriptions` variable from
54
     * the `tbl_extension` and `tbl_extensions_delegates` tables.
55
     */
56
    public function __construct()
57
    {
58
        if (empty(self::$_subscriptions) && Symphony::Database()->isConnected()) {
59
            $subscriptions = Symphony::Database()->fetch(
60
                "SELECT t1.name, t2.page, t2.delegate, t2.callback
61
                FROM `tbl_extensions` as t1 INNER JOIN `tbl_extensions_delegates` as t2 ON t1.id = t2.extension_id
62
                WHERE t1.status = 'enabled'
63
                ORDER BY t2.delegate, t1.name"
64
            );
65
66
            foreach ($subscriptions as $subscription) {
67
                self::$_subscriptions[$subscription['delegate']][] = $subscription;
68
            }
69
        }
70
    }
71
72
    public static function __getHandleFromFilename($filename)
73
    {
74
        return false;
75
    }
76
77
    /**
78
     * Given a name, returns the full class name of an Extension.
79
     * Extension use an 'extension' prefix.
80
     *
81
     * @param string $name
82
     *  The extension handle
83
     * @return string
84
     */
85
    public static function __getClassName($name)
86
    {
87
        return 'extension_' . $name;
88
    }
89
90
    /**
91
     * Finds an Extension by name by searching the `EXTENSIONS` folder and
92
     * returns the path to the folder.
93
     *
94
     * @param string $name
95
     *  The extension folder
96
     * @return string
97
     */
98
    public static function __getClassPath($name)
99
    {
100
        return EXTENSIONS . strtolower("/$name");
0 ignored issues
show
Bug introduced by
The constant EXTENSIONS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $name instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
101
    }
102
103
    /**
104
     * Given a name, return the path to the driver of the Extension.
105
     *
106
     * @see toolkit.ExtensionManager#__getClassPath()
107
     * @param string $name
108
     *  The extension folder
109
     * @return string
110
     */
111
    public static function __getDriverPath($name)
112
    {
113
        return self::__getClassPath($name) . '/extension.driver.php';
114
    }
115
116
    /**
117
     * This function returns an instance of an extension from it's name
118
     *
119
     * @param string $name
120
     *  The name of the Extension Class minus the extension prefix.
121
     * @throws SymphonyErrorPage
122
     * @throws Exception
123
     * @return Extension
124
     */
125
    public static function getInstance($name)
126
    {
127
        return (isset(self::$_pool[$name]) ? self::$_pool[$name] : self::create($name));
128
    }
129
130
    /**
131
     * Populates the `ExtensionManager::$_extensions` array with all the
132
     * extensions stored in `tbl_extensions`. If `ExtensionManager::$_extensions`
133
     * isn't empty, passing true as a parameter will force the array to update
134
     *
135
     * @param boolean $update
136
     *  Updates the `ExtensionManager::$_extensions` array even if it was
137
     *  populated, defaults to false.
138
     * @throws DatabaseException
139
     */
140
    private static function __buildExtensionList($update = false)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$update" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$update"; expected 0 but found 1
Loading history...
141
    {
142
        if (empty(self::$_extensions) || $update) {
143
            self::$_extensions = Symphony::Database()->fetch("SELECT * FROM `tbl_extensions`", 'name');
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal SELECT * FROM `tbl_extensions` does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
144
        }
145
    }
146
147
    /**
148
     * Returns the status of an Extension given an associative array containing
149
     * the Extension `handle` and `version` where the `version` is the file
150
     * version, not the installed version. This function returns an array
151
     * which may include a maximum of two statuses.
152
     *
153
     * @param array $about
154
     *  An associative array of the extension meta data, typically returned
155
     *  by `ExtensionManager::about()`. At the very least this array needs
156
     *  `handle` and `version` keys.
157
     * @return array
158
     *  An array of extension statuses, with the possible values being
159
     * `EXTENSION_ENABLED`, `EXTENSION_DISABLED`, `EXTENSION_REQUIRES_UPDATE`
160
     *  or `EXTENSION_NOT_INSTALLED`. If an extension doesn't exist,
161
     *  `EXTENSION_NOT_INSTALLED` will be returned.
162
     */
163
    public static function fetchStatus($about)
164
    {
165
        $return = array();
166
        self::__buildExtensionList();
167
168
        if (isset($about['handle']) && array_key_exists($about['handle'], self::$_extensions)) {
169
            if (self::$_extensions[$about['handle']]['status'] == 'enabled') {
170
                $return[] = Extension::EXTENSION_ENABLED;
171
            } else {
172
                $return[] = Extension::EXTENSION_DISABLED;
173
            }
174
        } else {
175
            $return[] = Extension::EXTENSION_NOT_INSTALLED;
176
        }
177
178
        if (isset($about['handle'], $about['version']) && self::__requiresUpdate($about['handle'], $about['version'])) {
179
            $return[] = Extension::EXTENSION_REQUIRES_UPDATE;
180
        }
181
182
        return $return;
183
    }
184
185
    /**
186
     * A convenience method that returns an extension version from it's name.
187
     *
188
     * @param string $name
189
     *  The name of the Extension Class minus the extension prefix.
190
     * @return string
191
     */
192
    public static function fetchInstalledVersion($name)
193
    {
194
        self::__buildExtensionList();
195
196
        return (isset(self::$_extensions[$name]) ? self::$_extensions[$name]['version'] : null);
197
    }
198
199
    /**
200
     * A convenience method that returns an extension ID from it's name.
201
     *
202
     * @param string $name
203
     *  The name of the Extension Class minus the extension prefix.
204
     * @return integer
205
     */
206
    public static function fetchExtensionID($name)
207
    {
208
        self::__buildExtensionList();
209
210
        return self::$_extensions[$name]['id'];
211
    }
212
213
    /**
214
     * Return an array all the Provider objects supplied by extensions,
215
     * optionally filtered by a given `$type`.
216
     *
217
     * @since Symphony 2.3
218
     * @todo Add information about the possible types
219
     * @param string $type
220
     *  This will only return Providers of this type. If null, which is
221
     *  default, all providers will be returned.
222
     * @throws Exception
223
     * @throws SymphonyErrorPage
224
     * @return array
225
     *  An array of objects
226
     */
227
    public static function getProvidersOf($type = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$type" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$type"; expected 0 but found 1
Loading history...
228
    {
229
        // Loop over all extensions and build an array of providable objects
230
        if (empty(self::$_providers)) {
231
            self::$_providers = array();
232
233
            foreach (self::listInstalledHandles() as $handle) {
234
                $obj = self::getInstance($handle);
235
236
                if (!method_exists($obj, 'providerOf')) {
237
                    continue;
238
                }
239
240
                $providers = $obj->providerOf();
241
242
                if (empty($providers)) {
243
                    continue;
244
                }
245
246
                // For each of the matching objects (by $type), resolve the object path
247
                self::$_providers = array_merge_recursive(self::$_providers, $obj->providerOf());
248
            }
249
        }
250
251
        // Return an array of objects
252
        if (is_null($type)) {
253
            return self::$_providers;
254
        }
255
256
        if (!isset(self::$_providers[$type])) {
257
            return array();
258
        }
259
260
        return self::$_providers[$type];
261
    }
262
263
    /**
264
     * This function will return the `Cacheable` object with the appropriate
265
     * caching layer for the given `$key`. This `$key` should be stored in
266
     * the Symphony configuration in the caching group with a reference
267
     * to the class of the caching object. If the key is not found, this
268
     * will return a default `Cacheable` object created with the MySQL driver.
269
     *
270
     * @since Symphony 2.4
271
     * @param string $key
272
     *  Should be a reference in the Configuration file to the Caching class
273
     * @param boolean $reuse
274
     *  By default true, which will reuse an existing Cacheable object of `$key`
275
     *  if it exists. If false, a new instance will be generated.
276
     * @return Cacheable
277
     */
278
    public static function getCacheProvider($key = null, $reuse = true)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$key" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$key"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$reuse" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$reuse"; expected 0 but found 1
Loading history...
279
    {
280
        $cacheDriver = Symphony::Configuration()->get($key, 'caching');
281
282
        if (in_array($cacheDriver, array_keys(Symphony::ExtensionManager()->getProvidersOf('cache')))) {
283
            $cacheable = new $cacheDriver;
284
        } else {
285
            $cacheable = Symphony::Database();
286
            $cacheDriver = 'CacheDatabase';
287
        }
288
289
        if ($reuse === false) {
290
            return new Cacheable($cacheable);
0 ignored issues
show
Bug introduced by
It seems like $cacheable can also be of type MySQL; however, parameter $cacheProvider of Cacheable::__construct() does only seem to accept iCache, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

290
            return new Cacheable(/** @scrutinizer ignore-type */ $cacheable);
Loading history...
291
        } elseif (!isset(self::$_pool[$cacheDriver])) {
292
            self::$_pool[$cacheDriver] = new Cacheable($cacheable);
293
        }
294
295
        return self::$_pool[$cacheDriver];
296
    }
297
298
    /**
299
     * Determines whether the current extension is installed or not by checking
300
     * for an id in `tbl_extensions`
301
     *
302
     * @param string $name
303
     *  The name of the Extension Class minus the extension prefix.
304
     * @return boolean
305
     */
306
    private static function __requiresInstallation($name)
307
    {
308
        self::__buildExtensionList();
309
        $id = self::$_extensions[$name]['id'];
310
311
        return (is_numeric($id) ? false : true);
312
    }
313
314
    /**
315
     * Determines whether an extension needs to be updated or not using
316
     * PHP's `version_compare` function. This function will return the
317
     * installed version if the extension requires an update, or
318
     * false otherwise.
319
     *
320
     * @param string $name
321
     *  The name of the Extension Class minus the extension prefix.
322
     * @param string $file_version
323
     *  The version of the extension from the **file**, not the Database.
324
     * @return string|boolean
325
     *  If the given extension (by $name) requires updating, the installed
326
     *  version is returned, otherwise, if the extension doesn't require
327
     *  updating, false.
328
     */
329
    private static function __requiresUpdate($name, $file_version)
330
    {
331
        $installed_version = self::fetchInstalledVersion($name);
332
333
        if (is_null($installed_version)) {
0 ignored issues
show
introduced by
The condition is_null($installed_version) is always false.
Loading history...
334
            return false;
335
        }
336
337
        return (version_compare($installed_version, $file_version, '<') ? $installed_version : false);
338
    }
339
340
    /**
341
     * Enabling an extension will re-register all it's delegates with Symphony.
342
     * It will also install or update the extension if needs be by calling the
343
     * extensions respective install and update methods. The enable method is
344
     * of the extension object is finally called.
345
     *
346
     * @see toolkit.ExtensionManager#registerDelegates()
347
     * @see toolkit.ExtensionManager#__canUninstallOrDisable()
348
     * @param string $name
349
     *  The name of the Extension Class minus the extension prefix.
350
     * @throws SymphonyErrorPage
351
     * @throws Exception
352
     * @return boolean
353
     */
354
    public static function enable($name)
355
    {
356
        $obj = self::getInstance($name);
357
358
        // If not installed, install it
359
        if (self::__requiresInstallation($name) && $obj->install() === false) {
0 ignored issues
show
introduced by
The condition $obj->install() === false is always false.
Loading history...
360
            // If the installation failed, run the uninstall method which
361
            // should rollback the install method. #1326
362
            $obj->uninstall();
363
            return false;
364
365
            // If the extension requires updating before enabling, then update it
366
        } elseif (($about = self::about($name)) && ($previousVersion = self::__requiresUpdate($name, $about['version'])) !== false) {
367
            $obj->update($previousVersion);
368
        }
369
370
        if (!isset($about)) {
371
            $about = self::about($name);
372
        }
373
374
        $id = self::fetchExtensionID($name);
375
376
        $fields = array(
377
            'name' => $name,
378
            'status' => 'enabled',
379
            'version' => $about['version']
380
        );
381
382
        // If there's no $id, the extension needs to be installed
383
        if (is_null($id)) {
0 ignored issues
show
introduced by
The condition is_null($id) is always false.
Loading history...
384
            Symphony::Database()->insert($fields, 'tbl_extensions');
385
            self::__buildExtensionList(true);
386
387
            // Extension is installed, so update!
388
        } else {
389
            Symphony::Database()->update($fields, 'tbl_extensions', sprintf(" `id` = %d ", $id));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal `id` = %d does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
390
        }
391
392
        self::registerDelegates($name);
393
394
        // Now enable the extension
395
        $obj->enable();
396
397
        return true;
398
    }
399
400
    /**
401
     * Disabling an extension will prevent it from executing but retain all it's
402
     * settings in the relevant tables. Symphony checks that an extension can
403
     * be disabled using the `canUninstallorDisable()` before removing
404
     * all delegate subscriptions from the database and calling the extension's
405
     * `disable()` function.
406
     *
407
     * @see toolkit.ExtensionManager#removeDelegates()
408
     * @see toolkit.ExtensionManager#__canUninstallOrDisable()
409
     * @param string $name
410
     *  The name of the Extension Class minus the extension prefix.
411
     * @throws DatabaseException
412
     * @throws SymphonyErrorPage
413
     * @throws Exception
414
     * @return boolean
415
     */
416
    public static function disable($name)
417
    {
418
        $obj = self::getInstance($name);
419
420
        self::__canUninstallOrDisable($obj);
421
422
        $info = self::about($name);
423
        $id = self::fetchExtensionID($name);
424
425
        Symphony::Database()->update(
426
            array(
427
                'name' => $name,
428
                'status' => 'disabled',
429
                'version' => $info['version']
430
            ),
431
            'tbl_extensions',
432
            sprintf(" `id` = %d ", $id)
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal `id` = %d does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
433
        );
434
435
        $obj->disable();
436
437
        self::removeDelegates($name);
438
439
        return true;
440
    }
441
442
    /**
443
     * Uninstalling an extension will unregister all delegate subscriptions and
444
     * remove all extension settings. Symphony checks that an extension can
445
     * be uninstalled using the `canUninstallorDisable()` before calling
446
     * the extension's `uninstall()` function. Alternatively, if this function
447
     * is called because the extension described by `$name` cannot be found
448
     * it's delegates and extension meta information will just be removed from the
449
     * database.
450
     *
451
     * @see toolkit.ExtensionManager#removeDelegates()
452
     * @see toolkit.ExtensionManager#__canUninstallOrDisable()
453
     * @param string $name
454
     *  The name of the Extension Class minus the extension prefix.
455
     * @throws Exception
456
     * @throws SymphonyErrorPage
457
     * @throws DatabaseException
458
     * @throws Exception
459
     * @return boolean
460
     */
461
    public static function uninstall($name)
462
    {
463
        // If this function is called because the extension doesn't exist,
464
        // then catch the error and just remove from the database. This
465
        // means that the uninstall() function will not run on the extension,
466
        // which may be a blessing in disguise as no entry data will be removed
467
        try {
468
            $obj = self::getInstance($name);
469
            self::__canUninstallOrDisable($obj);
470
            $obj->uninstall();
471
        } catch (SymphonyErrorPage $ex) {
472
            // Create a consistant key
473
            $key = str_replace('-', '_', $ex->getTemplateName());
474
475
            if ($key !== 'missing_extension') {
476
                throw $ex;
477
            }
478
        }
479
480
        self::removeDelegates($name);
481
        Symphony::Database()->delete('tbl_extensions', sprintf(" `name` = '%s' ", $name));
482
483
        return true;
484
    }
485
486
    /**
487
     * This functions registers an extensions delegates in `tbl_extensions_delegates`.
488
     *
489
     * @param string $name
490
     *  The name of the Extension Class minus the extension prefix.
491
     * @throws Exception
492
     * @throws SymphonyErrorPage
493
     * @return integer
494
     *  The Extension ID
495
     */
496
    public static function registerDelegates($name)
497
    {
498
        $obj = self::getInstance($name);
499
        $id = self::fetchExtensionID($name);
500
501
        if (!$id) {
502
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
503
        }
504
505
        Symphony::Database()->delete('tbl_extensions_delegates', sprintf("
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal \n `extension_id` = %d does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
506
            `extension_id` = %d ", $id
507
        ));
508
509
        $delegates = $obj->getSubscribedDelegates();
510
511
        if (is_array($delegates) && !empty($delegates)) {
512
            foreach ($delegates as $delegate) {
513
                Symphony::Database()->insert(
514
                    array(
515
                        'extension_id' => $id,
516
                        'page' => $delegate['page'],
517
                        'delegate' => $delegate['delegate'],
518
                        'callback' => $delegate['callback']
519
                    ),
520
                    'tbl_extensions_delegates'
521
                );
522
            }
523
        }
524
525
        // Remove the unused DB records
526
        self::cleanupDatabase();
527
528
        return $id;
529
    }
530
531
    /**
532
     * This function will remove all delegate subscriptions for an extension
533
     * given an extension's name. This triggers `cleanupDatabase()`
534
     *
535
     * @see toolkit.ExtensionManager#cleanupDatabase()
536
     * @param string $name
537
     *  The name of the Extension Class minus the extension prefix.
538
     * @return boolean
539
     */
540
    public static function removeDelegates($name)
541
    {
542
        $delegates = Symphony::Database()->fetchCol('id', sprintf("
543
            SELECT tbl_extensions_delegates.`id`
544
            FROM `tbl_extensions_delegates`
545
            LEFT JOIN `tbl_extensions`
546
            ON (`tbl_extensions`.id = `tbl_extensions_delegates`.extension_id)
547
            WHERE `tbl_extensions`.name = '%s'",
548
            $name
549
        ));
550
551
        if (!empty($delegates)) {
552
            Symphony::Database()->delete('tbl_extensions_delegates', " `id` IN ('". implode("', '", $delegates). "') ");
553
        }
554
555
        // Remove the unused DB records
556
        self::cleanupDatabase();
557
558
        return true;
559
    }
560
561
    /**
562
     * This function checks that if the given extension has provided Fields,
563
     * Data Sources or Events, that they aren't in use before the extension
564
     * is uninstalled or disabled. This prevents exceptions from occurring when
565
     * accessing an object that was using something provided by this Extension
566
     * can't anymore because it has been removed.
567
     *
568
     * @param Extension $obj
569
     *  An extension object
570
     * @throws SymphonyErrorPage
571
     * @throws Exception
572
     */
573
    private static function __canUninstallOrDisable(Extension $obj)
574
    {
575
        $extension_handle = strtolower(preg_replace('/^extension_/i', null, get_class($obj)));
576
        $about = self::about($extension_handle);
577
578
        // Fields:
579
        if (is_dir(EXTENSIONS . "/{$extension_handle}/fields")) {
0 ignored issues
show
Bug introduced by
The constant EXTENSIONS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $extension_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
580
            foreach (glob(EXTENSIONS . "/{$extension_handle}/fields/field.*.php") as $file) {
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $extension_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
581
                $type = preg_replace(array('/^field\./i', '/\.php$/i'), null, basename($file));
582
583
                if (FieldManager::isFieldUsed($type)) {
584
                    throw new Exception(
585
                        __('The field ‘%s’, provided by the Extension ‘%s’, is currently in use.', array(basename($file), $about['name']))
586
                        . ' ' . __("Please remove it from your sections prior to uninstalling or disabling.")
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal Please remove it from yo...nstalling or disabling. does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
587
                    );
588
                }
589
            }
590
        }
591
592
        // Data Sources:
593
        if (is_dir(EXTENSIONS . "/{$extension_handle}/data-sources")) {
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $extension_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
594
            foreach (glob(EXTENSIONS . "/{$extension_handle}/data-sources/data.*.php") as $file) {
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $extension_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
595
                $handle = preg_replace(array('/^data\./i', '/\.php$/i'), null, basename($file));
596
597
                if (PageManager::isDataSourceUsed($handle)) {
598
                    throw new Exception(
599
                        __('The Data Source ‘%s’, provided by the Extension ‘%s’, is currently in use.', array(basename($file), $about['name']))
600
                        . ' ' . __("Please remove it from your pages prior to uninstalling or disabling.")
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal Please remove it from yo...nstalling or disabling. does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
601
                    );
602
                }
603
            }
604
        }
605
606
        // Events
607
        if (is_dir(EXTENSIONS . "/{$extension_handle}/events")) {
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $extension_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
608
            foreach (glob(EXTENSIONS . "/{$extension_handle}/events/event.*.php") as $file) {
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $extension_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
609
                $handle = preg_replace(array('/^event\./i', '/\.php$/i'), null, basename($file));
610
611
                if (PageManager::isEventUsed($handle)) {
612
                    throw new Exception(
613
                        __('The Event ‘%s’, provided by the Extension ‘%s’, is currently in use.', array(basename($file), $about['name']))
614
                        . ' ' . __("Please remove it from your pages prior to uninstalling or disabling.")
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal Please remove it from yo...nstalling or disabling. does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
615
                    );
616
                }
617
            }
618
        }
619
620
        // Text Formatters
621
        if (is_dir(EXTENSIONS . "/{$extension_handle}/text-formatters")) {
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $extension_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
622
            foreach (glob(EXTENSIONS . "/{$extension_handle}/text-formatters/formatter.*.php") as $file) {
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $extension_handle instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
623
                $handle = preg_replace(array('/^formatter\./i', '/\.php$/i'), null, basename($file));
624
625
                if (FieldManager::isTextFormatterUsed($handle)) {
626
                    throw new Exception(
627
                        __('The Text Formatter ‘%s’, provided by the Extension ‘%s’, is currently in use.', array(basename($file), $about['name']))
628
                        . ' ' . __("Please remove it from your fields prior to uninstalling or disabling.")
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal Please remove it from yo...nstalling or disabling. does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
629
                    );
630
                }
631
            }
632
        }
633
    }
634
635
    /**
636
     * Given a delegate name, notify all extensions that have registered to that
637
     * delegate to executing their callbacks with a `$context` array parameter
638
     * that contains information about the current Symphony state.
639
     *
640
     * @param string $delegate
641
     *  The delegate name
642
     * @param string $page
643
     *  The current page namespace that this delegate operates in
644
     * @param array $context
645
     *  The `$context` param is an associative array that at minimum will contain
646
     *  the current Administration class, the current page object and the delegate
647
     *  name. Other context information may be passed to this function when it is
648
     *  called. eg.
649
     *
650
     * array(
651
     *        'parent' =>& $this->Parent,
652
     *        'page' => $page,
653
     *        'delegate' => $delegate
654
     *    );
655
     * @throws Exception
656
     * @throws SymphonyErrorPage
657
     * @return null|void
658
     */
659
    public static function notifyMembers($delegate, $page, array $context = array())
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$context" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$context"; expected 0 but found 1
Loading history...
660
    {
661
        // Make sure $page is an array
662
        if (!is_array($page)) {
0 ignored issues
show
introduced by
The condition is_array($page) is always false.
Loading history...
663
            $page = array($page);
664
        }
665
666
        // Support for global delegate subscription
667
        if (!in_array('*', $page)) {
668
            $page[] = '*';
669
        }
670
671
        $services = array();
672
673
        if (isset(self::$_subscriptions[$delegate])) {
674
            foreach (self::$_subscriptions[$delegate] as $subscription) {
675
                if (!in_array($subscription['page'], $page)) {
676
                    continue;
677
                }
678
679
                $services[] = $subscription;
680
            }
681
        }
682
683
        if (empty($services)) {
684
            return null;
685
        }
686
687
        $context += array('page' => $page, 'delegate' => $delegate);
688
        $profiling = Symphony::Profiler() instanceof Profiler;
689
690
        foreach ($services as $s) {
691
            if ($profiling) {
692
                // Initial seeding and query count
693
                Symphony::Profiler()->seed();
694
                $queries = Symphony::Database()->queryCount();
695
            }
696
697
            // Get instance of extension and execute the callback passing
698
            // the `$context` along
699
            $obj = self::getInstance($s['name']);
700
701
            if (is_object($obj) && method_exists($obj, $s['callback'])) {
702
                $obj->{$s['callback']}($context);
703
            }
704
705
            // Complete the Profiling sample
706
            if ($profiling) {
707
                $queries = Symphony::Database()->queryCount() - $queries;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $queries does not seem to be defined for all execution paths leading up to this point.
Loading history...
708
                Symphony::Profiler()->sample($delegate . '|' . $s['name'], PROFILE_LAP, 'Delegate', $queries);
0 ignored issues
show
Bug introduced by
The constant PROFILE_LAP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
709
            }
710
        }
711
    }
712
713
    /**
714
     * Returns an array of all the enabled extensions available
715
     *
716
     * @return array
717
     */
718
    public static function listInstalledHandles()
719
    {
720
        if (empty(self::$_enabled_extensions) && Symphony::Database()->isConnected()) {
721
            self::$_enabled_extensions = Symphony::Database()->fetchCol(
722
                'name',
723
                "SELECT `name` FROM `tbl_extensions` WHERE `status` = 'enabled'"
724
            );
725
        }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
726
        return self::$_enabled_extensions;
727
    }
728
729
    /**
730
     * Will return an associative array of all extensions and their about information
731
     *
732
     * @param string $filter
733
     *  Allows a regular expression to be passed to return only extensions whose
734
     *  folders match the filter.
735
     * @throws SymphonyErrorPage
736
     * @throws Exception
737
     * @return array
738
     *  An associative array with the key being the extension folder and the value
739
     *  being the extension's about information
740
     */
741
    public static function listAll($filter = '/^((?![-^?%:*|"<>]).)*$/')
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$filter" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$filter"; expected 0 but found 1
Loading history...
742
    {
743
        $result = array();
744
        $extensions = General::listDirStructure(EXTENSIONS, $filter, false, EXTENSIONS);
0 ignored issues
show
Bug introduced by
The constant EXTENSIONS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
Are you sure the assignment to $extensions is correct as General::listDirStructur...ter, false, EXTENSIONS) targeting General::listDirStructure() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
745
746
        if (is_array($extensions) && !empty($extensions)) {
0 ignored issues
show
introduced by
The condition is_array($extensions) is always false.
Loading history...
747
            foreach ($extensions as $extension) {
748
                $e = trim($extension, '/');
749
750
                if ($about = self::about($e)) {
751
                    $result[$e] = $about;
752
                }
753
            }
754
        }
755
756
        return $result;
757
    }
758
759
    /**
760
     * Custom user sorting function used inside `fetch` to recursively sort authors
761
     * by their names.
762
     *
763
     * @param array $a
764
     * @param array $b
765
     * @param integer $i
766
     * @return integer
767
     */
768
    private static function sortByAuthor($a, $b, $i = 0)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$i" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$i"; expected 0 but found 1
Loading history...
769
    {
770
        $first = $a;
771
        $second = $b;
772
773
        if (isset($a[$i])) {
774
            $first = $a[$i];
775
        }
776
777
        if (isset($b[$i])) {
778
            $second = $b[$i];
779
        }
780
781
        if ($first == $a && $second == $b && $first['name'] == $second['name']) {
782
            return 1;
783
        } elseif ($first['name'] == $second['name']) {
784
            return self::sortByAuthor($a, $b, $i + 1);
785
        } else {
786
            return ($first['name'] < $second['name']) ? -1 : 1;
787
        }
788
    }
789
790
    /**
791
     * This function will return an associative array of Extension information. The
792
     * information returned is defined by the `$select` parameter, which will allow
793
     * a developer to restrict what information is returned about the Extension.
794
     * Optionally, `$where` (not implemented) and `$order_by` parameters allow a developer to
795
     * further refine their query.
796
     *
797
     * @param array $select (optional)
798
     *  Accepts an array of keys to return from the listAll() method. If omitted, all keys
799
     *  will be returned.
800
     * @param array $where (optional)
801
     *  Not implemented.
802
     * @param string $order_by (optional)
803
     *  Allows a developer to return the extensions in a particular order. The syntax is the
804
     *  same as other `fetch` methods. If omitted this will return resources ordered by `name`.
805
     * @throws Exception
806
     * @throws SymphonyErrorPage
807
     * @return array
808
     *  An associative array of Extension information, formatted in the same way as the
809
     *  listAll() method.
810
     */
811
    public static function fetch(array $select = array(), array $where = array(), $order_by = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$select" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$select"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$where" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$where"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$order_by" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$order_by"; expected 0 but found 1
Loading history...
812
    {
813
        $extensions = self::listAll();
814
        $data = array();
815
816
        if (empty($select) && empty($where) && is_null($order_by)) {
817
            return $extensions;
818
        }
819
820
        if (empty($extensions)) {
821
            return array();
822
        }
823
824
        if (!is_null($order_by)) {
825
            $author = $name = $label = array();
826
            $order_by = array_map('strtolower', explode(' ', $order_by));
827
            $order = ($order_by[1] == 'desc') ? SORT_DESC : SORT_ASC;
828
            $sort = $order_by[0];
829
830
            if ($sort == 'author') {
831
                foreach ($extensions as $key => $about) {
832
                    $author[$key] = $about['author'];
833
                }
834
835
                uasort($author, array('self', 'sortByAuthor'));
836
837
                if ($order == SORT_DESC) {
838
                    $author = array_reverse($author);
839
                }
840
841
                foreach ($author as $key => $value) {
842
                    $data[$key] = $extensions[$key];
843
                }
844
845
                $extensions = $data;
846
            } elseif ($sort == 'name') {
847
                foreach ($extensions as $key => $about) {
848
                    $name[$key] = strtolower($about['name']);
849
                    $label[$key] = $key;
850
                }
851
852
                array_multisort($name, $order, $label, $order, $extensions);
853
            }
854
        }
855
856
        foreach ($extensions as $i => $e) {
857
            $data[$i] = array();
858
            foreach ($e as $key => $value) {
859
                // If $select is empty, we assume every field is requested
860
                if (in_array($key, $select) || empty($select)) {
861
                    $data[$i][$key] = $value;
862
                }
863
            }
864
        }
865
866
        return $data;
867
    }
868
869
    /**
870
     * This function will load an extension's meta information given the extension
871
     * `$name`. Since Symphony 2.3, this function will look for an `extension.meta.xml`
872
     * file inside the extension's folder. If this is not found, it will initialise
873
     * the extension and invoke the `about()` function. By default this extension will
874
     * return an associative array display the basic meta data about the given extension.
875
     * If the `$rawXML` parameter is passed true, and the extension has a `extension.meta.xml`
876
     * file, this function will return `DOMDocument` of the file.
877
     *
878
     * @param string $name
879
     *  The name of the Extension Class minus the extension prefix.
880
     * @param boolean $rawXML
881
     *  If passed as true, and is available, this function will return the
882
     *  DOMDocument of representation of the given extension's `extension.meta.xml`
883
     *  file. If the file is not available, the extension will return the normal
884
     *  `about()` results. By default this is false.
885
     * @throws Exception
886
     * @throws SymphonyErrorPage
887
     * @return array
888
     *  An associative array describing this extension
889
     */
890
    public static function about($name, $rawXML = false)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$rawXML" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$rawXML"; expected 0 but found 1
Loading history...
891
    {
892
        // See if the extension has the new meta format
893
        if (file_exists(self::__getClassPath($name) . '/extension.meta.xml')) {
894
            try {
895
                $meta = new DOMDocument;
896
                $meta->load(self::__getClassPath($name) . '/extension.meta.xml');
897
                $xpath = new DOMXPath($meta);
898
                $rootNamespace = $meta->lookupNamespaceUri($meta->namespaceURI);
899
900
                if (is_null($rootNamespace)) {
0 ignored issues
show
introduced by
The condition is_null($rootNamespace) is always false.
Loading history...
901
                    throw new Exception(__('Missing default namespace definition.'));
902
                } else {
903
                    $xpath->registerNamespace('ext', $rootNamespace);
904
                }
905
            } catch (Exception $ex) {
906
                Symphony::Engine()->throwCustomError(
907
                    __('The %1$s file for the %2$s extension is not valid XML: %3$s', array(
908
                        '<code>extension.meta.xml</code>',
909
                        '<code>' . $name . '</code>',
910
                        '<br /><code>' . $ex->getMessage() . '</code>'
911
                    ))
912
                );
913
            }
914
915
            // Load <extension>
916
            $extension = $xpath->query('/ext:extension')->item(0);
917
918
            // Check to see that the extension is named correctly, if it is
919
            // not, then return nothing
920
            if (self::__getClassName($name) !== self::__getClassName($xpath->evaluate('string(@id)', $extension))) {
921
                return array();
922
            }
923
924
            // If `$rawXML` is set, just return our DOMDocument instance
925
            if ($rawXML) {
926
                return $meta;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $meta returns the type DOMDocument which is incompatible with the documented return type array.
Loading history...
927
            }
928
929
            $about = array(
930
                'name' => $xpath->evaluate('string(ext:name)', $extension),
931
                'handle' => $name,
932
                'github' => $xpath->evaluate('string(ext:repo)', $extension),
933
                'discuss' => $xpath->evaluate('string(ext:url[@type="discuss"])', $extension),
934
                'homepage' => $xpath->evaluate('string(ext:url[@type="homepage"])', $extension),
935
                'wiki' => $xpath->evaluate('string(ext:url[@type="wiki"])', $extension),
936
                'issues' => $xpath->evaluate('string(ext:url[@type="issues"])', $extension),
937
                'status' => array()
938
            );
939
940
            // find the latest <release> (largest version number)
941
            $latest_release_version = '0.0.0';
942
            foreach ($xpath->query('//ext:release', $extension) as $release) {
943
                $version = $xpath->evaluate('string(@version)', $release);
944
945
                if (version_compare($version, $latest_release_version, '>')) {
946
                    $latest_release_version = $version;
947
                }
948
            }
949
950
            // Load the latest <release> information
951
            if ($release = $xpath->query("//ext:release[@version='$latest_release_version']", $extension)->item(0)) {
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $latest_release_version instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
952
                $about += array(
953
                    'version' => $xpath->evaluate('string(@version)', $release),
954
                    'release-date' => $xpath->evaluate('string(@date)', $release)
955
                );
956
957
                // If it exists, load in the 'min/max' version data for this release
958
                $required_min_version = $xpath->evaluate('string(@min)', $release);
959
                $required_max_version = $xpath->evaluate('string(@max)', $release);
960
                $current_symphony_version = Symphony::Configuration()->get('version', 'symphony');
961
962
                // Remove pre-release notes from the current Symphony version so that
963
                // we don't get false erros in the backend
964
                $current_symphony_version = preg_replace(array('/dev/i', '/-?beta\.?\d/i', '/-?rc\.?\d/i', '/\.0(?:\.0)?$/'), '', $current_symphony_version);
965
966
                // Munge the version number so that it makes sense in the backend.
967
                // Consider, 2.3.x. As the min version, this means 2.3 onwards,
968
                // for the max it implies any 2.3 release. RE: #1019
969
                // Also remove any .0 when doing the comparison to prevent extensions
970
                // that don't use semver yet. RE: #2146
971
                $required_min_version = preg_replace(array('/\.x/', '/\.0$/'), '', $required_min_version);
972
                $required_max_version = preg_replace(array('/\.x/', '/\.0$/'), 'p', $required_max_version);
973
974
                // Min version
975
                if (!empty($required_min_version) && version_compare($current_symphony_version, $required_min_version, '<')) {
976
                    $about['status'][] = Extension::EXTENSION_NOT_COMPATIBLE;
977
                    $about['required_version'] = $required_min_version;
978
979
                    // Max version
980
                } elseif (!empty($required_max_version) && version_compare($current_symphony_version, $required_max_version, '>')) {
981
                    $about['status'][] = Extension::EXTENSION_NOT_COMPATIBLE;
982
                    $about['required_version'] = str_replace('p', '.x', $required_max_version);
983
                }
984
            }
985
986
            // Add the <author> information
987
            foreach ($xpath->query('//ext:author', $extension) as $author) {
988
                $a = array(
989
                    'name' => $xpath->evaluate('string(ext:name)', $author),
990
                    'website' => $xpath->evaluate('string(ext:website)', $author),
991
                    'github' => $xpath->evaluate('string(ext:name/@github)', $author),
992
                    'email' => $xpath->evaluate('string(ext:email)', $author)
993
                );
994
995
                $about['author'][] = array_filter($a);
996
            }
997
998
            $about['status'] = array_merge($about['status'], self::fetchStatus($about));
999
            return $about;
1000
        } else {
1001
            Symphony::Log()->pushToLog(sprintf('%s does not have an extension.meta.xml file', $name), E_DEPRECATED, true);
1002
1003
            return array();
1004
        }
1005
    }
1006
1007
    /**
1008
     * Creates an instance of a given class and returns it
1009
     *
1010
     * @param string $name
1011
     *  The name of the Extension Class minus the extension prefix.
1012
     * @throws Exception
1013
     * @throws SymphonyErrorPage
1014
     * @return Extension
1015
     */
1016
    public static function create($name)
1017
    {
1018
        if (!isset(self::$_pool[$name])) {
1019
            $classname = self::__getClassName($name);
1020
            $path = self::__getDriverPath($name);
1021
1022
            if (!is_file($path)) {
1023
                $errMsg = __('Could not find extension %s at location %s.', array(
1024
                    '<code>' . $name . '</code>',
1025
                    '<code>' . str_replace(DOCROOT . '/', '', $path) . '</code>'
1026
                ));
1027
                try {
1028
                    Symphony::Engine()->throwCustomError(
1029
                        $errMsg,
1030
                        __('Symphony Extension Missing Error'),
1031
                        Page::HTTP_STATUS_ERROR,
1032
                        'missing_extension',
1033
                        array(
1034
                            'name' => $name,
1035
                            'path' => $path
1036
                        )
1037
                    );
1038
                } catch (Exception $ex) {
1039
                    throw new Exception($errMsg, 0, $ex);
1040
                }
1041
            }
1042
1043
            if (!class_exists($classname)) {
1044
                require_once($path);
1045
            }
1046
1047
            // Create the extension object
1048
            self::$_pool[$name] = new $classname(array());
1049
        }
1050
1051
        return self::$_pool[$name];
1052
    }
1053
1054
    /**
1055
     * A utility function that is used by the ExtensionManager to ensure
1056
     * stray delegates are not in `tbl_extensions_delegates`. It is called when
1057
     * a new Delegate is added or removed.
1058
     */
1059
    public static function cleanupDatabase()
1060
    {
1061
        // Grab any extensions sitting in the database
1062
        $rows = Symphony::Database()->fetch("SELECT `name` FROM `tbl_extensions`");
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal SELECT `name` FROM `tbl_extensions` does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
1063
1064
        // Iterate over each row
1065
        if (is_array($rows) && !empty($rows)) {
1066
            foreach ($rows as $r) {
1067
                $name = $r['name'];
1068
                $status = isset($r['status']) ? $r['status'] : null;
1069
1070
                // Grab the install location
1071
                $path = self::__getClassPath($name);
1072
                $existing_id = self::fetchExtensionID($name);
1073
1074
                // If it doesnt exist, remove the DB rows
1075
                if (!@is_dir($path)) {
1076
                    Symphony::Database()->delete("tbl_extensions_delegates", sprintf(" `extension_id` = %d ", $existing_id));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal tbl_extensions_delegates does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal `extension_id` = %d does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
1077
                    Symphony::Database()->delete('tbl_extensions', sprintf(" `id` = %d LIMIT 1", $existing_id));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal `id` = %d LIMIT 1 does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
1078
                } elseif ($status == 'disabled') {
1079
                    Symphony::Database()->delete("tbl_extensions_delegates", sprintf(" `extension_id` = %d ", $existing_id));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal tbl_extensions_delegates does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal `extension_id` = %d does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
1080
                }
1081
            }
1082
        }
1083
    }
1084
}
1085