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 -1225 2.4.6trunk View file →
@@ -1,1225 +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 - * Execute the everywhere snippets once the plugins are loaded
102 - */
103 - public function executeEverywhereSnippets() {
104 - echo $this->executeActiveSnippets( 'evrywhere' );
105 - }
106 -
107 - /**
108 - * Execute the snippets in header of page once the plugins are loaded
109 - */
110 - public function executeHeaderSnippets() {
111 - echo $this->executeActiveSnippets( 'auto', 'header' );
112 - }
113 -
114 - /**
115 - * Execute the snippets in footer of page once the plugins are loaded
116 - */
117 - public function executeFooterSnippets() {
118 - echo $this->executeActiveSnippets( 'auto', 'footer' );
119 - }
120 -
121 - /**
122 - * Execute the snippets before post
123 - *
124 - * @param $data
125 - * @param $query
126 - */
127 - public function executePostSnippets( $data, $query ) {
128 - global $post;
129 -
130 - $content = '';
131 -
132 - $post_type = ! empty( $post ) ? $post->post_type : get_post( $data->ID )->post_type;
133 - if ( is_singular( [ $post_type ] ) && $post->ID == $data->ID ) {
134 - if ( did_action( 'get_header' ) ) {
135 - // Перед заголовком
136 - $content = $this->executeActiveSnippets( 'auto', 'before_post' );
137 - }
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 - }
170 - else {
171 - preg_match_all( "/<\/p>/", $content, $matches );
172 - }
173 - $paragraphs = $matches[0];
174 -
175 - if ( $paragraph_number == 0 ) {
176 - $paragraph_number = 1;
177 - }
178 -
179 - if ( $content && $snippet_content && $paragraphs && $paragraph_number <= count( $paragraphs ) ) {
180 - $offset = 0;
181 - foreach ( $paragraphs as $paragraph_key => $paragraph ) {
182 - $position = strpos( $content, $paragraph, $offset ); // Позиция тега параграфа
183 - // Если указанный номер параграфа совпадает с текущим
184 - if ( $paragraph_key + 1 == $paragraph_number ) {
185 - if ( 'before' == $type ) {
186 - $content = substr( $content, 0, $position ) . $snippet_content . substr( $content, $position );
187 - }
188 - else {
189 - $content = substr( $content, 0, $position + 4 ) . $snippet_content . substr( $content, $position + 4 );
190 - }
191 - break;
192 - }
193 - else {
194 - $offset = $position + 1;
195 - }
196 - }
197 - }
198 -
199 - return $content;
200 - }
201 -
202 - /**
203 - * Handle posts content
204 - *
205 - * @param string $content
206 - * @param string $snippet_content
207 - * @param integer $post_number
208 - * @param string $type
209 - * @param object $query
210 - *
211 - * @return mixed
212 - */
213 - private function handlePostsContent( $content, $snippet_content, $post_number, $type, $query ) {
214 - global $winp_after_post_content;
215 - if ( $query->post_count > 0 ) {
216 - if ( $post_number == 0 ) {
217 - $post_number = 1;
218 - }
219 -
220 - if ( 'before' == $type && $query->current_post + 1 == $post_number ) {
221 - return $snippet_content;
222 - }
223 - else if ( 'after' == $type ) {
224 - // Номер поста совпадает
225 - if ( $query->current_post == $post_number ) {
226 - return $snippet_content;
227 - // Если это последний пост и указанный номер поста больше общего количества постов,
228 - // то нужно сохранить контент сниппета для вывода в конце данного поста
229 - }
230 - else if ( $query->current_post + 1 == $query->post_count && $post_number >= $query->post_count ) {
231 - $winp_after_post_content[ $query->post->ID ] = $snippet_content;
232 - }
233 - }
234 - }
235 -
236 - return $content;
237 - }
238 -
239 - /**
240 - * Execute the snippets page content
241 - *
242 - * @param $content
243 - *
244 - * @return mixed
245 - */
246 - public function executeContentSnippets( $content ) {
247 - global $post, $winp_after_post_content;
248 -
249 - $post_type = ! empty( $post ) ? $post->post_type : false;
250 -
251 - if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
252 - // Перед коротким описанием
253 - $content = $this->executeActiveSnippets( 'auto', 'before_excerpt' ) . $content;
254 -
255 - // После короткого описания
256 - $content .= $this->executeActiveSnippets( 'auto', 'after_excerpt' );
257 - }
258 -
259 - if ( is_singular( [ $post_type ] ) ) {
260 - // Перед параграфом
261 - $content = $this->executeActiveSnippets( 'auto', 'before_paragraph', $content );
262 -
263 - // После параграфа
264 - $content = $this->executeActiveSnippets( 'auto', 'after_paragraph', $content );
265 -
266 - // После заголовка
267 - $content = $this->executeActiveSnippets( 'auto', 'before_content' ) . $content;
268 -
269 - // После текста
270 - $content .= $this->executeActiveSnippets( 'auto', 'after_content' );
271 -
272 - // После поста
273 - $content .= $this->executeActiveSnippets( 'auto', 'after_post' );
274 -
275 - if ( ! comments_open( $post->ID ) && ! get_comments_number( $post->ID ) ) {
276 - remove_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
277 - }
278 - }
279 - else if ( ! is_null( $post ) && isset( $winp_after_post_content[ $post->ID ] ) ) {
280 - // После последнего поста в списке
281 - $content .= $winp_after_post_content[ $post->ID ];
282 - unset( $winp_after_post_content[ $post->ID ] );
283 - }
284 -
285 - return $content;
286 - }
287 -
288 - /**
289 - * Execute the snippets page excerpt
290 - *
291 - * @param $excerpt
292 - *
293 - * @return mixed
294 - */
295 - public function executeExcerptSnippets( $excerpt ) {
296 - if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
297 - // Перед коротким описанием
298 - $excerpt = $this->executeActiveSnippets( 'auto', 'before_excerpt' ) . $excerpt;
299 -
300 - // После короткого описания
301 - $excerpt .= $this->executeActiveSnippets( 'auto', 'after_excerpt' );
302 - }
303 -
304 - return $excerpt;
305 - }
306 -
307 - /**
308 - * Execute the list comments filter
309 - *
310 - * @param $args
311 - *
312 - * @return mixed
313 - */
314 - public function executeListCommentsSnippets( $args ) {
315 - global $winp_wp_data;
316 -
317 - $winp_wp_data['winp_comments_saved_end_callback'] = $args['end-callback'];
318 - $args['end-callback'] = [ $this, 'executeCommentsSnippets' ];
319 -
320 - return $args;
321 - }
322 -
323 - /**
324 - * Execute the snippets after page comments
325 - *
326 - * @param $comment
327 - * @param $args
328 - * @param $depth
329 - */
330 - public function executeCommentsSnippets( $comment, $args, $depth ) {
331 - global $winp_wp_data, $post;
332 -
333 - if ( ! empty ( $winp_wp_data['winp_comments_saved_end_callback'] ) ) {
334 - echo call_user_func( $winp_wp_data['winp_comments_saved_end_callback'], $comment, $args, $depth );
335 - }
336 -
337 - $content = '';
338 -
339 - $post_type = ! empty( $post ) ? $post->post_type : false;
340 - if ( is_singular( [ $post_type ] ) ) {
341 - // После комментариев
342 - $content = $this->executeActiveSnippets( 'auto', 'after_post' );
343 - }
344 -
345 - echo $content;
346 - }
347 -
348 - /**
349 - * Execute the custom snippets
350 - *
351 - * @since 2.4
352 - */
353 - public function executeCustomSnippets() {
354 - $locations = $this->snippets_locations->getInsertion( 'custom' );
355 - foreach ( $locations as $location => $data ) {
356 - $this->executeActiveSnippets( 'auto', $location );
357 - }
358 - }
359 -
360 - /**
361 - * Execute Woocommerce actions/hooks
362 - *
363 - * @param $location
364 - * @param $snippet_content
365 - *
366 - * @since 2.4
367 - */
368 - public function woocommerce_actions( $location, $snippet_content = '' ) {
369 - $action = function () use ( $location, $snippet_content ) {
370 - echo $snippet_content;
371 - };
372 -
373 - switch ( $location ) {
374 - case 'woo_before_shop_loop':
375 - add_filter( 'woocommerce_product_loop_start', function ( $content ) use ( $snippet_content ) {
376 - return $snippet_content . $content;
377 - } );
378 - break;
379 - case 'woo_after_shop_loop':
380 - add_filter( 'woocommerce_product_loop_end', function ( $content ) use ( $snippet_content ) {
381 - return $content . $snippet_content;
382 - } );
383 - break;
384 - case 'woo_before_single_product':
385 - add_action( 'woocommerce_before_single_product', $action, 10, 2 );
386 - break;
387 - case 'woo_after_single_product':
388 - add_action( 'woocommerce_after_single_product', $action, 10, 2 );
389 - break;
390 - case 'woo_before_single_product_summary':
391 - add_action( 'woocommerce_before_single_product_summary', $action, 10, 2 );
392 - break;
393 - case 'woo_after_single_product_summary':
394 - add_action( 'woocommerce_after_single_product_summary', $action, 10, 2 );
395 - break;
396 - case 'woo_single_product_summary_title':
397 - add_action( 'woocommerce_single_product_summary', $action, 6, 2 );
398 - break;
399 - case 'woo_single_product_summary_price':
400 - add_action( 'woocommerce_single_product_summary', $action, 15, 2 );
401 - break;
402 - case 'woo_single_product_summary_excerpt':
403 - add_action( 'woocommerce_single_product_summary', $action, 25, 2 );
404 - break;
405 - default:
406 - break;
407 - }
408 - }
409 -
410 - /**
411 - * Execute Woocommerce actions/hooks
412 - *
413 - * @param $location
414 - * @param $snippet_content
415 - *
416 - * @since 2.4
417 - */
418 - public function custom_actions( $location, $snippet_content = '' ) {
419 - if ( ! empty( $this->snippets_locations->getLocation( $location ) ) ) {
420 - /**
421 - * Action for a custom location applied in 'wbcr/woody/add_custom_location' filter
422 - *
423 - * @param array $location Slug of the location.
424 - * @param string $snippet_content Rendered snippet content
425 - *
426 - * @since 2.4
427 - */
428 - do_action( "wbcr/woody/do_custom_location/{$location}", $snippet_content );
429 - }
430 - }
431 -
432 - /**
433 - * Execute the snippets once the plugins are loaded
434 - *
435 - * @param string $scope
436 - * @param string $location
437 - * @param string $content
438 - * @param array $custom_params
439 - *
440 - * @return string
441 - */
442 - public function executeActiveSnippets( $scope = 'evrywhere', $location = '', $content = '', $custom_params = [] ) {
443 - /*
444 - global $wpdb;
445 -
446 - if ( $scope == 'evrywhere' ) {
447 - $sort = 'DESC';
448 - } else {
449 - $sort = 'ASC';
450 - }
451 - $snippets = $wpdb->get_results( "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_content, p2.meta_value as priority
452 - FROM {$wpdb->posts}
453 - INNER JOIN {$wpdb->postmeta} p1 ON ({$wpdb->posts}.ID = p1.post_id)
454 - INNER JOIN {$wpdb->postmeta} p2 ON ({$wpdb->posts}.ID = p2.post_id)
455 - INNER JOIN {$wpdb->postmeta} p3 ON ({$wpdb->posts}.ID = p3.post_id)
456 - WHERE (( p1.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_scope' AND p1.meta_value = '{$scope}')
457 - AND
458 - ( p3.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_activate' AND p3.meta_value = '1')
459 - AND p2.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_priority' )
460 - AND {$wpdb->posts}.post_type = '" . WINP_SNIPPETS_POST_TYPE . "'
461 - AND ({$wpdb->posts}.post_status = 'publish')
462 - ORDER BY CAST(priority AS UNSIGNED) {$sort}" );
463 - */
464 - $snippets = $this->snippets[ $scope ] ?? [];
465 -
466 - if ( empty( $snippets ) ) {
467 - return $content;
468 - }
469 -
470 - foreach ( (array) $snippets as $snippet ) {
471 - $id = (int) $snippet->ID;
472 - //$is_active = (int) WINP_Helper::getMetaOption( $id, 'snippet_activate', 0 );
473 - // Если это сниппет с автовставкой и выбранное место подходит под активный action
474 - $avail_place = ( 'auto' == $scope ? $location == WINP_Helper::getMetaOption( $id, 'snippet_location', '' ) : true );
475 - // Если условие отображения сниппета выполняется
476 - $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
477 - $is_condition = $snippet_type != WINP_SNIPPET_TYPE_PHP ? $this->checkCondition( $id ) : true;
478 -
479 - if ( $avail_place && $is_condition ) {
480 - $post_id = (int) WINP_Plugin::app()->request->post( 'post_ID', 0 );
481 -
482 - if ( isset( $_POST['wbcr_inp_snippet_scope'] ) && $post_id === $id && WINP_Plugin::app()->currentUserCan() ) {
483 - return $content;
484 - }
485 -
486 - if ( WINP_Helper::is_safe_mode() ) {
487 - return $content;
488 - }
489 -
490 - // WPML Compatibility
491 - if ( defined( 'WPML_PLUGIN_FILE' ) ) {
492 - $wpml_langs = WINP_Helper::getMetaOption( $id, 'snippet_wpml_lang', '' );
493 - if ( $wpml_langs !== '' && defined( 'ICL_LANGUAGE_CODE' ) ) {
494 - if ( ! in_array( ICL_LANGUAGE_CODE, explode( ',', $wpml_langs ) ) ) {
495 - continue;
496 - }
497 - }
498 - }
499 -
500 - $snippet_code = WINP_Helper::get_snippet_code( $snippet );
501 -
502 - /**
503 - * Filter snippet code before execute
504 - */
505 - $snippet_code = apply_filters( 'wbcr/inp/execute_snippet/snippet_code', $snippet_code, $id );
506 -
507 - if ( WINP_Plugin::app()->getOption( 'execute_shortcode' ) ) {
508 - $snippet_code = do_shortcode( $snippet_code );
509 - }
510 -
511 - if ( $snippet_type === WINP_SNIPPET_TYPE_TEXT || $snippet_type === WINP_SNIPPET_TYPE_AD ) {
512 - $snippet_content = '<div class="winp-text-snippet-container">' . $snippet_code . '</div>';
513 - }
514 - else if ( $snippet_type === WINP_SNIPPET_TYPE_CSS || $snippet_type === WINP_SNIPPET_TYPE_JS ) {
515 - $snippet_content = self::getJsCssSnippetData( $id );
516 - }
517 - else if ( $snippet_type === WINP_SNIPPET_TYPE_HTML ) {
518 - $snippet_content = $snippet_code;
519 - }
520 - else {
521 - $code = $this->prepareCode( $snippet_code, $id );
522 - ob_start();
523 - $this->executeSnippet( $code, $id, false );
524 - $snippet_content = ob_get_contents();
525 - ob_end_clean();
526 - }
527 -
528 - if ( 'auto' == $scope ) {
529 - switch ( $location ) {
530 - case 'before_paragraph': // Перед параграфом
531 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
532 - $content = $this->handleParagraphContent( $content, $snippet_content, $location_number );
533 - break;
534 - case 'after_paragraph': // После параграфа
535 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
536 - $content = $this->handleParagraphContent( $content, $snippet_content, $location_number, 'after' );
537 - break;
538 - case 'before_posts': // Перед записью
539 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
540 - $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'before', $custom_params );
541 - break;
542 - case 'after_posts': // После записи
543 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
544 - $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'after', $custom_params );
545 - break;
546 - default:
547 - $content = $snippet_content . $content;
548 - }
549 -
550 - /**
551 - * Action for woo actions
552 - *
553 - * @param array $location Slug of the location.
554 - * @param string $snippet_content Rendered snippet content
555 - *
556 - * @since 2.4
557 - */
558 - do_action( "wbcr/woody/do_woocommerce_actions", $location, $snippet_content );
559 -
560 - //$this->woocommerce_actions( $location, $snippet_content );
561 - $this->custom_actions( $location, $snippet_content );
562 - }
563 - else {
564 - $content = $snippet_content . $content;
565 - }
566 - }
567 - }
568 -
569 - return $content;
570 - }
571 -
572 - /**
573 - * Get js or css snippet data
574 - *
575 - * @param $snippet_id
576 - *
577 - * @return mixed|string
578 - */
579 - public static function getJsCssSnippetData( $snippet_id ) {
580 - $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
581 -
582 - $linking = WINP_Helper::getMetaOption( $snippet_id, 'snippet_linking' );
583 - $filetype = WINP_Helper::getMetaOption( $snippet_id, 'filetype', $snippet_type );
584 -
585 - $file_name = $snippet_id . '.' . $filetype;
586 - $slug = WINP_Helper::getMetaOption( $snippet_id, 'css_js_slug' );
587 - if ( ! empty( $slug ) ) {
588 - $file_name = $slug . '.' . $filetype;
589 - }
590 -
591 - if ( file_exists( WINP_UPLOAD_DIR . '/' . $file_name ) ) {
592 - if ( 'inline' == $linking ) {
593 - return file_get_contents( WINP_UPLOAD_DIR . '/' . $file_name );
594 - }
595 -
596 - if ( 'external' == $linking ) {
597 - $file_name .= '?ver=' . WINP_Helper::getMetaOption( $snippet_id, 'css_js_version', time() );
598 -
599 - if ( 'js' == $snippet_type ) {
600 - return PHP_EOL . "<script type='text/javascript' src='" . WINP_UPLOAD_URL . '/' . $file_name . "'></script>" . PHP_EOL;
601 - }
602 -
603 - if ( 'css' == $snippet_type ) {
604 - $short_filename = preg_replace( '@\.css\?ver=.*$@', '', $file_name );
605 -
606 - return PHP_EOL . "<link rel='stylesheet' id='" . $short_filename . "-css' href='" . WINP_UPLOAD_URL . '/' . $file_name . "' type='text/css' media='all' />" . PHP_EOL;
607 - }
608 - }
609 - }
610 -
611 - return "";
612 - }
613 -
614 - /**
615 - * Execute a snippet
616 - *
617 - * Code must NOT be escaped, as
618 - * it will be executed directly
619 - *
620 - * @param string $code The snippet code to execute
621 - * @param int $id The snippet ID
622 - * @param bool $catch_output Whether to attempt to suppress the output of execution using buffers
623 - *
624 - * @return mixed The result of the code execution
625 - */
626 - public function executeSnippet( $code, $id = 0, $catch_output = true ) {
627 - $id = (int) $id;
628 -
629 - if ( ! $id || empty( $code ) ) {
630 - return false;
631 - }
632 -
633 - if ( $catch_output ) {
634 - ob_start();
635 - }
636 -
637 - $snippet = get_post( $id );
638 -
639 - if ( empty( $snippet ) || $snippet->post_type !== WINP_SNIPPETS_POST_TYPE ) {
640 - return false;
641 - }
642 -
643 - $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', true );
644 -
645 - if ( $snippet_type == WINP_SNIPPET_TYPE_UNIVERSAL ) {
646 - $result = eval( "?>" . $code . "<?php " );
647 - }
648 - else if ( $snippet_type == WINP_SNIPPET_TYPE_PHP ) {
649 - $result = eval( $code );
650 - }
651 - else {
652 - $result = ! empty( $code );
653 - }
654 -
655 - if ( $catch_output ) {
656 - ob_end_clean();
657 - }
658 -
659 - return $result;
660 - }
661 -
662 - /**
663 - * Get property value
664 - *
665 - * @param $value
666 - * @param $property
667 - *
668 - * @return null
669 - */
670 - private function getPropertyValue( $value, $property ) {
671 - if ( is_object( $value ) ) {
672 - return $value->$property;
673 - }
674 - else if ( isset( $value[ $property ] ) ) {
675 - return $value[ $property ];
676 - }
677 -
678 - return null;
679 - }
680 -
681 - /**
682 - * Check conditional execution logic for the snippet
683 - *
684 - * @param $snippet_id
685 - *
686 - * @return bool
687 - */
688 - public function checkCondition( $snippet_id ) {
689 - // Итоговый результат условий
690 - $result = true;
691 - // Получаем сохранённые параметры условий
692 - $filters = get_post_meta( $snippet_id, WINP_Plugin::app()->getPrefix() . 'snippet_filters' );
693 - // Если условия указаны
694 - if ( ! ( empty( $filters ) || isset( $filters[0] ) && empty( $filters[0] ) ) ) {
695 - foreach ( $filters[0] as $filter ) {
696 - $conditions = $this->getPropertyValue( $filter, 'conditions' );
697 - // Если условия пусты, то пропускаем цикл
698 - if ( empty( $conditions ) ) {
699 - continue;
700 - }
701 - // Промежуточный результат AND условий
702 - $and_conditions = null;
703 - // Проходим по AND условиям
704 - foreach ( $conditions as $scope ) {
705 - $scope_conditions = $this->getPropertyValue( $scope, 'conditions' );
706 - // Если условия пусты, то пропускаем цикл
707 - if ( empty( $scope_conditions ) ) {
708 - continue;
709 - }
710 - // Промежуточный результат OR условий
711 - $or_conditions = null;
712 - // Проходим по OR условиям
713 - foreach ( $scope_conditions as $condition ) {
714 - $method_name = str_replace( '-', '_', $this->getPropertyValue( $condition, 'param' ) );
715 - $operator = $this->getPropertyValue( $condition, 'operator' );
716 - $value = $this->getPropertyValue( $condition, 'value' );
717 - // Получаем результат OR условий
718 - $or_conditions = is_null( $or_conditions ) ? $this->call_method( $method_name, $operator, $value ) : $or_conditions || $this->call_method( $method_name, $operator, $value );
719 - }
720 - // Получаем результат AND условий
721 - $and_conditions = is_null( $and_conditions ) ? $or_conditions : $and_conditions && $or_conditions;
722 - }
723 - // Получаем результат блока условий
724 - $result = $this->getPropertyValue( $filter, 'type' ) == 'showif' ? $and_conditions : ! $and_conditions;
725 - }
726 - }
727 -
728 - return $result;
729 - }
730 -
731 - /**
732 - * Call specified method
733 - *
734 - * @param $method_name
735 - * @param $operator
736 - * @param $value
737 - *
738 - * @return bool
739 - */
740 - private function call_method( $method_name, $operator, $value ) {
741 - if ( method_exists( $this, $method_name ) ) {
742 - return $this->$method_name( $operator, $value );
743 - }
744 - else {
745 - return apply_filters( 'wbcr/inp/execute/check_condition', false, $method_name, $operator, $value );
746 - }
747 - }
748 -
749 - /**
750 - * Retrieve the first error in a snippet's code
751 - *
752 - * @param int $snippet_id
753 - *
754 - * @return array|bool
755 - */
756 - public function getSnippetError( $snippet_id ) {
757 - if ( ! intval( $snippet_id ) ) {
758 - return false;
759 - }
760 -
761 - $snippet = get_post( $snippet_id );
762 -
763 - if ( ! $snippet ) {
764 - return false;
765 - }
766 -
767 - $snippet_code = WINP_Helper::get_snippet_code( $snippet );
768 - $snippet_code = $this->prepareCode( $snippet_code, $snippet_id );
769 -
770 - $result = $this->executeSnippet( $snippet_code, $snippet_id );
771 -
772 - if ( false !== $result ) {
773 - return false;
774 - }
775 -
776 - $error = error_get_last();
777 -
778 - if ( is_null( $error ) ) {
779 - return false;
780 - }
781 -
782 - return $error;
783 - }
784 -
785 - /**
786 - * Prepare the code by removing php tags from beginning and end
787 - *
788 - * @param string $code
789 - * @param integer $snippet_id
790 - *
791 - * @return string
792 - */
793 - public function prepareCode( $code, $snippet_id ) {
794 - $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
795 - 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 ) {
796 - /* Remove <?php and <? from beginning of snippet */
797 - $code = preg_replace( '|^[\s]*<\?(php)?|', '', $code );
798 -
799 - /* Remove ?> from end of snippet */
800 - $code = preg_replace( '|\?>[\s]*$|', '', $code );
801 - }
802 -
803 - return $code;
804 - }
805 -
806 - /**
807 - * Get current URL
808 - *
809 - * @return string
810 - */
811 - private function getCurrentUrl() {
812 - $out = "";
813 - $url = explode( '?', $_SERVER['REQUEST_URI'], 2 );
814 - if ( isset( $url[0] ) ) {
815 - $out = trim( $url[0], '/' );
816 - }
817 -
818 - return $out ? urldecode( $out ) : '/';
819 - }
820 -
821 - /**
822 - * Get referer URL
823 - *
824 - * @return string
825 - */
826 - private function getRefererUrl() {
827 - $out = "";
828 - $url = explode( '?', str_replace( site_url(), '', $_SERVER['HTTP_REFERER'] ), 2 );
829 - if ( isset( $url[0] ) ) {
830 - $out = trim( $url[0], '/' );
831 - }
832 -
833 - return $out ? urldecode( $out ) : '/';
834 - }
835 -
836 - /**
837 - * Check by operator
838 - *
839 - * @param $operation
840 - * @param $first
841 - * @param $second
842 - * @param $third
843 - *
844 - * @return bool
845 - */
846 - public function checkByOperator( $operation, $first, $second, $third = false ) {
847 - switch ( $operation ) {
848 - case 'equals':
849 - if ( is_array( $second ) ) {
850 - return in_array( $first, $second );
851 - }
852 - else {
853 - return $first === $second;
854 - }
855 - case 'notequal':
856 - if ( is_array( $second ) ) {
857 - return ! in_array( $first, $second );
858 - }
859 - else {
860 - return $first !== $second;
861 - }
862 - case 'less':
863 - case 'older':
864 - return $first > $second;
865 - case 'greater':
866 - case 'younger':
867 - return $first < $second;
868 - case 'contains':
869 - return strpos( $first, $second ) !== false;
870 - case 'notcontain':
871 - return strpos( $first, $second ) === false;
872 - case 'between':
873 - return $first < $second && $second < $third;
874 -
875 - default:
876 - return $first === $second;
877 - }
878 - }
879 -
880 - /**
881 - * A role of the user who views your website. The role "guest" is applied for unregistered users.
882 - *
883 - * @param string $operator
884 - * @param string $value
885 - *
886 - * @return boolean
887 - */
888 - private function user_role( $operator, $value ) {
889 - if ( ! is_user_logged_in() ) {
890 - return $this->checkByOperator( $operator, $value, 'guest' );
891 - }
892 - else {
893 - $current_user = wp_get_current_user();
894 - if ( ! ( $current_user instanceof WP_User ) ) {
895 - return false;
896 - }
897 -
898 - return $this->checkByOperator( $operator, $value, $current_user->roles[0] );
899 - }
900 - }
901 -
902 - /**
903 - * Get timestamp
904 - *
905 - * @param $units
906 - * @param $count
907 - *
908 - * @return integer
909 - */
910 - private function getTimestamp( $units, $count ) {
911 - switch ( $units ) {
912 - case 'seconds':
913 - return $count;
914 - case 'minutes':
915 - return $count * MINUTE_IN_SECONDS;
916 - case 'hours':
917 - return $count * HOUR_IN_SECONDS;
918 - case 'days':
919 - return $count * DAY_IN_SECONDS;
920 - case 'weeks':
921 - return $count * WEEK_IN_SECONDS;
922 - case 'months':
923 - return $count * MONTH_IN_SECONDS;
924 - case 'years':
925 - return $count * YEAR_IN_SECONDS;
926 -
927 - default:
928 - return $count;
929 - }
930 - }
931 -
932 - /**
933 - * Get date timestamp
934 - *
935 - * @param $value
936 - *
937 - * @return integer
938 - */
939 - public function getDateTimestamp( $value ) {
940 - if ( is_object( $value ) ) {
941 - return ( current_time( 'timestamp' ) - $this->getTimestamp( $value->units, $value->unitsCount ) ) * 1000;
942 - }
943 - else {
944 - return $value;
945 - }
946 - }
947 -
948 - /**
949 - * The date when the user who views your website was registered.
950 - * For unregistered users this date always equals to 1 Jan 1970.
951 - *
952 - * @param string $operator
953 - * @param string $value
954 - *
955 - * @return boolean
956 - */
957 - private function user_registered( $operator, $value ) {
958 - if ( ! is_user_logged_in() ) {
959 - return false;
960 - }
961 - else {
962 - $user = wp_get_current_user();
963 - $registered = strtotime( $user->data->user_registered ) * 1000;
964 -
965 - if ( $operator == 'equals' || $operator == 'notequal' ) {
966 - $registered = $registered / 1000;
967 - $timestamp = round( $this->getDateTimestamp( $value ) / 1000 );
968 -
969 - return $this->checkByOperator( $operator, date( "Y-m-d", $timestamp ), date( "Y-m-d", $registered ) );
970 - }
971 - else if ( $operator == 'between' ) {
972 - $start_timestamp = $this->getDateTimestamp( $value->start );
973 - $end_timestamp = $this->getDateTimestamp( $value->end );
974 -
975 - return $this->checkByOperator( $operator, $start_timestamp, $registered, $end_timestamp );
976 - }
977 - else {
978 - $timestamp = $this->getDateTimestamp( $value );
979 -
980 - return $this->checkByOperator( $operator, $timestamp, $registered );
981 - }
982 - }
983 - }
984 -
985 - /**
986 - * Check the user views your website from mobile device or not
987 - *
988 - * @param string $operator
989 - * @param string $value
990 - *
991 - * @return boolean
992 - *
993 - * @link https://stackoverflow.com/a/4117597
994 - */
995 - private function user_mobile( $operator, $value ) {
996 - $useragent = $_SERVER['HTTP_USER_AGENT'];
997 -
998 - 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 ) ) ) {
999 - return $operator === 'equals' && $value === 'yes' || $operator === 'notequal' && $value === 'no';
1000 - }
1001 - else {
1002 - return $operator === 'notequal' && $value === 'yes' || $operator === 'equals' && $value === 'no';
1003 - }
1004 - }
1005 -
1006 - /**
1007 - * Determines whether the user's browser has a cookie with a given name
1008 - *
1009 - * @param $operator
1010 - * @param $value
1011 - *
1012 - * @return boolean
1013 - */
1014 - private function user_cookie_name( $operator, $value ) {
1015 - if ( isset( $_COOKIE[ $value ] ) ) {
1016 - return $operator === 'equals';
1017 - }
1018 - else {
1019 - return $operator === 'notequal';
1020 - }
1021 - }
1022 -
1023 - /**
1024 - * A some selected page
1025 - *
1026 - * @param $operator
1027 - * @param $value
1028 - *
1029 - * @return boolean
1030 - */
1031 - private function location_some_page( $operator, $value ) {
1032 - $post_id = ( ! is_404() && ! is_search() && ! is_archive() && ! is_home() ) ? get_the_ID() : false;
1033 -
1034 - switch ( $value ) {
1035 - case 'base_web': // Basic - Entire Website
1036 - $result = true;
1037 - break;
1038 - case 'base_sing': // Basic - All Singulars
1039 - $result = is_singular();
1040 - break;
1041 - case 'base_arch': // Basic - All Archives
1042 - $result = is_archive();
1043 - break;
1044 - case 'spec_404': // Special Pages - 404 Page
1045 - $result = is_404();
1046 - break;
1047 - case 'spec_search': // Special Pages - Search Page
1048 - $result = is_search();
1049 - break;
1050 - case 'spec_blog': // Special Pages - Blog / Posts Page
1051 - $result = is_home();
1052 - break;
1053 - case 'spec_front': // Special Pages - Front Page
1054 - $result = is_front_page();
1055 - break;
1056 - case 'spec_date': // Special Pages - Date Archive
1057 - $result = is_date();
1058 - break;
1059 - case 'spec_auth': // Special Pages - Author Archive
1060 - $result = is_author();
1061 - break;
1062 - case 'post_all': // Posts - All Posts
1063 - case 'page_all': // Pages - All Pages
1064 - $result = false;
1065 - if ( false !== $post_id ) {
1066 - $post_type = 'post_all' == $value ? 'post' : 'page';
1067 - $result = $post_type == get_post_type( $post_id );
1068 - }
1069 - break;
1070 - case 'post_arch': // Posts - All Posts Archive
1071 - case 'page_arch': // Pages - All Pages Archive
1072 - $result = false;
1073 - if ( is_archive() ) {
1074 - $post_type = 'post_arch' == $value ? 'post' : 'page';
1075 - $result = $post_type == get_post_type();
1076 - }
1077 - break;
1078 - case 'post_cat': // Posts - All Categories Archive
1079 - case 'post_tag': // Posts - All Tags Archive
1080 - $result = false;
1081 - if ( is_archive() && 'post' == get_post_type() ) {
1082 - $taxonomy = 'post_tag' == $value ? 'post_tag' : 'category';
1083 - $obj = get_queried_object();
1084 -
1085 - $current_taxonomy = '';
1086 - if ( '' !== $obj && null !== $obj ) {
1087 - $current_taxonomy = $obj->taxonomy;
1088 - }
1089 -
1090 - if ( $current_taxonomy == $taxonomy ) {
1091 - $result = true;
1092 - }
1093 - }
1094 - break;
1095 -
1096 - default:
1097 - $result = false;
1098 - }
1099 -
1100 - if ( WINP_Helper::is_woo_active() ) {
1101 - switch ( $value ) {
1102 - case 'woo_product':
1103 - $result = is_product();
1104 - break;
1105 - case 'woo_arch':
1106 - $result = is_shop();
1107 - break;
1108 - case 'woo_cart':
1109 - $result = is_cart();
1110 - break;
1111 - case 'woo_checkout':
1112 - $result = is_checkout();
1113 - break;
1114 - case 'woo_checkout_pay':
1115 - $result = is_checkout_pay_page();
1116 - break;
1117 - case 'woo_cat':
1118 - $result = is_product_category();
1119 - break;
1120 - case 'woo_tag':
1121 - $result = is_product_tag();
1122 - break;
1123 - }
1124 - }
1125 -
1126 - return $this->checkByOperator( $operator, $result, true );
1127 - }
1128 -
1129 - /**
1130 - * An URL of the current page where a user who views your website is located
1131 - *
1132 - * @param $operator
1133 - * @param $value
1134 - *
1135 - * @return boolean
1136 - */
1137 - private function location_page( $operator, $value ) {
1138 - $url = $this->getCurrentUrl();
1139 -
1140 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1141 - }
1142 -
1143 - /**
1144 - * A referrer URL which has brought a user to the current page
1145 - *
1146 - * @param $operator
1147 - * @param $value
1148 - *
1149 - * @return boolean
1150 - */
1151 - private function location_referrer( $operator, $value ) {
1152 - $url = $this->getRefererUrl();
1153 -
1154 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
1155 - }
1156 -
1157 - /**
1158 - * A post type of the current page
1159 - *
1160 - * @param $operator
1161 - * @param $value
1162 - *
1163 - * @return boolean
1164 - */
1165 - private function location_post_type( $operator, $value ) {
1166 - if ( is_singular() ) {
1167 - return $this->checkByOperator( $operator, $value, get_post_type() );
1168 - }
1169 -
1170 - return false;
1171 - }
1172 -
1173 - /**
1174 - * A taxonomy page
1175 - *
1176 - * @param $operator
1177 - * @param $value
1178 - *
1179 - * @return boolean
1180 - * @since 2.2.8 The bug is fixed, the condition was not checked
1181 - * for tachonomies, only posts.
1182 - *
1183 - */
1184 - private function location_taxonomy( $operator, $value ) {
1185 - $term_id = null;
1186 -
1187 - if ( is_tax() || is_tag() || is_category() ) {
1188 - $term_id = get_queried_object()->term_id;
1189 -
1190 - if ( $term_id ) {
1191 - return $this->checkByOperator( $operator, intval( $value ), $term_id );
1192 - }
1193 - }
1194 -
1195 - return false;
1196 - }
1197 -
1198 - /**
1199 - * A taxonomy of the current page
1200 - *
1201 - * @param $operator
1202 - * @param $value
1203 - *
1204 - * @return boolean
1205 - * @since 2.4.0
1206 - */
1207 - private function page_taxonomy( $operator, $value ) {
1208 - $term_id = null;
1209 -
1210 - if ( is_singular() ) {
1211 - $post_cat = get_the_category( get_the_ID() );
1212 - if ( is_array( $post_cat ) ) {
1213 - foreach ( $post_cat as $item ) {
1214 - $term_id[] = $item->term_id;
1215 - }
1216 - }
1217 - }
1218 -
1219 - if ( $term_id ) {
1220 - return $this->checkByOperator( $operator, intval( $value ), $term_id );
1221 - }
1222 -
1223 - return false;
1224 - }
1225 -}
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 +}