Changeset 3460326
- Timestamp:
- 02/12/2026 08:38:05 PM (8 days ago)
- Location:
- myd-delivery
- Files:
-
- 6 added
- 8 deleted
- 26 edited
- 1 copied
-
tags/1.4.3 (copied) (copied from myd-delivery/trunk)
-
tags/1.4.3/README.txt (modified) (2 diffs)
-
tags/1.4.3/composer.json (modified) (1 diff)
-
tags/1.4.3/composer.lock (modified) (2 diffs)
-
tags/1.4.3/myd-delivery.php (modified) (3 diffs)
-
tags/1.4.3/templates/admin/dashboard.php (modified) (2 diffs)
-
tags/1.4.3/vendor/autoload.php (modified) (1 diff)
-
tags/1.4.3/vendor/composer/autoload_real.php (modified) (2 diffs)
-
tags/1.4.3/vendor/composer/autoload_static.php (modified) (2 diffs)
-
tags/1.4.3/vendor/composer/installed.json (modified) (2 diffs)
-
tags/1.4.3/vendor/composer/installed.php (modified) (3 diffs)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/assets/css/wpfeatureloop.css (deleted)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/assets/css/wpfeatureloop.min.css (added)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/assets/js/wpfeatureloop.js (deleted)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/assets/js/wpfeatureloop.min.js (added)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/phpcs.xml (deleted)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/Client.php (modified) (11 diffs)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/RestApi.php (modified) (14 diffs)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/Widget.php (modified) (8 diffs)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/templates/widget-inner.php (added)
-
tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/templates/widget.php (deleted)
-
trunk/README.txt (modified) (2 diffs)
-
trunk/composer.json (modified) (1 diff)
-
trunk/composer.lock (modified) (2 diffs)
-
trunk/myd-delivery.php (modified) (3 diffs)
-
trunk/templates/admin/dashboard.php (modified) (2 diffs)
-
trunk/vendor/autoload.php (modified) (1 diff)
-
trunk/vendor/composer/autoload_real.php (modified) (2 diffs)
-
trunk/vendor/composer/autoload_static.php (modified) (2 diffs)
-
trunk/vendor/composer/installed.json (modified) (2 diffs)
-
trunk/vendor/composer/installed.php (modified) (3 diffs)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/assets/css/wpfeatureloop.css (deleted)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/assets/css/wpfeatureloop.min.css (added)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/assets/js/wpfeatureloop.js (deleted)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/assets/js/wpfeatureloop.min.js (added)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/phpcs.xml (deleted)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/Client.php (modified) (11 diffs)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/RestApi.php (modified) (14 diffs)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/Widget.php (modified) (8 diffs)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/templates/widget-inner.php (added)
-
trunk/vendor/eduardovillao/wpfeatureloop-sdk/templates/widget.php (deleted)
Legend:
- Unmodified
- Added
- Removed
-
myd-delivery/tags/1.4.3/README.txt
r3457167 r3460326 5 5 Requires at least: 5.5 6 6 Tested up to: 6.9 7 Stable tag: 1.4. 27 Stable tag: 1.4.3 8 8 Requires PHP: 7.4 9 9 License: GPL-3.0+ … … 76 76 == Changelog == 77 77 78 = 1.4.3 = 79 * Changed: Code improvements. 80 78 81 = 1.4.2 = 79 82 * Changed: Code improvements. -
myd-delivery/tags/1.4.3/composer.json
r3457167 r3460326 20 20 }, 21 21 "require": { 22 "eduardovillao/wpfeatureloop-sdk": " ^1.0"22 "eduardovillao/wpfeatureloop-sdk": "*" 23 23 } 24 24 } -
myd-delivery/tags/1.4.3/composer.lock
r3457167 r3460326 5 5 "This file is @generated automatically" 6 6 ], 7 "content-hash": " 476065f09a8ec63d9d036fcc3ee6d61d",7 "content-hash": "20f28567c3f8a6f3ae2a3916cd2070ba", 8 8 "packages": [ 9 9 { 10 10 "name": "eduardovillao/wpfeatureloop-sdk", 11 "version": "v1. 0.0",11 "version": "v1.2.0", 12 12 "source": { 13 13 "type": "git", 14 14 "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", 21 21 "shasum": "" 22 22 }, … … 48 48 "source": "https://github.com/eduardovillao/wpfeatureloop-php-sdk" 49 49 }, 50 "time": "2026-02- 07T00:06:10+00:00"50 "time": "2026-02-12T20:04:34+00:00" 51 51 } 52 52 ], -
myd-delivery/tags/1.4.3/myd-delivery.php
r3457167 r3460326 6 6 * Author: EduardoVillao.me 7 7 * Author URI: https://eduardovillao.me/ 8 * Version: 1.4. 28 * Version: 1.4.3 9 9 * Requires PHP: 7.4 10 10 * Requires at least: 5.5 … … 26 26 define( 'MYDDELIVERY_BASENAME', plugin_basename( __FILE__ ) ); 27 27 define( 'MYDDELIVERY_DIRNAME', plugin_basename( __DIR__ ) ); 28 define( 'MYDDELIVERY_VERSION', '1.4. 2' );28 define( 'MYDDELIVERY_VERSION', '1.4.3' ); 29 29 define( 'MYDDELIVERY_MIN_PHP_VERSION', '7.4' ); 30 30 define( 'MYDDELIVERY_MIN_WP_VERSION', '5.5' ); … … 52 52 53 53 require_once __DIR__ . '/vendor/autoload.php'; 54 WPFeatureLoop\Client::init(54 \WPFeatureLoop\Client::init( 55 55 'pk_live_783e198c8177013cc256635034cd22ba', 56 56 'cml8xc9n7000004l4ofl5tovv', -
myd-delivery/tags/1.4.3/templates/admin/dashboard.php
r3457153 r3460326 1 1 <?php 2 3 use WPFeatureLoop\Client;4 2 5 3 if ( ! defined( 'ABSPATH' ) ) { … … 122 120 <?php if ( \current_user_can( 'manage_options' ) ) : ?> 123 121 <div class="mydd-admin-card"> 124 <?php echo Client::renderWidget(); ?>122 <?php echo \WPFeatureLoop\Client::getInstance('cml8xc9n7000004l4ofl5tovv')->renderWidget(); ?> 125 123 </div> 126 124 <?php endif; ?> -
myd-delivery/tags/1.4.3/vendor/autoload.php
r3457167 r3460326 20 20 require_once __DIR__ . '/composer/autoload_real.php'; 21 21 22 return ComposerAutoloaderInit 476065f09a8ec63d9d036fcc3ee6d61d::getLoader();22 return ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba::getLoader(); -
myd-delivery/tags/1.4.3/vendor/composer/autoload_real.php
r3457167 r3460326 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit 476065f09a8ec63d9d036fcc3ee6d61d5 class ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba 6 6 { 7 7 private static $loader; … … 25 25 require __DIR__ . '/platform_check.php'; 26 26 27 spl_autoload_register(array('ComposerAutoloaderInit 476065f09a8ec63d9d036fcc3ee6d61d', 'loadClassLoader'), true, true);27 spl_autoload_register(array('ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba', 'loadClassLoader'), true, true); 28 28 self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); 29 spl_autoload_unregister(array('ComposerAutoloaderInit 476065f09a8ec63d9d036fcc3ee6d61d', 'loadClassLoader'));29 spl_autoload_unregister(array('ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba', 'loadClassLoader')); 30 30 31 31 require __DIR__ . '/autoload_static.php'; 32 call_user_func(\Composer\Autoload\ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d::getInitializer($loader));32 call_user_func(\Composer\Autoload\ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::getInitializer($loader)); 33 33 34 34 $loader->register(true); -
myd-delivery/tags/1.4.3/vendor/composer/autoload_static.php
r3457167 r3460326 5 5 namespace Composer\Autoload; 6 6 7 class ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d7 class ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba 8 8 { 9 9 public static $prefixLengthsPsr4 = array ( … … 33 33 { 34 34 return \Closure::bind(function () use ($loader) { 35 $loader->prefixLengthsPsr4 = ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d::$prefixLengthsPsr4;36 $loader->prefixDirsPsr4 = ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d::$prefixDirsPsr4;37 $loader->classMap = ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d::$classMap;35 $loader->prefixLengthsPsr4 = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$prefixLengthsPsr4; 36 $loader->prefixDirsPsr4 = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$prefixDirsPsr4; 37 $loader->classMap = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$classMap; 38 38 39 39 }, null, ClassLoader::class); -
myd-delivery/tags/1.4.3/vendor/composer/installed.json
r3457167 r3460326 3 3 { 4 4 "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", 7 7 "source": { 8 8 "type": "git", 9 9 "url": "https://github.com/eduardovillao/wpfeatureloop-php-sdk.git", 10 "reference": " 465bf8d36a50458d1bcaf944988fe6efa9f59f06"10 "reference": "923fbe0d40ba0a8756feb374932271e470518cc6" 11 11 }, 12 12 "dist": { 13 13 "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", 16 16 "shasum": "" 17 17 }, … … 22 22 "squizlabs/php_codesniffer": "^3.12" 23 23 }, 24 "time": "2026-02- 07T00:06:10+00:00",24 "time": "2026-02-12T20:04:34+00:00", 25 25 "type": "library", 26 26 "installation-source": "dist", -
myd-delivery/tags/1.4.3/vendor/composer/installed.php
r3457167 r3460326 2 2 'root' => array( 3 3 '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', 7 7 'type' => 'project', 8 8 'install_path' => __DIR__ . '/../../', … … 12 12 'versions' => array( 13 13 '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', 17 17 'type' => 'project', 18 18 'install_path' => __DIR__ . '/../../', … … 21 21 ), 22 22 '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', 26 26 'type' => 'library', 27 27 'install_path' => __DIR__ . '/../eduardovillao/wpfeatureloop-sdk', -
myd-delivery/tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/Client.php
r3457167 r3460326 4 4 5 5 namespace WPFeatureLoop; 6 7 use WPFeatureLoop\Widget; 8 use WPFeatureLoop\RestApi; 9 use WPFeatureLoop\Api; 10 use WPFeatureLoop\User; 6 11 7 12 /** … … 9 14 * 10 15 * 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: 13 19 * ```php 14 20 * use WPFeatureLoop\Client; 15 21 * 16 * // Initialize client early (registers REST API routes)17 22 * Client::init('pk_live_xxx', 'project_id'); 18 23 * ``` … … 22 27 * use WPFeatureLoop\Client; 23 28 * 24 * // Render widget (that's it!) 25 * echo Client::renderWidget(['locale' => 'en']); 29 * echo Client::renderWidget(); 26 30 * ``` 27 31 */ … … 31 35 * SDK Version (used for cache busting) 32 36 */ 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) 37 41 */ 38 42 public const HANDLE = 'wpfeatureloop'; 39 43 40 44 /** 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; 44 55 45 56 /** … … 49 60 50 61 /** 51 * REST API handler52 */ 53 private RestApi $restApi;62 * Project ID 63 */ 64 private string $projectId; 54 65 55 66 /** … … 69 80 70 81 /** 71 * Initialize the client ( singleton pattern)82 * Initialize the client (registry pattern — one instance per projectId) 72 83 * 73 84 * Call this in your main plugin file so REST API routes are registered on every request. … … 80 91 public static function init(string $publicKey, string $projectId, array $options = []): Client 81 92 { 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) 92 104 * @return Client|null Returns null if not initialized 93 105 */ 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); 111 124 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; 112 135 } 113 136 … … 136 159 $this->capability = $options['capability'] ?? 'read'; 137 160 $this->language = $options['language'] ?? 'en'; 161 $this->projectId = $projectId; 138 162 139 163 // Assets URL based on SDK location … … 141 165 142 166 $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 } 147 174 148 175 // Register assets (admin only for now) … … 163 190 164 191 /** 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. 168 195 */ 169 196 public function registerAssets(): void 170 197 { 171 wp_register_style(172 self::HANDLE,173 $this->assetsUrl . '/css/wpfeatureloop.css',174 [],175 self::VERSION176 );177 178 198 wp_register_script( 179 199 self::HANDLE, 180 $this->assetsUrl . '/js/wpfeatureloop. js',200 $this->assetsUrl . '/js/wpfeatureloop.min.js', 181 201 [], 182 202 self::VERSION, … … 186 206 187 207 /** 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(). 191 211 */ 192 212 public function enqueueAssets(): void 193 213 { 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')) { 196 215 $this->registerAssets(); 197 216 } 198 217 199 wp_enqueue_style(self::HANDLE);200 218 wp_enqueue_script(self::HANDLE); 201 219 } -
myd-delivery/tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/RestApi.php
r3457167 r3460326 10 10 * Registers WordPress REST API endpoints for the widget. 11 11 * 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. 12 16 */ 13 17 class RestApi … … 19 23 20 24 /** 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')); 31 38 } 32 39 … … 41 48 'callback' => [$this, 'getFeatures'], 42 49 'permission_callback' => '__return_true', 50 'args' => self::PROJECT_ID_ARG, 43 51 ]); 44 52 … … 48 56 'callback' => [$this, 'createFeature'], 49 57 'permission_callback' => [$this, 'canInteract'], 50 'args' => [58 'args' => self::PROJECT_ID_ARG + [ 51 59 'title' => [ 52 60 'required' => true, … … 68 76 'callback' => [$this, 'vote'], 69 77 'permission_callback' => [$this, 'canInteract'], 70 'args' => [78 'args' => self::PROJECT_ID_ARG + [ 71 79 'id' => [ 72 80 'required' => true, … … 88 96 'callback' => [$this, 'getComments'], 89 97 'permission_callback' => '__return_true', 90 'args' => [98 'args' => self::PROJECT_ID_ARG + [ 91 99 'id' => [ 92 100 'required' => true, … … 102 110 'callback' => [$this, 'addComment'], 103 111 'permission_callback' => [$this, 'canInteract'], 104 'args' => [112 'args' => self::PROJECT_ID_ARG + [ 105 113 'id' => [ 106 114 'required' => true, … … 120 128 * Permission callback - check if user can interact 121 129 */ 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(); 125 139 } 126 140 … … 130 144 public function getFeatures(\WP_REST_Request $request): \WP_REST_Response 131 145 { 146 $client = $this->resolveClient($request); 147 148 if (!$client) { 149 return new \WP_REST_Response([ 150 'error' => 'Unknown project ID', 151 ], 400); 152 } 153 132 154 $args = [ 133 155 'status' => $request->get_param('status'), … … 139 161 $args = array_filter($args, fn($v) => $v !== null); 140 162 141 $result = $ this->client->getFeatures($args);163 $result = $client->getFeatures($args); 142 164 143 165 if (is_wp_error($result)) { … … 165 187 public function createFeature(\WP_REST_Request $request): \WP_REST_Response 166 188 { 189 $client = $this->resolveClient($request); 190 191 if (!$client) { 192 return new \WP_REST_Response([ 193 'error' => 'Unknown project ID', 194 ], 400); 195 } 196 167 197 $title = $request->get_param('title'); 168 198 $description = $request->get_param('description') ?? ''; 169 199 170 $result = $ this->client->createFeature($title, $description);200 $result = $client->createFeature($title, $description); 171 201 172 202 if (is_wp_error($result)) { … … 186 216 public function vote(\WP_REST_Request $request): \WP_REST_Response 187 217 { 218 $client = $this->resolveClient($request); 219 220 if (!$client) { 221 return new \WP_REST_Response([ 222 'error' => 'Unknown project ID', 223 ], 400); 224 } 225 188 226 $featureId = $request->get_param('id'); 189 227 $voteType = $request->get_param('vote'); // 'up', 'down', or 'none' 190 228 191 $result = $ this->client->vote($featureId, $voteType);229 $result = $client->vote($featureId, $voteType); 192 230 193 231 if (is_wp_error($result)) { … … 207 245 public function getComments(\WP_REST_Request $request): \WP_REST_Response 208 246 { 247 $client = $this->resolveClient($request); 248 249 if (!$client) { 250 return new \WP_REST_Response([ 251 'error' => 'Unknown project ID', 252 ], 400); 253 } 254 209 255 $featureId = $request->get_param('id'); 210 256 211 $result = $ this->client->getComments($featureId);257 $result = $client->getComments($featureId); 212 258 213 259 if (is_wp_error($result)) { … … 232 278 public function addComment(\WP_REST_Request $request): \WP_REST_Response 233 279 { 280 $client = $this->resolveClient($request); 281 282 if (!$client) { 283 return new \WP_REST_Response([ 284 'error' => 'Unknown project ID', 285 ], 400); 286 } 287 234 288 $featureId = $request->get_param('id'); 235 289 $text = $request->get_param('text'); 236 290 237 $result = $ this->client->addComment($featureId, $text);291 $result = $client->addComment($featureId, $text); 238 292 239 293 if (is_wp_error($result)) { -
myd-delivery/tags/1.4.3/vendor/eduardovillao/wpfeatureloop-sdk/src/Widget.php
r3457167 r3460326 4 4 5 5 namespace WPFeatureLoop; 6 7 use WPFeatureLoop\Client; 8 use WPFeatureLoop\RestApi; 6 9 7 10 /** … … 10 13 * Renders the feature voting widget with the same UI/UX as the JS SDK. 11 14 * 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. 12 18 */ 13 19 class Widget … … 32 38 */ 33 39 private string $templatesPath; 40 41 /** 42 * Whether inline styles have already been outputted (shared across instances) 43 */ 44 private static bool $stylesOutputted = false; 34 45 35 46 /** … … 128 139 129 140 /** 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 /** 130 164 * Render a template file with variables 131 165 */ … … 147 181 148 182 /** 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. 150 187 * 151 188 * @return string HTML … … 153 190 public function render(): string 154 191 { 155 // Enqueue assetsonly when widget is rendered192 // Enqueue JS only when widget is rendered 156 193 $this->client->enqueueAssets(); 157 194 158 195 $html = ''; 159 196 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', [ 163 209 'title' => $this->t('title'), 164 210 'subtitle' => $this->t('subtitle'), … … 195 241 ]); 196 242 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>'; 200 245 201 246 return $html; … … 210 255 { 211 256 return [ 212 ' container_id' => self::CONTAINER_ID,257 'project_id' => $this->client->getProjectId(), 213 258 'rest_url' => rest_url(RestApi::NAMESPACE), 214 259 'nonce' => wp_create_nonce('wp_rest'), -
myd-delivery/trunk/README.txt
r3457167 r3460326 5 5 Requires at least: 5.5 6 6 Tested up to: 6.9 7 Stable tag: 1.4. 27 Stable tag: 1.4.3 8 8 Requires PHP: 7.4 9 9 License: GPL-3.0+ … … 76 76 == Changelog == 77 77 78 = 1.4.3 = 79 * Changed: Code improvements. 80 78 81 = 1.4.2 = 79 82 * Changed: Code improvements. -
myd-delivery/trunk/composer.json
r3457167 r3460326 20 20 }, 21 21 "require": { 22 "eduardovillao/wpfeatureloop-sdk": " ^1.0"22 "eduardovillao/wpfeatureloop-sdk": "*" 23 23 } 24 24 } -
myd-delivery/trunk/composer.lock
r3457167 r3460326 5 5 "This file is @generated automatically" 6 6 ], 7 "content-hash": " 476065f09a8ec63d9d036fcc3ee6d61d",7 "content-hash": "20f28567c3f8a6f3ae2a3916cd2070ba", 8 8 "packages": [ 9 9 { 10 10 "name": "eduardovillao/wpfeatureloop-sdk", 11 "version": "v1. 0.0",11 "version": "v1.2.0", 12 12 "source": { 13 13 "type": "git", 14 14 "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", 21 21 "shasum": "" 22 22 }, … … 48 48 "source": "https://github.com/eduardovillao/wpfeatureloop-php-sdk" 49 49 }, 50 "time": "2026-02- 07T00:06:10+00:00"50 "time": "2026-02-12T20:04:34+00:00" 51 51 } 52 52 ], -
myd-delivery/trunk/myd-delivery.php
r3457167 r3460326 6 6 * Author: EduardoVillao.me 7 7 * Author URI: https://eduardovillao.me/ 8 * Version: 1.4. 28 * Version: 1.4.3 9 9 * Requires PHP: 7.4 10 10 * Requires at least: 5.5 … … 26 26 define( 'MYDDELIVERY_BASENAME', plugin_basename( __FILE__ ) ); 27 27 define( 'MYDDELIVERY_DIRNAME', plugin_basename( __DIR__ ) ); 28 define( 'MYDDELIVERY_VERSION', '1.4. 2' );28 define( 'MYDDELIVERY_VERSION', '1.4.3' ); 29 29 define( 'MYDDELIVERY_MIN_PHP_VERSION', '7.4' ); 30 30 define( 'MYDDELIVERY_MIN_WP_VERSION', '5.5' ); … … 52 52 53 53 require_once __DIR__ . '/vendor/autoload.php'; 54 WPFeatureLoop\Client::init(54 \WPFeatureLoop\Client::init( 55 55 'pk_live_783e198c8177013cc256635034cd22ba', 56 56 'cml8xc9n7000004l4ofl5tovv', -
myd-delivery/trunk/templates/admin/dashboard.php
r3457153 r3460326 1 1 <?php 2 3 use WPFeatureLoop\Client;4 2 5 3 if ( ! defined( 'ABSPATH' ) ) { … … 122 120 <?php if ( \current_user_can( 'manage_options' ) ) : ?> 123 121 <div class="mydd-admin-card"> 124 <?php echo Client::renderWidget(); ?>122 <?php echo \WPFeatureLoop\Client::getInstance('cml8xc9n7000004l4ofl5tovv')->renderWidget(); ?> 125 123 </div> 126 124 <?php endif; ?> -
myd-delivery/trunk/vendor/autoload.php
r3457167 r3460326 20 20 require_once __DIR__ . '/composer/autoload_real.php'; 21 21 22 return ComposerAutoloaderInit 476065f09a8ec63d9d036fcc3ee6d61d::getLoader();22 return ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba::getLoader(); -
myd-delivery/trunk/vendor/composer/autoload_real.php
r3457167 r3460326 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit 476065f09a8ec63d9d036fcc3ee6d61d5 class ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba 6 6 { 7 7 private static $loader; … … 25 25 require __DIR__ . '/platform_check.php'; 26 26 27 spl_autoload_register(array('ComposerAutoloaderInit 476065f09a8ec63d9d036fcc3ee6d61d', 'loadClassLoader'), true, true);27 spl_autoload_register(array('ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba', 'loadClassLoader'), true, true); 28 28 self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); 29 spl_autoload_unregister(array('ComposerAutoloaderInit 476065f09a8ec63d9d036fcc3ee6d61d', 'loadClassLoader'));29 spl_autoload_unregister(array('ComposerAutoloaderInit20f28567c3f8a6f3ae2a3916cd2070ba', 'loadClassLoader')); 30 30 31 31 require __DIR__ . '/autoload_static.php'; 32 call_user_func(\Composer\Autoload\ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d::getInitializer($loader));32 call_user_func(\Composer\Autoload\ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::getInitializer($loader)); 33 33 34 34 $loader->register(true); -
myd-delivery/trunk/vendor/composer/autoload_static.php
r3457167 r3460326 5 5 namespace Composer\Autoload; 6 6 7 class ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d7 class ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba 8 8 { 9 9 public static $prefixLengthsPsr4 = array ( … … 33 33 { 34 34 return \Closure::bind(function () use ($loader) { 35 $loader->prefixLengthsPsr4 = ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d::$prefixLengthsPsr4;36 $loader->prefixDirsPsr4 = ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d::$prefixDirsPsr4;37 $loader->classMap = ComposerStaticInit 476065f09a8ec63d9d036fcc3ee6d61d::$classMap;35 $loader->prefixLengthsPsr4 = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$prefixLengthsPsr4; 36 $loader->prefixDirsPsr4 = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$prefixDirsPsr4; 37 $loader->classMap = ComposerStaticInit20f28567c3f8a6f3ae2a3916cd2070ba::$classMap; 38 38 39 39 }, null, ClassLoader::class); -
myd-delivery/trunk/vendor/composer/installed.json
r3457167 r3460326 3 3 { 4 4 "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", 7 7 "source": { 8 8 "type": "git", 9 9 "url": "https://github.com/eduardovillao/wpfeatureloop-php-sdk.git", 10 "reference": " 465bf8d36a50458d1bcaf944988fe6efa9f59f06"10 "reference": "923fbe0d40ba0a8756feb374932271e470518cc6" 11 11 }, 12 12 "dist": { 13 13 "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", 16 16 "shasum": "" 17 17 }, … … 22 22 "squizlabs/php_codesniffer": "^3.12" 23 23 }, 24 "time": "2026-02- 07T00:06:10+00:00",24 "time": "2026-02-12T20:04:34+00:00", 25 25 "type": "library", 26 26 "installation-source": "dist", -
myd-delivery/trunk/vendor/composer/installed.php
r3457167 r3460326 2 2 'root' => array( 3 3 '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', 7 7 'type' => 'project', 8 8 'install_path' => __DIR__ . '/../../', … … 12 12 'versions' => array( 13 13 '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', 17 17 'type' => 'project', 18 18 'install_path' => __DIR__ . '/../../', … … 21 21 ), 22 22 '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', 26 26 'type' => 'library', 27 27 'install_path' => __DIR__ . '/../eduardovillao/wpfeatureloop-sdk', -
myd-delivery/trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/Client.php
r3457167 r3460326 4 4 5 5 namespace WPFeatureLoop; 6 7 use WPFeatureLoop\Widget; 8 use WPFeatureLoop\RestApi; 9 use WPFeatureLoop\Api; 10 use WPFeatureLoop\User; 6 11 7 12 /** … … 9 14 * 10 15 * 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: 13 19 * ```php 14 20 * use WPFeatureLoop\Client; 15 21 * 16 * // Initialize client early (registers REST API routes)17 22 * Client::init('pk_live_xxx', 'project_id'); 18 23 * ``` … … 22 27 * use WPFeatureLoop\Client; 23 28 * 24 * // Render widget (that's it!) 25 * echo Client::renderWidget(['locale' => 'en']); 29 * echo Client::renderWidget(); 26 30 * ``` 27 31 */ … … 31 35 * SDK Version (used for cache busting) 32 36 */ 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) 37 41 */ 38 42 public const HANDLE = 'wpfeatureloop'; 39 43 40 44 /** 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; 44 55 45 56 /** … … 49 60 50 61 /** 51 * REST API handler52 */ 53 private RestApi $restApi;62 * Project ID 63 */ 64 private string $projectId; 54 65 55 66 /** … … 69 80 70 81 /** 71 * Initialize the client ( singleton pattern)82 * Initialize the client (registry pattern — one instance per projectId) 72 83 * 73 84 * Call this in your main plugin file so REST API routes are registered on every request. … … 80 91 public static function init(string $publicKey, string $projectId, array $options = []): Client 81 92 { 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) 92 104 * @return Client|null Returns null if not initialized 93 105 */ 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); 111 124 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; 112 135 } 113 136 … … 136 159 $this->capability = $options['capability'] ?? 'read'; 137 160 $this->language = $options['language'] ?? 'en'; 161 $this->projectId = $projectId; 138 162 139 163 // Assets URL based on SDK location … … 141 165 142 166 $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 } 147 174 148 175 // Register assets (admin only for now) … … 163 190 164 191 /** 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. 168 195 */ 169 196 public function registerAssets(): void 170 197 { 171 wp_register_style(172 self::HANDLE,173 $this->assetsUrl . '/css/wpfeatureloop.css',174 [],175 self::VERSION176 );177 178 198 wp_register_script( 179 199 self::HANDLE, 180 $this->assetsUrl . '/js/wpfeatureloop. js',200 $this->assetsUrl . '/js/wpfeatureloop.min.js', 181 201 [], 182 202 self::VERSION, … … 186 206 187 207 /** 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(). 191 211 */ 192 212 public function enqueueAssets(): void 193 213 { 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')) { 196 215 $this->registerAssets(); 197 216 } 198 217 199 wp_enqueue_style(self::HANDLE);200 218 wp_enqueue_script(self::HANDLE); 201 219 } -
myd-delivery/trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/RestApi.php
r3457167 r3460326 10 10 * Registers WordPress REST API endpoints for the widget. 11 11 * 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. 12 16 */ 13 17 class RestApi … … 19 23 20 24 /** 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')); 31 38 } 32 39 … … 41 48 'callback' => [$this, 'getFeatures'], 42 49 'permission_callback' => '__return_true', 50 'args' => self::PROJECT_ID_ARG, 43 51 ]); 44 52 … … 48 56 'callback' => [$this, 'createFeature'], 49 57 'permission_callback' => [$this, 'canInteract'], 50 'args' => [58 'args' => self::PROJECT_ID_ARG + [ 51 59 'title' => [ 52 60 'required' => true, … … 68 76 'callback' => [$this, 'vote'], 69 77 'permission_callback' => [$this, 'canInteract'], 70 'args' => [78 'args' => self::PROJECT_ID_ARG + [ 71 79 'id' => [ 72 80 'required' => true, … … 88 96 'callback' => [$this, 'getComments'], 89 97 'permission_callback' => '__return_true', 90 'args' => [98 'args' => self::PROJECT_ID_ARG + [ 91 99 'id' => [ 92 100 'required' => true, … … 102 110 'callback' => [$this, 'addComment'], 103 111 'permission_callback' => [$this, 'canInteract'], 104 'args' => [112 'args' => self::PROJECT_ID_ARG + [ 105 113 'id' => [ 106 114 'required' => true, … … 120 128 * Permission callback - check if user can interact 121 129 */ 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(); 125 139 } 126 140 … … 130 144 public function getFeatures(\WP_REST_Request $request): \WP_REST_Response 131 145 { 146 $client = $this->resolveClient($request); 147 148 if (!$client) { 149 return new \WP_REST_Response([ 150 'error' => 'Unknown project ID', 151 ], 400); 152 } 153 132 154 $args = [ 133 155 'status' => $request->get_param('status'), … … 139 161 $args = array_filter($args, fn($v) => $v !== null); 140 162 141 $result = $ this->client->getFeatures($args);163 $result = $client->getFeatures($args); 142 164 143 165 if (is_wp_error($result)) { … … 165 187 public function createFeature(\WP_REST_Request $request): \WP_REST_Response 166 188 { 189 $client = $this->resolveClient($request); 190 191 if (!$client) { 192 return new \WP_REST_Response([ 193 'error' => 'Unknown project ID', 194 ], 400); 195 } 196 167 197 $title = $request->get_param('title'); 168 198 $description = $request->get_param('description') ?? ''; 169 199 170 $result = $ this->client->createFeature($title, $description);200 $result = $client->createFeature($title, $description); 171 201 172 202 if (is_wp_error($result)) { … … 186 216 public function vote(\WP_REST_Request $request): \WP_REST_Response 187 217 { 218 $client = $this->resolveClient($request); 219 220 if (!$client) { 221 return new \WP_REST_Response([ 222 'error' => 'Unknown project ID', 223 ], 400); 224 } 225 188 226 $featureId = $request->get_param('id'); 189 227 $voteType = $request->get_param('vote'); // 'up', 'down', or 'none' 190 228 191 $result = $ this->client->vote($featureId, $voteType);229 $result = $client->vote($featureId, $voteType); 192 230 193 231 if (is_wp_error($result)) { … … 207 245 public function getComments(\WP_REST_Request $request): \WP_REST_Response 208 246 { 247 $client = $this->resolveClient($request); 248 249 if (!$client) { 250 return new \WP_REST_Response([ 251 'error' => 'Unknown project ID', 252 ], 400); 253 } 254 209 255 $featureId = $request->get_param('id'); 210 256 211 $result = $ this->client->getComments($featureId);257 $result = $client->getComments($featureId); 212 258 213 259 if (is_wp_error($result)) { … … 232 278 public function addComment(\WP_REST_Request $request): \WP_REST_Response 233 279 { 280 $client = $this->resolveClient($request); 281 282 if (!$client) { 283 return new \WP_REST_Response([ 284 'error' => 'Unknown project ID', 285 ], 400); 286 } 287 234 288 $featureId = $request->get_param('id'); 235 289 $text = $request->get_param('text'); 236 290 237 $result = $ this->client->addComment($featureId, $text);291 $result = $client->addComment($featureId, $text); 238 292 239 293 if (is_wp_error($result)) { -
myd-delivery/trunk/vendor/eduardovillao/wpfeatureloop-sdk/src/Widget.php
r3457167 r3460326 4 4 5 5 namespace WPFeatureLoop; 6 7 use WPFeatureLoop\Client; 8 use WPFeatureLoop\RestApi; 6 9 7 10 /** … … 10 13 * Renders the feature voting widget with the same UI/UX as the JS SDK. 11 14 * 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. 12 18 */ 13 19 class Widget … … 32 38 */ 33 39 private string $templatesPath; 40 41 /** 42 * Whether inline styles have already been outputted (shared across instances) 43 */ 44 private static bool $stylesOutputted = false; 34 45 35 46 /** … … 128 139 129 140 /** 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 /** 130 164 * Render a template file with variables 131 165 */ … … 147 181 148 182 /** 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. 150 187 * 151 188 * @return string HTML … … 153 190 public function render(): string 154 191 { 155 // Enqueue assetsonly when widget is rendered192 // Enqueue JS only when widget is rendered 156 193 $this->client->enqueueAssets(); 157 194 158 195 $html = ''; 159 196 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', [ 163 209 'title' => $this->t('title'), 164 210 'subtitle' => $this->t('subtitle'), … … 195 241 ]); 196 242 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>'; 200 245 201 246 return $html; … … 210 255 { 211 256 return [ 212 ' container_id' => self::CONTAINER_ID,257 'project_id' => $this->client->getProjectId(), 213 258 'rest_url' => rest_url(RestApi::NAMESPACE), 214 259 'nonce' => wp_create_nonce('wp_rest'),
Note: See TracChangeset
for help on using the changeset viewer.