<?php
/**
 * Plugin Name: Figma to Elementor
 * Plugin URI:  https://marketbiz.co.il
 * Description: Converts Figma designs into Elementor legacy Section/Column templates.
 * Version:     1.0.0
 * Author:      MarketBiz
 * Author URI:  https://marketbiz.co.il
 * License:     GPL-2.0+
 * Text Domain: figma-to-elementor
 * Requires PHP: 7.4
 *
 * This plugin fetches a Figma file via the REST API, converts the design
 * into Elementor's legacy (Sections & Columns) JSON format, resolves image
 * assets, and saves the result as an importable Elementor template.
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

define( 'FTE_VERSION', '1.0.0' );
define( 'FTE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'FTE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'FTE_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );

// ---------------------------------------------------------------------------
// Autoload includes
// ---------------------------------------------------------------------------
require_once FTE_PLUGIN_DIR . 'includes/class-figma-client.php';
require_once FTE_PLUGIN_DIR . 'includes/class-row-detector.php';
require_once FTE_PLUGIN_DIR . 'includes/class-widget-mapper.php';
require_once FTE_PLUGIN_DIR . 'includes/class-converter.php';
require_once FTE_PLUGIN_DIR . 'includes/class-elementor-emitter.php';
require_once FTE_PLUGIN_DIR . 'includes/class-image-resolver.php';
require_once FTE_PLUGIN_DIR . 'includes/class-template-importer.php';
require_once FTE_PLUGIN_DIR . 'includes/class-admin-page.php';

// ---------------------------------------------------------------------------
// Boot
// ---------------------------------------------------------------------------

/**
 * Main plugin class — singleton.
 */
final class Figma_To_Elementor {

    private static $instance = null;

