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 +756 -217 2.4.4trunk 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,26 +120,25 @@
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' ] );
86 - add_filter( 'the_post', [ $this, 'executePostSnippets' ], 10, 2 );
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' ] );
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 }
97 -
98 141 }
99 142 }
100 143
101 144 /**
@@ -100,55 +143,51 @@
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 $data
126 - * @param $query
168 + * @param WP_Post $post
169 + * @param WP_Query $query
127 170 */
128 - public function executePostSnippets( $data, $query ) {
129 - global $post;
130 -
171 + public function executePostSnippets( $post, $query ) {
131 172 $content = '';
132 173
133 - $post_type = ! empty( $post ) ? $post->post_type : get_post( $data->ID )->post_type;
134 - if ( is_singular( [ $post_type ] ) && $post->ID == $data->ID ) {
174 + $post_type = ! empty( $post ) ? $post->post_type : get_post( $post->ID )->post_type;
175 + if ( is_singular( [ $post_type ] ) ) {
135 176 if ( did_action( 'get_header' ) ) {
136 177 // Перед заголовком
137 - $content = $this->executeActiveSnippets( 'auto', 'before_post' );
178 + $content = $this->execute_active_snippets( 'auto', 'before_post' );
138 179 }
139 - } else {
140 - if ( $query->post_count > 0 ) {
141 - if ( $query->post_count > 1 && $query->current_post > 0 && $query->post_count > $query->current_post ) {
142 - // Между записями
143 - $content = $this->executeActiveSnippets( 'auto', 'between_posts' );
144 - }
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 + }
145 185 // Перед записью
146 - $content .= $this->executeActiveSnippets( 'auto', 'before_posts', '', $query );
186 + $content .= $this->execute_active_snippets( 'auto', 'before_posts', '', $query );
147 187
148 188 // После записи
149 - $content .= $this->executeActiveSnippets( 'auto', 'after_posts', '', $query );
150 - }
189 + $content .= $this->execute_active_snippets( 'auto', 'after_posts', '', $query );
151 190 }
152 191
153 192 echo $content;
154 193 }
@@ -164,11 +203,11 @@
164 203 * @return mixed
165 204 */
166 205 private function handleParagraphContent( $content, $snippet_content, $paragraph_number, $type = 'before' ) {
167 206 if ( 'before' == $type ) {
168 - preg_match_all( "/<p(.*?)>/", $content, $matches );
207 + preg_match_all( '/<p(.*?)>/', $content, $matches );
169 208 } else {
170 - preg_match_all( "/<\/p>/", $content, $matches );
209 + preg_match_all( '/<\/p>/', $content, $matches );
171 210 }
172 211 $paragraphs = $matches[0];
173 212
174 213 if ( $paragraph_number == 0 ) {
@@ -198,13 +237,13 @@
198 237
199 238 /**
200 239 * Handle posts content
201 240 *
202 - * @param string $content
203 - * @param string $snippet_content
241 + * @param string $content
242 + * @param string $snippet_content
204 243 * @param integer $post_number
205 - * @param string $type
206 - * @param object $query
244 + * @param string $type
245 + * @param object $query
207 246 *
208 247 * @return mixed
209 248 */
210 249 private function handlePostsContent( $content, $snippet_content, $post_number, $type, $query ) {
@@ -215,15 +254,15 @@
215 254 }
216 255
217 256 if ( 'before' == $type && $query->current_post + 1 == $post_number ) {
218 257 return $snippet_content;
219 - } else if ( 'after' == $type ) {
258 + } elseif ( 'after' == $type ) {
220 259 // Номер поста совпадает
221 260 if ( $query->current_post == $post_number ) {
222 261 return $snippet_content;
223 262 // Если это последний пост и указанный номер поста больше общего количества постов,
224 263 // то нужно сохранить контент сниппета для вывода в конце данного поста
225 - } else if ( $query->current_post + 1 == $query->post_count && $post_number >= $query->post_count ) {
264 + } elseif ( $query->current_post + 1 == $query->post_count && $post_number >= $query->post_count ) {
226 265 $winp_after_post_content[ $query->post->ID ] = $snippet_content;
227 266 }
228 267 }
229 268 }
@@ -244,34 +283,34 @@
244 283 $post_type = ! empty( $post ) ? $post->post_type : false;
245 284
246 285 if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
247 286 // Перед коротким описанием
248 - $content = $this->executeActiveSnippets( 'auto', 'before_excerpt' ) . $content;
287 + $content = $this->execute_active_snippets( 'auto', 'before_excerpt' ) . $content;
249 288
250 289 // После короткого описания
251 - $content .= $this->executeActiveSnippets( 'auto', 'after_excerpt' );
290 + $content .= $this->execute_active_snippets( 'auto', 'after_excerpt' );
252 291 }
253 292
254 293 if ( is_singular( [ $post_type ] ) ) {
255 294 // Перед параграфом
256 - $content = $this->executeActiveSnippets( 'auto', 'before_paragraph', $content );
295 + $content = $this->execute_active_snippets( 'auto', 'before_paragraph', $content );
257 296
258 297 // После параграфа
259 - $content = $this->executeActiveSnippets( 'auto', 'after_paragraph', $content );
298 + $content = $this->execute_active_snippets( 'auto', 'after_paragraph', $content );
260 299
261 300 // После заголовка
262 - $content = $this->executeActiveSnippets( 'auto', 'before_content' ) . $content;
301 + $content = $this->execute_active_snippets( 'auto', 'before_content' ) . $content;
263 302
264 303 // После текста
265 - $content .= $this->executeActiveSnippets( 'auto', 'after_content' );
304 + $content .= $this->execute_active_snippets( 'auto', 'after_content' );
266 305
267 306 // После поста
268 - $content .= $this->executeActiveSnippets( 'auto', 'after_post' );
307 + $content .= $this->execute_active_snippets( 'auto', 'after_post' );
269 308
270 309 if ( ! comments_open( $post->ID ) && ! get_comments_number( $post->ID ) ) {
271 310 remove_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
272 311 }
273 - } else if ( ! is_null( $post ) && isset( $winp_after_post_content[ $post->ID ] ) ) {
312 + } elseif ( ! is_null( $post ) && isset( $winp_after_post_content[ $post->ID ] ) ) {
274 313 // После последнего поста в списке
275 314 $content .= $winp_after_post_content[ $post->ID ];
276 315 unset( $winp_after_post_content[ $post->ID ] );
277 316 }
@@ -288,12 +327,12 @@
288 327 */
289 328 public function executeExcerptSnippets( $excerpt ) {
290 329 if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
291 330 // Перед коротким описанием
292 - $excerpt = $this->executeActiveSnippets( 'auto', 'before_excerpt' ) . $excerpt;
331 + $excerpt = $this->execute_active_snippets( 'auto', 'before_excerpt' ) . $excerpt;
293 332
294 333 // После короткого описания
295 - $excerpt .= $this->executeActiveSnippets( 'auto', 'after_excerpt' );
334 + $excerpt .= $this->execute_active_snippets( 'auto', 'after_excerpt' );
296 335 }
297 336
298 337 return $excerpt;
299 338 }
@@ -323,9 +362,9 @@
323 362 */
324 363 public function executeCommentsSnippets( $comment, $args, $depth ) {
325 364 global $winp_wp_data, $post;
326 365
327 - if ( ! empty ( $winp_wp_data['winp_comments_saved_end_callback'] ) ) {
366 + if ( ! empty( $winp_wp_data['winp_comments_saved_end_callback'] ) ) {
328 367 echo call_user_func( $winp_wp_data['winp_comments_saved_end_callback'], $comment, $args, $depth );
329 368 }
330 369
331 370 $content = '';
@@ -332,9 +371,9 @@
332 371
333 372 $post_type = ! empty( $post ) ? $post->post_type : false;
334 373 if ( is_singular( [ $post_type ] ) ) {
335 374 // После комментариев
336 - $content = $this->executeActiveSnippets( 'auto', 'after_post' );
375 + $content = $this->execute_active_snippets( 'auto', 'after_post' );
337 376 }
338 377
339 378 echo $content;
340 379 }
@@ -346,9 +385,9 @@
346 385 */
347 386 public function executeCustomSnippets() {
348 387 $locations = $this->snippets_locations->getInsertion( 'custom' );
349 388 foreach ( $locations as $location => $data ) {
350 - $this->executeActiveSnippets( 'auto', $location );
389 + $this->execute_active_snippets( 'auto', $location );
351 390 }
352 391 }
353 392
354 393 /**
@@ -365,16 +404,22 @@
365 404 };
366 405
367 406 switch ( $location ) {
368 407 case 'woo_before_shop_loop':
369 - add_filter( 'woocommerce_product_loop_start', function ( $content ) use ( $snippet_content ) {
370 - return $snippet_content . $content;
371 - } );
408 + add_filter(
409 + 'woocommerce_product_loop_start',
410 + function ( $content ) use ( $snippet_content ) {
411 + return $snippet_content . $content;
412 + }
413 + );
372 414 break;
373 415 case 'woo_after_shop_loop':
374 - add_filter( 'woocommerce_product_loop_end', function ( $content ) use ( $snippet_content ) {
375 - return $content . $snippet_content;
376 - } );
416 + add_filter(
417 + 'woocommerce_product_loop_end',
418 + function ( $content ) use ( $snippet_content ) {
419 + return $content . $snippet_content;
420 + }
421 + );
377 422 break;
378 423 case 'woo_before_single_product':
379 424 add_action( 'woocommerce_before_single_product', $action, 10, 2 );
380 425 break;
@@ -428,127 +473,143 @@
428 473 *
429 474 * @param string $scope
430 475 * @param string $location
431 476 * @param string $content
432 - * @param array $custom_params
477 + * @param array $custom_params
433 478 *
434 479 * @return string
435 480 */
436 - public function executeActiveSnippets( $scope = 'evrywhere', $location = '', $content = '', $custom_params = [] ) {
437 - /*
438 - global $wpdb;
481 + public function execute_active_snippets( $scope = 'evrywhere', $location = '', $content = '', $custom_params = [] ) {
482 + $snippets = $this->snippets[ $scope ] ?? [];
439 483
440 - if ( $scope == 'evrywhere' ) {
441 - $sort = 'DESC';
442 - } else {
443 - $sort = 'ASC';
444 - }
445 - $snippets = $wpdb->get_results( "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_content, p2.meta_value as priority
446 - FROM {$wpdb->posts}
447 - INNER JOIN {$wpdb->postmeta} p1 ON ({$wpdb->posts}.ID = p1.post_id)
448 - INNER JOIN {$wpdb->postmeta} p2 ON ({$wpdb->posts}.ID = p2.post_id)
449 - INNER JOIN {$wpdb->postmeta} p3 ON ({$wpdb->posts}.ID = p3.post_id)
450 - WHERE (( p1.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_scope' AND p1.meta_value = '{$scope}')
451 - AND
452 - ( p3.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_activate' AND p3.meta_value = '1')
453 - AND p2.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_priority' )
454 - AND {$wpdb->posts}.post_type = '" . WINP_SNIPPETS_POST_TYPE . "'
455 - AND ({$wpdb->posts}.post_status = 'publish')
456 - ORDER BY CAST(priority AS UNSIGNED) {$sort}" );
457 - */
458 - $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;
459 500
460 - if ( empty( $snippets ) ) {
461 - return $content;
462 - }
501 + if ( $avail_place && $is_condition ) {
502 + $post_id = (int) WINP_HTTP::post( 'post_ID', 0 );
463 503
464 - foreach ( (array) $snippets as $snippet ) {
465 - $id = (int) $snippet->ID;
466 - //$is_active = (int) WINP_Helper::getMetaOption( $id, 'snippet_activate', 0 );
467 - // Если это сниппет с автовставкой и выбранное место подходит под активный action
468 - $avail_place = ( 'auto' == $scope ? $location == WINP_Helper::getMetaOption( $id, 'snippet_location', '' ) : true );
469 - // Если условие отображения сниппета выполняется
470 - $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
471 - $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 + }
472 509
473 - if ( $avail_place && $is_condition ) {
474 - $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 + }
475 519
476 - if ( isset( $_POST['wbcr_inp_snippet_scope'] ) && $post_id === $id && WINP_Plugin::app()->currentUserCan() ) {
477 - return $content;
478 - }
520 + $snippet_code = WINP_Helper::get_snippet_code( $snippet );
479 521
480 - if ( WINP_Helper::is_safe_mode() ) {
481 - return $content;
482 - }
522 + /**
523 + * Filter snippet code before execute
524 + */
525 + $snippet_code = apply_filters( 'wbcr/inp/execute_snippet/snippet_code', $snippet_code, $id );
483 526
484 - // WPML Compatibility
485 - if ( defined( 'WPML_PLUGIN_FILE' ) ) {
486 - $wpml_langs = WINP_Helper::getMetaOption( $id, 'snippet_wpml_lang', '' );
487 - if ( $wpml_langs !== '' && defined( 'ICL_LANGUAGE_CODE' ) ) {
488 - if ( ! in_array( ICL_LANGUAGE_CODE, explode( ',', $wpml_langs ) ) ) {
489 - continue;
490 - }
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 );
491 532 }
492 - }
493 533
494 - $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 + }
495 547
496 - if ( WINP_Plugin::app()->getOption( 'execute_shortcode' ) ) {
497 - $snippet_code = do_shortcode( $snippet_code );
498 - }
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 = '';
499 560
500 - if ( $snippet_type === WINP_SNIPPET_TYPE_TEXT || $snippet_type === WINP_SNIPPET_TYPE_AD ) {
501 - $snippet_content = '<div class="winp-text-snippet-container">' . $snippet_code . '</div>';
502 - } else if ( $snippet_type === WINP_SNIPPET_TYPE_CSS || $snippet_type === WINP_SNIPPET_TYPE_JS ) {
503 - $snippet_content = self::getJsCssSnippetData( $id );
504 - } else if ( $snippet_type === WINP_SNIPPET_TYPE_HTML ) {
505 - $snippet_content = $snippet_code;
506 - } else {
507 - $code = $this->prepareCode( $snippet_code, $id );
508 - ob_start();
509 - $this->executeSnippet( $code, $id, false );
510 - $snippet_content = ob_get_contents();
511 - ob_end_clean();
512 - }
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' );
513 563
514 - if ( 'auto' == $scope ) {
515 - switch ( $location ) {
516 - case 'before_paragraph': // Перед параграфом
517 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
518 - $content = $this->handleParagraphContent( $content, $snippet_content, $location_number );
519 - break;
520 - case 'after_paragraph': // После параграфа
521 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
522 - $content = $this->handleParagraphContent( $content, $snippet_content, $location_number, 'after' );
523 - break;
524 - case 'before_posts': // Перед записью
525 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
526 - $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'before', $custom_params );
527 - break;
528 - case 'after_posts': // После записи
529 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
530 - $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'after', $custom_params );
531 - break;
532 - default:
533 - $content = $snippet_content . $content;
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 + }
534 573 }
535 574
536 - /**
537 - * Action for woo actions
538 - *
539 - * @param array $location Slug of the location.
540 - * @param string $snippet_content Rendered snippet content
541 - *
542 - * @since 2.4
543 - */
544 - do_action( "wbcr/woody/do_woocommerce_actions", $location, $snippet_content );
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 + }
545 596
546 - //$this->woocommerce_actions( $location, $snippet_content );
547 - $this->custom_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 );
548 606
549 - } else {
550 - $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 + }
551 612 }
552 613 }
553 614 }
554 615
@@ -555,8 +616,107 @@
555 616 return $content;
556 617 }
557 618
558 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 + /**
559 719 * Get js or css snippet data
560 720 *
561 721 * @param $snippet_id
562 722 *
@@ -593,9 +753,9 @@
593 753 }
594 754 }
595 755 }
596 756
597 - return "";
757 + return '';
598 758 }
599 759
600 760 /**
601 761 * Execute a snippet
@@ -602,11 +762,11 @@
602 762 *
603 763 * Code must NOT be escaped, as
604 764 * it will be executed directly
605 765 *
606 - * @param string $code The snippet code to execute
607 - * @param int $id The snippet ID
608 - * @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.
609 769 *
610 770 * @return mixed The result of the code execution
611 771 */
612 772 public function executeSnippet( $code, $id = 0, $catch_output = true ) {
@@ -625,18 +785,37 @@
625 785 if ( empty( $snippet ) || $snippet->post_type !== WINP_SNIPPETS_POST_TYPE ) {
626 786 return false;
627 787 }
628 788
629 - $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 );
630 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 +
631 805 if ( $snippet_type == WINP_SNIPPET_TYPE_UNIVERSAL ) {
632 - $result = eval( "?>" . $code . "<?php " );
633 - } else if ( $snippet_type == WINP_SNIPPET_TYPE_PHP ) {
806 + $result = eval( '?>' . $code . '<?php ' );
807 + } elseif ( $snippet_type == WINP_SNIPPET_TYPE_PHP ) {
634 808 $result = eval( $code );
635 809 } else {
636 810 $result = ! empty( $code );
637 811 }
638 812
813 + if ( $is_executable ) {
814 + self::$current_snippet_id = 0;
815 + WINP_Error_Handler::clear_current_snippet();
816 + }
817 +
639 818 if ( $catch_output ) {
640 819 ob_end_clean();
641 820 }
642 821
@@ -643,8 +822,330 @@
643 822 return $result;
644 823 }
645 824
646 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 + /**
647 1148 * Get property value
648 1149 *
649 1150 * @param $value
650 1151 * @param $property
@@ -652,10 +1153,10 @@
652 1153 * @return null
653 1154 */
654 1155 private function getPropertyValue( $value, $property ) {
655 1156 if ( is_object( $value ) ) {
656 - return $value->$property;
657 - } else if ( isset( $value[ $property ] ) ) {
1157 + return $value->$property ?? null;
1158 + } elseif ( isset( $value[ $property ] ) ) {
658 1159 return $value[ $property ];
659 1160 }
660 1161
661 1162 return null;
@@ -671,9 +1172,9 @@
671 1172 public function checkCondition( $snippet_id ) {
672 1173 // Итоговый результат условий
673 1174 $result = true;
674 1175 // Получаем сохранённые параметры условий
675 - $filters = get_post_meta( $snippet_id, WINP_Plugin::app()->getPrefix() . 'snippet_filters' );
1176 + $filters = get_post_meta( $snippet_id, 'wbcr_inp_snippet_filters' );
676 1177 // Если условия указаны
677 1178 if ( ! ( empty( $filters ) || isset( $filters[0] ) && empty( $filters[0] ) ) ) {
678 1179 foreach ( $filters[0] as $filter ) {
679 1180 $conditions = $this->getPropertyValue( $filter, 'conditions' );
@@ -766,9 +1267,9 @@
766 1267
767 1268 /**
768 1269 * Prepare the code by removing php tags from beginning and end
769 1270 *
770 - * @param string $code
1271 + * @param string $code
771 1272 * @param integer $snippet_id
772 1273 *
773 1274 * @return string
774 1275 */
@@ -773,9 +1274,14 @@
773 1274 * @return string
774 1275 */
775 1276 public function prepareCode( $code, $snippet_id ) {
776 1277 $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
777 - 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 +
778 1284 /* Remove <?php and <? from beginning of snippet */
779 1285 $code = preg_replace( '|^[\s]*<\?(php)?|', '', $code );
780 1286
781 1287 /* Remove ?> from end of snippet */
@@ -790,9 +1296,9 @@
790 1296 *
791 1297 * @return string
792 1298 */
793 1299 private function getCurrentUrl() {
794 - $out = "";
1300 + $out = '';
795 1301 $url = explode( '?', $_SERVER['REQUEST_URI'], 2 );
796 1302 if ( isset( $url[0] ) ) {
797 1303 $out = trim( $url[0], '/' );
798 1304 }
@@ -805,9 +1311,9 @@
805 1311 *
806 1312 * @return string
807 1313 */
808 1314 private function getRefererUrl() {
809 - $out = "";
1315 + $out = '';
810 1316 $url = explode( '?', str_replace( site_url(), '', $_SERVER['HTTP_REFERER'] ), 2 );
811 1317 if ( isset( $url[0] ) ) {
812 1318 $out = trim( $url[0], '/' );
813 1319 }
@@ -817,16 +1323,16 @@
817 1323
818 1324 /**
819 1325 * Check by operator
820 1326 *
821 - * @param $operation
822 - * @param $first
823 - * @param $second
824 - * @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.
825 1331 *
826 1332 * @return bool
827 1333 */
828 - public function checkByOperator( $operation, $first, $second, $third = false ) {
1334 + public function check_by_operator( $operation, $first, $second, $third = false ) {
829 1335 switch ( $operation ) {
830 1336 case 'equals':
831 1337 if ( is_array( $second ) ) {
832 1338 return in_array( $first, $second );
@@ -866,9 +1372,9 @@
866 1372 * @return boolean
867 1373 */
868 1374 private function user_role( $operator, $value ) {
869 1375 if ( ! is_user_logged_in() ) {
870 - return $this->checkByOperator( $operator, $value, 'guest' );
1376 + return $this->check_by_operator( $operator, $value, 'guest' );
871 1377 } else {
872 1378 $current_user = wp_get_current_user();
873 1379 if ( ! ( $current_user instanceof WP_User ) ) {
874 1380 return false;
@@ -873,9 +1379,9 @@
873 1379 if ( ! ( $current_user instanceof WP_User ) ) {
874 1380 return false;
875 1381 }
876 1382
877 - return $this->checkByOperator( $operator, $value, $current_user->roles[0] );
1383 + return $this->check_by_operator( $operator, $value, $current_user->roles[0] );
878 1384 }
879 1385 }
880 1386
881 1387 /**
@@ -880,14 +1386,20 @@
880 1386
881 1387 /**
882 1388 * Get timestamp
883 1389 *
884 - * @param $units
885 - * @param $count
1390 + * @param string $units Time units.
1391 + * @param mixed $count Count of units.
886 1392 *
887 1393 * @return integer
888 1394 */
889 - 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 +
890 1402 switch ( $units ) {
891 1403 case 'seconds':
892 1404 return $count;
893 1405 case 'minutes':
@@ -910,15 +1422,19 @@
910 1422
911 1423 /**
912 1424 * Get date timestamp
913 1425 *
914 - * @param $value
1426 + * @param mixed $value Date value.
915 1427 *
916 - * @return integer
1428 + * @return int|mixed Returns 0 on validation failure, timestamp calculation, or the original value
917 1429 */
918 - public function getDateTimestamp( $value ) {
1430 + public function get_date_timestamp( $value ) {
919 1431 if ( is_object( $value ) ) {
920 - 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
921 1437 } else {
922 1438 return $value;
923 1439 }
924 1440 }
@@ -926,10 +1442,10 @@
926 1442 /**
927 1443 * The date when the user who views your website was registered.
928 1444 * For unregistered users this date always equals to 1 Jan 1970.
929 1445 *
930 - * @param string $operator
931 - * @param string $value
1446 + * @param string $operator Comparison operator.
1447 + * @param mixed $value Comparison value (object for 'between', mixed otherwise).
932 1448 *
933 1449 * @return boolean
934 1450 */
935 1451 private function user_registered( $operator, $value ) {
@@ -936,24 +1452,48 @@
936 1452 if ( ! is_user_logged_in() ) {
937 1453 return false;
938 1454 } else {
939 1455 $user = wp_get_current_user();
940 - $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;
941 1464
942 - if ( $operator == 'equals' || $operator == 'notequal' ) {
1465 + if ( 'equals' === $operator || 'notequal' === $operator ) {
943 1466 $registered = $registered / 1000;
944 - $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 );
945 1474
946 - return $this->checkByOperator( $operator, date( "Y-m-d", $timestamp ), date( "Y-m-d", $registered ) );
947 - } else if ( $operator == 'between' ) {
948 - $start_timestamp = $this->getDateTimestamp( $value->start );
949 - $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 + }
950 1486
951 - return $this->checkByOperator( $operator, $start_timestamp, $registered, $end_timestamp );
1487 + return $this->check_by_operator( $operator, $start_timestamp, $registered, $end_timestamp );
952 1488 } else {
953 - $timestamp = $this->getDateTimestamp( $value );
1489 + $timestamp = $this->get_date_timestamp( $value );
1490 +
1491 + if ( ! $timestamp || $timestamp <= 0 ) {
1492 + return false;
1493 + }
954 1494
955 - return $this->checkByOperator( $operator, $timestamp, $registered );
1495 + return $this->check_by_operator( $operator, $timestamp, $registered );
956 1496 }
957 1497 }
958 1498 }
959 1499
@@ -959,10 +1499,10 @@
959 1499
960 1500 /**
961 1501 * Check the user views your website from mobile device or not
962 1502 *
963 - * @param string $operator
964 - * @param string $value
1503 + * @param string $operator Comparison operator.
1504 + * @param string $value Comparison value.
965 1505 *
966 1506 * @return boolean
967 1507 *
968 1508 * @link https://stackoverflow.com/a/4117597
@@ -1063,9 +1603,9 @@
1063 1603 if ( $current_taxonomy == $taxonomy ) {
1064 1604 $result = true;
1065 1605 }
1066 1606 }
1067 - break;
1607 + break;
1068 1608
1069 1609 default:
1070 1610 $result = false;
1071 1611 }
@@ -1095,9 +1635,9 @@
1095 1635 break;
1096 1636 }
1097 1637 }
1098 1638
1099 - return $this->checkByOperator( $operator, $result, true );
1639 + return $this->check_by_operator( $operator, $result, true );
1100 1640 }
1101 1641
1102 1642 /**
1103 1643 * An URL of the current page where a user who views your website is located
@@ -1109,9 +1649,9 @@
1109 1649 */
1110 1650 private function location_page( $operator, $value ) {
1111 1651 $url = $this->getCurrentUrl();
1112 1652
1113 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1653 + return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1114 1654 }
1115 1655
1116 1656 /**
1117 1657 * A referrer URL which has brought a user to the current page
@@ -1123,9 +1663,9 @@
1123 1663 */
1124 1664 private function location_referrer( $operator, $value ) {
1125 1665 $url = $this->getRefererUrl();
1126 1666
1127 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1667 + return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1128 1668 }
1129 1669
1130 1670 /**
1131 1671 * A post type of the current page
@@ -1136,9 +1676,9 @@
1136 1676 * @return boolean
1137 1677 */
1138 1678 private function location_post_type( $operator, $value ) {
1139 1679 if ( is_singular() ) {
1140 - return $this->checkByOperator( $operator, $value, get_post_type() );
1680 + return $this->check_by_operator( $operator, $value, get_post_type() );
1141 1681 }
1142 1682
1143 1683 return false;
1144 1684 }
@@ -1151,9 +1691,8 @@
1151 1691 *
1152 1692 * @return boolean
1153 1693 * @since 2.2.8 The bug is fixed, the condition was not checked
1154 1694 * for tachonomies, only posts.
1155 - *
1156 1695 */
1157 1696 private function location_taxonomy( $operator, $value ) {
1158 1697 $term_id = null;
1159 1698
@@ -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,10 +1728,10 @@
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 }
1198 -}
1737 +}