Issues (31)

src/Tasks/UpdatePackageInfoTask.php (1 issue)

1
<?php
2
3
namespace BringYourOwnIdeas\Maintenance\Tasks;
4
5
use BringYourOwnIdeas\Maintenance\Util\ComposerLoader;
6
use BringYourOwnIdeas\Maintenance\Util\ModuleHealthLoader;
7
use BringYourOwnIdeas\Maintenance\Util\SupportedAddonsLoader;
8
use RuntimeException;
9
use SilverStripe\Control\HTTPRequest;
10
use SilverStripe\Core\Convert;
11
use SilverStripe\Core\Environment;
12
use SilverStripe\ORM\Queries\SQLDelete;
13
use SilverStripe\ORM\DataObjectSchema;
14
use BringYourOwnIdeas\Maintenance\Model\Package;
15
use SilverStripe\Dev\BuildTask;
16
17
/**
18
 * Parses a composer lock file in order to cache information about the installation.
19
 */
20
class UpdatePackageInfoTask extends BuildTask
21
{
22
    /**
23
     * {@inheritDoc}
24
     * @var string
25
     */
26
    private static $segment = 'UpdatePackageInfoTask';
27
28
    /**
29
     * A custom memory limit to set for this to increase to (or do nothing if the memory is already set high enough)
30
     *
31
     * @config
32
     * @var string
33
     */
34
    private static $memory_limit = '256m';
35
36
    /**
37
     * @var array Injector configuration
38
     * @config
39
     */
40
    private static $dependencies = [
41
        'ComposerLoader' => '%$BringYourOwnIdeas\\Maintenance\\Util\\ComposerLoader',
42
        'SupportedAddonsLoader' => '%$BringYourOwnIdeas\\Maintenance\\Util\\SupportedAddonsLoader',
43
        'ModuleHealthLoader' => '%$BringYourOwnIdeas\\Maintenance\\Util\\ModuleHealthLoader',
44
    ];
45
46
    /**
47
     * The "types" of composer libraries that will be processed. Anything without these types will be ignored.
48
     *
49
     * @config
50
     * @var array
51
     */
52
    private static $allowed_types = [
53
        'silverstripe-module',
54
        'silverstripe-vendormodule',
55
    ];
56
57
    /**
58
     * @var ComposerLoader
59
     */
60
    protected $composerLoader;
61
62
    /**
63
     * @var SupportedAddonsLoader
64
     */
65
    protected $supportedAddonsLoader;
66
67
    /**
68
     * @var ModuleHealthLoader
69
     */
70
    protected $moduleHealthLoader;
71
72
    /**
73
     * Fetch the composer loader
74
     *
75
     * @return ComposerLoader
76
     */
77
    public function getComposerLoader()
78
    {
79
        return $this->composerLoader;
80
    }
81
82
    /**
83
     * set composer loader - provided for use with Injector {@see Injector}
84
     *
85
     * @param ComposerLoader $composerLoader
86
     *
87
     * @return UpdatePackageInfoTask $this
88
     */
89
    public function setComposerLoader($composerLoader)
90
    {
91
        $this->composerLoader = $composerLoader;
92
        return $this;
93
    }
94
95
    /**
96
     * @return SupportedAddonsLoader
97
     */
98
    public function getSupportedAddonsLoader()
99
    {
100
        return $this->supportedAddonsLoader;
101
    }
102
103
    /**
104
     * @param SupportedAddonsLoader $supportedAddonsLoader
105
     * @return $this
106
     */
107
    public function setSupportedAddonsLoader(SupportedAddonsLoader $supportedAddonsLoader)
108
    {
109
        $this->supportedAddonsLoader = $supportedAddonsLoader;
110
        return $this;
111
    }
112
113
    /**
114
     * @return ModuleHealthLoader
115
     */
116
    public function getModuleHealthLoader()
117
    {
118
        return $this->moduleHealthLoader;
119
    }
120
121
    /**
122
     * @param ModuleHealthLoader $moduleHealthLoader
123
     * @return $this
124
     */
125
    public function setModuleHealthLoader(ModuleHealthLoader $moduleHealthLoader)
126
    {
127
        $this->moduleHealthLoader = $moduleHealthLoader;
128
        return $this;
129
    }
130
131
    public function getTitle()
132
    {
133
        return _t(__CLASS__ . '.TITLE', 'Refresh installed package info');
134
    }
135
136
    public function getDescription()
137
    {
138
        return _t(
139
            __CLASS__ . '.DESCRIPTION',
140
            'Repopulates installation summary, listing installed modules'.
141
                ' and information associated with each.'
142
        );
143
    }
144
145
    /**
146
     * Update database cached information about this site.
147
     *
148
     * @param HTTPRequest $request unused, can be null (must match signature of parent function).
149
     */
150
    public function run($request)
151
    {
152
        // Loading packages and all their updates can be quite memory intensive.
153
        $memoryLimit = $this->config()->get('memory_limit');
154
        if ($memoryLimit) {
155
            if (Environment::getMemoryLimitMax() < Convert::memstring2bytes($memoryLimit)) {
156
                Environment::setMemoryLimitMax($memoryLimit);
157
            }
158
            Environment::increaseMemoryLimitTo($memoryLimit);
159
        }
160
161
        $composerLock = $this->getComposerLoader()->getLock();
162
        $rawPackages = array_merge($composerLock->packages, (array) $composerLock->{'packages-dev'});
163
        $packages = $this->getPackageInfo($rawPackages);
164
165
        // Get "name" from $packages and put into an array
166
        $moduleNames = array_column($packages, 'Name');
167
168
        $supportedPackages = $this->getSupportedPackages();
169
        $moduleHealthInfo = $this->getHealthIndicator($moduleNames);
170
171
        // Extensions to the process that add data may rely on external services.
172
        // There may be a communication issue between the site and the external service,
173
        // so if there are 'none' we should assume this is untrue and _not_ proceed
174
        // to remove everything. Stale information is better than no information.
175
        if ($packages) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $packages of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
176
            // There is no onBeforeDelete for Package
177
            $table = DataObjectSchema::create()->tableName(Package::class);
178
            SQLDelete::create("\"$table\"")->execute();
179
            foreach ($packages as $package) {
180
                $packageName = $package['Name'];
181
                if (is_array($supportedPackages)) {
182
                    $package['Supported'] = in_array($packageName, $supportedPackages);
183
                }
184
                if (is_array($moduleHealthInfo) && isset($moduleHealthInfo[$packageName])) {
185
                    $package['Rating'] = $moduleHealthInfo[$packageName];
186
                }
187
                Package::create()->update($package)->write();
188
            }
189
        }
190
    }
