<?php
/**
* Image resolution post-processor.
*
* Walks the Elementor template, collects __FIGMA_IMAGE_REF__ placeholders,
* resolves them via the Figma API, downloads images into the WP Media Library,
* and replaces placeholders with WordPress attachment URLs.
*/
if ( ! defined( 'ABSPATH' ) ) exit;
class FTE_Image_Resolver {
private FTE_Figma_Client $client;
private string $file_key;
public function __construct( FTE_Figma_Client $client, string $file_key ) {
$this->client = $client;
$this->file_key = $file_key;
}
/**
* Resolve all image placeholders in an Elementor template (mutates in place).
*
* @param array &$template Elementor template (content array).
* @return array Manifest of resolved images.
*/
public function resolve( array &$template ): array {
// Step 1: Collect placeholders
$placeholders = $this->collect_placeholders( $template['content'] );
if ( empty( $placeholders ) ) {
return [];
}
// Step 2: Classify
$fill_refs = [];
$export_ids = [];
foreach ( $placeholders as $p ) {
if ( $p['requires_export'] ) {
$export_ids[] = $p['ref'];
} else {
$fill_refs[] = $p['ref'];
}
}
// Step 3: Resolve fill URLs
$fill_map = [];
if ( ! empty( $fill_refs ) ) {
$fill_map = $this->client->get_image_fills( $this->file_key );
}
// Step 4: Export node images
$export_map = [];
if ( ! empty( $export_ids ) ) {
$export_map = $this->client->export_images( $this->file_key, $export_ids, 'png', 2.0 );
}
// Step 5: Download to WP Media Library and build final URL map
$url_map = [];
$manifest = [];
foreach ( array_unique( $fill_refs ) as $ref ) {
$remote_url = $fill_map[ $ref ] ?? null;
if ( ! $remote_url ) continue;
$local_url = $this->sideload_to_media_library( $remote_url, "figma-fill-{$ref}" );
if ( $local_url ) {
$url_map[ $ref ] = $local_url;
$manifest[] = [ 'ref' => $ref, 'url' => $local_url, 'source' => 'fill' ];
}
}
foreach ( array_unique( $export_ids ) as $id ) {
$remote_url = $export_map[ $id ] ?? null;
if ( ! $remote_url ) continue;
$safe_id = str_replace( ':', '-', $id );
$local_url = $this->sideload_to_media_library( $remote_url, "figma-export-{$safe_id}" );
if ( $local_url ) {
$url_map[ $id ] = $local_url;
$manifest[] = [ 'ref' => $id, 'url' => $local_url, 'source' => 'export' ];
}
}
// Step 6: Replace in template
$template['content'] = $this->replace_placeholders( $template['content'], $url_map );
return $manifest;
}
// -----------------------------------------------------------------------
// Placeholder collection
// -----------------------------------------------------------------------
private function collect_placeholders( array $nodes ): array {
$results = [];
foreach ( $nodes as $node ) {
$this->scan_for_placeholders( $node, $results );
}
return $results;
}
private function scan_for_placeholders( array $node, array &$results ): void {
$requires_export = ! empty( $node['settings']['_requires_image_export'] );
// Scan settings recursively for placeholder strings
if ( isset( $node['settings'] ) ) {
$this->scan_value( $node['settings'], $requires_export, $results );
}
foreach ( $node['elements'] ?? [] as $child ) {
$this->scan_for_placeholders( $child, $results );
}
}
private function scan_value( $value, bool $requires_export, array &$results ): void {
if ( is_string( $value ) && str_starts_with( $value, '__FIGMA_IMAGE_REF__:' ) ) {
$ref = substr( $value, strlen( '__FIGMA_IMAGE_REF__:' ) );
$results[] = [
'ref' => $ref,
'requires_export' => $requires_export,
];
} elseif ( is_array( $value ) ) {
foreach ( $value as $v ) {
$this->scan_value( $v, $requires_export, $results );
}
}
}
// -----------------------------------------------------------------------
// Placeholder replacement
// -----------------------------------------------------------------------
private function replace_placeholders( array $nodes, array $url_map ): array {
return array_map( fn( $node ) => $this->replace_in_node( $node, $url_map ), $nodes );
}
private function replace_in_node( $value, array $url_map ) {
if ( is_string( $value ) && str_starts_with( $value, '__FIGMA_IMAGE_REF__:' ) ) {
$ref = substr( $value, strlen( '__FIGMA_IMAGE_REF__:' ) );
return $url_map[ $ref ] ?? '';
}
if ( is_array( $value ) ) {
$result = [];
foreach ( $value as $key => $v ) {
$result[ $key ] = $this->replace_in_node( $v, $url_map );
}
return $result;
}
return $value;
}
// -----------------------------------------------------------------------
// WordPress Media Library sideloading
// -----------------------------------------------------------------------
/**
* Download a remote image and add it to the WP Media Library.
*
* @param string $url Remote image URL.
* @param string $filename Desired filename (without extension).
* @return string|null Local WordPress URL, or null on failure.
*/
private function sideload_to_media_library( string $url, string $filename ): ?string {
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';
}
// Download to temp file
$tmp = download_url( $url, 30 );
if ( is_wp_error( $tmp ) ) {
return null;
}
$file_array = [
'name' => sanitize_file_name( $filename ) . '.png',
'tmp_name' => $tmp,
];
$attachment_id = media_handle_sideload( $file_array, 0, "Figma import: $filename" );
if ( is_wp_error( $attachment_id ) ) {
@unlink( $tmp );
return null;
}
return wp_get_attachment_url( $attachment_id );
}
}