'POST', 'callback' => [ $this, 'handle' ], 'permission_callback' => static function () { return current_user_can( 'manage_options' ); }, ] ); } public function handle( WP_REST_Request $request ): WP_REST_Response|WP_Error { $nonce = (string) ( $request->get_header( 'X-WP-Nonce' ) ?: $request->get_param( '_wpnonce' ) ); if ( '' === $nonce || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), 'wp_rest' ) ) { return new WP_Error( 'shiftora_invalid_nonce', __( 'Security check failed. Please refresh the page and try again.', 'shiftora' ), [ 'status' => 403 ] ); } $files = $request->get_file_params(); // PHP discards a body over post_max_size before this runs, leaving no file and no error // code. "No file provided" would be useless since the user did send one, so name the setting. $host_limits = $this->host_limits ?? HostUploadLimits::from_php(); if ( empty( $files['sql_file'] ) && $host_limits->body_exceeded() ) { return $this->host_upload_error( $host_limits ); } if ( empty( $files['sql_file'] ) || $files['sql_file']['error'] !== UPLOAD_ERR_OK ) { $upload_err = $files['sql_file']['error'] ?? UPLOAD_ERR_NO_FILE; // The host stopped it: name the setting and its value. if ( UPLOAD_ERR_INI_SIZE === $upload_err && $host_limits->caps() ) { return $this->host_upload_error( $host_limits ); } return new WP_Error( 'shiftora_no_file', $this->upload_error_message( $upload_err ), [ 'status' => 400 ] ); } $file = $files['sql_file']; // get_file_params() is $_FILES; a path PHP did not just receive is never read. if ( ! is_uploaded_file( (string) $file['tmp_name'] ) ) { return new WP_Error( 'shiftora_no_file', $this->upload_error_message( UPLOAD_ERR_NO_FILE ), [ 'status' => 400 ] ); } $ext = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) ); if ( $ext !== 'sql' ) { return new WP_Error( 'shiftora_invalid_file', __( 'Only .sql files are accepted.', 'shiftora' ), [ 'status' => 400 ] ); } // A low host limit doesn't refuse smaller files; this one is too big for this server, // so name the setting to raise. if ( $host_limits->refuses( (int) $file['size'] ) ) { return new WP_Error( 'shiftora_host_upload_too_small', $this->host_limit_message( $host_limits, (int) $file['size'] ), [ 'status' => 400, 'error_data' => [ 'status' => 400, 'retryable' => false, 'setting' => $host_limits->binding_setting(), 'host_limit' => $host_limits->effective(), 'file_size' => (int) $file['size'], ], ] ); } $declared_prefix = (string) preg_replace( '/[^a-zA-Z0-9_]/', '', (string) $request->get_param( 'source_prefix' ) ); return $this->importer_service()->import( (string) $file['tmp_name'], $declared_prefix ); } private function importer_service(): DumpImporter { return new DumpImporter( $this->importer, $this->wpdb ?? $GLOBALS['wpdb'] ); } /** The host refused the body before Shiftora saw it; names the setting and reports the ceiling as the host's own. */ private function host_upload_error( HostUploadLimits $host_limits ): WP_Error { return new WP_Error( 'shiftora_host_upload_too_small', $this->host_limit_message( $host_limits ), [ 'status' => 400, 'error_data' => [ 'status' => 400, 'retryable' => false, 'setting' => $host_limits->binding_setting(), 'host_limit' => $host_limits->effective(), ], ] ); } /** @param int $file_size Size that was rejected, when known. */ private function host_limit_message( HostUploadLimits $limits, int $file_size = 0 ): string { $setting = (string) $limits->binding_setting(); $human = function_exists( 'size_format' ) ? size_format( $limits->effective() ) : (string) $limits->effective(); if ( $file_size > 0 ) { return sprintf( /* translators: 1: uploaded file size, 2: server limit, 3: PHP setting name. */ __( 'This file is %1$s, which is larger than this server accepts in one upload (%2$s). Raise %3$s in your host\'s PHP settings, or import a smaller file.', 'shiftora' ), function_exists( 'size_format' ) ? size_format( $file_size ) : (string) $file_size, $human, $setting ); } return sprintf( /* translators: 1: server limit, 2: PHP setting name. */ __( 'This server accepts at most %1$s per upload (%2$s), so the file was discarded before it arrived. Raise %2$s in your host\'s PHP settings, or import a smaller file.', 'shiftora' ), $human, $setting ); } private function upload_error_message( int $code ): string { $messages = [ UPLOAD_ERR_INI_SIZE => __( 'The uploaded file exceeds the server upload limit.', 'shiftora' ), UPLOAD_ERR_FORM_SIZE => __( 'The uploaded file exceeds the form upload limit.', 'shiftora' ), UPLOAD_ERR_PARTIAL => __( 'The file was only partially uploaded.', 'shiftora' ), UPLOAD_ERR_NO_FILE => __( 'No SQL file was provided.', 'shiftora' ), UPLOAD_ERR_NO_TMP_DIR => __( 'Server is missing a temporary folder.', 'shiftora' ), UPLOAD_ERR_CANT_WRITE => __( 'Failed to write file to disk.', 'shiftora' ), UPLOAD_ERR_EXTENSION => __( 'A PHP extension stopped the file upload.', 'shiftora' ), ]; return $messages[ $code ] ?? __( 'An unknown upload error occurred.', 'shiftora' ); } }