Plugin Directory

Changeset 3460326


Ignore:
Timestamp:
02/12/2026 08:38:05 PM (8 days ago)
Author:
evcode
Message:

Update to version 1.4.3 from GitHub

Location:
myd-delivery
Files:
6 added
8 deleted
26 edited
1 copied

Legend:

Unmodified
Added
Removed
  • myd-delivery/tags/1.4.3/README.txt

    r3457167 r3460326  
    55Requires at least: 5.5
    66Tested up to: 6.9
    7 Stable tag: 1.4.2
     7Stable tag: 1.4.3
    88Requires PHP: 7.4
    99License: GPL-3.0+
     
    7676== Changelog ==
    7777
     78= 1.4.3 =
     79* Changed: Code improvements.
     80
    7881= 1.4.2 =
    7982* Changed: Code improvements.
  • myd-delivery/tags/1.4.3/composer.json

    r3457167 r3460326  
    2020    },
    2121    "require": {
    22         "eduardovillao/wpfeatureloop-sdk": "^1.0"
     22        "eduardovillao/wpfeatureloop-sdk": "*"
    2323    }
    2424}
  • myd-delivery/tags/1.4.3/composer.lock

    r3457167 r3460326  
    55        "This file is @generated automatically"
    66    ],
    7     "content-hash": "476065f09a8ec63d9d036fcc3ee6d61d",
     7    "content-hash": "20f28567c3f8a6f3ae2a3916cd2070ba",
    88    "packages": [
    99        {
    1010            "name": "eduardovillao/wpfeatureloop-sdk",
    11             "version": "v1.0.0",
     11            "version": "v1.2.0",
    1212            "source": {
    1313                "type": "git",
    1414                "url": "https://github.com/eduardovillao/wpfeatureloop-php-sdk.git",
    15                 "reference": "465bf8d36a50458d1bcaf944988fe6efa9f59f06"
    16             },
    17             "dist": {
    18                 "type": "zip",
    19                 "url": "https://api.github.com/repos/eduardovillao/wpfeatureloop-php-sdk/zipball/465bf8d36a50458d1bcaf944988fe6efa9f59f06",
    20                 "reference": "465bf8d36a50458d1bcaf944988fe6efa9f59f06",
     15                "reference": "923fbe0d40ba0a8756feb374932271e470518cc6"
     16            },
     17            "dist": {
     18                "type": "zip",
     19                "url": "https://api.github.com/repos/eduardovillao/wpfeatureloop-php-sdk/zipball/923fbe0d40ba0a8756feb374932271e470518cc6",
     20                "reference": "923fbe0d40ba0a8756feb374932271e470518cc6",
    2121                "shasum": ""
    2222            },
     
    4848                "source": "https://github.com/eduardovillao/wpfeatureloop-php-sdk"
    4949            },
    50             "time": "2026-02-07T00:06:10+00:00"
     50            "time": "2026-02-12T20:04:34+00:00"
    5151        }
    5252    ],
  • myd-delivery/tags/1.4.3/myd-delivery.php

    r3457167 r3460326  
    66 * Author: EduardoVillao.me
    77 * Author URI: https://eduardovillao.me/
    8  * Version: 1.4.2
     8 * Version: 1.4.3
    99 * Requires PHP: 7.4
    1010 * Requires at least: 5.5
     
    2626define( 'MYDDELIVERY_BASENAME', plugin_basename( __FILE__ ) );
    2727define( 'MYDDELIVERY_DIRNAME', plugin_basename( __DIR__ ) );
    28 define( 'MYDDELIVERY_VERSION', '1.4.2' );
     28define( 'MYDDELIVERY_VERSION', '1.4.3' );
    2929define( 'MYDDELIVERY_MIN_PHP_VERSION', '7.4' );
    3030define( 'MYDDELIVERY_MIN_WP_VERSION', '5.5' );
     
    5252
    5353require_once __DIR__ . '/vendor/autoload.php';
    54 WPFeatureLoop\Client::init(
     54\WPFeatureLoop\Client::init(
    5555    'pk_live_783e198c8177013cc256635034cd22ba',
    5656    'cml8xc9n7000004l4ofl5tovv',
  • myd-delivery/tags/1.4.3/templates/admin/dashboard.php

    r3457153 r3460326  
    11<?php
    2 
    3 use WPFeatureLoop\Client;
    42
    53if ( ! defined( 'ABSPATH' ) ) {
     
    122120            <?php if ( \current_user_can( 'manage_options' ) ) : ?>
    123121                <div class="mydd-admin-card">
    124                     <?php echo Client::renderWidget(); ?>
     122                    <?php echo \WPFeatureLoop\Client::getInstance('cml8xc9n7000004l4ofl5tovv')->renderWidget(); ?>
    125123                </div>
    126124            <?php endif; ?>
  • myd-delivery/tags/1.4.3/vendor/autoload.php

    r3457167 r3460326  
    2020require_once __DIR__ . '/composer/autoload_real.php';
    2121
    22 return ComposerAutoloaderInit476065f09a8ec63d9d036fcc3ee6d61d::getLoader();
     22return ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba::getLoader();
  • myd-delivery/tags/1.4.3/vendor/composer/autoload_real.php

    r3457167 r3460326  
    33// autoload_real.php @generated by Composer
    44
    5 class ComposerAutoloaderInit476065f09a8ec63d9d036fcc3ee6d61d
     5class ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba
    66{
    77    private static $loader;
     
    2525        require __DIR__ . '/platform_check.php';
    2626
    27         spl_autoload_register(array('ComposerAutoloaderInit476065f09a8ec63d9d036fcc3ee6d61d', 'loadClassLoader'), true, true);
     27        spl_autoload_register(array('ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba', 'loadClassLoader'), true, true);
    2828        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
    29         spl_autoload_unregister(array('ComposerAutoloaderInit476065f09a8ec63d9d036fcc3ee6d61d', 'loadClassLoader'));
     29        spl_autoload_unregister(array('ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba', 'loadClassLoader'));
    3030
    3131        require __DIR__ . '/autoload_static.php';
    32         call_user_func(\Composer\Autoload\ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d::getInitializer($loader));
     32        call_user_func(\Composer\Autoload\ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::getInitializer($loader));
    3333
    3434        $loader->register(true);
  • myd-delivery/tags/1.4.3/vendor/composer/autoload_static.php

    r3457167 r3460326  
    55namespace Composer\Autoload;
    66
    7 class ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d
     7class ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba
    88{
    99    public static $prefixLengthsPsr4 = array (
     
    3333    {
    3434        return \Closure::bind(function () use ($loader) {
    35             $loader->prefixLengthsPsr4 = ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d::$prefixLengthsPsr4;
    36             $loader->prefixDirsPsr4 = ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d::$prefixDirsPsr4;
    37             $loader->classMap = ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d::$classMap;
     35            $loader->prefixLengthsPsr4 = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$prefixLengthsPsr4;
     36            $loader->prefixDirsPsr4 = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$prefixDirsPsr4;
     37            $loader->classMap = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$classMap;
    3838
    3939        }, null, ClassLoader::class);
  • myd-delivery/tags/1.4.3/vendor/composer/installed.json

    r3457167 r3460326  
    33        {
    44            "name": "eduardovillao/wpfeatureloop-sdk",
    5             "version": "v1.0.0",
    6             "version_normalized": "1.0.0.0",
     5            "version": "v1.2.0",
     6            "version_normalized": "1.2.0.0",
    77            "source": {
    88                "type": "git",
    99                "url": "https://github.com/eduardovillao/wpfeatureloop-php-sdk.git",
    10                 "reference": "465bf8d36a50458d1bcaf944988fe6efa9f59f06"
     10                "reference": "923fbe0d40ba0a8756feb374932271e470518cc6"
    1111            },
    1212            "dist": {
    1313                "type": "zip",
    14                 "url": "https://api.github.com/repos/eduardovillao/wpfeatureloop-php-sdk/zipball/465bf8d36a50458d1bcaf944988fe6efa9f59f06",
    15                 "reference": "465bf8d36a50458d1bcaf944988fe6efa9f59f06",
     14                "url": "https://api.github.com/repos/eduardovillao/wpfeatureloop-php-sdk/zipball/923fbe0d40ba0a8756feb374932271e470518cc6",
     15                "reference": "923fbe0d40ba0a8756feb374932271e470518cc6",
    1616                "shasum": ""
    1717            },
     
    2222                "squizlabs/php_codesniffer": "^3.12"
    2323            },
    24             "time": "2026-02-07T00:06:10+00:00",
     24            "time": "2026-02-12T20:04:34+00:00",
    2525            "type": "library",
    2626            "installation-source": "dist",
  • myd-delivery/tags/1.4.3/vendor/composer/installed.php

    r3457167 r3460326  
    22    'root' => array(
    33        'name' => 'eduardovillao/myd-delivery',
    4         'pretty_version' => 'v1.4.2',
    5         'version' => '1.4.2.0',
    6         'reference' => '496d383a6e57bc9d654eb0d8fef2e17169ad7e4f',
     4        'pretty_version' => 'v1.4.3',
     5        'version' => '1.4.3.0',
     6        'reference' => '35999f563677a90baf9954827af9c8149b5170f9',
    77        'type' => 'project',
    88        'install_path' => __DIR__ . '/../../',
     
    1212    'versions' => array(
    1313        'eduardovillao/myd-delivery' => array(
    14             'pretty_version' => 'v1.4.2',
    15             'version' => '1.4.2.0',
    16             'reference' => '496d383a6e57bc9d654eb0d8fef2e17169ad7e4f',
     14            'pretty_version' => 'v1.4.3',
     15            'version' => '1.4.3.0',
     16            'reference' => '35999f563677a90baf9954827af9c8149b5170f9',
    1717            'type' => 'project',
    1818            'install_path' => __DIR__ . '/../../',
     
    2121        ),
    2222        'eduardovillao/wpfeatureloop-sdk' => array(
    23             'pretty_version' => 'v1.0.0',
    24             'version' => '1.0.0.0',
    25             'reference' => '465bf8d36a50458d1bcaf944988fe6efa9f59f06',
     23            'pretty_version' => 'v1.2.0',
     24            'version' => '1.2.0.0',
     25            'reference' => '923fbe0d40ba0a8756feb374932271e470518cc6',
    2626            'type' => 'library',
    2727            'install_path' => __DIR__ . '/../eduardovillao/wpfeatureloop-sdk',
  • myd-delivery/tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/Client.php

    r3457167 r3460326  
    44
    55namespace WPFeatureLoop;
     6
     7use WPFeatureLoop\Widget;
     8use WPFeatureLoop\RestApi;
     9use WPFeatureLoop\Api;
     10use WPFeatureLoop\User;
    611
    712/**
     
    914 *
    1015 * Main entry point for the FeatureLoop SDK.
    11  *
    12  * STEP 1 - In your main plugin file (runs on every request):
     16 * Supports multiple instances (one per project) on the same WordPress site.
     17 *
     18 * STEP 1 - In your main plugin file:
    1319 * ```php
    1420 * use WPFeatureLoop\Client;
    1521 *
    16  * // Initialize client early (registers REST API routes)
    1722 * Client::init('pk_live_xxx', 'project_id');
    1823 * ```
     
    2227 * use WPFeatureLoop\Client;
    2328 *
    24  * // Render widget (that's it!)
    25  * echo Client::renderWidget(['locale' => 'en']);
     29 * echo Client::renderWidget();
    2630 * ```
    2731 */
     
    3135     * SDK Version (used for cache busting)
    3236     */
    33     public const VERSION = '1.0.0';
    34 
    35     /**
    36      * Script/style handle
     37    public const VERSION = '1.2.0';
     38
     39    /**
     40     * Script/style handle (shared across instances — same files)
    3741     */
    3842    public const HANDLE = 'wpfeatureloop';
    3943
    4044    /**
    41      * Singleton instance
    42      */
    43     private static ?Client $instance = null;
     45     * Registry of instances by projectId
     46     *
     47     * @var array<string, Client>
     48     */
     49    private static array $instances = [];
     50
     51    /**
     52     * Whether REST API routes have been registered
     53     */
     54    private static bool $routesRegistered = false;
    4455
    4556    /**
     
    4960
    5061    /**
    51      * REST API handler
    52      */
    53     private RestApi $restApi;
     62     * Project ID
     63     */
     64    private string $projectId;
    5465
    5566    /**
     
    6980
    7081    /**
    71      * Initialize the client (singleton pattern)
     82     * Initialize the client (registry pattern — one instance per projectId)
    7283     *
    7384     * Call this in your main plugin file so REST API routes are registered on every request.
     
    8091    public static function init(string $publicKey, string $projectId, array $options = []): Client
    8192    {
    82         if (self::$instance === null) {
    83             self::$instance = new self($publicKey, $projectId, $options);
    84         }
    85 
    86         return self::$instance;
    87     }
    88 
    89     /**
    90      * Get the singleton instance
    91      *
     93        if (!isset(self::$instances[$projectId])) {
     94            self::$instances[$projectId] = new self($publicKey, $projectId, $options);
     95        }
     96
     97        return self::$instances[$projectId];
     98    }
     99
     100    /**
     101     * Get an instance from the registry
     102     *
     103     * @param string|null $projectId Project ID (null returns last registered instance)
    92104     * @return Client|null Returns null if not initialized
    93105     */
    94     public static function getInstance(): ?Client
    95     {
    96         return self::$instance;
    97     }
    98 
    99     /**
    100      * Render the widget (convenience static method)
    101      *
    102      * @return string HTML or empty string if not initialized
    103      */
    104     public static function renderWidget(): string
    105     {
    106         if (self::$instance === null) {
    107             return '<!-- WPFeatureLoop: Client not initialized. Call Client::init() first. -->';
    108         }
    109 
    110         $widget = new Widget(self::$instance);
     106    public static function getInstance(?string $projectId = null): ?Client
     107    {
     108        if ($projectId !== null) {
     109            return self::$instances[$projectId] ?? null;
     110        }
     111
     112        // Backward compat: return last registered instance
     113        return !empty(self::$instances) ? end(self::$instances) : null;
     114    }
     115
     116    /**
     117     * Render the widget
     118     *
     119     * @return string HTML
     120     */
     121    public function renderWidget(): string
     122    {
     123        $widget = new Widget($this);
    111124        return $widget->render();
     125    }
     126
     127    /**
     128     * Get project ID
     129     *
     130     * @return string
     131     */
     132    public function getProjectId(): string
     133    {
     134        return $this->projectId;
    112135    }
    113136
     
    136159        $this->capability = $options['capability'] ?? 'read';
    137160        $this->language = $options['language'] ?? 'en';
     161        $this->projectId = $projectId;
    138162
    139163        // Assets URL based on SDK location
     
    141165
    142166        $this->api = new Api($publicKey, $projectId, $apiUrl);
    143         $this->restApi = new RestApi($this);
    144 
    145         // Register REST API routes
    146         $this->scheduleOrRun('rest_api_init', [$this->restApi, 'registerRoutes']);
     167
     168        // Register REST API routes only once (shared across all instances)
     169        if (!self::$routesRegistered) {
     170            self::$routesRegistered = true;
     171            $restApi = new RestApi();
     172            $this->scheduleOrRun('rest_api_init', [$restApi, 'registerRoutes']);
     173        }
    147174
    148175        // Register assets (admin only for now)
     
    163190
    164191    /**
    165      * Register CSS and JS assets (called via admin_enqueue_scripts hook)
    166      *
    167      * This registers assets early so they're available for enqueuing later.
     192     * Register JS asset (called via admin_enqueue_scripts hook)
     193     *
     194     * CSS is inlined by Widget to avoid FOUC.
    168195     */
    169196    public function registerAssets(): void
    170197    {
    171         wp_register_style(
    172             self::HANDLE,
    173             $this->assetsUrl . '/css/wpfeatureloop.css',
    174             [],
    175             self::VERSION
    176         );
    177 
    178198        wp_register_script(
    179199            self::HANDLE,
    180             $this->assetsUrl . '/js/wpfeatureloop.js',
     200            $this->assetsUrl . '/js/wpfeatureloop.min.js',
    181201            [],
    182202            self::VERSION,
     
    186206
    187207    /**
    188      * Enqueue CSS and JS assets (called when widget renders)
    189      *
    190      * Assets must be registered first via registerAssets().
     208     * Enqueue JS asset (called when widget renders)
     209     *
     210     * CSS is handled inline by Widget::render().
    191211     */
    192212    public function enqueueAssets(): void
    193213    {
    194         // If not registered yet (edge case), register now
    195         if (!wp_style_is(self::HANDLE, 'registered')) {
     214        if (!wp_script_is(self::HANDLE, 'registered')) {
    196215            $this->registerAssets();
    197216        }
    198217
    199         wp_enqueue_style(self::HANDLE);
    200218        wp_enqueue_script(self::HANDLE);
    201219    }
  • myd-delivery/tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/RestApi.php

    r3457167 r3460326  
    1010 * Registers WordPress REST API endpoints for the widget.
    1111 * Endpoints are under /wp-json/wpfeatureloop/v1/
     12 *
     13 * Routes are registered once and shared across all Client instances.
     14 * Each request includes a project_id query parameter so the handler
     15 * can resolve the correct Client instance from the registry.
    1216 */
    1317class RestApi
     
    1923
    2024    /**
    21      * Client instance
    22      */
    23     private Client $client;
    24 
    25     /**
    26      * Constructor
    27      */
    28     public function __construct(Client $client)
    29     {
    30         $this->client = $client;
     25     * Common arg definition for project_id (required on all routes)
     26     */
     27    private const PROJECT_ID_ARG = [
     28        'project_id' => [
     29            'required' => true,
     30            'type' => 'string',
     31            'sanitize_callback' => 'sanitize_text_field',
     32        ],
     33    ];
     34
     35    private function resolveClient(\WP_REST_Request $request): ?Client
     36    {
     37        return Client::getInstance($request->get_param('project_id'));
    3138    }
    3239
     
    4148            'callback' => [$this, 'getFeatures'],
    4249            'permission_callback' => '__return_true',
     50            'args' => self::PROJECT_ID_ARG,
    4351        ]);
    4452
     
    4856            'callback' => [$this, 'createFeature'],
    4957            'permission_callback' => [$this, 'canInteract'],
    50             'args' => [
     58            'args' => self::PROJECT_ID_ARG + [
    5159                'title' => [
    5260                    'required' => true,
     
    6876            'callback' => [$this, 'vote'],
    6977            'permission_callback' => [$this, 'canInteract'],
    70             'args' => [
     78            'args' => self::PROJECT_ID_ARG + [
    7179                'id' => [
    7280                    'required' => true,
     
    8896            'callback' => [$this, 'getComments'],
    8997            'permission_callback' => '__return_true',
    90             'args' => [
     98            'args' => self::PROJECT_ID_ARG + [
    9199                'id' => [
    92100                    'required' => true,
     
    102110            'callback' => [$this, 'addComment'],
    103111            'permission_callback' => [$this, 'canInteract'],
    104             'args' => [
     112            'args' => self::PROJECT_ID_ARG + [
    105113                'id' => [
    106114                    'required' => true,
     
    120128     * Permission callback - check if user can interact
    121129     */
    122     public function canInteract(): bool
    123     {
    124         return $this->client->canInteract();
     130    public function canInteract(\WP_REST_Request $request): bool
     131    {
     132        $client = $this->resolveClient($request);
     133
     134        if (!$client) {
     135            return false;
     136        }
     137
     138        return $client->canInteract();
    125139    }
    126140
     
    130144    public function getFeatures(\WP_REST_Request $request): \WP_REST_Response
    131145    {
     146        $client = $this->resolveClient($request);
     147
     148        if (!$client) {
     149            return new \WP_REST_Response([
     150                'error' => 'Unknown project ID',
     151            ], 400);
     152        }
     153
    132154        $args = [
    133155            'status' => $request->get_param('status'),
     
    139161        $args = array_filter($args, fn($v) => $v !== null);
    140162
    141         $result = $this->client->getFeatures($args);
     163        $result = $client->getFeatures($args);
    142164
    143165        if (is_wp_error($result)) {
     
    165187    public function createFeature(\WP_REST_Request $request): \WP_REST_Response
    166188    {
     189        $client = $this->resolveClient($request);
     190
     191        if (!$client) {
     192            return new \WP_REST_Response([
     193                'error' => 'Unknown project ID',
     194            ], 400);
     195        }
     196
    167197        $title = $request->get_param('title');
    168198        $description = $request->get_param('description') ?? '';
    169199
    170         $result = $this->client->createFeature($title, $description);
     200        $result = $client->createFeature($title, $description);
    171201
    172202        if (is_wp_error($result)) {
     
    186216    public function vote(\WP_REST_Request $request): \WP_REST_Response
    187217    {
     218        $client = $this->resolveClient($request);
     219
     220        if (!$client) {
     221            return new \WP_REST_Response([
     222                'error' => 'Unknown project ID',
     223            ], 400);
     224        }
     225
    188226        $featureId = $request->get_param('id');
    189227        $voteType = $request->get_param('vote'); // 'up', 'down', or 'none'
    190228
    191         $result = $this->client->vote($featureId, $voteType);
     229        $result = $client->vote($featureId, $voteType);
    192230
    193231        if (is_wp_error($result)) {
     
    207245    public function getComments(\WP_REST_Request $request): \WP_REST_Response
    208246    {
     247        $client = $this->resolveClient($request);
     248
     249        if (!$client) {
     250            return new \WP_REST_Response([
     251                'error' => 'Unknown project ID',
     252            ], 400);
     253        }
     254
    209255        $featureId = $request->get_param('id');
    210256
    211         $result = $this->client->getComments($featureId);
     257        $result = $client->getComments($featureId);
    212258
    213259        if (is_wp_error($result)) {
     
    232278    public function addComment(\WP_REST_Request $request): \WP_REST_Response
    233279    {
     280        $client = $this->resolveClient($request);
     281
     282        if (!$client) {
     283            return new \WP_REST_Response([
     284                'error' => 'Unknown project ID',
     285            ], 400);
     286        }
     287
    234288        $featureId = $request->get_param('id');
    235289        $text = $request->get_param('text');
    236290
    237         $result = $this->client->addComment($featureId, $text);
     291        $result = $client->addComment($featureId, $text);
    238292
    239293        if (is_wp_error($result)) {
  • myd-delivery/tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/Widget.php

    r3457167 r3460326  
    44
    55namespace WPFeatureLoop;
     6
     7use WPFeatureLoop\Client;
     8use WPFeatureLoop\RestApi;
    69
    710/**
     
    1013 * Renders the feature voting widget with the same UI/UX as the JS SDK.
    1114 * Uses template files instead of inline HTML strings.
     15 *
     16 * Everything (modals, templates, toast) is rendered inside the container div
     17 * so the JS can scope all queries to this.container without needing unique IDs.
    1218 */
    1319class Widget
     
    3238     */
    3339    private string $templatesPath;
     40
     41    /**
     42     * Whether inline styles have already been outputted (shared across instances)
     43     */
     44    private static bool $stylesOutputted = false;
    3445
    3546    /**
     
    128139
    129140    /**
     141     * Get inline styles
     142     *
     143     * Reads pre-minified CSS file and wraps in <style> tag.
     144     * Only outputs once even with multiple widget instances.
     145     */
     146    private function getInlineStyles(): string
     147    {
     148        if (self::$stylesOutputted) {
     149            return '';
     150        }
     151
     152        self::$stylesOutputted = true;
     153
     154        $cssFile = dirname(__DIR__) . '/assets/css/wpfeatureloop.min.css';
     155
     156        if (!file_exists($cssFile)) {
     157            return '';
     158        }
     159
     160        return '<style>' . file_get_contents($cssFile) . '</style>';
     161    }
     162
     163    /**
    130164     * Render a template file with variables
    131165     */
     
    147181
    148182    /**
    149      * Render the complete widget (container + modals + templates + scripts)
     183     * Render the complete widget
     184     *
     185     * Everything (header, skeleton, modals, templates, toast) is inside
     186     * a single container div so JS can scope all queries.
    150187     *
    151188     * @return string HTML
     
    153190    public function render(): string
    154191    {
    155         // Enqueue assets only when widget is rendered
     192        // Enqueue JS only when widget is rendered
    156193        $this->client->enqueueAssets();
    157194
    158195        $html = '';
    159196
    160         // Main container with skeleton
    161         $html .= $this->renderTemplate('widget', [
    162             'container_id' => self::CONTAINER_ID,
     197        // Inline CSS before container to avoid FOUC
     198        $html .= $this->getInlineStyles();
     199
     200        // Open container with config as data attribute (JS reads it for auto-init)
     201        $html .= sprintf(
     202            '<div id="%s" class="wfl-container" data-loading="true" data-config="%s">',
     203            esc_attr(self::CONTAINER_ID),
     204            esc_attr(wp_json_encode($this->getJsConfig()))
     205        );
     206
     207        // Header + skeleton (visible content)
     208        $html .= $this->renderTemplate('widget-inner', [
    163209            'title' => $this->t('title'),
    164210            'subtitle' => $this->t('subtitle'),
     
    195241        ]);
    196242
    197         // JS Config
    198         $config = wp_json_encode($this->getJsConfig());
    199         $html .= sprintf('<script>window.wpfeatureloop_config = %s;</script>', $config);
     243        // Close container
     244        $html .= '</div>';
    200245
    201246        return $html;
     
    210255    {
    211256        return [
    212             'container_id' => self::CONTAINER_ID,
     257            'project_id' => $this->client->getProjectId(),
    213258            'rest_url' => rest_url(RestApi::NAMESPACE),
    214259            'nonce' => wp_create_nonce('wp_rest'),
  • myd-delivery/trunk/README.txt

    r3457167 r3460326  
    55Requires at least: 5.5
    66Tested up to: 6.9
    7 Stable tag: 1.4.2
     7Stable tag: 1.4.3
    88Requires PHP: 7.4
    99License: GPL-3.0+
     
    7676== Changelog ==
    7777
     78= 1.4.3 =
     79* Changed: Code improvements.
     80
    7881= 1.4.2 =
    7982* Changed: Code improvements.
  • myd-delivery/trunk/composer.json

    r3457167 r3460326  
    2020    },
    2121    "require": {
    22         "eduardovillao/wpfeatureloop-sdk": "^1.0"
     22        "eduardovillao/wpfeatureloop-sdk": "*"
    2323    }
    2424}
  • myd-delivery/trunk/composer.lock

    r3457167 r3460326  
    55        "This file is @generated automatically"
    66    ],
    7     "content-hash": "476065f09a8ec63d9d036fcc3ee6d61d",
     7    "content-hash": "20f28567c3f8a6f3ae2a3916cd2070ba",
    88    "packages": [
    99        {
    1010            "name": "eduardovillao/wpfeatureloop-sdk",
    11             "version": "v1.0.0",
     11            "version": "v1.2.0",
    1212            "source": {
    1313                "type": "git",
    1414                "url": "https://github.com/eduardovillao/wpfeatureloop-php-sdk.git",
    15                 "reference": "465bf8d36a50458d1bcaf944988fe6efa9f59f06"
    16             },
    17             "dist": {
    18                 "type": "zip",
    19                 "url": "https://api.github.com/repos/eduardovillao/wpfeatureloop-php-sdk/zipball/465bf8d36a50458d1bcaf944988fe6efa9f59f06",
    20                 "reference": "465bf8d36a50458d1bcaf944988fe6efa9f59f06",
     15                "reference": "923fbe0d40ba0a8756feb374932271e470518cc6"
     16            },
     17            "dist": {
     18                "type": "zip",
     19                "url": "https://api.github.com/repos/eduardovillao/wpfeatureloop-php-sdk/zipball/923fbe0d40ba0a8756feb374932271e470518cc6",
     20                "reference": "923fbe0d40ba0a8756feb374932271e470518cc6",
    2121                "shasum": ""
    2222            },
     
    4848                "source": "https://github.com/eduardovillao/wpfeatureloop-php-sdk"
    4949            },
    50             "time": "2026-02-07T00:06:10+00:00"
     50            "time": "2026-02-12T20:04:34+00:00"
    5151        }
    5252    ],
  • myd-delivery/trunk/myd-delivery.php

    r3457167 r3460326  
    66 * Author: EduardoVillao.me
    77 * Author URI: https://eduardovillao.me/
    8  * Version: 1.4.2
     8 * Version: 1.4.3
    99 * Requires PHP: 7.4
    1010 * Requires at least: 5.5
     
    2626define( 'MYDDELIVERY_BASENAME', plugin_basename( __FILE__ ) );
    2727define( 'MYDDELIVERY_DIRNAME', plugin_basename( __DIR__ ) );
    28 define( 'MYDDELIVERY_VERSION', '1.4.2' );
     28define( 'MYDDELIVERY_VERSION', '1.4.3' );
    2929define( 'MYDDELIVERY_MIN_PHP_VERSION', '7.4' );
    3030define( 'MYDDELIVERY_MIN_WP_VERSION', '5.5' );
     
    5252
    5353require_once __DIR__ . '/vendor/autoload.php';
    54 WPFeatureLoop\Client::init(
     54\WPFeatureLoop\Client::init(
    5555    'pk_live_783e198c8177013cc256635034cd22ba',
    5656    'cml8xc9n7000004l4ofl5tovv',
  • myd-delivery/trunk/templates/admin/dashboard.php

    r3457153 r3460326  
    11<?php
    2 
    3 use WPFeatureLoop\Client;
    42
    53if ( ! defined( 'ABSPATH' ) ) {
     
    122120            <?php if ( \current_user_can( 'manage_options' ) ) : ?>
    123121                <div class="mydd-admin-card">
    124                     <?php echo Client::renderWidget(); ?>
     122                    <?php echo \WPFeatureLoop\Client::getInstance('cml8xc9n7000004l4ofl5tovv')->renderWidget(); ?>
    125123                </div>
    126124            <?php endif; ?>
  • myd-delivery/trunk/vendor/autoload.php

    r3457167 r3460326  
    2020require_once __DIR__ . '/composer/autoload_real.php';
    2121
    22 return ComposerAutoloaderInit476065f09a8ec63d9d036fcc3ee6d61d::getLoader();
     22return ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba::getLoader();
  • myd-delivery/trunk/vendor/composer/autoload_real.php

    r3457167 r3460326  
    33// autoload_real.php @generated by Composer
    44
    5 class ComposerAutoloaderInit476065f09a8ec63d9d036fcc3ee6d61d
     5class ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba
    66{
    77    private static $loader;
     
    2525        require __DIR__ . '/platform_check.php';
    2626
    27         spl_autoload_register(array('ComposerAutoloaderInit476065f09a8ec63d9d036fcc3ee6d61d', 'loadClassLoader'), true, true);
     27        spl_autoload_register(array('ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba', 'loadClassLoader'), true, true);
    2828        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
    29         spl_autoload_unregister(array('ComposerAutoloaderInit476065f09a8ec63d9d036fcc3ee6d61d', 'loadClassLoader'));
     29        spl_autoload_unregister(array('ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba', 'loadClassLoader'));
    3030
    3131        require __DIR__ . '/autoload_static.php';
    32         call_user_func(\Composer\Autoload\ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d::getInitializer($loader));
     32        call_user_func(\Composer\Autoload\ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::getInitializer($loader));
    3333
    3434        $loader->register(true);
  • myd-delivery/trunk/vendor/composer/autoload_static.php

    r3457167 r3460326  
    55namespace Composer\Autoload;
    66
    7 class ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d
     7class ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba
    88{
    99    public static $prefixLengthsPsr4 = array (
     
    3333    {
    3434        return \Closure::bind(function () use ($loader) {
    35             $loader->prefixLengthsPsr4 = ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d::$prefixLengthsPsr4;
    36             $loader->prefixDirsPsr4 = ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d::$prefixDirsPsr4;
    37             $loader->classMap = ComposerStaticInit476065f09a8ec63d9d036fcc3ee6d61d::$classMap;
     35            $loader->prefixLengthsPsr4 = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$prefixLengthsPsr4;
     36            $loader->prefixDirsPsr4 = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$prefixDirsPsr4;
     37            $loader->classMap = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$classMap;
    3838
    3939        }, null, ClassLoader::class);
  • myd-delivery/trunk/vendor/composer/installed.json

    r3457167 r3460326  
    33        {
    44            "name": "eduardovillao/wpfeatureloop-sdk",
    5             "version": "v1.0.0",
    6             "version_normalized": "1.0.0.0",
     5            "version": "v1.2.0",
     6            "version_normalized": "1.2.0.0",
    77            "source": {
    88                "type": "git",
    99                "url": "https://github.com/eduardovillao/wpfeatureloop-php-sdk.git",
    10                 "reference": "465bf8d36a50458d1bcaf944988fe6efa9f59f06"
     10                "reference": "923fbe0d40ba0a8756feb374932271e470518cc6"
    1111            },
    1212            "dist": {
    1313                "type": "zip",
    14                 "url": "https://api.github.com/repos/eduardovillao/wpfeatureloop-php-sdk/zipball/465bf8d36a50458d1bcaf944988fe6efa9f59f06",
    15                 "reference": "465bf8d36a50458d1bcaf944988fe6efa9f59f06",
     14                "url": "https://api.github.com/repos/eduardovillao/wpfeatureloop-php-sdk/zipball/923fbe0d40ba0a8756feb374932271e470518cc6",
     15                "reference": "923fbe0d40ba0a8756feb374932271e470518cc6",
    1616                "shasum": ""
    1717            },
     
    2222                "squizlabs/php_codesniffer": "^3.12"
    2323            },
    24             "time": "2026-02-07T00:06:10+00:00",
     24            "time": "2026-02-12T20:04:34+00:00",
    2525            "type": "library",
    2626            "installation-source": "dist",
  • myd-delivery/trunk/vendor/composer/installed.php

    r3457167 r3460326  
    22    'root' => array(
    33        'name' => 'eduardovillao/myd-delivery',
    4         'pretty_version' => 'v1.4.2',
    5         'version' => '1.4.2.0',
    6         'reference' => '496d383a6e57bc9d654eb0d8fef2e17169ad7e4f',
     4        'pretty_version' => 'v1.4.3',
     5        'version' => '1.4.3.0',
     6        'reference' => '35999f563677a90baf9954827af9c8149b5170f9',
    77        'type' => 'project',
    88        'install_path' => __DIR__ . '/../../',
     
    1212    'versions' => array(
    1313        'eduardovillao/myd-delivery' => array(
    14             'pretty_version' => 'v1.4.2',
    15             'version' => '1.4.2.0',
    16             'reference' => '496d383a6e57bc9d654eb0d8fef2e17169ad7e4f',
     14            'pretty_version' => 'v1.4.3',
     15            'version' => '1.4.3.0',
     16            'reference' => '35999f563677a90baf9954827af9c8149b5170f9',
    1717            'type' => 'project',
    1818            'install_path' => __DIR__ . '/../../',
     
    2121        ),
    2222        'eduardovillao/wpfeatureloop-sdk' => array(
    23             'pretty_version' => 'v1.0.0',
    24             'version' => '1.0.0.0',
    25             'reference' => '465bf8d36a50458d1bcaf944988fe6efa9f59f06',
     23            'pretty_version' => 'v1.2.0',
     24            'version' => '1.2.0.0',
     25            'reference' => '923fbe0d40ba0a8756feb374932271e470518cc6',
    2626            'type' => 'library',
    2727            'install_path' => __DIR__ . '/../eduardovillao/wpfeatureloop-sdk',
  • myd-delivery/trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/Client.php

    r3457167 r3460326  
    44
    55namespace WPFeatureLoop;
     6
     7use WPFeatureLoop\Widget;
     8use WPFeatureLoop\RestApi;
     9use WPFeatureLoop\Api;
     10use WPFeatureLoop\User;
    611
    712/**
     
    914 *
    1015 * Main entry point for the FeatureLoop SDK.
    11  *
    12  * STEP 1 - In your main plugin file (runs on every request):
     16 * Supports multiple instances (one per project) on the same WordPress site.
     17 *
     18 * STEP 1 - In your main plugin file:
    1319 * ```php
    1420 * use WPFeatureLoop\Client;
    1521 *
    16  * // Initialize client early (registers REST API routes)
    1722 * Client::init('pk_live_xxx', 'project_id');
    1823 * ```
     
    2227 * use WPFeatureLoop\Client;
    2328 *
    24  * // Render widget (that's it!)
    25  * echo Client::renderWidget(['locale' => 'en']);
     29 * echo Client::renderWidget();
    2630 * ```
    2731 */
     
    3135     * SDK Version (used for cache busting)
    3236     */
    33     public const VERSION = '1.0.0';
    34 
    35     /**
    36      * Script/style handle
     37    public const VERSION = '1.2.0';
     38
     39    /**
     40     * Script/style handle (shared across instances — same files)
    3741     */
    3842    public const HANDLE = 'wpfeatureloop';
    3943
    4044    /**
    41      * Singleton instance
    42      */
    43     private static ?Client $instance = null;
     45     * Registry of instances by projectId
     46     *
     47     * @var array<string, Client>
     48     */
     49    private static array $instances = [];
     50
     51    /**
     52     * Whether REST API routes have been registered
     53     */
     54    private static bool $routesRegistered = false;
    4455
    4556    /**
     
    4960
    5061    /**
    51      * REST API handler
    52      */
    53     private RestApi $restApi;
     62     * Project ID
     63     */
     64    private string $projectId;
    5465
    5566    /**
     
    6980
    7081    /**
    71      * Initialize the client (singleton pattern)
     82     * Initialize the client (registry pattern — one instance per projectId)
    7283     *
    7384     * Call this in your main plugin file so REST API routes are registered on every request.
     
    8091    public static function init(string $publicKey, string $projectId, array $options = []): Client
    8192    {
    82         if (self::$instance === null) {
    83             self::$instance = new self($publicKey, $projectId, $options);
    84         }
    85 
    86         return self::$instance;
    87     }
    88 
    89     /**
    90      * Get the singleton instance
    91      *
     93        if (!isset(self::$instances[$projectId])) {
     94            self::$instances[$projectId] = new self($publicKey, $projectId, $options);
     95        }
     96
     97        return self::$instances[$projectId];
     98    }
     99
     100    /**
     101     * Get an instance from the registry
     102     *
     103     * @param string|null $projectId Project ID (null returns last registered instance)
    92104     * @return Client|null Returns null if not initialized
    93105     */
    94     public static function getInstance(): ?Client
    95     {
    96         return self::$instance;
    97     }
    98 
    99     /**
    100      * Render the widget (convenience static method)
    101      *
    102      * @return string HTML or empty string if not initialized
    103      */
    104     public static function renderWidget(): string
    105     {
    106         if (self::$instance === null) {
    107             return '<!-- WPFeatureLoop: Client not initialized. Call Client::init() first. -->';
    108         }
    109 
    110         $widget = new Widget(self::$instance);
     106    public static function getInstance(?string $projectId = null): ?Client
     107    {
     108        if ($projectId !== null) {
     109            return self::$instances[$projectId] ?? null;
     110        }
     111
     112        // Backward compat: return last registered instance
     113        return !empty(self::$instances) ? end(self::$instances) : null;
     114    }
     115
     116    /**
     117     * Render the widget
     118     *
     119     * @return string HTML
     120     */
     121    public function renderWidget(): string
     122    {
     123        $widget = new Widget($this);
    111124        return $widget->render();
     125    }
     126
     127    /**
     128     * Get project ID
     129     *
     130     * @return string
     131     */
     132    public function getProjectId(): string
     133    {
     134        return $this->projectId;
    112135    }
    113136
     
    136159        $this->capability = $options['capability'] ?? 'read';
    137160        $this->language = $options['language'] ?? 'en';
     161        $this->projectId = $projectId;
    138162
    139163        // Assets URL based on SDK location
     
    141165
    142166        $this->api = new Api($publicKey, $projectId, $apiUrl);
    143         $this->restApi = new RestApi($this);
    144 
    145         // Register REST API routes
    146         $this->scheduleOrRun('rest_api_init', [$this->restApi, 'registerRoutes']);
     167
     168        // Register REST API routes only once (shared across all instances)
     169        if (!self::$routesRegistered) {
     170            self::$routesRegistered = true;
     171            $restApi = new RestApi();
     172            $this->scheduleOrRun('rest_api_init', [$restApi, 'registerRoutes']);
     173        }
    147174
    148175        // Register assets (admin only for now)
     
    163190
    164191    /**
    165      * Register CSS and JS assets (called via admin_enqueue_scripts hook)
    166      *
    167      * This registers assets early so they're available for enqueuing later.
     192     * Register JS asset (called via admin_enqueue_scripts hook)
     193     *
     194     * CSS is inlined by Widget to avoid FOUC.
    168195     */
    169196    public function registerAssets(): void
    170197    {
    171         wp_register_style(
    172             self::HANDLE,
    173             $this->assetsUrl . '/css/wpfeatureloop.css',
    174             [],
    175             self::VERSION
    176         );
    177 
    178198        wp_register_script(
    179199            self::HANDLE,
    180             $this->assetsUrl . '/js/wpfeatureloop.js',
     200            $this->assetsUrl . '/js/wpfeatureloop.min.js',
    181201            [],
    182202            self::VERSION,
     
    186206
    187207    /**
    188      * Enqueue CSS and JS assets (called when widget renders)
    189      *
    190      * Assets must be registered first via registerAssets().
     208     * Enqueue JS asset (called when widget renders)
     209     *
     210     * CSS is handled inline by Widget::render().
    191211     */
    192212    public function enqueueAssets(): void
    193213    {
    194         // If not registered yet (edge case), register now
    195         if (!wp_style_is(self::HANDLE, 'registered')) {
     214        if (!wp_script_is(self::HANDLE, 'registered')) {
    196215            $this->registerAssets();
    197216        }
    198217
    199         wp_enqueue_style(self::HANDLE);
    200218        wp_enqueue_script(self::HANDLE);
    201219    }
  • myd-delivery/trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/RestApi.php

    r3457167 r3460326  
    1010 * Registers WordPress REST API endpoints for the widget.
    1111 * Endpoints are under /wp-json/wpfeatureloop/v1/
     12 *
     13 * Routes are registered once and shared across all Client instances.
     14 * Each request includes a project_id query parameter so the handler
     15 * can resolve the correct Client instance from the registry.
    1216 */
    1317class RestApi
     
    1923
    2024    /**
    21      * Client instance
    22      */
    23     private Client $client;
    24 
    25     /**
    26      * Constructor
    27      */
    28     public function __construct(Client $client)
    29     {
    30         $this->client = $client;
     25     * Common arg definition for project_id (required on all routes)
     26     */
     27    private const PROJECT_ID_ARG = [
     28        'project_id' => [
     29            'required' => true,
     30            'type' => 'string',
     31            'sanitize_callback' => 'sanitize_text_field',
     32        ],
     33    ];
     34
     35    private function resolveClient(\WP_REST_Request $request): ?Client
     36    {
     37        return Client::getInstance($request->get_param('project_id'));
    3138    }
    3239
     
    4148            'callback' => [$this, 'getFeatures'],
    4249            'permission_callback' => '__return_true',
     50            'args' => self::PROJECT_ID_ARG,
    4351        ]);
    4452
     
    4856            'callback' => [$this, 'createFeature'],
    4957            'permission_callback' => [$this, 'canInteract'],
    50             'args' => [
     58            'args' => self::PROJECT_ID_ARG + [
    5159                'title' => [
    5260                    'required' => true,
     
    6876            'callback' => [$this, 'vote'],
    6977            'permission_callback' => [$this, 'canInteract'],
    70             'args' => [
     78            'args' => self::PROJECT_ID_ARG + [
    7179                'id' => [
    7280                    'required' => true,
     
    8896            'callback' => [$this, 'getComments'],
    8997            'permission_callback' => '__return_true',
    90             'args' => [
     98            'args' => self::PROJECT_ID_ARG + [
    9199                'id' => [
    92100                    'required' => true,
     
    102110            'callback' => [$this, 'addComment'],
    103111            'permission_callback' => [$this, 'canInteract'],
    104             'args' => [
     112            'args' => self::PROJECT_ID_ARG + [
    105113                'id' => [
    106114                    'required' => true,
     
    120128     * Permission callback - check if user can interact
    121129     */
    122     public function canInteract(): bool
    123     {
    124         return $this->client->canInteract();
     130    public function canInteract(\WP_REST_Request $request): bool
     131    {
     132        $client = $this->resolveClient($request);
     133
     134        if (!$client) {
     135            return false;
     136        }
     137
     138        return $client->canInteract();
    125139    }
    126140
     
    130144    public function getFeatures(\WP_REST_Request $request): \WP_REST_Response
    131145    {
     146        $client = $this->resolveClient($request);
     147
     148        if (!$client) {
     149            return new \WP_REST_Response([
     150                'error' => 'Unknown project ID',
     151            ], 400);
     152        }
     153
    132154        $args = [
    133155            'status' => $request->get_param('status'),
     
    139161        $args = array_filter($args, fn($v) => $v !== null);
    140162
    141         $result = $this->client->getFeatures($args);
     163        $result = $client->getFeatures($args);
    142164
    143165        if (is_wp_error($result)) {
     
    165187    public function createFeature(\WP_REST_Request $request): \WP_REST_Response
    166188    {
     189        $client = $this->resolveClient($request);
     190
     191        if (!$client) {
     192            return new \WP_REST_Response([
     193                'error' => 'Unknown project ID',
     194            ], 400);
     195        }
     196
    167197        $title = $request->get_param('title');
    168198        $description = $request->get_param('description') ?? '';
    169199
    170         $result = $this->client->createFeature($title, $description);
     200        $result = $client->createFeature($title, $description);
    171201
    172202        if (is_wp_error($result)) {
     
    186216    public function vote(\WP_REST_Request $request): \WP_REST_Response
    187217    {
     218        $client = $this->resolveClient($request);
     219
     220        if (!$client) {
     221            return new \WP_REST_Response([
     222                'error' => 'Unknown project ID',
     223            ], 400);
     224        }
     225
    188226        $featureId = $request->get_param('id');
    189227        $voteType = $request->get_param('vote'); // 'up', 'down', or 'none'
    190228
    191         $result = $this->client->vote($featureId, $voteType);
     229        $result = $client->vote($featureId, $voteType);
    192230
    193231        if (is_wp_error($result)) {
     
    207245    public function getComments(\WP_REST_Request $request): \WP_REST_Response
    208246    {
     247        $client = $this->resolveClient($request);
     248
     249        if (!$client) {
     250            return new \WP_REST_Response([
     251                'error' => 'Unknown project ID',
     252            ], 400);
     253        }
     254
    209255        $featureId = $request->get_param('id');
    210256
    211         $result = $this->client->getComments($featureId);
     257        $result = $client->getComments($featureId);
    212258
    213259        if (is_wp_error($result)) {
     
    232278    public function addComment(\WP_REST_Request $request): \WP_REST_Response
    233279    {
     280        $client = $this->resolveClient($request);
     281
     282        if (!$client) {
     283            return new \WP_REST_Response([
     284                'error' => 'Unknown project ID',
     285            ], 400);
     286        }
     287
    234288        $featureId = $request->get_param('id');
    235289        $text = $request->get_param('text');
    236290
    237         $result = $this->client->addComment($featureId, $text);
     291        $result = $client->addComment($featureId, $text);
    238292
    239293        if (is_wp_error($result)) {
  • myd-delivery/trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/Widget.php

    r3457167 r3460326  
    44
    55namespace WPFeatureLoop;
     6
     7use WPFeatureLoop\Client;
     8use WPFeatureLoop\RestApi;
    69
    710/**
     
    1013 * Renders the feature voting widget with the same UI/UX as the JS SDK.
    1114 * Uses template files instead of inline HTML strings.
     15 *
     16 * Everything (modals, templates, toast) is rendered inside the container div
     17 * so the JS can scope all queries to this.container without needing unique IDs.
    1218 */
    1319class Widget
     
    3238     */
    3339    private string $templatesPath;
     40
     41    /**
     42     * Whether inline styles have already been outputted (shared across instances)
     43     */
     44    private static bool $stylesOutputted = false;
    3445
    3546    /**
     
    128139
    129140    /**
     141     * Get inline styles
     142     *
     143     * Reads pre-minified CSS file and wraps in <style> tag.
     144     * Only outputs once even with multiple widget instances.
     145     */
     146    private function getInlineStyles(): string
     147    {
     148        if (self::$stylesOutputted) {
     149            return '';
     150        }
     151
     152        self::$stylesOutputted = true;
     153
     154        $cssFile = dirname(__DIR__) . '/assets/css/wpfeatureloop.min.css';
     155
     156        if (!file_exists($cssFile)) {
     157            return '';
     158        }
     159
     160        return '<style>' . file_get_contents($cssFile) . '</style>';
     161    }
     162
     163    /**
    130164     * Render a template file with variables
    131165     */
     
    147181
    148182    /**
    149      * Render the complete widget (container + modals + templates + scripts)
     183     * Render the complete widget
     184     *
     185     * Everything (header, skeleton, modals, templates, toast) is inside
     186     * a single container div so JS can scope all queries.
    150187     *
    151188     * @return string HTML
     
    153190    public function render(): string
    154191    {
    155         // Enqueue assets only when widget is rendered
     192        // Enqueue JS only when widget is rendered
    156193        $this->client->enqueueAssets();
    157194
    158195        $html = '';
    159196
    160         // Main container with skeleton
    161         $html .= $this->renderTemplate('widget', [
    162             'container_id' => self::CONTAINER_ID,
     197        // Inline CSS before container to avoid FOUC
     198        $html .= $this->getInlineStyles();
     199
     200        // Open container with config as data attribute (JS reads it for auto-init)
     201        $html .= sprintf(
     202            '<div id="%s" class="wfl-container" data-loading="true" data-config="%s">',
     203            esc_attr(self::CONTAINER_ID),
     204            esc_attr(wp_json_encode($this->getJsConfig()))
     205        );
     206
     207        // Header + skeleton (visible content)
     208        $html .= $this->renderTemplate('widget-inner', [
    163209            'title' => $this->t('title'),
    164210            'subtitle' => $this->t('subtitle'),
     
    195241        ]);
    196242
    197         // JS Config
    198         $config = wp_json_encode($this->getJsConfig());
    199         $html .= sprintf('<script>window.wpfeatureloop_config = %s;</script>', $config);
     243        // Close container
     244        $html .= '</div>';
    200245
    201246        return $html;
     
    210255    {
    211256        return [
    212             'container_id' => self::CONTAINER_ID,
     257            'project_id' => $this->client->getProjectId(),
    213258            'rest_url' => rest_url(RestApi::NAMESPACE),
    214259            'nonce' => wp_create_nonce('wp_rest'),
Note: See TracChangeset for help on using the changeset viewer.