contains_embedded_asset( $blocks ) ) { return new WP_Error( 'asset_upload_forbidden', __( 'The current user cannot upload embedded images.', 'advanced-animation' ) ); } return array( 'blocks' => $blocks, 'assets' => array(), 'errors' => array() ); } $normalized = array(); foreach ( $blocks as $block ) { $normalized[] = $this->normalize_block( $block ); } if ( ! empty( $this->errors ) ) { return new WP_Error( 'asset_normalization_failed', __( 'One or more embedded images could not be uploaded.', 'advanced-animation' ), array( 'errors' => $this->errors, 'assets' => $this->assets ) ); } return array( 'blocks' => $normalized, 'assets' => $this->assets, 'errors' => array() ); } private function normalize_block( $block ) { if ( ! is_array( $block ) ) { return $block; } if ( isset( $block['attributes'] ) ) { $block['attributes'] = $this->normalize_value( $block['attributes'] ); } if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) { $children = array(); foreach ( $block['innerBlocks'] as $child ) { $children[] = $this->normalize_block( $child ); } $block['innerBlocks'] = $children; } return $block; } private function normalize_value( $value ) { if ( is_array( $value ) ) { foreach ( $value as $key => $item ) { $value[ $key ] = $this->normalize_value( $item ); } return $value; } if ( ! is_string( $value ) || '' === $value ) { return $value; } $stored_prefix = 0 === strpos( $value, 'base64:' ); $decoded = $stored_prefix ? base64_decode( substr( $value, 7 ), true ) : false; $working = false !== $decoded ? $decoded : $value; if ( 0 === stripos( $working, 'data:image/' ) ) { $url = $this->upload_data_uri( $working ); if ( $url ) { return $stored_prefix ? 'base64:' . base64_encode( $url ) : $url; } return $value; } $rewritten = preg_replace_callback( '/url\(\s*(["\']?)(data:image\/[^)"\']+)\1\s*\)/i', function ( $matches ) { $url = $this->upload_data_uri( html_entity_decode( $matches[2], ENT_QUOTES, 'UTF-8' ) ); return $url ? 'url(' . $matches[1] . $url . $matches[1] . ')' : $matches[0]; }, $working ); if ( $rewritten !== $working ) { return $stored_prefix ? 'base64:' . base64_encode( $rewritten ) : $rewritten; } return $value; } private function upload_data_uri( string $uri ): ?string { if ( isset( $this->cache[ $uri ] ) ) { return $this->cache[ $uri ]; } if ( ! preg_match( '#^data:(image/[a-z0-9.+-]+);base64,(.+)$#is', $uri, $matches ) ) { $this->errors[] = array( 'code' => 'invalid_data_uri', 'message' => 'Only base64 image data URIs are supported.' ); return null; } $mime = strtolower( $matches[1] ); $binary = base64_decode( preg_replace( '/\s+/', '', $matches[2] ), true ); if ( false === $binary ) { $this->errors[] = array( 'code' => 'invalid_base64', 'message' => 'Embedded image base64 data is invalid.' ); return null; } if ( strlen( $binary ) > wp_max_upload_size() ) { $this->errors[] = array( 'code' => 'asset_too_large', 'message' => 'Embedded image exceeds the WordPress upload limit.' ); return null; } $extensions = array( 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp', 'image/avif' => 'avif', 'image/svg+xml' => 'svg', ); if ( ! isset( $extensions[ $mime ] ) || ! $this->mime_is_allowed( $mime ) ) { $this->errors[] = array( 'code' => 'unsupported_mime', 'message' => 'Embedded image MIME type is not allowed.', 'mime' => $mime ); return null; } $hash = hash( 'sha256', $binary ); $found = get_posts( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'numberposts' => 1, 'meta_key' => '_advanced_addons_asset_hash', 'meta_value' => $hash, 'fields' => 'ids', ) ); if ( ! empty( $found ) ) { $url = wp_get_attachment_url( intval( $found[0] ) ); if ( $url ) { $this->cache[ $uri ] = $url; $this->assets[] = array( 'attachment_id' => intval( $found[0] ), 'url' => $url, 'reused' => true, 'hash' => $hash ); return $url; } } $filename = 'advanced-addons-' . substr( $hash, 0, 16 ) . '.' . $extensions[ $mime ]; $upload = wp_upload_bits( $filename, null, $binary ); if ( ! empty( $upload['error'] ) ) { $this->errors[] = array( 'code' => 'upload_failed', 'message' => $upload['error'] ); return null; } $attachment_id = wp_insert_attachment( array( 'post_mime_type' => $mime, 'post_title' => sanitize_file_name( pathinfo( $filename, PATHINFO_FILENAME ) ), 'post_status' => 'inherit', ), $upload['file'], 0, true ); if ( is_wp_error( $attachment_id ) ) { $this->errors[] = array( 'code' => 'attachment_failed', 'message' => $attachment_id->get_error_message() ); return null; } if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) { require_once ABSPATH . 'wp-admin/includes/image.php'; } $metadata = wp_generate_attachment_metadata( $attachment_id, $upload['file'] ); if ( is_array( $metadata ) ) { wp_update_attachment_metadata( $attachment_id, $metadata ); } update_post_meta( $attachment_id, '_advanced_addons_asset_hash', $hash ); $this->cache[ $uri ] = $upload['url']; $this->assets[] = array( 'attachment_id' => intval( $attachment_id ), 'url' => $upload['url'], 'reused' => false, 'hash' => $hash ); return $upload['url']; } private function mime_is_allowed( string $mime ): bool { foreach ( get_allowed_mime_types() as $allowed ) { if ( in_array( $mime, explode( '|', $allowed ), true ) || $mime === $allowed ) { return true; } } return false; } private function contains_embedded_asset( $value ): bool { if ( is_array( $value ) ) { foreach ( $value as $item ) { if ( $this->contains_embedded_asset( $item ) ) { return true; } } } return is_string( $value ) && ( false !== stripos( $value, 'data:image/' ) || false !== stripos( $value, 'base64:ZGF0YTppbWFnZS8' ) ); } }