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) { |
|
|
|
|
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()); |
|
|
|
|
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
|
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.