PluginProbe
PatternsWP – Gutenberg Block Patterns & Page Templates Library / trunk
PatternsWP – Gutenberg Block Patterns & Page Templates Library vtrunk
trunk 1.0.0 1.0.1 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9
← All changes | includes/class-patternswp-admin.php +856 -14 1.0.7trunk View file →
@@ -2,8 +2,13 @@
2 2 /**
3 3 * Admin Page
4 4 */
5 5
6 +// Exit if accessed directly
7 +if (!defined('ABSPATH')) {
8 + exit;
9 +}
10 +
6 11 class PatternsWP_Admin {
7 12
8 13 /**
9 14 * Constructor
@@ -11,15 +16,64 @@
11 16 public function __construct() {
12 17 // Add menu and pages
13 18 add_action('admin_menu', array($this, 'add_admin_menu'));
14 19 add_action('admin_init', array($this, 'patternswp_clear_cache'));
15 - add_action( 'admin_init', array( $this, 'patternswp_ensure_hourly_cron' ) );
16 - add_action( 'wp_ajax_patternswp_background_transient_load_ajax', array( $this, 'patternswp_background_transient_load_ajaxcc' ) );
17 - add_action( 'wp_ajax_nopriv_patternswp_background_transient_load_ajax', array( $this, 'patternswp_background_transient_load_ajaxcc' ) );
18 - add_action( 'patternswp_load_patterns_by_ra', array( $this, 'patternswp_load_patterns_by_remote_ajax' ) );
20 + add_action('admin_init', array($this, 'patternswp_ensure_hourly_cron'));
21 + add_action('wp_ajax_patternswp_background_transient_load_ajax', array($this, 'patternswp_background_transient_load_ajaxcc'));
22 + add_action('wp_ajax_nopriv_patternswp_background_transient_load_ajax', array($this, 'patternswp_background_transient_load_ajaxcc'));
23 + add_action('patternswp_load_patterns_by_ra', array($this, 'patternswp_load_patterns_by_remote_ajax'));
19 24 register_activation_hook(plugin_dir_path(__DIR__) . 'patternswp.php', array($this, 'patternswp_on_activation'));
20 25 add_action('admin_init', array($this, 'patternswp_redirect_on_activation'));
26 +
27 + // Initialize settings
28 + add_action('admin_init', array($this, 'register_pattern_visibility_settings'));
29 +
30 + // Add pattern deregistration hooks
31 + add_action('init', array($this, 'maybe_deregister_core_patterns'), 999);
32 + add_action('init', array($this, 'maybe_deregister_theme_patterns'), 1000);
33 + add_action('init', array($this, 'maybe_deregister_uncategorized_patterns'), 1001);
34 +
35 + // Add a second check later to ensure patterns are deregistered
36 + add_action('init', array($this, 'maybe_deregister_theme_patterns'), 9999);
37 + add_action('init', array($this, 'maybe_deregister_uncategorized_patterns'), 10000);
38 + add_action('init', array($this, 'maybe_deregister_core_patterns'), 10001);
39 +
40 + // error_log('[PatternsWP] Constructor - All pattern deregistration hooks registered');
41 +
42 + // Apply filters for different pattern sources
43 + add_filter('patternswp_patterns', array($this, 'filter_patterns_by_visibility'), 20);
44 + add_filter('block_editor_settings_all', array($this, 'filter_block_editor_settings'), 10, 2);
45 +
46 + // Filter REST API patterns
47 + add_filter('rest_wp_block_query', array($this, 'filter_rest_patterns'), 10, 2);
48 +
49 + // Handle core and theme patterns
50 + add_filter('should_load_remote_block_patterns', array($this, 'maybe_disable_remote_patterns'), 10, 1);
51 + add_filter('should_load_remote_patterns_default_override', array($this, 'maybe_disable_remote_patterns'), 10, 1);
52 +
53 + // Add action to clear pattern cache when theme changes
54 + add_action('switch_theme', array($this, 'clear_pattern_cache_on_theme_switch'));
55 +
56 + // Also filter the REST API response directly
57 + add_filter('rest_prepare_wp_block', array($this, 'filter_rest_block_response'), 10, 3);
58 +
59 + // Filter the block patterns list in the editor
60 + add_filter('wp_block_patterns', array($this, 'filter_block_patterns_list'), 10, 1);
61 +
62 + // Add a test hook to verify settings are loaded
63 + add_action('wp_loaded', array($this, 'test_settings'));
21 64 }
65 +
66 + /**
67 + * Test method to verify settings are loaded
68 + */
69 + public function test_settings() {
70 + $hide_theme = get_option('patternswp_hide_theme_patterns', false);
71 + $hide_uncategorized = get_option('patternswp_hide_uncategorized_patterns', false);
72 + $hide_core = get_option('patternswp_hide_core_patterns', false);
73 +
74 + // error_log('[PatternsWP] Settings Check - Theme: ' . ($hide_theme ? 'true' : 'false') . ', Uncategorized: ' . ($hide_uncategorized ? 'true' : 'false') . ', Core: ' . ($hide_core ? 'true' : 'false'));
75 + }
22 76
23 77 /**
24 78 * Add admin menu
25 79 */
@@ -47,10 +101,10 @@
47 101 );
48 102
49 103 // Add subpages
50 104 $this->add_subpage($menu_slug, 'Dashboard', 'Dashboard', 'patternswp-plugin-menu', array($this, 'render_main_page'));
105 + $this->add_subpage($menu_slug, 'Settings', 'Settings', 'patternswp-settings', array($this, 'render_pattern_visibility_page'));
51 106 $this->add_subpage($menu_slug, 'Support', 'Support', 'patternswp-plugin-page-1', array($this, 'patternswp_support'));
52 - $this->add_subpage($menu_slug, 'Clear Cache', 'Clear Cache', 'patternswp-clear-cache', array($this, 'patternswp_clear_cache_form'));
53 107 }
54 108
55 109 /**
56 110 * Add subpage
@@ -65,15 +119,29 @@
65 119 */
66 120 public function patterswp_tab( $tab ) {
67 121 $tabs = array(
68 122 'Dashboard' => 'patternswp-plugin-menu',
123 + 'Settings' => 'patternswp-settings',
69 124 'Support' => 'patternswp-plugin-page-1',
70 - 'Clear Cache' => 'patternswp-clear-cache',
71 125 'License' => 'patternswp-license_section'
72 126 );
73 127
128 + // Get the current page from URL parameter
129 + $current_page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'patternswp-plugin-menu';
130 +
131 + // Map page slugs to tab names
132 + $page_to_tab = array(
133 + 'patternswp-plugin-menu' => 'Dashboard',
134 + 'patternswp-settings' => 'Settings',
135 + 'patternswp-plugin-page-1' => 'Support',
136 + 'patternswp-license_section' => 'License'
137 + );
138 +
139 + // Determine current tab
140 + $current_tab = isset($page_to_tab[$current_page]) ? $page_to_tab[$current_page] : 'Dashboard';
141 +
74 142 foreach ($tabs as $name => $slug) {
75 - $class = ( $tab === $name ) ? 'nav-tab-active' : '';
143 + $class = ( $current_tab === $name ) ? 'nav-tab-active' : '';
76 144 echo '<a href="?page=' . esc_attr($slug) . '" class="nav-tab ' . esc_attr($class) . '">' . esc_html($name) . '</a>';
77 145 }
78 146 }
79 147
@@ -219,9 +287,9 @@
219 287 </h2>
220 288 <div class="patterns-wp-tabs-content">
221 289 <div id="tab-1" class="patterns-wp-tab-content patterns-wp-tab-active">
222 290 <div class="wrap">
223 - <div class="container" style="padding: 20px; background-color: white; border-radius: 5px; overflow: hidden; width: 70%;">
291 + <div class="pw-feature-box big-box" style="max-width: 1280px; padding: 40px; background-color: white; border-radius: 10px; overflow: hidden; margin: 0 auto;">
224 292 <div class="">
225 293 <form id="patternswp_clearcache_form" method="post">
226 294 <?php wp_nonce_field('patternswp_clear_cache_action', 'patternswp_clear_cache_nonce'); ?>
227 295 <input name="patternswp_clear_cache" type="submit" class="button button-primary" value="Clear Cache">
@@ -236,8 +304,37 @@
236 304 </div>
237 305 <?php }
238 306
239 307 /**
308 + * Clear pattern cache when theme is switched
309 + */
310 + public function clear_pattern_cache_on_theme_switch() {
311 + $this->clear_pattern_cache(false, false);
312 + }
313 +
314 + /**
315 + * Handle remote pattern loading based on settings
316 + */
317 + public function maybe_disable_remote_patterns($should_load) {
318 + if (get_option('patternswp_hide_core_patterns', false)) {
319 + return false;
320 + }
321 + return $should_load;
322 + }
323 +
324 + /**
325 + * Filter block editor settings to handle pattern visibility
326 + */
327 + public function filter_block_editor_settings($settings, $context) {
328 + if (get_option('patternswp_hide_core_patterns', false)) {
329 + $settings['__experimentalBlockPatterns'] = array();
330 + $settings['__experimentalBlockPatternCategories'] = array();
331 + $settings['__experimentalBlockPatterns'] = array();
332 + }
333 + return $settings;
334 + }
335 +
336 + /**
240 337 * Clear Cache
241 338 */
242 339 public function patternswp_clear_cache() {
243 340 global $wpdb;
@@ -279,16 +376,16 @@
279 376 delete_option($transient);
280 377 delete_option(str_replace('_transient_', '_transient_timeout_', $transient));
281 378 }
282 379
283 - //load patterns in background
380 + // Load patterns in background
284 381 $this->patternswp_load_patterns_by_remote_ajax();
285 382
286 - wp_redirect(add_query_arg(array(
287 - 'page' => 'patternswp-clear-cache',
288 - 'pt_msg' => urlencode('Clear Cache Successfully'),
289 - 'status' => 'success'
290 - ), admin_url('admin.php')));
383 + // Set a transient for the success message
384 + set_transient('patternswp_cache_cleared', 'Cache cleared successfully', 5);
385 +
386 + // Redirect to Settings page
387 + wp_safe_redirect(admin_url('admin.php?page=patternswp-settings'));
291 388 exit;
292 389 }
293 390 }
294 391
@@ -326,8 +423,753 @@
326 423 * Set transient on plugin activation
327 424 */
328 425 public function patternswp_on_activation() {
329 426 set_transient('patternswp_activation_redirect', true, 30);
427 + }
428 +
429 + /**
430 + * Clear pattern cache when visibility settings change
431 + */
432 + public function clear_pattern_cache($old_value, $new_value) {
433 + // Clear object cache first
434 + if (function_exists('wp_cache_flush')) {
435 + wp_cache_flush();
436 + }
437 +
438 + // Clear known transients
439 + $transients = [
440 + 'patternswp_all_patterns',
441 + 'patternswp_categorized_patterns',
442 + 'patternswp_patterns_data',
443 + 'patternswp_categories'
444 + ];
445 +
446 + foreach ($transients as $transient) {
447 + delete_transient($transient);
448 + }
449 +
450 + // Clear any remaining transients with our prefix
451 + if (!function_exists('delete_expired_transients')) {
452 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
453 + }
454 +
455 + // This is a core function that will clean up expired transients
456 + delete_expired_transients(true);
457 +
458 + // Clear specific pattern caches
459 + if (function_exists('wp_cache_delete')) {
460 + wp_cache_delete('all_patterns', 'patternswp');
461 + wp_cache_delete('categorized_patterns', 'patternswp');
462 + }
463 + }
464 +
465 + /**
466 + * Attempt to deregister any patterns from the active or child theme.
467 + */
468 + public function maybe_deregister_theme_patterns() {
469 + $hide_theme_patterns = get_option('patternswp_hide_theme_patterns', false);
470 + // error_log('[PatternsWP] maybe_deregister_theme_patterns called - hide_theme_patterns: ' . ($hide_theme_patterns ? 'true' : 'false'));
471 +
472 + if (!$hide_theme_patterns) {
473 + // error_log('[PatternsWP] Theme pattern hiding is disabled');
474 + return;
475 + }
476 +
477 + // Get the active theme.
478 + $theme = wp_get_theme();
479 +
480 + // Let's get some theme data.
481 + $paths_to_search = array(
482 + $theme->stylesheet,
483 + );
484 +
485 + // Check if this is a child theme.
486 + if ($theme->parent()) {
487 + $parent_theme = wp_get_theme($theme->parent()->template);
488 + $paths_to_search[] = $parent_theme->template;
489 + }
490 + $paths_to_search = array_unique($paths_to_search);
491 +
492 + // error_log('[PatternsWP] Theme paths to search: ' . implode(', ', $paths_to_search));
493 +
494 + // Get all registered patterns.
495 + $patterns = \WP_Block_Patterns_Registry::get_instance();
496 + $all_patterns = $patterns->get_all_registered();
497 +
498 + // error_log('[PatternsWP] Total registered patterns: ' . count($all_patterns));
499 +
500 + // Loop through all patterns and deregister any that are from the active or child theme.
501 + foreach ($all_patterns as $index => $pattern) {
502 + $file_path = $pattern['filePath'] ?? '';
503 + if (empty($file_path)) {
504 + continue;
505 + }
506 +
507 + // Check if the pattern is from the active or child theme.
508 + foreach ($paths_to_search as $path) {
509 + if (false !== strpos($file_path, 'themes/' . $path)) {
510 + unregister_block_pattern($pattern['name']);
511 + // error_log('[PatternsWP] Deregistered theme pattern: ' . $pattern['name'] . ' from path: ' . $file_path);
512 + }
513 + }
514 + }
515 + }
516 +
517 + /**
518 + * Deregister any uncategorized patterns.
519 + */
520 + public function maybe_deregister_uncategorized_patterns() {
521 + $hide_uncategorized_enabled = get_option('patternswp_hide_uncategorized_patterns', false);
522 + // error_log('[PatternsWP] maybe_deregister_uncategorized_patterns called - hide_uncategorized: ' . ($hide_uncategorized_enabled ? 'true' : 'false'));
523 +
524 + // Exit early if not enabled.
525 + if (!$hide_uncategorized_enabled) {
526 + // error_log('[PatternsWP] Uncategorized pattern hiding is disabled');
527 + return;
528 + }
529 +
530 + // Retrieve all patterns.
531 + $patterns = \WP_Block_Patterns_Registry::get_instance();
532 + $all_patterns = $patterns->get_all_registered();
533 +
534 + // error_log('[PatternsWP] Checking ' . count($all_patterns) . ' patterns for uncategorized ones');
535 +
536 + // Loop through all patterns and deregister any that are uncategorized.
537 + foreach ($all_patterns as $index => $pattern) {
538 + if (!isset($pattern['categories']) || empty($pattern['categories'])) {
539 + unregister_block_pattern($pattern['name']);
540 + // error_log('[PatternsWP] Deregistered uncategorized pattern: ' . $pattern['name']);
541 + } else {
542 + $found = false;
543 + $block_categories = $pattern['categories'] ?? array();
544 + foreach ($block_categories as $block_category) {
545 + $categories = \WP_Block_Pattern_Categories_Registry::get_instance();
546 + if ($categories->is_registered($block_category)) {
547 + $found = true;
548 + }
549 + }
550 + if (!$found) {
551 + unregister_block_pattern($pattern['name']);
552 + // error_log('[PatternsWP] Deregistered uncategorized pattern (invalid category): ' . $pattern['name']);
553 + }
554 + }
555 + }
556 + }
557 +
558 + /**
559 + * Deregister core patterns.
560 + */
561 + public function maybe_deregister_core_patterns() {
562 + $hide_core_patterns = get_option('patternswp_hide_core_patterns', false);
563 + // error_log('[PatternsWP] maybe_deregister_core_patterns called - hide_core_patterns: ' . ($hide_core_patterns ? 'true' : 'false'));
564 +
565 + // Exit early if not enabled.
566 + if (!$hide_core_patterns) {
567 + // error_log('[PatternsWP] Core pattern hiding is disabled');
568 + return;
569 + }
570 +
571 + // Retrieve all patterns.
572 + $patterns = \WP_Block_Patterns_Registry::get_instance();
573 + $all_patterns = $patterns->get_all_registered();
574 +
575 + // error_log('[PatternsWP] Checking ' . count($all_patterns) . ' patterns for core ones');
576 +
577 + // Loop through all patterns and deregister any that are core patterns.
578 + foreach ($all_patterns as $index => $pattern) {
579 + // Core patterns typically have names starting with 'core/' or no file path
580 + $pattern_name = $pattern['name'] ?? '';
581 + $file_path = $pattern['filePath'] ?? '';
582 +
583 + // Check if it's a core pattern
584 + $is_core_pattern = false;
585 + $reason = '';
586 +
587 + // Method 1: Check if pattern name starts with 'core/'
588 + if (strpos($pattern_name, 'core/') === 0) {
589 + $is_core_pattern = true;
590 + $reason = 'name starts with core/';
591 + }
592 +
593 + // Method 2: Check if file path contains wp-includes
594 + if (!$is_core_pattern && !empty($file_path) && strpos($file_path, 'wp-includes') !== false) {
595 + $is_core_pattern = true;
596 + $reason = 'file path contains wp-includes';
597 + }
598 +
599 + // Method 3: Check if no file path (likely core pattern)
600 + if (!$is_core_pattern && empty($file_path)) {
601 + $is_core_pattern = true;
602 + $reason = 'no file path';
603 + }
604 +
605 + if ($is_core_pattern) {
606 + unregister_block_pattern($pattern['name']);
607 + // error_log('[PatternsWP] Deregistered core pattern: ' . $pattern['name'] . ' (' . $reason . ')');
608 + }
609 + }
610 + }
611 +
612 + /**
613 + * Filter the block patterns list in the editor
614 + */
615 + public function filter_block_patterns_list($patterns) {
616 + // error_log('[PatternsWP] filter_block_patterns_list called with ' . count($patterns) . ' patterns');
617 +
618 + $hide_theme_patterns = get_option('patternswp_hide_theme_patterns', false);
619 + $hide_uncategorized = get_option('patternswp_hide_uncategorized_patterns', false);
620 + $hide_core_patterns = get_option('patternswp_hide_core_patterns', false);
621 +
622 + // error_log('[PatternsWP] Block patterns filter - Theme: ' . ($hide_theme_patterns ? 'true' : 'false') . ', Uncategorized: ' . ($hide_uncategorized ? 'true' : 'false') . ', Core: ' . ($hide_core_patterns ? 'true' : 'false'));
623 +
624 + // If no filters are enabled, return original patterns
625 + if (!$hide_theme_patterns && !$hide_uncategorized && !$hide_core_patterns) {
626 + return $patterns;
627 + }
628 +
629 + $filtered_patterns = array();
630 +
631 + foreach ($patterns as $pattern) {
632 + $should_include = true;
633 +
634 + // Check theme patterns
635 + if ($hide_theme_patterns && $should_include) {
636 + // Check if pattern name starts with theme prefix
637 + if (isset($pattern['name']) && strpos($pattern['name'], 'theme-') === 0) {
638 + $should_include = false;
639 + // error_log('[PatternsWP] Excluding theme pattern by name: ' . $pattern['name']);
640 + }
641 + // Check if pattern has theme file path
642 + if (isset($pattern['filePath']) && strpos($pattern['filePath'], 'themes/') !== false) {
643 + $should_include = false;
644 + // error_log('[PatternsWP] Excluding theme pattern by path: ' . $pattern['name']);
645 + }
646 + }
647 +
648 + // Check uncategorized patterns
649 + if ($hide_uncategorized && $should_include) {
650 + if (!isset($pattern['categories']) || empty($pattern['categories'])) {
651 + $should_include = false;
652 + // error_log('[PatternsWP] Excluding uncategorized pattern: ' . $pattern['name']);
653 + }
654 + }
655 +
656 + // Check core patterns
657 + if ($hide_core_patterns && $should_include) {
658 + if (isset($pattern['name']) && strpos($pattern['name'], 'core/') === 0) {
659 + $should_include = false;
660 + // error_log('[PatternsWP] Excluding core pattern: ' . $pattern['name']);
661 + }
662 + }
663 +
664 + if ($should_include) {
665 + $filtered_patterns[] = $pattern;
666 + }
667 + }
668 +
669 + // error_log('[PatternsWP] Filtered from ' . count($patterns) . ' to ' . count($filtered_patterns) . ' patterns');
670 +
671 + return $filtered_patterns;
672 + }
673 +
674 + /**
675 + * Filter REST API response for individual blocks
676 + */
677 + public function filter_rest_block_response($response, $post, $request) {
678 + // Only filter in the block editor context
679 + if (!isset($request['context']) || $request['context'] !== 'edit') {
680 + return $response;
681 + }
682 +
683 + $hide_theme_patterns = get_option('patternswp_hide_theme_patterns', false);
684 + $hide_uncategorized = get_option('patternswp_hide_uncategorized_patterns', false);
685 + $hide_core_patterns = get_option('patternswp_hide_core_patterns', false);
686 +
687 + // If no filters are enabled, return original response
688 + if (!$hide_theme_patterns && !$hide_uncategorized && !$hide_core_patterns) {
689 + return $response;
690 + }
691 +
692 + // Get the pattern data
693 + $pattern_data = $response->data;
694 +
695 + // Check if this should be hidden
696 + $should_hide = false;
697 +
698 + // Check theme patterns
699 + if ($hide_theme_patterns) {
700 + // Check if pattern has theme-specific metadata
701 + if (isset($pattern_data['patternswp_source']) && $pattern_data['patternswp_source'] === 'theme') {
702 + $should_hide = true;
703 + }
704 + }
705 +
706 + // Check uncategorized patterns
707 + if (!$should_hide && $hide_uncategorized) {
708 + if (empty($pattern_data['pattern_categories'])) {
709 + $should_hide = true;
710 + }
711 + }
712 +
713 + // Check core patterns
714 + if (!$should_hide && $hide_core_patterns) {
715 + if (isset($pattern_data['name']) && strpos($pattern_data['name'], 'core/') === 0) {
716 + $should_hide = true;
717 + }
718 + }
719 +
720 + if ($should_hide) {
721 + // error_log('[PatternsWP] Filtering REST response for pattern: ' . ($pattern_data['title'] ?? 'Unknown'));
722 + // Return an error response to hide this pattern
723 + return new WP_Error(
724 + 'rest_pattern_hidden',
725 + 'Pattern is hidden by visibility settings',
726 + array('status' => 404)
727 + );
728 + }
729 +
730 + return $response;
731 + }
732 +
733 + /**
734 + * Filter REST API patterns
735 + */
736 + public function filter_rest_patterns($args, $request) {
737 + // Only filter if we're in the block editor context
738 + if (!isset($request['context']) || $request['context'] !== 'edit') {
739 + return $args;
740 + }
741 +
742 + // Get the current registered patterns
743 + $patterns = \WP_Block_Patterns_Registry::get_instance();
744 + $all_patterns = $patterns->get_all_registered();
745 +
746 + $hide_theme_patterns = get_option('patternswp_hide_theme_patterns', false);
747 + $hide_uncategorized = get_option('patternswp_hide_uncategorized_patterns', false);
748 + $hide_core_patterns = get_option('patternswp_hide_core_patterns', false);
749 +
750 + // If no filters are enabled, return original args
751 + if (!$hide_theme_patterns && !$hide_uncategorized && !$hide_core_patterns) {
752 + return $args;
753 + }
754 +
755 + // Get theme info for theme pattern detection
756 + $theme = wp_get_theme();
757 + $paths_to_search = array($theme->stylesheet);
758 + if ($theme->parent()) {
759 + $parent_theme = wp_get_theme($theme->parent()->template);
760 + $paths_to_search[] = $parent_theme->template;
761 + }
762 + $paths_to_search = array_unique($paths_to_search);
763 +
764 + // Build list of pattern names to exclude
765 + $exclude_patterns = array();
766 +
767 + foreach ($all_patterns as $pattern) {
768 + $should_exclude = false;
769 +
770 + // Check if it's a theme pattern
771 + if ($hide_theme_patterns) {
772 + $file_path = $pattern['filePath'] ?? '';
773 + if (!empty($file_path)) {
774 + foreach ($paths_to_search as $path) {
775 + if (false !== strpos($file_path, 'themes/' . $path)) {
776 + $should_exclude = true;
777 + break;
778 + }
779 + }
780 + }
781 + }
782 +
783 + // Check if it's a core pattern
784 + if (!$should_exclude && $hide_core_patterns) {
785 + $pattern_name = $pattern['name'] ?? '';
786 + $file_path = $pattern['filePath'] ?? '';
787 +
788 + // Method 1: Check if pattern name starts with 'core/'
789 + if (strpos($pattern_name, 'core/') === 0) {
790 + $should_exclude = true;
791 + }
792 +
793 + // Method 2: Check if file path contains wp-includes
794 + if (!$should_exclude && !empty($file_path) && strpos($file_path, 'wp-includes') !== false) {
795 + $should_exclude = true;
796 + }
797 +
798 + // Method 3: Check if no file path (likely core pattern)
799 + if (!$should_exclude && empty($file_path)) {
800 + $should_exclude = true;
801 + }
802 + }
803 +
804 + // Check if it's uncategorized
805 + if (!$should_exclude && $hide_uncategorized) {
806 + if (!isset($pattern['categories']) || empty($pattern['categories'])) {
807 + $should_exclude = true;
808 + } else {
809 + $found = false;
810 + $block_categories = $pattern['categories'] ?? array();
811 + foreach ($block_categories as $block_category) {
812 + $categories = \WP_Block_Pattern_Categories_Registry::get_instance();
813 + if ($categories->is_registered($block_category)) {
814 + $found = true;
815 + break;
816 + }
817 + }
818 + if (!$found) {
819 + $should_exclude = true;
820 + }
821 + }
822 + }
823 +
824 + if ($should_exclude) {
825 + $exclude_patterns[] = $pattern['name'];
826 + }
827 + }
828 +
829 + // Add exclude filter using direct SQL for better performance
830 + if (!empty($exclude_patterns)) {
831 + add_filter('posts_where', function($where) use ($exclude_patterns) {
832 + global $wpdb;
833 +
834 + // Prepare placeholders for the IN clause
835 + $placeholders = array_fill(0, count($exclude_patterns), '%s');
836 + $placeholders = implode(',', $placeholders);
837 +
838 + // Prepare the exclusion subquery
839 + $exclude_sql = $wpdb->prepare(
840 + "SELECT post_id
841 + FROM {$wpdb->postmeta}
842 + WHERE meta_key = 'pattern_id'
843 + AND meta_value IN ($placeholders)",
844 + $exclude_patterns
845 + );
846 +
847 + // Add the exclusion to the WHERE clause
848 + $where .= " AND {$wpdb->posts}.ID NOT IN ($exclude_sql)";
849 +
850 + return $where;
851 + });
852 +
853 + // Ensure we don't use post__not_in or meta_query
854 + unset($args['post__not_in']);
855 + unset($args['meta_query']);
856 + }
857 +
858 + return $args;
859 + }
860 +
861 + /**
862 + * Filter patterns based on visibility settings
863 + */
864 + /**
865 + * Check if a pattern is a theme pattern
866 + */
867 + private function is_theme_pattern($pattern) {
868 + // Check source
869 + if (!empty($pattern['source']) && $pattern['source'] === 'theme') {
870 + return true;
871 + }
872 +
873 + // Check name prefix
874 + if (!empty($pattern['name']) && (strpos($pattern['name'], 'theme-') === 0 ||
875 + strpos($pattern['name'], 'tw/') === 0)) {
876 + return true;
877 + }
878 +
879 + // Check categories
880 + if (!empty($pattern['categories'])) {
881 + $categories = is_array($pattern['categories']) ?
882 + $pattern['categories'] :
883 + array_map('trim', explode(',', $pattern['categories']));
884 +
885 + foreach ($categories as $category) {
886 + if (is_string($category) &&
887 + (stripos($category, 'theme') !== false ||
888 + stripos($category, 'template') !== false)) {
889 + return true;
890 + }
891 + }
892 + }
893 +
894 + return false;
895 + }
896 +
897 + /**
898 + * Check if a pattern is a core pattern
899 + */
900 + private function is_core_pattern($pattern) {
901 + // Check source
902 + if (!empty($pattern['source']) && $pattern['source'] === 'core') {
903 + return true;
904 + }
905 +
906 + // Check name prefix
907 + if (!empty($pattern['name']) && strpos($pattern['name'], 'core/') === 0) {
908 + return true;
909 + }
910 +
911 + return false;
912 + }
913 +
914 + /**
915 + * Check if a pattern is uncategorized
916 + */
917 + private function is_uncategorized_pattern($pattern) {
918 + if (empty($pattern['categories'])) {
919 + return true;
920 + }
921 +
922 + $categories = is_array($pattern['categories']) ?
923 + $pattern['categories'] :
924 + array_filter(array_map('trim', explode(',', $pattern['categories'])));
925 +
926 + return empty($categories);
927 + }
928 +
929 + /**
930 + * Filter patterns based on visibility settings
931 + */
932 + public function filter_patterns_by_visibility($patterns) {
933 + // Debug logging
934 + // error_log('[PatternsWP] Filter applied with ' . count((array)$patterns) . ' patterns');
935 +
936 + // If no patterns or not an array, return early
937 + if (empty($patterns) || !is_array($patterns)) {
938 + // error_log('[PatternsWP] No patterns to filter');
939 + return $patterns;
940 + }
941 +
942 + $filtered_patterns = array();
943 + $hide_theme_patterns = get_option('patternswp_hide_theme_patterns', false);
944 + $hide_uncategorized = get_option('patternswp_hide_uncategorized_patterns', false);
945 + $hide_core_patterns = get_option('patternswp_hide_core_patterns', false);
946 +
947 + // error_log('[PatternsWP] Settings - Hide Theme: ' . ($hide_theme_patterns ? 'true' : 'false') .
948 + // ', Hide Uncategorized: ' . ($hide_uncategorized ? 'true' : 'false') .
949 + // ', Hide Core: ' . ($hide_core_patterns ? 'true' : 'false'));
950 +
951 + foreach ($patterns as $pattern) {
952 + // Skip if pattern is not an array
953 + if (!is_array($pattern)) {
954 + $filtered_patterns[] = $pattern;
955 + continue;
956 + }
957 +
958 + // Skip theme patterns if enabled
959 + if ($hide_theme_patterns && $this->is_theme_pattern($pattern)) {
960 + // error_log('[PatternsWP] Skipping theme pattern: ' . ($pattern['title'] ?? 'Unknown'));
961 + continue;
962 + }
963 +
964 + // Skip core patterns if enabled
965 + if ($hide_core_patterns && $this->is_core_pattern($pattern)) {
966 + // error_log('[PatternsWP] Skipping core pattern: ' . ($pattern['title'] ?? 'Unknown'));
967 + continue;
968 + }
969 +
970 + // Skip uncategorized patterns if enabled
971 + if ($hide_uncategorized && $this->is_uncategorized_pattern($pattern)) {
972 + // error_log('[PatternsWP] Skipping uncategorized pattern: ' . ($pattern['title'] ?? 'Unknown'));
973 + continue;
974 + }
975 +
976 + $filtered_patterns[] = $pattern;
977 + }
978 +
979 + // error_log('[PatternsWP] Filtered to ' . count($filtered_patterns) . ' patterns');
980 + return $filtered_patterns;
981 + }
982 +
983 + /**
984 + * Register Pattern Visibility settings
985 + */
986 + public function register_pattern_visibility_settings() {
987 + // Register settings
988 + register_setting('patternswp_visibility_settings', 'patternswp_hide_theme_patterns', array(
989 + 'type' => 'boolean',
990 + 'default' => false,
991 + 'sanitize_callback' => 'rest_sanitize_boolean',
992 + ));
993 +
994 + register_setting('patternswp_visibility_settings', 'patternswp_hide_uncategorized_patterns', array(
995 + 'type' => 'boolean',
996 + 'default' => false,
997 + 'sanitize_callback' => 'rest_sanitize_boolean',
998 + ));
999 +
1000 + register_setting('patternswp_visibility_settings', 'patternswp_hide_core_patterns', array(
1001 + 'type' => 'boolean',
1002 + 'default' => false,
1003 + 'sanitize_callback' => 'rest_sanitize_boolean',
1004 + ));
1005 +
1006 + // Clear cache when settings are updated
1007 + add_action('update_option_patternswp_hide_theme_patterns', array($this, 'clear_pattern_cache'), 10, 2);
1008 + add_action('update_option_patternswp_hide_uncategorized_patterns', array($this, 'clear_pattern_cache'), 10, 2);
1009 + add_action('update_option_patternswp_hide_core_patterns', array($this, 'clear_pattern_cache'), 10, 2);
1010 +
1011 + // Add settings section
1012 + add_settings_section(
1013 + 'patternswp_visibility_section',
1014 + __('Pattern Visibility', 'patternswp'),
1015 + array($this, 'pattern_visibility_section_callback'),
1016 + 'patternswp-settings'
1017 + );
1018 +
1019 + // Add settings fields
1020 + add_settings_field(
1021 + 'patternswp_hide_theme_patterns',
1022 + __('Hide Theme Patterns', 'patternswp'),
1023 + array($this, 'render_checkbox_field'),
1024 + 'patternswp-settings',
1025 + 'patternswp_visibility_section',
1026 + array(
1027 + 'label_for' => 'patternswp_hide_theme_patterns',
1028 + 'description' => __('Prevent patterns registered by the active theme from displaying in the patterns list.', 'patternswp')
1029 + )
1030 + );
1031 +
1032 + add_settings_field(
1033 + 'patternswp_hide_uncategorized_patterns',
1034 + __('Hide Uncategorized Patterns', 'patternswp'),
1035 + array($this, 'render_checkbox_field'),
1036 + 'patternswp-settings',
1037 + 'patternswp_visibility_section',
1038 + array(
1039 + 'label_for' => 'patternswp_hide_uncategorized_patterns',
1040 + 'description' => __('Prevent any patterns not in any registered categories from displaying.', 'patternswp')
1041 + )
1042 + );
1043 +
1044 + add_settings_field(
1045 + 'patternswp_hide_core_patterns',
1046 + __('Hide Core Patterns', 'patternswp'),
1047 + array($this, 'render_checkbox_field'),
1048 + 'patternswp-settings',
1049 + 'patternswp_visibility_section',
1050 + array(
1051 + 'label_for' => 'patternswp_hide_core_patterns',
1052 + 'description' => __('Remove all core patterns from the pattern selector by disabling core patterns.', 'patternswp')
1053 + )
1054 + );
1055 + }
1056 +
1057 + /**
1058 + * Pattern Visibility section callback
1059 + */
1060 + public function pattern_visibility_section_callback() {
1061 + echo '<p>' . esc_html__('Control which patterns are visible in the block editor.', 'patternswp') . '</p>';
1062 + }
1063 +
1064 + /**
1065 + * Render checkbox field
1066 + */
1067 + public function render_checkbox_field($args) {
1068 + $option = get_option($args['label_for']);
1069 + ?>
1070 + <label>
1071 + <input type="checkbox"
1072 + name="<?php echo esc_attr($args['label_for']); ?>"
1073 + value="1"
1074 + <?php checked(1, $option, true); ?>>
1075 + <span class="description"><?php echo esc_html($args['description']); ?></span>
1076 + </label>
1077 + <?php
1078 + }
1079 +
1080 + /**
1081 + * Render Pattern Visibility page
1082 + */
1083 + public function render_pattern_visibility_page() {
1084 + // Check user capabilities
1085 + if (!current_user_can('manage_options')) {
1086 + return;
1087 + }
1088 +
1089 + // Show success/error messages
1090 + if (isset($_GET['settings-updated'])) {
1091 + add_settings_error(
1092 + 'patternswp_messages',
1093 + 'patternswp_message',
1094 + __('Settings Saved', 'patternswp'),
1095 + 'updated'
1096 + );
1097 + }
1098 +
1099 + // Show cache clear messages from transient
1100 + $cache_message = get_transient('patternswp_cache_cleared');
1101 + if ($cache_message) {
1102 + add_settings_error(
1103 + 'patternswp_messages',
1104 + 'patternswp_cache_message',
1105 + $cache_message,
1106 + 'updated'
1107 + );
1108 + // Delete the transient so it only shows once
1109 + delete_transient('patternswp_cache_cleared');
1110 + } elseif (isset($_GET['pt_msg']) && isset($_GET['status'])) {
1111 + // Keep backward compatibility with URL parameters
1112 + $message = sanitize_text_field(wp_unslash($_GET['pt_msg']));
1113 + $status = sanitize_text_field(wp_unslash($_GET['status']));
1114 + $type = $status === 'success' ? 'updated' : 'error';
1115 +
1116 + add_settings_error(
1117 + 'patternswp_messages',
1118 + 'patternswp_cache_message',
1119 + $message,
1120 + $type
1121 + );
1122 + }
1123 +
1124 + settings_errors('patternswp_messages');
1125 + ?>
1126 + <div class="wrap">
1127 + <h1><?php echo esc_html(get_admin_page_title()); ?></h1>
1128 +
1129 + <h2 class="nav-tab-wrapper">
1130 + <?php
1131 + $this->patterswp_tab('');
1132 + ?>
1133 + </h2>
1134 + <div class="patterns-wp-tabs-content">
1135 + <div id="tab-1" class="patterns-wp-tab-content patterns-wp-tab-active">
1136 + <div class="wrap">
1137 + <div class="pw-feature-box big-box" style="max-width: 1280px; padding: 40px; background-color: white; border-radius: 10px; overflow: hidden; margin: 0 auto;">
1138 + <form action="options.php" method="post">
1139 + <?php
1140 + // Output security fields
1141 + settings_fields('patternswp_visibility_settings');
1142 + // Output settings sections and fields
1143 + do_settings_sections('patternswp-settings');
1144 + // Output save settings button
1145 + submit_button(__('Save Settings', 'patternswp'));
1146 + ?>
1147 + </form>
1148 +
1149 + <hr style="margin: 30px 0;">
1150 +
1151 + <h3><?php esc_html_e('Cache Management', 'patternswp'); ?></h3>
1152 + <p><?php esc_html_e('Clear all cached patterns and data. This may be necessary if patterns are not updating correctly or after changing visibility settings.', 'patternswp'); ?></p>
1153 +
1154 + <form method="post" action="">
1155 + <?php wp_nonce_field('patternswp_clear_cache_action', 'patternswp_clear_cache_nonce'); ?>
1156 + <input type="hidden" name="patternswp_clear_cache" value="1">
1157 + <?php
1158 + submit_button(
1159 + __('Clear All Cache', 'patternswp'),
1160 + 'primary',
1161 + 'patternswp_clear_cache',
1162 + false
1163 + );
1164 + ?>
1165 + </form>
1166 + </div>
1167 + </div>
1168 + </div>
1169 + </div>
1170 + </div>
1171 + <?php
330 1172 }
331 1173
332 1174 /**
333 1175 * Redirect to welcome page after activation