PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / 2.7.7
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts v2.7.7
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 +737 -198 2.4.72.7.7 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,17 +96,18 @@
53 96 define( 'WINP_UPLOAD_URL', $dir['baseurl'] . '/winp-css-js' );
54 97 }
55 98 global $wpdb;
56 99
100 + // todo: Simplify your request. Seems written ugly
57 101 $sql = "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_content, p2.meta_value as priority
58 102 FROM {$wpdb->posts}
59 103 INNER JOIN {$wpdb->postmeta} p1 ON ({$wpdb->posts}.ID = p1.post_id)
60 104 INNER JOIN {$wpdb->postmeta} p2 ON ({$wpdb->posts}.ID = p2.post_id)
61 105 INNER JOIN {$wpdb->postmeta} p3 ON ({$wpdb->posts}.ID = p3.post_id)
62 - 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')
63 107 AND
64 - ( p3.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_activate' AND p3.meta_value = '1')
65 - 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' )
66 110 AND {$wpdb->posts}.post_type = '" . WINP_SNIPPETS_POST_TYPE . "'
67 111 AND ({$wpdb->posts}.post_status = 'publish')
68 112 ORDER BY CAST(priority AS UNSIGNED) %s";
69 113
@@ -76,21 +120,21 @@
76 120
77 121 /**
78 122 * Register hooks
79 123 */
80 - public function registerHooks() {
81 - add_action( 'plugins_loaded', [ $this, 'executeEverywhereSnippets' ], 1 );
124 + public function register_hooks() {
125 + add_action( 'init', [ $this, 'execute_everywhere_snippets' ], 1 );
82 126
83 - if ( ! is_admin() ) { #issue PCS-45 fix bug with WPBPage Builder Frontend Editor
84 - add_action( 'wp_head', [ $this, 'executeHeaderSnippets' ] );
85 - 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' ] );
86 130 add_action( 'the_post', [ $this, 'executePostSnippets' ], 10, 2 );
87 131 add_filter( 'the_content', [ $this, 'executeContentSnippets' ] );
88 132 add_filter( 'the_excerpt', [ $this, 'executeExcerptSnippets' ] );
89 133 // Бесполезный хук, который вызывается на каждый комментарий. Если их много, увеличивается нагрузка
90 - //add_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
134 + // add_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
91 135
92 - //add_action( 'wp_head', [ $this, 'executeWoocommerceSnippets' ] );
136 + // add_action( 'wp_head', [ $this, 'executeWoocommerceSnippets' ] );
93 137
94 138 if ( ! empty( $this->snippets_locations->getInsertion( 'custom' ) ) ) {
95 139 add_action( 'wp_head', [ $this, 'executeCustomSnippets' ] );
96 140 }
@@ -99,30 +143,30 @@
99 143
100 144 /**
101 145 * Execute the everywhere snippets once the plugins are loaded
102 146 */
103 - public function executeEverywhereSnippets() {
104 - echo $this->executeActiveSnippets( 'evrywhere' );
147 + public function execute_everywhere_snippets() {
148 + echo $this->execute_active_snippets( 'evrywhere' );
105 149 }
106 150
107 151 /**
108 152 * Execute the snippets in header of page once the plugins are loaded
109 153 */
110 - public function executeHeaderSnippets() {
111 - echo $this->executeActiveSnippets( 'auto', 'header' );
154 + public function execute_header_snippets() {
155 + echo $this->execute_active_snippets( 'auto', 'header' );
112 156 }
113 157
114 158 /**
115 159 * Execute the snippets in footer of page once the plugins are loaded
116 160 */
117 - public function executeFooterSnippets() {
118 - echo $this->executeActiveSnippets( 'auto', 'footer' );
161 + public function execute_footer_snippets() {
162 + echo $this->execute_active_snippets( 'auto', 'footer' );
119 163 }
120 164
121 165 /**
122 166 * Execute the snippets before post
123 167 *
124 - * @param WP_Post $post
168 + * @param WP_Post $post
125 169 * @param WP_Query $query
126 170 */
127 171 public function executePostSnippets( $post, $query ) {
128 172 $content = '';
@@ -130,22 +174,20 @@
130 174 $post_type = ! empty( $post ) ? $post->post_type : get_post( $post->ID )->post_type;
131 175 if ( is_singular( [ $post_type ] ) ) {
132 176 if ( did_action( 'get_header' ) ) {
133 177 // Перед заголовком
134 - $content = $this->executeActiveSnippets( 'auto', 'before_post' );
178 + $content = $this->execute_active_snippets( 'auto', 'before_post' );
135 179 }
136 - } else {
137 - if ( $query->post_count > 0 ) {
138 - if ( $query->post_count > 1 && $query->current_post > 0 && $query->post_count > $query->current_post ) {
139 - // Между записями
140 - $content = $this->executeActiveSnippets( 'auto', 'between_posts' );
141 - }
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 + }
142 185 // Перед записью
143 - $content .= $this->executeActiveSnippets( 'auto', 'before_posts', '', $query );
186 + $content .= $this->execute_active_snippets( 'auto', 'before_posts', '', $query );
144 187
145 188 // После записи
146 - $content .= $this->executeActiveSnippets( 'auto', 'after_posts', '', $query );
147 - }
189 + $content .= $this->execute_active_snippets( 'auto', 'after_posts', '', $query );
148 190 }
149 191
150 192 echo $content;
151 193 }
@@ -195,13 +237,13 @@
195 237
196 238 /**
197 239 * Handle posts content
198 240 *
199 - * @param string $content
200 - * @param string $snippet_content
241 + * @param string $content
242 + * @param string $snippet_content
201 243 * @param integer $post_number
202 - * @param string $type
203 - * @param object $query
244 + * @param string $type
245 + * @param object $query
204 246 *
205 247 * @return mixed
206 248 */
207 249 private function handlePostsContent( $content, $snippet_content, $post_number, $type, $query ) {
@@ -241,29 +283,29 @@
241 283 $post_type = ! empty( $post ) ? $post->post_type : false;
242 284
243 285 if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
244 286 // Перед коротким описанием
245 - $content = $this->executeActiveSnippets( 'auto', 'before_excerpt' ) . $content;
287 + $content = $this->execute_active_snippets( 'auto', 'before_excerpt' ) . $content;
246 288
247 289 // После короткого описания
248 - $content .= $this->executeActiveSnippets( 'auto', 'after_excerpt' );
290 + $content .= $this->execute_active_snippets( 'auto', 'after_excerpt' );
249 291 }
250 292
251 293 if ( is_singular( [ $post_type ] ) ) {
252 294 // Перед параграфом
253 - $content = $this->executeActiveSnippets( 'auto', 'before_paragraph', $content );
295 + $content = $this->execute_active_snippets( 'auto', 'before_paragraph', $content );
254 296
255 297 // После параграфа
256 - $content = $this->executeActiveSnippets( 'auto', 'after_paragraph', $content );
298 + $content = $this->execute_active_snippets( 'auto', 'after_paragraph', $content );
257 299
258 300 // После заголовка
259 - $content = $this->executeActiveSnippets( 'auto', 'before_content' ) . $content;
301 + $content = $this->execute_active_snippets( 'auto', 'before_content' ) . $content;
260 302
261 303 // После текста
262 - $content .= $this->executeActiveSnippets( 'auto', 'after_content' );
304 + $content .= $this->execute_active_snippets( 'auto', 'after_content' );
263 305
264 306 // После поста
265 - $content .= $this->executeActiveSnippets( 'auto', 'after_post' );
307 + $content .= $this->execute_active_snippets( 'auto', 'after_post' );
266 308
267 309 if ( ! comments_open( $post->ID ) && ! get_comments_number( $post->ID ) ) {
268 310 remove_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
269 311 }
@@ -285,12 +327,12 @@
285 327 */
286 328 public function executeExcerptSnippets( $excerpt ) {
287 329 if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
288 330 // Перед коротким описанием
289 - $excerpt = $this->executeActiveSnippets( 'auto', 'before_excerpt' ) . $excerpt;
331 + $excerpt = $this->execute_active_snippets( 'auto', 'before_excerpt' ) . $excerpt;
290 332
291 333 // После короткого описания
292 - $excerpt .= $this->executeActiveSnippets( 'auto', 'after_excerpt' );
334 + $excerpt .= $this->execute_active_snippets( 'auto', 'after_excerpt' );
293 335 }
294 336
295 337 return $excerpt;
296 338 }
@@ -329,9 +371,9 @@
329 371
330 372 $post_type = ! empty( $post ) ? $post->post_type : false;
331 373 if ( is_singular( [ $post_type ] ) ) {
332 374 // После комментариев
333 - $content = $this->executeActiveSnippets( 'auto', 'after_post' );
375 + $content = $this->execute_active_snippets( 'auto', 'after_post' );
334 376 }
335 377
336 378 echo $content;
337 379 }
@@ -343,9 +385,9 @@
343 385 */
344 386 public function executeCustomSnippets() {
345 387 $locations = $this->snippets_locations->getInsertion( 'custom' );
346 388 foreach ( $locations as $location => $data ) {
347 - $this->executeActiveSnippets( 'auto', $location );
389 + $this->execute_active_snippets( 'auto', $location );
348 390 }
349 391 }
350 392
351 393 /**
@@ -362,16 +404,22 @@
362 404 };
363 405
364 406 switch ( $location ) {
365 407 case 'woo_before_shop_loop':
366 - add_filter( 'woocommerce_product_loop_start', function ( $content ) use ( $snippet_content ) {
367 - return $snippet_content . $content;
368 - } );
408 + add_filter(
409 + 'woocommerce_product_loop_start',
410 + function ( $content ) use ( $snippet_content ) {
411 + return $snippet_content . $content;
412 + }
413 + );
369 414 break;
370 415 case 'woo_after_shop_loop':
371 - add_filter( 'woocommerce_product_loop_end', function ( $content ) use ( $snippet_content ) {
372 - return $content . $snippet_content;
373 - } );
416 + add_filter(
417 + 'woocommerce_product_loop_end',
418 + function ( $content ) use ( $snippet_content ) {
419 + return $content . $snippet_content;
420 + }
421 + );
374 422 break;
375 423 case 'woo_before_single_product':
376 424 add_action( 'woocommerce_before_single_product', $action, 10, 2 );
377 425 break;
@@ -425,131 +473,143 @@
425 473 *
426 474 * @param string $scope
427 475 * @param string $location
428 476 * @param string $content
429 - * @param array $custom_params
477 + * @param array $custom_params
430 478 *
431 479 * @return string
432 480 */
433 - public function executeActiveSnippets( $scope = 'evrywhere', $location = '', $content = '', $custom_params = [] ) {
434 - /*
435 - global $wpdb;
481 + public function execute_active_snippets( $scope = 'evrywhere', $location = '', $content = '', $custom_params = [] ) {
482 + $snippets = $this->snippets[ $scope ] ?? [];
436 483
437 - if ( $scope == 'evrywhere' ) {
438 - $sort = 'DESC';
439 - } else {
440 - $sort = 'ASC';
441 - }
442 - $snippets = $wpdb->get_results( "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_content, p2.meta_value as priority
443 - FROM {$wpdb->posts}
444 - INNER JOIN {$wpdb->postmeta} p1 ON ({$wpdb->posts}.ID = p1.post_id)
445 - INNER JOIN {$wpdb->postmeta} p2 ON ({$wpdb->posts}.ID = p2.post_id)
446 - INNER JOIN {$wpdb->postmeta} p3 ON ({$wpdb->posts}.ID = p3.post_id)
447 - WHERE (( p1.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_scope' AND p1.meta_value = '{$scope}')
448 - AND
449 - ( p3.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_activate' AND p3.meta_value = '1')
450 - AND p2.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_priority' )
451 - AND {$wpdb->posts}.post_type = '" . WINP_SNIPPETS_POST_TYPE . "'
452 - AND ({$wpdb->posts}.post_status = 'publish')
453 - ORDER BY CAST(priority AS UNSIGNED) {$sort}" );
454 - */
455 - $snippets = $this->snippets[ $scope ] ?? [];
484 + if ( ! empty( $snippets ) ) {
485 + foreach ( (array) $snippets as $snippet ) {
486 + $id = (int) $snippet->ID;
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 );
495 + // Если это сниппет с автовставкой и выбранное место подходит под активный action
496 + $avail_place = ( 'auto' == $scope ? $location == WINP_Helper::getMetaOption( $id, 'snippet_location', '' ) : true );
497 + // Если условие отображения сниппета выполняется
498 + $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
499 + $is_condition = $snippet_type != WINP_SNIPPET_TYPE_PHP ? $this->checkCondition( $id ) : true;
456 500
457 - if ( empty( $snippets ) ) {
458 - return $content;
459 - }
501 + if ( $avail_place && $is_condition ) {
502 + $post_id = (int) WINP_HTTP::post( 'post_ID', 0 );
460 503
461 - foreach ( (array) $snippets as $snippet ) {
462 - $id = (int) $snippet->ID;
463 - //$is_active = (int) WINP_Helper::getMetaOption( $id, 'snippet_activate', 0 );
464 - // Если это сниппет с автовставкой и выбранное место подходит под активный action
465 - $avail_place = ( 'auto' == $scope ? $location == WINP_Helper::getMetaOption( $id, 'snippet_location', '' ) : true );
466 - // Если условие отображения сниппета выполняется
467 - $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
468 - $is_condition = $snippet_type != WINP_SNIPPET_TYPE_PHP ? $this->checkCondition( $id ) : true;
504 + if ( ( isset( $_POST['wbcr_inp_snippet_scope'] )
505 + && $post_id === $id
506 + && WINP_Plugin::app()->current_user_car() ) || WINP_Helper::is_safe_mode() ) {
507 + return $content;
508 + }
469 509
470 - if ( $avail_place && $is_condition ) {
471 - $post_id = (int) WINP_Plugin::app()->request->post( 'post_ID', 0 );
510 + // WPML Compatibility
511 + if ( defined( 'WPML_PLUGIN_FILE' ) ) {
512 + $wpml_langs = WINP_Helper::getMetaOption( $id, 'snippet_wpml_lang', '' );
513 + if ( $wpml_langs !== '' && defined( 'ICL_LANGUAGE_CODE' ) ) {
514 + if ( ! in_array( ICL_LANGUAGE_CODE, explode( ',', $wpml_langs ) ) ) {
515 + continue;
516 + }
517 + }
518 + }
472 519
473 - if ( isset( $_POST['wbcr_inp_snippet_scope'] ) && $post_id === $id && WINP_Plugin::app()->currentUserCan() ) {
474 - return $content;
475 - }
520 + $snippet_code = WINP_Helper::get_snippet_code( $snippet );
476 521
477 - if ( WINP_Helper::is_safe_mode() ) {
478 - return $content;
479 - }
522 + /**
523 + * Filter snippet code before execute
524 + */
525 + $snippet_code = apply_filters( 'wbcr/inp/execute_snippet/snippet_code', $snippet_code, $id );
480 526
481 - // WPML Compatibility
482 - if ( defined( 'WPML_PLUGIN_FILE' ) ) {
483 - $wpml_langs = WINP_Helper::getMetaOption( $id, 'snippet_wpml_lang', '' );
484 - if ( $wpml_langs !== '' && defined( 'ICL_LANGUAGE_CODE' ) ) {
485 - if ( ! in_array( ICL_LANGUAGE_CODE, explode( ',', $wpml_langs ) ) ) {
486 - continue;
487 - }
527 + // Track this snippet as executed.
528 + $this->track_executed_snippet( $id, $scope );
529 +
530 + if ( get_option( 'wbcr_inp_execute_shortcode' ) ) {
531 + $snippet_code = do_shortcode( $snippet_code );
488 532 }
489 - }
490 533
491 - $snippet_code = WINP_Helper::get_snippet_code( $snippet );
534 + if ( $snippet_type === WINP_SNIPPET_TYPE_TEXT || $snippet_type === WINP_SNIPPET_TYPE_AD ) {
535 + $snippet_content = '<div class="winp-text-snippet-container">' . $snippet_code . '</div>';
536 + } elseif ( $snippet_type === WINP_SNIPPET_TYPE_CSS || $snippet_type === WINP_SNIPPET_TYPE_JS ) {
537 + $snippet_content = self::getJsCssSnippetData( $id );
538 + } elseif ( $snippet_type === WINP_SNIPPET_TYPE_HTML ) {
539 + $snippet_content = $snippet_code;
540 + } else {
541 + $code = $this->prepareCode( $snippet_code, $id );
542 + ob_start();
543 + $this->executeSnippet( $code, $id, false );
544 + $snippet_content = ob_get_contents();
545 + ob_end_clean();
546 + }
492 547
493 - /**
494 - * Filter snippet code before execute
495 - */
496 - $snippet_code = apply_filters( 'wbcr/inp/execute_snippet/snippet_code', $snippet_code, $id );
548 + // If the user has prohibited the insertion of unfiltered HTML,
549 + // we prohibit the execution of snippets.
550 + if ( ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML )
551 + && ! in_array(
552 + $snippet_type,
553 + [
554 + WINP_SNIPPET_TYPE_TEXT,
555 + WINP_SNIPPET_TYPE_AD,
556 + WINP_SNIPPET_TYPE_CSS,
557 + ]
558 + ) ) {
559 + $snippet_content = '';
497 560
498 - if ( WINP_Plugin::app()->getOption( 'execute_shortcode' ) ) {
499 - $snippet_code = do_shortcode( $snippet_code );
500 - }
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' );
501 563
502 - if ( $snippet_type === WINP_SNIPPET_TYPE_TEXT || $snippet_type === WINP_SNIPPET_TYPE_AD ) {
503 - $snippet_content = '<div class="winp-text-snippet-container">' . $snippet_code . '</div>';
504 - } elseif ( $snippet_type === WINP_SNIPPET_TYPE_CSS || $snippet_type === WINP_SNIPPET_TYPE_JS ) {
505 - $snippet_content = self::getJsCssSnippetData( $id );
506 - } elseif ( $snippet_type === WINP_SNIPPET_TYPE_HTML ) {
507 - $snippet_content = $snippet_code;
508 - } else {
509 - $code = $this->prepareCode( $snippet_code, $id );
510 - ob_start();
511 - $this->executeSnippet( $code, $id, false );
512 - $snippet_content = ob_get_contents();
513 - ob_end_clean();
514 - }
564 + switch ( $location ) {
565 + case 'header':
566 + case 'footer':
567 + $snippet_content = '<!-- ' . $error_text . '-->';
568 + break;
569 + default:
570 + $snippet_content = $error_text;
571 + }
572 + }
573 + }
515 574
516 - if ( 'auto' == $scope ) {
517 - switch ( $location ) {
518 - case 'before_paragraph': // Перед параграфом
519 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
520 - $content = $this->handleParagraphContent( $content, $snippet_content, $location_number );
521 - break;
522 - case 'after_paragraph': // После параграфа
523 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
524 - $content = $this->handleParagraphContent( $content, $snippet_content, $location_number, 'after' );
525 - break;
526 - case 'before_posts': // Перед записью
527 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
528 - $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'before', $custom_params );
529 - break;
530 - case 'after_posts': // После записи
531 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
532 - $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'after', $custom_params );
533 - break;
534 - default:
535 - $content = $snippet_content . $content;
536 - }
575 + if ( 'auto' == $scope ) {
576 + switch ( $location ) {
577 + case 'before_paragraph': // Перед параграфом
578 + $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
579 + $content = $this->handleParagraphContent( $content, $snippet_content, $location_number );
580 + break;
581 + case 'after_paragraph': // После параграфа
582 + $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
583 + $content = $this->handleParagraphContent( $content, $snippet_content, $location_number, 'after' );
584 + break;
585 + case 'before_posts': // Перед записью
586 + $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
587 + $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'before', $custom_params );
588 + break;
589 + case 'after_posts': // После записи
590 + $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
591 + $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'after', $custom_params );
592 + break;
593 + default:
594 + $content = $snippet_content . $content;
595 + }
537 596
538 - /**
539 - * Action for woo actions
540 - *
541 - * @param array $location Slug of the location.
542 - * @param string $snippet_content Rendered snippet content
543 - *
544 - * @since 2.4
545 - */
546 - do_action( 'wbcr/woody/do_woocommerce_actions', $location, $snippet_content );
597 + /**
598 + * Action for woo actions
599 + *
600 + * @param array $location Slug of the location.
601 + * @param string $snippet_content Rendered snippet content
602 + *
603 + * @since 2.4
604 + */
605 + do_action( 'wbcr/woody/do_woocommerce_actions', $location, $snippet_content );
547 606
548 - //$this->woocommerce_actions( $location, $snippet_content );
549 - $this->custom_actions( $location, $snippet_content );
550 - } else {
551 - $content = $snippet_content . $content;
607 + // $this->woocommerce_actions( $location, $snippet_content );
608 + $this->custom_actions( $location, $snippet_content );
609 + } else {
610 + $content = $snippet_content . $content;
611 + }
552 612 }
553 613 }
554 614 }
555 615
@@ -556,8 +616,107 @@
556 616 return $content;
557 617 }
558 618
559 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 + /**
560 719 * Get js or css snippet data
561 720 *
562 721 * @param $snippet_id
563 722 *
@@ -603,11 +762,11 @@
603 762 *
604 763 * Code must NOT be escaped, as
605 764 * it will be executed directly
606 765 *
607 - * @param string $code The snippet code to execute
608 - * @param int $id The snippet ID
609 - * @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.
610 769 *
611 770 * @return mixed The result of the code execution
612 771 */
613 772 public function executeSnippet( $code, $id = 0, $catch_output = true ) {
@@ -626,10 +785,24 @@
626 785 if ( empty( $snippet ) || $snippet->post_type !== WINP_SNIPPETS_POST_TYPE ) {
627 786 return false;
628 787 }
629 788
630 - $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 );
631 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 +
632 805 if ( $snippet_type == WINP_SNIPPET_TYPE_UNIVERSAL ) {
633 806 $result = eval( '?>' . $code . '<?php ' );
634 807 } elseif ( $snippet_type == WINP_SNIPPET_TYPE_PHP ) {
635 808 $result = eval( $code );
@@ -636,8 +809,13 @@
636 809 } else {
637 810 $result = ! empty( $code );
638 811 }
639 812
813 + if ( $is_executable ) {
814 + self::$current_snippet_id = 0;
815 + WINP_Error_Handler::clear_current_snippet();
816 + }
817 +
640 818 if ( $catch_output ) {
641 819 ob_end_clean();
642 820 }
643 821
@@ -644,8 +822,330 @@
644 822 return $result;
645 823 }
646 824
647 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 + /**
648 1148 * Get property value
649 1149 *
650 1150 * @param $value
651 1151 * @param $property
@@ -672,9 +1172,9 @@
672 1172 public function checkCondition( $snippet_id ) {
673 1173 // Итоговый результат условий
674 1174 $result = true;
675 1175 // Получаем сохранённые параметры условий
676 - $filters = get_post_meta( $snippet_id, WINP_Plugin::app()->getPrefix() . 'snippet_filters' );
1176 + $filters = get_post_meta( $snippet_id, 'wbcr_inp_snippet_filters' );
677 1177 // Если условия указаны
678 1178 if ( ! ( empty( $filters ) || isset( $filters[0] ) && empty( $filters[0] ) ) ) {
679 1179 foreach ( $filters[0] as $filter ) {
680 1180 $conditions = $this->getPropertyValue( $filter, 'conditions' );
@@ -767,9 +1267,9 @@
767 1267
768 1268 /**
769 1269 * Prepare the code by removing php tags from beginning and end
770 1270 *
771 - * @param string $code
1271 + * @param string $code
772 1272 * @param integer $snippet_id
773 1273 *
774 1274 * @return string
775 1275 */
@@ -774,9 +1274,14 @@
774 1274 * @return string
775 1275 */
776 1276 public function prepareCode( $code, $snippet_id ) {
777 1277 $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
778 - if ( $snippet_type != WINP_SNIPPET_TYPE_UNIVERSAL && $snippet_type != WINP_SNIPPET_TYPE_CSS && $snippet_type != WINP_SNIPPET_TYPE_JS && $snippet_type != WINP_SNIPPET_TYPE_HTML ) {
1278 +
1279 + if ( $snippet_type != WINP_SNIPPET_TYPE_UNIVERSAL
1280 + && $snippet_type != WINP_SNIPPET_TYPE_CSS
1281 + && $snippet_type != WINP_SNIPPET_TYPE_JS
1282 + && $snippet_type != WINP_SNIPPET_TYPE_HTML ) {
1283 +
779 1284 /* Remove <?php and <? from beginning of snippet */
780 1285 $code = preg_replace( '|^[\s]*<\?(php)?|', '', $code );
781 1286
782 1287 /* Remove ?> from end of snippet */
@@ -818,16 +1323,16 @@
818 1323
819 1324 /**
820 1325 * Check by operator
821 1326 *
822 - * @param $operation
823 - * @param $first
824 - * @param $second
825 - * @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.
826 1331 *
827 1332 * @return bool
828 1333 */
829 - public function checkByOperator( $operation, $first, $second, $third = false ) {
1334 + public function check_by_operator( $operation, $first, $second, $third = false ) {
830 1335 switch ( $operation ) {
831 1336 case 'equals':
832 1337 if ( is_array( $second ) ) {
833 1338 return in_array( $first, $second );
@@ -867,9 +1372,9 @@
867 1372 * @return boolean
868 1373 */
869 1374 private function user_role( $operator, $value ) {
870 1375 if ( ! is_user_logged_in() ) {
871 - return $this->checkByOperator( $operator, $value, 'guest' );
1376 + return $this->check_by_operator( $operator, $value, 'guest' );
872 1377 } else {
873 1378 $current_user = wp_get_current_user();
874 1379 if ( ! ( $current_user instanceof WP_User ) ) {
875 1380 return false;
@@ -874,9 +1379,9 @@
874 1379 if ( ! ( $current_user instanceof WP_User ) ) {
875 1380 return false;
876 1381 }
877 1382
878 - return $this->checkByOperator( $operator, $value, $current_user->roles[0] );
1383 + return $this->check_by_operator( $operator, $value, $current_user->roles[0] );
879 1384 }
880 1385 }
881 1386
882 1387 /**
@@ -881,14 +1386,20 @@
881 1386
882 1387 /**
883 1388 * Get timestamp
884 1389 *
885 - * @param $units
886 - * @param $count
1390 + * @param string $units Time units.
1391 + * @param mixed $count Count of units.
887 1392 *
888 1393 * @return integer
889 1394 */
890 - 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 +
891 1402 switch ( $units ) {
892 1403 case 'seconds':
893 1404 return $count;
894 1405 case 'minutes':
@@ -911,15 +1422,19 @@
911 1422
912 1423 /**
913 1424 * Get date timestamp
914 1425 *
915 - * @param $value
1426 + * @param mixed $value Date value.
916 1427 *
917 - * @return integer
1428 + * @return int|mixed Returns 0 on validation failure, timestamp calculation, or the original value
918 1429 */
919 - public function getDateTimestamp( $value ) {
1430 + public function get_date_timestamp( $value ) {
920 1431 if ( is_object( $value ) ) {
921 - 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
922 1437 } else {
923 1438 return $value;
924 1439 }
925 1440 }
@@ -927,10 +1442,10 @@
927 1442 /**
928 1443 * The date when the user who views your website was registered.
929 1444 * For unregistered users this date always equals to 1 Jan 1970.
930 1445 *
931 - * @param string $operator
932 - * @param string $value
1446 + * @param string $operator Comparison operator.
1447 + * @param mixed $value Comparison value (object for 'between', mixed otherwise).
933 1448 *
934 1449 * @return boolean
935 1450 */
936 1451 private function user_registered( $operator, $value ) {
@@ -937,24 +1452,48 @@
937 1452 if ( ! is_user_logged_in() ) {
938 1453 return false;
939 1454 } else {
940 1455 $user = wp_get_current_user();
941 - $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;
942 1464
943 - if ( $operator == 'equals' || $operator == 'notequal' ) {
1465 + if ( 'equals' === $operator || 'notequal' === $operator ) {
944 1466 $registered = $registered / 1000;
945 - $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 );
946 1474
947 - return $this->checkByOperator( $operator, date( 'Y-m-d', $timestamp ), date( 'Y-m-d', $registered ) );
948 - } elseif ( $operator == 'between' ) {
949 - $start_timestamp = $this->getDateTimestamp( $value->start );
950 - $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 + }
951 1486
952 - return $this->checkByOperator( $operator, $start_timestamp, $registered, $end_timestamp );
1487 + return $this->check_by_operator( $operator, $start_timestamp, $registered, $end_timestamp );
953 1488 } else {
954 - $timestamp = $this->getDateTimestamp( $value );
1489 + $timestamp = $this->get_date_timestamp( $value );
1490 +
1491 + if ( ! $timestamp || $timestamp <= 0 ) {
1492 + return false;
1493 + }
955 1494
956 - return $this->checkByOperator( $operator, $timestamp, $registered );
1495 + return $this->check_by_operator( $operator, $timestamp, $registered );
957 1496 }
958 1497 }
959 1498 }
960 1499
@@ -960,10 +1499,10 @@
960 1499
961 1500 /**
962 1501 * Check the user views your website from mobile device or not
963 1502 *
964 - * @param string $operator
965 - * @param string $value
1503 + * @param string $operator Comparison operator.
1504 + * @param string $value Comparison value.
966 1505 *
967 1506 * @return boolean
968 1507 *
969 1508 * @link https://stackoverflow.com/a/4117597
@@ -1096,9 +1635,9 @@
1096 1635 break;
1097 1636 }
1098 1637 }
1099 1638
1100 - return $this->checkByOperator( $operator, $result, true );
1639 + return $this->check_by_operator( $operator, $result, true );
1101 1640 }
1102 1641
1103 1642 /**
1104 1643 * An URL of the current page where a user who views your website is located
@@ -1110,9 +1649,9 @@
1110 1649 */
1111 1650 private function location_page( $operator, $value ) {
1112 1651 $url = $this->getCurrentUrl();
1113 1652
1114 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1653 + return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1115 1654 }
1116 1655
1117 1656 /**
1118 1657 * A referrer URL which has brought a user to the current page
@@ -1124,9 +1663,9 @@
1124 1663 */
1125 1664 private function location_referrer( $operator, $value ) {
1126 1665 $url = $this->getRefererUrl();
1127 1666
1128 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1667 + return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1129 1668 }
1130 1669
1131 1670 /**
1132 1671 * A post type of the current page
@@ -1137,9 +1676,9 @@
1137 1676 * @return boolean
1138 1677 */
1139 1678 private function location_post_type( $operator, $value ) {
1140 1679 if ( is_singular() ) {
1141 - return $this->checkByOperator( $operator, $value, get_post_type() );
1680 + return $this->check_by_operator( $operator, $value, get_post_type() );
1142 1681 }
1143 1682
1144 1683 return false;
1145 1684 }
@@ -1160,9 +1699,9 @@
1160 1699 if ( is_tax() || is_tag() || is_category() ) {
1161 1700 $term_id = get_queried_object()->term_id;
1162 1701
1163 1702 if ( $term_id ) {
1164 - return $this->checkByOperator( $operator, intval( $value ), $term_id );
1703 + return $this->check_by_operator( $operator, intval( $value ), $term_id );
1165 1704 }
1166 1705 }
1167 1706
1168 1707 return false;
@@ -1189,9 +1728,9 @@
1189 1728 }
1190 1729 }
1191 1730
1192 1731 if ( $term_id ) {
1193 - return $this->checkByOperator( $operator, intval( $value ), $term_id );
1732 + return $this->check_by_operator( $operator, intval( $value ), $term_id );
1194 1733 }
1195 1734
1196 1735 return false;
1197 1736 }