1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
4
|
|
|
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
5
|
|
|
* |
6
|
|
|
* Licensed under The MIT License |
7
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
8
|
|
|
* Redistributions of files must retain the above copyright notice. |
9
|
|
|
* |
10
|
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
11
|
|
|
* @link https://cakephp.org CakePHP(tm) Project |
12
|
|
|
* @since 3.0.0 |
13
|
|
|
* @license https://opensource.org/licenses/mit-license.php MIT License |
14
|
|
|
*/ |
15
|
|
|
namespace Cake\Shell\Task; |
16
|
|
|
|
17
|
|
|
use Cake\Console\Shell; |
18
|
|
|
use Cake\Core\Plugin; |
19
|
|
|
use Cake\Filesystem\Folder; |
20
|
|
|
use Cake\Utility\Inflector; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Task for symlinking / copying plugin assets to app's webroot. |
24
|
|
|
*/ |
25
|
|
|
class AssetsTask extends Shell |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* Attempt to symlink plugin assets to app's webroot. If symlinking fails it |
29
|
|
|
* fallbacks to copying the assets. For vendor namespaced plugin, parent folder |
30
|
|
|
* for vendor name are created if required. |
31
|
|
|
* |
32
|
|
|
* @param string|null $name Name of plugin for which to symlink assets. |
33
|
|
|
* If null all plugins will be processed. |
34
|
|
|
* @return void |
35
|
|
|
*/ |
36
|
|
|
public function symlink($name = null) |
37
|
|
|
{ |
38
|
|
|
$this->_process($this->_list($name)); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Copying plugin assets to app's webroot. For vendor namespaced plugin, |
43
|
|
|
* parent folder for vendor name are created if required. |
44
|
|
|
* |
45
|
|
|
* @param string|null $name Name of plugin for which to symlink assets. |
46
|
|
|
* If null all plugins will be processed. |
47
|
|
|
* @return void |
48
|
|
|
*/ |
49
|
|
|
public function copy($name = null) |
50
|
|
|
{ |
51
|
|
|
$this->_process($this->_list($name), true, $this->param('overwrite')); |
|
|
|
|
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Remove plugin assets from app's webroot. |
56
|
|
|
* |
57
|
|
|
* @param string|null $name Name of plugin for which to remove assets. |
58
|
|
|
* If null all plugins will be processed. |
59
|
|
|
* @return void |
60
|
|
|
* @since 3.5.12 |
61
|
|
|
*/ |
62
|
|
|
public function remove($name = null) |
63
|
|
|
{ |
64
|
|
|
$plugins = $this->_list($name); |
65
|
|
|
|
66
|
|
|
foreach ($plugins as $plugin => $config) { |
67
|
|
|
$this->out(); |
68
|
|
|
$this->out('For plugin: ' . $plugin); |
69
|
|
|
$this->hr(); |
70
|
|
|
|
71
|
|
|
$this->_remove($config); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
$this->out(); |
75
|
|
|
$this->out('Done'); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Get list of plugins to process. Plugins without a webroot directory are skipped. |
80
|
|
|
* |
81
|
|
|
* @param string|null $name Name of plugin for which to symlink assets. |
82
|
|
|
* If null all plugins will be processed. |
83
|
|
|
* @return array List of plugins with meta data. |
84
|
|
|
*/ |
85
|
|
|
protected function _list($name = null) |
86
|
|
|
{ |
87
|
|
|
if ($name === null) { |
88
|
|
|
$pluginsList = Plugin::loaded(); |
89
|
|
|
} else { |
90
|
|
|
if (!Plugin::isLoaded($name)) { |
91
|
|
|
$this->err(sprintf('Plugin %s is not loaded.', $name)); |
92
|
|
|
|
93
|
|
|
return []; |
94
|
|
|
} |
95
|
|
|
$pluginsList = [$name]; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$plugins = []; |
99
|
|
|
|
100
|
|
|
foreach ($pluginsList as $plugin) { |
101
|
|
|
$path = Plugin::path($plugin) . 'webroot'; |
102
|
|
|
if (!is_dir($path)) { |
103
|
|
|
$this->verbose('', 1); |
104
|
|
|
$this->verbose( |
105
|
|
|
sprintf('Skipping plugin %s. It does not have webroot folder.', $plugin), |
106
|
|
|
2 |
107
|
|
|
); |
108
|
|
|
continue; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
$link = Inflector::underscore($plugin); |
112
|
|
|
$dir = WWW_ROOT; |
113
|
|
|
$namespaced = false; |
114
|
|
|
if (strpos($link, '/') !== false) { |
115
|
|
|
$namespaced = true; |
116
|
|
|
$parts = explode('/', $link); |
117
|
|
|
$link = array_pop($parts); |
118
|
|
|
$dir = WWW_ROOT . implode(DIRECTORY_SEPARATOR, $parts) . DIRECTORY_SEPARATOR; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$plugins[$plugin] = [ |
122
|
|
|
'srcPath' => Plugin::path($plugin) . 'webroot', |
123
|
|
|
'destDir' => $dir, |
124
|
|
|
'link' => $link, |
125
|
|
|
'namespaced' => $namespaced, |
126
|
|
|
]; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return $plugins; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Process plugins |
134
|
|
|
* |
135
|
|
|
* @param array $plugins List of plugins to process |
136
|
|
|
* @param bool $copy Force copy mode. Default false. |
137
|
|
|
* @param bool $overwrite Overwrite existing files. |
138
|
|
|
* @return void |
139
|
|
|
*/ |
140
|
|
|
protected function _process($plugins, $copy = false, $overwrite = false) |
141
|
|
|
{ |
142
|
|
|
$overwrite = (bool)$this->param('overwrite'); |
143
|
|
|
|
144
|
|
|
foreach ($plugins as $plugin => $config) { |
145
|
|
|
$this->out(); |
146
|
|
|
$this->out('For plugin: ' . $plugin); |
147
|
|
|
$this->hr(); |
148
|
|
|
|
149
|
|
|
if ( |
150
|
|
|
$config['namespaced'] && |
151
|
|
|
!is_dir($config['destDir']) && |
152
|
|
|
!$this->_createDirectory($config['destDir']) |
153
|
|
|
) { |
154
|
|
|
continue; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
$dest = $config['destDir'] . $config['link']; |
158
|
|
|
|
159
|
|
|
if (file_exists($dest)) { |
160
|
|
|
if ($overwrite && !$this->_remove($config)) { |
161
|
|
|
continue; |
162
|
|
|
} elseif (!$overwrite) { |
163
|
|
|
$this->verbose( |
164
|
|
|
$dest . ' already exists', |
165
|
|
|
1 |
166
|
|
|
); |
167
|
|
|
|
168
|
|
|
continue; |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
if (!$copy) { |
173
|
|
|
$result = $this->_createSymlink( |
174
|
|
|
$config['srcPath'], |
175
|
|
|
$dest |
176
|
|
|
); |
177
|
|
|
if ($result) { |
178
|
|
|
continue; |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$this->_copyDirectory( |
183
|
|
|
$config['srcPath'], |
184
|
|
|
$dest |
185
|
|
|
); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
$this->out(); |
189
|
|
|
$this->out('Done'); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Remove folder/symlink. |
194
|
|
|
* |
195
|
|
|
* @param array $config Plugin config. |
196
|
|
|
* @return bool |
197
|
|
|
*/ |
198
|
|
|
protected function _remove($config) |
199
|
|
|
{ |
200
|
|
|
if ($config['namespaced'] && !is_dir($config['destDir'])) { |
201
|
|
|
$this->verbose( |
202
|
|
|
$config['destDir'] . $config['link'] . ' does not exist', |
203
|
|
|
1 |
204
|
|
|
); |
205
|
|
|
|
206
|
|
|
return false; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
$dest = $config['destDir'] . $config['link']; |
210
|
|
|
|
211
|
|
|
if (!file_exists($dest)) { |
212
|
|
|
$this->verbose( |
213
|
|
|
$dest . ' does not exist', |
214
|
|
|
1 |
215
|
|
|
); |
216
|
|
|
|
217
|
|
|
return false; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
if (is_link($dest)) { |
221
|
|
|
// @codingStandardsIgnoreLine |
222
|
|
|
if (@unlink($dest)) { |
223
|
|
|
$this->out('Unlinked ' . $dest); |
224
|
|
|
|
225
|
|
|
return true; |
226
|
|
|
} else { |
227
|
|
|
$this->err('Failed to unlink ' . $dest); |
228
|
|
|
|
229
|
|
|
return false; |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
$folder = new Folder($dest); |
234
|
|
|
if ($folder->delete()) { |
235
|
|
|
$this->out('Deleted ' . $dest); |
236
|
|
|
|
237
|
|
|
return true; |
238
|
|
|
} else { |
239
|
|
|
$this->err('Failed to delete ' . $dest); |
240
|
|
|
|
241
|
|
|
return false; |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Create directory |
247
|
|
|
* |
248
|
|
|
* @param string $dir Directory name |
249
|
|
|
* @return bool |
250
|
|
|
*/ |
251
|
|
|
protected function _createDirectory($dir) |
252
|
|
|
{ |
253
|
|
|
$old = umask(0); |
254
|
|
|
// @codingStandardsIgnoreStart |
255
|
|
|
$result = @mkdir($dir, 0755, true); |
256
|
|
|
// @codingStandardsIgnoreEnd |
257
|
|
|
umask($old); |
258
|
|
|
|
259
|
|
|
if ($result) { |
260
|
|
|
$this->out('Created directory ' . $dir); |
261
|
|
|
|
262
|
|
|
return true; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
$this->err('Failed creating directory ' . $dir); |
266
|
|
|
|
267
|
|
|
return false; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Create symlink |
272
|
|
|
* |
273
|
|
|
* @param string $target Target directory |
274
|
|
|
* @param string $link Link name |
275
|
|
|
* @return bool |
276
|
|
|
*/ |
277
|
|
|
protected function _createSymlink($target, $link) |
278
|
|
|
{ |
279
|
|
|
// @codingStandardsIgnoreStart |
280
|
|
|
$result = @symlink($target, $link); |
281
|
|
|
// @codingStandardsIgnoreEnd |
282
|
|
|
|
283
|
|
|
if ($result) { |
284
|
|
|
$this->out('Created symlink ' . $link); |
285
|
|
|
|
286
|
|
|
return true; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
return false; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Copy directory |
294
|
|
|
* |
295
|
|
|
* @param string $source Source directory |
296
|
|
|
* @param string $destination Destination directory |
297
|
|
|
* @return bool |
298
|
|
|
*/ |
299
|
|
|
protected function _copyDirectory($source, $destination) |
300
|
|
|
{ |
301
|
|
|
$folder = new Folder($source); |
302
|
|
|
if ($folder->copy(['to' => $destination])) { |
303
|
|
|
$this->out('Copied assets to directory ' . $destination); |
304
|
|
|
|
305
|
|
|
return true; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$this->err('Error copying assets to directory ' . $destination); |
309
|
|
|
|
310
|
|
|
return false; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Gets the option parser instance and configures it. |
315
|
|
|
* |
316
|
|
|
* @return \Cake\Console\ConsoleOptionParser |
317
|
|
|
*/ |
318
|
|
|
public function getOptionParser() |
319
|
|
|
{ |
320
|
|
|
$parser = parent::getOptionParser(); |
321
|
|
|
|
322
|
|
|
$parser->addSubcommand('symlink', [ |
323
|
|
|
'help' => 'Symlink (copy as fallback) plugin assets to app\'s webroot.', |
324
|
|
|
])->addSubcommand('copy', [ |
325
|
|
|
'help' => 'Copy plugin assets to app\'s webroot.', |
326
|
|
|
])->addSubcommand('remove', [ |
327
|
|
|
'help' => 'Remove plugin assets from app\'s webroot.', |
328
|
|
|
])->addArgument('name', [ |
329
|
|
|
'help' => 'A specific plugin you want to symlink assets for.', |
330
|
|
|
'optional' => true, |
331
|
|
|
])->addOption('overwrite', [ |
332
|
|
|
'help' => 'Overwrite existing symlink / folder / files.', |
333
|
|
|
'default' => false, |
334
|
|
|
'boolean' => true, |
335
|
|
|
]); |
336
|
|
|
|
337
|
|
|
return $parser; |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.