MODXPackages::unInstallPackage()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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