PluginProbe
PatternsWP – Gutenberg Block Patterns & Page Templates Library / 1.0.5
PatternsWP – Gutenberg Block Patterns & Page Templates Library v1.0.5
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 +24 -956 trunk1.0.5 View file →
@@ -2,13 +2,8 @@
2 2 /**
3 3 * Admin Page
4 4 */
5 5
6 -// Exit if accessed directly
7 -if (!defined('ABSPATH')) {
8 - exit;
9 -}
10 -
11 6 class PatternsWP_Admin {
12 7
13 8 /**
14 9 * Constructor
@@ -16,64 +11,9 @@
16 11 public function __construct() {
17 12 // Add menu and pages
18 13 add_action('admin_menu', array($this, 'add_admin_menu'));
19 14 add_action('admin_init', array($this, 'patternswp_clear_cache'));
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'));
24 - register_activation_hook(plugin_dir_path(__DIR__) . 'patternswp.php', array($this, 'patternswp_on_activation'));
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'));
64 15 }
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 - }
76 16
77 17 /**
78 18 * Add admin menu
79 19 */
@@ -101,10 +41,10 @@
101 41 );
102 42
103 43 // Add subpages
104 44 $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'));
106 45 $this->add_subpage($menu_slug, 'Support', 'Support', 'patternswp-plugin-page-1', array($this, 'patternswp_support'));
46 + $this->add_subpage($menu_slug, 'Clear Cache', 'Clear Cache', 'patternswp-clear-cache', array($this, 'patternswp_clear_cache_form'));
107 47 }
108 48
109 49 /**
110 50 * Add subpage
@@ -114,39 +54,8 @@
114 54 add_submenu_page($parent_slug, $page_title, $menu_title, 'manage_options', $menu_slug, $callback);
115 55 }
116 56
117 57 /**
118 - * Render tabs
119 - */
120 - public function patterswp_tab( $tab ) {
121 - $tabs = array(
122 - 'Dashboard' => 'patternswp-plugin-menu',
123 - 'Settings' => 'patternswp-settings',
124 - 'Support' => 'patternswp-plugin-page-1',
125 - 'License' => 'patternswp-license_section'
126 - );
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 -
142 - foreach ($tabs as $name => $slug) {
143 - $class = ( $current_tab === $name ) ? 'nav-tab-active' : '';
144 - echo '<a href="?page=' . esc_attr($slug) . '" class="nav-tab ' . esc_attr($class) . '">' . esc_html($name) . '</a>';
145 - }
146 - }
147 -
148 - /**
149 58 * Render main page
150 59 */
151 60 public function render_main_page() { ?>
152 61 <div class="wrap">
@@ -151,9 +60,12 @@
151 60 public function render_main_page() { ?>
152 61 <div class="wrap">
153 62 <h1><?php esc_html_e('PatternsWP', 'patternswp'); ?></h1>
154 63 <h2 class="nav-tab-wrapper">
155 - <?php $this->patterswp_tab( 'Dashboard' ); ?>
64 + <a href="?page=patternswp-plugin-menu" class="nav-tab nav-tab-active"><?php esc_html_e('Dashboard', 'patternswp'); ?></a>
65 + <a href="?page=patternswp-plugin-page-1" class="nav-tab"><?php esc_html_e('Support', 'patternswp'); ?></a>
66 + <a href="?page=patternswp-clear-cache" class="nav-tab"><?php esc_html_e('Clear Cache', 'patternswp'); ?></a>
67 + <a href="?page=patternswp-license_section" class="nav-tab"><?php esc_html_e('License', 'patternswp'); ?></a>
156 68 </h2>
157 69 <div class="patterns-wp-tabs-content">
158 70 <div id="tab-1" class="patterns-wp-tab-content patterns-wp-tab-active">
159 71 <div class="wrap">
@@ -163,21 +75,13 @@
163 75 <h4><?php echo esc_html('Hello, ' . wp_get_current_user()->display_name . ' 👋'); ?></h4>
164 76 <h1 class="feature-title"><?php esc_html_e('Welcome to PatternsWP', 'patternswp'); ?></h1>
165 77 <p><?php esc_html_e('Thanks for choosing PatternsWP! Follow these three simple steps to get started!', 'patternswp'); ?></p>
166 78 </div>
167 - <div class="column" style="padding: 10px; text-align: center;">
168 - <a href="<?php echo esc_url(admin_url('post-new.php?post_type=page')); ?>" class="button button-primary">
169 - <?php esc_html_e('Start building with PatternsWP', 'patternswp'); ?>
170 - </a>
171 - </div>
172 79 </div>
173 80 <div class="about__section has-3-columns">
174 81 <div class="column" style="padding: 40px;">
175 82 <div class="about__image">
176 - <?php
177 - // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
178 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . 'pwp-welcome-01.png') . '" alt="" height="auto" width="100%">';
179 - ?>
83 + <img src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'pwp-welcome-01.png'); ?>" alt="" height="auto" width="100%">
180 84 </div>
181 85 <h4><?php esc_html_e('01. Open the PatternsWP Library', 'patternswp'); ?></h4>
182 86 <p><?php esc_html_e('When editing a page or post in the block editor, locate the PatternsWP Library button in the editor’s header. Click it to access a collection of pre-designed patterns and full-page templates.', 'patternswp'); ?></p>
183 87 </div>
@@ -182,12 +86,9 @@
182 86 <p><?php esc_html_e('When editing a page or post in the block editor, locate the PatternsWP Library button in the editor’s header. Click it to access a collection of pre-designed patterns and full-page templates.', 'patternswp'); ?></p>
183 87 </div>
184 88 <div class="column" style="padding: 40px;">
185 89 <div class="about__image">
186 - <?php
187 - // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
188 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . 'pwp-welcome-02.png') . '" alt="" height="auto" width="100%">';
189 - ?>
90 + <img src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'pwp-welcome-02.png'); ?>" alt="" height="auto" width="100%">
190 91 </div>
191 92 <h4><?php esc_html_e('02. Browse Patterns & Templates', 'patternswp'); ?></h4>
192 93 <p><?php esc_html_e('Explore a diverse range of block patterns and full-page layouts. Use the search box or filter by category to quickly find the perfect design for your website.', 'patternswp'); ?></p>
193 94 </div>
@@ -192,12 +93,9 @@
192 93 <p><?php esc_html_e('Explore a diverse range of block patterns and full-page layouts. Use the search box or filter by category to quickly find the perfect design for your website.', 'patternswp'); ?></p>
193 94 </div>
194 95 <div class="column" style="padding: 40px;">
195 96 <div class="about__image">
196 - <?php
197 - // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
198 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . 'pwp-welcome-03.png') . '" alt="" height="auto" width="100%">';
199 - ?>
97 + <img src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'pwp-welcome-03.png'); ?>" alt="" height="auto" width="100%">
200 98 </div>
201 99 <h4><?php esc_html_e('03. Add Patterns & Customize', 'patternswp'); ?></h4>
202 100 <p><?php esc_html_e('Once you’ve found the right pattern, add it to your page with a single click. Every pattern is fully customizable, allowing you to tweak colors, typography, and content effortlessly.', 'patternswp'); ?></p>
203 101 </div>
@@ -215,9 +113,12 @@
215 113 public function patternswp_support() { ?>
216 114 <div class="wrap">
217 115 <h1><?php esc_html_e('Support', 'patternswp'); ?></h1>
218 116 <h2 class="nav-tab-wrapper">
219 - <?php $this->patterswp_tab( 'Support' ); ?>
117 + <a href="?page=patternswp-plugin-menu" class="nav-tab"><?php esc_html_e('Dashboard', 'patternswp'); ?></a>
118 + <a href="?page=patternswp-plugin-page-1" class="nav-tab nav-tab-active"><?php esc_html_e('Support', 'patternswp'); ?></a>
119 + <a href="?page=patternswp-clear-cache" class="nav-tab"><?php esc_html_e('Clear Cache', 'patternswp'); ?></a>
120 + <a href="?page=patternswp-license_section" class="nav-tab"><?php esc_html_e('License', 'patternswp'); ?></a>
220 121 </h2>
221 122 <div class="patterns-wp-tabs-content">
222 123 <div id="tab-1" class="patterns-wp-tab-content patterns-wp-tab-active">
223 124 </div>
@@ -282,14 +183,17 @@
282 183 public function patternswp_clear_cache_form() { ?>
283 184 <div class="wrap">
284 185 <h1><?php esc_html_e('Clear Cache', 'patternswp'); ?></h1>
285 186 <h2 class="nav-tab-wrapper">
286 - <?php $this->patterswp_tab( 'Clear Cache' ); ?>
187 + <a href="?page=patternswp-plugin-menu" class="nav-tab"><?php esc_html_e('Dashboard', 'patternswp'); ?></a>
188 + <a href="?page=patternswp-plugin-page-1" class="nav-tab"><?php esc_html_e('Support', 'patternswp'); ?></a>
189 + <a href="?page=patternswp-clear-cache" class="nav-tab nav-tab-active"><?php esc_html_e('Clear Cache', 'patternswp'); ?></a>
190 + <a href="?page=patternswp-license_section" class="nav-tab"><?php esc_html_e('License', 'patternswp'); ?></a>
287 191 </h2>
288 192 <div class="patterns-wp-tabs-content">
289 193 <div id="tab-1" class="patterns-wp-tab-content patterns-wp-tab-active">
290 194 <div class="wrap">
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;">
195 + <div class="container" style="padding: 20px; background-color: white; border-radius: 5px; overflow: hidden; width: 70%;">
292 196 <div class="">
293 197 <form id="patternswp_clearcache_form" method="post">
294 198 <?php wp_nonce_field('patternswp_clear_cache_action', 'patternswp_clear_cache_nonce'); ?>
295 199 <input name="patternswp_clear_cache" type="submit" class="button button-primary" value="Clear Cache">
@@ -304,37 +208,8 @@
304 208 </div>
305 209 <?php }
306 210
307 211 /**
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 - /**
337 212 * Clear Cache
338 213 */
339 214 public function patternswp_clear_cache() {
340 215 global $wpdb;
@@ -339,18 +214,11 @@
339 214 public function patternswp_clear_cache() {
340 215 global $wpdb;
341 216
342 217 if (isset($_POST['patternswp_clear_cache'])) {
343 - if (!isset($_POST['patternswp_clear_cache_nonce'])) {
344 - wp_die(esc_attr__('Security check failed. Please try again.', 'patternswp'));
218 + if (!isset($_POST['patternswp_clear_cache_nonce']) || !wp_verify_nonce($_POST['patternswp_clear_cache_nonce'], 'patternswp_clear_cache_action')) {
219 + wp_die(esc_attr('Security check failed. Please try again.', 'patternswp'));
345 220 }
346 -
347 - $nonce = sanitize_text_field(wp_unslash($_POST['patternswp_clear_cache_nonce']));
348 -
349 - if (!wp_verify_nonce($nonce, 'patternswp_clear_cache_action')) {
350 - wp_die(esc_attr__('Security check failed. Please try again.', 'patternswp'));
351 - }
352 -
353 221 if (!current_user_can('manage_options')) {
354 222 wp_die(esc_attr('You do not have sufficient permissions to perform this action.', 'patternswp'));
355 223 }
356 224
@@ -358,14 +226,12 @@
358 226 delete_transient('patternswp_category_type');
359 227
360 228 // Delete all API-related transients
361 229 $pattern = '_transient_patternswp_';
362 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
363 230 $results = $wpdb->get_col($wpdb->prepare("SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s", $wpdb->esc_like($pattern) . '%'));
364 231
365 232 // Delete all patterns cache
366 233 $patterns = 'patterns_cache_';
367 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
368 234 $wpdb->query(
369 235 $wpdb->prepare(
370 236 "DELETE FROM $wpdb->options WHERE option_name LIKE %s",
371 237 $wpdb->esc_like($patterns) . '%'
@@ -376,813 +242,15 @@
376 242 delete_option($transient);
377 243 delete_option(str_replace('_transient_', '_transient_timeout_', $transient));
378 244 }
379 245
380 - // Load patterns in background
381 - $this->patternswp_load_patterns_by_remote_ajax();
382 -
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'));
246 + wp_redirect(add_query_arg(array(
247 + 'page' => 'patternswp-clear-cache',
248 + 'pt_msg' => urlencode('Clear Cache Successfully'),
249 + 'status' => 'success'
250 + ), admin_url('admin.php')));
388 251 exit;
389 252 }
390 253 }
391 -
392 - /**
393 - * Ensure hourly cron job is scheduled
394 - */
395 - public function patternswp_ensure_hourly_cron() {
396 - if ( ! wp_next_scheduled( 'patternswp_hourly_transient_load' ) ) {
397 - wp_schedule_event( time(), 'hourly', 'patternswp_hourly_transient_load' );
398 - }
399 - }
400 -
401 - /**
402 - * AJAX handler to load transient data
403 - */
404 - public function patternswp_background_transient_load_ajaxcc() {
405 - do_action( 'patternswp_hourly_transient_load' );
406 - wp_die();
407 - }
408 -
409 - /**
410 - * Load patterns via AJAX
411 - */
412 - public function patternswp_load_patterns_by_remote_ajax() {
413 - $ajax_url = admin_url('admin-ajax.php');
414 - wp_remote_post( $ajax_url , array(
415 - 'body' => array( 'action' => 'patternswp_background_transient_load_ajax' ),
416 - 'timeout' => 1,
417 - 'blocking' => false,
418 - 'sslverify' => false,
419 - ));
420 - }
421 -
422 - /**
423 - * Set transient on plugin activation
424 - */
425 - public function patternswp_on_activation() {
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
1172 - }
1173 -
1174 - /**
1175 - * Redirect to welcome page after activation
1176 - */
1177 -public function patternswp_redirect_on_activation() {
1178 - if (get_transient('patternswp_activation_redirect')) {
1179 - delete_transient('patternswp_activation_redirect');
1180 - if (!is_network_admin() && !isset($_GET['activate-multi'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Safe usage: only checking if 'activate-multi' is set
1181 - wp_safe_redirect(admin_url('admin.php?page=patternswp-plugin-menu'));
1182 - exit;
1183 - }
1184 - }
1185 -}
1186 254 }
1187 255
1188 256 new PatternsWP_Admin();