PluginProbe
GutSlider – All in One Slider and Carousel Blocks for Gutenberg / 3.0.0
GutSlider – All in One Slider and Carousel Blocks for Gutenberg v3.0.0
3.1.0 3.0.0 2.13.2 2.13.1 2.13.0 trunk 1.0.0 2.1.0 2.10.0 2.10.1 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.12.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.1 2.5.3 2.5.4 2.5.5 2.6.1 All 56 releases
slider-blocks / admin / admin.php

admin.php in GutSlider – All in One Slider and Carousel Blocks for Gutenberg 3.0.0, at admin/admin.php

674 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * GutSlider admin dashboard bootstrap.
4 *
5 * Registers the admin menu, loads dashboard assets, and exposes the REST
6 * endpoints used by the dashboard UI to persist changes.
7 *
8 * @package GutSliderBlocks
9 */
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 if ( ! class_exists( 'GutSlider_Admin' ) ) {
16
17 /**
18 * Admin dashboard controller.
19 */
20 class GutSlider_Admin {
21
22 /**
23 * Option key holding the general settings array.
24 *
25 * @var string
26 */
27 const SETTINGS_OPTION = 'gutslider_settings';
28
29 /**
30 * Top level menu slug.
31 *
32 * @var string
33 */
34 const MENU_SLUG = 'gutslider-blocks';
35
36 /**
37 * Page slug for the blocks manager.
38 *
39 * @var string
40 */
41 const BLOCKS_SLUG = 'gutslider-blocks-settings';
42
43 /**
44 * Page slug for the settings screen.
45 *
46 * @var string
47 */
48 const SETTINGS_SLUG = 'gutslider-settings';
49
50 /**
51 * Default values for the general settings.
52 *
53 * @var array<string, mixed>
54 */
55 private static $settings_defaults = array(
56 'css_delivery' => 'file',
57 'google_fonts' => true,
58 'pattern_library' => true,
59 'remove_data' => false,
60 );
61
62 /**
63 * Constructor.
64 */
65 public function __construct() {
66 add_action( 'admin_menu', array( $this, 'admin_menu' ), 20 );
67 add_action( 'admin_enqueue_scripts', array( $this, 'admin_assets' ) );
68 add_action( 'rest_api_init', array( $this, 'register_routes' ) );
69 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
70 add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ) );
71 }
72
73 /* ---------------------------------------------------------------
74 * Settings helpers
75 * ------------------------------------------------------------- */
76
77 /**
78 * Get the general settings, merged over the defaults.
79 *
80 * @return array<string, mixed> Settings array.
81 */
82 public static function get_settings() {
83 $stored = get_option( self::SETTINGS_OPTION, array() );
84
85 if ( ! is_array( $stored ) ) {
86 $stored = array();
87 }
88
89 return wp_parse_args( $stored, self::$settings_defaults );
90 }
91
92 /**
93 * Get a single setting value.
94 *
95 * @param string $key Setting key.
96 * @param mixed $default Fallback when the key is unknown.
97 * @return mixed Setting value.
98 */
99 public static function get_setting( $key, $default = null ) {
100 $settings = self::get_settings();
101
102 return array_key_exists( $key, $settings ) ? $settings[ $key ] : $default;
103 }
104
105 /**
106 * Build the option key that stores a block's enabled state.
107 *
108 * @param string $block_name Block directory name.
109 * @return string Option key.
110 */
111 public static function block_option_key( $block_name ) {
112 return 'gut_' . str_replace( '-', '_', $block_name );
113 }
114
115 /**
116 * Read the block definitions from the shared data file.
117 *
118 * @return array<int, array<string, mixed>> Block definitions.
119 */
120 public function get_blocks() {
121 $blocks_file = GUTSLIDER_DIR . '/includes/Api/blocks.php';
122
123 if ( ! file_exists( $blocks_file ) ) {
124 return array();
125 }
126
127 $blocks = include $blocks_file;
128
129 return is_array( $blocks ) ? $blocks : array();
130 }
131
132 /**
133 * Decorate the block definitions with their current state.
134 *
135 * @return array<int, array<string, mixed>> Block definitions.
136 */
137 public function get_blocks_with_state() {
138 $has_pro = defined( 'GUTSLIDER_PRO_VERSION' );
139 $blocks = array();
140
141 foreach ( $this->get_blocks() as $block ) {
142 $is_pro = ! empty( $block['is_pro'] );
143
144 $block['is_pro'] = $is_pro;
145 $block['locked'] = $is_pro && ! $has_pro;
146 $block['option_key'] = self::block_option_key( $block['name'] );
147 $block['enabled'] = $block['locked']
148 ? false
149 : (bool) get_option( $block['option_key'], true );
150
151 $blocks[] = $block;
152 }
153
154 return $blocks;
155 }
156
157 /**
158 * Count how many blocks are currently enabled.
159 *
160 * @param array<int, array<string, mixed>> $blocks Decorated blocks.
161 * @return int Enabled block count.
162 */
163 public static function count_enabled( array $blocks ) {
164 $count = 0;
165
166 foreach ( $blocks as $block ) {
167 if ( ! empty( $block['enabled'] ) ) {
168 ++$count;
169 }
170 }
171
172 return $count;
173 }
174
175 /* ---------------------------------------------------------------
176 * Menu + assets
177 * ------------------------------------------------------------- */
178
179 /**
180 * Register the admin menu and its sub pages.
181 *
182 * @return void
183 */
184 public function admin_menu() {
185 $icon = 'data:image/svg+xml;base64,' . base64_encode( '<svg width="20" height="20" viewBox="0 0 45 45" xmlns="http://www.w3.org/2000/svg"><path fill="black" d="M5 45C3.625 45 2.44833 44.5108 1.47 43.5325C0.491667 42.5542 0.00166667 41.3767 0 40V5C0 3.625 0.49 2.44833 1.47 1.47C2.45 0.491667 3.62667 0.00166667 5 0H40C41.375 0 42.5525 0.49 43.5325 1.47C44.5125 2.45 45.0017 3.62667 45 5V40C45 41.375 44.5108 42.5525 43.5325 43.5325C42.5542 44.5125 41.3767 45.0017 40 45H5ZM17.5 35H27.5C28.875 35 30.0525 34.5108 31.0325 33.5325C32.0125 32.5542 32.5017 31.3767 32.5 30V20H22.5V25H27.5V30H17.5V15H32.5C32.5 13.625 32.0108 12.4483 31.0325 11.47C30.0542 10.4917 28.8767 10.0017 27.5 10H17.5C16.125 10 14.9483 10.49 13.97 11.47C12.9917 12.45 12.5017 13.6267 12.5 15V30C12.5 31.375 12.99 32.5525 13.97 33.5325C14.95 34.5125 16.1267 35.0017 17.5 35Z"/></svg>' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Inline menu icon.
186
187 add_menu_page(
188 __( 'GutSlider', 'slider-blocks' ),
189 __( 'GutSlider', 'slider-blocks' ),
190 'manage_options',
191 self::MENU_SLUG,
192 array( $this, 'render_dashboard_page' ),
193 $icon,
194 100
195 );
196
197 add_submenu_page(
198 self::MENU_SLUG,
199 __( 'Dashboard', 'slider-blocks' ),
200 __( 'Dashboard', 'slider-blocks' ),
201 'manage_options',
202 self::MENU_SLUG,
203 array( $this, 'render_dashboard_page' )
204 );
205
206 add_submenu_page(
207 self::MENU_SLUG,
208 __( 'Blocks', 'slider-blocks' ),
209 __( 'Blocks', 'slider-blocks' ),
210 'manage_options',
211 self::BLOCKS_SLUG,
212 array( $this, 'render_blocks_page' )
213 );
214
215 add_submenu_page(
216 self::MENU_SLUG,
217 __( 'Settings', 'slider-blocks' ),
218 __( 'Settings', 'slider-blocks' ),
219 'manage_options',
220 self::SETTINGS_SLUG,
221 array( $this, 'render_settings_page' )
222 );
223 }
224
225 /**
226 * Screen IDs that should receive the dashboard assets.
227 *
228 * @return array<int, string> Screen IDs.
229 */
230 public static function screen_ids() {
231 return array(
232 'toplevel_page_' . self::MENU_SLUG,
233 'gutslider_page_' . self::BLOCKS_SLUG,
234 'gutslider_page_' . self::SETTINGS_SLUG,
235 'gutslider_page_gutslider-license',
236 );
237 }
238
239 /**
240 * Whether the current request is a GutSlider dashboard screen.
241 *
242 * @param string $screen Current screen ID.
243 * @return bool True on a dashboard screen.
244 */
245 public static function is_dashboard_screen( $screen = '' ) {
246 if ( '' === $screen ) {
247 $current = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
248 $screen = $current ? $current->id : '';
249 }
250
251 return in_array( $screen, self::screen_ids(), true );
252 }
253
254 /**
255 * Enqueue the dashboard stylesheet and script.
256 *
257 * @param string $screen Current screen ID.
258 * @return void
259 */
260 public function admin_assets( $screen ) {
261 if ( ! self::is_dashboard_screen( $screen ) ) {
262 return;
263 }
264
265 wp_enqueue_style(
266 'gutslider-admin',
267 GUTSLIDER_URL . 'admin/css/admin.css',
268 array(),
269 GUTSLIDER_VERSION
270 );
271
272 wp_enqueue_script(
273 'gutslider-admin',
274 GUTSLIDER_URL . 'admin/js/admin.js',
275 array( 'wp-i18n' ),
276 GUTSLIDER_VERSION,
277 true
278 );
279
280 wp_localize_script(
281 'gutslider-admin',
282 'gutslider',
283 array(
284 'version' => GUTSLIDER_VERSION,
285 'isPro' => defined( 'GUTSLIDER_PRO_VERSION' ),
286 'proVersion' => defined( 'GUTSLIDER_PRO_VERSION' ) ? GUTSLIDER_PRO_VERSION : '',
287 'restUrl' => esc_url_raw( rest_url( 'gutslider/v1/' ) ),
288 'nonce' => wp_create_nonce( 'wp_rest' ),
289 'pricingUrl' => 'https://gutslider.com/pricing',
290 'i18n' => array(
291 'saved' => __( 'Changes saved', 'slider-blocks' ),
292 'saveFailed' => __( 'Could not save. Please try again.', 'slider-blocks' ),
293 'discarded' => __( 'Changes discarded', 'slider-blocks' ),
294 'cacheCleared' => __( 'Style cache cleared', 'slider-blocks' ),
295 /* translators: %d: number of unsaved changes. */
296 'changeSingle' => __( '%d unsaved change', 'slider-blocks' ),
297 /* translators: %d: number of unsaved changes. */
298 'changePlural' => __( '%d unsaved changes', 'slider-blocks' ),
299 'leaveWarning' => __( 'You have unsaved changes.', 'slider-blocks' ),
300 /* translators: 1: enabled block count, 2: total block count. */
301 'enabledCount' => __( '%1$d of %2$d enabled', 'slider-blocks' ),
302 ),
303 )
304 );
305 }
306
307 /**
308 * Add a marker class so the stylesheet can reset the admin chrome.
309 *
310 * @param string $classes Existing body classes.
311 * @return string Filtered body classes.
312 */
313 public function admin_body_class( $classes ) {
314 if ( self::is_dashboard_screen() ) {
315 $classes .= ' gutslider-admin-page ';
316 }
317
318 return $classes;
319 }
320
321 /**
322 * Replace the admin footer credit on dashboard screens.
323 *
324 * @param string $text Default footer text.
325 * @return string Filtered footer text.
326 */
327 public function admin_footer_text( $text ) {
328 if ( ! self::is_dashboard_screen() ) {
329 return $text;
330 }
331
332 return sprintf(
333 /* translators: %s: link to the review page. */
334 esc_html__( 'Enjoying GutSlider? %s on WordPress.org.', 'slider-blocks' ),
335 '<a href="https://wordpress.org/support/plugin/slider-blocks/reviews/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Leave a review', 'slider-blocks' ) . '</a>'
336 );
337 }
338
339 /* ---------------------------------------------------------------
340 * Views
341 * ------------------------------------------------------------- */
342
343 /**
344 * Load a dashboard view.
345 *
346 * @param string $view View file name without extension.
347 * @param array<string, mixed> $args Variables exposed to the view.
348 * @return void
349 */
350 private function view( $view, array $args = array() ) {
351 $file = GUTSLIDER_DIR . '/admin/views/' . $view . '.php';
352
353 if ( ! file_exists( $file ) ) {
354 return;
355 }
356
357 $admin = $this;
358 $has_pro = defined( 'GUTSLIDER_PRO_VERSION' );
359
360 // phpcs:ignore WordPress.PHP.DontExtract.extract_extract -- Controlled, internal view data.
361 extract( $args, EXTR_SKIP );
362
363 include $file;
364 }
365
366 /**
367 * Render the dashboard (overview) screen.
368 *
369 * @return void
370 */
371 public function render_dashboard_page() {
372 $blocks = $this->get_blocks_with_state();
373
374 $this->view(
375 'dashboard',
376 array(
377 'blocks' => $blocks,
378 'total' => count( $blocks ),
379 'enabled' => self::count_enabled( $blocks ),
380 'current' => self::MENU_SLUG,
381 )
382 );
383 }
384
385 /**
386 * Render the blocks manager screen.
387 *
388 * @return void
389 */
390 public function render_blocks_page() {
391 $blocks = $this->get_blocks_with_state();
392
393 $this->view(
394 'blocks',
395 array(
396 'blocks' => $blocks,
397 'total' => count( $blocks ),
398 'enabled' => self::count_enabled( $blocks ),
399 'current' => self::BLOCKS_SLUG,
400 )
401 );
402 }
403
404 /**
405 * Render the settings screen.
406 *
407 * @return void
408 */
409 public function render_settings_page() {
410 $this->view(
411 'settings',
412 array(
413 'settings' => self::get_settings(),
414 'current' => self::SETTINGS_SLUG,
415 )
416 );
417 }
418
419 /* ---------------------------------------------------------------
420 * REST endpoints
421 * ------------------------------------------------------------- */
422
423 /**
424 * Register the dashboard REST routes.
425 *
426 * @return void
427 */
428 public function register_routes() {
429 register_rest_route(
430 'gutslider/v1',
431 '/dashboard/blocks',
432 array(
433 'methods' => WP_REST_Server::CREATABLE,
434 'callback' => array( $this, 'rest_save_blocks' ),
435 'permission_callback' => array( $this, 'can_manage' ),
436 'args' => array(
437 'blocks' => array(
438 'required' => true,
439 'type' => 'object',
440 ),
441 ),
442 )
443 );
444
445 register_rest_route(
446 'gutslider/v1',
447 '/dashboard/settings',
448 array(
449 'methods' => WP_REST_Server::CREATABLE,
450 'callback' => array( $this, 'rest_save_settings' ),
451 'permission_callback' => array( $this, 'can_manage' ),
452 'args' => array(
453 'settings' => array(
454 'required' => true,
455 'type' => 'object',
456 ),
457 ),
458 )
459 );
460
461 register_rest_route(
462 'gutslider/v1',
463 '/dashboard/clear-cache',
464 array(
465 'methods' => WP_REST_Server::CREATABLE,
466 'callback' => array( $this, 'rest_clear_cache' ),
467 'permission_callback' => array( $this, 'can_manage' ),
468 )
469 );
470 }
471
472 /**
473 * Permission callback for the dashboard routes.
474 *
475 * @return bool True when the user may manage options.
476 */
477 public function can_manage() {
478 return current_user_can( 'manage_options' );
479 }
480
481 /**
482 * Persist the enabled state of one or more blocks.
483 *
484 * Writes both the per-block option consumed by the block registrar
485 * and the `active` flag on the cached block list, keeping the two
486 * storage formats in sync.
487 *
488 * @param WP_REST_Request $request Incoming request.
489 * @return WP_REST_Response|WP_Error Response payload.
490 */
491 public function rest_save_blocks( WP_REST_Request $request ) {
492 $requested = $request->get_param( 'blocks' );
493
494 if ( ! is_array( $requested ) || empty( $requested ) ) {
495 return new WP_Error(
496 'gutslider_invalid_payload',
497 __( 'No block changes were supplied.', 'slider-blocks' ),
498 array( 'status' => 400 )
499 );
500 }
501
502 $has_pro = defined( 'GUTSLIDER_PRO_VERSION' );
503 $known = array();
504
505 foreach ( $this->get_blocks() as $block ) {
506 $known[ $block['name'] ] = ! empty( $block['is_pro'] );
507 }
508
509 $saved = array();
510
511 foreach ( $requested as $name => $enabled ) {
512 $name = sanitize_text_field( (string) $name );
513
514 if ( ! isset( $known[ $name ] ) ) {
515 continue;
516 }
517
518 // Pro blocks cannot be enabled without the Pro plugin.
519 if ( $known[ $name ] && ! $has_pro ) {
520 continue;
521 }
522
523 $enabled = rest_sanitize_boolean( $enabled );
524
525 /*
526 * Store '1'/'0' rather than a boolean: update_option() treats a
527 * `false` value as identical to a missing option and skips the
528 * write, which would silently drop the first "disable" of a
529 * block that has never been toggled before.
530 */
531 update_option( self::block_option_key( $name ), $enabled ? '1' : '0' );
532 $saved[ $name ] = $enabled;
533 }
534
535 $this->sync_block_list( $saved );
536
537 return rest_ensure_response(
538 array(
539 'success' => true,
540 'blocks' => $saved,
541 )
542 );
543 }
544
545 /**
546 * Mirror enabled states onto the cached `gutslider_blocks` option.
547 *
548 * @param array<string, bool> $saved Map of block name to enabled state.
549 * @return void
550 */
551 private function sync_block_list( array $saved ) {
552 if ( empty( $saved ) ) {
553 return;
554 }
555
556 $blocks = get_option( 'gutslider_blocks' );
557
558 if ( ! is_array( $blocks ) || empty( $blocks ) ) {
559 return;
560 }
561
562 $changed = false;
563
564 foreach ( $blocks as $index => $block ) {
565 if ( isset( $block['name'], $saved[ $block['name'] ] ) ) {
566 $blocks[ $index ]['active'] = $saved[ $block['name'] ];
567 $changed = true;
568 }
569 }
570
571 if ( $changed ) {
572 update_option( 'gutslider_blocks', $blocks );
573 }
574 }
575
576 /**
577 * Persist the general settings.
578 *
579 * @param WP_REST_Request $request Incoming request.
580 * @return WP_REST_Response|WP_Error Response payload.
581 */
582 public function rest_save_settings( WP_REST_Request $request ) {
583 $incoming = $request->get_param( 'settings' );
584
585 if ( ! is_array( $incoming ) ) {
586 return new WP_Error(
587 'gutslider_invalid_payload',
588 __( 'No settings were supplied.', 'slider-blocks' ),
589 array( 'status' => 400 )
590 );
591 }
592
593 $settings = self::get_settings();
594 $previous_delivery = $settings['css_delivery'];
595
596 if ( isset( $incoming['css_delivery'] ) ) {
597 $settings['css_delivery'] = in_array( $incoming['css_delivery'], array( 'file', 'inline' ), true )
598 ? $incoming['css_delivery']
599 : 'file';
600 }
601
602 if ( isset( $incoming['google_fonts'] ) ) {
603 $settings['google_fonts'] = rest_sanitize_boolean( $incoming['google_fonts'] );
604 }
605
606 if ( isset( $incoming['pattern_library'] ) ) {
607 $settings['pattern_library'] = rest_sanitize_boolean( $incoming['pattern_library'] );
608 }
609
610 if ( isset( $incoming['remove_data'] ) ) {
611 $settings['remove_data'] = rest_sanitize_boolean( $incoming['remove_data'] );
612 }
613
614 update_option( self::SETTINGS_OPTION, $settings );
615
616 /*
617 * Switching to inline delivery leaves the previously generated
618 * stylesheets orphaned in the uploads directory, so clear them out.
619 */
620 if ( 'inline' === $settings['css_delivery'] && 'inline' !== $previous_delivery ) {
621 self::delete_generated_css();
622 }
623
624 return rest_ensure_response(
625 array(
626 'success' => true,
627 'settings' => $settings,
628 )
629 );
630 }
631
632 /**
633 * Delete every generated stylesheet in the uploads directory.
634 *
635 * @return WP_REST_Response Response payload.
636 */
637 public function rest_clear_cache() {
638 return rest_ensure_response(
639 array(
640 'success' => true,
641 'deleted' => self::delete_generated_css(),
642 )
643 );
644 }
645
646 /**
647 * Remove all generated stylesheets from the uploads directory.
648 *
649 * @return int Number of files deleted.
650 */
651 private static function delete_generated_css() {
652 $upload_dir = wp_upload_dir();
653 $css_dir = trailingslashit( $upload_dir['basedir'] ) . 'gutslider-styles';
654 $deleted = 0;
655
656 if ( is_dir( $css_dir ) ) {
657 $files = glob( $css_dir . '/*.css' );
658
659 if ( is_array( $files ) ) {
660 foreach ( $files as $file ) {
661 if ( is_file( $file ) && wp_delete_file_from_directory( $file, $css_dir ) ) {
662 ++$deleted;
663 }
664 }
665 }
666 }
667
668 return $deleted;
669 }
670 }
671 }
672
673 new GutSlider_Admin();
674