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 -961 2.3.1trunk View file →
@@ -1,961 +1,1737 @@
1 -<?php
2 -/**
3 - * Execute snippet
4 - *
5 - * @author Webcraftic <wordpress.webraftic@gmail.com>
6 - * @copyright (c) 16.11.2018, Webcraftic
7 - * @version 1.0
8 - */
9 -
10 -// Exit if accessed directly
11 -if ( ! defined( 'ABSPATH' ) ) {
12 - exit;
13 -}
14 -
15 -class WINP_Execute_Snippet {
16 -
17 - /**
18 - * WINP_Execute_Snippet constructor.
19 - */
20 - public function __construct() {
21 - if ( ! defined( 'WINP_UPLOAD_DIR' ) ) {
22 - $dir = wp_upload_dir();
23 - define( 'WINP_UPLOAD_DIR', $dir['basedir'] . '/winp-css-js' );
24 - }
25 -
26 - if ( ! defined( 'WINP_UPLOAD_URL' ) ) {
27 - $dir = wp_upload_dir();
28 - define( 'WINP_UPLOAD_URL', $dir['baseurl'] . '/winp-css-js' );
29 - }
30 - }
31 -
32 - /**
33 - * Register hooks
34 - */
35 - public function registerHooks() {
36 - add_action( 'plugins_loaded', [ $this, 'executeEverywhereSnippets' ], 1 );
37 -
38 - if ( ! is_admin() ) { #issue PCS-45 fix bug with WPBPage Builder Frontend Editor
39 - add_action( 'wp_head', [ $this, 'executeHeaderSnippets' ] );
40 - add_action( 'wp_footer', [ $this, 'executeFooterSnippets' ] );
41 - add_filter( 'the_post', [ $this, 'executePostSnippets' ], 10, 2 );
42 - add_filter( 'the_content', [ $this, 'executeContentSnippets' ] );
43 - add_filter( 'the_excerpt', [ $this, 'executeExcerptSnippets' ] );
44 - add_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
45 - }
46 - }
47 -
48 - /**
49 - * Execute the evrywhere snippets once the plugins are loaded
50 - */
51 - public function executeEverywhereSnippets() {
52 - echo $this->executeActiveSnippets( 'evrywhere' );
53 - }
54 -
55 - /**
56 - * Execute the snippets in header of page once the plugins are loaded
57 - */
58 - public function executeHeaderSnippets() {
59 - echo $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_HEADER );
60 - }
61 -
62 - /**
63 - * Execute the snippets in footer of page once the plugins are loaded
64 - */
65 - public function executeFooterSnippets() {
66 - echo $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_FOOTER );
67 - }
68 -
69 - /**
70 - * Execute the snippets before post
71 - *
72 - * @param $data
73 - * @param $query
74 - */
75 - public function executePostSnippets( $data, $query ) {
76 - global $post;
77 -
78 - $content = '';
79 -
80 - $post_type = ! empty( $post ) ? $post->post_type : get_post( $data->ID )->post_type;
81 - if ( is_singular( [ $post_type ] ) && $post->ID == $data->ID ) {
82 - if ( did_action( 'get_header' ) ) {
83 - // Перед заголовком
84 - $content = $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_BEFORE_POST );
85 - }
86 - } else {
87 - if ( $query->post_count > 0 ) {
88 - if ( $query->post_count > 1 && $query->current_post > 0 && $query->post_count > $query->current_post ) {
89 - // Между записями
90 - $content = $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_BETWEEN_POSTS );
91 - }
92 - // Перед записью
93 - $content .= $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_BEFORE_POSTS, '', $query );
94 -
95 - // После записи
96 - $content .= $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_AFTER_POSTS, '', $query );
97 - }
98 - }
99 -
100 - echo $content;
101 - }
102 -
103 - /**
104 - * Handle paragraph content
105 - *
106 - * @param $content
107 - * @param $snippet_content
108 - * @param $paragraph_number
109 - * @param $type
110 - *
111 - * @return mixed
112 - */
113 - private function handleParagraphContent( $content, $snippet_content, $paragraph_number, $type = 'before' ) {
114 - if ( 'before' == $type ) {
115 - preg_match_all( "/<p(.*?)>/", $content, $matches );
116 - } else {
117 - preg_match_all( "/<\/p>/", $content, $matches );
118 - }
119 - $paragraphs = $matches[0];
120 -
121 - if ( $paragraph_number == 0 ) {
122 - $paragraph_number = 1;
123 - }
124 -
125 - if ( $content && $snippet_content && $paragraphs && $paragraph_number <= count( $paragraphs ) ) {
126 - $offset = 0;
127 - foreach ( $paragraphs as $paragraph_key => $paragraph ) {
128 - $position = strpos( $content, $paragraph, $offset ); // Позиция тега параграфа
129 - // Если указанный номер параграфа совпадает с текущим
130 - if ( $paragraph_key + 1 == $paragraph_number ) {
131 - if ( 'before' == $type ) {
132 - $content = substr( $content, 0, $position ) . $snippet_content . substr( $content, $position );
133 - } else {
134 - $content = substr( $content, 0, $position + 4 ) . $snippet_content . substr( $content, $position + 4 );
135 - }
136 - break;
137 - } else {
138 - $offset = $position + 1;
139 - }
140 - }
141 - }
142 -
143 - return $content;
144 - }
145 -
146 - /**
147 - * Handle posts content
148 - *
149 - * @param string $content
150 - * @param string $snippet_content
151 - * @param integer $post_number
152 - * @param string $type
153 - * @param object $query
154 - *
155 - * @return mixed
156 - */
157 - private function handlePostsContent( $content, $snippet_content, $post_number, $type, $query ) {
158 - global $winp_after_post_content;
159 - if ( $query->post_count > 0 ) {
160 - if ( $post_number == 0 ) {
161 - $post_number = 1;
162 - }
163 -
164 - if ( 'before' == $type && $query->current_post + 1 == $post_number ) {
165 - return $snippet_content;
166 - } else if ( 'after' == $type ) {
167 - // Номер поста совпадает
168 - if ( $query->current_post == $post_number ) {
169 - return $snippet_content;
170 - // Если это последний пост и указанный номер поста больше общего количества постов,
171 - // то нужно сохранить контент сниппета для вывода в конце данного поста
172 - } else if ( $query->current_post + 1 == $query->post_count && $post_number >= $query->post_count ) {
173 - $winp_after_post_content[ $query->post->ID ] = $snippet_content;
174 - }
175 - }
176 - }
177 -
178 - return $content;
179 - }
180 -
181 - /**
182 - * Execute the snippets page content
183 - *
184 - * @param $content
185 - *
186 - * @return mixed
187 - */
188 - public function executeContentSnippets( $content ) {
189 - global $post, $winp_after_post_content;
190 -
191 - $post_type = ! empty( $post ) ? $post->post_type : false;
192 -
193 - if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
194 - // Перед коротким описанием
195 - $content = $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_BEFORE_EXCERPT ) . $content;
196 -
197 - // После короткого описания
198 - $content .= $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_AFTER_EXCERPT );
199 - }
200 -
201 - if ( is_singular( [ $post_type ] ) ) {
202 - // Перед параграфом
203 - $content = $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_BEFORE_PARAGRAPH, $content );
204 -
205 - // После параграфа
206 - $content = $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_AFTER_PARAGRAPH, $content );
207 -
208 - // После заголовка
209 - $content = $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_BEFORE_CONTENT ) . $content;
210 -
211 - // После текста
212 - $content .= $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_AFTER_CONTENT );
213 -
214 - // После поста
215 - $content .= $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_AFTER_POST );
216 -
217 - if ( ! comments_open( $post->ID ) && ! get_comments_number( $post->ID ) ) {
218 - remove_filter( 'wp_list_comments_args', [ $this, 'executeListCommentsSnippets' ] );
219 - }
220 - } else if ( ! is_null( $post ) && isset( $winp_after_post_content[ $post->ID ] ) ) {
221 - // После последнего поста в списке
222 - $content .= $winp_after_post_content[ $post->ID ];
223 - unset( $winp_after_post_content[ $post->ID ] );
224 - }
225 -
226 - return $content;
227 - }
228 -
229 - /**
230 - * Execute the snippets page excerpt
231 - *
232 - * @param $excerpt
233 - *
234 - * @return mixed
235 - */
236 - public function executeExcerptSnippets( $excerpt ) {
237 - if ( is_category() || is_archive() || is_tag() || is_tax() || is_search() ) {
238 - // Перед коротким описанием
239 - $excerpt = $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_BEFORE_EXCERPT ) . $excerpt;
240 -
241 - // После короткого описания
242 - $excerpt .= $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_AFTER_EXCERPT );
243 - }
244 -
245 - return $excerpt;
246 - }
247 -
248 - /**
249 - * Execute the list comments filter
250 - *
251 - * @param $args
252 - *
253 - * @return mixed
254 - */
255 - public function executeListCommentsSnippets( $args ) {
256 - global $winp_wp_data;
257 -
258 - $winp_wp_data['winp_comments_saved_end_callback'] = $args['end-callback'];
259 - $args['end-callback'] = [ $this, 'executeCommentsSnippets' ];
260 -
261 - return $args;
262 - }
263 -
264 - /**
265 - * Execute the snippets after page comments
266 - *
267 - * @param $comment
268 - * @param $args
269 - * @param $depth
270 - */
271 - public function executeCommentsSnippets( $comment, $args, $depth ) {
272 - global $winp_wp_data, $post;
273 -
274 - if ( ! empty ( $winp_wp_data['winp_comments_saved_end_callback'] ) ) {
275 - echo call_user_func( $winp_wp_data['winp_comments_saved_end_callback'], $comment, $args, $depth );
276 - }
277 -
278 - $content = '';
279 -
280 - $post_type = ! empty( $post ) ? $post->post_type : false;
281 - if ( is_singular( [ $post_type ] ) ) {
282 - // После комментариев
283 - $content = $this->executeActiveSnippets( 'auto', WINP_SNIPPET_AUTO_AFTER_POST );
284 - }
285 -
286 - echo $content;
287 - }
288 -
289 - /**
290 - * Execute the snippets once the plugins are loaded
291 - *
292 - * @param string $scope
293 - * @param string $auto
294 - * @param string $content
295 - * @param array $custom_params
296 - *
297 - * @return string
298 - */
299 - public function executeActiveSnippets( $scope = 'evrywhere', $auto = '', $content = '', $custom_params = [] ) {
300 - global $wpdb;
301 -
302 - $snippets = $wpdb->get_results( "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_content
303 - FROM {$wpdb->posts}
304 - INNER JOIN {$wpdb->postmeta} ON ({$wpdb->posts}.ID = {$wpdb->postmeta}.post_id)
305 - WHERE (( {$wpdb->postmeta}.meta_key = '" . WINP_Plugin::app()->getPrefix() . "snippet_scope'
306 - AND {$wpdb->postmeta}.meta_value = '{$scope}'))
307 - AND {$wpdb->posts}.post_type = '" . WINP_SNIPPETS_POST_TYPE . "'
308 - AND (({$wpdb->posts}.post_status = 'publish'))" );
309 -
310 -
311 - if ( empty( $snippets ) ) {
312 - return $content;
313 - }
314 -
315 - foreach ( (array) $snippets as $snippet ) {
316 - $id = (int) $snippet->ID;
317 -
318 - $is_active = (int) WINP_Helper::getMetaOption( $id, 'snippet_activate', 0 );
319 - // Если это сниппет с автовставкой и выбранное место подходит под активный action
320 - $avail_place = ( 'auto' == $scope ? $auto == WINP_Helper::getMetaOption( $id, 'snippet_location', '' ) : true );
321 - // Если условие отображения сниппена выполняется
322 - $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', WINP_SNIPPET_TYPE_PHP );
323 - $is_condition = $snippet_type != WINP_SNIPPET_TYPE_PHP ? $this->checkCondition( $id ) : true;
324 -
325 - if ( $is_active && $avail_place && $is_condition ) {
326 - $post_id = (int) WINP_Plugin::app()->request->post( 'post_ID', 0 );
327 -
328 - if ( isset( $_POST['wbcr_inp_snippet_scope'] ) && $post_id === $id && WINP_Plugin::app()->currentUserCan() ) {
329 - return $content;
330 - }
331 -
332 - if ( WINP_Helper::is_safe_mode() ) {
333 - return $content;
334 - }
335 -
336 - $snippet_code = WINP_Helper::get_snippet_code( $snippet );
337 -
338 - if ( $snippet_type === WINP_SNIPPET_TYPE_TEXT ) {
339 - $snippet_content = '<div class="winp-text-snippet-contanier">' . $snippet_code . '</div>';
340 - } else if ( $snippet_type === WINP_SNIPPET_TYPE_CSS || $snippet_type === WINP_SNIPPET_TYPE_JS ) {
341 - $snippet_content = self::getJsCssSnippetData( $id );
342 - } else if ( $snippet_type === WINP_SNIPPET_TYPE_HTML ) {
343 - $snippet_content = $snippet_code;
344 - } else {
345 - $code = $this->prepareCode( $snippet_code, $id );
346 - ob_start();
347 - $this->executeSnippet( $code, $id, false );
348 - $snippet_content = ob_get_contents();
349 - ob_end_clean();
350 - }
351 -
352 - if ( 'auto' == $scope ) {
353 - switch ( $auto ) {
354 - case WINP_SNIPPET_AUTO_BEFORE_PARAGRAPH: // Перед параграфом
355 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
356 - $content = $this->handleParagraphContent( $content, $snippet_content, $location_number );
357 - break;
358 - case WINP_SNIPPET_AUTO_AFTER_PARAGRAPH: // После параграфа
359 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
360 - $content = $this->handleParagraphContent( $content, $snippet_content, $location_number, 'after' );
361 - break;
362 - case WINP_SNIPPET_AUTO_BEFORE_POSTS: // Перед записью
363 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
364 - $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'before', $custom_params );
365 - break;
366 - case WINP_SNIPPET_AUTO_AFTER_POSTS: // После записи
367 - $location_number = WINP_Helper::getMetaOption( $id, 'snippet_p_number', 0 );
368 - $content = $this->handlePostsContent( $content, $snippet_content, $location_number, 'after', $custom_params );
369 - break;
370 - default:
371 - $content = $snippet_content . $content;
372 - }
373 - } else {
374 - $content = $snippet_content . $content;
375 - }
376 - }
377 - }
378 -
379 - return $content;
380 - }
381 -
382 - /**
383 - * Get js or css snippet data
384 - *
385 - * @param $snippet_id
386 - *
387 - * @return mixed|string
388 - */
389 - public static function getJsCssSnippetData( $snippet_id ) {
390 - $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
391 -
392 - $linking = WINP_Helper::getMetaOption( $snippet_id, 'snippet_linking' );
393 - $filetype = WINP_Helper::getMetaOption( $snippet_id, 'filetype', $snippet_type );
394 -
395 - $file_name = $snippet_id . '.' . $filetype;
396 - $slug = WINP_Helper::getMetaOption( $snippet_id, 'css_js_slug' );
397 - if ( ! empty( $slug ) ) {
398 - $file_name = $slug . '.' . $filetype;
399 - }
400 -
401 - if ( file_exists( WINP_UPLOAD_DIR . '/' . $file_name ) ) {
402 - if ( 'inline' == $linking ) {
403 - return file_get_contents( WINP_UPLOAD_DIR . '/' . $file_name );
404 - }
405 -
406 - if ( 'external' == $linking ) {
407 - $file_name .= '?ver=' . WINP_Helper::getMetaOption( $snippet_id, 'css_js_version', time() );
408 -
409 - if ( 'js' == $snippet_type ) {
410 - return PHP_EOL . "<script type='text/javascript' src='" . WINP_UPLOAD_URL . '/' . $file_name . "'></script>" . PHP_EOL;
411 - }
412 -
413 - if ( 'css' == $snippet_type ) {
414 - $short_filename = preg_replace( '@\.css\?ver=.*$@', '', $file_name );
415 -
416 - return PHP_EOL . "<link rel='stylesheet' id='" . $short_filename . "-css' href='" . WINP_UPLOAD_URL . '/' . $file_name . "' type='text/css' media='all' />" . PHP_EOL;
417 - }
418 - }
419 - }
420 -
421 - return "";
422 - }
423 -
424 - /**
425 - * Execute a snippet
426 - *
427 - * Code must NOT be escaped, as
428 - * it will be executed directly
429 - *
430 - * @param string $code The snippet code to execute
431 - * @param int $id The snippet ID
432 - * @param bool $catch_output Whether to attempt to suppress the output of execution using buffers
433 - *
434 - * @return mixed The result of the code execution
435 - */
436 - public function executeSnippet( $code, $id = 0, $catch_output = true ) {
437 - $id = (int) $id;
438 -
439 - if ( ! $id || empty( $code ) ) {
440 - return false;
441 - }
442 -
443 - if ( $catch_output ) {
444 - ob_start();
445 - }
446 -
447 - $snippet = get_post( $id );
448 -
449 - if ( empty( $snippet ) || $snippet->post_type !== WINP_SNIPPETS_POST_TYPE ) {
450 - return false;
451 - }
452 -
453 - $snippet_type = WINP_Helper::getMetaOption( $id, 'snippet_type', true );
454 -
455 - if ( $snippet_type == WINP_SNIPPET_TYPE_UNIVERSAL ) {
456 - $result = eval( "?>" . $code . "<?php " );
457 - } else if ( $snippet_type == WINP_SNIPPET_TYPE_PHP ) {
458 - $result = eval( $code );
459 - } else {
460 - $result = ! empty( $code );
461 - }
462 -
463 - if ( $catch_output ) {
464 - ob_end_clean();
465 - }
466 -
467 - return $result;
468 - }
469 -
470 - /**
471 - * Get property value
472 - *
473 - * @param $value
474 - * @param $property
475 - *
476 - * @return null
477 - */
478 - private function getPropertyValue( $value, $property ) {
479 - if ( is_object( $value ) ) {
480 - return $value->$property;
481 - } else if ( isset( $value[ $property ] ) ) {
482 - return $value[ $property ];
483 - }
484 -
485 - return null;
486 - }
487 -
488 - /**
489 - * Check conditional execution logic for the snippet
490 - *
491 - * @param $snippet_id
492 - *
493 - * @return bool
494 - */
495 - public function checkCondition( $snippet_id ) {
496 - // Итоговый результат условий
497 - $result = true;
498 - // Получаем сохранённые параметры условий
499 - $filters = get_post_meta( $snippet_id, WINP_Plugin::app()->getPrefix() . 'snippet_filters' );
500 - // Если условия указаны
501 - if ( ! ( empty( $filters ) || isset( $filters[0] ) && empty( $filters[0] ) ) ) {
502 - foreach ( $filters[0] as $filter ) {
503 - $conditions = $this->getPropertyValue( $filter, 'conditions' );
504 - // Если условия пусты, то пропускаем цикл
505 - if ( empty( $conditions ) ) {
506 - continue;
507 - }
508 - // Промежуточный результат AND условий
509 - $and_conditions = null;
510 - // Проходим по AND условиям
511 - foreach ( $conditions as $scope ) {
512 - $scope_conditions = $this->getPropertyValue( $scope, 'conditions' );
513 - // Если условия пусты, то пропускаем цикл
514 - if ( empty( $scope_conditions ) ) {
515 - continue;
516 - }
517 - // Промежуточный результат OR условий
518 - $or_conditions = null;
519 - // Проходим по OR условиям
520 - foreach ( $scope_conditions as $condition ) {
521 - $method_name = str_replace( '-', '_', $this->getPropertyValue( $condition, 'param' ) );
522 - $operator = $this->getPropertyValue( $condition, 'operator' );
523 - $value = $this->getPropertyValue( $condition, 'value' );
524 - // Получаем результат OR условий
525 - $or_conditions = is_null( $or_conditions ) ? $this->call_method( $method_name, $operator, $value ) : $or_conditions || $this->call_method( $method_name, $operator, $value );
526 - }
527 - // Получаем результат AND условий
528 - $and_conditions = is_null( $and_conditions ) ? $or_conditions : $and_conditions && $or_conditions;
529 - }
530 - // Получаем результат блока условий
531 - $result = $this->getPropertyValue( $filter, 'type' ) == 'showif' ? $and_conditions : ! $and_conditions;
532 - }
533 - }
534 -
535 - return $result;
536 - }
537 -
538 - /**
539 - * Call specified method
540 - *
541 - * @param $method_name
542 - * @param $operator
543 - * @param $value
544 - *
545 - * @return bool
546 - */
547 - private function call_method( $method_name, $operator, $value ) {
548 - if ( method_exists( $this, $method_name ) ) {
549 - return $this->$method_name( $operator, $value );
550 - } else {
551 - return apply_filters( 'wbcr/inp/execute/check_condition', false, $method_name, $operator, $value );
552 - }
553 - }
554 -
555 - /**
556 - * Retrieve the first error in a snippet's code
557 - *
558 - * @param int $snippet_id
559 - *
560 - * @return array|bool
561 - */
562 - public function getSnippetError( $snippet_id ) {
563 - if ( ! intval( $snippet_id ) ) {
564 - return false;
565 - }
566 -
567 - $snippet = get_post( $snippet_id );
568 -
569 - if ( ! $snippet ) {
570 - return false;
571 - }
572 -
573 - $snippet_code = WINP_Helper::get_snippet_code( $snippet );
574 - $snippet_code = $this->prepareCode( $snippet_code, $snippet_id );
575 -
576 - $result = $this->executeSnippet( $snippet_code, $snippet_id );
577 -
578 - if ( false !== $result ) {
579 - return false;
580 - }
581 -
582 - $error = error_get_last();
583 -
584 - if ( is_null( $error ) ) {
585 - return false;
586 - }
587 -
588 - return $error;
589 - }
590 -
591 - /**
592 - * Prepare the code by removing php tags from beginning and end
593 - *
594 - * @param string $code
595 - * @param integer $snippet_id
596 - *
597 - * @return string
598 - */
599 - public function prepareCode( $code, $snippet_id ) {
600 - $snippet_type = WINP_Helper::get_snippet_type( $snippet_id );
601 - 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 ) {
602 - /* Remove <?php and <? from beginning of snippet */
603 - $code = preg_replace( '|^[\s]*<\?(php)?|', '', $code );
604 -
605 - /* Remove ?> from end of snippet */
606 - $code = preg_replace( '|\?>[\s]*$|', '', $code );
607 - }
608 -
609 - return $code;
610 - }
611 -
612 - /**
613 - * Get current URL
614 - *
615 - * @return string
616 - */
617 - private function getCurrentUrl() {
618 - $out = "";
619 - $url = explode( '?', $_SERVER['REQUEST_URI'], 2 );
620 - if ( isset( $url[0] ) ) {
621 - $out = trim( $url[0], '/' );
622 - }
623 -
624 - return $out ? urldecode( $out ) : '/';
625 - }
626 -
627 - /**
628 - * Get referer URL
629 - *
630 - * @return string
631 - */
632 - private function getRefererUrl() {
633 - $out = "";
634 - $url = explode( '?', str_replace( site_url(), '', $_SERVER['HTTP_REFERER'] ), 2 );
635 - if ( isset( $url[0] ) ) {
636 - $out = trim( $url[0], '/' );
637 - }
638 -
639 - return $out ? urldecode( $out ) : '/';
640 - }
641 -
642 - /**
643 - * Check by operator
644 - *
645 - * @param $operation
646 - * @param $first
647 - * @param $second
648 - * @param $third
649 - *
650 - * @return bool
651 - */
652 - public function checkByOperator( $operation, $first, $second, $third = false ) {
653 - switch ( $operation ) {
654 - case 'equals':
655 - return $first === $second;
656 - case 'notequal':
657 - return $first !== $second;
658 - case 'less':
659 - case 'older':
660 - return $first > $second;
661 - case 'greater':
662 - case 'younger':
663 - return $first < $second;
664 - case 'contains':
665 - return strpos( $first, $second ) !== false;
666 - case 'notcontain':
667 - return strpos( $first, $second ) === false;
668 - case 'between':
669 - return $first < $second && $second < $third;
670 -
671 - default:
672 - return $first === $second;
673 - }
674 - }
675 -
676 - /**
677 - * A role of the user who views your website. The role "guest" is applied for unregistered users.
678 - *
679 - * @param string $operator
680 - * @param string $value
681 - *
682 - * @return boolean
683 - */
684 - private function user_role( $operator, $value ) {
685 - if ( ! is_user_logged_in() ) {
686 - return $this->checkByOperator( $operator, $value, 'guest' );
687 - } else {
688 - $current_user = wp_get_current_user();
689 - if ( ! ( $current_user instanceof WP_User ) ) {
690 - return false;
691 - }
692 -
693 - return $this->checkByOperator( $operator, $value, $current_user->roles[0] );
694 - }
695 - }
696 -
697 - /**
698 - * Get timestamp
699 - *
700 - * @param $units
701 - * @param $count
702 - *
703 - * @return integer
704 - */
705 - private function getTimestamp( $units, $count ) {
706 - switch ( $units ) {
707 - case 'seconds':
708 - return $count;
709 - case 'minutes':
710 - return $count * MINUTE_IN_SECONDS;
711 - case 'hours':
712 - return $count * HOUR_IN_SECONDS;
713 - case 'days':
714 - return $count * DAY_IN_SECONDS;
715 - case 'weeks':
716 - return $count * WEEK_IN_SECONDS;
717 - case 'months':
718 - return $count * MONTH_IN_SECONDS;
719 - case 'years':
720 - return $count * YEAR_IN_SECONDS;
721 -
722 - default:
723 - return $count;
724 - }
725 - }
726 -
727 - /**
728 - * Get date timestamp
729 - *
730 - * @param $value
731 - *
732 - * @return integer
733 - */
734 - public function getDateTimestamp( $value ) {
735 - if ( is_object( $value ) ) {
736 - return ( current_time( 'timestamp' ) - $this->getTimestamp( $value->units, $value->unitsCount ) ) * 1000;
737 - } else {
738 - return $value;
739 - }
740 - }
741 -
742 - /**
743 - * The date when the user who views your website was registered.
744 - * For unregistered users this date always equals to 1 Jan 1970.
745 - *
746 - * @param string $operator
747 - * @param string $value
748 - *
749 - * @return boolean
750 - */
751 - private function user_registered( $operator, $value ) {
752 - if ( ! is_user_logged_in() ) {
753 - return false;
754 - } else {
755 - $user = wp_get_current_user();
756 - $registered = strtotime( $user->data->user_registered ) * 1000;
757 -
758 - if ( $operator == 'equals' || $operator == 'notequal' ) {
759 - $registered = $registered / 1000;
760 - $timestamp = round( $this->getDateTimestamp( $value ) / 1000 );
761 -
762 - return $this->checkByOperator( $operator, date( "Y-m-d", $timestamp ), date( "Y-m-d", $registered ) );
763 - } else if ( $operator == 'between' ) {
764 - $start_timestamp = $this->getDateTimestamp( $value->start );
765 - $end_timestamp = $this->getDateTimestamp( $value->end );
766 -
767 - return $this->checkByOperator( $operator, $start_timestamp, $registered, $end_timestamp );
768 - } else {
769 - $timestamp = $this->getDateTimestamp( $value );
770 -
771 - return $this->checkByOperator( $operator, $timestamp, $registered );
772 - }
773 - }
774 - }
775 -
776 - /**
777 - * Check the user views your website from mobile device or not
778 - *
779 - * @param string $operator
780 - * @param string $value
781 - *
782 - * @return boolean
783 - *
784 - * @link https://stackoverflow.com/a/4117597
785 - */
786 - private function user_mobile( $operator, $value ) {
787 - $useragent = $_SERVER['HTTP_USER_AGENT'];
788 -
789 - 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 ) ) ) {
790 - return $operator === 'equals' && $value === 'yes' || $operator === 'notequal' && $value === 'no';
791 - } else {
792 - return $operator === 'notequal' && $value === 'yes' || $operator === 'equals' && $value === 'no';
793 - }
794 - }
795 -
796 - /**
797 - * Determines whether the user's browser has a cookie with a given name
798 - *
799 - * @param $operator
800 - * @param $value
801 - *
802 - * @return boolean
803 - */
804 - private function user_cookie_name( $operator, $value ) {
805 - if ( isset( $_COOKIE[ $value ] ) ) {
806 - return $operator === 'equals';
807 - } else {
808 - return $operator === 'notequal';
809 - }
810 - }
811 -
812 - /**
813 - * A some selected page
814 - *
815 - * @param $operator
816 - * @param $value
817 - *
818 - * @return boolean
819 - */
820 - private function location_some_page( $operator, $value ) {
821 - $post_id = ( ! is_404() && ! is_search() && ! is_archive() && ! is_home() ) ? get_the_ID() : false;
822 -
823 - switch ( $value ) {
824 - case 'base_web': // Basic - Entire Website
825 - $result = true;
826 - break;
827 - case 'base_sing': // Basic - All Singulars
828 - $result = is_singular();
829 - break;
830 - case 'base_arch': // Basic - All Archives
831 - $result = is_archive();
832 - break;
833 - case 'spec_404': // Special Pages - 404 Page
834 - $result = is_404();
835 - break;
836 - case 'spec_search': // Special Pages - Search Page
837 - $result = is_search();
838 - break;
839 - case 'spec_blog': // Special Pages - Blog / Posts Page
840 - $result = is_home();
841 - break;
842 - case 'spec_front': // Special Pages - Front Page
843 - $result = is_front_page();
844 - break;
845 - case 'spec_date': // Special Pages - Date Archive
846 - $result = is_date();
847 - break;
848 - case 'spec_auth': // Special Pages - Author Archive
849 - $result = is_author();
850 - break;
851 - case 'post_all': // Posts - All Posts
852 - case 'page_all': // Pages - All Pages
853 - $result = false;
854 - if ( false !== $post_id ) {
855 - $post_type = 'post_all' == $value ? 'post' : 'page';
856 - $result = $post_type == get_post_type( $post_id );
857 - }
858 - break;
859 - case 'post_arch': // Posts - All Posts Archive
860 - case 'page_arch': // Pages - All Pages Archive
861 - $result = false;
862 - if ( is_archive() ) {
863 - $post_type = 'post_arch' == $value ? 'post' : 'page';
864 - $result = $post_type == get_post_type();
865 - }
866 - break;
867 - case 'post_cat': // Posts - All Categories Archive
868 - case 'post_tag': // Posts - All Tags Archive
869 - $result = false;
870 - if ( is_archive() && 'post' == get_post_type() ) {
871 - $taxonomy = 'post_tag' == $value ? 'post_tag' : 'category';
872 - $obj = get_queried_object();
873 -
874 - $current_taxonomy = '';
875 - if ( '' !== $obj && null !== $obj ) {
876 - $current_taxonomy = $obj->taxonomy;
877 - }
878 -
879 - if ( $current_taxonomy == $taxonomy ) {
880 - $result = true;
881 - }
882 - }
883 - break;
884 -
885 - default:
886 - $result = true;
887 - }
888 -
889 - return $this->checkByOperator( $operator, $result, true );
890 - }
891 -
892 - /**
893 - * An URL of the current page where a user who views your website is located
894 - *
895 - * @param $operator
896 - * @param $value
897 - *
898 - * @return boolean
899 - */
900 - private function location_page( $operator, $value ) {
901 - $url = $this->getCurrentUrl();
902 -
903 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
904 - }
905 -
906 - /**
907 - * A referrer URL which has brought a user to the current page
908 - *
909 - * @param $operator
910 - * @param $value
911 - *
912 - * @return boolean
913 - */
914 - private function location_referrer( $operator, $value ) {
915 - $url = $this->getRefererUrl();
916 -
917 - return $url ? $this->checkByOperator( $operator, trim( $url, '/' ), trim( $value, '/' ) ) : false;
918 - }
919 -
920 - /**
921 - * A post type of the current page
922 - *
923 - * @param $operator
924 - * @param $value
925 - *
926 - * @return boolean
927 - */
928 - private function location_post_type( $operator, $value ) {
929 - if ( is_singular() ) {
930 - return $this->checkByOperator( $operator, $value, get_post_type() );
931 - }
932 -
933 - return false;
934 - }
935 -
936 - /**
937 - * A taxonomy of the current page
938 - *
939 - * @since 2.2.8 The bug is fixed, the condition was not checked
940 - * for tachonomies, only posts.
941 - *
942 - * @param $operator
943 - * @param $value
944 - *
945 - * @return boolean
946 - */
947 - private function location_taxonomy( $operator, $value ) {
948 - $term_id = null;
949 -
950 - if ( is_tax() || is_tag() || is_category() ) {
951 - $term_id = get_queried_object()->term_id;
952 -
953 - if ( $term_id ) {
954 - return $this->checkByOperator( $operator, intval( $value ), $term_id );
955 - }
956 - }
957 -
958 - return false;
959 - }
960 -
961 -}
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 +}