'POST', 'callback' => [ $this, 'resolve' ], 'permission_callback' => [ $this, 'allow_public_access' ], ] ); register_rest_route( self::NAMESPACE, self::REPLACE_PATH, [ 'methods' => 'POST', 'callback' => [ $this, 'replace' ], 'permission_callback' => [ $this, 'allow_public_access' ], ] ); } /** * Permission gate for the callbacks. * * Always returns true: these endpoints are reached by the backend worker, * not a logged-in user, so there is no cookie/nonce to check. The real * authentication is the Ed25519 signature, verified inside each handler. */ public function allow_public_access() { return true; } /** * POST /mintall/v1/internal/protection/resolve * * Body: { external_product_id, external_file_id } * * Returns the data the backend needs to certify the image: * { product: { title, url }, image: { src, alt } } * or a sentinel when the referenced entity is gone: * { product_missing: true } — product no longer exists (→ OBSOLETE) * { image_missing: true } — attachment no longer exists (→ OBSOLETE) */ public function resolve( $request ) { if ( ! Callback_Signature::verify( $request, self::RESOLVE_PATH ) ) { return $this->invalid_signature(); } $params = $request->get_json_params(); $product_id = isset( $params['external_product_id'] ) ? absint( $params['external_product_id'] ) : 0; $attachment_id = isset( $params['external_file_id'] ) ? absint( $params['external_file_id'] ) : 0; $product = $product_id ? wc_get_product( $product_id ) : false; if ( ! $product ) { return new \WP_REST_Response( [ 'product_missing' => true ] ); } if ( ! $attachment_id || 'attachment' !== get_post_type( $attachment_id ) ) { return new \WP_REST_Response( [ 'image_missing' => true ] ); } $src = wp_get_attachment_url( $attachment_id ); $alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); return new \WP_REST_Response( [ 'product' => [ 'title' => $product->get_name(), 'url' => get_permalink( $product_id ), ], 'image' => [ // A null src tells the backend the image is unrecoverable // (permanent failure) rather than missing entirely. 'src' => $src ? $src : null, 'alt' => $alt ? $alt : '', ], ] ); } /** * POST /mintall/v1/internal/protection/replace * * Body: { external_product_id, external_file_id, signed_image_url } * * Downloads the certified image from the signed (SAS) URL, adds it to the * media library attached to the product, and swaps it in wherever the * original file is used — featured image, gallery, or inline description * content. Returns the new attachment id so the backend can track the * swapped asset: * { ok: true, new_attachment_id: "" } */ public function replace( $request ) { if ( ! Callback_Signature::verify( $request, self::REPLACE_PATH ) ) { return $this->invalid_signature(); } $params = $request->get_json_params(); $product_id = isset( $params['external_product_id'] ) ? absint( $params['external_product_id'] ) : 0; $old_attach_id = isset( $params['external_file_id'] ) ? absint( $params['external_file_id'] ) : 0; $signed_url = isset( $params['signed_image_url'] ) ? esc_url_raw( $params['signed_image_url'] ) : ''; $product = $product_id ? wc_get_product( $product_id ) : false; if ( ! $product ) { return $this->error_response( 'Product not found.', 404 ); } if ( empty( $signed_url ) ) { return $this->error_response( 'Missing signed_image_url.', 400 ); } $new_attach_id = $this->sideload_image( $signed_url, $product_id, $product->get_name() ); if ( is_wp_error( $new_attach_id ) ) { // A failed download/sideload is transient from the backend's view // (it may retry), so surface it as a 5xx rather than a 4xx. return $this->error_response( $new_attach_id->get_error_message(), 502 ); } // Swap the certified copy in wherever the original file is used on the // product — featured image, gallery, and inline description content. if ( $old_attach_id ) { Product_Images::apply_replacement( $product, $old_attach_id, $new_attach_id ); } return new \WP_REST_Response( [ 'ok' => true, 'new_attachment_id' => (string) $new_attach_id, ] ); } /** * Download a remote image and add it to the media library. * * @param string $url Signed (SAS) image URL. * @param int $product_id Product to attach the media to. * @param string $title Product name, used for the attachment title. * @return int|\WP_Error New attachment id, or an error on download/sideload failure. */ private function sideload_image( $url, $product_id, $title ) { if ( ! function_exists( 'media_handle_sideload' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; } $tmp = download_url( $url, 120 ); if ( is_wp_error( $tmp ) ) { return $tmp; } // Derive a filename from the URL path, ignoring query params. SAS URLs // carry a token query string, so fall back to a synthetic name when the // path has no usable extension. $url_path = (string) wp_parse_url( $url, PHP_URL_PATH ); $filename = basename( $url_path ); if ( empty( $filename ) || ! preg_match( '/\.\w+$/', $filename ) ) { $filename = 'mintall-certified-' . $product_id . '.jpg'; } $file_array = [ 'name' => sanitize_file_name( $filename ), 'tmp_name' => $tmp, ]; $attachment_id = media_handle_sideload( $file_array, $product_id, "{$title} - Certified by Mintall" ); if ( is_wp_error( $attachment_id ) && file_exists( $tmp ) ) { wp_delete_file( $tmp ); } // Mark certified copies so they can be told apart from originals. if ( ! is_wp_error( $attachment_id ) ) { update_post_meta( $attachment_id, '_mintall_certified', '1' ); } return $attachment_id; } /** * 403 response for a request whose signature did not verify. The backend * treats 4xx as a permanent failure and will not retry. */ private function invalid_signature() { return new \WP_REST_Response( [ 'error' => 'invalid_signature' ], 403 ); } }