Completed
Push — renovate/major-react-monorepo ( 880c2b...a6f86c )
by
unknown
355:09 queued 345:32
created

Test_Plugin_Factory   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 515
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 515
rs 3.28
c 0
b 0
f 0
wmc 64
lcom 1
cbo 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A create() 0 9 2
A create_test_plugin() 0 37 4
A with_file() 0 4 1
C with_class() 0 64 13
A with_autoloader_version() 0 4 1
A with_composer_config() 0 4 1
B make() 0 43 5
A is_using_local_package() 0 3 1
A build_composer_config() 0 26 4
C execute_composer() 0 79 13
B remove_directory() 0 35 9
B has_plugin_changed() 0 43 9

How to fix   Complexity   

Complex Class

Complex classes like Test_Plugin_Factory often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Test_Plugin_Factory, and based on these observations, apply Extract Interface, too.

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 )
0 ignored issues
show
Documentation introduced by
$autoloads is of type array<string,array<integ...,{\"0\":\"string\"}>"}>, but the function expects a array<integer,string>.

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:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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 ) ) );
0 ignored issues
show
Documentation introduced by
array('config' => array(...=> $namespace_version)) is of type array<string,array<strin...uffix\":\"string\"}>"}>, but the function expects a array<integer,string>.

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:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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