add_settings_page( ... ), 22 ); \add_action( 'admin_post_blocks_export', $this->handle_export( ... ) ); \add_action( 'admin_post_blocks_import', $this->handle_import( ... ) ); } public function add_settings_page(): void { \add_submenu_page( 'block', \__( 'Import / Export', 'blocks' ), \__( 'Import / Export', 'blocks' ), 'manage_options', self::PAGE_SLUG, $this->view->render( ... ), ); } public function handle_export(): never { $this->authorize( 'blocks_export', 'blocks_export_nonce' ); $json = \wp_json_encode( $this->service->export_payload(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); if ( ! \is_string( $json ) ) { \wp_die( \esc_html__( 'Export failed.', 'blocks' ) ); } $host = \wp_parse_url( \home_url(), PHP_URL_HOST ); $host = \is_string( $host ) ? $host : 'site'; $filename = \sanitize_file_name( 'blocks-export-' . $host . '-' . \gmdate( 'Ymd-His' ) . '.json' ); \nocache_headers(); \header( 'Content-Type: application/json; charset=utf-8' ); \header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); echo $json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON attachment encoded by wp_json_encode(). exit; } public function handle_import(): never { $this->authorize( 'blocks_import', 'blocks_import_nonce' ); $file = $this->uploaded_file(); if ( \is_wp_error( $file ) ) { $this->redirect_with( array( 'import_error' => 'nofile' ) ); } $result = $this->process_upload( $file ); if ( \is_wp_error( $result ) ) { $this->redirect_with( array( 'import_error' => 'invalid' ) ); } $this->redirect_with( array( 'imported' => 1, 'created' => $result['created'], 'updated' => $result['updated'], 'skipped' => $result['skipped'], ), ); } /** * @param array $file * @return array{created: int, updated: int, skipped: int, variables: int}|\WP_Error */ private function process_upload( array $file ): array|\WP_Error { if ( ! \function_exists( 'wp_handle_upload' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } $upload = \wp_handle_upload( $file, array( 'test_form' => false, 'mimes' => array( 'json' => 'application/json' ), ), ); if ( isset( $upload['error'] ) || ! isset( $upload['file'] ) || ! \is_string( $upload['file'] ) ) { return new \WP_Error( 'blocks_import_upload' ); } $path = $upload['file']; try { $size = \filesize( $path ); if ( ! \is_int( $size ) || 0 >= $size || self::MAX_UPLOAD_BYTES < $size ) { return new \WP_Error( 'blocks_import_size' ); } $raw = \file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local upload managed by wp_handle_upload(). if ( ! \is_string( $raw ) ) { return new \WP_Error( 'blocks_import_read' ); } $payload = \json_decode( $raw, true, 512, JSON_THROW_ON_ERROR ); if ( ! \is_array( $payload ) ) { return new \WP_Error( 'blocks_import_invalid' ); } // phpcs:disable WordPress.Security.NonceVerification.Missing -- handle_import() verifies the import nonce before calling this private method. $result = $this->service->import_payload( $payload, isset( $_POST['blocks_import_overwrite'] ) ); // phpcs:enable return $result; } catch ( JsonException ) { return new \WP_Error( 'blocks_import_json' ); } finally { if ( \is_file( $path ) ) { \wp_delete_file( $path ); } } } private function authorize( string $action, string $nonce_name ): void { if ( ! \current_user_can( 'manage_options' ) ) { \wp_die( \esc_html__( 'Permission denied.', 'blocks' ) ); } \check_admin_referer( $action, $nonce_name ); } /** @return array|\WP_Error */ private function uploaded_file(): array|\WP_Error { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- authorize() verified the nonce; WordPress validates and moves the upload. $file = $_FILES['blocks_import_file'] ?? null; if ( ! \is_array( $file ) ) { return new \WP_Error( 'blocks_import_no_file' ); } $error = $file['error'] ?? UPLOAD_ERR_NO_FILE; $size = $file['size'] ?? 0; if ( UPLOAD_ERR_OK !== $error || ! \is_int( $size ) || 0 >= $size || self::MAX_UPLOAD_BYTES < $size ) { return new \WP_Error( 'blocks_import_no_file' ); } return $file; } /** @param array $parameters */ private function redirect_with( array $parameters ): never { \wp_safe_redirect( \add_query_arg( $parameters, \admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ) ); exit; } }