Schema::MAX_BYTES ) { return new \WP_Error( 'customfields_local_read', __( 'The schema must be an unchanged regular file of at most 1 MB.', 'customfields-com' ) ); } $json = stream_get_contents( $stream, Schema::MAX_BYTES + 1 ); clearstatcache( true, $real ); $resolved = self::local_path( $path ); $after = is_wp_error( $resolved ) ? false : @lstat( $resolved ); if ( ! is_string( $json ) || strlen( $json ) > Schema::MAX_BYTES || ! $after || $resolved !== $real || $opened['dev'] !== $after['dev'] || $opened['ino'] !== $after['ino'] || $opened['size'] !== $after['size'] || $opened['mtime'] !== $after['mtime'] || $opened['ctime'] !== $after['ctime'] ) { return new \WP_Error( 'customfields_local_read', __( 'The schema file changed while reading or exceeds the 1 MB limit. Read it again.', 'customfields-com' ) ); } return $json; } finally { fclose( $stream ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Close the bounded read-only local import stream. } } /** * Export, check or import a complete schema snapshot. * * ## OPTIONS * * : export, check, or import. * [] * : Local JSON path, required for check/import. * [--apply] * : Apply a validated import; otherwise only inspect. * [--expected-hash=] * : Require the current schema SHA-256 when applying. */ public static function cli( $args, $assoc ) { $operation = $args[0] ?? ''; $groups = customfields_get_groups(); $hash = hash( 'sha256', Schema::encode( $groups ) ); if ( 'export' === $operation ) { \WP_CLI::line( Schema::encode( $groups ) ); return; } if ( ! in_array( $operation, array( 'check', 'import' ), true ) || empty( $args[1] ) ) { \WP_CLI::error( 'Use export, check , or import . Files must be readable JSON under 1 MB.' ); } // CLI paths may be relative to the operator's working directory; the reader still rejects links. $path = preg_match( '~^(?:/|[A-Za-z]:[\\\\/])~', $args[1] ) ? $args[1] : getcwd() . '/' . $args[1]; $json = self::read_local( $path ); if ( is_wp_error( $json ) ) { \WP_CLI::error( $json->get_error_message() ); } $incoming = Schema::decode( $json ); if ( is_wp_error( $incoming ) ) { \WP_CLI::error( $incoming->get_error_message() ); } \WP_CLI::line( 'Current schema hash: ' . $hash ); \WP_CLI::line( 'Validated groups: ' . count( $incoming ) . '. Import replaces definitions only; content is unchanged.' ); if ( 'import' !== $operation || ! isset( $assoc['apply'] ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { \WP_CLI::error( 'Use --user= to apply schema changes.' ); } if ( empty( $assoc['expected-hash'] ) || ! hash_equals( $hash, (string) $assoc['expected-hash'] ) ) { \WP_CLI::error( 'Review the schema and pass the matching --expected-hash before applying.' ); } update_option( 'customfields_groups', $incoming, false ); do_action( 'customfields_schema_saved', $incoming, $groups ); \WP_CLI::success( 'Schema imported. Existing values were preserved.' ); } }