PluginPackage::name()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 4
rs 10
1
<?php
2
/**
3
 * Licensed under The GPL-3.0 License
4
 * For full copyright and license information, please see the LICENSE.txt
5
 * Redistributions of files must retain the above copyright notice.
6
 *
7
 * @since    2.0.0
8
 * @author   Christopher Castro <[email protected]>
9
 * @link     http://www.quickappscms.org
10
 * @license  http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License
11
 */
12
namespace CMS\Core\Package;
13
14
use Cake\ORM\TableRegistry;
15
use Cake\Utility\Inflector;
16
use CMS\Core\Plugin;
17
18
/**
19
 * Represents a QuickAppsCMS plugin or theme.
20
 *
21
 * @property string $name CamelizedName of the plugin, e.g. `Megamenu`
22
 * @property string $humanName Human readable name of the plugin, e.g. `Megamenu Builder`
23
 * @property string $package Composer package name as `vendor/name`, e.g. `quickapps/megamenu`
24
 * @property string $path Full path to plugin root directory, where the `src` directory can be found
25
 * @property bool $isTheme Whether or not this plugin is actually a theme
26
 * @property bool $hasHelp Whether or not this plugin provides help documentation
27
 * @property bool $hasSettings Whether or not this plugin provides configurable values
28
 * @property bool $status Whether this plugin is enabled or not
29
 * @property array $aspects List of aspect classes used by AOP API
30
 * @property array $eventListeners List of event listener classes
31
 * @property array $settings Array of configurable values
32
 * @property array $composer Composer's json file as an array
33
 * @property array $permissions Plugin permissions tree indexed by role
34
 */
