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