1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Class file for the factory that generates test plugin data. |
4
|
|
|
* |
5
|
|
|
* @package automattic/jetpack-autoloader |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
// phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents |
9
|
|
|
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents |
10
|
|
|
// phpcs:disable WordPress.WP.AlternativeFunctions.json_encode_json_encode |
11
|
|
|
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.system_calls_exec |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Class Test_Plugin_Factory |
15
|
|
|
*/ |
16
|
|
|
class Test_Plugin_Factory { |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* The root namespace all of the test class files will live in. |
20
|
|
|
*/ |
21
|
|
|
const TESTING_NAMESPACE = 'Automattic\\Jetpack\\AutoloaderTesting\\'; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* The string representation of the current autoloader. |
25
|
|
|
*/ |
26
|
|
|
const CURRENT = 'current'; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* A constant for the autoloader version of a current plugin. |
30
|
|
|
*/ |
31
|
|
|
const VERSION_CURRENT = '1000.0.0.0'; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Indicates whether or not the plugin is an mu-plugin. |
35
|
|
|
* |
36
|
|
|
* @var bool |
37
|
|
|
*/ |
38
|
|
|
private $is_mu_plugin; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* The slug of the plugin we're creating. |
42
|
|
|
* |
43
|
|
|
* @var string |
44
|
|
|
*/ |
45
|
|
|
private $slug; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* The composer autoloads that we're going to write to the configuration. |
49
|
|
|
* |
50
|
|
|
* @var array |
51
|
|
|
*/ |
52
|
|
|
private $autoloads; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* The files that will be created as part of the plugin. |
56
|
|
|
* |
57
|
|
|
* @var array |
58
|
|
|
*/ |
59
|
|
|
private $files; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* The version of the autoloader we want to utilize. |
63
|
|
|
* |
64
|
|
|
* @var string |
65
|
|
|
*/ |
66
|
|
|
private $autoloader_version; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* The custom options we would like to pass to composer. |
70
|
|
|
* |
71
|
|
|
* @var string[] |
72
|
|
|
*/ |
73
|
|
|
private $composer_options; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Constructor. |
77
|
|
|
* |
78
|
|
|
* @param bool $is_mu_plugin Indicates whether or not the plugin is an mu-plugin. |
79
|
|
|
* @param string $slug The slug of the plugin. |
80
|
|
|
* @param string[] $autoloads The composer autoloads for the plugin. |
81
|
|
|
*/ |
82
|
|
|
private function __construct( $is_mu_plugin, $slug, $autoloads ) { |
83
|
|
|
$this->is_mu_plugin = $is_mu_plugin; |
84
|
|
|
$this->slug = $slug; |
85
|
|
|
$this->autoloads = $autoloads; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Creates a new factory for the plugin and returns it. |
90
|
|
|
* |
91
|
|
|
* @param bool $is_mu_plugin Indicates whether or not the plugin is an mu-plugin. |
92
|
|
|
* @param string $slug The slug of the plugin we're building. |
93
|
|
|
* @param string[] $autoloads The composer autoloads for the plugin we're building. |
94
|
|
|
* @return Test_Plugin_Factory |
95
|
|
|
* @throws \InvalidArgumentException When the slug is invalid. |
96
|
|
|
*/ |
97
|
|
|
public static function create( $is_mu_plugin, $slug, $autoloads ) { |
98
|
|
|
if ( false !== strpos( $slug, ' ' ) ) { |
99
|
|
|
throw new \InvalidArgumentException( 'Plugin slugs may not have spaces.' ); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$slug = strtolower( preg_replace( '/[^A-Za-z0-9\-_]/', '', $slug ) ); |
103
|
|
|
|
104
|
|
|
return new Test_Plugin_Factory( $is_mu_plugin, $slug, $autoloads ); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Creates a new factory configured for a generic test plugin and returns it. |
109
|
|
|
* |
110
|
|
|
* @param bool $is_mu_plugin Indicates whether or not the plugin is an mu-plugin. |
111
|
|
|
* @param string $version The version of the autoloader we want the plugin to use. |
112
|
|
|
* @return Test_Plugin_Factory |
113
|
|
|
*/ |
114
|
|
|
public static function create_test_plugin( $is_mu_plugin, $version ) { |
115
|
|
|
// We will use a global to detect when a file has been loaded by the autoloader. |
116
|
|
|
global $jetpack_autoloader_testing_loaded_files; |
117
|
|
|
if ( ! isset( $jetpack_autoloader_testing_loaded_files ) ) { |
118
|
|
|
$jetpack_autoloader_testing_loaded_files = array(); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$file_version = $version; |
122
|
|
|
if ( self::CURRENT === $version ) { |
123
|
|
|
$file_version = self::VERSION_CURRENT; |
124
|
|
|
$namespace_version = 'Current'; |
125
|
|
|
} else { |
126
|
|
|
$namespace_version = 'v' . str_replace( '.', '_', $version ); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
// Avoid namespace collisions between plugins & mu-plugins. |
130
|
|
|
if ( $is_mu_plugin ) { |
131
|
|
|
$namespace_version .= 'mu'; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
// We need to define all of the autoloads that the files contained within will utilize. |
135
|
|
|
$autoloads = array( |
136
|
|
|
'classmap' => array( 'includes' ), |
137
|
|
|
'psr-4' => array( |
138
|
|
|
self::TESTING_NAMESPACE => 'src', |
139
|
|
|
), |
140
|
|
|
'files' => array( 'functions.php' ), |
141
|
|
|
); |
142
|
|
|
|
143
|
|
|
return self::create( $is_mu_plugin, str_replace( '.', '_', $version ), $autoloads ) |
|
|
|
|
144
|
|
|
->with_class( 'classmap', 'Classmap_Test_Class', "\tconst VERSION = '$file_version';" ) |
145
|
|
|
->with_class( 'psr-4', self::TESTING_NAMESPACE . 'SharedTestClass', "\tconst VERSION = '$file_version';" ) |
146
|
|
|
->with_class( 'psr-4', self::TESTING_NAMESPACE . "$namespace_version\\UniqueTestClass", '' ) |
147
|
|
|
->with_file( 'functions.php', "<?php\n\nglobal \$jetpack_autoloader_testing_loaded_files;\n\$jetpack_autoloader_testing_loaded_files[] = '$file_version';" ) |
148
|
|
|
->with_autoloader_version( $version ) |
149
|
|
|
->with_composer_config( array( 'config' => array( 'autoloader-suffix' => $namespace_version ) ) ); |
|
|
|
|
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Adds a file to the plugin being built. |
154
|
|
|
* |
155
|
|
|
* @param string $path The path for the file in the plugin directory. |
156
|
|
|
* @param string $content The content for the file. |
157
|
|
|
* @return $this |
158
|
|
|
*/ |
159
|
|
|
public function with_file( $path, $content ) { |
160
|
|
|
$this->files[ $path ] = $content; |
161
|
|
|
return $this; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Adds a class file to the plugin being built. |
166
|
|
|
* |
167
|
|
|
* @param string $autoload_type The type of autoloading to name the file for. 'classmap', 'psr-4', 'psr-0' are options. |
168
|
|
|
* @param string $fqn The fully qualified name for the class. |
169
|
|
|
* @param string $content The content of the class. |
170
|
|
|
* @return $this |
171
|
|
|
* @throws \InvalidArgumentException When the input arguments are invalid. |
172
|
|
|
*/ |
173
|
|
|
public function with_class( $autoload_type, $fqn, $content ) { |
174
|
|
|
if ( ! isset( $this->autoloads[ $autoload_type ] ) ) { |
175
|
|
|
throw new \InvalidArgumentException( 'The autoload type for this class is not registered with the factory.' ); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
// The path to the file depends on the type of autoloading it utilizes. |
179
|
|
|
$fqn = ltrim( $fqn, '\\' ); |
180
|
|
|
if ( false !== strpos( $fqn, '\\' ) ) { |
181
|
|
|
$class_name = substr( $fqn, strripos( $fqn, '\\' ) + 1 ); |
182
|
|
|
$namespace = substr( $fqn, 0, -strlen( $class_name ) - 1 ); |
183
|
|
|
} else { |
184
|
|
|
$class_name = $fqn; |
185
|
|
|
$namespace = null; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
$path = null; |
189
|
|
|
switch ( $autoload_type ) { |
190
|
|
|
case 'classmap': |
191
|
|
|
$path = 'includes' . DIRECTORY_SEPARATOR . 'class-' . strtolower( str_replace( '_', '-', $class_name ) ) . '.php'; |
192
|
|
|
break; |
193
|
|
|
|
194
|
|
|
case 'psr-0': |
195
|
|
|
case 'psr-4': |
196
|
|
|
// Find the associated autoload entry so we can create the correct path. |
197
|
|
|
$autoload_namespaces = $this->autoloads[ $autoload_type ]; |
198
|
|
|
foreach ( $autoload_namespaces as $autoload_namespace => $dir ) { |
199
|
|
|
if ( is_array( $dir ) ) { |
200
|
|
|
throw new \InvalidArgumentException( 'The factory only supports single mapping for PSR-0/PSR-4 namespaces.' ); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
$check = substr( $namespace . '\\', 0, strlen( $autoload_namespace ) ); |
204
|
|
|
if ( $autoload_namespace !== $check ) { |
205
|
|
|
continue; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
// Build a path using the rest of the namespace. |
209
|
|
|
$path = $dir . DIRECTORY_SEPARATOR; |
210
|
|
|
$structure = explode( '\\', substr( $namespace, strlen( $check ) ) ); |
211
|
|
|
foreach ( $structure as $s ) { |
212
|
|
|
$path .= $s . DIRECTORY_SEPARATOR; |
213
|
|
|
} |
214
|
|
|
break; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
if ( ! isset( $path ) ) { |
218
|
|
|
throw new \InvalidArgumentException( 'The namespace for this class is not in the factory\'s autoloads.' ); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
// PSR-0 treats underscores in the class name as directory separators. |
222
|
|
|
$path .= str_replace( '_', 'psr-0' === $autoload_type ? DIRECTORY_SEPARATOR : '', $class_name ) . '.php'; |
223
|
|
|
break; |
224
|
|
|
|
225
|
|
|
default: |
226
|
|
|
throw new \InvalidArgumentException( 'The given autoload type is invalid.' ); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
$file_content = "<?php\n\n"; |
230
|
|
|
if ( isset( $namespace ) ) { |
231
|
|
|
$file_content .= "namespace $namespace;\n\n"; |
232
|
|
|
} |
233
|
|
|
$file_content .= "class $class_name {\n$content\n}"; |
234
|
|
|
|
235
|
|
|
return $this->with_file( $path, $file_content ); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Declares the version of the autoloader that the plugin should use. When "current" is passed the package |
240
|
|
|
* will use a symlink to the local package instead of an external dependency. |
241
|
|
|
* |
242
|
|
|
* @param string $version The version of autoloader to use. Pass "current" to use the local package. |
243
|
|
|
* @return $this |
244
|
|
|
*/ |
245
|
|
|
public function with_autoloader_version( $version ) { |
246
|
|
|
$this->autoloader_version = $version; |
247
|
|
|
return $this; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Adds options that will be passed to the plugin's composer.json file. |
252
|
|
|
* |
253
|
|
|
* @param string[] $options The options that we want to set in the composer config. |
254
|
|
|
* @return $this |
255
|
|
|
*/ |
256
|
|
|
public function with_composer_config( $options ) { |
257
|
|
|
$this->composer_options = $options; |
258
|
|
|
return $this; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Brings the plugin to life and returns the absolute path to the plugin directory. |
263
|
|
|
* |
264
|
|
|
* @return string |
265
|
|
|
* @throws \RuntimeException When the factory fails to initialize composer. |
266
|
|
|
*/ |
267
|
|
|
public function make() { |
268
|
|
|
if ( $this->is_mu_plugin ) { |
269
|
|
|
$plugin_dir = WPMU_PLUGIN_DIR . DIRECTORY_SEPARATOR . $this->slug; |
270
|
|
|
} else { |
271
|
|
|
$plugin_dir = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $this->slug; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
$plugin_file = "<?php\n/**\n * Plugin Name: {$this->slug}\n */\n"; |
275
|
|
|
$composer_config = $this->build_composer_config(); |
276
|
|
|
|
277
|
|
|
// Don't write the plugin if it hasn't changed. |
278
|
|
|
if ( ! $this->has_plugin_changed( $plugin_dir, $plugin_file, $composer_config ) ) { |
279
|
|
|
return $plugin_dir; |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
// We want a clean directory to ensure files get removed. |
283
|
|
|
$this->remove_directory( $plugin_dir ); |
284
|
|
|
|
285
|
|
|
// Start by writing the main plugin file. |
286
|
|
|
mkdir( $plugin_dir, 0777, true ); |
287
|
|
|
file_put_contents( $plugin_dir . DIRECTORY_SEPARATOR . $this->slug . '.php', $plugin_file ); |
288
|
|
|
|
289
|
|
|
// Write all of the files into the plugin directory. |
290
|
|
|
foreach ( $this->files as $path => $content ) { |
291
|
|
|
$dir = dirname( $plugin_dir . DIRECTORY_SEPARATOR . $path ); |
292
|
|
|
if ( ! is_dir( $dir ) ) { |
293
|
|
|
mkdir( $dir, 0777, true ); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
file_put_contents( $plugin_dir . DIRECTORY_SEPARATOR . $path, $content ); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
// We also need to write the composer configuration for the plugin. |
300
|
|
|
file_put_contents( |
301
|
|
|
$plugin_dir . DIRECTORY_SEPARATOR . 'composer.json', |
302
|
|
|
json_encode( $composer_config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) |
303
|
|
|
); |
304
|
|
|
|
305
|
|
|
// Now that our plugin folder is ready let's install it. |
306
|
|
|
$this->execute_composer( $plugin_dir ); |
307
|
|
|
|
308
|
|
|
return $plugin_dir; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Indicates whether or not we are linking to the local package. |
313
|
|
|
* |
314
|
|
|
* @return bool |
315
|
|
|
*/ |
316
|
|
|
private function is_using_local_package() { |
317
|
|
|
return self::CURRENT === $this->autoloader_version; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Creates and returns the configuration for the composer.json file. |
322
|
|
|
* |
323
|
|
|
* @return array |
324
|
|
|
*/ |
325
|
|
|
private function build_composer_config() { |
326
|
|
|
$composer_config = array( |
327
|
|
|
'name' => 'testing/' . $this->slug, |
328
|
|
|
'autoload' => $this->autoloads, |
329
|
|
|
); |
330
|
|
|
if ( $this->is_using_local_package() ) { |
331
|
|
|
$composer_config['require'] = array( 'automattic/jetpack-autoloader' => 'dev-master' ); |
332
|
|
|
$composer_config['repositories'] = array( |
333
|
|
|
array( |
334
|
|
|
'type' => 'path', |
335
|
|
|
'url' => TEST_PACKAGE_DIR, |
336
|
|
|
'options' => array( |
337
|
|
|
'symlink' => true, |
338
|
|
|
), |
339
|
|
|
), |
340
|
|
|
); |
341
|
|
|
} elseif ( isset( $this->autoloader_version ) ) { |
342
|
|
|
$composer_config['require'] = array( 'automattic/jetpack-autoloader' => $this->autoloader_version ); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
if ( isset( $this->composer_options ) ) { |
346
|
|
|
$composer_config = array_merge( $composer_config, $this->composer_options ); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
return $composer_config; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Downloads the appropriate version of Composer and executes an install in the plugin directory. |
354
|
|
|
* |
355
|
|
|
* @param string $plugin_dir The plugin directory we want to execute Composer in. |
356
|
|
|
* @throws \RuntimeException When Composer fails to execute. |
357
|
|
|
*/ |
358
|
|
|
private function execute_composer( $plugin_dir ) { |
359
|
|
|
// Due to changes in the autoloader over time we cannot assume that whatever version of composer |
360
|
|
|
// the developer has installed is compatible. To address these differences we will download a |
361
|
|
|
// composer package that is compatible based on ranges of autoloader versions. |
362
|
|
|
$composer_versions = array( |
363
|
|
|
'2.0.9' => array( |
364
|
|
|
'min' => '2.6.0', |
365
|
|
|
'url' => 'https://getcomposer.org/download/2.0.9/composer.phar', |
366
|
|
|
'sha256' => '24faa5bc807e399f32e9a21a33fbb5b0686df9c8850efabe2c047c2ccfb9f9cc', |
367
|
|
|
), |
368
|
|
|
// Version 2.0.6 of Composer changed a base class we used to inherit in a way that throws fatals. |
369
|
|
|
'2.0.5' => array( |
370
|
|
|
'min' => '2.0.0', |
371
|
|
|
'url' => 'https://getcomposer.org/download/2.0.5/composer.phar', |
372
|
|
|
'sha256' => 'e786d1d997efc1eb463d7447394b6ad17a144afcf8e505a3ce3cb0f60c3302f9', |
373
|
|
|
), |
374
|
|
|
// Version 2.x support was not added until the 2.x version of the autoloader. |
375
|
|
|
'1.10.20' => array( |
376
|
|
|
'min' => '1.0.0', |
377
|
|
|
'url' => 'https://getcomposer.org/download/1.10.20/composer.phar', |
378
|
|
|
'sha256' => 'e70b1024c194e07db02275dd26ed511ce620ede45c1e237b3ef51d5f8171348d', |
379
|
|
|
), |
380
|
|
|
); |
381
|
|
|
// Make sure that we're iterating from the oldest Composer version to the newest. |
382
|
|
|
uksort( $composer_versions, 'version_compare' ); |
383
|
|
|
|
384
|
|
|
// When we're not installing the autoloader we can just use the latest version. |
385
|
|
|
if ( ! isset( $this->autoloader_version ) ) { |
386
|
|
|
$selected = '2.0.9'; |
387
|
|
|
} else { |
388
|
|
|
// Find the latest version of Composer that is compatible with our autoloader. |
389
|
|
|
$version = self::CURRENT === $this->autoloader_version ? self::VERSION_CURRENT : $this->autoloader_version; |
390
|
|
|
$selected = null; |
391
|
|
|
foreach ( $composer_versions as $composer_version => $data ) { |
392
|
|
|
if ( version_compare( $version, $data['min'], '<' ) ) { |
393
|
|
|
break; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
$selected = $composer_version; |
397
|
|
|
} |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
// Download the selected version of Composer if we haven't already done so. |
401
|
|
|
$composer_bin = TEST_TEMP_BIN_DIR . DIRECTORY_SEPARATOR . 'composer_' . str_replace( '.', '_', $selected ) . '.phar'; |
402
|
|
|
if ( ! file_exists( $composer_bin ) ) { |
403
|
|
|
$data = $composer_versions[ $selected ]; |
404
|
|
|
$content = file_get_contents( $data['url'] ); |
405
|
|
|
if ( hash( 'sha256', $content ) !== $data['sha256'] ) { |
406
|
|
|
throw new \RuntimeException( 'The Composer file downloaded has a different SHA256 than expected.' ); |
407
|
|
|
} |
408
|
|
|
file_put_contents( $composer_bin, $content ); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
// We can finally execute Composer now that we're ready. |
412
|
|
|
exec( 'php ' . escapeshellarg( $composer_bin ) . ' install -d ' . escapeshellarg( $plugin_dir ) . ' 2>&1' ); |
413
|
|
|
if ( ! is_file( $plugin_dir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php' ) ) { |
414
|
|
|
throw new \RuntimeException( 'Unable to execute the `' . $composer_bin . '` archive for tests.' ); |
415
|
|
|
} |
416
|
|
|
if ( isset( $this->autoloader_version ) && ! is_file( $plugin_dir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload_packages.php' ) ) { |
417
|
|
|
throw new \RuntimeException( 'Failed to install the autoloader.' ); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
// Local autoloaders require using the branch but we may not want to treat it as a developer build. |
421
|
|
|
if ( $this->is_using_local_package() ) { |
422
|
|
|
$manifest_dir = $plugin_dir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; |
423
|
|
|
$manifests = array( 'jetpack_autoload_classmap.php', 'jetpack_autoload_psr4.php', 'jetpack_autoload_psr0.php', 'jetpack_autoload_filemap.php' ); |
424
|
|
|
foreach ( $manifests as $manifest ) { |
425
|
|
|
$manifest = $manifest_dir . $manifest; |
426
|
|
|
if ( ! is_file( $manifest ) ) { |
427
|
|
|
continue; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
$content = file_get_contents( $manifest ); |
431
|
|
|
// Use a sufficiently large version so that the local package will always be the latest autoloader. |
432
|
|
|
$content = str_replace( 'dev-master', self::VERSION_CURRENT, $content ); |
433
|
|
|
file_put_contents( $manifest, $content ); |
434
|
|
|
} |
435
|
|
|
} |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Recursively removes a directory and all of its files. |
440
|
|
|
* |
441
|
|
|
* @param string $dir The directory to remove. |
442
|
|
|
*/ |
443
|
|
|
private function remove_directory( $dir ) { |
444
|
|
|
if ( ! is_dir( $dir ) ) { |
445
|
|
|
return; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
$empty_directories = array(); |
449
|
|
|
$directories_to_empty = array( $dir ); |
450
|
|
|
// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition |
451
|
|
|
while ( null !== ( $dir = array_shift( $directories_to_empty ) ) ) { |
452
|
|
|
$paths = scandir( $dir ); |
453
|
|
|
foreach ( $paths as $path ) { |
454
|
|
|
if ( '.' === $path || '..' === $path ) { |
455
|
|
|
continue; |
456
|
|
|
} |
457
|
|
|
// Keep the path absolute. |
458
|
|
|
$path = $dir . DIRECTORY_SEPARATOR . $path; |
459
|
|
|
|
460
|
|
|
// Subdirectories need to be emptied before they can be deleted. |
461
|
|
|
// Take care not to follow symlinks as it will destroy everything. |
462
|
|
|
if ( is_dir( $path ) && ! is_link( $path ) ) { |
463
|
|
|
$directories_to_empty[] = $path; |
464
|
|
|
continue; |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
unlink( $path ); |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
// Add to the front so that we delete children before parents. |
471
|
|
|
array_unshift( $empty_directories, $dir ); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
foreach ( $empty_directories as $dir ) { |
475
|
|
|
rmdir( $dir ); |
476
|
|
|
} |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* Checks whether or not the plugin should be written to disk. |
481
|
|
|
* |
482
|
|
|
* @param string $plugin_dir The directory we want to write the plugin to. |
483
|
|
|
* @param string $plugin_file The content for the plugin file. |
484
|
|
|
* @param array $composer_config The content for the composer.json file. |
485
|
|
|
* @return bool |
486
|
|
|
*/ |
487
|
|
|
private function has_plugin_changed( $plugin_dir, $plugin_file, &$composer_config ) { |
488
|
|
|
// Always write clean plugins. |
489
|
|
|
if ( ! is_file( $plugin_dir . DIRECTORY_SEPARATOR . 'composer.json' ) ) { |
490
|
|
|
return true; |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
// Prepare a checksum object for comparison and store it in the composer config so we can retrieve it later. |
494
|
|
|
$factory_checksum = array( |
495
|
|
|
'plugin' => hash( 'crc32', $plugin_file ), |
496
|
|
|
'composer' => hash( 'crc32', json_encode( $composer_config ) ), |
497
|
|
|
'files' => array(), |
498
|
|
|
); |
499
|
|
|
foreach ( $this->files as $path => $content ) { |
500
|
|
|
$factory_checksum['files'][ $path ] = hash( 'crc32', $content ); |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
// When we're using the local package it is important that we also include the autoloader files in the checksum |
504
|
|
|
// since they would indicate a change in the package that warrants rebuilding the autoloader as well. |
505
|
|
|
if ( $this->is_using_local_package() ) { |
506
|
|
|
$factory_checksum['autoloader-files'] = array(); |
507
|
|
|
|
508
|
|
|
$src_dir = TEST_PACKAGE_DIR . DIRECTORY_SEPARATOR . 'src'; |
509
|
|
|
$autoloader_files = scandir( $src_dir ); |
510
|
|
|
foreach ( $autoloader_files as $file ) { |
511
|
|
|
if ( '.' === $file || '..' === $file ) { |
512
|
|
|
continue; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
$factory_checksum['autoloader-files'][ $file ] = hash_file( 'crc32', $src_dir . DIRECTORY_SEPARATOR . $file ); |
516
|
|
|
} |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
$composer_config['extra']['test-plugin-checksum'] = $factory_checksum; |
520
|
|
|
|
521
|
|
|
// Retrieve the checksum from the existing plugin so that we can detect whether or not the plugin has changed. |
522
|
|
|
$config = json_decode( file_get_contents( $plugin_dir . DIRECTORY_SEPARATOR . 'composer.json' ), true ); |
523
|
|
|
if ( false === $config || ! isset( $config['extra']['test-plugin-checksum'] ) ) { |
524
|
|
|
return true; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
// Only write the plugin to disk if it has changed. |
528
|
|
|
return $config['extra']['test-plugin-checksum'] !== $factory_checksum; |
529
|
|
|
} |
530
|
|
|
} |
531
|
|
|
|
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: