Passed
Push — 4 ( bffb7e...c14394 )
by Garion
08:34 queued 10s
created

VersionProvider::getComposerLock()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 10
nop 1
dl 0
loc 27
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Manifest;
4
5
use SilverStripe\Core\Config\Config;
6
use Psr\SimpleCache\CacheInterface;
7
use SilverStripe\Core\Config\Configurable;
8
use SilverStripe\Core\Convert;
9
use SilverStripe\Core\Injector\Injector;
10
11
/**
12
 * The version provider will look up configured modules and examine the composer.lock file
13
 * to find the current version installed for each. This is used for the logo title in the CMS
14
 * via {@link LeftAndMain::CMSVersion()}
15
 *
16
 * Example configuration:
17
 *
18
 * <code>
19
 * SilverStripe\Core\Manifest\VersionProvider:
20
 *   modules:
21
 *     # package/name: Package Title
22
 *     silverstripe/framework: Framework
23
 *     silverstripe/cms: CMS
24
 * </code>
25
 */
26
class VersionProvider
27
{
28
29
    use Configurable;
30
31
    /**
32
     * @var array
33
     */
34
    private static $modules = [];
0 ignored issues
show
introduced by
The private property $modules is not used, and could be removed.
Loading history...
35
36
    /**
37
     * Gets a comma delimited string of package titles and versions
38
     *
39
     * @return string
40
     */
41
    public function getVersion()
42
    {
43
        $modules = $this->getModules();
44
        $lockModules = $this->getModuleVersionFromComposer(array_keys($modules));
45
        $moduleVersions = [];
46
        foreach ($modules as $module => $title) {
47
            if (!array_key_exists($module, $lockModules)) {
48
                continue;
49
            }
50
            $version = $lockModules[$module];
51
            $moduleVersions[$module] = [$title, $version];
52
        }
53
        $moduleVersions = $this->filterModules($moduleVersions);
54
        $ret = [];
55
        foreach ($moduleVersions as $module => $value) {
56
            list($title, $version) = $value;
57
            $ret[] = "$title: $version";
58
        }
59
        return implode(', ', $ret);
60
    }
61
62
    /**
63
     * Filter modules to only use the last module from a git repo, for example
64
     *
65
     * [
66
     *   silverstripe/framework => ['Framework', 1.1.1'],
67
     *   silverstripe/cms => ['CMS', 2.2.2'],
68
     *   silverstripe/recipe-cms => ['CMS Recipe', '3.3.3'],
69
     *   cwp/cwp-core => ['CWP', '4.4.4']
70
     * ]
71
     * =>
72
     * [
73
     *   silverstripe/recipe-cms => ['CMS Recipe', '3.3.3'],
74
     *   cwp/cwp-core => ['CWP', '4.4.4']
75
     * ]
76
     *
77
     * @param array $modules
78
     * @return array
79
     */
80
    private function filterModules(array $modules)
81
    {
82
        $accountModule = [];
83
        foreach ($modules as $module => $value) {
84
            if (!preg_match('#^([a-z0-9\-]+)/([a-z0-9\-]+)$#', $module, $m)) {
85
                continue;
86
            }
87
            $account = $m[1];
88
            $accountModule[$account] = [$module, $value];
89
        }
90
        $ret = [];
91
        foreach ($accountModule as $account => $arr) {
92
            list($module, $value) = $arr;
93
            $ret[$module] = $value;
94
        }
95
        return $ret;
96
    }
97
98
    /**
99
     * Gets the configured core modules to use for the SilverStripe application version
100
     *
101
     * @return array
102
     */
103
    public function getModules()
104
    {
105
        $modules = Config::inst()->get(self::class, 'modules');
106
        return !empty($modules) ? $modules : ['silverstripe/framework' => 'Framework'];
107
    }
108
109
    /**
110
     * Tries to obtain version number from composer.lock if it exists
111
     *
112
     * @param array $modules
113
     * @return array
114
     */
115
    public function getModuleVersionFromComposer($modules = [])
116
    {
117
        $versions = [];
118
        $lockData = $this->getComposerLock();
119
        if ($lockData && !empty($lockData['packages'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lockData 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...
120
            foreach ($lockData['packages'] as $package) {
121
                if (in_array($package['name'], $modules) && isset($package['version'])) {
122
                    $versions[$package['name']] = $package['version'];
123
                }
124
            }
125
        }
126
        return $versions;
127
    }
128
129
    /**
130
     * Load composer.lock's contents and return it
131
     *
132
     * @param bool $cache
133
     * @return array
134
     */
135
    protected function getComposerLock($cache = true)
136
    {
137
        $composerLockPath = $this->getComposerLockPath();
138
        if (!file_exists($composerLockPath)) {
139
            return [];
140
        }
141
142
        $lockData = [];
143
        $jsonData = file_get_contents($composerLockPath);
144
145
        if ($cache) {
146
            $cache = Injector::inst()->get(CacheInterface::class . '.VersionProvider_composerlock');
147
            $cacheKey = md5($jsonData);
148
            if ($versions = $cache->get($cacheKey)) {
149
                $lockData = json_decode($versions, true);
150
            }
151
        }
152
153
        if (empty($lockData) && $jsonData) {
154
            $lockData = json_decode($jsonData, true);
155
156
            if ($cache) {
157
                $cache->set($cacheKey, $jsonData);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cacheKey does not seem to be defined for all execution paths leading up to this point.
Loading history...
158
            }
159
        }
160
161
        return $lockData;
162
    }
163
164
    /**
165
     * @return string
166
     */
167
    protected function getComposerLockPath(): string
168
    {
169
        return BASE_PATH . '/composer.lock';
170
    }
171
}
172