PluginProbe ʕ •ᴥ•ʔ
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / trunk
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz vtrunk
2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8 0.0.9 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.1.2 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.12.3 1.13.0 1.13.1 1.13.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.8.0 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.2 2.6.0
sureforms / inc / lib / astra-notices / class-bsf-admin-notices.php
sureforms / inc / lib / astra-notices Last commit date
class-bsf-admin-notices.php 4 months ago notices.css 4 months ago notices.js 4 months ago
class-bsf-admin-notices.php
449 lines
1 <?php
2 /**
3 * BSF Admin Notices
4 *
5 * An easy to use PHP Library to add dismissible admin notices in the WordPress admin.
6 *
7 * @package BSF Admin Notices
8 * @since 1.2.0
9 */
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit; // Exit if accessed directly.
13 }
14
15 if ( ! class_exists( 'BSF_Admin_Notices' ) ) :
16
17 /**
18 * BSF_Admin_Notices
19 *
20 * Renamed from Astra_Notices. All runtime strings (AJAX action, nonce,
21 * script handles, JS globals, CSS classes, option keys, ID prefixes) are
22 * intentionally frozen at their original values so old plugin JS/CSS that
23 * is already shipped continues to work without updates.
24 *
25 * @since 1.2.0
26 */
27 class BSF_Admin_Notices {
28
29 /**
30 * Library version.
31 *
32 * @access private
33 * @var string
34 * @since 1.2.0
35 */
36 private static $version = '1.2.1';
37
38 /**
39 * Registered notices.
40 *
41 * @access private
42 * @var array
43 * @since 1.2.0
44 */
45 private static $notices = array();
46
47 /**
48 * Instance.
49 *
50 * @access private
51 * @var object Class object.
52 * @since 1.2.0
53 */
54 private static $instance;
55
56 /**
57 * Initiator.
58 *
59 * @since 1.2.0
60 * @return object initialized object of class.
61 */
62 public static function get_instance() {
63 if ( ! isset( self::$instance ) ) {
64 self::$instance = new self();
65 }
66 return self::$instance;
67 }
68
69 /**
70 * Constructor.
71 *
72 * @since 1.2.0
73 */
74 public function __construct() {
75 add_action( 'admin_notices', array( $this, 'show_notices' ), 30 );
76 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
77 add_action( 'wp_ajax_astra-notice-dismiss', array( $this, 'dismiss_notice' ) );
78 add_filter( 'wp_kses_allowed_html', array( $this, 'add_data_attributes' ), 10, 2 );
79 }
80
81 /**
82 * Filters and returns a list of allowed tags and attributes for a given context.
83 *
84 * @param array $allowedposttags array of allowed tags.
85 * @param string $context Context type (explicit).
86 * @since 1.2.0
87 * @return array
88 */
89 public function add_data_attributes( $allowedposttags, $context ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
90 $allowedposttags['a']['data-repeat-notice-after'] = true;
91
92 return $allowedposttags;
93 }
94
95 /**
96 * Add Notice.
97 *
98 * @since 1.2.0
99 * @param array $args Notice arguments.
100 * @return void
101 */
102 public static function add_notice( $args = array() ) {
103 self::$notices[] = $args;
104
105 if ( ! isset( $args['id'] ) ) {
106 return;
107 }
108
109 $notice_id = sanitize_key( $args['id'] ); // Notice ID.
110 $notices = get_option( 'allowed_astra_notices', array() );
111 if ( ! in_array( $notice_id, $notices, true ) ) {
112 $notices[] = $notice_id; // Add notice id to the array.
113 update_option( 'allowed_astra_notices', $notices ); // Update the option.
114 }
115 }
116
117 /**
118 * Dismiss Notice.
119 *
120 * @since 1.2.0
121 * @return void
122 */
123 public function dismiss_notice() {
124 check_ajax_referer( 'astra-notices', 'nonce' );
125
126 $notice_id = ( isset( $_POST['notice_id'] ) ) ? sanitize_key( wp_unslash( $_POST['notice_id'] ) ) : '';
127 $repeat_notice_after = ( isset( $_POST['repeat_notice_after'] ) ) ? absint( $_POST['repeat_notice_after'] ) : 0;
128 $notice = $this->get_notice_by_id( $notice_id );
129 $capability = isset( $notice['capability'] ) ? $notice['capability'] : 'manage_options';
130
131 $has_cap = current_user_can( $capability );
132
133 /**
134 * Filters whether the current user passes the capability check for notice dismissal.
135 *
136 * Both the legacy and new filter names are fired for backward compatibility.
137 * Filters can only restrict access (return false), never grant it — if the
138 * underlying current_user_can() check fails, filters cannot override to true.
139 */
140 $cap_check = apply_filters( 'astra_notices_user_cap_check', $has_cap );
141 $cap_check = apply_filters( 'bsf_admin_notices_user_cap_check', $cap_check );
142
143 if ( ! $has_cap || ! $cap_check ) {
144 wp_send_json_error( esc_html__( 'Permission denied.', 'astra-notices' ) );
145 }
146
147 $allowed_notices = get_option( 'allowed_astra_notices', array() ); // Get allowed notices.
148
149 // Define restricted user meta keys using the dynamic table prefix.
150 global $wpdb;
151 $wp_default_meta_keys = array(
152 $wpdb->prefix . 'capabilities',
153 $wpdb->prefix . 'user_level',
154 $wpdb->prefix . 'user-settings',
155 'account_status',
156 'session_tokens',
157 );
158
159 // if $notice_id does not start with astra-notices-id and notice_id is not from the allowed notices, then return.
160 if ( 0 !== strpos( $notice_id, 'astra-notices-id-' ) && ( ! in_array( $notice_id, $allowed_notices, true ) ) ) {
161 wp_send_json_error( esc_html__( 'Invalid notice ID.', 'astra-notices' ) );
162 }
163
164 // Valid inputs?
165 if ( ! empty( $notice_id ) ) {
166
167 if ( in_array( $notice_id, $wp_default_meta_keys, true ) ) {
168 wp_send_json_error( esc_html__( 'Invalid notice ID.', 'astra-notices' ) );
169 }
170
171 if ( ! empty( $repeat_notice_after ) ) {
172 set_transient( $notice_id, true, $repeat_notice_after );
173 } else {
174 update_user_meta( get_current_user_id(), $notice_id, 'notice-dismissed' );
175 }
176
177 wp_send_json_success();
178 }
179
180 wp_send_json_error();
181 }
182
183 /**
184 * Enqueue Scripts.
185 *
186 * @since 1.2.0
187 * @return void
188 */
189 public function enqueue_scripts() {
190 wp_register_style( 'astra-notices', self::get_url() . 'notices.css', array(), self::$version );
191 wp_register_script( 'astra-notices', self::get_url() . 'notices.js', array( 'jquery' ), self::$version, true );
192 wp_localize_script(
193 'astra-notices',
194 'astraNotices',
195 array(
196 '_notice_nonce' => wp_create_nonce( 'astra-notices' ),
197 )
198 );
199 }
200
201 /**
202 * Sort the notices based on the given priority of the notice.
203 * This function is called from usort()
204 *
205 * @since 1.2.0
206 * @param array $notice_1 First notice.
207 * @param array $notice_2 Second Notice.
208 * @return array
209 */
210 public function sort_notices( $notice_1, $notice_2 ) {
211 if ( ! isset( $notice_1['priority'] ) ) {
212 $notice_1['priority'] = 10;
213 }
214 if ( ! isset( $notice_2['priority'] ) ) {
215 $notice_2['priority'] = 10;
216 }
217
218 return $notice_1['priority'] - $notice_2['priority'];
219 }
220
221 /**
222 * Get all registered notices.
223 *
224 * @return array|null
225 */
226 private function get_notices() {
227 usort( self::$notices, array( $this, 'sort_notices' ) );
228
229 return self::$notices;
230 }
231
232 /**
233 * Get notice by notice_id.
234 *
235 * @param string $notice_id Notice id.
236 *
237 * @return array notice based on the notice id.
238 */
239 private function get_notice_by_id( $notice_id ) {
240 if ( empty( $notice_id ) ) {
241 return array();
242 }
243
244 $notices = $this->get_notices();
245 $notice = wp_list_filter(
246 $notices,
247 array(
248 'id' => $notice_id,
249 )
250 );
251
252 return ( ! empty( $notice ) && isset( $notice[0] ) ) ? $notice[0] : array();
253 }
254
255 /**
256 * Display the notices in the WordPress admin.
257 *
258 * @since 1.2.0
259 * @return void
260 */
261 public function show_notices() {
262 $defaults = array(
263 'id' => '', // Optional, Notice ID. If empty it set `astra-notices-id-<$array-index>`.
264 'type' => 'info', // Optional, Notice type. Default `info`. Expected [info, warning, notice, error].
265 'message' => '', // Optional, Message.
266 'show_if' => true, // Optional, Show notice on custom condition. E.g. 'show_if' => if( is_admin() ) ? true, false, .
267 'repeat-notice-after' => '', // Optional, Dismiss-able notice time. It'll auto show after given time.
268 'display-notice-after' => false, // Optional, Dismiss-able notice time. It'll auto show after given time.
269 'class' => '', // Optional, Additional notice wrapper class.
270 'priority' => 10, // Priority of the notice.
271 'display-with-other-notices' => true, // Should the notice be displayed if other notices are being displayed from BSF_Admin_Notices.
272 'is_dismissible' => true,
273 'capability' => 'manage_options', // User capability - This capability is required for the current user to see this notice.
274 );
275
276 // Count for the notices that are rendered.
277 $notices_displayed = 0;
278 $notices = $this->get_notices();
279
280 foreach ( $notices as $key => $notice ) {
281 $notice = wp_parse_args( $notice, $defaults );
282
283 // Show notices only for users with `manage_options` cap.
284 if ( ! current_user_can( $notice['capability'] ) ) {
285 continue;
286 }
287
288 $notice['id'] = self::get_notice_id( $notice, $key );
289 $notice['classes'] = self::get_wrap_classes( $notice );
290
291 // Notices visible after transient expire.
292 if ( isset( $notice['show_if'] ) && true === $notice['show_if'] ) {
293
294 // don't display the notice if it is not supposed to be displayed with other notices.
295 if ( 0 !== $notices_displayed && false === $notice['display-with-other-notices'] ) {
296 continue;
297 }
298
299 if ( self::is_expired( $notice ) ) {
300
301 self::markup( $notice );
302 ++$notices_displayed;
303 }
304 }
305 }
306 }
307
308 /**
309 * Render a notice.
310 *
311 * @since 1.2.0
312 * @param array $notice Notice markup.
313 * @return void
314 */
315 public static function markup( $notice = array() ) {
316 wp_enqueue_script( 'astra-notices' );
317 wp_enqueue_style( 'astra-notices' );
318
319 // Dual-emit: legacy (astra_notice_*) + new (bsf_admin_notice_*) hooks for backward compat.
320 // Note: consumers hooking BOTH names for the same event will be called twice.
321 do_action( 'astra_notice_before_markup' );
322 do_action( 'bsf_admin_notice_before_markup' );
323
324 do_action( "astra_notice_before_markup_{$notice['id']}" );
325 do_action( "bsf_admin_notice_before_markup_{$notice['id']}" );
326
327 ?>
328 <div id="<?php echo esc_attr( $notice['id'] ); ?>" class="<?php echo esc_attr( 'astra-notice-wrapper ' . $notice['classes'] ); ?>" data-repeat-notice-after="<?php echo esc_attr( $notice['repeat-notice-after'] ); ?>">
329 <div class="astra-notice-container">
330 <?php do_action( "astra_notice_inside_markup_{$notice['id']}" ); ?>
331 <?php do_action( "bsf_admin_notice_inside_markup_{$notice['id']}" ); ?>
332 <?php echo wp_kses_post( $notice['message'] ); ?>
333 </div>
334 </div>
335 <?php
336
337 do_action( "astra_notice_after_markup_{$notice['id']}" );
338 do_action( "bsf_admin_notice_after_markup_{$notice['id']}" );
339
340 do_action( 'astra_notice_after_markup' );
341 do_action( 'bsf_admin_notice_after_markup' );
342 }
343
344 /**
345 * Get wrapper classes for a notice.
346 *
347 * @since 1.2.0
348 *
349 * @param array $notice Notice arguments.
350 * @return array Notice wrapper classes.
351 */
352 private static function get_wrap_classes( $notice ) {
353 $classes = array( 'astra-notice', 'notice' );
354
355 if ( $notice['is_dismissible'] ) {
356 $classes[] = 'is-dismissible';
357 }
358
359 $classes[] = $notice['class'];
360 if ( isset( $notice['type'] ) && '' !== $notice['type'] ) {
361 $classes[] = 'notice-' . $notice['type'];
362 }
363
364 return esc_attr( implode( ' ', $classes ) );
365 }
366
367 /**
368 * Get HTML ID for a given notice.
369 *
370 * @since 1.2.0
371 *
372 * @param array $notice Notice arguments.
373 * @param int $key Notice array index.
374 * @return string HTML if for the notice.
375 */
376 private static function get_notice_id( $notice, $key ) {
377 if ( isset( $notice['id'] ) && ! empty( $notice['id'] ) ) {
378 return $notice['id'];
379 }
380
381 return 'astra-notices-id-' . $key;
382 }
383
384 /**
385 * Check if the notice is expires.
386 *
387 * @since 1.2.0
388 *
389 * @param array $notice Notice arguments.
390 * @return boolean
391 */
392 private static function is_expired( $notice ) {
393 $transient_status = get_transient( $notice['id'] );
394
395 if ( false === $transient_status ) {
396
397 if ( isset( $notice['display-notice-after'] ) && false !== $notice['display-notice-after'] ) {
398
399 if ( 'delayed-notice' !== get_user_meta( get_current_user_id(), $notice['id'], true ) &&
400 'notice-dismissed' !== get_user_meta( get_current_user_id(), $notice['id'], true ) ) {
401 set_transient( $notice['id'], 'delayed-notice', $notice['display-notice-after'] );
402 update_user_meta( get_current_user_id(), $notice['id'], 'delayed-notice' );
403
404 return false;
405 }
406 }
407
408 // Check the user meta status if current notice is dismissed or delay completed.
409 $meta_status = get_user_meta( get_current_user_id(), $notice['id'], true );
410
411 if ( empty( $meta_status ) || 'delayed-notice' === $meta_status ) {
412 return true;
413 }
414 }
415
416 return false;
417 }
418
419 /**
420 * Get base URL for the library assets.
421 *
422 * @return mixed URL.
423 */
424 public static function get_url() {
425 $path = wp_normalize_path( dirname( __FILE__ ) ); // phpcs:ignore Modernize.FunctionCalls.Dirname.FileConstant
426 $theme_dir = wp_normalize_path( get_template_directory() );
427
428 if ( false !== strpos( $path, $theme_dir ) ) {
429 return trailingslashit( get_template_directory_uri() . str_replace( $theme_dir, '', $path ) );
430 } else {
431 return plugin_dir_url( __FILE__ );
432 }
433 }
434 }
435
436 /**
437 * Kicking this off by calling 'get_instance()' method
438 */
439 BSF_Admin_Notices::get_instance();
440
441 endif;
442
443 // Backward compatibility alias for bsf-analytics library and third-party plugins
444 // that still reference the old class name. Safe to remove once all consumers
445 // are updated.
446 if ( ! class_exists( 'Astra_Notices' ) ) {
447 class_alias( 'BSF_Admin_Notices', 'Astra_Notices' ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.class_aliasFound
448 }
449