Library   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 68
c 1
b 0
f 0
dl 0
loc 270
rs 9.84
wmc 32

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 11 3
A getType() 0 8 2
A exposePaths() 0 14 3
A getPath() 0 3 1
A getBasePublicPath() 0 7 2
A getJson() 0 9 2
A getBasePath() 0 3 1
A publicPathExists() 0 3 1
A getPublicPath() 0 11 3
A requiresExpose() 0 9 3
A __construct() 0 5 1
A getExposedFolders() 0 17 4
A installedIntoVendor() 0 3 1
A validateFolder() 0 12 4
A getRelativePath() 0 3 1
1
<?php
2
3
namespace SilverStripe\VendorPlugin;
4
5
use Composer\Json\JsonFile;
6
use LogicException;
7
use SilverStripe\VendorPlugin\Methods\ExposeMethod;
8
9
/**
10
 * Represents a library being installed
11
 */
12
class Library
13
{
14
    const TRIM_CHARS = '/\\';
15
16
    /**
17
     * Hard-coded 'public' web-root folder
18
     */
19
    const PUBLIC_PATH = 'public';
20
21
    /**
22
     * Subfolder to map within public webroot
23
     */
24
    const RESOURCES_PATH = 'resources';
25
26
    /**
27
     * Project root
28
     *
29
     * @var string
30
     */
31
    protected $basePath = null;
32
33
    /**
34
     * Install path of this library
35
     *
36
     * @var string
37
     */
38
    protected $path = null;
39
40
    /**
41
     * Build a vendor module library
42
     *
43
     * @param string $basePath Project root folder
44
     * @param string $libraryPath Path to this library
45
     * @param string $name Composer name of this library
46
     */
47
    public function __construct($basePath, $libraryPath, $name = null)
48
    {
49
        $this->basePath = realpath($basePath);
50
        $this->path = realpath($libraryPath);
51
        $this->name = $name;
52
    }
53
54
    /**
55
     * Module name
56
     *
57
     * @var string
58
     */
59
    protected $name = null;
60
61
    /**
62
     * Get module name
63
     *
64
     * @return string
65
     */
66
    public function getName()
67
    {
68
        if ($this->name) {
69
            return $this->name;
70
        }
71
        // Get from composer
72
        $json = $this->getJson();
73
        if (isset($json['name'])) {
74
            $this->name = $json['name'];
75
        }
76
        return $this->name;
77
    }
78
79
    /**
80
     * Get type of library
81
     *
82
     * @return string
83
     */
84
    public function getType()
85
    {
86
        // Get from composer
87
        $json = $this->getJson();
88
        if (isset($json['type'])) {
89
            return $json['type'];
90
        }
91
        return 'module';
92
    }
93
94
    /**
95
     * Get path to base project for this module
96
     *
97
     * @return string Path with no trailing slash E.g. /var/www/
98
     */
99
    public function getBasePath()
100
    {
101
        return $this->basePath;
102
    }
103
104
    /**
105
     * Get base path to expose all libraries to
106
     *
107
     * @return string Path with no trailing slash E.g. /var/www/public/resources
108
     */
109
    public function getBasePublicPath()
110
    {
111
        $projectPath = $this->getBasePath();
112
        $publicPath = $this->publicPathExists()
113
            ? Util::joinPaths($projectPath, self::PUBLIC_PATH, self::RESOURCES_PATH)
114
            : Util::joinPaths($projectPath, self::RESOURCES_PATH);
115
        return $publicPath;
116
    }
117
118
    /**
119
     * Get path for this module
120
     *
121
     * @return string Path with no trailing slash E.g. /var/www/vendor/silverstripe/module
122
     */
123
    public function getPath()
124
    {
125
        return $this->path;
126
    }
127
128
    /**
129
     * Get path relative to base dir.
130
     * If module path is base this will be empty string
131
     *
132
     * @return string Path with trimmed slashes. E.g. vendor/silverstripe/module.
133
     * This will be empty for the base project.
134
     */
135
    public function getRelativePath()
136
    {
137
        return trim(substr($this->path, strlen($this->basePath)), self::TRIM_CHARS);
138
    }
139
140
    /**
141
     * Get base path to map resources for this module
142
     *
143
     * @return string Path with trimmed slashes. E.g. /var/www/public/resources/vendor/silverstripe/module
144
     */
145
    public function getPublicPath()
146
    {
147
        $relativePath = $this->getRelativePath();
148
149
        // 4.0 compatibility: If there is no public folder, and this is a vendor path,
150
        // remove the leading `vendor` from the destination
151
        if (!$this->publicPathExists() && $this->installedIntoVendor()) {
152
            $relativePath = substr($relativePath, strlen('vendor/'));
153
        }
154
155
        return Util::joinPaths($this->getBasePublicPath(), $relativePath);
156
    }
157
158
    /**
159
     * Cache of composer.json content
160
     *
161
     * @var array
162
     */
163
    protected $json = [];
164
165
    /**
166
     * Get json content for this module from composer.json
167
     *
168
     * @return array
169
     */
170
    protected function getJson()
171
    {
172
        if ($this->json) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->json 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...
173
            return $this->json;
174
        }
175
        $composer = Util::joinPaths($this->getPath(), 'composer.json');
176
        $file = new JsonFile($composer);
177
        $this->json = $file->read();
178
        return $this->json;
179
    }
