PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / trunk
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts vtrunk
2.7.7 2.7.6 2.7.5 2.7.4 trunk 1.3 2.0.4 2.0.6 2.1.91 2.2.4 2.2.7 2.2.9 2.3.1 2.3.10 2.4.10 2.4.2 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.6.0 2.6.1 2.7.0 All 28 releases
← All changes | includes/class.execute.snippet.php +645 -133 2.6.1trunk View file →
@@ -1,14 +1,12 @@
1 1 <?php
2 2 /**
3 3 * Execute snippet
4 4 *
5 - * @author Artem Prihodko <webtemyk@yandex.ru>
6 - * @copyright (c) 2020, CreativeMotion
7 - * @version 2.4
5 + * @package Woody_Code_Snippets
8 6 */
9 7
10 -// Exit if accessed directly
8 +// Exit if accessed directly.
11 9 if ( ! defined( 'ABSPATH' ) ) {
12 10 exit;
13 11 }
14 12
@@ -28,8 +26,29 @@
28 26 * @var WINP_Insertion_Locations
29 27 */
30 28 public $snippets_locations;
31 29
30 + /**
31 + * Current snippet ID being executed.
32 + *
33 + * @var int Current snippet ID being executed (for error handling).
34 + */
35 + private static $current_snippet_id = 0;
36 +
37 + /**
38 + * Indicates whether shutdown handler has been registered.
39 + *
40 + * @var bool Whether shutdown handler has been registered.
41 + */
42 + private static $shutdown_registered = false;
43 +
44 + /**
45 + * Tracked executed snippets on current page.
46 + *
47 + * @var array<int, array{id: int, name: string, type: string, location: string, scope: string}>
48 + */
49 + private $executed_snippets = [];
50 +
32 51 public static function app() {
33 52 if ( self::$instance === null ) {
34 53 self::$instance = new self();
35 54 }
@@ -37,12 +56,36 @@
37 56 return self::$instance;
38 57 }
39 58
40 59 /**
60 + * Get executed snippets on current page.
61 + *
62 + * @return array<int, array{id: int, name: string, type: string, location: string, scope: string}>
63 + */
64 + public function get_executed_snippets() {
65 + return $this->executed_snippets;
66 + }
67 +
68 + /**
41 69 * WINP_Execute_Snippet constructor.
42 70 */
43 71 public function __construct() {
44 72 self::$instance = $this;
73 +
74 + // Check if this is an AJAX validation request and skip the snippet being validated.
75 + if ( wp_doing_ajax()
76 + && isset( $_POST['action'] ) && 'wbcr_inp_ajax_validate_snippet' === $_POST['action']
77 + && isset( $_POST['post_id'] ) ) {
78 + $validating_snippet_id = (int) $_POST['post_id'];
79 + add_filter(
80 + 'winp_skip_snippet_execution',
81 + function ( $should_skip, $snippet_id ) use ( $validating_snippet_id ) {
82 + return $should_skip || $snippet_id === $validating_snippet_id;
83 + },
84 + 1,
85 + 2
86 + );
87 + }
45 88
46 89 if ( ! defined( 'WINP_UPLOAD_DIR' ) ) {
47 90 $dir = wp_upload_dir();
48 91 define( 'WINP_UPLOAD_DIR', $dir['basedir'] . '/winp-css-js' );
@@ -53,18 +96,18 @@
53 96 define( 'WINP_UPLOAD_URL', $dir['baseurl'] . '/winp-css-js' );
54 97 }
55 98 global $wpdb;
56 99
57 - //todo: Simplify your request. Seems written ugly
100 + // todo: Simplify your request. Seems written ugly
58 101 $sql = "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_content, p2.meta_value as priority
59 102 FROM {$wpdb->posts}
60 103 INNER JOIN {$wpdb->postmeta} p1 ON ({$wpdb->posts}.ID = p1.post_id)
61 104 INNER JOIN {$wpdb->postmeta} p2 ON ({$wpdb->posts}.ID = p2.post_id)
62 105 INNER JOIN {$wpdb->postmeta} p3 ON ({$wpdb->posts}.ID = p3.post_id)
63 - WHERE (( p1.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_scope' AND p1.meta_value = '%s')
106 + WHERE (( p1.meta_key = 'wbcr_inp_snippet_scope' AND p1.meta_value = '%s')
64 107 AND
65 - ( p3.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_activate' AND p3.meta_value = '1')
66 - AND p2.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_priority' )
108 + ( p3.meta_key = 'wbcr_inp_snippet_activate' AND p3.meta_value = '1')
109 + AND p2.meta_key = 'wbcr_inp_snippet_priority' )
67 110 AND {$wpdb->posts}.post_type = '" . WINP_SNIPPETS_POST_TYPE . "'
68 111 AND ({$wpdb->posts}.post_status = 'publish')
69 112 ORDER BY CAST(priority AS UNSIGNED) %s";
70 113
@@ -77,21 +120,21 @@
77 120
78 121 /**
79 122 * Register hooks
80 123 */
81 - public function registerHooks() {
82 - add_action( 'plugins_loaded', [ $this, 'executeEverywhereSnippets' ], 1 );
124 + public function register_hooks() {
125 + add_action( 'init', [ $this, 'execute_everywhere_snippets' ], 1 );
83 126
84 - if ( ! is_admin() ) { #issue PCS-45 fix bug with WPBPage Builder Frontend Editor
85 - add_action( 'wp_head', [ $this, 'executeHeaderSnippets' ] );
86 - add_action( 'wp_footer', [ $this, 'executeFooterSnippets' ] );
127 + if ( ! is_admin() ) { // issue PCS-45 fix bug with WPBPage Builder Frontend Editor
128 + add_action( 'wp_head', [ $this, 'execute_header_snippets' ] );
129 + add_action( 'wp_footer', [ $this, 'execute_footer_snippets' ] );
87 130 add_action( 'the_post', [ $this, 'executePostSnippets' ], 10, 2 );
88 131 add_filter( 'the_content', [ $this, 'executeContentSnippets' ] );
89 132 add_filter( 'the_excerpt', [ $this, 'executeExcerptSnippets' ] );
90 133 // Бесполезный хук, который вызывается на каждый комментарий. Если их много, увеличивается нагрузка
91 - //add_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
134 + // add_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
92 135
93 - //add_action( 'wp_head', [ $this, 'executeWoocommerceSnippets' ] );
136 + // add_action( 'wp_head', [ $this, 'executeWoocommerceSnippets' ] );
94 137
95 138 if ( ! empty( $this->snippets_locations->getInsertion( 'custom' ) ) ) {
96 139 add_action( 'wp_head', [ $this, 'executeCustomSnippets' ] );
97 140 }
@@ -100,30 +143,30 @@
100 143
101 144 /**
102 145 * Execute the everywhere snippets once the plugins are loaded
103 146 */
104 - public function executeEverywhereSnippets() {
105 - echo $this->executeActiveSnippets( 'evrywhere' );
147 + public function execute_everywhere_snippets() {
148 + echo $this->execute_active_snippets( 'evrywhere' );
106 149 }
107 150
108 151 /**
109 152 * Execute the snippets in header of page once the plugins are loaded
110 153 */
111 - public function executeHeaderSnippets() {
112 - echo $this->executeActiveSnippets( 'auto', 'header' );
154 + public function execute_header_snippets() {
155 + echo $this->execute_active_snippets( 'auto', 'header' );
113 156 }
114 157
115 158 /**
116 159 * Execute the snippets in footer of page once the plugins are loaded
117 160 */
118 - public function executeFooterSnippets() {
119 - echo $this->executeActiveSnippets( 'auto', 'footer' );
161 + public function execute_footer_snippets() {
162 + echo $this->execute_active_snippets( 'auto', 'footer' );
120 163 }
121 164
122 165 /**
123 166 * Execute the snippets before post
124 167 *
125 - * @param WP_Post $post
168 + * @param WP_Post $post
126 169 * @param WP_Query $query
127 170 */
128 171 public function executePostSnippets( $post, $query ) {
129 172 $content = '';
@@ -131,22 +174,20 @@
131 174 $post_type = ! empty( $post ) ? $post->post_type : get_post( $post->ID )->post_type;
132 175 if ( is_singular( [ $post_type ] ) ) {
133 176 if ( did_action( 'get_header' ) ) {
134 177 // Перед заголовком
135 - $content = $this->executeActiveSnippets( 'auto', 'before_post' );
178 + $content = $this->execute_active_snippets( 'auto', 'before_post' );
136 179 }
137 - } else {
138 - if ( $query->post_count > 0 ) {
139 - if ( $query->post_count > 1 && $query->current_post > 0 && $query->post_count > $query->current_post ) {
140 - // Между записями
141 - $content = $this->executeActiveSnippets( 'auto', 'between_posts' );
142 - }
180 + } elseif ( $query->post_count > 0 ) {
181 + if ( $query->post_count > 1 && $query->current_post > 0 && $query->post_count > $query->current_post ) {
182 + // Между записями
183 + $content = $this->execute_active_snippets( 'auto', 'between_posts' );
184 + }
143 185 // Перед записью
144 - $content .= $this->executeActiveSnippets( 'auto', 'before_posts', '', $query );
186 + $content .= $this->execute_active_snippets( 'auto', 'before_posts', '', $query );
145 187
146 188 // После записи
147 - $content .= $this->executeActiveSnippets( 'auto', 'after_posts', '', $query );
148 - }
189 + $content .= $this->execute_active_snippets( 'auto', 'after_posts', '', $query );
149 190 }
150 191
151 192 echo $content;
152 193 }
@@ -196,13 +237,13 @@
196 237
197 238 /**
198 239 * Handle posts content
199 240 *
200 - * @param string $content
201 - * @param string $snippet_content
241 + * @param string $content
242 + * @param string $snippet_content
202 243 * @param integer $post_number
203 - * @param string $type
204 - * @param object $query
244 + * @param string $type
245 + * @param object $query
205 246 *
206 247 * @return mixed
207 248 */
208 249 private function handlePostsContent( $content, $snippet_content, $post_number, $type, $query ) {
@@ -242,29 +283,29 @@
242 283 $post_type = ! empty( $post ) ? $post->post_type : false;
243 284
244 285 if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
245 286 // Перед коротким описанием
246 - $content = $this->executeActiveSnippets( 'auto', 'before_excerpt' ) . $content;
287 + $content = $this->execute_active_snippets( 'auto', 'before_excerpt' ) . $content;
247 288
248 289 // После короткого описания
249 - $content .= $this->executeActiveSnippets( 'auto', 'after_excerpt' );
290 + $content .= $this->execute_active_snippets( 'auto', 'after_excerpt' );
250 291 }
251 292
252 293 if ( is_singular( [ $post_type ] ) ) {
253 294 // Перед параграфом
254 - $content = $this->executeActiveSnippets( 'auto', 'before_paragraph', $content );
295 + $content = $this->execute_active_snippets( 'auto', 'before_paragraph', $content );
255 296
256 297 // После параграфа
257 - $content = $this->executeActiveSnippets( 'auto', 'after_paragraph', $content );
298 + $content = $this->execute_active_snippets( 'auto', 'after_paragraph', $content );
258 299
259 300 // После заголовка
260 - $content = $this->executeActiveSnippets( 'auto', 'before_content' ) . $content;
301 + $content = $this->execute_active_snippets( 'auto', 'before_content' ) . $content;
261 302
262 303 // После текста
263 - $content .= $this->executeActiveSnippets( 'auto', 'after_content' );
304 + $content .= $this->execute_active_snippets( 'auto', 'after_content' );
264 305
265 306 // После поста
266 - $content .= $this->executeActiveSnippets( 'auto', 'after_post' );
307 + $content .= $this->execute_active_snippets( 'auto', 'after_post' );
267 308
268 309 if ( ! comments_open( $post->ID ) && ! get_comments_number( $post->ID ) ) {
269 310 remove_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
270 311 }
@@ -286,12 +327,12 @@
286 327 */
287 328 public function executeExcerptSnippets( $excerpt ) {
288 329 if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
289 330 // Перед коротким описанием
290 - $excerpt = $this->executeActiveSnippets( 'auto', 'before_excerpt' ) . $excerpt;
331 + $excerpt = $this->execute_active_snippets( 'auto', 'before_excerpt' ) . $excerpt;
291 332
292 333 // После короткого описания
293 - $excerpt .= $this->executeActiveSnippets( 'auto', 'after_excerpt' );
334 + $excerpt .= $this->execute_active_snippets( 'auto', 'after_excerpt' );
294 335 }
295 336
296 337 return $excerpt;
297 338 }
@@ -330,9 +371,9 @@
330 371
331 372 $post_type = ! empty( $post ) ? $post->post_type : false;
332 373 if ( is_singular( [ $post_type ] ) ) {
333 374 // После комментариев
334 - $content = $this->executeActiveSnippets( 'auto', 'after_post' );
375 + $content = $this->execute_active_snippets( 'auto', 'after_post' );
335 376 }
336 377
337 378 echo $content;
338 379 }
@@ -344,9 +385,9 @@
344 385 */
345 386 public function executeCustomSnippets() {
346 387 $locations = $this->snippets_locations->getInsertion( 'custom' );
347 388 foreach ( $locations as $location => $data ) {
348 - $this->executeActiveSnippets( 'auto', $location );
389 + $this->execute_active_snippets( 'auto', $location );
349 390 }
350 391 }
351 392
352 393 /**
@@ -363,16 +404,22 @@
363 404 };
364 405
365 406 switch ( $location ) {
366 407 case 'woo_before_shop_loop':
367 - add_filter( 'woocommerce_product_loop_start', function ( $content ) use ( $snippet_content ) {
368 - return $snippet_content . $content;
369 - } );
408 + add_filter(
409 + 'woocommerce_product_loop_start',
410 + function ( $content ) use ( $snippet_content ) {
411 + return $snippet_content . $content;
412 + }
413 + );
370 414 break;
371 415 case 'woo_after_shop_loop':
372 - add_filter( 'woocommerce_product_loop_end', function ( $content ) use ( $snippet_content ) {
373 - return $content . $snippet_content;
374 - } );
416 + add_filter(
417 + 'woocommerce_product_loop_end',
418 + function ( $content ) use ( $snippet_content ) {
419 + return $content . $snippet_content;
420 + }
421 + );
375 422 break;
376 423 case 'woo_before_single_product':
377 424 add_action( 'woocommerce_before_single_product', $action, 10, 2 );
378 425 break;
@@ -426,41 +473,26 @@
426 473 *
427 474 * @param string $scope
428 475 * @param string $location
429 476 * @param string $content
430 - * @param array $custom_params
477 + * @param array $custom_params
431 478 *
432 479 * @return string
433 480 */
434 - public function executeActiveSnippets( $scope = 'evrywhere', $location = '', $content = '', $custom_params = [] ) {
435 - /*
436 - global $wpdb;
437 -
438 - if ( $scope == 'evrywhere' ) {
439 - $sort = 'DESC';
440 - } else {
441 - $sort = 'ASC';
442 - }
443 - $snippets = $wpdb->get_results( "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_content, p2.meta_value as priority
444 - FROM {$wpdb->posts}
445 - INNER JOIN {$wpdb->postmeta} p1 ON ({$wpdb->posts}.ID = p1.post_id)
446 - INNER JOIN {$wpdb->postmeta} p2 ON ({$wpdb->posts}.ID = p2.post_id)
447 - INNER JOIN {$wpdb->postmeta} p3 ON ({$wpdb->posts}.ID = p3.post_id)
448 - WHERE (( p1.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_scope' AND p1.meta_value = '{$scope}')
449 - AND
450 - ( p3.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_activate' AND p3.meta_value = '1')
451 - AND p2.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_priority' )
452 - AND {$wpdb->posts}.post_type = '" . WINP_SNIPPETS_POST_TYPE . "'
453 - AND ({$wpdb->posts}.post_status = 'publish')
454 - ORDER BY CAST(priority AS UNSIGNED) {$sort}" );
455 - */
456 -
481 + public function execute_active_snippets( $scope = 'evrywhere', $location = '', $content = '', $custom_params = [] ) {
457 482 $snippets = $this->snippets[ $scope ] ?? [];
458 483
459 484 if ( ! empty( $snippets ) ) {
460 485 foreach ( (array) $snippets as $snippet ) {
461 486 $id = (int) $snippet->ID;
462 - //$is_active = (int) WINP_Helper::getMetaOption( $id, 'snippet_activate', 0 );
487 +
488 + // Allow filtering to skip specific snippet IDs.
489 + $should_skip = apply_filters( 'winp_skip_snippet_execution', false, $id );
490 + if ( $should_skip ) {
491 + continue;
492 + }
493 +
494 + // $is_active = (int) WINP_Helper::getMetaOption( $id, 'snippet_activate', 0 );
463 495 // Если это сниппет с автовставкой и выбранное место подходит под активный action
464 496 $avail_place = ( 'auto' == $scope ? $location == WINP_Helper::getMetaOption( $id, 'snippet_location', '' ) : true );
465 497 // Если условие отображения сниппета выполняется
466 498 $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
@@ -466,13 +498,13 @@
466 498 $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
467 499 $is_condition = $snippet_type != WINP_SNIPPET_TYPE_PHP ? $this->checkCondition( $id ) : true;
468 500
469 501 if ( $avail_place && $is_condition ) {
470 - $post_id = (int) WINP_Plugin::app()->request->post( 'post_ID', 0 );
502 + $post_id = (int) WINP_HTTP::post( 'post_ID', 0 );
471 503
472 504 if ( ( isset( $_POST['wbcr_inp_snippet_scope'] )
473 - && $post_id === $id
474 - && WINP_Plugin::app()->currentUserCan() ) || WINP_Helper::is_safe_mode() ) {
505 + && $post_id === $id
506 + && WINP_Plugin::app()->current_user_car() ) || WINP_Helper::is_safe_mode() ) {
475 507 return $content;
476 508 }
477 509
478 510 // WPML Compatibility
@@ -491,9 +523,12 @@
491 523 * Filter snippet code before execute
492 524 */
493 525 $snippet_code = apply_filters( 'wbcr/inp/execute_snippet/snippet_code', $snippet_code, $id );
494 526
495 - if ( WINP_Plugin::app()->getOption( 'execute_shortcode' ) ) {
527 + // Track this snippet as executed.
528 + $this->track_executed_snippet( $id, $scope );
529 +
530 + if ( get_option( 'wbcr_inp_execute_shortcode' ) ) {
496 531 $snippet_code = do_shortcode( $snippet_code );
497 532 }
498 533
499 534 if ( $snippet_type === WINP_SNIPPET_TYPE_TEXT || $snippet_type === WINP_SNIPPET_TYPE_AD ) {
@@ -512,17 +547,20 @@
512 547
513 548 // If the user has prohibited the insertion of unfiltered HTML,
514 549 // we prohibit the execution of snippets.
515 550 if ( ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML )
516 - && ! in_array( $snippet_type, [
517 - WINP_SNIPPET_TYPE_TEXT,
518 - WINP_SNIPPET_TYPE_AD,
519 - WINP_SNIPPET_TYPE_CSS
520 - ] ) ) {
551 + && ! in_array(
552 + $snippet_type,
553 + [
554 + WINP_SNIPPET_TYPE_TEXT,
555 + WINP_SNIPPET_TYPE_AD,
556 + WINP_SNIPPET_TYPE_CSS,
557 + ]
558 + ) ) {
521 559 $snippet_content = '';
522 560
523 - if ( is_user_logged_in() && WINP_Plugin::app()->currentUserCan() ) {
524 - $error_text = __( '[Woody snippet cannot be executed because you have disabled the insertion of unfiltered html!]', 'insert-php' );
561 + if ( is_user_logged_in() && WINP_Plugin::app()->current_user_car() ) {
562 + $error_text = __( 'This Woody snippet cannot run because unfiltered HTML insertion is disabled.', 'insert-php' );
525 563
526 564 switch ( $location ) {
527 565 case 'header':
528 566 case 'footer':
@@ -565,9 +603,9 @@
565 603 * @since 2.4
566 604 */
567 605 do_action( 'wbcr/woody/do_woocommerce_actions', $location, $snippet_content );
568 606
569 - //$this->woocommerce_actions( $location, $snippet_content );
607 + // $this->woocommerce_actions( $location, $snippet_content );
570 608 $this->custom_actions( $location, $snippet_content );
571 609 } else {
572 610 $content = $snippet_content . $content;
573 611 }
@@ -578,8 +616,107 @@
578 616 return $content;
579 617 }
580 618
581 619 /**
620 + * Track shortcode snippet execution.
621 + *
622 + * Public method for shortcode classes to call.
623 + *
624 + * @param int $snippet_id Snippet ID.
625 + * @return void
626 + */
627 + public function track_shortcode_snippet( $snippet_id ) {
628 + $this->track_executed_snippet( $snippet_id, 'shortcode' );
629 + }
630 +
631 + /**
632 + * Track executed snippet.
633 + *
634 + * @param int $snippet_id Snippet ID.
635 + * @param string $scope Snippet scope.
636 + * @return void
637 + */
638 + private function track_executed_snippet( $snippet_id, $scope ) {
639 + if ( isset( $this->executed_snippets[ $snippet_id ] ) ) {
640 + return; // Already tracked.
641 + }
642 +
643 + $snippet = get_post( $snippet_id );
644 + if ( ! $snippet ) {
645 + return;
646 + }
647 +
648 + $snippet_type = WINP_Helper::getMetaOption( $snippet_id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
649 + $snippet_location = WINP_Helper::getMetaOption( $snippet_id, 'snippet_location', '' );
650 +
651 + // Map location to human-readable label.
652 + $location_label = $this->get_location_label( $scope, $snippet_location );
653 +
654 + $this->executed_snippets[ $snippet_id ] = [
655 + 'id' => $snippet_id,
656 + 'name' => $snippet->post_title,
657 + 'type' => $this->get_type_label( $snippet_type ),
658 + 'location' => $location_label,
659 + 'scope' => $scope,
660 + ];
661 + }
662 +
663 + /**
664 + * Get human-readable location label.
665 + *
666 + * @param string $scope Snippet scope.
667 + * @param string $location Snippet location.
668 + * @return string
669 + */
670 + private function get_location_label( $scope, $location ) {
671 + if ( 'evrywhere' === $scope ) {
672 + return __( 'Everywhere', 'insert-php' );
673 + }
674 +
675 + if ( 'shortcode' === $scope ) {
676 + return __( 'Shortcode', 'insert-php' );
677 + }
678 +
679 + $location_labels = [
680 + 'header' => __( 'Header', 'insert-php' ),
681 + 'footer' => __( 'Footer', 'insert-php' ),
682 + 'before_post' => __( 'Before Post', 'insert-php' ),
683 + 'before_content' => __( 'Before Content', 'insert-php' ),
684 + 'before_paragraph' => __( 'Before Paragraph', 'insert-php' ),
685 + 'after_paragraph' => __( 'After Paragraph', 'insert-php' ),
686 + 'after_content' => __( 'After Content', 'insert-php' ),
687 + 'after_post' => __( 'After Post', 'insert-php' ),
688 + 'before_excerpt' => __( 'Before Excerpt', 'insert-php' ),
689 + 'after_excerpt' => __( 'After Excerpt', 'insert-php' ),
690 + 'before_posts' => __( 'Before Posts', 'insert-php' ),
691 + 'after_posts' => __( 'After Posts', 'insert-php' ),
692 + 'after_comments' => __( 'After Comments', 'insert-php' ),
693 + ];
694 +
695 + return $location_labels[ $location ] ?? ucwords( str_replace( '_', ' ', $location ) );
696 + }
697 +
698 + /**
699 + * Get human-readable type label.
700 + *
701 + * @param string $type Snippet type.
702 + * @return string
703 + */
704 + private function get_type_label( $type ) {
705 + $type_labels = [
706 + WINP_SNIPPET_TYPE_PHP => 'PHP',
707 + WINP_SNIPPET_TYPE_UNIVERSAL => 'Universal',
708 + WINP_SNIPPET_TYPE_HTML => 'HTML',
709 + WINP_SNIPPET_TYPE_CSS => 'CSS',
710 + WINP_SNIPPET_TYPE_JS => 'JS',
711 + WINP_SNIPPET_TYPE_TEXT => 'TEXT',
712 + WINP_SNIPPET_TYPE_AD => 'AD',
713 + ];
714 +
715 + return $type_labels[ $type ] ?? 'PHP';
716 + }
717 +
718 + /**
582 719 * Get js or css snippet data
583 720 *
584 721 * @param $snippet_id
585 722 *
@@ -625,11 +762,11 @@
625 762 *
626 763 * Code must NOT be escaped, as
627 764 * it will be executed directly
628 765 *
629 - * @param string $code The snippet code to execute
630 - * @param int $id The snippet ID
631 - * @param bool $catch_output Whether to attempt to suppress the output of execution using buffers
766 + * @param string $code The snippet code to execute.
767 + * @param int $id The snippet ID.
768 + * @param bool $catch_output Whether to attempt to suppress the output of execution using buffers.
632 769 *
633 770 * @return mixed The result of the code execution
634 771 */
635 772 public function executeSnippet( $code, $id = 0, $catch_output = true ) {
@@ -648,10 +785,24 @@
648 785 if ( empty( $snippet ) || $snippet->post_type !== WINP_SNIPPETS_POST_TYPE ) {
649 786 return false;
650 787 }
651 788
652 - $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', true );
789 + $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', true );
790 + $is_executable = in_array( $snippet_type, [ WINP_SNIPPET_TYPE_PHP, WINP_SNIPPET_TYPE_UNIVERSAL ], true );
653 791
792 + if ( $is_executable ) {
793 + // Preserve the active snippet context if execution terminates the request.
794 + self::$current_snippet_id = $id;
795 + WINP_Error_Handler::init();
796 + WINP_Error_Handler::set_current_snippet( $id, $snippet->post_title );
797 +
798 + // Register shutdown function once to catch fatal errors.
799 + if ( ! self::$shutdown_registered ) {
800 + register_shutdown_function( [ $this, 'handle_snippet_shutdown' ] );
801 + self::$shutdown_registered = true;
802 + }
803 + }
804 +
654 805 if ( $snippet_type == WINP_SNIPPET_TYPE_UNIVERSAL ) {
655 806 $result = eval( '?>' . $code . '<?php ' );
656 807 } elseif ( $snippet_type == WINP_SNIPPET_TYPE_PHP ) {
657 808 $result = eval( $code );
@@ -658,8 +809,13 @@
658 809 } else {
659 810 $result = ! empty( $code );
660 811 }
661 812
813 + if ( $is_executable ) {
814 + self::$current_snippet_id = 0;
815 + WINP_Error_Handler::clear_current_snippet();
816 + }
817 +
662 818 if ( $catch_output ) {
663 819 ob_end_clean();
664 820 }
665 821
@@ -666,8 +822,330 @@
666 822 return $result;
667 823 }
668 824
669 825 /**
826 + * Handle fatal errors during snippet execution.
827 + *
828 + * @return void
829 + */
830 + public function handle_snippet_shutdown() {
831 + $error = error_get_last();
832 +
833 + // If there's a fatal error and a snippet was being executed, check if it's from the snippet.
834 + if ( self::$current_snippet_id && $error && in_array( $error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ] ) ) {
835 + // Only log if the error originated from the snippet code itself.
836 + if ( $this->is_error_from_snippet( $error ) ) {
837 + $this->log_snippet_error( self::$current_snippet_id, $error );
838 + }
839 + }
840 +
841 + // Clear the snippet ID after processing.
842 + self::$current_snippet_id = 0;
843 + }
844 +
845 + /**
846 + * Check if the error originated from the snippet code.
847 + *
848 + * @param array<string, mixed> $error Error details.
849 + *
850 + * @return bool True if error is from snippet, false otherwise.
851 + */
852 + private function is_error_from_snippet( $error ) {
853 + if ( ! isset( $error['file'] ) ) {
854 + return false;
855 + }
856 +
857 + $error_file = $error['file'];
858 +
859 + // Check if error is from eval'd code (PHP snippets executed via eval).
860 + if ( strpos( $error_file, "eval()'d code" ) !== false ) {
861 + return true;
862 + }
863 +
864 + return false;
865 + }
866 +
867 + /**
868 + * Log snippet error via admin notice
869 + *
870 + * @param int $snippet_id Snippet ID.
871 + * @param array<string, mixed> $error Error details.
872 + *
873 + * @return void
874 + */
875 + private function log_snippet_error( $snippet_id, $error ) {
876 + // Validate error array has required keys.
877 + if ( ! isset( $error['message'], $error['file'], $error['line'] ) ) {
878 + return;
879 + }
880 +
881 + // Generate unique notice ID based on snippet ID and error details.
882 + $error_signature = md5( $snippet_id . $error['message'] . $error['file'] . $error['line'] );
883 + $notice_id = 'snippet_error_' . $error_signature;
884 +
885 + // Get snippet title for better context.
886 + $snippet = get_post( $snippet_id );
887 + $snippet_title = $snippet ? $snippet->post_title : "ID {$snippet_id}";
888 +
889 + // Extract the main error message (before stack trace).
890 + $error_message = $error['message'];
891 + if ( strpos( $error_message, ' Stack trace:' ) !== false ) {
892 + $error_message = substr( $error_message, 0, strpos( $error_message, ' Stack trace:' ) );
893 + }
894 + $error_message = trim( $error_message );
895 +
896 + // Shorten file path for readability.
897 + $file_path = str_replace( ABSPATH, '', $error['file'] );
898 +
899 + // Get edit link for the snippet.
900 + $edit_link = admin_url( 'post.php?post=' . $snippet_id . '&action=edit' );
901 +
902 + // Build notice message.
903 + $message = sprintf(
904 + '<div class="winp-error-notice-header"><strong>%s</strong> %s <a href="%s" class="winp-snippet-edit-link" target="_blank">%s</a></div><details><summary>%s</summary><div class="winp-error-details"><div class="winp-error-message"><strong>%s:</strong><br><code>%s</code></div><div class="winp-error-location"><strong>%s:</strong><br>%s <span class="winp-line-number">%s %d</span></div></div></details><div class="winp-error-notice-footer"><p class="winp-dismiss-help">%s</p><button type="button" class="button winp-manual-dismiss-btn">%s</button></div>',
905 + __( 'Snippet Error Detected', 'insert-php' ),
906 + // Translators: 1: Snippet title.
907 + sprintf( __( 'Snippet "%s" caused a fatal error.', 'insert-php' ), esc_html( $snippet_title ) ),
908 + esc_url( $edit_link ),
909 + __( 'Edit Snippet', 'insert-php' ),
910 + __( 'Show error details', 'insert-php' ),
911 + __( 'Error', 'insert-php' ),
912 + esc_html( $error_message ),
913 + __( 'Location', 'insert-php' ),
914 + esc_html( $file_path ),
915 + __( 'line', 'insert-php' ),
916 + $error['line'],
917 + __( 'If you have fixed this error, you can dismiss this notice.', 'insert-php' ),
918 + __( 'Dismiss', 'insert-php' )
919 + );
920 +
921 + // Store error notice in option to be displayed on next admin page load.
922 + $pending_notices = get_option( 'winp_pending_error_notices', [] );
923 + if ( ! is_array( $pending_notices ) ) {
924 + $pending_notices = [];
925 + }
926 +
927 + // Only add if not already present (avoid race conditions).
928 + if ( ! isset( $pending_notices[ $notice_id ] ) ) {
929 + $pending_notices[ $notice_id ] = [
930 + 'message' => $message,
931 + 'type' => 'error',
932 + ];
933 +
934 + update_option( 'winp_pending_error_notices', $pending_notices, false );
935 +
936 + // Send email notification if enabled and this is a new error.
937 + $this->send_error_email( $notice_id, $snippet_id, $snippet_title, $error_message, $file_path, $error['line'], $edit_link );
938 + }
939 + }
940 +
941 + /**
942 + * Send email notification for snippet error
943 + *
944 + * @param string $error_signature Unique error identifier.
945 + * @param int $snippet_id The ID of the snippet.
946 + * @param string $snippet_title The title of the snippet.
947 + * @param string $error_message The error message.
948 + * @param string $file_path The file path where error occurred.
949 + * @param int $line_number The line number where error occurred.
950 + * @param string $edit_link Link to edit the snippet.
951 + *
952 + * @return void
953 + */
954 + private function send_error_email( $error_signature, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link ) {
955 + // Check if email notifications are enabled.
956 + $email_enabled = get_option( 'wbcr_inp_error_email_enabled' );
957 + if ( ! $email_enabled ) {
958 + return;
959 + }
960 +
961 + // Get email address.
962 + $email_address = get_option( 'wbcr_inp_error_email_address', get_option( 'admin_email' ) );
963 + if ( empty( $email_address ) || ! is_email( $email_address ) ) {
964 + return;
965 + }
966 +
967 + // Check if we've already emailed about this error.
968 + $emailed_errors = get_option( 'winp_emailed_errors', [] );
969 + if ( ! is_array( $emailed_errors ) ) {
970 + $emailed_errors = [];
971 + }
972 +
973 + // If we've already emailed about this error, skip.
974 + if ( isset( $emailed_errors[ $error_signature ] ) ) {
975 + return;
976 + }
977 +
978 + // Clean up old entries (older than 30 days).
979 + $thirty_days_ago = time() - ( 30 * DAY_IN_SECONDS );
980 + foreach ( $emailed_errors as $hash => $timestamp ) {
981 + if ( $timestamp < $thirty_days_ago ) {
982 + unset( $emailed_errors[ $hash ] );
983 + }
984 + }
985 +
986 + // Build email content.
987 + $site_name = get_bloginfo( 'name' );
988 + $admin_url = admin_url( 'edit.php?post_type=' . WINP_SNIPPETS_POST_TYPE );
989 + $subject = sprintf( '[%s] Snippet Error Detected: %s', $site_name, $snippet_title );
990 +
991 + $email_body = $this->get_error_email_template( $site_name, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link, $admin_url );
992 +
993 + // Set email headers for HTML.
994 + $headers = [
995 + 'Content-Type: text/html; charset=UTF-8',
996 + ];
997 +
998 + // Send email.
999 + $sent = wp_mail( $email_address, $subject, $email_body, $headers );
1000 +
1001 + // If email sent successfully, mark this error as emailed.
1002 + if ( $sent ) {
1003 + $emailed_errors[ $error_signature ] = time();
1004 + update_option( 'winp_emailed_errors', $emailed_errors, false );
1005 + }
1006 + }
1007 +
1008 + /**
1009 + * Get HTML email template for error notification
1010 + *
1011 + * @param string $site_name Site name.
1012 + * @param int $snippet_id Snippet ID.
1013 + * @param string $snippet_title Snippet title.
1014 + * @param string $error_message Error message.
1015 + * @param string $file_path File path.
1016 + * @param int $line_number Line number.
1017 + * @param string $edit_link Edit link.
1018 + * @param string $admin_url Admin URL.
1019 + *
1020 + * @return string HTML email template.
1021 + */
1022 + private function get_error_email_template( $site_name, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link, $admin_url ) {
1023 + ob_start();
1024 + ?>
1025 + <!DOCTYPE html>
1026 + <html>
1027 + <head>
1028 + <meta charset="UTF-8">
1029 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
1030 + <title><?php echo esc_html( __( 'Snippet Error Notification', 'insert-php' ) ); ?></title>
1031 + </head>
1032 + <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; background-color: #f0f0f1;">
1033 + <table role="presentation" style="width: 100%; border-collapse: collapse;">
1034 + <tr>
1035 + <td align="center" style="padding: 40px 20px;">
1036 + <table role="presentation" style="width: 100%; max-width: 600px; border-collapse: collapse; background-color: #ffffff; border: 1px solid #c3c4c7; border-radius: 2px;">
1037 + <!-- Header -->
1038 + <tr>
1039 + <td style="padding: 24px 30px; background-color: #2271b1; border-bottom: 1px solid #135e96;">
1040 + <h1 style="margin: 0; color: #ffffff; font-size: 20px; font-weight: 600;">
1041 + ⚠️ <?php echo esc_html( __( 'Snippet Error Detected', 'insert-php' ) ); ?>
1042 + </h1>
1043 + </td>
1044 + </tr>
1045 +
1046 + <!-- Content -->
1047 + <tr>
1048 + <td style="padding: 30px;">
1049 + <p style="margin: 0 0 16px; color: #1d2327; font-size: 14px; line-height: 1.6;">
1050 + <?php echo esc_html( __( 'Hello,', 'insert-php' ) ); ?>
1051 + </p>
1052 +
1053 + <p style="margin: 0 0 20px; color: #1d2327; font-size: 14px; line-height: 1.6;">
1054 + <?php
1055 + printf(
1056 + // translators: 1: snippet title, 2: site name.
1057 + esc_html( __( 'A fatal error has been detected in the snippet "%1$s" on your site %2$s.', 'insert-php' ) ),
1058 + '<strong>' . esc_html( $snippet_title ) . '</strong>',
1059 + '<strong>' . esc_html( $site_name ) . '</strong>'
1060 + );
1061 + ?>
1062 + </p>
1063 +
1064 + <!-- Error Details Box -->
1065 + <table role="presentation" style="width: 100%; border-collapse: collapse; margin: 24px 0; background-color: #fcf0f1; border-left: 4px solid #d63638; border-radius: 0;">
1066 + <tr>
1067 + <td style="padding: 16px 20px;">
1068 + <p style="margin: 0 0 12px; color: #8c1c13; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
1069 + <?php echo esc_html( __( 'Error Details', 'insert-php' ) ); ?>
1070 + </p>
1071 +
1072 + <div style="margin-bottom: 12px;">
1073 + <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1074 + <?php echo esc_html( __( 'Error Message:', 'insert-php' ) ); ?>
1075 + </p>
1076 + <p style="margin: 0; padding: 8px 12px; background-color: #ffffff; border: 1px solid #dcdcde; color: #1d2327; font-size: 13px; font-family: Consolas, Monaco, monospace; word-break: break-word; line-height: 1.5;">
1077 + <?php echo esc_html( $error_message ); ?>
1078 + </p>
1079 + </div>
1080 +
1081 + <div style="margin-bottom: 12px;">
1082 + <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1083 + <?php echo esc_html( __( 'Location:', 'insert-php' ) ); ?>
1084 + </p>
1085 + <p style="margin: 0; padding: 8px 12px; background-color: #ffffff; border: 1px solid #dcdcde; color: #1d2327; font-size: 13px; font-family: Consolas, Monaco, monospace; line-height: 1.5;">
1086 + <?php echo esc_html( $file_path ); ?> <span style="color: #646970;"><?php echo esc_html( __( 'line', 'insert-php' ) ); ?> <?php echo intval( $line_number ); ?></span>
1087 + </p>
1088 + </div>
1089 +
1090 + <div>
1091 + <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1092 + <?php echo esc_html( __( 'Snippet:', 'insert-php' ) ); ?>
1093 + </p>
1094 + <p style="margin: 0; padding: 8px 12px; background-color: #ffffff; border: 1px solid #dcdcde; color: #1d2327; font-size: 13px; line-height: 1.5;">
1095 + <?php echo esc_html( $snippet_title ); ?> <span style="color: #646970;">(ID: <?php echo intval( $snippet_id ); ?>)</span>
1096 + </p>
1097 + </div>
1098 + </td>
1099 + </tr>
1100 + </table>
1101 +
1102 + <!-- Action Buttons -->
1103 + <table role="presentation" style="width: 100%; border-collapse: collapse; margin: 24px 0;">
1104 + <tr>
1105 + <td style="padding: 0;">
1106 + <a href="<?php echo esc_url( $edit_link ); ?>" style="display: inline-block; padding: 10px 20px; background-color: #2271b1; color: #ffffff; text-decoration: none; border-radius: 3px; font-weight: 500; font-size: 13px; margin-right: 8px; border: 1px solid #2271b1;">
1107 + <?php echo esc_html( __( 'Edit Snippet', 'insert-php' ) ); ?>
1108 + </a>
1109 + <a href="<?php echo esc_url( $admin_url ); ?>" style="display: inline-block; padding: 10px 20px; background-color: #f6f7f7; color: #2c3338; text-decoration: none; border-radius: 3px; font-weight: 500; font-size: 13px; border: 1px solid #c3c4c7;">
1110 + <?php echo esc_html( __( 'View All Snippets', 'insert-php' ) ); ?>
1111 + </a>
1112 + </td>
1113 + </tr>
1114 + </table>
1115 +
1116 + <p style="margin: 20px 0 0; color: #646970; font-size: 13px; line-height: 1.6;">
1117 + <?php echo esc_html( __( 'This notification is sent only once per unique error. You can disable these notifications in the plugin settings.', 'insert-php' ) ); ?>
1118 + </p>
1119 + </td>
1120 + </tr>
1121 +
1122 + <!-- Footer -->
1123 + <tr>
1124 + <td style="padding: 16px 30px; background-color: #f6f7f7; border-top: 1px solid #dcdcde;">
1125 + <p style="margin: 0; color: #646970; font-size: 12px; text-align: center;">
1126 + <?php
1127 + printf(
1128 + // translators: %s: site name.
1129 + esc_html( __( 'This email was sent by Woody Code Snippets on %s', 'insert-php' ) ),
1130 + '<strong>' . esc_html( $site_name ) . '</strong>'
1131 + );
1132 + ?>
1133 + </p>
1134 + </td>
1135 + </tr>
1136 + </table>
1137 + </td>
1138 + </tr>
1139 + </table>
1140 + </body>
1141 + </html>
1142 + <?php
1143 + $output = ob_get_clean();
1144 + return false !== $output ? $output : '';
1145 + }
1146 +
1147 + /**
670 1148 * Get property value
671 1149 *
672 1150 * @param $value
673 1151 * @param $property
@@ -694,9 +1172,9 @@
694 1172 public function checkCondition( $snippet_id ) {
695 1173 // Итоговый результат условий
696 1174 $result = true;
697 1175 // Получаем сохранённые параметры условий
698 - $filters = get_post_meta( $snippet_id, WINP_Plugin::app()->getPrefix() . 'snippet_filters' );
1176 + $filters = get_post_meta( $snippet_id, 'wbcr_inp_snippet_filters' );
699 1177 // Если условия указаны
700 1178 if ( ! ( empty( $filters ) || isset( $filters[0] ) && empty( $filters[0] ) ) ) {
701 1179 foreach ( $filters[0] as $filter ) {
702 1180 $conditions = $this->getPropertyValue( $filter, 'conditions' );
@@ -789,9 +1267,9 @@
789 1267
790 1268 /**
791 1269 * Prepare the code by removing php tags from beginning and end
792 1270 *
793 - * @param string $code
1271 + * @param string $code
794 1272 * @param integer $snippet_id
795 1273 *
796 1274 * @return string
797 1275 */
@@ -798,11 +1276,11 @@
798 1276 public function prepareCode( $code, $snippet_id ) {
799 1277 $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
800 1278
801 1279 if ( $snippet_type != WINP_SNIPPET_TYPE_UNIVERSAL
802 - && $snippet_type != WINP_SNIPPET_TYPE_CSS
803 - && $snippet_type != WINP_SNIPPET_TYPE_JS
804 - && $snippet_type != WINP_SNIPPET_TYPE_HTML ) {
1280 + && $snippet_type != WINP_SNIPPET_TYPE_CSS
1281 + && $snippet_type != WINP_SNIPPET_TYPE_JS
1282 + && $snippet_type != WINP_SNIPPET_TYPE_HTML ) {
805 1283
806 1284 /* Remove <?php and <? from beginning of snippet */
807 1285 $code = preg_replace( '|^[\s]*<\?(php)?|', '', $code );
808 1286
@@ -845,16 +1323,16 @@
845 1323
846 1324 /**
847 1325 * Check by operator
848 1326 *
849 - * @param $operation
850 - * @param $first
851 - * @param $second
852 - * @param $third
1327 + * @param string $operation Comparison operator.
1328 + * @param mixed $first First value.
1329 + * @param mixed $second Second value.
1330 + * @param bool $third Third value.
853 1331 *
854 1332 * @return bool
855 1333 */
856 - public function checkByOperator( $operation, $first, $second, $third = false ) {
1334 + public function check_by_operator( $operation, $first, $second, $third = false ) {
857 1335 switch ( $operation ) {
858 1336 case 'equals':
859 1337 if ( is_array( $second ) ) {
860 1338 return in_array( $first, $second );
@@ -894,9 +1372,9 @@
894 1372 * @return boolean
895 1373 */
896 1374 private function user_role( $operator, $value ) {
897 1375 if ( ! is_user_logged_in() ) {
898 - return $this->checkByOperator( $operator, $value, 'guest' );
1376 + return $this->check_by_operator( $operator, $value, 'guest' );
899 1377 } else {
900 1378 $current_user = wp_get_current_user();
901 1379 if ( ! ( $current_user instanceof WP_User ) ) {
902 1380 return false;
@@ -901,9 +1379,9 @@
901 1379 if ( ! ( $current_user instanceof WP_User ) ) {
902 1380 return false;
903 1381 }
904 1382
905 - return $this->checkByOperator( $operator, $value, $current_user->roles[0] );
1383 + return $this->check_by_operator( $operator, $value, $current_user->roles[0] );
906 1384 }
907 1385 }
908 1386
909 1387 /**
@@ -908,14 +1386,20 @@
908 1386
909 1387 /**
910 1388 * Get timestamp
911 1389 *
912 - * @param $units
913 - * @param $count
1390 + * @param string $units Time units.
1391 + * @param mixed $count Count of units.
914 1392 *
915 1393 * @return integer
916 1394 */
917 - private function getTimestamp( $units, $count ) {
1395 + private function get_timestamp( $units, $count ) {
1396 + if ( ! is_numeric( $count ) ) {
1397 + return 0;
1398 + }
1399 +
1400 + $count = (int) $count;
1401 +
918 1402 switch ( $units ) {
919 1403 case 'seconds':
920 1404 return $count;
921 1405 case 'minutes':
@@ -938,15 +1422,19 @@
938 1422
939 1423 /**
940 1424 * Get date timestamp
941 1425 *
942 - * @param $value
1426 + * @param mixed $value Date value.
943 1427 *
944 - * @return integer
1428 + * @return int|mixed Returns 0 on validation failure, timestamp calculation, or the original value
945 1429 */
946 - public function getDateTimestamp( $value ) {
1430 + public function get_date_timestamp( $value ) {
947 1431 if ( is_object( $value ) ) {
948 - return ( current_time( 'timestamp' ) - $this->getTimestamp( $value->units, $value->unitsCount ) ) * 1000;
1432 + if ( ! isset( $value->units ) || ! isset( $value->unitsCount ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1433 + return 0;
1434 + }
1435 + $current_timestamp = current_datetime()->getTimestamp();
1436 + return ( $current_timestamp - $this->get_timestamp( $value->units, $value->unitsCount ) ) * 1000; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
949 1437 } else {
950 1438 return $value;
951 1439 }
952 1440 }
@@ -954,10 +1442,10 @@
954 1442 /**
955 1443 * The date when the user who views your website was registered.
956 1444 * For unregistered users this date always equals to 1 Jan 1970.
957 1445 *
958 - * @param string $operator
959 - * @param string $value
1446 + * @param string $operator Comparison operator.
1447 + * @param mixed $value Comparison value (object for 'between', mixed otherwise).
960 1448 *
961 1449 * @return boolean
962 1450 */
963 1451 private function user_registered( $operator, $value ) {
@@ -964,24 +1452,48 @@
964 1452 if ( ! is_user_logged_in() ) {
965 1453 return false;
966 1454 } else {
967 1455 $user = wp_get_current_user();
968 - $registered = strtotime( $user->data->user_registered ) * 1000;
1456 + $registered = strtotime( $user->data->user_registered );
1457 +
1458 + // Validate that we have a valid registration timestamp.
1459 + if ( false === $registered ) {
1460 + return false;
1461 + }
1462 +
1463 + $registered = $registered * 1000;
969 1464
970 - if ( $operator == 'equals' || $operator == 'notequal' ) {
1465 + if ( 'equals' === $operator || 'notequal' === $operator ) {
971 1466 $registered = $registered / 1000;
972 - $timestamp = round( $this->getDateTimestamp( $value ) / 1000 );
1467 + $timestamp = $this->get_date_timestamp( $value );
1468 +
1469 + if ( ! $timestamp || $timestamp <= 0 ) {
1470 + return false;
1471 + }
1472 +
1473 + $timestamp = round( $timestamp / 1000 );
973 1474
974 - return $this->checkByOperator( $operator, date( 'Y-m-d', $timestamp ), date( 'Y-m-d', $registered ) );
975 - } elseif ( $operator == 'between' ) {
976 - $start_timestamp = $this->getDateTimestamp( $value->start );
977 - $end_timestamp = $this->getDateTimestamp( $value->end );
1475 + return $this->check_by_operator( $operator, gmdate( 'Y-m-d', (int) $timestamp ), gmdate( 'Y-m-d', (int) $registered ) );
1476 + } elseif ( 'between' === $operator ) {
1477 + if ( ! is_object( $value ) || ! isset( $value->start ) || ! isset( $value->end ) ) {
1478 + return false;
1479 + }
1480 + $start_timestamp = $this->get_date_timestamp( $value->start );
1481 + $end_timestamp = $this->get_date_timestamp( $value->end );
1482 +
1483 + if ( ! $start_timestamp || $start_timestamp <= 0 || ! $end_timestamp || $end_timestamp <= 0 ) {
1484 + return false;
1485 + }
978 1486
979 - return $this->checkByOperator( $operator, $start_timestamp, $registered, $end_timestamp );
1487 + return $this->check_by_operator( $operator, $start_timestamp, $registered, $end_timestamp );
980 1488 } else {
981 - $timestamp = $this->getDateTimestamp( $value );
1489 + $timestamp = $this->get_date_timestamp( $value );
1490 +
1491 + if ( ! $timestamp || $timestamp <= 0 ) {
1492 + return false;
1493 + }
982 1494
983 - return $this->checkByOperator( $operator, $timestamp, $registered );
1495 + return $this->check_by_operator( $operator, $timestamp, $registered );
984 1496 }
985 1497 }
986 1498 }
987 1499
@@ -987,10 +1499,10 @@
987 1499
988 1500 /**
989 1501 * Check the user views your website from mobile device or not
990 1502 *
991 - * @param string $operator
992 - * @param string $value
1503 + * @param string $operator Comparison operator.
1504 + * @param string $value Comparison value.
993 1505 *
994 1506 * @return boolean
995 1507 *
996 1508 * @link https://stackoverflow.com/a/4117597
@@ -1123,9 +1635,9 @@
1123 1635 break;
1124 1636 }
1125 1637 }
1126 1638
1127 - return $this->checkByOperator( $operator, $result, true );
1639 + return $this->check_by_operator( $operator, $result, true );
1128 1640 }
1129 1641
1130 1642 /**
1131 1643 * An URL of the current page where a user who views your website is located
@@ -1137,9 +1649,9 @@
1137 1649 */
1138 1650 private function location_page( $operator, $value ) {
1139 1651 $url = $this->getCurrentUrl();
1140 1652
1141 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1653 + return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1142 1654 }
1143 1655
1144 1656 /**
1145 1657 * A referrer URL which has brought a user to the current page
@@ -1151,9 +1663,9 @@
1151 1663 */
1152 1664 private function location_referrer( $operator, $value ) {
1153 1665 $url = $this->getRefererUrl();
1154 1666
1155 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1667 + return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1156 1668 }
1157 1669
1158 1670 /**
1159 1671 * A post type of the current page
@@ -1164,9 +1676,9 @@
1164 1676 * @return boolean
1165 1677 */
1166 1678 private function location_post_type( $operator, $value ) {
1167 1679 if ( is_singular() ) {
1168 - return $this->checkByOperator( $operator, $value, get_post_type() );
1680 + return $this->check_by_operator( $operator, $value, get_post_type() );
1169 1681 }
1170 1682
1171 1683 return false;
1172 1684 }
@@ -1187,9 +1699,9 @@
1187 1699 if ( is_tax() || is_tag() || is_category() ) {
1188 1700 $term_id = get_queried_object()->term_id;
1189 1701
1190 1702 if ( $term_id ) {
1191 - return $this->checkByOperator( $operator, intval( $value ), $term_id );
1703 + return $this->check_by_operator( $operator, intval( $value ), $term_id );
1192 1704 }
1193 1705 }
1194 1706
1195 1707 return false;
@@ -1216,9 +1728,9 @@
1216 1728 }
1217 1729 }
1218 1730
1219 1731 if ( $term_id ) {
1220 - return $this->checkByOperator( $operator, intval( $value ), $term_id );
1732 + return $this->check_by_operator( $operator, intval( $value ), $term_id );
1221 1733 }
1222 1734
1223 1735 return false;
1224 1736 }