    public static function instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        add_action( 'admin_menu', [ $this, 'register_admin_menu' ] );
        add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
        add_action( 'wp_ajax_fte_convert', [ $this, 'ajax_convert' ] );
        add_action( 'wp_ajax_fte_fetch_pages', [ $this, 'ajax_fetch_pages' ] );
        add_action( 'wp_ajax_fte_test_token', [ $this, 'ajax_test_token' ] );
        add_action( 'wp_ajax_fte_diagnose', [ $this, 'ajax_diagnose' ] );
        add_action( 'admin_init', [ $this, 'register_settings' ] );
    }

    // -----------------------------------------------------------------------
    // Admin menu
    // -----------------------------------------------------------------------

    public function register_admin_menu() {
        add_menu_page(
            __( 'Figma to Elementor', 'figma-to-elementor' ),
            __( 'Figma → Elementor', 'figma-to-elementor' ),
            'manage_options',
            'figma-to-elementor',
            [ $this, 'render_admin_page' ],
            'dashicons-layout',
            59
        );
    }

    public function render_admin_page() {
        FTE_Admin_Page::render();
    }

    // -----------------------------------------------------------------------
    // Settings (stores the Figma token in wp_options)
    // -----------------------------------------------------------------------

    public function register_settings() {
        register_setting( 'fte_settings_group', 'fte_figma_token', [
            'type'              => 'string',
            'sanitize_callback' => 'sanitize_text_field',
            'default'           => '',
        ] );
    }

    // -----------------------------------------------------------------------
    // Admin assets
    // -----------------------------------------------------------------------

    public function enqueue_admin_assets( $hook ) {
        if ( 'toplevel_page_figma-to-elementor' !== $hook ) {
            return;
        }
        wp_enqueue_style(
            'fte-admin',
            FTE_PLUGIN_URL . 'assets/css/admin.css',
            [],
            FTE_VERSION
        );
        wp_enqueue_script(
            'fte-admin',
            FTE_PLUGIN_URL . 'assets/js/admin.js',
            [ 'jquery' ],
            FTE_VERSION,
            true
        );
        wp_localize_script( 'fte-admin', 'fteAjax', [
            'ajaxurl' => admin_url( 'admin-ajax.php' ),
            'nonce'   => wp_create_nonce( 'fte_nonce' ),
        ] );
    }

    // -----------------------------------------------------------------------
    // AJAX: Full diagnostic — tests every layer independently
    // -----------------------------------------------------------------------

    public function ajax_diagnose() {
        check_ajax_referer( 'fte_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( 'Unauthorized', 403 );
        }

        $diag = [];

        // 1. Check token is stored
        $token = get_option( 'fte_figma_token', '' );
        $diag['token_saved'] = ! empty( $token );
        $diag['token_length'] = strlen( $token );
        $diag['token_prefix'] = substr( $token, 0, 5 ) . '...';

        // 2. Test basic outbound HTTPS (to a known endpoint)
        $test_response = wp_remote_get( 'https://httpbin.org/get', [ 'timeout' => 10 ] );
        if ( is_wp_error( $test_response ) ) {
            $diag['outbound_https'] = 'FAIL: ' . $test_response->get_error_message();
        } else {
            $diag['outbound_https'] = 'OK (HTTP ' . wp_remote_retrieve_response_code( $test_response ) . ')';
        }

        // 3. Test connectivity to api.figma.com specifically
        $figma_response = wp_remote_get( 'https://api.figma.com/v1/me', [
            'timeout' => 15,
            'headers' => [
                'X-Figma-Token' => $token,
                'Content-Type'  => 'application/json',
            ],
        ] );

        if ( is_wp_error( $figma_response ) ) {
            $diag['figma_api'] = 'FAIL: ' . $figma_response->get_error_message();
            $diag['figma_error_code'] = $figma_response->get_error_code();
            $diag['figma_error_data'] = $figma_response->get_error_data();
        } else {
            $code = wp_remote_retrieve_response_code( $figma_response );
            $body = wp_remote_retrieve_body( $figma_response );
            $diag['figma_api'] = 'HTTP ' . $code;
            $diag['figma_response_body'] = substr( $body, 0, 500 );

            if ( $code === 200 ) {
                $data = json_decode( $body, true );
                $diag['figma_user'] = ( $data['handle'] ?? '?' ) . ' (' . ( $data['email'] ?? '?' ) . ')';
            }
        }

        // 4. Test file access (if file_key provided)
        $raw_input = sanitize_text_field( $_POST['file_key'] ?? '' );
        if ( ! empty( $raw_input ) ) {
            $parsed = self::parse_figma_input( $raw_input );
            $diag['parsed_file_key'] = $parsed['file_key'];
            $diag['parsed_node_id']  = $parsed['node_id'];

            if ( ! empty( $parsed['file_key'] ) ) {
                $file_url = 'https://api.figma.com/v1/files/' . urlencode( $parsed['file_key'] ) . '?depth=1';
                $file_response = wp_remote_get( $file_url, [
                    'timeout' => 30,
                    'headers' => [
                        'X-Figma-Token' => $token,
                        'Content-Type'  => 'application/json',
                    ],
                ] );

                if ( is_wp_error( $file_response ) ) {
                    $diag['file_fetch'] = 'FAIL: ' . $file_response->get_error_message();
                } else {
                    $fcode = wp_remote_retrieve_response_code( $file_response );
                    $fbody = wp_remote_retrieve_body( $file_response );
                    $diag['file_fetch'] = 'HTTP ' . $fcode;
                    $diag['file_response'] = substr( $fbody, 0, 300 );
                }
            }
        }

        // 5. PHP/server info
        $diag['php_version']    = PHP_VERSION;
        $diag['wp_version']     = get_bloginfo( 'version' );
        $diag['curl_installed'] = function_exists( 'curl_version' ) ? curl_version()['version'] : 'NOT AVAILABLE';
        $diag['ssl_available']  = extension_loaded( 'openssl' ) ? 'Yes' : 'No';

        wp_send_json_success( $diag );
    }

    // -----------------------------------------------------------------------
    // AJAX: Test token — hits GET /v1/me to verify the token is valid
    // -----------------------------------------------------------------------

    public function ajax_test_token() {
        check_ajax_referer( 'fte_nonce', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( 'Unauthorized', 403 );
        }

        $token = get_option( 'fte_figma_token', '' );
        if ( empty( $token ) ) {
            wp_send_json_error( 'No token saved. Enter your Figma token and click Save first.' );
        }

        $client = new FTE_Figma_Client( $token );
        $result = $client->get_me();

        if ( is_wp_error( $result ) ) {
            $msg = $result->get_error_message();
            // Give specific guidance based on error
            if ( strpos( $msg, '403' ) !== false ) {
                wp_send_json_error( 'Token rejected (HTTP 403). The token is invalid or expired. Generate a new one at figma.com → Settings → Personal Access Tokens.' );
            }
            wp_send_json_error( 'Connection failed: ' . $msg );
        }

        $email  = $result['email'] ?? 'unknown';
        $handle = $result['handle'] ?? 'unknown';

        wp_send_json_success( [
            'email'  => $email,
            'handle' => $handle,
            'message' => sprintf( 'Token is valid! Connected as %s (%s).', $handle, $email ),
        ] );
    }

    // -----------------------------------------------------------------------
    // AJAX: Fetch pages/frames from Figma file (for the dropdown)
    // -----------------------------------------------------------------------

    public function ajax_fetch_pages() {
        check_ajax_referer( 'fte_nonce', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( 'Unauthorized', 403 );
        }

        $token     = get_option( 'fte_figma_token', '' );
        $raw_input = sanitize_text_field( $_POST['file_key'] ?? '' );
        $parsed    = self::parse_figma_input( $raw_input );
        $file_key  = $parsed['file_key'];

        if ( empty( $token ) || empty( $file_key ) ) {
            wp_send_json_error( 'Token and file key are required. Enter a Figma file key or paste a full Figma URL.' );
        }

        $client = new FTE_Figma_Client( $token );
        $result = $client->get_file( $file_key, 2 ); // depth=2 for pages+frame names only

        if ( is_wp_error( $result ) ) {
            $msg = $result->get_error_message();
            // Give actionable guidance for common errors
            if ( strpos( $msg, '404' ) !== false ) {
                wp_send_json_error(
                    'File not found (HTTP 404). This usually means: (1) The file key is wrong — check your URL. '
                    . '(2) Your Figma token does not have access to this file — the file may be in a different team/org. '
                    . '(3) The token is expired — try generating a new one. '
                    . 'Use the "Test Connection" button to verify your token works.'
                );
            }
            if ( strpos( $msg, '403' ) !== false ) {
                wp_send_json_error( 'Access denied (HTTP 403). Your token is invalid or expired. Generate a new one at figma.com → Settings → Personal Access Tokens.' );
            }
            wp_send_json_error( $msg );
        }

        // Extract page → frame list
        $pages = [];
        $document = $result['document'] ?? [];
        foreach ( ( $document['children'] ?? [] ) as $page ) {
            $frames = [];
            foreach ( ( $page['children'] ?? [] ) as $frame ) {
                $frames[] = [
                    'id'   => $frame['id'],
                    'name' => $frame['name'],
                ];
            }
            $pages[] = [
                'id'     => $page['id'],
                'name'   => $page['name'],
                'frames' => $frames,
            ];
        }

        wp_send_json_success( [
            'file_name'       => $result['name'] ?? 'Untitled',
            'pages'           => $pages,
            'parsed_file_key' => $file_key,
            'parsed_node_id'  => $parsed['node_id'] ?? '',
        ] );
    }

    // -----------------------------------------------------------------------
    // URL / File Key Parser
    // -----------------------------------------------------------------------

    /**
     * Extract file_key and optional node_id from a raw user input.
     *
     * Handles all Figma URL formats:
     *   https://www.figma.com/file/XXXXXX/Name
     *   https://www.figma.com/design/XXXXXX/Name
     *   https://www.figma.com/proto/XXXXXX/Name?node-id=1-2
     *   https://figma.com/file/XXXXXX/...
     *   or just a bare file key: XXXXXX
     *
     * Node IDs in URLs use dashes (1-2) but the API uses colons (1:2).
     */
    public static function parse_figma_input( string $input ): array {
        $input = trim( $input );

        $result = [
            'file_key' => '',
            'node_id'  => '',
        ];

        // Pattern: Figma URL containing /file/, /design/, or /proto/
        if ( preg_match( '#figma\.com/(?:file|design|proto)/([a-zA-Z0-9]+)#', $input, $m ) ) {
            $result['file_key'] = $m[1];

            // Extract node-id query param if present (URL uses dashes: 1-2)
            if ( preg_match( '#[?&]node-id=([0-9]+-[0-9]+)#', $input, $nm ) ) {
                // Convert URL dash format (1-2) to API colon format (1:2)
                $result['node_id'] = str_replace( '-', ':', $nm[1] );
            }
        } else {
            // Assume bare file key — strip any whitespace or trailing slashes
            $result['file_key'] = preg_replace( '/[^a-zA-Z0-9]/', '', $input );
        }

        return $result;
    }

    // -----------------------------------------------------------------------
    // AJAX: Run the full conversion pipeline
    // -----------------------------------------------------------------------

    public function ajax_convert() {
        check_ajax_referer( 'fte_nonce', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( 'Unauthorized', 403 );
        }

        $token     = get_option( 'fte_figma_token', '' );
        $raw_input = sanitize_text_field( $_POST['file_key'] ?? '' );
        $parsed    = self::parse_figma_input( $raw_input );
        $file_key  = $parsed['file_key'];
        $node_id   = sanitize_text_field( $_POST['node_id'] ?? '' );
        $title     = sanitize_text_field( $_POST['title'] ?? 'Imported from Figma' );

        // If no node_id from the dropdown, try the one parsed from the URL
        if ( empty( $node_id ) && ! empty( $parsed['node_id'] ) ) {
            $node_id = $parsed['node_id'];
        }

        if ( empty( $token ) || empty( $file_key ) ) {
            wp_send_json_error( 'Token and file key are required.' );
        }

        // Increase time limit for large files
        set_time_limit( 120 );

        try {
            $client = new FTE_Figma_Client( $token );

            // Step 1: Fetch the target node
            if ( ! empty( $node_id ) ) {
                $node_data = $client->get_nodes( $file_key, [ $node_id ] );
                if ( is_wp_error( $node_data ) ) {
                    wp_send_json_error( 'Figma API error: ' . $node_data->get_error_message() );
                }
                $target = $node_data['nodes'][ $node_id ]['document'] ?? null;
            } else {
                $file_data = $client->get_file( $file_key );
                if ( is_wp_error( $file_data ) ) {
                    wp_send_json_error( 'Figma API error: ' . $file_data->get_error_message() );
                }
                // Default: first frame on first page
                $target = $file_data['document']['children'][0]['children'][0] ?? null;
            }

            if ( ! $target ) {
                wp_send_json_error( 'Could not find the target frame in the Figma file.' );
            }

            // Step 2: Convert
            $converter = new FTE_Converter();
            $logical_rows = $converter->convert( $target );

            // Step 3: Emit Elementor JSON
            $emitter  = new FTE_Elementor_Emitter();
            $template = $emitter->emit_template( $logical_rows, $title );

            // Step 4: Resolve images
            $resolver = new FTE_Image_Resolver( $client, $file_key );
            $resolved = $resolver->resolve( $template );

            // Step 5: Validate
            $errors = $emitter->validate( $template );

            // Step 6: Import into Elementor
            $importer   = new FTE_Template_Importer();
            $template_id = $importer->import( $template, $title );

            if ( is_wp_error( $template_id ) ) {
                wp_send_json_error( 'Import failed: ' . $template_id->get_error_message() );
            }

            wp_send_json_success( [
                'template_id'      => $template_id,
                'edit_url'         => admin_url( "post.php?post={$template_id}&action=elementor" ),
                'sections'         => count( $template['content'] ),
                'images_resolved'  => count( $resolved ),
                'validation_errors' => $errors,
            ] );

        } catch ( \Exception $e ) {
            wp_send_json_error( 'Conversion error: ' . $e->getMessage() );
        }
    }
}

// Initialize
Figma_To_Elementor::instance();

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *


Notice: ob_end_flush(): Failed to send buffer of zlib output compression (1) in /home/greenspacecoil/public_html/wp-includes/functions.php on line 5493

Notice: ob_end_flush(): Failed to send buffer of zlib output compression (1) in /home/greenspacecoil/public_html/wp-includes/functions.php on line 5493