PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / 2.7.7
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts v2.7.7
2.7.7 2.7.6 2.7.5 2.7.4 trunk 1.3 2.0.4 2.0.6 2.1.91 2.2.4 2.2.7 2.2.9 2.3.1 2.3.10 2.4.10 2.4.2 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.6.0 2.6.1 2.7.0 All 28 releases
insert-php / includes / class.execute.snippet.php

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

1,745 lines 55.4 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 $is_executable = in_array( $snippet_type, [ WINP_SNIPPET_TYPE_PHP, WINP_SNIPPET_TYPE_UNIVERSAL ], true );
795
796 if ( $is_executable ) {
797 // Preserve the active snippet context if execution terminates the request.
798 self::$current_snippet_id = $id;
799 WINP_Error_Handler::init();
800 WINP_Error_Handler::set_current_snippet( $id, $snippet->post_title );
801
802 // Register shutdown function once to catch fatal errors.
803 if ( ! self::$shutdown_registered ) {
804 register_shutdown_function( [ $this, 'handle_snippet_shutdown' ] );
805 self::$shutdown_registered = true;
806 }
807 }
808
809 if ( $snippet_type == WINP_SNIPPET_TYPE_UNIVERSAL ) {
810 $result = eval( '?>' . $code . '<?php ' );
811 } elseif ( $snippet_type == WINP_SNIPPET_TYPE_PHP ) {
812 $result = eval( $code );
813 } else {
814 $result = ! empty( $code );
815 }
816
817 if ( $is_executable ) {
818 self::$current_snippet_id = 0;
819 WINP_Error_Handler::clear_current_snippet();
820 }
821
822 if ( $catch_output ) {
823 ob_end_clean();
824 }
825
826 return $result;
827 }
828
829 /**
830 * Handle fatal errors during snippet execution.
831 *
832 * @return void
833 */
834 public function handle_snippet_shutdown() {
835 $error = error_get_last();
836
837 // If there's a fatal error and a snippet was being executed, check if it's from the snippet.
838 if ( self::$current_snippet_id && $error && in_array( $error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ] ) ) {
839 // Only log if the error originated from the snippet code itself.
840 if ( $this->is_error_from_snippet( $error ) ) {
841 $this->log_snippet_error( self::$current_snippet_id, $error );
842 }
843 }
844
845 // Clear the snippet ID after processing.
846 self::$current_snippet_id = 0;
847 }
848
849 /**
850 * Check if the error originated from the snippet code.
851 *
852 * @param array<string, mixed> $error Error details.
853 *
854 * @return bool True if error is from snippet, false otherwise.
855 */
856 private function is_error_from_snippet( $error ) {
857 if ( ! isset( $error['file'] ) ) {
858 return false;
859 }
860
861 $error_file = $error['file'];
862
863 // Check if error is from eval'd code (PHP snippets executed via eval).
864 if ( strpos( $error_file, "eval()'d code" ) !== false ) {
865 return true;
866 }
867
868 return false;
869 }
870
871 /**
872 * Log snippet error via admin notice
873 *
874 * @param int $snippet_id Snippet ID.
875 * @param array<string, mixed> $error Error details.
876 *
877 * @return void
878 */
879 private function log_snippet_error( $snippet_id, $error ) {
880 // Validate error array has required keys.
881 if ( ! isset( $error['message'], $error['file'], $error['line'] ) ) {
882 return;
883 }
884
885 // Generate unique notice ID based on snippet ID and error details.
886 $error_signature = md5( $snippet_id . $error['message'] . $error['file'] . $error['line'] );
887 $notice_id = 'snippet_error_' . $error_signature;
888
889 // Get snippet title for better context.
890 $snippet = get_post( $snippet_id );
891 $snippet_title = $snippet ? $snippet->post_title : "ID {$snippet_id}";
892
893 // Extract the main error message (before stack trace).
894 $error_message = $error['message'];
895 if ( strpos( $error_message, ' Stack trace:' ) !== false ) {
896 $error_message = substr( $error_message, 0, strpos( $error_message, ' Stack trace:' ) );
897 }
898 $error_message = trim( $error_message );
899
900 // Shorten file path for readability.
901 $file_path = str_replace( ABSPATH, '', $error['file'] );
902
903 // Get edit link for the snippet.
904 $edit_link = admin_url( 'post.php?post=' . $snippet_id . '&action=edit' );
905
906 // Build notice message.
907 $message = sprintf(
908 '<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>',
909 __( 'Snippet Error Detected', 'insert-php' ),
910 // Translators: 1: Snippet title.
911 sprintf( __( 'Snippet "%s" caused a fatal error.', 'insert-php' ), esc_html( $snippet_title ) ),
912 esc_url( $edit_link ),
913 __( 'Edit Snippet', 'insert-php' ),
914 __( 'Show error details', 'insert-php' ),
915 __( 'Error', 'insert-php' ),
916 esc_html( $error_message ),
917 __( 'Location', 'insert-php' ),
918 esc_html( $file_path ),
919 __( 'line', 'insert-php' ),
920 $error['line'],
921 __( 'If you have fixed this error, you can dismiss this notice.', 'insert-php' ),
922 __( 'Dismiss', 'insert-php' )
923 );
924
925 // Store error notice in option to be displayed on next admin page load.
926 $pending_notices = get_option( 'winp_pending_error_notices', [] );
927 if ( ! is_array( $pending_notices ) ) {
928 $pending_notices = [];
929 }
930
931 // Only add if not already present (avoid race conditions).
932 if ( ! isset( $pending_notices[ $notice_id ] ) ) {
933 $pending_notices[ $notice_id ] = [
934 'message' => $message,
935 'type' => 'error',
936 ];
937
938 update_option( 'winp_pending_error_notices', $pending_notices, false );
939
940 // Send email notification if enabled and this is a new error.
941 $this->send_error_email( $notice_id, $snippet_id, $snippet_title, $error_message, $file_path, $error['line'], $edit_link );
942 }
943 }
944
945 /**
946 * Send email notification for snippet error
947 *
948 * @param string $error_signature Unique error identifier.
949 * @param int $snippet_id The ID of the snippet.
950 * @param string $snippet_title The title of the snippet.
951 * @param string $error_message The error message.
952 * @param string $file_path The file path where error occurred.
953 * @param int $line_number The line number where error occurred.
954 * @param string $edit_link Link to edit the snippet.
955 *
956 * @return void
957 */
958 private function send_error_email( $error_signature, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link ) {
959 // Check if email notifications are enabled.
960 $email_enabled = get_option( 'wbcr_inp_error_email_enabled' );
961 if ( ! $email_enabled ) {
962 return;
963 }
964
965 // Get email address.
966 $email_address = get_option( 'wbcr_inp_error_email_address', get_option( 'admin_email' ) );
967 if ( empty( $email_address ) || ! is_email( $email_address ) ) {
968 return;
969 }
970
971 // Check if we've already emailed about this error.
972 $emailed_errors = get_option( 'winp_emailed_errors', [] );
973 if ( ! is_array( $emailed_errors ) ) {
974 $emailed_errors = [];
975 }
976
977 // If we've already emailed about this error, skip.
978 if ( isset( $emailed_errors[ $error_signature ] ) ) {
979 return;
980 }
981
982 // Clean up old entries (older than 30 days).
983 $thirty_days_ago = time() - ( 30 * DAY_IN_SECONDS );
984 foreach ( $emailed_errors as $hash => $timestamp ) {
985 if ( $timestamp < $thirty_days_ago ) {
986 unset( $emailed_errors[ $hash ] );
987 }
988 }
989
990 // Build email content.
991 $site_name = get_bloginfo( 'name' );
992 $admin_url = admin_url( 'edit.php?post_type=' . WINP_SNIPPETS_POST_TYPE );
993 $subject = sprintf( '[%s] Snippet Error Detected: %s', $site_name, $snippet_title );
994
995 $email_body = $this->get_error_email_template( $site_name, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link, $admin_url );
996
997 // Set email headers for HTML.
998 $headers = [
999 'Content-Type: text/html; charset=UTF-8',
1000 ];
1001
1002 // Send email.
1003 $sent = wp_mail( $email_address, $subject, $email_body, $headers );
1004
1005 // If email sent successfully, mark this error as emailed.
1006 if ( $sent ) {
1007 $emailed_errors[ $error_signature ] = time();
1008 update_option( 'winp_emailed_errors', $emailed_errors, false );
1009 }
1010 }
1011
1012 /**
1013 * Get HTML email template for error notification
1014 *
1015 * @param string $site_name Site name.
1016 * @param int $snippet_id Snippet ID.
1017 * @param string $snippet_title Snippet title.
1018 * @param string $error_message Error message.
1019 * @param string $file_path File path.
1020 * @param int $line_number Line number.
1021 * @param string $edit_link Edit link.
1022 * @param string $admin_url Admin URL.
1023 *
1024 * @return string HTML email template.
1025 */
1026 private function get_error_email_template( $site_name, $snippet_id, $snippet_title, $error_message, $file_path, $line_number, $edit_link, $admin_url ) {
1027 ob_start();
1028 ?>
1029 <!DOCTYPE html>
1030 <html>
1031 <head>
1032 <meta charset="UTF-8">
1033 <meta name="viewport" content="width=device-width, initial-scale=1.0">
1034 <title><?php echo esc_html( __( 'Snippet Error Notification', 'insert-php' ) ); ?></title>
1035 </head>
1036 <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;">
1037 <table role="presentation" style="width: 100%; border-collapse: collapse;">
1038 <tr>
1039 <td align="center" style="padding: 40px 20px;">
1040 <table role="presentation" style="width: 100%; max-width: 600px; border-collapse: collapse; background-color: #ffffff; border: 1px solid #c3c4c7; border-radius: 2px;">
1041 <!-- Header -->
1042 <tr>
1043 <td style="padding: 24px 30px; background-color: #2271b1; border-bottom: 1px solid #135e96;">
1044 <h1 style="margin: 0; color: #ffffff; font-size: 20px; font-weight: 600;">
1045 ⚠️ <?php echo esc_html( __( 'Snippet Error Detected', 'insert-php' ) ); ?>
1046 </h1>
1047 </td>
1048 </tr>
1049
1050 <!-- Content -->
1051 <tr>
1052 <td style="padding: 30px;">
1053 <p style="margin: 0 0 16px; color: #1d2327; font-size: 14px; line-height: 1.6;">
1054 <?php echo esc_html( __( 'Hello,', 'insert-php' ) ); ?>
1055 </p>
1056
1057 <p style="margin: 0 0 20px; color: #1d2327; font-size: 14px; line-height: 1.6;">
1058 <?php
1059 printf(
1060 // translators: 1: snippet title, 2: site name.
1061 esc_html( __( 'A fatal error has been detected in the snippet "%1$s" on your site %2$s.', 'insert-php' ) ),
1062 '<strong>' . esc_html( $snippet_title ) . '</strong>',
1063 '<strong>' . esc_html( $site_name ) . '</strong>'
1064 );
1065 ?>
1066 </p>
1067
1068 <!-- Error Details Box -->
1069 <table role="presentation" style="width: 100%; border-collapse: collapse; margin: 24px 0; background-color: #fcf0f1; border-left: 4px solid #d63638; border-radius: 0;">
1070 <tr>
1071 <td style="padding: 16px 20px;">
1072 <p style="margin: 0 0 12px; color: #8c1c13; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
1073 <?php echo esc_html( __( 'Error Details', 'insert-php' ) ); ?>
1074 </p>
1075
1076 <div style="margin-bottom: 12px;">
1077 <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1078 <?php echo esc_html( __( 'Error Message:', 'insert-php' ) ); ?>
1079 </p>
1080 <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;">
1081 <?php echo esc_html( $error_message ); ?>
1082 </p>
1083 </div>
1084
1085 <div style="margin-bottom: 12px;">
1086 <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1087 <?php echo esc_html( __( 'Location:', 'insert-php' ) ); ?>
1088 </p>
1089 <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;">
1090 <?php echo esc_html( $file_path ); ?> <span style="color: #646970;"><?php echo esc_html( __( 'line', 'insert-php' ) ); ?> <?php echo intval( $line_number ); ?></span>
1091 </p>
1092 </div>
1093
1094 <div>
1095 <p style="margin: 0 0 6px; color: #3c434a; font-size: 13px; font-weight: 600;">
1096 <?php echo esc_html( __( 'Snippet:', 'insert-php' ) ); ?>
1097 </p>
1098 <p style="margin: 0; padding: 8px 12px; background-color: #ffffff; border: 1px solid #dcdcde; color: #1d2327; font-size: 13px; line-height: 1.5;">
1099 <?php echo esc_html( $snippet_title ); ?> <span style="color: #646970;">(ID: <?php echo intval( $snippet_id ); ?>)</span>
1100 </p>
1101 </div>
1102 </td>
1103 </tr>
1104 </table>
1105
1106 <!-- Action Buttons -->
1107 <table role="presentation" style="width: 100%; border-collapse: collapse; margin: 24px 0;">
1108 <tr>
1109 <td style="padding: 0;">
1110 <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;">
1111 <?php echo esc_html( __( 'Edit Snippet', 'insert-php' ) ); ?>
1112 </a>
1113 <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;">
1114 <?php echo esc_html( __( 'View All Snippets', 'insert-php' ) ); ?>
1115 </a>
1116 </td>
1117 </tr>
1118 </table>
1119
1120 <p style="margin: 20px 0 0; color: #646970; font-size: 13px; line-height: 1.6;">
1121 <?php echo esc_html( __( 'This notification is sent only once per unique error. You can disable these notifications in the plugin settings.', 'insert-php' ) ); ?>
1122 </p>
1123 </td>
1124 </tr>
1125
1126 <!-- Footer -->
1127 <tr>
1128 <td style="padding: 16px 30px; background-color: #f6f7f7; border-top: 1px solid #dcdcde;">
1129 <p style="margin: 0; color: #646970; font-size: 12px; text-align: center;">
1130 <?php
1131 printf(
1132 // translators: %s: site name.
1133 esc_html( __( 'This email was sent by Woody Code Snippets on %s', 'insert-php' ) ),
1134 '<strong>' . esc_html( $site_name ) . '</strong>'
1135 );
1136 ?>
1137 </p>
1138 </td>
1139 </tr>
1140 </table>
1141 </td>
1142 </tr>
1143 </table>
1144 </body>
1145 </html>
1146 <?php
1147 $output = ob_get_clean();
1148 return false !== $output ? $output : '';
1149 }
1150
1151 /**
1152 * Get property value
1153 *
1154 * @param $value
1155 * @param $property
1156 *
1157 * @return null
1158 */
1159 private function getPropertyValue( $value, $property ) {
1160 if ( is_object( $value ) ) {
1161 return $value->$property ?? null;
1162 } elseif ( isset( $value[ $property ] ) ) {
1163 return $value[ $property ];
1164 }
1165
1166 return null;
1167 }
1168
1169 /**
1170 * Check conditional execution logic for the snippet
1171 *
1172 * @param $snippet_id
1173 *
1174 * @return bool
1175 */
1176 public function checkCondition( $snippet_id ) {
1177 // Итоговый результат условий
1178 $result = true;
1179 // Получаем со�
1180 ранённые параметры условий
1181 $filters = get_post_meta( $snippet_id, 'wbcr_inp_snippet_filters' );
1182 // Если условия указаны
1183 if ( ! ( empty( $filters ) || isset( $filters[0] ) && empty( $filters[0] ) ) ) {
1184 foreach ( $filters[0] as $filter ) {
1185 $conditions = $this->getPropertyValue( $filter, 'conditions' );
1186 // Если условия пусты, то пропускаем цикл
1187 if ( empty( $conditions ) ) {
1188 continue;
1189 }
1190 // Промежуточный результат AND условий
1191 $and_conditions = null;
1192 // Про�
1193 одим по AND условиям
1194 foreach ( $conditions as $scope ) {
1195 $scope_conditions = $this->getPropertyValue( $scope, 'conditions' );
1196 // Если условия пусты, то пропускаем цикл
1197 if ( empty( $scope_conditions ) ) {
1198 continue;
1199 }
1200 // Промежуточный результат OR условий
1201 $or_conditions = null;
1202 // Про�
1203 одим по OR условиям
1204 foreach ( $scope_conditions as $condition ) {
1205 $method_name = str_replace( '-', '_', $this->getPropertyValue( $condition, 'param' ) );
1206 $operator = $this->getPropertyValue( $condition, 'operator' );
1207 $value = $this->getPropertyValue( $condition, 'value' );
1208 // Получаем результат OR условий
1209 $or_conditions = is_null( $or_conditions ) ? $this->call_method( $method_name, $operator, $value ) : $or_conditions || $this->call_method( $method_name, $operator, $value );
1210 }
1211 // Получаем результат AND условий
1212 $and_conditions = is_null( $and_conditions ) ? $or_conditions : $and_conditions && $or_conditions;
1213 }
1214 // Получаем результат блока условий
1215 $result = $this->getPropertyValue( $filter, 'type' ) == 'showif' ? $and_conditions : ! $and_conditions;
1216 }
1217 }
1218
1219 return $result;
1220 }
1221
1222 /**
1223 * Call specified method
1224 *
1225 * @param $method_name
1226 * @param $operator
1227 * @param $value
1228 *
1229 * @return bool
1230 */
1231 private function call_method( $method_name, $operator, $value ) {
1232 if ( method_exists( $this, $method_name ) ) {
1233 return $this->$method_name( $operator, $value );
1234 } else {
1235 return apply_filters( 'wbcr/inp/execute/check_condition', false, $method_name, $operator, $value );
1236 }
1237 }
1238
1239 /**
1240 * Retrieve the first error in a snippet's code
1241 *
1242 * @param int $snippet_id
1243 *
1244 * @return array|bool
1245 */
1246 public function getSnippetError( $snippet_id ) {
1247 if ( ! intval( $snippet_id ) ) {
1248 return false;
1249 }
1250
1251 $snippet = get_post( $snippet_id );
1252
1253 if ( ! $snippet ) {
1254 return false;
1255 }
1256
1257 $snippet_code = WINP_Helper::get_snippet_code( $snippet );
1258 $snippet_code = $this->prepareCode( $snippet_code, $snippet_id );
1259
1260 $result = $this->executeSnippet( $snippet_code, $snippet_id );
1261
1262 if ( false !== $result ) {
1263 return false;
1264 }
1265
1266 $error = error_get_last();
1267
1268 if ( is_null( $error ) ) {
1269 return false;
1270 }
1271
1272 return $error;
1273 }
1274
1275 /**
1276 * Prepare the code by removing php tags from beginning and end
1277 *
1278 * @param string $code
1279 * @param integer $snippet_id
1280 *
1281 * @return string
1282 */
1283 public function prepareCode( $code, $snippet_id ) {
1284 $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
1285
1286 if ( $snippet_type != WINP_SNIPPET_TYPE_UNIVERSAL
1287 && $snippet_type != WINP_SNIPPET_TYPE_CSS
1288 && $snippet_type != WINP_SNIPPET_TYPE_JS
1289 && $snippet_type != WINP_SNIPPET_TYPE_HTML ) {
1290
1291 /* Remove <?php and <? from beginning of snippet */
1292 $code = preg_replace( '|^[\s]*<\?(php)?|', '', $code );
1293
1294 /* Remove ?> from end of snippet */
1295 $code = preg_replace( '|\?>[\s]*$|', '', $code );
1296 }
1297
1298 return $code;
1299 }
1300
1301 /**
1302 * Get current URL
1303 *
1304 * @return string
1305 */
1306 private function getCurrentUrl() {
1307 $out = '';
1308 $url = explode( '?', $_SERVER['REQUEST_URI'], 2 );
1309 if ( isset( $url[0] ) ) {
1310 $out = trim( $url[0], '/' );
1311 }
1312
1313 return $out ? urldecode( $out ) : '/';
1314 }
1315
1316 /**
1317 * Get referer URL
1318 *
1319 * @return string
1320 */
1321 private function getRefererUrl() {
1322 $out = '';
1323 $url = explode( '?', str_replace( site_url(), '', $_SERVER['HTTP_REFERER'] ), 2 );
1324 if ( isset( $url[0] ) ) {
1325 $out = trim( $url[0], '/' );
1326 }
1327
1328 return $out ? urldecode( $out ) : '/';
1329 }
1330
1331 /**
1332 * Check by operator
1333 *
1334 * @param string $operation Comparison operator.
1335 * @param mixed $first First value.
1336 * @param mixed $second Second value.
1337 * @param bool $third Third value.
1338 *
1339 * @return bool
1340 */
1341 public function check_by_operator( $operation, $first, $second, $third = false ) {
1342 switch ( $operation ) {
1343 case 'equals':
1344 if ( is_array( $second ) ) {
1345 return in_array( $first, $second );
1346 } else {
1347 return $first === $second;
1348 }
1349 case 'notequal':
1350 if ( is_array( $second ) ) {
1351 return ! in_array( $first, $second );
1352 } else {
1353 return $first !== $second;
1354 }
1355 case 'less':
1356 case 'older':
1357 return $first > $second;
1358 case 'greater':
1359 case 'younger':
1360 return $first < $second;
1361 case 'contains':
1362 return strpos( $first, $second ) !== false;
1363 case 'notcontain':
1364 return strpos( $first, $second ) === false;
1365 case 'between':
1366 return $first < $second && $second < $third;
1367
1368 default:
1369 return $first === $second;
1370 }
1371 }
1372
1373 /**
1374 * A role of the user who views your website. The role "guest" is applied for unregistered users.
1375 *
1376 * @param string $operator
1377 * @param string $value
1378 *
1379 * @return boolean
1380 */
1381 private function user_role( $operator, $value ) {
1382 if ( ! is_user_logged_in() ) {
1383 return $this->check_by_operator( $operator, $value, 'guest' );
1384 } else {
1385 $current_user = wp_get_current_user();
1386 if ( ! ( $current_user instanceof WP_User ) ) {
1387 return false;
1388 }
1389
1390 return $this->check_by_operator( $operator, $value, $current_user->roles[0] );
1391 }
1392 }
1393
1394 /**
1395 * Get timestamp
1396 *
1397 * @param string $units Time units.
1398 * @param mixed $count Count of units.
1399 *
1400 * @return integer
1401 */
1402 private function get_timestamp( $units, $count ) {
1403 if ( ! is_numeric( $count ) ) {
1404 return 0;
1405 }
1406
1407 $count = (int) $count;
1408
1409 switch ( $units ) {
1410 case 'seconds':
1411 return $count;
1412 case 'minutes':
1413 return $count * MINUTE_IN_SECONDS;
1414 case 'hours':
1415 return $count * HOUR_IN_SECONDS;
1416 case 'days':
1417 return $count * DAY_IN_SECONDS;
1418 case 'weeks':
1419 return $count * WEEK_IN_SECONDS;
1420 case 'months':
1421 return $count * MONTH_IN_SECONDS;
1422 case 'years':
1423 return $count * YEAR_IN_SECONDS;
1424
1425 default:
1426 return $count;
1427 }
1428 }
1429
1430 /**
1431 * Get date timestamp
1432 *
1433 * @param mixed $value Date value.
1434 *
1435 * @return int|mixed Returns 0 on validation failure, timestamp calculation, or the original value
1436 */
1437 public function get_date_timestamp( $value ) {
1438 if ( is_object( $value ) ) {
1439 if ( ! isset( $value->units ) || ! isset( $value->unitsCount ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1440 return 0;
1441 }
1442 $current_timestamp = current_datetime()->getTimestamp();
1443 return ( $current_timestamp - $this->get_timestamp( $value->units, $value->unitsCount ) ) * 1000; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1444 } else {
1445 return $value;
1446 }
1447 }
1448
1449 /**
1450 * The date when the user who views your website was registered.
1451 * For unregistered users this date always equals to 1 Jan 1970.
1452 *
1453 * @param string $operator Comparison operator.
1454 * @param mixed $value Comparison value (object for 'between', mixed otherwise).
1455 *
1456 * @return boolean
1457 */
1458 private function user_registered( $operator, $value ) {
1459 if ( ! is_user_logged_in() ) {
1460 return false;
1461 } else {
1462 $user = wp_get_current_user();
1463 $registered = strtotime( $user->data->user_registered );
1464
1465 // Validate that we have a valid registration timestamp.
1466 if ( false === $registered ) {
1467 return false;
1468 }
1469
1470 $registered = $registered * 1000;
1471
1472 if ( 'equals' === $operator || 'notequal' === $operator ) {
1473 $registered = $registered / 1000;
1474 $timestamp = $this->get_date_timestamp( $value );
1475
1476 if ( ! $timestamp || $timestamp <= 0 ) {
1477 return false;
1478 }
1479
1480 $timestamp = round( $timestamp / 1000 );
1481
1482 return $this->check_by_operator( $operator, gmdate( 'Y-m-d', (int) $timestamp ), gmdate( 'Y-m-d', (int) $registered ) );
1483 } elseif ( 'between' === $operator ) {
1484 if ( ! is_object( $value ) || ! isset( $value->start ) || ! isset( $value->end ) ) {
1485 return false;
1486 }
1487 $start_timestamp = $this->get_date_timestamp( $value->start );
1488 $end_timestamp = $this->get_date_timestamp( $value->end );
1489
1490 if ( ! $start_timestamp || $start_timestamp <= 0 || ! $end_timestamp || $end_timestamp <= 0 ) {
1491 return false;
1492 }
1493
1494 return $this->check_by_operator( $operator, $start_timestamp, $registered, $end_timestamp );
1495 } else {
1496 $timestamp = $this->get_date_timestamp( $value );
1497
1498 if ( ! $timestamp || $timestamp <= 0 ) {
1499 return false;
1500 }
1501
1502 return $this->check_by_operator( $operator, $timestamp, $registered );
1503 }
1504 }
1505 }
1506
1507 /**
1508 * Check the user views your website from mobile device or not
1509 *
1510 * @param string $operator Comparison operator.
1511 * @param string $value Comparison value.
1512 *
1513 * @return boolean
1514 *
1515 * @link https://stackoverflow.com/a/4117597
1516 */
1517 private function user_mobile( $operator, $value ) {
1518 $useragent = $_SERVER['HTTP_USER_AGENT'];
1519
1520 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 ) ) ) {
1521 return $operator === 'equals' && $value === 'yes' || $operator === 'notequal' && $value === 'no';
1522 } else {
1523 return $operator === 'notequal' && $value === 'yes' || $operator === 'equals' && $value === 'no';
1524 }
1525 }
1526
1527 /**
1528 * Determines whether the user's browser has a cookie with a given name
1529 *
1530 * @param $operator
1531 * @param $value
1532 *
1533 * @return boolean
1534 */
1535 private function user_cookie_name( $operator, $value ) {
1536 if ( isset( $_COOKIE[ $value ] ) ) {
1537 return $operator === 'equals';
1538 } else {
1539 return $operator === 'notequal';
1540 }
1541 }
1542
1543 /**
1544 * A some selected page
1545 *
1546 * @param $operator
1547 * @param $value
1548 *
1549 * @return boolean
1550 */
1551 private function location_some_page( $operator, $value ) {
1552 $post_id = ( ! is_404() && ! is_search() && ! is_archive() && ! is_home() ) ? get_the_ID() : false;
1553
1554 switch ( $value ) {
1555 case 'base_web': // Basic - Entire Website
1556 $result = true;
1557 break;
1558 case 'base_sing': // Basic - All Singulars
1559 $result = is_singular();
1560 break;
1561 case 'base_arch': // Basic - All Archives
1562 $result = is_archive();
1563 break;
1564 case 'spec_404': // Special Pages - 404 Page
1565 $result = is_404();
1566 break;
1567 case 'spec_search': // Special Pages - Search Page
1568 $result = is_search();
1569 break;
1570 case 'spec_blog': // Special Pages - Blog / Posts Page
1571 $result = is_home();
1572 break;
1573 case 'spec_front': // Special Pages - Front Page
1574 $result = is_front_page();
1575 break;
1576 case 'spec_date': // Special Pages - Date Archive
1577 $result = is_date();
1578 break;
1579 case 'spec_auth': // Special Pages - Author Archive
1580 $result = is_author();
1581 break;
1582 case 'post_all': // Posts - All Posts
1583 case 'page_all': // Pages - All Pages
1584 $result = false;
1585 if ( false !== $post_id ) {
1586 $post_type = 'post_all' == $value ? 'post' : 'page';
1587 $result = $post_type == get_post_type( $post_id );
1588 }
1589 break;
1590 case 'post_arch': // Posts - All Posts Archive
1591 case 'page_arch': // Pages - All Pages Archive
1592 $result = false;
1593 if ( is_archive() ) {
1594 $post_type = 'post_arch' == $value ? 'post' : 'page';
1595 $result = $post_type == get_post_type();
1596 }
1597 break;
1598 case 'post_cat': // Posts - All Categories Archive
1599 case 'post_tag': // Posts - All Tags Archive
1600 $result = false;
1601 if ( is_archive() && 'post' == get_post_type() ) {
1602 $taxonomy = 'post_tag' == $value ? 'post_tag' : 'category';
1603 $obj = get_queried_object();
1604
1605 $current_taxonomy = '';
1606 if ( '' !== $obj && null !== $obj ) {
1607 $current_taxonomy = $obj->taxonomy;
1608 }
1609
1610 if ( $current_taxonomy == $taxonomy ) {
1611 $result = true;
1612 }
1613 }
1614 break;
1615
1616 default:
1617 $result = false;
1618 }
1619
1620 if ( WINP_Helper::is_woo_active() ) {
1621 switch ( $value ) {
1622 case 'woo_product':
1623 $result = is_product();
1624 break;
1625 case 'woo_arch':
1626 $result = is_shop();
1627 break;
1628 case 'woo_cart':
1629 $result = is_cart();
1630 break;
1631 case 'woo_checkout':
1632 $result = is_checkout();
1633 break;
1634 case 'woo_checkout_pay':
1635 $result = is_checkout_pay_page();
1636 break;
1637 case 'woo_cat':
1638 $result = is_product_category();
1639 break;
1640 case 'woo_tag':
1641 $result = is_product_tag();
1642 break;
1643 }
1644 }
1645
1646 return $this->check_by_operator( $operator, $result, true );
1647 }
1648
1649 /**
1650 * An URL of the current page where a user who views your website is located
1651 *
1652 * @param $operator
1653 * @param $value
1654 *
1655 * @return boolean
1656 */
1657 private function location_page( $operator, $value ) {
1658 $url = $this->getCurrentUrl();
1659
1660 return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1661 }
1662
1663 /**
1664 * A referrer URL which has brought a user to the current page
1665 *
1666 * @param $operator
1667 * @param $value
1668 *
1669 * @return boolean
1670 */
1671 private function location_referrer( $operator, $value ) {
1672 $url = $this->getRefererUrl();
1673
1674 return $url ? $this->check_by_operator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1675 }
1676
1677 /**
1678 * A post type of the current page
1679 *
1680 * @param $operator
1681 * @param $value
1682 *
1683 * @return boolean
1684 */
1685 private function location_post_type( $operator, $value ) {
1686 if ( is_singular() ) {
1687 return $this->check_by_operator( $operator, $value, get_post_type() );
1688 }
1689
1690 return false;
1691 }
1692
1693 /**
1694 * A taxonomy page
1695 *
1696 * @param $operator
1697 * @param $value
1698 *
1699 * @return boolean
1700 * @since 2.2.8 The bug is fixed, the condition was not checked
1701 * for tachonomies, only posts.
1702 */
1703 private function location_taxonomy( $operator, $value ) {
1704 $term_id = null;
1705
1706 if ( is_tax() || is_tag() || is_category() ) {
1707 $term_id = get_queried_object()->term_id;
1708
1709 if ( $term_id ) {
1710 return $this->check_by_operator( $operator, intval( $value ), $term_id );
1711 }
1712 }
1713
1714 return false;
1715 }
1716
1717 /**
1718 * A taxonomy of the current page
1719 *
1720 * @param $operator
1721 * @param $value
1722 *
1723 * @return boolean
1724 * @since 2.4.0
1725 */
1726 private function page_taxonomy( $operator, $value ) {
1727 $term_id = null;
1728
1729 if ( is_singular() ) {
1730 $post_cat = get_the_category( get_the_ID() );
1731 if ( is_array( $post_cat ) ) {
1732 foreach ( $post_cat as $item ) {
1733 $term_id[] = $item->term_id;
1734 }
1735 }
1736 }
1737
1738 if ( $term_id ) {
1739 return $this->check_by_operator( $operator, intval( $value ), $term_id );
1740 }
1741
1742 return false;
1743 }
1744 }
1745