Passed
Push — master ( 0edf2f...319285 )
by Josh
02:58
created

MODXPackages::removePackage()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 3
eloc 14
c 1
b 1
f 0
nc 3
nop 2
dl 0
loc 27
rs 9.7998
1
<?php
2
3
namespace LCI\Blend\Transport;
4
5
use LCI\Blend\Exception\TransportException;
6
use LCI\Blend\Exception\TransportNotFoundException;
7
use LCI\MODX\Console\Helpers\UserInteractionHandler;
8
use modTransportPackage;
0 ignored issues
show
Bug introduced by
The type modTransportPackage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use modTransportProvider;
0 ignored issues
show
Bug introduced by
The type modTransportProvider was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use modX;
0 ignored issues
show
Bug introduced by
The type modX was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use xPDO;
0 ignored issues
show
Bug introduced by
The type xPDO was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use xPDOTransport;
0 ignored issues
show
Bug introduced by
The type xPDOTransport was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
14
class MODXPackages
15
{
16
    /** @var string  */
17
    protected $date_format = '';
18
19
    /** @var \modX  */
20
    protected $modx;
21
22
    /** @var array $providerCache */
23
    protected $providerCache = array();
24
25
    protected $provider_map = [];
26
27
    /** @var int $updates_cache_expire */
28
    protected $updates_cache_expire = 300;
29
30
    /** @var array  */
31
    protected $possible_package_signatures = [];
32
33
    /** @var \LCI\MODX\Console\Helpers\UserInteractionHandler */
34
    protected $userInteractionHandler;
35
36
    /**
37
     * MODXPackages constructor.
38
     * @param modX $modx
39
     * @param UserInteractionHandler $userInteractionHandler
40
     */
41
    public function __construct(modX $modx, UserInteractionHandler $userInteractionHandler)
42
    {
43
        $this->modx = $modx;
44
45
        $this->userInteractionHandler = $userInteractionHandler;
46
47
        $this->date_format = $this->modx->getOption('manager_date_format') .', '. $this->modx->getOption('manager_time_format');
48
    }
49
50
    // Code ideas for MODX packages from: core/model/modx/processors/workspace/packages/getlist.class.php and
51
    // https://github.com/modmore/SiteDashClient/blob/master/core/components/sitedashclient/src/Package/Update.php
52
53
    /**
54
     * Get basic version information about the package
55
     *
56
     * @param string $signature
57
     * @return array
58
     */
59
    public static function getVersionInfo($signature)
60
    {
61
        $parts = explode('-', $signature);
62
        return [
63
            'base' => $parts[0],
64
            'version' => $parts[1],
65
            'release' => (isset($parts[2]) ? $parts[2] : '')
66
        ];
67
    }
68
69
    /**
70
     * @param int $limit
71
     * @param int $start
72
     * @param string $search
73
     * @return array
74
     */
75
    public function getList($limit=25, $start=0, $search='') {
76
        $data = [
77
            'limit' => $limit,
78
            'packages' => [],
79
            'start' => $start,
80
            'total' => 0
81
        ];
82
83
        /** @var array $package_list */
84
        $package_list = $this->modx->call(
85
            'transport.modTransportPackage',
86
            'listPackages',
87
            [
88
                &$this->modx,
89
                1,
90
                $limit > 0 ? $limit : 0,
91
                $start,
92
                $search
93
            ]
94
        );
95
96
97
        if ($package_list > 0) {
98
            /** @var modTransportPackage $package */
99
            foreach ($package_list['collection'] as $package) {
100
                if ($package->get('installed') == '0000-00-00 00:00:00') {
101
                    $package->set('installed', null);
102
                }
103
104
                $package_array = $package->toArray();
105
106
                $package_array['name'] = $package_array['package_name'];
107
108
                $version_info = self::getVersionInfo($package_array['signature']);
109
                $package_array['version'] = $version_info['version'];
110
                $package_array['release'] = $version_info['release'];
111
112
                $package_array = $this->formatDates($package_array);
113
                $package_array['updates'] = $this->getAvailableUpdateInfo($package);
114
115
                $data['packages'][] = $package_array;
116
            }
117
        }
118
119
        $data['total'] = $package_list['total'];
120
        return $data;
121
    }
122
123
    /**
124
     * This is only needed if TransportNotFoundException is caught
125
     * @return array
126
     */
127
    public function getPossiblePackageSignatures(): array
128
    {
129
        return $this->possible_package_signatures;
130
    }
131
132
    /**
133
     * @param string $signature - ex: ace-1.8.0-pl
134
     * @param bool $latest_version
135
     * @param string $provider_name
136
     * @return bool
137
     * @throws TransportException
138
     * @throws TransportNotFoundException
139
     */
140
    public function requirePackage($signature, $latest_version=true, $provider_name='modx.com')
141
    {
142
        // partial signature like fred sent, so is this package installed?
143
        $package_info = static::getVersionInfo($signature);
144
145
        // get the latest installed, might sort for the release column
146
        $query = $this->modx->newQuery('transport.modTransportPackage');
147
        $query->where(['signature:LIKE' => $package_info['base'].'-%']);
148
        $query->sortby('version_major', 'DESC');
149
        $query->sortby('version_minor', 'DESC');
150
        $query->sortby('version_patch', 'DESC');
151
        $query->sortby('release_index', 'ASC');
152
        $query->limit(1);
153
154
        /** @var modTransportPackage $package */
155
        $package = $this->modx->getObject('transport.modTransportPackage', $query);
156
157
        $type = 'install';
158
159
        if ($package instanceof \modTransportPackage) {
160
            $signature = $package->get('signature');
161
            $provider = $this->getPackageProvider($package);
162
            $type = 'update';
163
164
        } else {
165
            $provider = $this->getProvider($provider_name);
166
        }
167
168
        $transfer_options = [];
169
170
        // this only returns the
171
        $options = $this->getPackageLatestVersions($provider, $signature);
0 ignored issues
show
Bug introduced by
It seems like $provider can also be of type boolean; however, parameter $provider of LCI\Blend\Transport\MODX...PackageLatestVersions() does only seem to accept modTransportProvider, 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

171
        $options = $this->getPackageLatestVersions(/** @scrutinizer ignore-type */ $provider, $signature);
Loading history...
172
173
        if (isset($options[$signature])) {
174
            $transfer_options['location'] = $options[$signature]['location'];
175
        }
176
177
        if ($latest_version && count($options) > 0) {
178
            $opt = reset($options);
179
180
            $signature = $opt['signature'];
181
            $transfer_options['location'] = $opt['location'];
182
183
        } elseif ($type == 'update' && $package instanceof modTransportPackage && !empty($package->get('installed'))) {
184
            MODXPackagesConfig::addPackageConfig($signature, $latest_version, $provider_name);
185
            $this->userInteractionHandler->tellUser('Extra '.$signature.' is already installed, skipping!', userInteractionHandler::MASSAGE_ERROR);
186
            return true;
187
        }
188
189
        if (!$package instanceof modTransportPackage || ($package instanceof modTransportPackage && $package->get('signature') != $signature)) {
190
            $package = $this->downloadPackageFiles($signature, $provider, $latest_version);
0 ignored issues
show
Bug introduced by
It seems like $provider can also be of type boolean; however, parameter $provider of LCI\Blend\Transport\MODX...:downloadPackageFiles() does only seem to accept modTransportProvider, 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

190
            $package = $this->downloadPackageFiles($signature, /** @scrutinizer ignore-type */ $provider, $latest_version);
Loading history...
191
        }
192
193
        if (!$package instanceof \modTransportPackage) {
194
            $this->userInteractionHandler->tellUser('Extra '.$signature.' not found', userInteractionHandler::MASSAGE_ERROR);
195
            return false;
196
        }
197
198
        if ($success = $this->runPackageInstallUpdate($package)) {
199
            MODXPackagesConfig::addPackageConfig($signature, $latest_version, $provider_name);
200
        }
201
202
        return $success;
203
    }
204
205
    /**
206
     * @param string $signature - ex: ace-1.8.0-pl
207
     * @param bool $force
208
     * @return bool
209
     * @throws TransportException
210
     */
211
    public function removePackage($signature, $force=true)
212
    {
213
        /** @var modTransportPackage $package */
214
        $package = $this->modx->getObject('transport.modTransportPackage', [
215
            'signature' => $signature,
216
        ]);
217
218
        if ($package instanceof \modTransportPackage) {
219
            $package->getTransport();
220
221
            if (!$success = $package->removePackage($force)) {
222
                throw new TransportException('Error Package did not uninstall.');
223
            }
224
225
            $this->userInteractionHandler->tellUser('Extra '.$signature.' has been removed', userInteractionHandler::MASSAGE_SUCCESS);
226
227
            $this->modx->cacheManager->refresh([
228
                $this->modx->getOption('cache_packages_key', null, 'packages') => []
229
            ]);
230
            $this->modx->cacheManager->refresh();
231
232
        } else {
233
            throw new TransportException('Package with the signature: '.$signature.' does not seem to be installed. '.
234
                'Run the extra command to see list of installed extras with proper signatures. You can only remove packages that are installed.');
235
        }
236
237
        return $success;
238
    }
239
240
    /**
241
     * @param string $signature - ex: ace-1.8.0-pl
242
     * @param int $preexisting_mode, see xPDOTransport
243
     *  xPDOTransport::PRESERVE_PREEXISTING = 0;
244
    xPDOTransport::REMOVE_PREEXISTING = 1;
245
    xPDOTransport::RESTORE_PREEXISTING = 2;
246
     * @return bool
247
     * @throws TransportException
248
     */
249
    public function unInstallPackage($signature, $preexisting_mode=null)
250
    {
251
        /** @var modTransportPackage $package */
252
        $package = $this->modx->getObject('transport.modTransportPackage', [
253
            'signature' => $signature,
254
        ]);
255
256
        if ($package instanceof \modTransportPackage) {
257
            $package->getTransport();
258
            /* uninstall package */
259
            $options = array(
260
                xPDOTransport::PREEXISTING_MODE => is_null($preexisting_mode) ? xPDOTransport::REMOVE_PREEXISTING : $preexisting_mode,
261
            );
262
263
            if (!$success = $package->uninstall($options)) {
264
                throw new TransportException('Error Package did not uninstall.');
265
            }
266
267
            $this->userInteractionHandler->tellUser('Extra '.$signature.' has been uninstalled', userInteractionHandler::MASSAGE_SUCCESS);
268
269
            $this->modx->cacheManager->refresh([
270
                $this->modx->getOption('cache_packages_key', null, 'packages') => []
271
            ]);
272
            $this->modx->cacheManager->refresh();
273
274
        } else {
275
            throw new TransportException('Package with the signature: '.$signature.' does not seem to be installed. '.
276
                'Run the extra command to see list of installed extras with proper signatures. You can only remove packages that are installed.');
277
        }
278
279
        return $success;
280
    }
281
282
    // @TODO local packages
283
284
    /**
285
     * @param string $signature - ex: ace-1.8.0-pl
286
     * @param modTransportProvider $provider
287
     * @param bool $latest
288
     * @throws TransportNotFoundException
289
     * @return bool|modTransportPackage
290
     */
291
    protected function downloadPackageFiles($signature, modTransportProvider $provider, $latest=true)
292
    {
293
        $transfer_options = [];
294
295
        $options = $this->getPackageLatestVersions($provider, $signature);
296
297
        if (isset($options[$signature])) {
298
            $transfer_options['location'] = $options[$signature]['location'];
299
        }
300
301
        if ($latest && count($options) > 1) {
302
            // @TODO review:
303
            $opt = reset($options);
304
305
            $signature = $opt['signature'];
306
            $transfer_options['location'] = $opt['location'];
307
        }
308
309
        /** @var modTransportPackage|bool $package */
310
        $package = $provider->transfer($signature, null, $transfer_options);
311
        if (!$package) {
312
            $parts = self::getVersionInfo($signature);
313
314
            $this->possible_package_signatures = $provider->find(['query' => $parts['base']]);
315
316
            throw new TransportNotFoundException('Failed to download package ' . $signature.
317
                ' from the '.$provider->get('name').' transport provider. Verify the signature and provider');
318
        }
319
320
        return $package;
321
    }
322
323
    /**
324
     * @param modTransportPackage $package
325
     * @return bool
326
     * @throws TransportException
327
     */
328
    protected function runPackageInstallUpdate(modTransportPackage $package)
329
    {
330
        $installed = $package->install([]);
331
        $this->modx->cacheManager->refresh([
332
            $this->modx->getOption('cache_packages_key', null, 'packages') => []
333
        ]);
334
        $this->modx->cacheManager->refresh();
335
336
        if (!$installed) {
337
            throw new TransportException('Failed to install package ' . $package->signature);
338
        }
339
340
        $this->userInteractionHandler->tellUser('Extra '.$package->get('signature').' has been installed!', userInteractionHandler::MASSAGE_SUCCESS);
341
342
        $this->modx->invokeEvent('OnPackageInstall', array(
343
            'package' => $package,
344
            'action' => $package->previousVersionInstalled() ? \xPDOTransport::ACTION_UPGRADE : \xPDOTransport::ACTION_INSTALL
345
        ));
346
347
        return $installed;
348
    }
349
350
    /**
351
     * Format installed, created and updated dates
352
     * @param array $packageArray
353
     * @return array
354
     */
355
    protected function formatDates(array $packageArray)
356
    {
357
        if ($packageArray['updated'] != '0000-00-00 00:00:00' && $packageArray['updated'] != null) {
358
            $packageArray['updated'] = utf8_encode(date($this->date_format, strtotime($packageArray['updated'])));
359
        } else {
360
            $packageArray['updated'] = '';
361
        }
362
363
        $packageArray['created']= utf8_encode(date($this->date_format, strtotime($packageArray['created'])));
364
365
        if ($packageArray['installed'] == null || $packageArray['installed'] == '0000-00-00 00:00:00') {
366
            $packageArray['installed'] = null;
367
        } else {
368
            $packageArray['installed'] = utf8_encode(date($this->date_format, strtotime($packageArray['installed'])));
369
        }
370
        return $packageArray;
371
    }
372
373
    /**
374
     * @param modTransportPackage $package
375
     * @return array
376
     */
377
    protected function getAvailableUpdateInfo(modTransportPackage $package)
378
    {
379
        $updates = [
380
            'count' => 0,
381
            'versions' => []
382
        ];
383
        if ($package->get('provider') > 0 && $this->modx->getOption('auto_check_pkg_updates',null,false)) {
384
            $updateCacheKey = 'mgr/providers/updates/'.$package->get('provider').'/'.$package->get('signature');
385
            $updateCacheOptions = array(
386
                xPDO::OPT_CACHE_KEY => $this->modx->cacheManager->getOption('cache_packages_key', null, 'packages'),
387
                xPDO::OPT_CACHE_HANDLER => $this->modx->cacheManager->getOption('cache_packages_handler', null, $this->modx->cacheManager->getOption(xPDO::OPT_CACHE_HANDLER)),
388
            );
389
            $updates = $this->modx->cacheManager->get($updateCacheKey, $updateCacheOptions);
390
391
            if (empty($updates)) {
392
                /* cache providers to speed up load time */
393
                /** @var modTransportProvider $provider */
394
                $provider = $this->getPackageProvider($package);
395
396
                if ($provider) {
0 ignored issues
show
introduced by
$provider is of type modTransportProvider, thus it always evaluated to true.
Loading history...
397
                    $options = $this->getPackageLatestVersions($provider, $package->get('signature'));
398
399
                    $updates['count'] = count($options);
400
                    $updates['versions'] = $options;
401
402
                    $this->modx->cacheManager->set($updateCacheKey, $updates, $this->updates_cache_expire, $updateCacheOptions);
403
                }
404
            }
405
        }
406
407
        return $updates;
408
    }
409
410
    /**
411
     * @param modTransportProvider $provider
412
     * @param string $signature
413
     * @return array - only returns values if the extra has a version that is more recent then the signature
414
     */
415
    protected function getPackageLatestVersions(modTransportProvider $provider, $signature)
416
    {
417
        $package_versions = $provider->latest($signature);
418
419
        $options = [];
420
        /** @var \SimpleXMLElement $package */
421
        foreach ($package_versions as $package_version) {
422
            $version_info = array_merge([
423
                'location' => (string)$package_version['location'],
424
                'signature' => (string)$package_version['signature'],
425
                'package_name' => (string)$package_version['package_name'],
426
                'release' => '',
427
                'version' => ''
428
            ],
429
                self::getVersionInfo((string)$package_version['signature'])
430
            );
431
432
            $options[$version_info['signature']] = $version_info;
433
        }
434
435
        return $options;
436
    }
437
438
    /**
439
     * @param modTransportPackage $package
440
     * @return modTransportProvider|bool
441
     */
442
    protected function getPackageProvider(modTransportPackage $package)
443
    {
444
        /* cache providers to speed up load time */
445
        /** @var modTransportProvider|bool $provider */
446
        if (!empty($this->providerCache[$package->get('provider')])) {
447
            return $this->providerCache[$package->get('provider')];
448
        }
449
450
        /** @var modTransportProvider|bool $provider */
451
        if ($provider = $package->getOne('Provider')) {
452
            $this->providerCache[$provider->get('id')] = $provider;
453
        }
454
455
        return $provider;
456
    }
457
458
    /**
459
     * @param string $name
460
     * @return bool|modTransportProvider
461
     */
462
    protected function getProvider($name='modx.com')
463
    {
464
        if (isset($this->provider_map[$name]) && $this->providerCache[$this->provider_map[$name]]) {
465
            return $this->providerCache[$this->provider_map[$name]];
466
        }
467
468
        /** @var modTransportProvider|bool $provider */
469
        $provider = $this->modx->getObject('transport.modTransportProvider', ['name' => $name]);
470
471
        if ($provider) {
472
            $this->provider_map[$name] = $provider->get('id');
473
            $this->providerCache[$this->provider_map[$name]] = $provider;
474
        }
475
476
        return $provider;
477
    }
478
}
479