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.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 2.7.1 All 27 releases
insert-php / includes / class.execute.snippet.php

class.execute.snippet.php in Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts trunk, at includes/class.execute.snippet.php

1,735 lines 55.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Execute snippet
4 *
5 * @package Woody_Code_Snippets
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 class WINP_Execute_Snippet {
14
15 /**
16 * @var self
17 */
18 private static $instance;
19
20 /**
21 * @var array
22 */
23 public $snippets;
24
25 /**
26 * @var WINP_Insertion_Locations
27 */
28 public $snippets_locations;
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
51 public static function app() {
52 if ( self::$instance === null ) {
53 self::$instance = new self();
54 }
55
56 return self::$instance;
57 }
58
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 /**
69 * WINP_Execute_Snippet constructor.
70 */
71 public function __construct() {
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 }
88
89 if ( ! defined( 'WINP_UPLOAD_DIR' ) ) {
90 $dir = wp_upload_dir();
91 define( 'WINP_UPLOAD_DIR', $dir['basedir'] . '/winp-css-js' );
92 }
93
94 if ( ! defined( 'WINP_UPLOAD_URL' ) ) {
95 $dir = wp_upload_dir();
96 define( 'WINP_UPLOAD_URL', $dir['baseurl'] . '/winp-css-js' );
97 }
98 global $wpdb;
99
100 // todo: Simplify your request. Seems written ugly
101 $sql = "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_content, p2.meta_value as priority
102 FROM {$wpdb->posts}
103 INNER JOIN {$wpdb->postmeta} p1 ON ({$wpdb->posts}.ID = p1.post_id)
104 INNER JOIN {$wpdb->postmeta} p2 ON ({$wpdb->posts}.ID = p2.post_id)
105 INNER JOIN {$wpdb->postmeta} p3 ON ({$wpdb->posts}.ID = p3.post_id)
106 WHERE (( p1.meta_key = 'wbcr_inp_snippet_scope' AND p1.meta_value = '%s')
107 AND
108 ( p3.meta_key = 'wbcr_inp_snippet_activate' AND p3.meta_value = '1')
109 AND p2.meta_key = 'wbcr_inp_snippet_priority' )
110 AND {$wpdb->posts}.post_type = '" . WINP_SNIPPETS_POST_TYPE . "'
111 AND ({$wpdb->posts}.post_status = 'publish')
112 ORDER BY CAST(priority AS UNSIGNED) %s";
113
114 $this->snippets['evrywhere'] = $wpdb->get_results( sprintf( $sql, 'evrywhere', 'DESC' ) );
115 $this->snippets['auto'] = $wpdb->get_results( sprintf( $sql, 'auto', 'ASC' ) );
116
117 global $winp_snippets_locations;
118 $this->snippets_locations = new WINP_Insertion_Locations();
119 }
120
121 /**
122 * Register hooks
123 */
124 public function register_hooks() {
125 add_action( 'init', [ $this, 'execute_everywhere_snippets' ], 1 );
126
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 );
131 add_filter( 'the_content', [ $this, 'executeContentSnippets' ] );
132 add_filter( 'the_excerpt', [ $this, 'executeExcerptSnippets' ] );
133 // Бесполезный �
134 ук, который вызывается на каждый комментарий. Если и�
135 много, увеличивается нагрузка
136 // add_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
137
138 // add_action( 'wp_head', [ $this, 'executeWoocommerceSnippets' ] );
139
140 if ( ! empty( $this->snippets_locations->getInsertion( 'custom' ) ) ) {
141 add_action( 'wp_head', [ $this, 'executeCustomSnippets' ] );
142 }
143 }
144 }
145
146 /**
147 * Execute the everywhere snippets once the plugins are loaded
148 */
149 public function execute_everywhere_snippets() {
150 echo $this->execute_active_snippets( 'evrywhere' );
151 }
152
153 /**
154 * Execute the snippets in header of page once the plugins are loaded
155 */
156 public function execute_header_snippets() {
157 echo $this->execute_active_snippets( 'auto', 'header' );
158 }
159
160 /**
161 * Execute the snippets in footer of page once the plugins are loaded
162 */
163 public function execute_footer_snippets() {
164 echo $this->execute_active_snippets( 'auto', 'footer' );
165 }
166
167 /**
168 * Execute the snippets before post
169 *
170 * @param WP_Post $post
171 * @param WP_Query $query
172 */
173 public function executePostSnippets( $post, $query ) {
174 $content = '';
175
176 $post_type = ! empty( $post ) ? $post->post_type : get_post( $post->ID )->post_type;
177 if ( is_singular( [ $post_type ] ) ) {
178 if ( did_action( 'get_header' ) ) {
179 // Перед заголовком
180 $content = $this->execute_active_snippets( 'auto', 'before_post' );
181 }
182 } elseif ( $query->post_count > 0 ) {
183 if ( $query->post_count > 1 && $query->current_post > 0 && $query->post_count > $query->current_post ) {
184 // Между записями
185 $content = $this->execute_active_snippets( 'auto', 'between_posts' );
186 }
187 // Перед записью
188 $content .= $this->execute_active_snippets( 'auto', 'before_posts', '', $query );
189
190 // После записи
191 $content .= $this->execute_active_snippets( 'auto', 'after_posts', '', $query );
192 }
193
194 echo $content;
195 }
196
197 /**
198 * Handle paragraph content
199 *
200 * @param $content
201 * @param $snippet_content
202 * @param $paragraph_number
203 * @param $type
204 *
205 * @return mixed
206 */
207 private function handleParagraphContent( $content, $snippet_content, $paragraph_number, $type = 'before' ) {
208 if ( 'before' == $type ) {
209 preg_match_all( '/<p(.*?)>/', $content, $matches );
210 } else {
211 preg_match_all( '/<\/p>/', $content, $matches );
212 }
213 $paragraphs = $matches[0];
214
215 if ( $paragraph_number == 0 ) {
216 $paragraph_number = 1;
217 }
218
219 if ( $content && $snippet_content && $paragraphs && $paragraph_number <= count( $paragraphs ) ) {
220 $offset = 0;
221 foreach ( $paragraphs as $paragraph_key => $paragraph ) {
222 $position = strpos( $content, $paragraph, $offset ); // Позиция тега параграфа
223 // Если указанный номер параграфа совпадает с текущим
224 if ( $paragraph_key + 1 == $paragraph_number ) {
225 if ( 'before' == $type ) {
226 $content = substr( $content, 0, $position ) . $snippet_content . substr( $content, $position );
227 } else {
228 $content = substr( $content, 0, $position + 4 ) . $snippet_content . substr( $content, $position + 4 );
229 }
230 break;
231 } else {
232 $offset = $position + 1;
233 }
234 }
235 }
236
237 return $content;
238 }
239
240 /**
241 * Handle posts content
242 *
243 * @param string $content
244 * @param string $snippet_content
245 * @param integer $post_number
246 * @param string $type
247 * @param object $query
248 *
249 * @return mixed
250 */
251 private function handlePostsContent( $content, $snippet_content, $post_number, $type, $query ) {
252 global $winp_after_post_content;
253 if ( $query->post_count > 0 ) {
254 if ( $post_number == 0 ) {
255 $post_number = 1;
256 }
257
258 if ( 'before' == $type && $query->current_post + 1 == $post_number ) {
259 return $snippet_content;
260 } elseif ( 'after' == $type ) {
261 // Номер поста совпадает
262 if ( $query->current_post == $post_number ) {
263 return $snippet_content;
264 // Если это последний пост и указанный номер поста больше общего количества постов,
265 // то нужно со�
266 ранить контент сниппета для вывода в конце данного поста
267 } elseif ( $query->current_post + 1 == $query->post_count && $post_number >= $query->post_count ) {
268 $winp_after_post_content[ $query->post->ID ] = $snippet_content;
269 }
270 }
271 }
272
273 return $content;
274 }
275
276 /**
277 * Execute the snippets page content
278 *
279 * @param $content
280 *
281 * @return mixed
282 */
283 public function executeContentSnippets( $content ) {
284 global $post, $winp_after_post_content;
285
286 $post_type = ! empty( $post ) ? $post->post_type : false;
287
288 if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
289 // Перед коротким описанием
290 $content = $this->execute_active_snippets( 'auto', 'before_excerpt' ) . $content;
291
292 // После короткого описания
293 $content .= $this->execute_active_snippets( 'auto', 'after_excerpt' );
294 }
295
296 if ( is_singular( [ $post_type ] ) ) {
297 // Перед параграфом
298 $content = $this->execute_active_snippets( 'auto', 'before_paragraph', $content );
299
300 // После параграфа
301 $content = $this->execute_active_snippets( 'auto', 'after_paragraph', $content );
302
303 // После заголовка
304 $content = $this->execute_active_snippets( 'auto', 'before_content' ) . $content;
305
306 // После текста
307 $content .= $this->execute_active_snippets( 'auto', 'after_content' );
308
309 // После поста
310 $content .= $this->execute_active_snippets( 'auto', 'after_post' );
311
312 if ( ! comments_open( $post->ID ) && ! get_comments_number( $post->ID ) ) {
313 remove_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
314 }
315 } elseif ( ! is_null( $post ) && isset( $winp_after_post_content[ $post->ID ] ) ) {
316 // После последнего поста в списке
317 $content .= $winp_after_post_content[ $post->ID ];
318 unset( $winp_after_post_content[ $post->ID ] );
319 }
320
321 return $content;
322 }
323
324 /**
325 * Execute the snippets page excerpt
326 *
327 * @param $excerpt
328 *
329 * @return mixed
330 */
331 public function executeExcerptSnippets( $excerpt ) {
332 if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
333 // Перед коротким описанием
334 $excerpt = $this->execute_active_snippets( 'auto', 'before_excerpt' ) . $excerpt;
335
336 // После короткого описания
337 $excerpt .= $this->execute_active_snippets( 'auto', 'after_excerpt' );
338 }
339
340 return $excerpt;
341 }
342
343 /**
344 * Execute the list comments filter
345 *
346 * @param $args
347 *
348 * @return mixed
349 */
350 public function executeListCommentsSnippets( $args ) {
351 global $winp_wp_data;
352
353 $winp_wp_data['winp_comments_saved_end_callback'] = $args['end-callback'];
354 $args['end-callback'] = [ $this, 'executeCommentsSnippets' ];
355
356 return $args;
357 }
358
359 /**
360 * Execute the snippets after page comments
361 *
362 * @param $comment
363 * @param $args
364 * @param $depth
365 */
366 public function executeCommentsSnippets( $comment, $args, $depth ) {
367 global $winp_wp_data, $post;
368
369 if ( ! empty( $winp_wp_data['winp_comments_saved_end_callback'] ) ) {
370 echo call_user_func( $winp_wp_data['winp_comments_saved_end_callback'], $comment, $args, $depth );
371 }
372
373 $content = '';
374
375 $post_type = ! empty( $post ) ? $post->post_type : false;
376 if ( is_singular( [ $post_type ] ) ) {
377 // После комментариев
378 $content = $this->execute_active_snippets( 'auto', 'after_post' );
379 }
380
381 echo $content;
382 }
383
384 /**
385 * Execute the custom snippets
386 *
387 * @since 2.4
388 */
389 public function executeCustomSnippets() {
390 $locations = $this->snippets_locations->getInsertion( 'custom' );
391 foreach ( $locations as $location => $data ) {
392 $this->execute_active_snippets( 'auto', $location );
393 }
394 }
395
396 /**
397 * Execute Woocommerce actions/hooks
398 *
399 * @param $location
400 * @param $snippet_content
401 *
402 * @since 2.4
403 */
404 public function woocommerce_actions( $location, $snippet_content = '' ) {
405 $action = function () use ( $location, $snippet_content ) {
406 echo $snippet_content;
407 };
408
409 switch ( $location ) {
410 case 'woo_before_shop_loop':
411 add_filter(
412 'woocommerce_product_loop_start',
413 function ( $content ) use ( $snippet_content ) {
414 return $snippet_content . $content;
415 }
416 );
417 break;
418 case 'woo_after_shop_loop':
419 add_filter(
420 'woocommerce_product_loop_end',
421 function ( $content ) use ( $snippet_content ) {
422 return $content . $snippet_content;
423 }
424 );
425 break;
426 case 'woo_before_single_product':
427 add_action( 'woocommerce_before_single_product', $action, 10, 2 );
428 break;
429 case 'woo_after_single_product':
430 add_action( 'woocommerce_after_single_product', $action, 10, 2 );
431 break;
432 case 'woo_before_single_product_summary':
433 add_action( 'woocommerce_before_single_product_summary', $action, 10, 2 );
434 break;
435 case 'woo_after_single_product_summary':
436 add_action( 'woocommerce_after_single_product_summary', $action, 10, 2 );
437 break;
438 case 'woo_single_product_summary_title':
439 add_action( 'woocommerce_single_product_summary', $action, 6, 2 );
440 break;
441 case 'woo_single_product_summary_price':
442 add_action( 'woocommerce_single_product_summary', $action, 15, 2 );
443 break;
444 case 'woo_single_product_summary_excerpt':
445 add_action( 'woocommerce_single_product_summary', $action, 25, 2 );
446 break;
447 default:
448 break;
449 }
450 }
451
452 /**
453 * Execute Woocommerce actions/hooks
454 *
455 * @param $location
456 * @param $snippet_content
457 *
458 * @since 2.4
459 */
460 public function custom_actions( $location, $snippet_content = '' ) {
461 if ( ! empty( $this->snippets_locations->getLocation( $location ) ) ) {
462 /**
463 * Action for a custom location applied in 'wbcr/woody/add_custom_location' filter
464 *
465 * @param array $location Slug of the location.
466 * @param string $snippet_content Rendered snippet content
467 *
468 * @since 2.4
469 */
470 do_action( "wbcr/woody/do_custom_location/{$location}", $snippet_content );
471 }
472 }
473
474 /**
475 * Execute the snippets once the plugins are loaded
476 *
477 * @param string $scope
478 * @param string $location
479 * @param string $content
480 * @param array $custom_params
481 *
482 * @return string
483 */
484 public function execute_active_snippets( $scope = 'evrywhere', $location = '', $content = '', $custom_params = [] ) {
485 $snippets = $this->snippets[ $scope ] ?? [];
486
487 if ( ! empty( $snippets ) ) {
488 foreach ( (array) $snippets as $snippet ) {
489 $id = (int) $snippet->ID;
490
491 // Allow filtering to skip specific snippet IDs.
492 $should_skip = apply_filters( 'winp_skip_snippet_execution', false, $id );
493 if ( $should_skip ) {
494 continue;
495 }
496
497 // $is_active = (int) WINP_Helper::getMetaOption( $id, 'snippet_activate', 0 );
498 // Если это сниппет с автовставкой и выбранное место под�
499 одит под активный action
500 $avail_place = ( 'auto' == $scope ? $location == WINP_Helper::getMetaOption( $id, 'snippet_location', '' ) : true );
501 // Если условие отображения сниппета выполняется
502 $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
503 $is_condition = $snippet_type != WINP_SNIPPET_TYPE_PHP ? $this->checkCondition( $id ) : true;
504
505 if ( $avail_place && $is_condition ) {
506 $post_id = (int) WINP_HTTP::post( 'post_ID', 0 );
507
508 if ( ( isset( $_POST['wbcr_inp_snippet_scope'] )
509 && $post_id === $id
510 && WINP_Plugin::app()->current_user_car() ) || WINP_Helper::is_safe_mode() ) {
511 return $content;
512 }
513
514 // WPML Compatibility
515 if ( defined( 'WPML_PLUGIN_FILE' ) ) {
516 $wpml_langs = WINP_Helper::getMetaOption( $id, 'snippet_wpml_lang', '' );
517 if ( $wpml_langs !== '' && defined( 'ICL_LANGUAGE_CODE' ) ) {
518 if ( ! in_array( ICL_LANGUAGE_CODE, explode( ',', $wpml_langs ) ) ) {
519 continue;
520 }
521 }
522 }
523
524 $snippet_code = WINP_Helper::get_snippet_code( $snippet );
525
526 /**
527 * Filter snippet code before execute
528 */
529 $snippet_code = apply_filters( 'wbcr/inp/execute_snippet/snippet_code', $snippet_code, $id );
530
531 // Track this snippet as executed.
532 $this->track_executed_snippet( $id, $scope );
533
534 if ( get_option( 'wbcr_inp_execute_shortcode' ) ) {
535 $snippet_code = do_shortcode( $snippet_code );
536 }
537
538 if ( $snippet_type === WINP_SNIPPET_TYPE_TEXT || $snippet_type === WINP_SNIPPET_TYPE_AD ) {
539 $snippet_content = '<div class="winp-text-snippet-container">' . $snippet_code . '</div>';
540 } elseif ( $snippet_type === WINP_SNIPPET_TYPE_CSS || $snippet_type === WINP_SNIPPET_TYPE_JS ) {
541 $snippet_content = self::getJsCssSnippetData( $id );
542 } elseif ( $snippet_type === WINP_SNIPPET_TYPE_HTML ) {
543 $snippet_content = $snippet_code;
544 } else {
545 $code = $this->prepareCode( $snippet_code, $id );
546 ob_start();
547 $this->executeSnippet( $code, $id, false );
548 $snippet_content = ob_get_contents();
549 ob_end_clean();
550 }
551
552 // If the user has prohibited the insertion of unfiltered HTML,
553 // we prohibit the execution of snippets.
554 if ( ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML )
555 && ! in_array(
556 $snippet_type,
557 [
558 WINP_SNIPPET_TYPE_TEXT,
559 WINP_SNIPPET_TYPE_AD,
560 WINP_SNIPPET_TYPE_CSS,
561 ]
562 ) ) {
563 $snippet_content = '';
564
565 if ( is_user_logged_in() && WINP_Plugin::app()->current_user_car() ) {
566 $error_text = __( 'This Woody snippet cannot run because unfiltered HTML insertion is disabled.', 'insert-php' );
567
568 switch ( $location ) {
569 case 'header':
570 case 'footer':
571 $snippet_content = '<!-- ' . $error_text . '-->';
572 break;
573 default:
574 $snippet_content = $error_text;
575 }
576 }
577 }
578
579 if ( 'auto' == $scope ) {
580 switch ( $location ) {
581 case 'before_paragraph': // Перед параграфом
582 $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
583 $content = $this->handleParagraphContent( $content, $snippet_content, $location_number );
584 break;
585 case 'after_paragraph': // После параграфа
586 $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
587 $content = $this->handleParagraphContent( $content, $snippet_content, $location_number, 'after' );
588 break;
589 case 'before_posts': // Перед записью
590 $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
591 $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'before', $custom_params );
592 break;
593 case 'after_posts': // После записи
594 $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
595 $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'after', $custom_params );
596 break;
597 default:
598 $content = $snippet_content . $content;
599 }
600
601 /**
602 * Action for woo actions
603 *
604 * @param array $location Slug of the location.
605 * @param string $snippet_content Rendered snippet content
606 *
607 * @since 2.4
608 */
609 do_action( 'wbcr/woody/do_woocommerce_actions', $location, $snippet_content );
610
611 // $this->woocommerce_actions( $location, $snippet_content );
612 $this->custom_actions( $location, $snippet_content );
613 } else {
614 $content = $snippet_content . $content;
615 }
616 }
617 }
618 }
619
620 return $content;
621 }
622
623 /**
624 * Track shortcode snippet execution.
625 *
626 * Public method for shortcode classes to call.
627 *
628 * @param int $snippet_id Snippet ID.
629 * @return void
630 */
631 public function track_shortcode_snippet( $snippet_id ) {
632 $this->track_executed_snippet( $snippet_id, 'shortcode' );
633 }
634
635 /**
636 * Track executed snippet.
637 *
638 * @param int $snippet_id Snippet ID.
639 * @param string $scope Snippet scope.
640 * @return void
641 */
642 private function track_executed_snippet( $snippet_id, $scope ) {
643 if ( isset( $this->executed_snippets[ $snippet_id ] ) ) {
644 return; // Already tracked.
645 }
646
647 $snippet = get_post( $snippet_id );
648 if ( ! $snippet ) {
649 return;
650 }
651
652 $snippet_type = WINP_Helper::getMetaOption( $snippet_id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
653 $snippet_location = WINP_Helper::getMetaOption( $snippet_id, 'snippet_location', '' );
654
655 // Map location to human-readable label.
656 $location_label = $this->get_location_label( $scope, $snippet_location );
657
658 $this->executed_snippets[ $snippet_id ] = [
659 'id' => $snippet_id,
660 'name' => $snippet->post_title,
661 'type' => $this->get_type_label( $snippet_type ),
662 'location' => $location_label,
663 'scope' => $scope,
664 ];
665 }
666
667 /**
668 * Get human-readable location label.
669 *
670 * @param string $scope Snippet scope.
671 * @param string $location Snippet location.
672 * @return string
673 */
674 private function get_location_label( $scope, $location ) {
675 if ( 'evrywhere' === $scope ) {
676 return __( 'Everywhere', 'insert-php' );
677 }
678
679 if ( 'shortcode' === $scope ) {
680 return __( 'Shortcode', 'insert-php' );
681 }
682
683 $location_labels = [
684 'header' => __( 'Header', 'insert-php' ),
685 'footer' => __( 'Footer', 'insert-php' ),
686 'before_post' => __( 'Before Post', 'insert-php' ),
687 'before_content' => __( 'Before Content', 'insert-php' ),
688 'before_paragraph' => __( 'Before Paragraph', 'insert-php' ),
689 'after_paragraph' => __( 'After Paragraph', 'insert-php' ),
690 'after_content' => __( 'After Content', 'insert-php' ),
691 'after_post' => __( 'After Post', 'insert-php' ),
692 'before_excerpt' => __( 'Before Excerpt', 'insert-php' ),
693 'after_excerpt' => __( 'After Excerpt', 'insert-php' ),
694 'before_posts' => __( 'Before Posts', 'insert-php' ),
695 'after_posts' => __( 'After Posts', 'insert-php' ),
696 'after_comments' => __( 'After Comments', 'insert-php' ),
697 ];
698
699 return $location_labels[ $location ] ?? ucwords( str_replace( '_', ' ', $location ) );
700 }
701
702 /**
703 * Get human-readable type label.
704 *
705 * @param string $type Snippet type.
706 * @return string
707 */
708 private function get_type_label( $type ) {
709 $type_labels = [
710 WINP_SNIPPET_TYPE_PHP => 'PHP',
711 WINP_SNIPPET_TYPE_UNIVERSAL => 'Universal',
712 WINP_SNIPPET_TYPE_HTML => 'HTML',
713 WINP_SNIPPET_TYPE_CSS => 'CSS',
714 WINP_SNIPPET_TYPE_JS => 'JS',
715 WINP_SNIPPET_TYPE_TEXT => 'TEXT',
716 WINP_SNIPPET_TYPE_AD => 'AD',
717 ];
718
719 return $type_labels[ $type ] ?? 'PHP';
720 }
721
722 /**
723 * Get js or css snippet data
724 *
725 * @param $snippet_id
726 *
727 * @return mixed|string
728 */
729 public static function getJsCssSnippetData( $snippet_id ) {
730 $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
731
732 $linking = WINP_Helper::getMetaOption( $snippet_id, 'snippet_linking' );
733 $filetype = WINP_Helper::getMetaOption( $snippet_id, 'filetype', $snippet_type );
734
735 $file_name = $snippet_id . '.' . $filetype;
736 $slug = WINP_Helper::getMetaOption( $snippet_id, 'css_js_slug' );
737 if ( ! empty( $slug ) ) {
738 $file_name = $slug . '.' . $filetype;
739 }
740
741 if ( file_exists( WINP_UPLOAD_DIR . '/' . $file_name ) ) {
742 if ( 'inline' == $linking ) {
743 return file_get_contents( WINP_UPLOAD_DIR . '/' . $file_name );
744 }
745
746 if ( 'external' == $linking ) {
747 $file_name .= '?ver=' . WINP_Helper::getMetaOption( $snippet_id, 'css_js_version', time() );
748
749 if ( 'js' == $snippet_type ) {
750 return PHP_EOL . "<script type='text/javascript' src='" . WINP_UPLOAD_URL . '/' . $file_name . "'></script>" . PHP_EOL;
751 }
752
753 if ( 'css' == $snippet_type ) {
754 $short_filename = preg_replace( '@\.css\?ver=.*$@', '', $file_name );
755
756 return PHP_EOL . "<link rel='stylesheet' id='" . $short_filename . "-css' href='" . WINP_UPLOAD_URL . '/' . $file_name . "' type='text/css' media='all' />" . PHP_EOL;
757 }
758 }
759 }
760
761 return '';
762 }
763
764 /**
765 * Execute a snippet
766 *
767 * Code must NOT be escaped, as
768 * it will be executed directly
769 *
770 * @param string $code The snippet code to execute.
771 * @param int $id The snippet ID.
772 * @param bool $catch_output Whether to attempt to suppress the output of execution using buffers.
773 *
774 * @return mixed The result of the code execution
775 */
776 public function executeSnippet( $code, $id = 0, $catch_output = true ) {
777 $id = (int) $id;
778
779 if ( ! $id || empty( $code ) ) {
780 return false;
781 }
782
783 if ( $catch_output ) {
784 ob_start();
785 }
786
787 $snippet = get_post( $id );
788
789 if ( empty( $snippet ) || $snippet->post_type !== WINP_SNIPPETS_POST_TYPE ) {
790 return false;
791 }
792
793 $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', true );
794
795 // Set current snippet ID for error handling.
796 self::$current_snippet_id = $id;
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 if ( $snippet_type == WINP_SNIPPET_TYPE_UNIVERSAL ) {
805 $result = eval( '?>' . $code . '<?php ' );
806 } elseif ( $snippet_type == WINP_SNIPPET_TYPE_PHP ) {
807 $result = eval( $code );
808 } else {
809 $result = ! empty( $code );
810 }
811
812 if ( $catch_output ) {
813 ob_end_clean();
814 }
815
816 return $result;
817 }
818
819 /**
820 * Handle fatal errors during snippet execution.
821 *
822 * @return void
823 */
824 public function handle_snippet_shutdown() {
825 $error = error_get_last();
826
827 // If there's a fatal error and a snippet was being executed, check if it's from the snippet.
828 if ( self::$current_snippet_id && $error && in_array( $error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ] ) ) {
829 // Only log if the error originated from the snippet code itself.
830 if ( $this->is_error_from_snippet( $error ) ) {
831 $this->log_snippet_error( self::$current_snippet_id, $error );
832 }
833 }
834
835 // Clear the snippet ID after processing.
836 self::$current_snippet_id = 0;
837 }
838
839 /**
840 * Check if the error originated from the snippet code.
841 *
842 * @param array<string, mixed> $error Error details.
843 *
844 * @return bool True if error is from snippet, false otherwise.
845 */
846 private function is_error_from_snippet( $error ) {
847 if ( ! isset( $error['file'] ) ) {
848 return false;
849 }
850
851 $error_file = $error['file'];
852
853 // Check if error is from eval'd code (PHP snippets executed via eval).
854 if ( strpos( $error_file, "eval()'d code" ) !== false ) {
855 return true;
856 }
857
858 return false;
859 }
860
861 /**
862 * Log snippet error via admin notice
863 *
864 * @param int $snippet_id Snippet ID.
865 * @param array<string, mixed> $error Error details.
866 *
867 * @return void
868 */
869 private function log_snippet_error( $snippet_id, $error ) {
870 // Validate error array has required keys.
871 if ( ! isset( $error['message'], $error['file'], $error['line'] ) ) {
872 return;
873 }
874
875 // Generate unique notice ID based on snippet ID and error details.
876 $error_signature = md5( $snippet_id . $error['message'] . $error['file'] . $error['line'] );
877 $notice_id = 'snippet_error_' . $error_signature;
878
879 // Get snippet title for better context.
880 $snippet = get_post( $snippet_id );
881 $snippet_title = $snippet ? $snippet->post_title : "ID {$snippet_id}";
882
883 // Extract the main error message (before stack trace).
884 $error_message = $error['message'];
885 if ( strpos( $error_message, ' Stack trace:' ) !== false ) {
886 $error_message = substr( $error_message, 0, strpos( $error_message, ' Stack trace:' ) );
887 }
888 $error_message = trim( $error_message );
889
890 // Shorten file path for readability.
891 $file_path = str_replace( ABSPATH, '', $error['file'] );
892
893 // Get edit link for the snippet.
894 $edit_link = admin_url( 'post.php?post=' . $snippet_id . '&action=edit' );
895
896 // Build notice message.
897 $message = sprintf(
898 '<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>',
899 __( 'Snippet Error Detected', 'insert-php' ),
900 // Translators: 1: Snippet title.
901 sprintf( __( 'Snippet "%s" caused a fatal error.', 'insert-php' ), esc_html( $snippet_title ) ),
902 esc_url( $edit_link ),
903 __( 'Edit Snippet', 'insert-php' ),
904 __( 'Show error details', 'insert-php' ),
905 __( 'Error', 'insert-php' ),
906 esc_html( $error_message ),
907 __( 'Location', 'insert-php' ),
908 esc_html( $file_path ),
909 __( 'line', 'insert-php' ),
910 $error['line'],
911 __( 'If you have fixed this error, you can dismiss this notice.', 'insert-php' ),
912 __( 'Dismiss', 'insert-php' )
913 );
914
915 // Store error notice in option to be displayed on next admin page load.
916 $pending_notices = get_option( 'winp_pending_error_notices', [] );
917 if ( ! is_array( $pending_notices ) ) {
918 $pending_notices = [];
919 }
920
921 // Only add if not already present (avoid race conditions).
922 if ( ! isset( $pending_notices[ $notice_id ] ) ) {
923 $pending_notices[ $notice_id ] = [
924 'message' => $message,
925 'type' => 'error',
926 ];
927
928 update_option( 'winp_pending_error_notices', $pending_notices, false );
929
930 // Send email notification if enabled and this is a new error.
931 $this->send_error_email( $notice_id, $snippet_id, $snippet_title, $error_message, $file_path, $error['line'], $edit_link );
932 }
933 }
934
935 /**
936 * Send email notification for snippet error
937 *
938 * @param string $error_signature Unique error identifier.
939 * @param int $snippet_id The ID of the snippet.
940 * @param string $snippet_title The title of the snippet.
941 * @param string $error_message The error message.
942 * @param string $file_path The file path where error occurred.
943 * @param int $line_number The line number where error occurred.
944 * @param string $edit_link Link to edit the snippet.
945 *
946 * @return void
947 */
948 private function send_error_email( $error_signature, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link ) {
949 // Check if email notifications are enabled.
950 $email_enabled = get_option( 'wbcr_inp_error_email_enabled' );
951 if ( ! $email_enabled ) {
952 return;
953 }
954
955 // Get email address.
956 $email_address = get_option( 'wbcr_inp_error_email_address', get_option( 'admin_email' ) );
957 if ( empty( $email_address ) || ! is_email( $email_address ) ) {
958 return;
959 }
960
961 // Check if we've already emailed about this error.
962 $emailed_errors = get_option( 'winp_emailed_errors', [] );
963 if ( ! is_array( $emailed_errors ) ) {
964 $emailed_errors = [];
965 }
966
967 // If we've already emailed about this error, skip.
968 if ( isset( $emailed_errors[ $error_signature ] ) ) {
969 return;
970 }
971
972 // Clean up old entries (older than 30 days).
973 $thirty_days_ago = time() - ( 30 * DAY_IN_SECONDS );
974 foreach ( $emailed_errors as $hash => $timestamp ) {
975 if ( $timestamp < $thirty_days_ago ) {
976 unset( $emailed_errors[ $hash ] );
977 }
978 }
979
980 // Build email content.
981 $site_name = get_bloginfo( 'name' );
982 $admin_url = admin_url( 'edit.php?post_type=' . WINP_SNIPPETS_POST_TYPE );
983 $subject = sprintf( '[%s] Snippet Error Detected: %s', $site_name, $snippet_title );
984
985 $email_body = $this->get_error_email_template( $site_name, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link, $admin_url );
986
987 // Set email headers for HTML.
988 $headers = [
989 'Content-Type: text/html; charset=UTF-8',
990 ];
991
992 // Send email.
993 $sent = wp_mail( $email_address, $subject, $email_body, $headers );
994
995 // If email sent successfully, mark this error as emailed.
996 if ( $sent ) {
997 $emailed_errors[ $error_signature ] = time();
998 update_option( 'winp_emailed_errors', $emailed_errors, false );
999 }
1000 }
1001
1002 /**
1003 * Get HTML email template for error notification
1004 *
1005 * @param string $site_name Site name.
1006 * @param int $snippet_id Snippet ID.
1007 * @param string $snippet_title Snippet title.
1008 * @param string $error_message Error message.
1009 * @param string $file_path File path.
1010 * @param int $line_number Line number.
1011 * @param string $edit_link Edit link.
1012 * @param string $admin_url Admin URL.
1013 *
1014 * @return string HTML email template.
1015 */
1016 private function get_error_email_template( $site_name, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link, $admin_url ) {
1017 ob_start();
1018 ?>
1019 <!DOCTYPE html>
1020 <html>
1021 <head>
1022 <meta charset="UTF-8">
1023 <meta name="viewport" content="width=device-width, initial-scale=1.0">
1024 <title><?php echo esc_html( __( 'Snippet Error Notification', 'insert-php' ) ); ?></title>
1025 </head>
1026 <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;">
1027 <table role="presentation" style="width: 100%; border-collapse: collapse;">
1028 <tr>
1029 <td align="center" style="padding: 40px 20px;">
1030 <table role="presentation" style="width: 100%; max-width: 600px; border-collapse: collapse; background-color: #ffffff; border: 1px solid #c3c4c7; border-radius: 2px;">
1031 <!-- Header -->
1032 <tr>
1033 <td style="padding: 24px 30px; background-color: #2271b1; border-bottom: 1px solid #135e96;">
1034 <h1 style="margin: 0; color: #ffffff; font-size: 20px; font-weight: 600;">
1035 ⚠️ <?php echo esc_html( __( 'Snippet Error Detected', 'insert-php' ) ); ?>
1036 </h1>
1037 </td>
1038 </tr>
1039
1040 <!-- Content -->
1041 <tr>
1042 <td style="padding: 30px;">
1043 <p style="margin: 0 0 16px; color: #1d2327; font-size: 14px; line-height: 1.6;">
1044 <?php echo esc_html( __( 'Hello,', 'insert-php' ) ); ?>
1045 </p>
1046
1047 <p style="margin: 0 0 20px; color: #1d2327; font-size: 14px; line-height: 1.6;">
1048 <?php
1049 printf(
1050 // translators: 1: snippet title, 2: site name.
1051 esc_html( __( 'A fatal error has been detected in the snippet "%1$s" on your site %2$s.', 'insert-php' ) ),
1052 '<strong>' . esc_html( $snippet_title ) . '</strong>',
1053 '<strong>' . esc_html( $site_name ) . '</strong>'
1054 );
1055 ?>
1056 </p>
1057
1058 <!-- Error Details Box -->
1059 <table role="presentation" style="width: 100%; border-collapse: collapse; margin: 24px 0; background-color: #fcf0f1; border-left: 4px solid #d63638; border-radius: 0;">
1060 <tr>
1061 <td style="padding: 16px 20px;">
1062 <p style="margin: 0 0 12px; color: #8c1c13; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
1063 <?php echo esc_html( __( 'Error Details', 'insert-php' ) ); ?>
1064 </p>
1065
1066 <div style="margin-bottom: 12px;">
1067 <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1068 <?php echo esc_html( __( 'Error Message:', 'insert-php' ) ); ?>
1069 </p>
1070 <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;">
1071 <?php echo esc_html( $error_message ); ?>
1072 </p>
1073 </div>
1074
1075 <div style="margin-bottom: 12px;">
1076 <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1077 <?php echo esc_html( __( 'Location:', 'insert-php' ) ); ?>
1078 </p>
1079 <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;">
1080 <?php echo esc_html( $file_path ); ?> <span style="color: #646970;"><?php echo esc_html( __( 'line', 'insert-php' ) ); ?> <?php echo intval( $line_number ); ?></span>
1081 </p>
1082 </div>
1083
1084 <div>
1085 <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1086 <?php echo esc_html( __( 'Snippet:', 'insert-php' ) ); ?>
1087 </p>
1088 <p style="margin: 0; padding: 8px 12px; background-color: #ffffff; border: 1px solid #dcdcde; color: #1d2327; font-size: 13px; line-height: 1.5;">
1089 <?php echo esc_html( $snippet_title ); ?> <span style="color: #646970;">(ID: <?php echo intval( $snippet_id ); ?>)</span>
1090 </p>
1091 </div>
1092 </td>
1093 </tr>
1094 </table>
1095
1096 <!-- Action Buttons -->
1097 <table role="presentation" style="width: 100%; border-collapse: collapse; margin: 24px 0;">
1098 <tr>
1099 <td style="padding: 0;">
1100 <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;">
1101 <?php echo esc_html( __( 'Edit Snippet', 'insert-php' ) ); ?>
1102 </a>
1103 <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;">
1104 <?php echo esc_html( __( 'View All Snippets', 'insert-php' ) ); ?>
1105 </a>
1106 </td>
1107 </tr>
1108 </table>
1109
1110 <p style="margin: 20px 0 0; color: #646970; font-size: 13px; line-height: 1.6;">
1111 <?php echo esc_html( __( 'This notification is sent only once per unique error. You can disable these notifications in the plugin settings.', 'insert-php' ) ); ?>
1112 </p>
1113 </td>
1114 </tr>
1115
1116 <!-- Footer -->
1117 <tr>
1118 <td style="padding: 16px 30px; background-color: #f6f7f7; border-top: 1px solid #dcdcde;">
1119 <p style="margin: 0; color: #646970; font-size: 12px; text-align: center;">
1120 <?php
1121 printf(
1122 // translators: %s: site name.
1123 esc_html( __( 'This email was sent by Woody Code Snippets on %s', 'insert-php' ) ),
1124 '<strong>' . esc_html( $site_name ) . '</strong>'
1125 );
1126 ?>
1127 </p>
1128 </td>
1129 </tr>
1130 </table>
1131 </td>
1132 </tr>
1133 </table>
1134 </body>
1135 </html>
1136 <?php
1137 $output = ob_get_clean();
1138 return false !== $output ? $output : '';
1139 }
1140
1141 /**
1142 * Get property value
1143 *
1144 * @param $value
1145 * @param $property
1146 *
1147 * @return null
1148 */
1149 private function getPropertyValue( $value, $property ) {
1150 if ( is_object( $value ) ) {
1151 return $value->$property ?? null;
1152 } elseif ( isset( $value[ $property ] ) ) {
1153 return $value[ $property ];
1154 }
1155
1156 return null;
1157 }
1158
1159 /**
1160 * Check conditional execution logic for the snippet
1161 *
1162 * @param $snippet_id
1163 *
1164 * @return bool
1165 */
1166 public function checkCondition( $snippet_id ) {
1167 // Итоговый результат условий
1168 $result = true;
1169 // Получаем со�
1170 ранённые параметры условий
1171 $filters = get_post_meta( $snippet_id, 'wbcr_inp_snippet_filters' );
1172 // Если условия указаны
1173 if ( ! ( empty( $filters ) || isset( $filters[0] ) && empty( $filters[0] ) ) ) {
1174 foreach ( $filters[0] as $filter ) {
1175 $conditions = $this->getPropertyValue( $filter, 'conditions' );
1176 // Если условия пусты, то пропускаем цикл
1177 if ( empty( $conditions ) ) {
1178 continue;
1179 }
1180 // Промежуточный результат AND условий
1181 $and_conditions = null;
1182 // Про�
1183 одим по AND условиям
1184 foreach ( $conditions as $scope ) {
1185 $scope_conditions = $this->getPropertyValue( $scope, 'conditions' );
1186 // Если условия пусты, то пропускаем цикл
1187 if ( empty( $scope_conditions ) ) {
1188 continue;
1189 }
1190 // Промежуточный результат OR условий
1191 $or_conditions = null;
1192 // Про�
1193 одим по OR условиям
1194 foreach ( $scope_conditions as $condition ) {
1195 $method_name = str_replace( '-', '_', $this->getPropertyValue( $condition, 'param' ) );
1196 $operator = $this->getPropertyValue( $condition, 'operator' );
1197 $value = $this->getPropertyValue( $condition, 'value' );
1198 // Получаем результат OR условий
1199 $or_conditions = is_null( $or_conditions ) ? $this->call_method( $method_name, $operator, $value ) : $or_conditions || $this->call_method( $method_name, $operator, $value );
1200 }
1201 // Получаем результат AND условий
1202 $and_conditions = is_null( $and_conditions ) ? $or_conditions : $and_conditions && $or_conditions;
1203 }
1204 // Получаем результат блока условий
1205 $result = $this->getPropertyValue( $filter, 'type' ) == 'showif' ? $and_conditions : ! $and_conditions;
1206 }
1207 }
1208
1209 return $result;
1210 }
1211
1212 /**
1213 * Call specified method
1214 *
1215 * @param $method_name
1216 * @param $operator
1217 * @param $value
1218 *
1219 * @return bool
1220 */
1221 private function call_method( $method_name, $operator, $value ) {
1222 if ( method_exists( $this, $method_name ) ) {
1223 return $this->$method_name( $operator, $value );
1224 } else {
1225 return apply_filters( 'wbcr/inp/execute/check_condition', false, $method_name, $operator, $value );
1226 }
1227 }
1228
1229 /**
1230 * Retrieve the first error in a snippet's code
1231 *
1232 * @param int $snippet_id
1233 *
1234 * @return array|bool
1235 */
1236 public function getSnippetError( $snippet_id ) {
1237 if ( ! intval( $snippet_id ) ) {
1238 return false;
1239 }
1240
1241 $snippet = get_post( $snippet_id );
1242
1243 if ( ! $snippet ) {
1244 return false;
1245 }
1246
1247 $snippet_code = WINP_Helper::get_snippet_code( $snippet );
1248 $snippet_code = $this->prepareCode( $snippet_code, $snippet_id );
1249
1250 $result = $this->executeSnippet( $snippet_code, $snippet_id );
1251
1252 if ( false !== $result ) {
1253 return false;
1254 }
1255
1256 $error = error_get_last();
1257
1258 if ( is_null( $error ) ) {
1259 return false;
1260 }
1261
1262 return $error;
1263 }
1264
1265 /**
1266 * Prepare the code by removing php tags from beginning and end
1267 *
1268 * @param string $code
1269 * @param integer $snippet_id
1270 *
1271 * @return string
1272 */
1273 public function prepareCode( $code, $snippet_id ) {
1274 $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
1275
1276 if ( $snippet_type != WINP_SNIPPET_TYPE_UNIVERSAL
1277 && $snippet_type != WINP_SNIPPET_TYPE_CSS
1278 && $snippet_type != WINP_SNIPPET_TYPE_JS
1279 && $snippet_type != WINP_SNIPPET_TYPE_HTML ) {
1280
1281 /* Remove <?php and <? from beginning of snippet */
1282 $code = preg_replace( '|^[\s]*<\?(php)?|', '', $code );
1283
1284 /* Remove ?> from end of snippet */
1285 $code = preg_replace( '|\?>[\s]*$|', '', $code );
1286 }
1287
1288 return $code;
1289 }
1290
1291 /**
1292 * Get current URL
1293 *
1294 * @return string
1295 */
1296 private function getCurrentUrl() {
1297 $out = '';
1298 $url = explode( '?', $_SERVER['REQUEST_URI'], 2 );
1299 if ( isset( $url[0] ) ) {
1300 $out = trim( $url[0], '/' );
1301 }
1302
1303 return $out ? urldecode( $out ) : '/';
1304 }
1305
1306 /**
1307 * Get referer URL
1308 *
1309 * @return string
1310 */
1311 private function getRefererUrl() {
1312 $out = '';
1313 $url = explode( '?', str_replace( site_url(), '', $_SERVER['HTTP_REFERER'] ), 2 );
1314 if ( isset( $url[0] ) ) {
1315 $out = trim( $url[0], '/' );
1316 }
1317
1318 return $out ? urldecode( $out ) : '/';
1319 }
1320
1321 /**
1322 * Check by operator
1323 *
1324 * @param string $operation Comparison operator.
1325 * @param mixed $first First value.
1326 * @param mixed $second Second value.
1327 * @param bool $third Third value.
1328 *
1329 * @return bool
1330 */
1331 public function check_by_operator( $operation, $first, $second, $third = false ) {
1332 switch ( $operation ) {
1333 case 'equals':
1334 if ( is_array( $second ) ) {
1335 return in_array( $first, $second );
1336 } else {
1337 return $first === $second;
1338 }
1339 case 'notequal':
1340 if ( is_array( $second ) ) {
1341 return ! in_array( $first, $second );
1342 } else {
1343 return $first !== $second;
1344 }
1345 case 'less':
1346 case 'older':
1347 return $first > $second;
1348 case 'greater':
1349 case 'younger':
1350 return $first < $second;
1351 case 'contains':
1352 return strpos( $first, $second ) !== false;
1353 case 'notcontain':
1354 return strpos( $first, $second ) === false;
1355 case 'between':
1356 return $first < $second && $second < $third;
1357
1358 default:
1359 return $first === $second;
1360 }
1361 }
1362
1363 /**
1364 * A role of the user who views your website. The role "guest" is applied for unregistered users.
1365 *
1366 * @param string $operator
1367 * @param string $value
1368 *
1369 * @return boolean
1370 */
1371 private function user_role( $operator, $value ) {
1372 if ( ! is_user_logged_in() ) {
1373 return $this->check_by_operator( $operator, $value, 'guest' );
1374 } else {
1375 $current_user = wp_get_current_user();
1376 if ( ! ( $current_user instanceof WP_User ) ) {
1377 return false;
1378 }
1379
1380 return $this->check_by_operator( $operator, $value, $current_user->roles[0] );
1381 }
1382 }
1383
1384 /**
1385 * Get timestamp
1386 *
1387 * @param string $units Time units.
1388 * @param mixed $count Count of units.
1389 *
1390 * @return integer
1391 */
1392 private function get_timestamp( $units, $count ) {
1393 if ( ! is_numeric( $count ) ) {
1394 return 0;
1395 }
1396
1397 $count = (int) $count;
1398
1399 switch ( $units ) {
1400 case 'seconds':
1401 return $count;
1402 case 'minutes':
1403 return $count * MINUTE_IN_SECONDS;
1404 case 'hours':
1405 return $count * HOUR_IN_SECONDS;
1406 case 'days':
1407 return $count * DAY_IN_SECONDS;
1408 case 'weeks':
1409 return $count * WEEK_IN_SECONDS;
1410 case 'months':
1411 return $count * MONTH_IN_SECONDS;
1412 case 'years':
1413 return $count * YEAR_IN_SECONDS;
1414
1415 default:
1416 return $count;
1417 }
1418 }
1419
1420 /**
1421 * Get date timestamp
1422 *
1423 * @param mixed $value Date value.
1424 *
1425 * @return int|mixed Returns 0 on validation failure, timestamp calculation, or the original value
1426 */
1427 public function get_date_timestamp( $value ) {
1428 if ( is_object( $value ) ) {
1429 if ( ! isset( $value->units ) || ! isset( $value->unitsCount ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1430 return 0;
1431 }
1432 $current_timestamp = current_datetime()->getTimestamp();
1433 return ( $current_timestamp - $this->get_timestamp( $value->units, $value->unitsCount ) ) * 1000; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1434 } else {
1435 return $value;
1436 }
1437 }
1438
1439 /**
1440 * The date when the user who views your website was registered.
1441 * For unregistered users this date always equals to 1 Jan 1970.
1442 *
1443 * @param string $operator Comparison operator.
1444 * @param mixed $value Comparison value (object for 'between', mixed otherwise).
1445 *
1446 * @return boolean
1447 */
1448 private function user_registered( $operator, $value ) {
1449 if ( ! is_user_logged_in() ) {
1450 return false;
1451 } else {
1452 $user = wp_get_current_user();
1453 $registered = strtotime( $user->data->user_registered );
1454
1455 // Validate that we have a valid registration timestamp.
1456 if ( false === $registered ) {
1457 return false;
1458 }
1459
1460 $registered = $registered * 1000;
1461
1462 if ( 'equals' === $operator || 'notequal' === $operator ) {
1463 $registered = $registered / 1000;
1464 $timestamp = $this->get_date_timestamp( $value );
1465
1466 if ( ! $timestamp || $timestamp <= 0 ) {
1467 return false;
1468 }
1469
1470 $timestamp = round( $timestamp / 1000 );
1471
1472 return $this->check_by_operator( $operator, gmdate( 'Y-m-d', (int) $timestamp ), gmdate( 'Y-m-d', (int) $registered ) );
1473 } elseif ( 'between' === $operator ) {
1474 if ( ! is_object( $value ) || ! isset( $value->start ) || ! isset( $value->end ) ) {
1475 return false;
1476 }
1477 $start_timestamp = $this->get_date_timestamp( $value->start );
1478 $end_timestamp = $this->get_date_timestamp( $value->end );
1479
1480 if ( ! $start_timestamp || $start_timestamp <= 0 || ! $end_timestamp || $end_timestamp <= 0 ) {
1481 return false;
1482 }
1483
1484 return $this->check_by_operator( $operator, $start_timestamp, $registered, $end_timestamp );
1485 } else {
1486 $timestamp = $this->get_date_timestamp( $value );
1487
1488 if ( ! $timestamp || $timestamp <= 0 ) {
1489 return false;
1490 }
1491
1492 return $this->check_by_operator( $operator, $timestamp, $registered );
1493 }
1494 }
1495 }
1496
1497 /**
1498 * Check the user views your website from mobile device or not
1499 *
1500 * @param string $operator Comparison operator.
1501 * @param string $value Comparison value.
1502 *
1503 * @return boolean
1504 *
1505 * @link https://stackoverflow.com/a/4117597
1506 */
1507 private function user_mobile( $operator, $value ) {
1508 $useragent = $_SERVER['HTTP_USER_AGENT'];
1509
1510 if ( preg_match( '/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i', $useragent ) || preg_match( '/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', substr( $useragent, 0, 4 ) ) ) {
1511 return $operator === 'equals' && $value === 'yes' || $operator === 'notequal' && $value === 'no';
1512 } else {
1513 return $operator === 'notequal' && $value === 'yes' || $operator === 'equals' && $value === 'no';
1514 }
1515 }
1516
1517 /**
1518 * Determines whether the user's browser has a cookie with a given name
1519 *
1520 * @param $operator
1521 * @param $value
1522 *
1523 * @return boolean
1524 */
1525 private function user_cookie_name( $operator, $value ) {
1526 if ( isset( $_COOKIE[ $value ] ) ) {
1527 return $operator === 'equals';
1528 } else {
1529 return $operator === 'notequal';
1530 }
1531 }
1532
1533 /**
1534 * A some selected page
1535 *
1536 * @param $operator
1537 * @param $value
1538 *
1539 * @return boolean
1540 */
1541 private function location_some_page( $operator, $value ) {
1542 $post_id = ( ! is_404() && ! is_search() && ! is_archive() && ! is_home() ) ? get_the_ID() : false;
1543
1544 switch ( $value ) {
1545 case 'base_web': // Basic - Entire Website
1546 $result = true;
1547 break;
1548 case 'base_sing': // Basic - All Singulars
1549 $result = is_singular();
1550 break;
1551 case 'base_arch': // Basic - All Archives
1552 $result = is_archive();
1553 break;
1554 case 'spec_404': // Special Pages - 404 Page
1555 $result = is_404();
1556 break;
1557 case 'spec_search': // Special Pages - Search Page
1558 $result = is_search();
1559 break;
1560 case 'spec_blog': // Special Pages - Blog / Posts Page
1561 $result = is_home();
1562 break;
1563 case 'spec_front': // Special Pages - Front Page
1564 $result = is_front_page();
1565 break;
1566 case 'spec_date': // Special Pages - Date Archive
1567 $result = is_date();
1568 break;
1569 case 'spec_auth': // Special Pages - Author Archive
1570 $result = is_author();
1571 break;
1572 case 'post_all': // Posts - All Posts
1573 case 'page_all': // Pages - All Pages
1574 $result = false;
1575 if ( false !== $post_id ) {
1576 $post_type = 'post_all' == $value ? 'post' : 'page';
1577 $result = $post_type == get_post_type( $post_id );
1578 }
1579 break;
1580 case 'post_arch': // Posts - All Posts Archive
1581 case 'page_arch': // Pages - All Pages Archive
1582 $result = false;
1583 if ( is_archive() ) {
1584 $post_type = 'post_arch' == $value ? 'post' : 'page';
1585 $result = $post_type == get_post_type();
1586 }
1587 break;
1588 case 'post_cat': // Posts - All Categories Archive
1589 case 'post_tag': // Posts - All Tags Archive
1590 $result = false;
1591 if ( is_archive() && 'post' == get_post_type() ) {
1592 $taxonomy = 'post_tag' == $value ? 'post_tag' : 'category';
1593 $obj = get_queried_object();
1594
1595 $current_taxonomy = '';
1596 if ( '' !== $obj && null !== $obj ) {
1597 $current_taxonomy = $obj->taxonomy;
1598 }
1599
1600 if ( $current_taxonomy == $taxonomy ) {
1601 $result = true;
1602 }
1603 }
1604 break;
1605
1606 default:
1607 $result = false;
1608 }
1609
1610 if ( WINP_Helper::is_woo_active() ) {
1611 switch ( $value ) {
1612 case 'woo_product':
1613 $result = is_product();
1614 break;
1615 case 'woo_arch':
1616 $result = is_shop();
1617 break;
1618 case 'woo_cart':
1619 $result = is_cart();
1620 break;
1621 case 'woo_checkout':
1622 $result = is_checkout();
1623 break;
1624 case 'woo_checkout_pay':
1625 $result = is_checkout_pay_page();
1626 break;
1627 case 'woo_cat':
1628 $result = is_product_category();
1629 break;
1630 case 'woo_tag':
1631 $result = is_product_tag();
1632 break;
1633 }
1634 }
1635
1636 return $this->check_by_operator( $operator, $result, true );
1637 }
1638
1639 /**
1640 * An URL of the current page where a user who views your website is located
1641 *
1642 * @param $operator
1643 * @param $value
1644 *
1645 * @return boolean
1646 */
1647 private function location_page( $operator, $value ) {
1648 $url = $this->getCurrentUrl();
1649
1650 return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1651 }
1652
1653 /**
1654 * A referrer URL which has brought a user to the current page
1655 *
1656 * @param $operator
1657 * @param $value
1658 *
1659 * @return boolean
1660 */
1661 private function location_referrer( $operator, $value ) {
1662 $url = $this->getRefererUrl();
1663
1664 return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1665 }
1666
1667 /**
1668 * A post type of the current page
1669 *
1670 * @param $operator
1671 * @param $value
1672 *
1673 * @return boolean
1674 */
1675 private function location_post_type( $operator, $value ) {
1676 if ( is_singular() ) {
1677 return $this->check_by_operator( $operator, $value, get_post_type() );
1678 }
1679
1680 return false;
1681 }
1682
1683 /**
1684 * A taxonomy page
1685 *
1686 * @param $operator
1687 * @param $value
1688 *
1689 * @return boolean
1690 * @since 2.2.8 The bug is fixed, the condition was not checked
1691 * for tachonomies, only posts.
1692 */
1693 private function location_taxonomy( $operator, $value ) {
1694 $term_id = null;
1695
1696 if ( is_tax() || is_tag() || is_category() ) {
1697 $term_id = get_queried_object()->term_id;
1698
1699 if ( $term_id ) {
1700 return $this->check_by_operator( $operator, intval( $value ), $term_id );
1701 }
1702 }
1703
1704 return false;
1705 }
1706
1707 /**
1708 * A taxonomy of the current page
1709 *
1710 * @param $operator
1711 * @param $value
1712 *
1713 * @return boolean
1714 * @since 2.4.0
1715 */
1716 private function page_taxonomy( $operator, $value ) {
1717 $term_id = null;
1718
1719 if ( is_singular() ) {
1720 $post_cat = get_the_category( get_the_ID() );
1721 if ( is_array( $post_cat ) ) {
1722 foreach ( $post_cat as $item ) {
1723 $term_id[] = $item->term_id;
1724 }
1725 }
1726 }
1727
1728 if ( $term_id ) {
1729 return $this->check_by_operator( $operator, intval( $value ), $term_id );
1730 }
1731
1732 return false;
1733 }
1734 }
1735