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