180
181
    /**
182
     * Determine if this module should be exposed.
183
     * Note: If not using public folders, only vendor modules need to be exposed
184
     *
185
     * @return bool
186
     */
187
    public function requiresExpose()
188
    {
189
        // Don't expose if no folders configured
190
        if (!$this->getExposedFolders()) {
191
            return false;
192
        }
193
194
        // Expose if either public root exists, or vendor module
195
        return $this->publicPathExists() || $this->installedIntoVendor();
196
    }
197
198
    /**
199
     * Expose all web accessible paths for this module
200
     *
201
     * @param ExposeMethod $method
202
     */
203
    public function exposePaths(ExposeMethod $method)
204
    {
205
        // No-op if exposure not necessary for this configuration
206
        if (!$this->requiresExpose()) {
207
            return;
208
        }
209
        $folders = $this->getExposedFolders();
210
        $sourcePath = $this->getPath();
211
        $targetPath = $this->getPublicPath();
212
        foreach ($folders as $folder) {
213
            // Get paths for this folder and delegate to expose method
214
            $folderSourcePath = Util::joinPaths($sourcePath, $folder);
215
            $folderTargetPath = Util::joinPaths($targetPath, $folder);
216
            $method->exposeDirectory($folderSourcePath, $folderTargetPath);
217
        }
218
    }
219
220
    /**
221
     * Get name of all folders to expose (relative to module root)
222
     *
223
     * @return array
224
     */
225
    public function getExposedFolders()
226
    {
227
        $data = $this->getJson();
228
229
        // Get all dirs to expose
230
        if (empty($data['extra']['expose'])) {
231
            return [];
232
        }
233
        $expose = $data['extra']['expose'];
234
235
        // Validate all paths are safe
236
        foreach ($expose as $exposeFolder) {
237
            if (!$this->validateFolder($exposeFolder)) {
238
                throw new LogicException("Invalid module folder " . $exposeFolder);
239
            }
240
        }
241
        return $expose;
242
    }
243
244
    /**
245
     * Validate the given folder is allowed
246
     *
247
     * @param string $exposeFolder Relative folder name to check
248
     * @return bool
249
     */
250
    protected function validateFolder($exposeFolder)
251
    {
252
        if (strstr($exposeFolder, '.')) {
253
            return false;
254
        }
255
        if (strpos($exposeFolder, '/') === 0) {
256
            return false;
257
        }
258
        if (strpos($exposeFolder, '\\') === 0) {
259
            return false;
260
        }
261
        return true;
262
    }
263
264
    /**
265
     * Determin eif the public folder exists
266
     *
267
     * @return bool
268
     */
269
    public function publicPathExists()
270
    {
271
        return is_dir(Util::joinPaths($this->getBasePath(), self::PUBLIC_PATH));
272
    }
273
274
    /**
275
     * Check if this module is installed in vendor
276
     *
277
     * @return bool
278
     */
279
    protected function installedIntoVendor()
280
    {
281
        return preg_match('#^vendor[/\\\\]#', $this->getRelativePath());
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('#^ven...his->getRelativePath()) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
282
    }
283
}
284