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