35
class PluginPackage extends BasePackage
36
{
37
38
    /**
39
     * Plugin information.
40
     *
41
     * @var array
42
     */
43
    protected $_info = [];
44
45
    /**
46
     * Permissions tree for this plugin.
47
     *
48
     * @var null|array
49
     */
50
    protected $_permissions = null;
51
52
    /**
53
     * {@inheritDoc}
54
     *
55
     * @return string CamelizedName plugin name
56
     */
57
    public function name()
58
    {
59
        return (string)Inflector::camelize(str_replace('-', '_', parent::name()));
60
    }
61
62
    /**
63
     * Gets plugin's permissions tree.
64
     *
65
     * ### Output example:
66
     *
67
     * ```php
68
     * [
69
     *     'administrator' => [
70
     *         'Plugin/Controller/action',
71
     *         'Plugin/Controller/action2',
72
     *         ...
73
     *     ],
74
     *     'role-machine-name' => [
75
     *         'Plugin/Controller/anotherAction',
76
     *         'Plugin/Controller/anotherAction2',
77
     *     ],
78
     *     ...
79
     * ]
80
     * ```
81
     *
82
     * @return array Permissions index by role's machine-name
83
     */
84
    public function permissions()
85
    {
86
        if (is_array($this->_permissions)) {
87
            return $this->_permissions;
88
        }
89
90
        $out = [];
91
        $acosTable = TableRegistry::get('User.Acos');
92
        $permissions = $acosTable
93
            ->Permissions
94
            ->find()
95
            ->where(['Acos.plugin' => $this->name])
96
            ->contain(['Acos', 'Roles'])
97
            ->all();
98
99 View Code Duplication
        foreach ($permissions as $permission) {
100
            if (!isset($out[$permission->role->slug])) {
101
                $out[$permission->role->slug] = [];
102
            }
103
            $out[$permission->role->slug][] = implode(
104
                '/',
105
                $acosTable
106
                ->find('path', ['for' => $permission->aco->id])
107
                ->extract('alias')
108
                ->toArray()
109
            );
110
        }
111
112
        $this->_permissions = $out;
113
114
        return $out;
115
    }
116
117
    /**
118
     * Magic getter to access properties that exists on info().
119
     *
120
     * @param string $property Name of the property to access
121
     * @return mixed
122
     */
123
    public function &__get($property)
124
    {
125
        return $this->info($property);
126
    }
127
128
    /**
129
     * Gets information for this plugin.
130
     *
131
     * When `$full` is set to true some additional keys will be repent in the
132
     * resulting array:
133
     *
134
     * - `settings`: Plugin's settings info fetched from DB.
135
     * - `composer`: Composer JSON information, converted to an array.
136
     * - `permissions`: Permissions tree for this plugin, see `PluginPackage::permissions()`
137
     *
138
     * ### Example:
139
     *
140
     * Reading full information:
141
     *
142
     * ```php
143
     * $plugin->info();
144
     *
145
     * // returns an array as follow:
146
     * [
147
     *     'name' => 'User,
148
     *     'isTheme' => false,
149
     *     'hasHelp' => true,
150
     *     'hasSettings' => false,
151
     *     'eventListeners' => [ ... ],
152
     *     'status' => 1,
153
     *     'path' => '/path/to/plugin',
154
     *     'settings' => [ ... ], // only when $full = true
155
     *     'composer' => [ ... ], // only when $full = true
156
     *     'permissions' => [ ... ], // only when $full = true
157
     * ]
158
     * ```
159
     *
160
     * Additionally the first argument, $key, can be used to get an specific value
161
     * using a dot syntax path:
162
     *
163
     * ```php
164
     * $plugin->info('isTheme');
165
     * $plugin->info('settings.some_key');
166
     * ```
167
     *
168
     * If the given path is not found NULL will be returned
169
     *
170
     * @param string $key Optional path to read from the resulting array
171
     * @return mixed Plugin information as an array if no key is given, or the
172
     *  requested value if a valid $key was provided, or NULL if $key path is not
173
     *  found
174
     */
175
    public function &info($key = null)
176
    {
177
        $plugin = $this->name();
178
        if (empty($this->_info)) {
179
            $this->_info = (array)quickapps("plugins.{$plugin}");
180
        }
181
182
        $parts = explode('.', $key);
183
        $getComposer = in_array('composer', $parts) || $key === null;
184
        $getSettings = in_array('settings', $parts) || $key === null;
185
        $getPermissions = in_array('permissions', $parts) || $key === null;
186
187
        if ($getComposer && !isset($this->_info['composer'])) {
188
            $this->_info['composer'] = $this->composer();
189
        }
190
191
        if ($getSettings && !isset($this->_info['settings'])) {
192
            $this->_info['settings'] = (array)$this->settings();
193
        }
194
195
        if ($getPermissions && !isset($this->_info['permissions'])) {
196
            $this->_info['permissions'] = (array)$this->permissions();
197
        }
198
199
        if ($key === null) {
200
            return $this->_info;
201
        }
202
203
        return $this->_getKey($parts);
204
    }
205
206
    /**
207
     * Gets info value for the given key.
208
     *
209
     * @param string|array $key The path to read. String using a dot-syntax, or an
210
     *  array result of exploding by `.` symbol
211
     * @return mixed
212
     */
213
    protected function &_getKey($key)
214
    {
215
        $default = null;
216
        $parts = is_string($key) ? explode('.', $key) : $key;
217
218
        switch (count($parts)) {
219
            case 1:
220
                if (isset($this->_info[$parts[0]])) {
221
                    return $this->_info[$parts[0]];
222
                }
223
224
                return $default;
225
            case 2:
226
                if (isset($this->_info[$parts[0]][$parts[1]])) {
227
                    return $this->_info[$parts[0]][$parts[1]];
228
                }
229
230
                return $default;
231
            case 3:
232
                if (isset($this->_info[$parts[0]][$parts[1]][$parts[2]])) {
233
                    return $this->_info[$parts[0]][$parts[1]][$parts[2]];
234
                }
235
236
                return $default;
237
            case 4:
238
                if (isset($this->_info[$parts[0]][$parts[1]][$parts[2]][$parts[3]])) {
239
                    return $this->_info[$parts[0]][$parts[1]][$parts[2]][$parts[3]];
240
                }
241
242
                return $default;
243
            default:
244
                $data = $this->_info;
245
                foreach ($parts as $key) {
246
                    if (is_array($data) && isset($data[$key])) {
247
                        $data = $data[$key];
248
                    } else {
249
                        return $default;
250
                    }
251
                }
252
        }
253
254
        return $data;
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260
    public function composer($full = false)
261
    {
262
        $composer = parent::composer($full);
263 View Code Duplication
        if ($this->isTheme && !isset($composer['extra']['admin'])) {
264
            $composer['extra']['admin'] = false;
265
        }
266 View Code Duplication
        if ($this->isTheme && !isset($composer['extra']['regions'])) {
267
            $composer['extra']['regions'] = [];
268
        }
269
270
        return $composer;
271
    }
272
273
    /**
274
     * Gets settings from DB for this plugin. Or reads a single settings key value.
275
     *
276
     * @param string $key Which setting to read, the entire settings will be
277
     *  returned if no key is provided
278
     * @return mixed Array of settings if $key was not provided, or the requested
279
     *  value for the given $key (null of key does not exists)
280
     */
281
    public function settings($key = null)
282
    {
283
        $plugin = $this->name();
284
        if ($cache = $this->config('settings')) {
285
            if ($key !== null) {
286
                $cache = isset($cache[$key]) ? $cache[$key] : null;
287
            }
288
289
            return $cache;
290
        }
291
292
        $pluginsTable = TableRegistry::get('System.Plugins');
293
        $settings = [];
294
        $dbInfo = $pluginsTable->find()
295
            ->cache("{$plugin}_settings", 'plugins')
296
            ->select(['name', 'settings'])
297
            ->where(['name' => $plugin])
298
            ->limit(1)
299
            ->first();
300
301
        if ($dbInfo) {
302
            $settings = (array)$dbInfo->settings;
303
        }
304
305
        if (empty($settings)) {
306
            $settings = (array)$pluginsTable->trigger("Plugin.{$plugin}.settingsDefaults")->result;
307
        }
308
309
        $this->config('settings', $settings);
310
        if ($key !== null) {
311
            $settings = isset($settings[$key]) ? $settings[$key] : null;
312
        }
313
314
        return $settings;
315
    }
316
317
    /**
318
     * Gets a collection list of plugin that depends on this plugin.
319
     *
320
     * @return \Cake\Collection\Collection List of plugins
321
     */
322
    public function requiredBy()
323
    {
324
        if ($cache = $this->config('required_by')) {
325
            return collection($cache);
326
        }
327
328
        Plugin::dropCache();
329
        $out = [];
330
        $plugins = Plugin::get()->filter(function ($v, $k) {
331
            return $v->name() !== $this->name();
332
        });
333
334
        foreach ($plugins as $plugin) {
335
            if ($dependencies = $plugin->dependencies($plugin->name)) {
336
                $packages = array_map(function ($item) {
337
                    list(, $package) = packageSplit($item, true);
338
339
                    return strtolower($package);
340
                }, array_keys($dependencies));
341
342
                if (in_array(strtolower($this->name()), $packages)) {
343
                    $out[] = $plugin;
344
                }
345
            }
346
        }
347
348
        $this->config('required_by', $out);
349
350
        return collection($out);
351
    }
352
353
    /**
354
     * {@inheritDoc}
355
     *
356
     * It will look for plugin's version in the following places:
357
     *
358
     * - Plugin's "composer.json" file.
359
     * - Plugin's "VERSION.txt" file (or any file matching "/version?(\.\w+)/i").
360
     * - Composer's "installed.json" file.
361
     *
362
     * If not found `dev-master` is returned by default. If plugin is not registered
363
     * on QuickAppsCMS (not installed) an empty string will be returned instead.
364
     *
365
     * @return string Plugin's version, for instance `1.2.x-dev`
366
     */
367
    public function version()
368
    {
369
        if (parent::version() !== null) {
370
            return parent::version();
371
        }
372
373
        if (!Plugin::exists($this->name())) {
374
            $this->_version = '';
375
376
            return $this->_version;
377
        }
378
379
        // from composer.json
380
        if (!empty($this->composer['version'])) {
381
            $this->_version = $this->composer['version'];
382
383
            return $this->_version;
384
        }
385
386
        // from version.txt
387
        $files = glob($this->path . '/*', GLOB_NOSORT);
388
        foreach ($files as $file) {
389
            $fileName = basename(strtolower($file));
390
            if (preg_match('/version?(\.\w+)/i', $fileName)) {
391
                $versionFile = file($file);
392
                $version = trim(array_pop($versionFile));
393
                $this->_version = $version;
394
395
                return $this->_version;
396
            }
397
        }
398
399
        // from installed.json
400
        $installedJson = normalizePath(VENDOR_INCLUDE_PATH . "composer/installed.json");
401
        if (is_readable($installedJson)) {
402
            $json = (array)json_decode(file_get_contents($installedJson), true);
403
            foreach ($json as $pkg) {
404
                if (isset($pkg['version']) &&
405
                    strtolower($pkg['name']) === strtolower($this->_packageName)
406
                ) {
407
                    $this->_version = $pkg['version'];
408
409
                    return $this->_version;
410
                }
411
            }
412
        }
413
414
        $this->_version = 'dev-master';
415
416
        return $this->_version;
417
    }
418
419
    /**
420
     * Returns an array that can be used to describe the internal state of this
421
     * object.
422
     *
423
     * @return array
424
     */
425
    public function __debugInfo()
426
    {
427
        return $this->info(null);
428
    }
429
}
430