1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Bluz composer plugin |
4
|
|
|
* |
5
|
|
|
* @copyright Bluz PHP Team |
6
|
|
|
* @link https://github.com/bluzphp/composer-plugin |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* @namespace |
11
|
|
|
*/ |
12
|
|
|
namespace Bluz\Composer\Installers; |
13
|
|
|
|
14
|
|
|
use Composer\Composer; |
15
|
|
|
use Composer\EventDispatcher\EventSubscriberInterface; |
16
|
|
|
use Composer\Installer\PackageEvents; |
17
|
|
|
use Composer\IO\IOInterface; |
18
|
|
|
use Composer\Plugin\PluginInterface; |
19
|
|
|
use Composer\Script\Event; |
20
|
|
|
use Composer\Script\ScriptEvents; |
21
|
|
|
use Symfony\Component\Filesystem\Exception\IOException; |
22
|
|
|
use Symfony\Component\Filesystem\Filesystem; |
23
|
|
|
use Symfony\Component\Finder\Finder; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Class Plugin |
27
|
|
|
* |
28
|
|
|
* @package Bluz\Composer\Installers |
29
|
|
|
*/ |
30
|
|
|
class Plugin implements PluginInterface, EventSubscriberInterface |
31
|
|
|
{ |
32
|
|
|
const PERMISSION_CODE = 0755; |
33
|
|
|
const REPEAT = 5; |
34
|
|
|
const DIRECTORIES = [ |
35
|
|
|
'application', |
36
|
|
|
'data', |
37
|
|
|
'public', |
38
|
|
|
'tests' |
39
|
|
|
]; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var Installer |
43
|
|
|
*/ |
44
|
|
|
protected $installer; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var string |
48
|
|
|
*/ |
49
|
|
|
protected $environment; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var Filesystem |
53
|
|
|
*/ |
54
|
|
|
protected $filesystem; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Create instance, define constants |
58
|
|
|
*/ |
59
|
|
|
public function __construct() |
60
|
|
|
{ |
61
|
|
|
defined('PATH_ROOT') ?: define('PATH_ROOT', realpath($_SERVER['DOCUMENT_ROOT'])); |
62
|
|
|
defined('DS') ?: define('DS', DIRECTORY_SEPARATOR); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Called after the plugin is loaded |
67
|
|
|
* |
68
|
|
|
* It setup composer installer |
69
|
|
|
* |
70
|
|
|
* {@inheritDoc} |
71
|
|
|
*/ |
72
|
|
|
public function activate(Composer $composer, IOInterface $io) |
73
|
|
|
{ |
74
|
|
|
$this->installer = new Installer($io, $composer); |
75
|
|
|
$composer->getInstallationManager()->addInstaller($this->installer); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Registered events after the plugin is loaded |
80
|
|
|
* |
81
|
|
|
* {@inheritDoc} |
82
|
|
|
*/ |
83
|
|
|
public static function getSubscribedEvents(): array |
84
|
|
|
{ |
85
|
|
|
return [ |
86
|
|
|
// copy files to working directory |
87
|
|
|
PackageEvents::POST_PACKAGE_INSTALL => 'copyFiles', |
88
|
|
|
// copy new files |
89
|
|
|
PackageEvents::POST_PACKAGE_UPDATE => 'copyFiles', |
90
|
|
|
// removed unchanged files |
91
|
|
|
PackageEvents::PRE_PACKAGE_UPDATE => 'removeFiles', |
92
|
|
|
// removed all files |
93
|
|
|
PackageEvents::PRE_PACKAGE_UNINSTALL => 'removeFiles', |
94
|
|
|
// copy extra files from root composer.json |
95
|
|
|
ScriptEvents::POST_UPDATE_CMD => 'copyExtraFiles', |
96
|
|
|
// remove extra files from root composer.json |
97
|
|
|
// ScriptEvents::PRE_UPDATE_CMD => 'removeExtraFiles' |
|
|
|
|
98
|
|
|
]; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Hook which is called after install package |
104
|
|
|
* It copies bluz module |
105
|
|
|
* |
106
|
|
|
* @throws \InvalidArgumentException |
107
|
|
|
*/ |
108
|
|
|
public function copyFiles() |
109
|
|
|
{ |
110
|
|
|
if (file_exists($this->installer->getVendorPath())) { |
111
|
|
|
$this->copyModule(); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Hook which is called before update package |
117
|
|
|
* It checks bluz module |
118
|
|
|
*/ |
119
|
|
|
public function removeFiles() |
120
|
|
|
{ |
121
|
|
|
if (file_exists($this->installer->getVendorPath())) { |
122
|
|
|
$this->removeModule(); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Copy extra files from compose.json of project |
128
|
|
|
* |
129
|
|
|
* @param Event $event |
130
|
|
|
* |
131
|
|
|
* @return void |
132
|
|
|
* @throws \InvalidArgumentException |
133
|
|
|
*/ |
134
|
|
|
public function copyExtraFiles(Event $event) |
135
|
|
|
{ |
136
|
|
|
$extras = $event->getComposer()->getPackage()->getExtra(); |
137
|
|
|
if (array_key_exists('copy-files', $extras)) { |
138
|
|
|
$this->installer->getIo()->write( |
139
|
|
|
sprintf(' - Copied additional file(s)'), |
140
|
|
|
true |
141
|
|
|
); |
142
|
|
|
$this->copyExtras($extras['copy-files']); |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Remove extra files from compose.json of project |
148
|
|
|
* |
149
|
|
|
* @param Event $event |
150
|
|
|
* |
151
|
|
|
* @return void |
152
|
|
|
*/ |
153
|
|
|
public function removeExtraFiles(Event $event) |
154
|
|
|
{ |
155
|
|
|
$extras = $event->getComposer()->getPackage()->getExtra(); |
156
|
|
|
if (array_key_exists('copy-files', $extras)) { |
157
|
|
|
$this->removeExtras($extras['copy-files']); |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Get Filesystem |
163
|
|
|
* |
164
|
|
|
* @return Filesystem |
165
|
|
|
*/ |
166
|
|
|
protected function getFilesystem() |
167
|
|
|
{ |
168
|
|
|
if (!$this->filesystem) { |
169
|
|
|
$this->filesystem = new Filesystem(); |
170
|
|
|
} |
171
|
|
|
return $this->filesystem; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* getExtra |
176
|
|
|
* |
177
|
|
|
* @return array |
178
|
|
|
*/ |
179
|
|
|
protected function getExtraFiles() : array |
180
|
|
|
{ |
181
|
|
|
$moduleJson = json_decode(file_get_contents($this->installer->getVendorPath() . DS . 'composer.json'), true); |
182
|
|
|
|
183
|
|
|
if (isset($moduleJson, $moduleJson['extra'], $moduleJson['extra']['copy-files'])) { |
184
|
|
|
return $moduleJson['extra']['copy-files']; |
185
|
|
|
} |
186
|
|
|
return []; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Copy Module files |
191
|
|
|
* |
192
|
|
|
* @return void |
193
|
|
|
* @throws \InvalidArgumentException |
194
|
|
|
*/ |
195
|
|
|
protected function copyModule() |
196
|
|
|
{ |
197
|
|
|
$this->copyExtras($this->getExtraFiles()); |
198
|
|
|
|
199
|
|
|
foreach (self::DIRECTORIES as $directory) { |
200
|
|
|
$this->copy( |
201
|
|
|
$this->installer->getVendorPath() . DS . $directory . DS, |
202
|
|
|
PATH_ROOT . DS . $directory . DS |
203
|
|
|
); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
$this->installer->getIo()->write( |
207
|
|
|
sprintf( |
208
|
|
|
' - Copied <comment>%s</comment> module to application', |
209
|
|
|
basename($this->installer->getVendorPath()) |
210
|
|
|
), |
211
|
|
|
true |
212
|
|
|
); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* copyExtras |
217
|
|
|
* |
218
|
|
|
* @param array $files |
219
|
|
|
* |
220
|
|
|
* @return void |
221
|
|
|
* @throws \InvalidArgumentException |
222
|
|
|
*/ |
223
|
|
|
protected function copyExtras($files) |
224
|
|
|
{ |
225
|
|
|
foreach ($files as $source => $target) { |
226
|
|
|
$this->copy( |
227
|
|
|
dirname($this->installer->getVendorPath(), 2) . DS . $source, |
228
|
|
|
PATH_ROOT . DS . $target |
229
|
|
|
); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* It recursively copies the files and directories |
235
|
|
|
* |
236
|
|
|
* @param $source |
237
|
|
|
* @param $target |
238
|
|
|
* |
239
|
|
|
* @return void |
240
|
|
|
* @throws \InvalidArgumentException |
241
|
|
|
*/ |
242
|
|
|
protected function copy($source, $target) |
243
|
|
|
{ |
244
|
|
|
// skip, if not exists |
245
|
|
|
if (!file_exists($source)) { |
246
|
|
|
return; |
247
|
|
|
} |
248
|
|
|
// skip, if target exists |
249
|
|
|
if (is_file($target)) { |
250
|
|
|
$this->installer->getIo()->write( |
251
|
|
|
sprintf(' - File <comment>%s</comment> already exists', $target), |
252
|
|
|
true, |
253
|
|
|
IOInterface::VERBOSE |
254
|
|
|
); |
255
|
|
|
return; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
// Check the renaming of file for direct moving (file-to-file) |
259
|
|
|
$isRenameFile = substr($target, -1) !== '/' && !is_dir($source); |
260
|
|
|
|
261
|
|
|
if (file_exists($target) && !is_dir($target) && !$isRenameFile) { |
262
|
|
|
throw new \InvalidArgumentException('Destination directory is not a directory'); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
try { |
266
|
|
|
if ($isRenameFile) { |
267
|
|
|
$this->getFilesystem()->mkdir(dirname($target)); |
268
|
|
|
} else { |
269
|
|
|
$this->getFilesystem()->mkdir($target); |
270
|
|
|
} |
271
|
|
|
} catch (IOException $e) { |
272
|
|
|
throw new \InvalidArgumentException( |
273
|
|
|
sprintf('Could not create directory `%s`', $target) |
274
|
|
|
); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
if (false === file_exists($source)) { |
278
|
|
|
throw new \InvalidArgumentException( |
279
|
|
|
sprintf('Source directory or file `%s` does not exist', $source) |
280
|
|
|
); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
if (is_dir($source)) { |
284
|
|
|
$finder = new Finder; |
285
|
|
|
$finder->files()->in($source); |
286
|
|
|
|
287
|
|
|
foreach ($finder as $file) { |
288
|
|
|
try { |
289
|
|
|
$this->getFilesystem()->copy($file, $target . DS . $file->getRelativePathname()); |
290
|
|
|
} catch (IOException $e) { |
291
|
|
|
throw new \InvalidArgumentException( |
292
|
|
|
sprintf('Could not copy `%s`', $file->getBaseName()) |
293
|
|
|
); |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
} else { |
297
|
|
|
try { |
298
|
|
|
if ($isRenameFile) { |
299
|
|
|
$this->getFilesystem()->copy($source, $target); |
300
|
|
|
} else { |
301
|
|
|
$this->getFilesystem()->copy($source, $target . '/' . basename($source)); |
302
|
|
|
} |
303
|
|
|
} catch (IOException $e) { |
304
|
|
|
throw new \InvalidArgumentException(sprintf('Could not copy `%s`', $source)); |
305
|
|
|
} |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$this->installer->getIo()->write( |
309
|
|
|
sprintf(' - Copied file(s) from <comment>%s</comment> to <comment>%s</comment>', $source, $target), |
310
|
|
|
true, |
311
|
|
|
IOInterface::VERBOSE |
312
|
|
|
); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* It recursively removes the files and empty directories |
317
|
|
|
* @return void |
318
|
|
|
*/ |
319
|
|
|
protected function removeModule() |
320
|
|
|
{ |
321
|
|
|
$this->removeExtras($this->getExtraFiles()); |
322
|
|
|
|
323
|
|
|
foreach (self::DIRECTORIES as $directory) { |
324
|
|
|
$this->remove($directory); |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
$this->installer->getIo()->write( |
328
|
|
|
sprintf( |
329
|
|
|
' - Removed <comment>%s</comment> module from application', |
330
|
|
|
basename($this->installer->getVendorPath()) |
331
|
|
|
), |
332
|
|
|
true |
333
|
|
|
); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* removeExtras |
338
|
|
|
* |
339
|
|
|
* @param array $files |
340
|
|
|
* |
341
|
|
|
* @return void |
342
|
|
|
*/ |
343
|
|
|
protected function removeExtras($files) |
344
|
|
|
{ |
345
|
|
|
foreach ($files as $source => $target) { |
346
|
|
|
$this->installer->getIo()->write( |
347
|
|
|
sprintf(' - Skipped additional file(s) <comment>%s</comment>', $target), |
348
|
|
|
true |
349
|
|
|
); |
350
|
|
|
} |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* It recursively removes the files and directories |
355
|
|
|
* @param $directory |
356
|
|
|
* @return void |
357
|
|
|
*/ |
358
|
|
|
protected function remove($directory) |
359
|
|
|
{ |
360
|
|
|
$sourcePath = $this->installer->getVendorPath() . DS . $directory; |
361
|
|
|
|
362
|
|
|
if (!is_dir($sourcePath)) { |
363
|
|
|
return; |
364
|
|
|
} |
365
|
|
|
foreach ($iterator = new \RecursiveIteratorIterator( |
366
|
|
|
new \RecursiveDirectoryIterator( |
367
|
|
|
$sourcePath, |
368
|
|
|
\RecursiveDirectoryIterator::SKIP_DOTS |
369
|
|
|
), |
370
|
|
|
\RecursiveIteratorIterator::CHILD_FIRST |
371
|
|
|
) as $item) { |
372
|
|
|
// path to copied file |
373
|
|
|
$current = PATH_ROOT . DS . $directory . DS . $iterator->getSubPathName(); |
374
|
|
|
|
375
|
|
|
// remove empty directories |
376
|
|
|
if (is_dir($current)) { |
377
|
|
|
if (count(scandir($current, SCANDIR_SORT_ASCENDING)) === 2) { |
378
|
|
|
rmdir($current); |
379
|
|
|
$this->installer->getIo()->write( |
380
|
|
|
" - Removed directory `{$iterator->getSubPathName()}`", |
381
|
|
|
true, |
382
|
|
|
IOInterface::VERBOSE |
383
|
|
|
); |
384
|
|
|
} else { |
385
|
|
|
$this->installer->getIo()->write( |
386
|
|
|
sprintf( |
387
|
|
|
' - <comment>Skipped directory `%s`</comment>', |
388
|
|
|
$directory . DS . $iterator->getSubPathName() |
389
|
|
|
), |
390
|
|
|
true, |
391
|
|
|
IOInterface::VERBOSE |
392
|
|
|
); |
393
|
|
|
} |
394
|
|
|
continue; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
// skip already removed files |
398
|
|
|
if (!is_file($current)) { |
399
|
|
|
continue; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
if (md5_file($item) === md5_file($current)) { |
403
|
|
|
// remove file |
404
|
|
|
unlink($current); |
405
|
|
|
$this->installer->getIo()->write( |
406
|
|
|
" - Removed file `{$iterator->getSubPathName()}`", |
407
|
|
|
true, |
408
|
|
|
IOInterface::VERBOSE |
409
|
|
|
); |
410
|
|
|
} else { |
411
|
|
|
// or skip changed files |
412
|
|
|
$this->installer->getIo()->write( |
413
|
|
|
" - <comment>File `{$iterator->getSubPathName()}` has changed</comment>", |
414
|
|
|
true, |
415
|
|
|
IOInterface::VERBOSE |
416
|
|
|
); |
417
|
|
|
} |
418
|
|
|
} |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.