# blocks/trunk/src/Admin/TransferPage.php

Blocks – Reusable Content, Shortcodes &amp; Site Variables, version trunk. 172 lines.

- Page: https://pluginprobe.com/plugins/blocks/trunk/code/src/Admin/TransferPage.php
- Raw: https://pluginprobe.com/plugins/blocks/trunk/raw/src/Admin/TransferPage.php
- Modified: 2026-07-13T20:34:48+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/blocks/trunk/code/src/Admin/TransferPage.php#L10-L20`.

```php
<?php

declare(strict_types=1);

namespace RenzoJohnson\Blocks\Admin;

use JsonException;
use RenzoJohnson\Blocks\Transfer\TransferService;

\defined( 'ABSPATH' ) || exit;

final class TransferPage {

	private const PAGE_SLUG        = 'blocks-tools';
	private const MAX_UPLOAD_BYTES = 5_242_880;

	public function __construct( private readonly TransferService $service, private readonly TransferView $view ) {
	}

	public function register_hooks(): void {
		\add_action( 'admin_menu', $this->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<string, mixed> $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<string, mixed>|\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<string, int|string> $parameters */
	private function redirect_with( array $parameters ): never {
		\wp_safe_redirect( \add_query_arg( $parameters, \admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ) );
		exit;
	}
}

```
