PluginProbe
PatternsWP – Gutenberg Block Patterns & Page Templates Library / 1.1.0
PatternsWP – Gutenberg Block Patterns & Page Templates Library v1.1.0
1.1.0 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 +348 -325 1.0.81.1.0 View file →
@@ -15,12 +15,14 @@
15 15 */
16 16 public function __construct() {
17 17 // Add menu and pages
18 18 add_action('admin_menu', array($this, 'add_admin_menu'));
19 + add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
19 20 add_action('admin_init', array($this, 'patternswp_clear_cache'));
20 - add_action('admin_init', array($this, 'patternswp_ensure_hourly_cron'));
21 + add_action('admin_init', array($this, 'patternswp_ensure_daily_cron'));
21 22 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('wp_ajax_patternswp_save_settings', array($this, 'ajax_save_settings'));
24 + add_action('wp_ajax_patternswp_clear_cache_ajax', array($this, 'ajax_clear_cache'));
23 25 add_action('patternswp_load_patterns_by_ra', array($this, 'patternswp_load_patterns_by_remote_ajax'));
24 26 register_activation_hook(plugin_dir_path(__DIR__) . 'patternswp.php', array($this, 'patternswp_on_activation'));
25 27 add_action('admin_init', array($this, 'patternswp_redirect_on_activation'));
26 28
@@ -36,10 +38,8 @@
36 38 add_action('init', array($this, 'maybe_deregister_theme_patterns'), 9999);
37 39 add_action('init', array($this, 'maybe_deregister_uncategorized_patterns'), 10000);
38 40 add_action('init', array($this, 'maybe_deregister_core_patterns'), 10001);
39 41
40 - error_log('[PatternsWP] Constructor - All pattern deregistration hooks registered');
41 -
42 42 // Apply filters for different pattern sources
43 43 add_filter('patternswp_patterns', array($this, 'filter_patterns_by_visibility'), 20);
44 44 add_filter('block_editor_settings_all', array($this, 'filter_block_editor_settings'), 10, 2);
45 45
@@ -61,8 +61,237 @@
61 61
62 62 // Add a test hook to verify settings are loaded
63 63 add_action('wp_loaded', array($this, 'test_settings'));
64 64 }
65 +
66 + /**
67 + * Whether the current screen is a PatternsWP admin page.
68 + *
69 + * @param string $hook_suffix Current admin page hook.
70 + * @return bool
71 + */
72 + private function is_patternswp_admin_page( $hook_suffix ) {
73 + $pages = array(
74 + 'toplevel_page_patternswp-plugin-menu',
75 + 'patternswp_page_patternswp-settings',
76 + 'patternswp_page_patternswp-plugin-page-1',
77 + 'patternswp_page_patternswp-license_section',
78 + );
79 + return in_array( $hook_suffix, $pages, true );
80 + }
81 +
82 + /**
83 + * Map menu slug to React page id.
84 + *
85 + * @return string
86 + */
87 + private function get_current_admin_page_id() {
88 + $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : 'patternswp-plugin-menu'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
89 + $map = array(
90 + 'patternswp-plugin-menu' => 'dashboard',
91 + 'patternswp-settings' => 'settings',
92 + 'patternswp-plugin-page-1' => 'support',
93 + 'patternswp-license_section' => 'license',
94 + );
95 + return isset( $map[ $page ] ) ? $map[ $page ] : 'dashboard';
96 + }
97 +
98 + /**
99 + * Enqueue React admin assets on PatternsWP screens.
100 + *
101 + * @param string $hook_suffix Current admin page hook.
102 + */
103 + public function enqueue_admin_assets( $hook_suffix ) {
104 + if ( ! $this->is_patternswp_admin_page( $hook_suffix ) ) {
105 + return;
106 + }
107 +
108 + $script_path = 'assets/js/patternswp-admin.js';
109 + $style_path = 'assets/css/patternswp-admin.css';
110 +
111 + wp_enqueue_style( 'wp-components' );
112 + wp_enqueue_style(
113 + 'patternswp-admin-styles',
114 + PWP_PLUGIN_URL . $style_path,
115 + array( 'wp-components' ),
116 + patternswp_asset_version( $style_path )
117 + );
118 +
119 + wp_enqueue_script(
120 + 'patternswp-admin-scripts',
121 + PWP_PLUGIN_URL . $script_path,
122 + array(
123 + 'wp-element',
124 + 'wp-components',
125 + 'wp-i18n',
126 + 'wp-api-fetch',
127 + 'wp-dom-ready',
128 + 'wp-primitives',
129 + ),
130 + patternswp_asset_version( $script_path ),
131 + true
132 + );
133 +
134 + $license_option = get_option( 'patternswp_license_key', array() );
135 + $license_key = isset( $license_option['patternswp_pro_license_key'] ) ? $license_option['patternswp_pro_license_key'] : '';
136 + $is_active = PatternsWP_API_Section::get_instance()->is_license_active();
137 +
138 + if ( ! empty( $license_key ) && strlen( $license_key ) > 8 ) {
139 + $masked_key = substr( $license_key, 0, 8 ) . str_repeat( 'X', strlen( $license_key ) - 8 );
140 + } else {
141 + $masked_key = $license_key;
142 + }
143 +
144 + $initial_notice = null;
145 + if ( isset( $_GET['pt_msg'] ) && ! empty( $_GET['pt_msg'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
146 + $status = isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : 'info'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
147 + $notice_status = 'info';
148 + if ( 'success' === $status ) {
149 + $notice_status = 'success';
150 + } elseif ( 'error' === $status ) {
151 + $notice_status = 'error';
152 + }
153 + $initial_notice = array(
154 + 'status' => $notice_status,
155 + 'message' => sanitize_text_field( urldecode( wp_unslash( $_GET['pt_msg'] ) ) ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
156 + );
157 + }
158 +
159 + $images_base = plugin_dir_url( __FILE__ );
160 +
161 + wp_localize_script(
162 + 'patternswp-admin-scripts',
163 + 'patternswpAdmin',
164 + array(
165 + 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
166 + 'adminUrl' => admin_url( 'admin.php' ),
167 + 'newPageUrl' => admin_url( 'post-new.php?post_type=page' ),
168 + 'nonce' => wp_create_nonce( 'patternswp_admin_nonce' ),
169 + 'currentPage' => $this->get_current_admin_page_id(),
170 + 'userName' => wp_get_current_user()->display_name,
171 + 'pluginVersion' => PWP_P_VERSION,
172 + 'isLicenseActive' => (bool) $is_active,
173 + 'settings' => array(
174 + 'hideThemePatterns' => (bool) rest_sanitize_boolean( get_option( 'patternswp_hide_theme_patterns', false ) ),
175 + 'hideUncategorizedPatterns' => (bool) rest_sanitize_boolean( get_option( 'patternswp_hide_uncategorized_patterns', false ) ),
176 + 'hideCorePatterns' => (bool) rest_sanitize_boolean( get_option( 'patternswp_hide_core_patterns', false ) ),
177 + ),
178 + 'license' => array(
179 + 'maskedKey' => $masked_key,
180 + 'activated' => (bool) $is_active,
181 + ),
182 + 'images' => array(
183 + 'step1' => $images_base . 'pwp-welcome-01.png',
184 + 'step2' => $images_base . 'pwp-welcome-02.png',
185 + 'step3' => $images_base . 'pwp-welcome-03.png',
186 + ),
187 + 'initialNotice' => $initial_notice,
188 + )
189 + );
190 + }
191 +
192 + /**
193 + * Shared React mount markup for all admin pages.
194 + *
195 + * @param string $page_title Screen reader / fallback title.
196 + */
197 + private function render_admin_app( $page_title ) {
198 + if ( ! current_user_can( 'manage_options' ) ) {
199 + return;
200 + }
201 + ?>
202 + <div class="wrap patternswp-admin-wrap">
203 + <h1 class="screen-reader-text"><?php echo esc_html( $page_title ); ?></h1>
204 + <div id="patternswp-admin-root">
205 + <div class="patternswp-admin__loading">
206 + <span class="spinner is-active" style="float:none;margin:0;"></span>
207 + </div>
208 + </div>
209 + </div>
210 + <?php
211 + }
212 +
213 + /**
214 + * AJAX: save pattern visibility settings.
215 + */
216 + public function ajax_save_settings() {
217 + if ( ! current_user_can( 'manage_options' ) ) {
218 + wp_send_json_error( array( 'message' => __( 'You do not have permission to manage these settings.', 'patternswp' ) ), 403 );
219 + }
220 +
221 + check_ajax_referer( 'patternswp_admin_nonce', 'nonce' );
222 +
223 + $hide_theme = ! empty( $_POST['hide_theme_patterns'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['hide_theme_patterns'] ) );
224 + $hide_uncat = ! empty( $_POST['hide_uncategorized_patterns'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['hide_uncategorized_patterns'] ) );
225 + $hide_core = ! empty( $_POST['hide_core_patterns'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['hide_core_patterns'] ) );
226 +
227 + update_option( 'patternswp_hide_theme_patterns', $hide_theme ? 1 : 0, false );
228 + update_option( 'patternswp_hide_uncategorized_patterns', $hide_uncat ? 1 : 0, false );
229 + update_option( 'patternswp_hide_core_patterns', $hide_core ? 1 : 0, false );
230 +
231 + wp_send_json_success(
232 + array(
233 + 'message' => __( 'Settings saved.', 'patternswp' ),
234 + 'settings' => array(
235 + 'hideThemePatterns' => $hide_theme,
236 + 'hideUncategorizedPatterns' => $hide_uncat,
237 + 'hideCorePatterns' => $hide_core,
238 + ),
239 + )
240 + );
241 + }
242 +
243 + /**
244 + * AJAX: clear pattern cache.
245 + */
246 + public function ajax_clear_cache() {
247 + global $wpdb;
248 +
249 + if ( ! current_user_can( 'manage_options' ) ) {
250 + wp_send_json_error( array( 'message' => __( 'You do not have permission to clear the cache.', 'patternswp' ) ), 403 );
251 + }
252 +
253 + check_ajax_referer( 'patternswp_admin_nonce', 'nonce' );
254 +
255 + try {
256 + delete_transient( 'patternswp_category_type' );
257 +
258 + $pattern = '_transient_patternswp_';
259 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
260 + $results = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s", $wpdb->esc_like( $pattern ) . '%' ) );
261 +
262 + $patterns = 'patterns_cache_';
263 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
264 + $wpdb->query(
265 + $wpdb->prepare(
266 + "DELETE FROM $wpdb->options WHERE option_name LIKE %s",
267 + $wpdb->esc_like( $patterns ) . '%'
268 + )
269 + );
270 +
271 + foreach ( $results as $transient ) {
272 + delete_option( $transient );
273 + delete_option( str_replace( '_transient_', '_transient_timeout_', $transient ) );
274 + }
275 +
276 + $this->clear_pattern_cache( false, false );
277 + $api = PatternsWP_API_Section::get_instance();
278 + $api->purge_library_caches();
279 + $api->schedule_library_refresh( 1 );
280 + } catch ( \Throwable $e ) {
281 + wp_send_json_error(
282 + array(
283 + 'message' => __( 'Could not clear the cache. Please try again.', 'patternswp' ),
284 + )
285 + );
286 + }
287 +
288 + wp_send_json_success(
289 + array(
290 + 'message' => __( 'Cache cleared. Patterns will reload in the background.', 'patternswp' ),
291 + )
292 + );
293 + }
65 294
66 295 /**
67 296 * Test method to verify settings are loaded
68 297 */
@@ -70,9 +299,9 @@
70 299 $hide_theme = get_option('patternswp_hide_theme_patterns', false);
71 300 $hide_uncategorized = get_option('patternswp_hide_uncategorized_patterns', false);
72 301 $hide_core = get_option('patternswp_hide_core_patterns', false);
73 302
74 - error_log('[PatternsWP] Settings Check - Theme: ' . ($hide_theme ? 'true' : 'false') . ', Uncategorized: ' . ($hide_uncategorized ? 'true' : 'false') . ', Core: ' . ($hide_core ? 'true' : 'false'));
303 + // error_log('[PatternsWP] Settings Check - Theme: ' . ($hide_theme ? 'true' : 'false') . ', Uncategorized: ' . ($hide_uncategorized ? 'true' : 'false') . ', Core: ' . ($hide_core ? 'true' : 'false'));
75 304 }
76 305
77 306 /**
78 307 * Add admin menu
@@ -124,10 +353,10 @@
124 353 'Support' => 'patternswp-plugin-page-1',
125 354 'License' => 'patternswp-license_section'
126 355 );
127 356
128 - // Get the current page from URL parameter
129 - $current_page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'patternswp-plugin-menu';
357 + // Get the current page from URL parameter (read-only tab highlight; no state change).
358 + $current_page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : 'patternswp-plugin-menu'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
130 359
131 360 // Map page slugs to tab names
132 361 $page_to_tab = array(
133 362 'patternswp-plugin-menu' => 'Dashboard',
@@ -147,163 +376,25 @@
147 376
148 377 /**
149 378 * Render main page
150 379 */
151 - public function render_main_page() { ?>
152 - <div class="wrap">
153 - <h1><?php esc_html_e('PatternsWP', 'patternswp'); ?></h1>
154 - <h2 class="nav-tab-wrapper">
155 - <?php $this->patterswp_tab( 'Dashboard' ); ?>
156 - </h2>
157 - <div class="patterns-wp-tabs-content">
158 - <div id="tab-1" class="patterns-wp-tab-content patterns-wp-tab-active">
159 - <div class="wrap">
160 - <div class="pw-feature-box big-box" style="max-width: 1280px; padding: 40px; background-color: white; border-radius: 10px; overflow: hidden;">
161 - <div class="about__section has-1-columns">
162 - <div class="column" style="text-align:center;">
163 - <h4><?php echo esc_html('Hello, ' . wp_get_current_user()->display_name . ' 👋'); ?></h4>
164 - <h1 class="feature-title"><?php esc_html_e('Welcome to PatternsWP', 'patternswp'); ?></h1>
165 - <p><?php esc_html_e('Thanks for choosing PatternsWP! Follow these three simple steps to get started!', 'patternswp'); ?></p>
166 - </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 - </div>
173 - <div class="about__section has-3-columns">
174 - <div class="column" style="padding: 40px;">
175 - <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 - ?>
180 - </div>
181 - <h4><?php esc_html_e('01. Open the PatternsWP Library', 'patternswp'); ?></h4>
182 - <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 - </div>
184 - <div class="column" style="padding: 40px;">
185 - <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 - ?>
190 - </div>
191 - <h4><?php esc_html_e('02. Browse Patterns & Templates', 'patternswp'); ?></h4>
192 - <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 - </div>
194 - <div class="column" style="padding: 40px;">
195 - <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 - ?>
200 - </div>
201 - <h4><?php esc_html_e('03. Add Patterns & Customize', 'patternswp'); ?></h4>
202 - <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 - </div>
204 - </div>
205 - </div>
206 - </div>
207 - </div>
208 - </div>
209 - </div>
210 - <?php }
380 + public function render_main_page() {
381 + $this->render_admin_app( __( 'PatternsWP', 'patternswp' ) );
382 + }
211 383
212 384 /**
213 385 * Support page
214 386 */
215 - public function patternswp_support() { ?>
216 - <div class="wrap">
217 - <h1><?php esc_html_e('Support', 'patternswp'); ?></h1>
218 - <h2 class="nav-tab-wrapper">
219 - <?php $this->patterswp_tab( 'Support' ); ?>
220 - </h2>
221 - <div class="patterns-wp-tabs-content">
222 - <div id="tab-1" class="patterns-wp-tab-content patterns-wp-tab-active">
223 - </div>
224 - <div id="tab-2" class="patterns-wp-tab-content">
225 - <div class="wrap">
226 - <div class="pw-feature-box big-box" style="max-width: 1280px; padding: 40px; background-color: white; border-radius: 10px; overflow: hidden;">
227 - <div class="about__section has-1-columns">
228 - <div class="column" style="">
229 - <h1 class="feature-title"><?php esc_html_e('Support', 'patternswp'); ?></h1>
230 - <p><?php esc_html_e('Need help with PatternsWP? Whether you have a question, need technical assistance, or just want to reach out, we’re here for you. Contact us, and we’ll be happy to assist!', 'patternswp'); ?></p>
231 - </div>
232 - <div style="height: 1px; background-color: #ccc; width: 100%; margin: 20px 0;"></div>
233 - </div>
234 - <div class="about__section has-2-columns">
235 - <div class="column" style="padding-right: 100px;">
236 -
237 - <h4><?php esc_html_e('Frequently asked questions', 'patternswp'); ?></h4>
238 - <p>
239 - <?php
240 - printf(
241 - esc_attr('Here, you will find answers to commonly asked questions about using PatternsWP. If you need further assistance, feel free to %s.', 'patternswp'),
242 - '<a href="https://thepatternswp.com/contact/" target="_blank">'.esc_attr('contact us via the support form →', 'patternswp').'</a>'
243 - );
244 - ?>
245 - </p>
246 - </div>
247 - <div class="column" style="padding: 0px;">
248 - <h5>
249 - <a href="https://thepatternswp.com/docs/getting-started-with-patternswp/" target="_blank" style="color: #3858e9; text-decoration: none;">
250 - <?php esc_html_e('Getting Started with PatternsWP →', 'patternswp'); ?>
251 - </a>
252 - </h5>
253 - <div style="height: 1px; background-color: #ccc; width: 100%; margin: 10px 0;"></div>
254 - <h5>
255 - <a href="https://thepatternswp.com/docs/how-to-install-patternswp/" target="_blank" style="color: #3858e9; text-decoration: none;">
256 - <?php esc_html_e('How to install PatternsWP →', 'patternswp'); ?>
257 - </a>
258 - </h5> <div style="height: 1px; background-color: #ccc; width: 100%; margin: 10px 0;"></div>
259 - <h5>
260 - <a href="https://thepatternswp.com/docs/how-to-upgrade-patternswp-to-pro/" target="_blank" style="color: #3858e9; text-decoration: none;">
261 - <?php esc_html_e('How to Upgrade PatternsWP to Pro →', 'patternswp'); ?>
262 - </a>
263 - </h5> <div style="height: 1px; background-color: #ccc; width: 100%; margin: 10px 0;"></div>
264 - <h5>
265 - <a href="https://thepatternswp.com/docs/patternswp-support/" target="_blank" style="color: #3858e9; text-decoration: none;">
266 - <?php esc_html_e('PatternsWP Support →', 'patternswp'); ?>
267 - </a>
268 - </h5> <div style="height: 1px; background-color: #ccc; width: 100%; margin: 10px 0;"></div>
269 - </div>
270 -
271 - </div>
272 - </div>
273 - </div>
274 - </div>
275 - </div>
276 - </div>
277 - <?php }
387 + public function patternswp_support() {
388 + $this->render_admin_app( __( 'Support', 'patternswp' ) );
389 + }
278 390
279 391 /**
280 392 * Clear Cache page
281 393 */
282 - public function patternswp_clear_cache_form() { ?>
283 - <div class="wrap">
284 - <h1><?php esc_html_e('Clear Cache', 'patternswp'); ?></h1>
285 - <h2 class="nav-tab-wrapper">
286 - <?php $this->patterswp_tab( 'Clear Cache' ); ?>
287 - </h2>
288 - <div class="patterns-wp-tabs-content">
289 - <div id="tab-1" class="patterns-wp-tab-content patterns-wp-tab-active">
290 - <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;">
292 - <div class="">
293 - <form id="patternswp_clearcache_form" method="post">
294 - <?php wp_nonce_field('patternswp_clear_cache_action', 'patternswp_clear_cache_nonce'); ?>
295 - <input name="patternswp_clear_cache" type="submit" class="button button-primary" value="Clear Cache">
296 - <div>
297 - </div>
298 - </form>
299 - </div>
300 - </div>
301 - </div>
302 - </div>
303 - </div>
304 - </div>
305 - <?php }
394 + public function patternswp_clear_cache_form() {
395 + $this->render_admin_app( __( 'Clear Cache', 'patternswp' ) );
396 + }
306 397
307 398 /**
308 399 * Clear pattern cache when theme is switched
309 400 */
@@ -350,9 +441,9 @@
350 441 wp_die(esc_attr__('Security check failed. Please try again.', 'patternswp'));
351 442 }
352 443
353 444 if (!current_user_can('manage_options')) {
354 - wp_die(esc_attr('You do not have sufficient permissions to perform this action.', 'patternswp'));
445 + wp_die(esc_html__('You do not have sufficient permissions to perform this action.', 'patternswp'));
355 446 }
356 447
357 448 // Delete category type transient
358 449 delete_transient('patternswp_category_type');
@@ -377,9 +468,11 @@
377 468 delete_option(str_replace('_transient_', '_transient_timeout_', $transient));
378 469 }
379 470
380 471 // Load patterns in background
381 - $this->patternswp_load_patterns_by_remote_ajax();
472 + $api = PatternsWP_API_Section::get_instance();
473 + $api->purge_library_caches();
474 + $api->schedule_library_refresh( 1 );
382 475
383 476 // Set a transient for the success message
384 477 set_transient('patternswp_cache_cleared', 'Cache cleared successfully', 5);
385 478
@@ -389,14 +482,19 @@
389 482 }
390 483 }
391 484
392 485 /**
393 - * Ensure hourly cron job is scheduled
486 + * Ensure daily cron job is scheduled
394 487 */
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' );
488 + public function patternswp_ensure_daily_cron() {
489 + if ( wp_next_scheduled( 'patternswp_hourly_transient_load' ) ) {
490 + wp_clear_scheduled_hook( 'patternswp_hourly_transient_load' );
398 491 }
492 + if ( ! wp_next_scheduled( 'patternswp_daily_transient_load' ) ) {
493 + wp_schedule_event( time(), 'daily', 'patternswp_daily_transient_load' );
494 + }
495 +
496 + PatternsWP_API_Section::get_instance()->maybe_schedule_warm();
399 497 }
400 498
401 499 /**
402 500 * AJAX handler to load transient data
@@ -401,23 +499,23 @@
401 499 /**
402 500 * AJAX handler to load transient data
403 501 */
404 502 public function patternswp_background_transient_load_ajaxcc() {
405 - do_action( 'patternswp_hourly_transient_load' );
503 + if ( ! current_user_can( 'manage_options' ) ) {
504 + wp_die( -1, 403 );
505 + }
506 +
507 + check_ajax_referer( 'patternswp_admin_nonce', 'nonce' );
508 +
509 + do_action( 'patternswp_daily_transient_load' );
406 510 wp_die();
407 511 }
408 512
409 513 /**
410 - * Load patterns via AJAX
514 + * Warm pattern transients without an unauthenticated HTTP loopback.
411 515 */
412 516 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 - ));
517 + do_action( 'patternswp_daily_transient_load' );
420 518 }
421 519
422 520 /**
423 521 * Set transient on plugin activation
@@ -423,8 +521,9 @@
423 521 * Set transient on plugin activation
424 522 */
425 523 public function patternswp_on_activation() {
426 524 set_transient('patternswp_activation_redirect', true, 30);
525 + PatternsWP_API_Section::get_instance()->schedule_library_refresh( 3 );
427 526 }
428 527
429 528 /**
430 529 * Clear pattern cache when visibility settings change
@@ -429,19 +528,18 @@
429 528 /**
430 529 * Clear pattern cache when visibility settings change
431 530 */
432 531 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 532 // Clear known transients
439 533 $transients = [
440 534 'patternswp_all_patterns',
441 535 'patternswp_categorized_patterns',
442 536 'patternswp_patterns_data',
443 - 'patternswp_categories'
537 + 'patternswp_categories',
538 + 'patternswp_api_token',
539 + 'patternswp_library_lock',
540 + 'patternswp_lib_free_count',
541 + 'patternswp_lib_pro_count',
444 542 ];
445 543
446 544 foreach ($transients as $transient) {
447 545 delete_transient($transient);
@@ -466,12 +564,12 @@
466 564 * Attempt to deregister any patterns from the active or child theme.
467 565 */
468 566 public function maybe_deregister_theme_patterns() {
469 567 $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'));
568 + // error_log('[PatternsWP] maybe_deregister_theme_patterns called - hide_theme_patterns: ' . ($hide_theme_patterns ? 'true' : 'false'));
471 569
472 570 if (!$hide_theme_patterns) {
473 - error_log('[PatternsWP] Theme pattern hiding is disabled');
571 + // error_log('[PatternsWP] Theme pattern hiding is disabled');
474 572 return;
475 573 }
476 574
477 575 // Get the active theme.
@@ -488,15 +586,15 @@
488 586 $paths_to_search[] = $parent_theme->template;
489 587 }
490 588 $paths_to_search = array_unique($paths_to_search);
491 589
492 - error_log('[PatternsWP] Theme paths to search: ' . implode(', ', $paths_to_search));
590 + // error_log('[PatternsWP] Theme paths to search: ' . implode(', ', $paths_to_search));
493 591
494 592 // Get all registered patterns.
495 593 $patterns = \WP_Block_Patterns_Registry::get_instance();
496 594 $all_patterns = $patterns->get_all_registered();
497 595
498 - error_log('[PatternsWP] Total registered patterns: ' . count($all_patterns));
596 + // error_log('[PatternsWP] Total registered patterns: ' . count($all_patterns));
499 597
500 598 // Loop through all patterns and deregister any that are from the active or child theme.
501 599 foreach ($all_patterns as $index => $pattern) {
502 600 $file_path = $pattern['filePath'] ?? '';
@@ -507,9 +605,9 @@
507 605 // Check if the pattern is from the active or child theme.
508 606 foreach ($paths_to_search as $path) {
509 607 if (false !== strpos($file_path, 'themes/' . $path)) {
510 608 unregister_block_pattern($pattern['name']);
511 - error_log('[PatternsWP] Deregistered theme pattern: ' . $pattern['name'] . ' from path: ' . $file_path);
609 + // error_log('[PatternsWP] Deregistered theme pattern: ' . $pattern['name'] . ' from path: ' . $file_path);
512 610 }
513 611 }
514 612 }
515 613 }
@@ -518,13 +616,13 @@
518 616 * Deregister any uncategorized patterns.
519 617 */
520 618 public function maybe_deregister_uncategorized_patterns() {
521 619 $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'));
620 + // error_log('[PatternsWP] maybe_deregister_uncategorized_patterns called - hide_uncategorized: ' . ($hide_uncategorized_enabled ? 'true' : 'false'));
523 621
524 622 // Exit early if not enabled.
525 623 if (!$hide_uncategorized_enabled) {
526 - error_log('[PatternsWP] Uncategorized pattern hiding is disabled');
624 + // error_log('[PatternsWP] Uncategorized pattern hiding is disabled');
527 625 return;
528 626 }
529 627
530 628 // Retrieve all patterns.
@@ -530,15 +628,15 @@
530 628 // Retrieve all patterns.
531 629 $patterns = \WP_Block_Patterns_Registry::get_instance();
532 630 $all_patterns = $patterns->get_all_registered();
533 631
534 - error_log('[PatternsWP] Checking ' . count($all_patterns) . ' patterns for uncategorized ones');
632 + // error_log('[PatternsWP] Checking ' . count($all_patterns) . ' patterns for uncategorized ones');
535 633
536 634 // Loop through all patterns and deregister any that are uncategorized.
537 635 foreach ($all_patterns as $index => $pattern) {
538 636 if (!isset($pattern['categories']) || empty($pattern['categories'])) {
539 637 unregister_block_pattern($pattern['name']);
540 - error_log('[PatternsWP] Deregistered uncategorized pattern: ' . $pattern['name']);
638 + // error_log('[PatternsWP] Deregistered uncategorized pattern: ' . $pattern['name']);
541 639 } else {
542 640 $found = false;
543 641 $block_categories = $pattern['categories'] ?? array();
544 642 foreach ($block_categories as $block_category) {
@@ -548,9 +646,9 @@
548 646 }
549 647 }
550 648 if (!$found) {
551 649 unregister_block_pattern($pattern['name']);
552 - error_log('[PatternsWP] Deregistered uncategorized pattern (invalid category): ' . $pattern['name']);
650 + // error_log('[PatternsWP] Deregistered uncategorized pattern (invalid category): ' . $pattern['name']);
553 651 }
554 652 }
555 653 }
556 654 }
@@ -555,17 +653,25 @@
555 653 }
556 654 }
557 655
558 656 /**
657 + * Check if a registered block pattern belongs to PatternsWP.
658 + */
659 + private function is_patternswp_registry_pattern( $pattern_name ) {
660 + return strpos( $pattern_name, 'patternswp-gutenberg-block-patterns/' ) === 0
661 + || strpos( $pattern_name, 'patternswp/' ) === 0;
662 + }
663 +
664 + /**
559 665 * Deregister core patterns.
560 666 */
561 667 public function maybe_deregister_core_patterns() {
562 668 $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'));
669 + // error_log('[PatternsWP] maybe_deregister_core_patterns called - hide_core_patterns: ' . ($hide_core_patterns ? 'true' : 'false'));
564 670
565 671 // Exit early if not enabled.
566 672 if (!$hide_core_patterns) {
567 - error_log('[PatternsWP] Core pattern hiding is disabled');
673 + // error_log('[PatternsWP] Core pattern hiding is disabled');
568 674 return;
569 675 }
570 676
571 677 // Retrieve all patterns.
@@ -571,9 +677,9 @@
571 677 // Retrieve all patterns.
572 678 $patterns = \WP_Block_Patterns_Registry::get_instance();
573 679 $all_patterns = $patterns->get_all_registered();
574 680
575 - error_log('[PatternsWP] Checking ' . count($all_patterns) . ' patterns for core ones');
681 + // error_log('[PatternsWP] Checking ' . count($all_patterns) . ' patterns for core ones');
576 682
577 683 // Loop through all patterns and deregister any that are core patterns.
578 684 foreach ($all_patterns as $index => $pattern) {
579 685 // Core patterns typically have names starting with 'core/' or no file path
@@ -578,8 +684,12 @@
578 684 foreach ($all_patterns as $index => $pattern) {
579 685 // Core patterns typically have names starting with 'core/' or no file path
580 686 $pattern_name = $pattern['name'] ?? '';
581 687 $file_path = $pattern['filePath'] ?? '';
688 +
689 + if ( $this->is_patternswp_registry_pattern( $pattern_name ) ) {
690 + continue;
691 + }
582 692
583 693 // Check if it's a core pattern
584 694 $is_core_pattern = false;
585 695 $reason = '';
@@ -595,9 +705,9 @@
595 705 $is_core_pattern = true;
596 706 $reason = 'file path contains wp-includes';
597 707 }
598 708
599 - // Method 3: Check if no file path (likely core pattern)
709 + // Method 3: Check if no file path (likely core pattern, but not plugin-registered)
600 710 if (!$is_core_pattern && empty($file_path)) {
601 711 $is_core_pattern = true;
602 712 $reason = 'no file path';
603 713 }
@@ -603,9 +713,9 @@
603 713 }
604 714
605 715 if ($is_core_pattern) {
606 716 unregister_block_pattern($pattern['name']);
607 - error_log('[PatternsWP] Deregistered core pattern: ' . $pattern['name'] . ' (' . $reason . ')');
717 + // error_log('[PatternsWP] Deregistered core pattern: ' . $pattern['name'] . ' (' . $reason . ')');
608 718 }
609 719 }
610 720 }
611 721
@@ -612,15 +722,17 @@
612 722 /**
613 723 * Filter the block patterns list in the editor
614 724 */
615 725 public function filter_block_patterns_list($patterns) {
616 - error_log('[PatternsWP] filter_block_patterns_list called with ' . count($patterns) . ' patterns');
726 + if ( ! is_array( $patterns ) ) {
727 + return $patterns;
728 + }
617 729
618 730 $hide_theme_patterns = get_option('patternswp_hide_theme_patterns', false);
619 731 $hide_uncategorized = get_option('patternswp_hide_uncategorized_patterns', false);
620 732 $hide_core_patterns = get_option('patternswp_hide_core_patterns', false);
621 733
622 - error_log('[PatternsWP] Block patterns filter - Theme: ' . ($hide_theme_patterns ? 'true' : 'false') . ', Uncategorized: ' . ($hide_uncategorized ? 'true' : 'false') . ', Core: ' . ($hide_core_patterns ? 'true' : 'false'));
734 + // error_log('[PatternsWP] Block patterns filter - Theme: ' . ($hide_theme_patterns ? 'true' : 'false') . ', Uncategorized: ' . ($hide_uncategorized ? 'true' : 'false') . ', Core: ' . ($hide_core_patterns ? 'true' : 'false'));
623 735
624 736 // If no filters are enabled, return original patterns
625 737 if (!$hide_theme_patterns && !$hide_uncategorized && !$hide_core_patterns) {
626 738 return $patterns;
@@ -635,14 +747,14 @@
635 747 if ($hide_theme_patterns && $should_include) {
636 748 // Check if pattern name starts with theme prefix
637 749 if (isset($pattern['name']) && strpos($pattern['name'], 'theme-') === 0) {
638 750 $should_include = false;
639 - error_log('[PatternsWP] Excluding theme pattern by name: ' . $pattern['name']);
751 + // error_log('[PatternsWP] Excluding theme pattern by name: ' . $pattern['name']);
640 752 }
641 753 // Check if pattern has theme file path
642 754 if (isset($pattern['filePath']) && strpos($pattern['filePath'], 'themes/') !== false) {
643 755 $should_include = false;
644 - error_log('[PatternsWP] Excluding theme pattern by path: ' . $pattern['name']);
756 + // error_log('[PatternsWP] Excluding theme pattern by path: ' . $pattern['name']);
645 757 }
646 758 }
647 759
648 760 // Check uncategorized patterns
@@ -648,9 +760,9 @@
648 760 // Check uncategorized patterns
649 761 if ($hide_uncategorized && $should_include) {
650 762 if (!isset($pattern['categories']) || empty($pattern['categories'])) {
651 763 $should_include = false;
652 - error_log('[PatternsWP] Excluding uncategorized pattern: ' . $pattern['name']);
764 + // error_log('[PatternsWP] Excluding uncategorized pattern: ' . $pattern['name']);
653 765 }
654 766 }
655 767
656 768 // Check core patterns
@@ -656,9 +768,9 @@
656 768 // Check core patterns
657 769 if ($hide_core_patterns && $should_include) {
658 770 if (isset($pattern['name']) && strpos($pattern['name'], 'core/') === 0) {
659 771 $should_include = false;
660 - error_log('[PatternsWP] Excluding core pattern: ' . $pattern['name']);
772 + // error_log('[PatternsWP] Excluding core pattern: ' . $pattern['name']);
661 773 }
662 774 }
663 775
664 776 if ($should_include) {
@@ -665,9 +777,9 @@
665 777 $filtered_patterns[] = $pattern;
666 778 }
667 779 }
668 780
669 - error_log('[PatternsWP] Filtered from ' . count($patterns) . ' to ' . count($filtered_patterns) . ' patterns');
781 + // error_log('[PatternsWP] Filtered from ' . count($patterns) . ' to ' . count($filtered_patterns) . ' patterns');
670 782
671 783 return $filtered_patterns;
672 784 }
673 785
@@ -717,9 +829,9 @@
717 829 }
718 830 }
719 831
720 832 if ($should_hide) {
721 - error_log('[PatternsWP] Filtering REST response for pattern: ' . ($pattern_data['title'] ?? 'Unknown'));
833 + // error_log('[PatternsWP] Filtering REST response for pattern: ' . ($pattern_data['title'] ?? 'Unknown'));
722 834 // Return an error response to hide this pattern
723 835 return new WP_Error(
724 836 'rest_pattern_hidden',
725 837 'Pattern is hidden by visibility settings',
@@ -783,21 +895,16 @@
783 895 // Check if it's a core pattern
784 896 if (!$should_exclude && $hide_core_patterns) {
785 897 $pattern_name = $pattern['name'] ?? '';
786 898 $file_path = $pattern['filePath'] ?? '';
787 -
788 - // Method 1: Check if pattern name starts with 'core/'
789 - if (strpos($pattern_name, 'core/') === 0) {
899 +
900 + if ( $this->is_patternswp_registry_pattern( $pattern_name ) ) {
901 + // Keep PatternsWP patterns visible.
902 + } elseif (strpos($pattern_name, 'core/') === 0) {
790 903 $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) {
904 + } elseif (!empty($file_path) && strpos($file_path, 'wp-includes') !== false) {
795 905 $should_exclude = true;
796 - }
797 -
798 - // Method 3: Check if no file path (likely core pattern)
799 - if (!$should_exclude && empty($file_path)) {
906 + } elseif (empty($file_path)) {
800 907 $should_exclude = true;
801 908 }
802 909 }
803 910
@@ -827,29 +934,26 @@
827 934 }
828 935
829 936 // Add exclude filter using direct SQL for better performance
830 937 if (!empty($exclude_patterns)) {
831 - add_filter('posts_where', function($where) use ($exclude_patterns) {
938 + $pwp_posts_where_filter = function($where) use ($exclude_patterns, &$pwp_posts_where_filter) {
832 939 global $wpdb;
940 +
941 + // Self-remove so this filter does not leak into other queries.
942 + remove_filter( 'posts_where', $pwp_posts_where_filter );
833 943
834 - // Prepare placeholders for the IN clause
835 - $placeholders = array_fill(0, count($exclude_patterns), '%s');
836 - $placeholders = implode(',', $placeholders);
944 + $placeholders = implode( ',', array_fill( 0, count( $exclude_patterns ), '%s' ) );
945 + $query = 'SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = %s AND meta_value IN (' . $placeholders . ')';
946 +
947 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders is a list of %s tokens; values are bound below.
948 + $exclude_sql = $wpdb->prepare( $query, array_merge( array( 'pattern_id' ), $exclude_patterns ) );
837 949
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 950 // Add the exclusion to the WHERE clause
848 951 $where .= " AND {$wpdb->posts}.ID NOT IN ($exclude_sql)";
849 952
850 953 return $where;
851 - });
954 + };
955 + add_filter( 'posts_where', $pwp_posts_where_filter );
852 956
853 957 // Ensure we don't use post__not_in or meta_query
854 958 unset($args['post__not_in']);
855 959 unset($args['meta_query']);
@@ -881,12 +985,19 @@
881 985 $categories = is_array($pattern['categories']) ?
882 986 $pattern['categories'] :
883 987 array_map('trim', explode(',', $pattern['categories']));
884 988
885 - foreach ($categories as $category) {
886 - if (is_string($category) &&
887 - (stripos($category, 'theme') !== false ||
888 - stripos($category, 'template') !== false)) {
989 + foreach ( $categories as $category ) {
990 + if ( ! is_string( $category ) ) {
991 + continue;
992 + }
993 +
994 + // PatternsWP library slugs such as patternswp-page-templates.
995 + if ( 0 === strpos( $category, 'patternswp-' ) || 0 === strpos( $category, 'patternswp/' ) ) {
996 + continue;
997 + }
998 +
999 + if ( false !== stripos( $category, 'theme' ) || false !== stripos( $category, 'template' ) ) {
889 1000 return true;
890 1001 }
891 1002 }
892 1003 }
@@ -930,13 +1041,13 @@
930 1041 * Filter patterns based on visibility settings
931 1042 */
932 1043 public function filter_patterns_by_visibility($patterns) {
933 1044 // Debug logging
934 - error_log('[PatternsWP] Filter applied with ' . count((array)$patterns) . ' patterns');
1045 + // error_log('[PatternsWP] Filter applied with ' . count((array)$patterns) . ' patterns');
935 1046
936 1047 // If no patterns or not an array, return early
937 1048 if (empty($patterns) || !is_array($patterns)) {
938 - error_log('[PatternsWP] No patterns to filter');
1049 + // error_log('[PatternsWP] No patterns to filter');
939 1050 return $patterns;
940 1051 }
941 1052
942 1053 $filtered_patterns = array();
@@ -943,34 +1054,33 @@
943 1054 $hide_theme_patterns = get_option('patternswp_hide_theme_patterns', false);
944 1055 $hide_uncategorized = get_option('patternswp_hide_uncategorized_patterns', false);
945 1056 $hide_core_patterns = get_option('patternswp_hide_core_patterns', false);
946 1057
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'));
1058 + // error_log('[PatternsWP] Settings - Hide Theme: ' . ($hide_theme_patterns ? 'true' : 'false') .
1059 + // ', Hide Uncategorized: ' . ($hide_uncategorized ? 'true' : 'false') .
1060 + // ', Hide Core: ' . ($hide_core_patterns ? 'true' : 'false'));
950 1061
951 1062 foreach ($patterns as $pattern) {
952 1063 // Skip if pattern is not an array
953 1064 if (!is_array($pattern)) {
954 - $filtered_patterns[] = $pattern;
955 1065 continue;
956 1066 }
957 1067
958 1068 // Skip theme patterns if enabled
959 1069 if ($hide_theme_patterns && $this->is_theme_pattern($pattern)) {
960 - error_log('[PatternsWP] Skipping theme pattern: ' . ($pattern['title'] ?? 'Unknown'));
1070 + // error_log('[PatternsWP] Skipping theme pattern: ' . ($pattern['title'] ?? 'Unknown'));
961 1071 continue;
962 1072 }
963 1073
964 1074 // Skip core patterns if enabled
965 1075 if ($hide_core_patterns && $this->is_core_pattern($pattern)) {
966 - error_log('[PatternsWP] Skipping core pattern: ' . ($pattern['title'] ?? 'Unknown'));
1076 + // error_log('[PatternsWP] Skipping core pattern: ' . ($pattern['title'] ?? 'Unknown'));
967 1077 continue;
968 1078 }
969 1079
970 1080 // Skip uncategorized patterns if enabled
971 1081 if ($hide_uncategorized && $this->is_uncategorized_pattern($pattern)) {
972 - error_log('[PatternsWP] Skipping uncategorized pattern: ' . ($pattern['title'] ?? 'Unknown'));
1082 + // error_log('[PatternsWP] Skipping uncategorized pattern: ' . ($pattern['title'] ?? 'Unknown'));
973 1083 continue;
974 1084 }
975 1085
976 1086 $filtered_patterns[] = $pattern;
@@ -975,9 +1085,9 @@
975 1085
976 1086 $filtered_patterns[] = $pattern;
977 1087 }
978 1088
979 - error_log('[PatternsWP] Filtered to ' . count($filtered_patterns) . ' patterns');
1089 + // error_log('[PatternsWP] Filtered to ' . count($filtered_patterns) . ' patterns');
980 1090 return $filtered_patterns;
981 1091 }
982 1092
983 1093 /**
@@ -1080,96 +1190,9 @@
1080 1190 /**
1081 1191 * Render Pattern Visibility page
1082 1192 */
1083 1193 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
1194 + $this->render_admin_app( __( 'Settings', 'patternswp' ) );
1172 1195 }
1173 1196
1174 1197 /**
1175 1198 * Redirect to welcome page after activation