PluginProbe ʕ •ᴥ•ʔ
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More / 4.2.0
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More v4.2.0
4.2.0 4.1.0 4.0.9 4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 trunk 1.0.0 2.0.0 2.0.1 2.0.2 2.0.3 3.0 3.0.1 3.0.2 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.2.0 3.2.1 3.2.2 3.2.4 3.2.5 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.4.5 3.4.6 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1
superb-blocks / src / gutenberg / class-gutenberg-controller.php
superb-blocks / src / gutenberg Last commit date
block-api 3 weeks ago form 2 days ago import 3 weeks ago popup 3 weeks ago templates 3 weeks ago class-gutenberg-block-styles.php 3 weeks ago class-gutenberg-cache-util.php 3 weeks ago class-gutenberg-conditions-controller.php 3 weeks ago class-gutenberg-controller.php 2 days ago class-gutenberg-dynamic-content-controller.php 3 weeks ago class-gutenberg-enhancements-controller.php 2 weeks ago class-gutenberg-social-icons-controller.php 3 weeks ago class-gutenberg-sticky-controller.php 3 weeks ago class-gutenberg-z-index-controller.php 3 weeks ago
class-gutenberg-controller.php
721 lines
1 <?php
2
3 namespace SuperbAddons\Gutenberg\Controllers;
4
5 defined('ABSPATH') || exit();
6
7 use SuperbAddons\Admin\Controllers\SettingsController;
8 use SuperbAddons\Admin\Controllers\Wizard\WizardController;
9 use SuperbAddons\Admin\Controllers\Wizard\WizardRestorationPointController;
10 use SuperbAddons\Admin\Utils\AdminLinkSource;
11 use SuperbAddons\Admin\Utils\AdminLinkUtil;
12 use SuperbAddons\Commerce\CommerceController;
13 use SuperbAddons\Data\Controllers\CompatibilitySettingsOptionKey;
14 use SuperbAddons\Data\Controllers\KeyController;
15 use SuperbAddons\Data\Controllers\LinkController;
16 use SuperbAddons\Data\Controllers\RestController;
17 use SuperbAddons\Data\Utils\AllowedTemplateHTMLUtil;
18 use SuperbAddons\Data\Utils\ScriptTranslations;
19 use SuperbAddons\Gutenberg\BlocksAPI\Controllers\AuthorBoxController;
20 use SuperbAddons\Gutenberg\BlocksAPI\Controllers\DynamicBlockAssets;
21 use SuperbAddons\Gutenberg\BlocksAPI\Controllers\RecentPostsController;
22 use SuperbAddons\Gutenberg\BlocksAPI\Controllers\TableOfContentsController;
23 use SuperbAddons\Library\Controllers\FavoritesController;
24 use SuperbAddons\Library\Controllers\LibraryController;
25 use SuperbAddons\Library\Controllers\LibraryRequestController;
26 use SuperbAddons\Data\Controllers\OptionController;
27 use SuperbAddons\Gutenberg\Form\FormAccessControl;
28 use SuperbAddons\Gutenberg\Form\FormController;
29 use SuperbAddons\Gutenberg\Form\FormEmailConfigCheck;
30 use SuperbAddons\Gutenberg\Form\FormEncryption;
31 use SuperbAddons\Gutenberg\Form\FormFileHandler;
32 use SuperbAddons\Gutenberg\Form\FormPermissions;
33 use SuperbAddons\Gutenberg\Form\FormRegistry;
34 use SuperbAddons\Gutenberg\Import\GutenbergBlockIdRegenerator;
35 use SuperbAddons\Gutenberg\Popup\PopupButtonRender;
36 use SuperbAddons\Gutenberg\Popup\PopupRegistry;
37
38 class GutenbergController
39 {
40 const MINIMUM_WORDPRESS_VERSION = '6.2';
41 const MINIMUM_PHP_VERSION = '5.6';
42
43 const VARIABLE_FALLBACKS_HANDLE = 'superb-addons-variable-fallbacks';
44
45 const BLOCK_PREFIX = 'superb-addons/';
46
47 // Parent blocks that users can toggle on/off
48 const TOGGLEABLE_BLOCKS = array(
49 'author-box',
50 'ratings',
51 'table-of-contents',
52 'recent-posts',
53 'cover-image',
54 'google-maps',
55 'reveal-buttons',
56 'accordion',
57 'carousel',
58 'countdown',
59 'progress-bar',
60 'popup',
61 'form',
62 'add-to-cart',
63 );
64
65 // Child blocks that should be hidden when their parent is disabled
66 const CHILD_BLOCK_MAP = array(
67 'carousel-slide' => 'carousel',
68 'reveal-button' => 'reveal-buttons',
69 'form-field' => 'form',
70 'form-step' => 'form',
71 'multistep-form' => 'form',
72 );
73
74 // Block variations shown as separate discoverable blocks in admin UI,
75 // sharing the enable/disable state of their parent toggleable block.
76 // Map: variation slug => parent slug (must be in TOGGLEABLE_BLOCKS).
77 const DISCOVERABLE_VARIATIONS = array(
78 'buy-now' => 'add-to-cart',
79 'multistep-form' => 'form',
80 );
81
82 // Blocks that should not be available in the widget editor
83 const WIDGET_EDITOR_EXCLUDED_BLOCKS = array(
84 'superb-addons/form',
85 'superb-addons/multistep-form',
86 'superb-addons/form-field',
87 'superb-addons/form-step',
88 'superb-addons/popup',
89 );
90
91 const PATTERN_BLOCK_ARG = 'is_pattern_block';
92 const BLOCKS = array(
93 ['path' => "animated-heading", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueueAnimatedHeader')]],
94 ['path' => "author-box", "args" => ['render_callback' => array(AuthorBoxController::class, 'Render')]],
95 ['path' => "ratings", "args" => []],
96 ['path' => "table-of-contents", "args" => ['render_callback' => array(TableOfContentsController::class, 'DynamicRender')]],
97 ['path' => "recent-posts", "args" => ['render_callback' => array(RecentPostsController::class, 'DynamicRender')]],
98 ['path' => "cover-image", "args" => []],
99 ['path' => "google-maps", "args" => []],
100 ['path' => "reveal-buttons", "args" => []],
101 ['path' => "reveal-button", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueueRevealButton')]],
102 ['path' => "accordion", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueueAccordion')]],
103 ['path' => "carousel", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueueCarousel')]],
104 ['path' => "carousel-slide", "args" => []],
105 ['path' => "countdown", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueueCountdown')]],
106 ['path' => "progress-bar", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueueProgressBar')]],
107 ['path' => "popup", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueuePopup')]],
108 ['path' => "form", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueueForm')]],
109 ['path' => "form-field", "args" => []],
110 ['path' => "multistep-form", "args" => ['render_callback' => array(DynamicBlockAssets::class, 'EnqueueForm')]],
111 ['path' => "form-step", "args" => []],
112 ['path' => "add-to-cart", "args" => ['render_callback' => array(CommerceController::class, 'RenderBlock')]],
113 );
114
115 public function __construct()
116 {
117 WizardRestorationPointController::Initialize();
118
119 if (!self::is_compatible()) {
120 return;
121 }
122
123 add_action('block_categories_all', array($this, 'RegisterBlockCategory'), defined('PHP_INT_MAX') ? PHP_INT_MAX : 999, 2);
124 add_action('init', array($this, 'RegisterJsxRuntimeFallback'), 0);
125 add_action('init', array($this, 'RegisterBlocksAndStyles'), 0);
126 add_action('enqueue_block_editor_assets', array($this, 'EnqueueBlockEditorAssets'));
127 add_filter('register_block_type_args', array($this, 'MaybeHideDisabledBlock'), 10, 2);
128 add_filter('allowed_block_types_all', array($this, 'MaybeExcludeBlocksFromWidgetEditor'), 10, 2);
129
130 // enqueue_block_assets fires on the frontend AND inside the editor iframe.
131 // Patterns CSS is registered through here so it reaches the iframe correctly.
132 add_action("enqueue_block_assets", array($this, 'EnqueueEditorIframeAssets'));
133
134 add_action('enqueue_block_editor_assets', array($this, 'EnqueueVariableFallbacks'), PHP_INT_MIN);
135 add_action("wp_enqueue_scripts", array($this, 'EnqueueVariableFallbacks'), PHP_INT_MIN);
136 add_action('wp_print_styles', array($this, 'ReorderVariableFallbacks'), PHP_INT_MAX);
137
138 GutenbergEnhancementsController::Initialize();
139 TableOfContentsController::Initialize();
140 FormController::Initialize();
141 CommerceController::Initialize();
142 PopupRegistry::Initialize();
143 PopupButtonRender::Initialize();
144 WizardController::Initialize();
145 }
146
147 /**
148 * Register the `react-jsx-runtime` script on WordPress versions that do not ship it.
149 *
150 * The block editor bundles are compiled with the automatic JSX runtime, so every
151 * block script depends on the `react-jsx-runtime` handle and reads
152 * `window.ReactJSXRuntime`. Core registers that handle from 6.6 on; on older
153 * versions a script with an unregistered dependency is silently never printed,
154 * which left every Superb block unavailable in the editor. The fallback file only
155 * exposes the JSX runtime of the React that WordPress already loads.
156 *
157 * Approach as documented by the Gutenberg team:
158 * https://github.com/WordPress/gutenberg/issues/62202#issuecomment-2156796649
159 */
160 public function RegisterJsxRuntimeFallback()
161 {
162 if (wp_script_is('react-jsx-runtime', 'registered')) {
163 return;
164 }
165 wp_register_script(
166 'react-jsx-runtime',
167 SUPERBADDONS_ASSETS_PATH . '/js/react-jsx-runtime.js',
168 array('react'),
169 SUPERBADDONS_VERSION,
170 true
171 );
172 }
173
174 /**
175 * Check if WooCommerce plugin files exist in wp-content/plugins (installed but possibly inactive).
176 */
177 public static function IsWooCommerceInstalled()
178 {
179 if (!function_exists('get_plugins')) {
180 require_once ABSPATH . 'wp-admin/includes/plugin.php';
181 }
182 $plugins = get_plugins();
183 return isset($plugins['woocommerce/woocommerce.php']);
184 }
185
186 /**
187 * Activation URL for WooCommerce with the proper nonce, or '' if not installed.
188 */
189 public static function GetWooCommerceActivateUrl()
190 {
191 if (!self::IsWooCommerceInstalled()) {
192 return '';
193 }
194 $plugin = 'woocommerce/woocommerce.php';
195 return wp_nonce_url(
196 self_admin_url('plugins.php?action=activate&plugin=' . urlencode($plugin) . '&plugin_status=all'),
197 'activate-plugin_' . $plugin
198 );
199 }
200
201 /**
202 * Total number of blocks shown in the admin UI, counting each discoverable variation separately.
203 */
204 public static function GetDiscoverableBlockTotal()
205 {
206 return count(self::TOGGLEABLE_BLOCKS) + count(self::DISCOVERABLE_VARIATIONS);
207 }
208
209 /**
210 * Number of currently-enabled blocks shown in the admin UI, counting each discoverable variation
211 * separately. A variation is enabled when its parent toggleable block is not disabled.
212 */
213 public static function GetDiscoverableBlockActiveCount($disabled_blocks)
214 {
215 $active = count(self::TOGGLEABLE_BLOCKS) - count($disabled_blocks);
216 foreach (self::DISCOVERABLE_VARIATIONS as $parent_slug) {
217 if (!in_array($parent_slug, $disabled_blocks, true)) {
218 $active++;
219 }
220 }
221 return $active;
222 }
223
224 public static function is_compatible()
225 {
226 // Check for required WP version
227 if (version_compare(get_bloginfo('version'), self::MINIMUM_WORDPRESS_VERSION, '<')) {
228 return false;
229 }
230
231 // Check for required PHP version
232 if (version_compare(PHP_VERSION, self::MINIMUM_PHP_VERSION, '<')) {
233 return false;
234 }
235
236 return true;
237 }
238
239 public static function is_block_theme()
240 {
241 if (!function_exists('wp_is_block_theme')) {
242 return false;
243 }
244
245 if (!wp_is_block_theme()) {
246 return false;
247 }
248
249 return true;
250 }
251
252 public static function GetGutenbergLibraryMenuItems()
253 {
254 return array(
255 array(
256 "id" => "patterns",
257 "premium_url" => AdminLinkUtil::GetExpLink(AdminLinkSource::LIBRARY_ITEM),
258 "title" => esc_html__('Patterns', "superb-blocks"),
259 "routes" => array(
260 "list" => LibraryRequestController::GUTENBERG_V2_LIST_ROUTE,
261 "insert" => LibraryRequestController::GUTENBERG_V2_INSERT_ROUTE
262 ),
263 "type" => LibraryRequestController::GUTENBERG_TYPE_PATTERN,
264 "hidden" => false
265 ),
266 array(
267 "id" => "pages",
268 "premium_url" => AdminLinkUtil::GetExpLink(AdminLinkSource::LIBRARY_PAGE_ITEM),
269 "title" => esc_html__('Pages', "superb-blocks"),
270 "routes" => array(
271 "list" => LibraryRequestController::GUTENBERG_V2_LIST_ROUTE,
272 "insert" => LibraryRequestController::GUTENBERG_V2_INSERT_ROUTE
273 ),
274 "type" => LibraryRequestController::GUTENBERG_TYPE_PAGE,
275 "hidden" => false
276 )
277 );
278 }
279
280 public function EnqueuePatternAssets()
281 {
282 wp_enqueue_style(
283 'superb-addons-patterns'
284 );
285 }
286
287 public function EnqueueVariableFallbacks()
288 {
289 $fallbacks = ":root{--wp--preset--color--primary:#1f7cec;--wp--preset--color--primary-hover:#3993ff;--wp--preset--color--base:#fff;--wp--preset--color--featured:#0a284b;--wp--preset--color--contrast-light:#fff;--wp--preset--color--contrast-dark:#000;--wp--preset--color--mono-1:#0d3c74;--wp--preset--color--mono-2:#64748b;--wp--preset--color--mono-3:#e2e8f0;--wp--preset--color--mono-4:#f8fafc;--wp--preset--spacing--superbspacing-xxsmall:clamp(5px,1vw,10px);--wp--preset--spacing--superbspacing-xsmall:clamp(10px,2vw,20px);--wp--preset--spacing--superbspacing-small:clamp(20px,4vw,40px);--wp--preset--spacing--superbspacing-medium:clamp(30px,6vw,60px);--wp--preset--spacing--superbspacing-large:clamp(40px,8vw,80px);--wp--preset--spacing--superbspacing-xlarge:clamp(50px,10vw,100px);--wp--preset--spacing--superbspacing-xxlarge:clamp(60px,12vw,120px);--wp--preset--font-size--superbfont-tiny:clamp(10px,0.625rem + ((1vw - 3.2px) * 0.227),12px);--wp--preset--font-size--superbfont-xxsmall:clamp(12px,0.75rem + ((1vw - 3.2px) * 0.227),14px);--wp--preset--font-size--superbfont-xsmall:clamp(16px,1rem + ((1vw - 3.2px) * 1),16px);--wp--preset--font-size--superbfont-small:clamp(16px,1rem + ((1vw - 3.2px) * 0.227),18px);--wp--preset--font-size--superbfont-medium:clamp(18px,1.125rem + ((1vw - 3.2px) * 0.227),20px);--wp--preset--font-size--superbfont-large:clamp(24px,1.5rem + ((1vw - 3.2px) * 0.909),32px);--wp--preset--font-size--superbfont-xlarge:clamp(32px,2rem + ((1vw - 3.2px) * 1.818),48px);--wp--preset--font-size--superbfont-xxlarge:clamp(40px,2.5rem + ((1vw - 3.2px) * 2.727),64px)}.has-primary-color{color:var(--wp--preset--color--primary)!important}.has-primary-hover-color{color:var(--wp--preset--color--primary-hover)!important}.has-base-color{color:var(--wp--preset--color--base)!important}.has-featured-color{color:var(--wp--preset--color--featured)!important}.has-contrast-light-color{color:var(--wp--preset--color--contrast-light)!important}.has-contrast-dark-color{color:var(--wp--preset--color--contrast-dark)!important}.has-mono-1-color{color:var(--wp--preset--color--mono-1)!important}.has-mono-2-color{color:var(--wp--preset--color--mono-2)!important}.has-mono-3-color{color:var(--wp--preset--color--mono-3)!important}.has-mono-4-color{color:var(--wp--preset--color--mono-4)!important}.has-primary-background-color{background-color:var(--wp--preset--color--primary)!important}.has-primary-hover-background-color{background-color:var(--wp--preset--color--primary-hover)!important}.has-base-background-color{background-color:var(--wp--preset--color--base)!important}.has-featured-background-color{background-color:var(--wp--preset--color--featured)!important}.has-contrast-light-background-color{background-color:var(--wp--preset--color--contrast-light)!important}.has-contrast-dark-background-color{background-color:var(--wp--preset--color--contrast-dark)!important}.has-mono-1-background-color{background-color:var(--wp--preset--color--mono-1)!important}.has-mono-2-background-color{background-color:var(--wp--preset--color--mono-2)!important}.has-mono-3-background-color{background-color:var(--wp--preset--color--mono-3)!important}.has-mono-4-background-color{background-color:var(--wp--preset--color--mono-4)!important}.has-superbfont-tiny-font-size{font-size:var(--wp--preset--font-size--superbfont-tiny)!important}.has-superbfont-xxsmall-font-size{font-size:var(--wp--preset--font-size--superbfont-xxsmall)!important}.has-superbfont-xsmall-font-size{font-size:var(--wp--preset--font-size--superbfont-xsmall)!important}.has-superbfont-small-font-size{font-size:var(--wp--preset--font-size--superbfont-small)!important}.has-superbfont-medium-font-size{font-size:var(--wp--preset--font-size--superbfont-medium)!important}.has-superbfont-large-font-size{font-size:var(--wp--preset--font-size--superbfont-large)!important}.has-superbfont-xlarge-font-size{font-size:var(--wp--preset--font-size--superbfont-xlarge)!important}.has-superbfont-xxlarge-font-size{font-size:var(--wp--preset--font-size--superbfont-xxlarge)!important}";
290 wp_add_inline_style(self::VARIABLE_FALLBACKS_HANDLE, $fallbacks);
291 wp_enqueue_style(self::VARIABLE_FALLBACKS_HANDLE);
292 }
293
294 public function ReorderVariableFallbacks()
295 {
296 // Some themes/plugins may incorrectly call wp_enqueue_global_styles outside of the enqueue phase, causing our fallbacks to be enqueued in the wrong order.
297 // To fix this, we will move our fallbacks to the front of the queue
298 global $wp_styles;
299
300 if (!isset($wp_styles) || !is_object($wp_styles)) {
301 return;
302 }
303 if (!isset($wp_styles->queue) || !is_array($wp_styles->queue) || empty($wp_styles->queue)) {
304 return;
305 }
306 if (!isset($wp_styles->registered[self::VARIABLE_FALLBACKS_HANDLE])) {
307 return;
308 }
309 if (!in_array(self::VARIABLE_FALLBACKS_HANDLE, $wp_styles->queue)) {
310 return;
311 }
312 if ($wp_styles->queue[0] === self::VARIABLE_FALLBACKS_HANDLE) {
313 return;
314 }
315
316 $wp_styles->queue = array_diff($wp_styles->queue, array(self::VARIABLE_FALLBACKS_HANDLE));
317 array_unshift($wp_styles->queue, self::VARIABLE_FALLBACKS_HANDLE);
318 }
319
320 public function EnqueueEditorIframeAssets()
321 {
322 global $pagenow;
323
324 // Patterns CSS — needed on the frontend and inside the editor iframe.
325 // This hook is the only path that reaches the iframe correctly.
326 $this->EnqueuePatternAssets();
327
328 // Enhancements CSS must load inside the editor iframe so that
329 // media queries respond to the iframe width (device preview).
330 if (is_admin()) {
331 wp_enqueue_style(
332 'superb-addons-editor-enhancements',
333 SUPERBADDONS_ASSETS_PATH . '/css/editor-enhancements.min.css',
334 array(),
335 SUPERBADDONS_VERSION
336 );
337 }
338
339 // Site-editor-only assets
340 if ('site-editor.php' === $pagenow) {
341 wp_enqueue_style(
342 'superb-gutenberg-layout-library',
343 SUPERBADDONS_ASSETS_PATH . '/css/layout-library-preview.min.css',
344 array(),
345 SUPERBADDONS_VERSION
346 );
347 $this->EnqueueVariableFallbacks();
348 }
349 }
350
351 public function EnqueueBlockEditorAssets()
352 {
353 self::AddonsLibrary();
354 self::EditorEnhancements();
355 wp_enqueue_script(
356 'superb-addons-gutenberg-library',
357 SUPERBADDONS_ASSETS_PATH . '/js/gutenberg/pattern-library.js',
358 array("jquery", "wp-plugins", "wp-hooks", "wp-data", "wp-element", "wp-i18n", "wp-components", "wp-compose", "wp-blocks", "wp-editor", "wp-block-editor"),
359 SUPERBADDONS_VERSION,
360 true
361 );
362 ScriptTranslations::Set('superb-addons-gutenberg-library');
363 wp_enqueue_script(
364 'superb-addons-upsell-modal',
365 SUPERBADDONS_ASSETS_PATH . '/js/gutenberg/upsell-modal.js',
366 array("wp-plugins", "wp-data", "wp-element", "wp-i18n", "wp-components", "wp-url", "wp-escape-html"),
367 SUPERBADDONS_VERSION,
368 true
369 );
370 ScriptTranslations::Set('superb-addons-upsell-modal');
371 wp_enqueue_script(
372 'superb-addons-upsell-modal-admin',
373 SUPERBADDONS_ASSETS_PATH . '/js/admin/upsell-modal.js',
374 array('wp-i18n', 'wp-url', 'wp-escape-html'),
375 SUPERBADDONS_VERSION,
376 true
377 );
378 ScriptTranslations::Set('superb-addons-upsell-modal-admin');
379 wp_enqueue_style(
380 'superb-addons-admin-modal',
381 SUPERBADDONS_ASSETS_PATH . '/css/admin-modal.min.css',
382 array(),
383 SUPERBADDONS_VERSION
384 );
385 wp_localize_script('superb-addons-gutenberg-library', 'superblayoutlibrary_g', array(
386 "style_placeholder" => esc_html__('All themes', "superb-blocks"),
387 "category_placeholder" => esc_html__('All categories', "superb-blocks"),
388 "snacks" => array(
389 "settings_save_message" => esc_html__("Settings saved successfully.", "superb-blocks"),
390 "settings_save_error" => esc_html__("Something went wrong while attempting to save your settings. Please try again or contact support if the problem persists.", "superb-blocks"),
391 "insert_error" => esc_html__('Something went wrong while attempting to insert this element. Please try again or contact support if the problem persists.', "superb-blocks"),
392 "list_error" => esc_html__('Something went wrong while attempting to list elements. Please try again or contact support if the problem persists.', "superb-blocks")
393 ),
394 "menu_items" => self::GetGutenbergLibraryMenuItems(),
395 "chunk_route" => LibraryRequestController::GUTENBERG_V2_LIST_CHUNK_ROUTE,
396 "favorites" => FavoritesController::GetFavorites(get_current_user_id()),
397 "rest" => array(
398 "base" => \get_rest_url(),
399 "namespace" => RestController::NAMESPACE,
400 "nonce" => wp_create_nonce("wp_rest")
401 ),
402 "addons_link_id" => AdminLinkUtil::GetLinkID(),
403 "visibility" => array(
404 "roles" => GutenbergConditionsController::GetConditionsRoles(),
405 ),
406 ));
407
408 LinkController::Localize('superb-addons-gutenberg-library');
409 wp_enqueue_style(
410 'superb-addons-elements',
411 SUPERBADDONS_ASSETS_PATH . '/css/framework.min.css',
412 array(),
413 SUPERBADDONS_VERSION
414 );
415 wp_enqueue_style(
416 'superb-addons-font-manrope',
417 SUPERBADDONS_ASSETS_PATH . '/fonts/manrope/manrope.css',
418 array(),
419 SUPERBADDONS_VERSION
420 );
421 wp_enqueue_style(
422 'superb-gutenberg-editor-layout-library',
423 SUPERBADDONS_ASSETS_PATH . '/css/layout-library-editor.min.css',
424 array(),
425 SUPERBADDONS_VERSION
426 );
427 wp_enqueue_style(
428 'superb-gutenberg-layout-library',
429 SUPERBADDONS_ASSETS_PATH . '/css/layout-library-preview.min.css',
430 array(),
431 SUPERBADDONS_VERSION
432 );
433 wp_enqueue_style(
434 'superbaddons-toast',
435 SUPERBADDONS_ASSETS_PATH . '/css/toast.min.css',
436 array(),
437 SUPERBADDONS_VERSION
438 );
439 wp_enqueue_script('superb-addons-select2', SUPERBADDONS_ASSETS_PATH . '/lib/select2.min.js', array('jquery'), SUPERBADDONS_VERSION, true);
440 wp_enqueue_style(
441 'superbaddons-select2',
442 SUPERBADDONS_ASSETS_PATH . '/lib/select2.min.css',
443 array(),
444 SUPERBADDONS_VERSION
445 );
446
447 wp_enqueue_script(
448 'superbaddons-animated-heading',
449 SUPERBADDONS_ASSETS_PATH . '/js/dynamic-blocks/animated-heading.js',
450 [],
451 SUPERBADDONS_VERSION,
452 true
453 );
454
455 // Form editor config — pass settings URL, REST namespace, and registry (API key status checked dynamically)
456 $ac_enabled = FormAccessControl::IsEnabled();
457 wp_add_inline_script('superb-addons-gutenberg-library', 'window.superbFormsEditorConfig = ' . wp_json_encode(array(
458 'registry' => FormRegistry::GetAll(),
459 'settingsUrl' => admin_url('admin.php?page=superbaddons-settings#integrations'),
460 'restNamespace' => RestController::NAMESPACE,
461 'encryptionAvailable' => FormEncryption::IsAvailable(),
462 'emailConfigured' => FormEmailConfigCheck::IsConfigured(),
463 'formAccessControl' => array(
464 'enabled' => $ac_enabled,
465 'canEdit' => $ac_enabled ? FormPermissions::Can('edit') : true,
466 'canConfigure' => $ac_enabled ? FormPermissions::Can('configure') : true,
467 'canCreate' => $ac_enabled ? FormPermissions::Can('create') : true,
468 'sensitiveAttrs' => FormAccessControl::GetSensitiveAttrs(),
469 ),
470 // Picker for the file-field accept whitelist. Sourced from
471 // FormFileHandler so the editor list, server-side validation, and
472 // the superbaddons_form_allowed_file_types filter stay in sync;
473 // deny-listed extensions are stripped there so editors cannot pick
474 // an extension whose uploads will be rejected.
475 'allowedFileTypes' => FormFileHandler::GetAllowedFileTypes(),
476 'maxUploadSizeMb' => max(1, (int) floor(wp_max_upload_size() / (1024 * 1024))),
477 ), JSON_HEX_TAG) . ';', 'before');
478
479 // Popup registry — pass registered popups to the block editor
480 wp_add_inline_script('superb-addons-gutenberg-library', 'window.superbPopupsEditorConfig = ' . wp_json_encode(array(
481 'registry' => PopupRegistry::GetAll(),
482 ), JSON_HEX_TAG) . ';', 'before');
483
484 // Commerce (Add to Cart) editor config: WC active/installed state, REST namespace, recent picks.
485 wp_add_inline_script('superb-addons-gutenberg-library', 'window.superbAddToCartEditorConfig = ' . wp_json_encode(array(
486 'wcActive' => CommerceController::IsWcActive(),
487 'wcInstalled' => self::IsWooCommerceInstalled(),
488 'wcSupported' => CommerceController::IsWcActive() && CommerceController::IsWcVersionSupported(),
489 'wcActivateUrl' => self::GetWooCommerceActivateUrl(),
490 'wcInstallUrl' => admin_url('plugin-install.php?tab=plugin-information&plugin=woocommerce'),
491 'restNamespace' => RestController::NAMESPACE,
492 'recentPicks' => CommerceController::GetRecentPicks(get_current_user_id()),
493 'currencySymbol' => CommerceController::IsWcActive() ? html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8') : '$',
494 'currencyPosition' => CommerceController::IsWcActive() ? (string) get_option('woocommerce_currency_pos', 'left') : 'left',
495 'canEditCoupons' => current_user_can('edit_shop_coupons'),
496 ), JSON_HEX_TAG) . ';', 'before');
497
498 // Enhancements
499 wp_enqueue_style(
500 'superb-addons-editor-enhancements',
501 SUPERBADDONS_ASSETS_PATH . '/css/editor-enhancements.min.css',
502 array(),
503 SUPERBADDONS_VERSION
504 );
505
506 /// Compatibility
507
508 if (SettingsController::IsCompatibilitySettingRelevantAndEnabled(CompatibilitySettingsOptionKey::SPECTRA_BLOCK_SPACING)) {
509 wp_enqueue_script(
510 'superb-addons-block-spacing-compatibility-fix',
511 SUPERBADDONS_ASSETS_PATH . '/js/compatibility/block-spacing.js',
512 array('jquery'),
513 SUPERBADDONS_VERSION,
514 true
515 );
516 }
517 }
518
519 public function RegisterBlockCategory($block_categories, $block_editor_context)
520 {
521 return array_merge(
522 array(
523 array(
524 'slug' => 'superb-addons-blocks',
525 'title' => __('Superb Addons', "superb-blocks"),
526 ),
527 ),
528 $block_categories
529 );
530 }
531
532 /**
533 * Hide disabled blocks from the inserter while keeping them registered
534 * so existing content still renders.
535 */
536 public function MaybeHideDisabledBlock($args, $block_type)
537 {
538 if (strpos($block_type, self::BLOCK_PREFIX) !== 0) {
539 return $args;
540 }
541
542 $slug = str_replace(self::BLOCK_PREFIX, '', $block_type);
543 $disabled = OptionController::GetDisabledBlocks();
544
545 // Check if this block is directly disabled
546 $is_disabled = in_array($slug, $disabled, true);
547
548 // Check if this is a child block whose parent is disabled
549 if (!$is_disabled && isset(self::CHILD_BLOCK_MAP[$slug])) {
550 $parent = self::CHILD_BLOCK_MAP[$slug];
551 $is_disabled = in_array($parent, $disabled, true);
552 }
553
554 // Hide form blocks from inserter when access control is enabled and user lacks 'create'
555 if (!$is_disabled && FormAccessControl::IsEnabled() && !FormPermissions::Can('create')) {
556 $form_slugs = array('form', 'multistep-form');
557 if (in_array($slug, $form_slugs, true)) {
558 $is_disabled = true;
559 }
560 // Also hide child blocks (form-field, form-step) via CHILD_BLOCK_MAP
561 if (!$is_disabled && isset(self::CHILD_BLOCK_MAP[$slug])) {
562 $parent = self::CHILD_BLOCK_MAP[$slug];
563 if (in_array($parent, $form_slugs, true)) {
564 $is_disabled = true;
565 }
566 }
567 }
568
569 if ($is_disabled) {
570 if (!isset($args['supports'])) {
571 $args['supports'] = array();
572 }
573 $args['supports']['inserter'] = false;
574 }
575
576 return $args;
577 }
578
579 /**
580 * Remove blocks that are not supported in the widget editor context.
581 * Widget areas store blocks in wp_options, not post content, so blocks
582 * that rely on save_post hooks (like forms) cannot track their state.
583 */
584 public function MaybeExcludeBlocksFromWidgetEditor($allowed_block_types, $editor_context)
585 {
586 if (!isset($editor_context->name)) {
587 return $allowed_block_types;
588 }
589
590 if ($editor_context->name !== 'core/edit-widgets' && $editor_context->name !== 'core/customize-widgets') {
591 return $allowed_block_types;
592 }
593
594 // If all blocks are allowed (default), build the full list minus excluded blocks
595 if ($allowed_block_types === true) {
596 $registry = \WP_Block_Type_Registry::get_instance();
597 $allowed_block_types = array_keys($registry->get_all_registered());
598 }
599
600 return array_values(array_diff($allowed_block_types, self::WIDGET_EDITOR_EXCLUDED_BLOCKS));
601 }
602
603 public function RegisterBlocksAndStyles()
604 {
605 foreach (self::BLOCKS as $block) {
606 if (isset($block['args'][self::PATTERN_BLOCK_ARG])) {
607 $block['args']['category'] = 'superb-addons-blocks-patterns';
608 unset($block['args'][self::PATTERN_BLOCK_ARG]);
609 }
610 register_block_type(SUPERBADDONS_PLUGIN_DIR . 'blocks/' . $block['path'], $block['args']);
611 }
612
613 wp_register_style(self::VARIABLE_FALLBACKS_HANDLE, false, array(), SUPERBADDONS_VERSION);
614 wp_register_style(
615 'superb-addons-patterns',
616 SUPERBADDONS_ASSETS_PATH . '/css/patterns.min.css',
617 array(),
618 SUPERBADDONS_VERSION
619 );
620 }
621
622 private static function EditorEnhancements()
623 {
624 add_action('admin_footer', function () {
625 AllowedTemplateHTMLUtil::enable_safe_styles();
626 ob_start();
627 include(SUPERBADDONS_PLUGIN_DIR . 'src/gutenberg/templates/block-quick-options.php');
628 $template = ob_get_clean();
629 echo '<script type="text/template" id="tmpl-gutenberg-superb-block-quick-options">' . wp_kses($template, AllowedTemplateHTMLUtil::get_allowed_html()) . '</script>';
630 AllowedTemplateHTMLUtil::disable_safe_styles();
631 });
632 }
633
634 public static function AddonsLibrary()
635 {
636 add_action('admin_footer', function () {
637 AllowedTemplateHTMLUtil::enable_safe_styles();
638 ///// Buttons
639 ob_start();
640 include(SUPERBADDONS_PLUGIN_DIR . 'src/gutenberg/templates/library-button.php');
641 $template = ob_get_clean();
642 echo '<script type="text/template" id="tmpl-gutenberg-superb-library-button">' . wp_kses($template, "post") . '</script>';
643 //
644 ob_start();
645 include(SUPERBADDONS_PLUGIN_DIR . 'src/gutenberg/templates/pattern-tab-library-button.php');
646 $template = ob_get_clean();
647 echo '<script type="text/template" id="tmpl-gutenberg-superb-library-button-patternstab">' . wp_kses($template, "post") . '</script>';
648 //
649 ob_start();
650 include(SUPERBADDONS_PLUGIN_DIR . 'src/gutenberg/templates/appender-button.php');
651 $template = ob_get_clean();
652 echo '<script type="text/template" id="tmpl-gutenberg-superb-library-appender-button">' . wp_kses($template, "post") . '</script>';
653 AllowedTemplateHTMLUtil::disable_safe_styles();
654 ////// Library
655 LibraryController::InsertTemplatesWithWrapper();
656 });
657 }
658
659 public static function GutenbergDataImportAction($data)
660 {
661 if (!isset($data['content'])) {
662 return $data;
663 }
664
665 $options_controller = new OptionController();
666 $preferred_domain = $options_controller->GetPreferredDomain();
667 // Proxy is preferred if domain starts with https://superbthemes.com
668 $is_proxy_preferred = strpos($preferred_domain, 'https://superbthemes.com') === 0;
669
670 $content = $data['content'];
671 $content = preg_replace_callback("/(http)?s?:?(\/\/[^\"']*\.(?:png|jpg|jpeg|gif|png|webp))/", function ($matches) use ($preferred_domain, $is_proxy_preferred) {
672 // Get the URL
673 $url = $matches[0];
674 if ($is_proxy_preferred) {
675 $url = $preferred_domain . "image-library/theme-designer-images?path=" . $url;
676 }
677 $basename = pathinfo($url, PATHINFO_BASENAME);
678 $title = sanitize_title($basename);
679
680 // Check if attachment exists based on the file slug
681 $posts = get_posts(
682 array(
683 'post_type' => 'attachment',
684 'title' => $title,
685 'numberposts' => 1,
686 )
687 );
688 if (!empty($posts)) {
689 // Return existing attachment URL
690 $attachment = $posts[0];
691 return wp_get_attachment_image_url($attachment->ID, 'full');
692 }
693
694 if (!function_exists('media_sideload_image')) {
695 require_once(ABSPATH . 'wp-admin/includes/image.php');
696 require_once(ABSPATH . 'wp-admin/includes/file.php');
697 require_once(ABSPATH . 'wp-admin/includes/media.php');
698 }
699
700 // Create new attachment
701 $attachment_id = \media_sideload_image($url, 0, $title, 'id');
702 if (is_wp_error($attachment_id) || !is_numeric($attachment_id)) {
703 // Return original URL if any error occurred
704 return $url;
705 }
706
707 // Return new attachment URL
708 return wp_get_attachment_image_url($attachment_id, 'full');
709 }, $content);
710
711 // Regenerate owner block IDs (popupId, formId, accordionId, etc.) and
712 // rewrite matching references (e.g. core/button spbaddPopupTarget) so
713 // that every library import produces fresh, collision-free IDs.
714 $content = GutenbergBlockIdRegenerator::RegenerateIds($content);
715
716 $data['content'] = $content;
717
718 return $data;
719 }
720 }
721