191
192
    /**
193
     * Fetch information about the installed packages.
194
     *
195
     * @param array $packageList list of packages as objects, formatted as one finds in a composer.lock
196
     *
197
     * @return array indexed array of package information, represented as associative arrays.
198
     */
199
    public function getPackageInfo($packageList)
200
    {
201
        $formatInfo = function ($package) {
202
            // Convert object to array, with Capitalised keys
203
            $package = get_object_vars($package);
204
            return array_combine(
205
                array_map('ucfirst', array_keys($package)),
206
                $package
207
            );
208
        };
209
210
        $packageList = array_map($formatInfo, $packageList);
211
        $this->extend('updatePackageInfo', $packageList);
212
        return $packageList;
213
    }
214
215
    /**
216
     * Return an array of supported modules as fetched from addons.silverstripe.org. Outputs a message and returns null
217
     * if an error occurs
218
     *
219
     * @return null|array
220
     */
221
    public function getSupportedPackages()
222
    {
223
        try {
224
            return $this->getSupportedAddonsLoader()->getAddonNames() ?: [];
225
        } catch (RuntimeException $exception) {
226
            echo $exception->getMessage() . PHP_EOL;
227
        }
228
229
        return null;
230
    }
231
232
    /**
233
     * Return an array of module health information as fetched from addons.silverstripe.org. Outputs a message and
234
     * returns null if an error occurs
235
     *
236
     * @param string[] $moduleNames
237
     * @return null|array
238
     */
239
    public function getHealthIndicator(array $moduleNames)
240
    {
241
        try {
242
            return $this->getModuleHealthLoader()->setModuleNames($moduleNames)->getModuleHealthInfo() ?: [];
243
        } catch (RuntimeException $exception) {
244
            echo $exception->getMessage() . PHP_EOL;
245
        }
246
247
        return null;
248
    }
249
}
250