PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / 2.7.9
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts v2.7.9
2.7.9 2.7.8 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 All 30 releases
insert-php / admin / metaboxes / snippet-metabox.php

snippet-metabox.php in Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts 2.7.9, at admin/metaboxes/snippet-metabox.php

1,431 lines 46.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Cloud Sync Metabox
4 *
5 * @package Woody_Code_Snippets
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Cloud Sync Metabox
15 */
16 class WINP_Snippet_MetaBox {
17
18 /**
19 * Constructor
20 */
21 public function __construct() {
22 add_action(
23 'add_meta_boxes',
24 function () {
25 add_meta_box(
26 'winp-cloud-sync',
27 __( 'Sync to Cloud Library', 'insert-php' ),
28 [ $this, 'render_cloud_sync' ],
29 WINP_SNIPPETS_POST_TYPE,
30 'side',
31 'default'
32 );
33
34 add_meta_box(
35 'winp-base-options',
36 __( 'General Options', 'insert-php' ),
37 [ $this, 'render_base_options' ],
38 WINP_SNIPPETS_POST_TYPE,
39 'normal',
40 'default'
41 );
42
43 $snippet_type = WINP_Helper::get_snippet_type();
44
45 if ( WINP_SNIPPET_TYPE_PHP !== $snippet_type ) {
46 add_meta_box(
47 'winp-snippet-conditions',
48 __( 'Display Conditions', 'insert-php' ),
49 [ $this, 'render_snippet_conditions' ],
50 WINP_SNIPPETS_POST_TYPE,
51 'normal',
52 'default'
53 );
54 }
55
56 add_meta_box(
57 'winp-code-revisions',
58 __( 'Code Revisions', 'insert-php' ),
59 [ $this, 'render_code_revisions' ],
60 WINP_SNIPPETS_POST_TYPE,
61 'normal',
62 'default'
63 );
64 }
65 );
66
67 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
68 add_action( 'admin_enqueue_scripts', [ $this, 'deregister_default_editor_resourses' ] );
69
70 add_action( 'admin_head', [ $this, 'remove_media_button' ] );
71 add_filter( 'wp_default_editor', [ $this, 'set_default_editor' ] );
72 add_action( 'admin_footer-post.php', [ $this, 'print_code_editor_scripts' ], 99 );
73 add_action( 'admin_footer-post-new.php', [ $this, 'print_code_editor_scripts' ], 99 );
74 add_action( 'edit_form_after_editor', [ $this, 'php_editor_markup' ], 10, 1 );
75
76 add_filter( 'admin_body_class', [ $this, 'admin_body_class' ] );
77 add_action( 'edit_form_top', [ $this, 'edit_form_top' ] );
78 add_action( 'post_submitbox_misc_actions', [ $this, 'post_submitbox_show_shortcode' ] );
79 add_action( 'edit_form_after_title', [ $this, 'keep_html_entities' ] );
80
81 add_filter( 'pre_post_content', [ $this, 'stop_post_filters' ] );
82 add_filter( 'content_save_pre', [ $this, 'init_post_filters' ], 9999 );
83
84 add_action( 'save_post', [ $this, 'on_saving_snippet' ], 11 );
85 add_action( 'save_post', [ $this, 'after_saving_snippet' ], 15 );
86 }
87
88 /**
89 * Enqueue modal assets
90 *
91 * @param string $hook Hook suffix.
92 *
93 * @return void
94 */
95 public function enqueue_assets( $hook ) {
96 if ( ! in_array( $hook, [ 'post.php', 'post-new.php' ] ) ) {
97 return;
98 }
99
100 $screen = get_current_screen();
101 if ( ! $screen || WINP_SNIPPETS_POST_TYPE !== $screen->post_type ) {
102 return;
103 }
104
105 $post_id = get_the_ID();
106 if ( ! $post_id ) {
107 return;
108 }
109
110 $asset_file = include WINP_PLUGIN_DIR . '/admin/assets/metabox/build/index.asset.php';
111
112 wp_enqueue_style(
113 'winp-metabox-styles',
114 WINP_PLUGIN_URL . '/admin/assets/metabox/build/style-index.css',
115 [],
116 $asset_file['version']
117 );
118
119 wp_enqueue_script(
120 'winp-metabox-scripts',
121 WINP_PLUGIN_URL . '/admin/assets/metabox/build/index.js',
122 $asset_file['dependencies'],
123 $asset_file['version'],
124 true
125 );
126
127 wp_set_script_translations( 'winp-metabox-scripts', 'insert-php' );
128
129 $api_object = WINP_Plugin::app()->get_api_object();
130 $is_changed = $api_object->is_key() ? $api_object->is_changed( $post_id ) === true : false;
131
132 // Get revisions data.
133 $revisions_data = $this->get_revisions_data( $post_id );
134
135 // Get snippet filters for conditions.
136 $snippet_filters = get_post_meta( $post_id, 'wbcr_inp_snippet_filters', true );
137 $changed_filters = get_post_meta( $post_id, 'wbcr_inp_changed_filters', true );
138
139 // Get grouped filter params for conditions dropdown.
140 $grouped_filter_params = $this->get_grouped_filter_params();
141
142 // Get location options for base-options dropdown.
143 $location_options = $this->get_location_options();
144
145 // Get WPML languages if available.
146 $wpml_languages = [];
147 if ( defined( 'WPML_PLUGIN_FILE' ) ) {
148 $wpml = apply_filters( 'wpml_active_languages', null, null );
149 if ( is_array( $wpml ) ) {
150 foreach ( $wpml as $item ) {
151 $wpml_languages[] = [
152 'code' => $item['code'],
153 'name' => $item['native_name'],
154 'flag' => $item['country_flag_url'],
155 ];
156 }
157 }
158 }
159
160 wp_localize_script(
161 'winp-metabox-scripts',
162 'winpMetaboxData',
163 [
164 'snippetId' => $post_id,
165 'isPremium' => $api_object->is_key(),
166 'isChanged' => $is_changed,
167 'upgradeUrl' => tsdk_utmify( WINP_UPGRADE, 'cloud_sync_metabox', 'upgrade_button' ),
168 'revisions' => $revisions_data['revisions'],
169 'deleteNonce' => wp_create_nonce( 'winp_rev_delete' ),
170 'restoreNonces' => $revisions_data['restoreNonces'],
171 'snippetFilters' => ! empty( $snippet_filters ) ? $snippet_filters : [],
172 'changedFilters' => ! empty( $changed_filters ) ? intval( $changed_filters ) : 0,
173 'groupedFiltersParams' => $grouped_filter_params,
174 'locationOptions' => $location_options,
175 'wpmlLanguages' => $wpml_languages,
176 ]
177 );
178
179 // Enqueue code editor styles.
180 wp_enqueue_style( 'winp-ccm', WINP_PLUGIN_URL . '/admin/assets/dist/css/ccm.min.css', [], WINP_PLUGIN_VERSION );
181 wp_enqueue_style( 'winp-code-editor-style', WINP_PLUGIN_URL . '/admin/assets/css/code-editor-style.css', [], WINP_PLUGIN_VERSION );
182 wp_enqueue_style( 'winp-snippet-edit', WINP_PLUGIN_URL . '/admin/assets/css/snippet-edit.css', [], WINP_PLUGIN_VERSION );
183
184 $code_editor_theme = get_option( 'wbcr_inp_code_editor_theme' );
185
186 if ( ! empty( $code_editor_theme ) && 'default' !== $code_editor_theme ) {
187 wp_enqueue_style( 'winp-codemirror-theme', WINP_PLUGIN_URL . '/admin/assets/css/cmthemes/' . $code_editor_theme . '.css', [], WINP_PLUGIN_VERSION );
188 }
189
190 global $post;
191
192 if ( empty( $post ) || WINP_SNIPPETS_POST_TYPE !== $post->post_type ) {
193 return;
194 }
195
196 $snippet_type = WINP_Helper::get_snippet_type();
197
198 // Enqueue snippet validation script.
199 wp_enqueue_script( 'winp-snippet-validation', WINP_PLUGIN_URL . '/admin/assets/js/snippet-validation.js', [ 'jquery' ], WINP_PLUGIN_VERSION, true );
200 wp_localize_script(
201 'winp-snippet-validation',
202 'wbcrInpValidation',
203 [
204 'nonce' => wp_create_nonce( 'winp_validate_snippet_' . $post->ID ),
205 'postType' => WINP_SNIPPETS_POST_TYPE,
206 'validatingText' => __( 'Validating code...', 'insert-php' ),
207 'errorTitle' => __( 'Code Validation Error', 'insert-php' ),
208 'errorText' => __( 'The code snippet contains errors. Please fix them before publishing.', 'insert-php' ),
209 ]
210 );
211
212 if ( WINP_SNIPPET_TYPE_TEXT === $snippet_type || WINP_SNIPPET_TYPE_AD === $snippet_type ) {
213 return;
214 }
215
216 $code_editor_mode = 'application/x-httpd-php';
217 if ( WINP_SNIPPET_TYPE_PHP === $snippet_type ) {
218 $code_editor_mode = 'text/x-php';
219
220 wp_enqueue_script( 'phpparser', WINP_PLUGIN_URL . '/admin/assets/js/codemirror/php-parser.js', [ 'wp-codemirror' ], WINP_PLUGIN_VERSION, true );
221 wp_enqueue_script( 'phplint', WINP_PLUGIN_URL . '/admin/assets/js/codemirror/php-lint.js', [ 'wp-codemirror', 'phpparser' ], WINP_PLUGIN_VERSION, true );
222 } elseif ( WINP_SNIPPET_TYPE_CSS === $snippet_type ) {
223 $code_editor_mode = 'text/css';
224
225 wp_enqueue_script( 'csslint' );
226 } elseif ( WINP_SNIPPET_TYPE_JS === $snippet_type ) {
227 $code_editor_mode = 'application/javascript';
228
229 wp_enqueue_script( 'esprima' );
230 wp_enqueue_script( 'jshint' );
231 wp_enqueue_script( 'jsonlint' );
232 } elseif ( WINP_SNIPPET_TYPE_HTML === $snippet_type ) {
233 $code_editor_mode = 'text/html';
234
235 wp_enqueue_script( 'htmlhint' );
236 if ( ! current_user_can( 'unfiltered_html' ) ) {
237 wp_enqueue_script( 'htmlhint-kses' );
238 }
239 }
240
241 $code_editor_theme = get_option( 'wbcr_inp_code_editor_theme' );
242
243 $settings = [
244 'type' => $code_editor_mode,
245 'codemirror' => [
246 'mode' => [
247 'name' => $code_editor_mode,
248 'startOpen' => WINP_SNIPPET_TYPE_PHP !== $snippet_type,
249 ],
250 'theme' => ! empty( $code_editor_theme ) && 'default' !== $code_editor_theme ? $code_editor_theme : 'default',
251 'matchBrackets' => true,
252 'styleActiveLine' => true,
253 'continueComments' => true,
254 'autoCloseTags' => true,
255 'inputStyle' => 'contenteditable',
256 'direction' => 'ltr',
257 'lint' => true,
258 'gutters' => [ 'CodeMirror-lint-markers' ],
259 'matchTags' => [
260 'bothTags' => true,
261 ],
262 'extraKeys' => [
263 'Ctrl-Space' => 'autocomplete',
264 'Ctrl-/' => 'toggleComment',
265 'Cmd-/' => 'toggleComment',
266 'Alt-F' => 'findPersistent',
267 'Ctrl-F' => 'findPersistent',
268 'Cmd-F' => 'findPersistent',
269 ],
270 'indentWithTabs' => get_option( 'wbcr_inp_code_editor_indent_with_tabs', true ),
271 'tabSize' => (int) get_option( 'wbcr_inp_code_editor_tab_size', 4 ),
272 'indentUnit' => (int) get_option( 'wbcr_inp_code_editor_indent_size', 4 ),
273 'lineNumbers' => get_option( 'wbcr_inp_code_editor_line_numbers', true ),
274 'lineWrapping' => get_option( 'wbcr_inp_code_editor_wrap_lines', true ),
275 'autoCloseBrackets' => get_option( 'wbcr_inp_code_editor_auto_close_brackets', true ),
276 ],
277 ];
278
279 if ( get_option( 'wbcr_inp_code_editor_highlight_selection_matches', true ) ) {
280 $settings['codemirror']['highlightSelectionMatches'] = [
281 'showToken' => true,
282 'style' => 'winp-matchhighlight',
283 ];
284 } else {
285 $settings['codemirror']['highlightSelectionMatches'] = false;
286 }
287
288 wp_enqueue_code_editor( $settings );
289
290 wp_add_inline_script(
291 'wp-codemirror',
292 'window.CodeMirror = wp.CodeMirror;'
293 );
294 }
295
296 /**
297 * Render metabox content
298 *
299 * @return void
300 */
301 public function render_cloud_sync() {
302 ?>
303 <div id="winp-cloud-sync-root"></div>
304 <?php
305 }
306
307 /**
308 * Render Base Options metabox content
309 *
310 * @return void
311 */
312 public function render_base_options() {
313 global $post;
314 $snippet_type = WINP_Helper::get_snippet_type();
315 $snippet_scope = get_post_meta( $post->ID, 'wbcr_inp_snippet_scope', true );
316 $custom_name = get_post_meta( $post->ID, 'wbcr_inp_snippet_custom_name', true );
317 $snippet_location = get_post_meta( $post->ID, 'wbcr_inp_snippet_location', true );
318 $snippet_p_number = get_post_meta( $post->ID, 'wbcr_inp_snippet_p_number', true );
319 $snippet_linking = get_post_meta( $post->ID, 'wbcr_inp_snippet_linking', true );
320 $snippet_description = get_post_meta( $post->ID, 'wbcr_inp_snippet_description', true );
321 $snippet_tags = get_post_meta( $post->ID, 'wbcr_inp_snippet_tags', true );
322 $snippet_wpml_lang = get_post_meta( $post->ID, 'wbcr_inp_snippet_wpml_lang', true );
323 ?>
324 <input id="winp_snippet_scope" name="wbcr_inp_snippet_scope" value="<?php echo esc_attr( $snippet_scope ? $snippet_scope : 'shortcode' ); ?>" type="hidden"/>
325 <input id="winp_snippet_custom_name" name="wbcr_inp_snippet_custom_name" value="<?php echo esc_attr( $custom_name ); ?>" type="hidden"/>
326 <?php if ( WINP_SNIPPET_TYPE_PHP !== $snippet_type ) : ?>
327 <input id="winp_snippet_location" name="wbcr_inp_snippet_location" value="<?php echo esc_attr( $snippet_location ? $snippet_location : 'header' ); ?>" type="hidden"/>
328 <input id="winp_snippet_p_number" name="wbcr_inp_snippet_p_number" value="<?php echo esc_attr( $snippet_p_number ? $snippet_p_number : '0' ); ?>" type="hidden"/>
329 <?php endif; ?>
330 <?php if ( WINP_SNIPPET_TYPE_CSS === $snippet_type || WINP_SNIPPET_TYPE_JS === $snippet_type ) : ?>
331 <input id="winp_snippet_linking" name="wbcr_inp_snippet_linking" value="<?php echo esc_attr( $snippet_linking ? $snippet_linking : 'external' ); ?>" type="hidden"/>
332 <?php endif; ?>
333 <input id="winp_snippet_description" name="wbcr_inp_snippet_description" value="<?php echo esc_attr( $snippet_description ); ?>" type="hidden"/>
334 <?php if ( WINP_SNIPPET_TYPE_TEXT !== $snippet_type && WINP_SNIPPET_TYPE_AD !== $snippet_type ) : ?>
335 <input id="winp_snippet_tags" name="wbcr_inp_snippet_tags" value="<?php echo esc_attr( $snippet_tags ); ?>" type="hidden"/>
336 <?php endif; ?>
337 <?php if ( defined( 'WPML_PLUGIN_FILE' ) ) : ?>
338 <input id="winp_snippet_wpml_lang" name="wbcr_inp_snippet_wpml_lang" value="<?php echo esc_attr( $snippet_wpml_lang ); ?>" type="hidden"/>
339 <?php endif; ?>
340 <div id="winp-base-options-root"></div>
341 <?php
342 }
343
344 /**
345 * Render Code Revisions metabox content
346 *
347 * @return void
348 */
349 public function render_code_revisions() {
350 ?>
351 <div id="winp-code-revisions-root"></div>
352 <?php
353 }
354
355 /**
356 * Render Snippet Conditions metabox content
357 *
358 * @param WP_Post $post Current post object.
359 *
360 * @return void
361 */
362 public function render_snippet_conditions( $post ) {
363 ?>
364 <?php $changed_filters = get_post_meta( $post->ID, 'wbcr_inp_changed_filters', true ); ?>
365 <input id="winp_changed_filters" name="wbcr_inp_changed_filters" value="<?php echo empty( $changed_filters ) ? 0 : 1; ?>" type="hidden"/>
366 <?php $snippet_filters = get_post_meta( $post->ID, 'wbcr_inp_snippet_filters', true ); ?>
367 <input id="winp_visibility_filters" name="wbcr_inp_snippet_filters"
368 value='<?php echo ! empty( $snippet_filters ) ? wp_json_encode( $snippet_filters ) : '[]'; ?>'
369 type="hidden"/>
370 <?php wp_nonce_field( 'wbcr_inp_snippet_' . $post->ID . '_conditions_metabox', 'wbcr_inp_snippet_conditions_metabox_nonce' ); ?>
371 <div id="winp-snippet-conditions-root"></div>
372 <?php
373 }
374
375 /**
376 * Get revisions data for the snippet
377 *
378 * @param int $post_id Post ID.
379 *
380 * @return array{revisions: array<int, array{id: int, date: string, author: string, canDelete: bool, canRestore: bool}>, restoreNonces: array<int, string>} Revisions data with nonces.
381 */
382 private function get_revisions_data( $post_id ) {
383 if ( ! $post_id ) {
384 return [
385 'revisions' => [],
386 'restoreNonces' => [],
387 ];
388 }
389
390 $revisions = wp_get_post_revisions(
391 $post_id,
392 [
393 'numberposts' => 10,
394 'order' => 'DESC',
395 'orderby' => 'date ID',
396 ]
397 );
398
399 if ( empty( $revisions ) ) {
400 return [
401 'revisions' => [],
402 'restoreNonces' => [],
403 ];
404 }
405
406 $snippet = get_post( $post_id );
407 if ( ! $snippet instanceof WP_Post ) {
408 return [
409 'revisions' => [],
410 'restoreNonces' => [],
411 ];
412 }
413
414 $user_id = get_current_user_id();
415 $user_can_edit = current_user_can( 'edit_' . WINP_SNIPPETS_POST_TYPE . 's' );
416 $user_can_edit_other = current_user_can( 'edit_others_' . WINP_SNIPPETS_POST_TYPE . 's' );
417 $user_can_delete = current_user_can( 'delete_' . WINP_SNIPPETS_POST_TYPE );
418 $user_can_delete_other = current_user_can( 'delete_others_' . WINP_SNIPPETS_POST_TYPE . 's' );
419
420 $revisions_data = [];
421 $restore_nonces = [];
422
423 foreach ( $revisions as $revision ) {
424 if ( ! $revision instanceof WP_Post ) {
425 continue;
426 }
427
428 // Skip autosaves.
429 if ( wp_is_post_autosave( $revision ) ) {
430 continue;
431 }
432
433 $author = get_userdata( (int) $revision->post_author );
434 $can_delete = ( (int) $revision->post_author === $user_id && $user_can_delete ) || ( (int) $revision->post_author !== $user_id && $user_can_delete_other );
435 $can_restore = ( (int) $snippet->post_author === $user_id && $user_can_edit ) || ( (int) $snippet->post_author !== $user_id && $user_can_edit_other );
436
437 $revisions_data[] = [
438 'id' => $revision->ID,
439 'date' => date_i18n( 'M j, Y @ H:i', strtotime( $revision->post_modified ) ),
440 'author' => $author ? $author->display_name : __( 'Unknown', 'insert-php' ),
441 'canDelete' => $can_delete,
442 'canRestore' => $can_restore,
443 ];
444
445 // Generate restore nonce for each revision.
446 $restore_nonces[ $revision->ID ] = wp_create_nonce( 'winp_rev_' . $revision->ID . '_restore' );
447 }
448
449 return [
450 'revisions' => $revisions_data,
451 'restoreNonces' => $restore_nonces,
452 ];
453 }
454
455 /**
456 * Disable post filtering. Snippets code cannot be filtered, otherwise it will cause errors.
457 *
458 * @param mixed $value The content value.
459 *
460 * @return mixed
461 */
462 public function stop_post_filters( $value ) {
463 global $wbcr__has_kses, $wbcr__has_targeted_link_rel_filters;
464
465 $screen = get_current_screen();
466 if ( empty( $screen ) || WINP_SNIPPETS_POST_TYPE !== $screen->post_type ) {
467 return $value;
468 }
469
470 $snippet_type = WINP_Helper::get_snippet_type();
471
472 if ( WINP_SNIPPET_TYPE_TEXT !== $snippet_type && WINP_SNIPPET_TYPE_AD !== $snippet_type ) {
473 // Prevent content filters from corrupting JSON in post_content.
474 $wbcr__has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) );
475 if ( $wbcr__has_kses ) {
476 kses_remove_filters();
477 }
478 $wbcr__has_targeted_link_rel_filters = ( false !== has_filter( 'content_save_pre', 'wp_targeted_link_rel' ) );
479 if ( $wbcr__has_targeted_link_rel_filters ) {
480 remove_filter( 'content_save_pre', 'wp_targeted_link_rel' );
481 }
482 }
483
484 return $value;
485 }
486
487 /**
488 * Enable post filtering.
489 *
490 * @param mixed $value The content value.
491 *
492 * @return mixed
493 */
494 public function init_post_filters( $value ) {
495 global $wbcr__has_kses, $wbcr__has_targeted_link_rel_filters;
496
497 $screen = get_current_screen();
498 if ( empty( $screen ) || WINP_SNIPPETS_POST_TYPE !== $screen->post_type ) {
499 return $value;
500 }
501
502 if ( $wbcr__has_kses ) {
503 kses_init_filters();
504 }
505
506 if ( $wbcr__has_targeted_link_rel_filters ) {
507 add_filter( 'content_save_pre', 'wp_targeted_link_rel' );
508 }
509
510 unset( $wbcr__has_kses );
511 unset( $wbcr__has_targeted_link_rel_filters );
512
513 return $value;
514 }
515
516 /**
517 * Add the codemirror editor in the `post` screen
518 *
519 * @param WP_Post|null $post Current post object.
520 *
521 * @return void
522 */
523 public function keep_html_entities( $post ) {
524 $current_screen = get_current_screen();
525
526 if ( ! $post || ! $current_screen || ( WINP_SNIPPETS_POST_TYPE !== $current_screen->post_type ) ) {
527 return;
528 }
529
530 if ( get_option( 'wbcr_inp_keep_html_entities' ) && strstr( $post->post_content, '&' ) ) {
531
532 // First the ampresands.
533 $post->post_content = str_replace( '&amp', htmlentities( '&amp' ), $post->post_content );
534
535 // Then the rest of the entities.
536 $html_flags = defined( 'ENT_HTML5' ) ? ENT_QUOTES | ENT_HTML5 : ENT_QUOTES;
537 $entities = get_html_translation_table( HTML_ENTITIES, $html_flags );
538
539 $amp_entity_key = array_search( '&amp;', $entities, true );
540 if ( false !== $amp_entity_key ) {
541 unset( $entities[ $amp_entity_key ] );
542 }
543
544 $entity_patterns = array_map(
545 static function ( $entity ) {
546 return preg_quote( rtrim( (string) $entity, ';' ), '/' );
547 },
548 $entities
549 );
550 $regular_expression = '/(' . implode( '|', $entity_patterns ) . ')/i';
551
552 preg_match_all( $regular_expression, $post->post_content, $matches );
553
554 if ( isset( $matches[0] ) && count( $matches[0] ) > 0 ) {
555 foreach ( $matches[0] as $_entity ) {
556 $post->post_content = str_replace( $_entity, htmlentities( $_entity ), $post->post_content );
557 }
558 }
559 }
560 }
561
562 /**
563 * Remove media button
564 *
565 * @return void
566 */
567 public function remove_media_button() {
568 global $post;
569
570 if ( empty( $post ) || WINP_SNIPPETS_POST_TYPE !== $post->post_type ) {
571 return;
572 }
573
574 $snippet_type = WINP_Helper::get_snippet_type();
575 if ( WINP_SNIPPET_TYPE_AD === $snippet_type ) {
576 return;
577 }
578
579 remove_action( 'media_buttons', 'media_buttons' );
580 }
581
582 /**
583 * Set default editor based on snippet type
584 *
585 * @param string $type Editor type.
586 *
587 * @return string
588 */
589 public function set_default_editor( $type ) {
590 global $post;
591
592 if ( empty( $post ) || WINP_SNIPPETS_POST_TYPE !== $post->post_type ) {
593 return $type;
594 }
595
596 $snippet_type = WINP_Helper::get_snippet_type();
597 if ( WINP_SNIPPET_TYPE_AD === $snippet_type ) {
598 return 'html';
599 }
600
601 return $type;
602 }
603
604 /**
605 * Deregister other CodeMirror styles
606 *
607 * @return void
608 */
609 public function deregister_default_editor_resourses() {
610 global $post;
611
612 if ( empty( $post ) || WINP_SNIPPETS_POST_TYPE !== $post->post_type ) {
613 return;
614 }
615
616 /* Remove other CodeMirror styles */
617 wp_deregister_style( 'codemirror' );
618 }
619
620 /**
621 * Print Code Editor scripts
622 *
623 * @return void
624 */
625 public function print_code_editor_scripts() {
626 global $post;
627
628 if ( empty( $post ) || WINP_SNIPPETS_POST_TYPE !== $post->post_type ) {
629 return;
630 }
631
632 $snippet_type = WINP_Helper::get_snippet_type();
633 if ( WINP_SNIPPET_TYPE_TEXT === $snippet_type || WINP_SNIPPET_TYPE_AD === $snippet_type ) {
634 return;
635 }
636 ?>
637 <script>
638 /* Loads CodeMirror on the snippet editor */
639 (function () {
640 var editor = wp.codeEditor.initialize( document.getElementById( 'post_content' ), wp.codeEditor.defaultSettings.codemirror );
641
642 if ( editor && editor.codemirror ) {
643 var existingKeys = editor.codemirror.getOption( 'extraKeys' ) || {};
644 var newKeys = {};
645
646 for ( var key in existingKeys ) {
647 if ( existingKeys.hasOwnProperty( key ) ) {
648 newKeys[key] = existingKeys[key];
649 }
650 }
651
652 // Add new key binding
653 newKeys['Ctrl-S'] = function( cm ) {
654 document.getElementById( 'publish' ).click();
655 };
656
657 newKeys['Cmd-S'] = function( cm ) {
658 document.getElementById( 'publish' ).click();
659 };
660
661 editor.codemirror.setOption( 'extraKeys', newKeys );
662 }
663 })();
664
665 jQuery(document).ready(function ($) {
666 $('.wp-editor-tabs').remove();
667 });
668 </script>
669 <?php
670 }
671
672 /**
673 * Markup PHP snippet editor.
674 *
675 * @param WP_Post $post Post Object.
676 *
677 * @return void
678 */
679 public function php_editor_markup( $post ) {
680
681 if ( WINP_SNIPPETS_POST_TYPE !== $post->post_type ) {
682 return;
683 }
684
685 $snippet_type = WINP_Helper::get_snippet_type();
686 if ( WINP_SNIPPET_TYPE_TEXT === $snippet_type || WINP_SNIPPET_TYPE_AD === $snippet_type ) {
687 return;
688 }
689
690 wp_nonce_field( basename( __FILE__ ), WINP_SNIPPETS_POST_TYPE );
691
692 $snippet_code = WINP_Helper::get_snippet_code( $post );
693
694 ?>
695 <div class="wp-editor-container winp-editor-container">
696 <textarea id="post_content" name="post_content" class="wp-editor-area winp-php-content"><?php echo esc_html( $snippet_code ); ?></textarea>
697 </div>
698 <?php
699 }
700
701 /**
702 * Adds one or more classes to the body tag in the dashboard.
703 *
704 * @param string $classes Current body classes.
705 *
706 * @return string Altered body classes.
707 */
708 public function admin_body_class( $classes ) {
709 global $post;
710
711 if ( ! empty( $post ) && WINP_SNIPPETS_POST_TYPE === $post->post_type ) {
712 $snippet_type = WINP_Helper::get_snippet_type();
713
714 $new_classes = 'wbcr-inp-snippet-type-' . esc_attr( $snippet_type );
715
716 if ( WINP_SNIPPET_TYPE_TEXT !== $snippet_type && WINP_SNIPPET_TYPE_AD !== $snippet_type ) {
717 $new_classes .= ' winp-snippet-enabled';
718 }
719
720 return ' ' . $new_classes . ' ' . $classes;
721 }
722
723 return $classes;
724 }
725
726 /**
727 * Add hidden tag to edit post form
728 * Set post title for snippet post with status auto-draft
729 *
730 * @param WP_Post|null $current_post Current post object.
731 *
732 * @return void
733 */
734 public function edit_form_top( $current_post ) {
735 if ( ! $current_post || WINP_SNIPPETS_POST_TYPE !== $current_post->post_type ) {
736 return;
737 }
738
739 $snippet_type = WINP_HTTP::get( 'winp_item', WINP_SNIPPET_TYPE_PHP, 'sanitize_key' );
740 $snippet_type = WINP_Helper::getMetaOption( $current_post->ID, 'snippet_type', $snippet_type );
741
742 echo '<input type="hidden" id="wbcr_inp_snippet_type" name="wbcr_inp_snippet_type" value="' . esc_attr( $snippet_type ) . '">';
743
744 if ( 'auto-draft' === $current_post->post_status && WINP_Helper::getMetaOption( $current_post->ID, 'snippet_draft', false ) ) {
745 global $post;
746
747 $retrieved_post = get_post( $current_post->ID );
748 if ( $retrieved_post ) {
749 $post->post_title = $retrieved_post->post_title;
750 }
751 }
752 }
753
754 /**
755 * Show shortcode in the publish box
756 *
757 * @param WP_Post|null $post Current post object.
758 *
759 * @return void
760 */
761 public function post_submitbox_show_shortcode( $post ) {
762 if ( ! $post || ( WINP_SNIPPETS_POST_TYPE !== $post->post_type ) ) {
763 return;
764 }
765
766 if ( WINP_Helper::getMetaOption( $post->ID, 'snippet_draft', false ) ) {
767 return;
768 }
769
770 $snippet_scope = WINP_Helper::getMetaOption( $post->ID, 'snippet_scope' );
771 $value = '';
772 $shortcode = '';
773
774 if ( 'shortcode' === $snippet_scope ) {
775 $shortcode = WINP_Helper::get_where_use_text( $post );
776 } else {
777 $value = WINP_Helper::get_where_use_text( $post );
778 $shortcode = WINP_Helper::get_shortcode_text( $post );
779 }
780 echo "<div class='wbcr_inp_shortcode_input_container'><label for='wbcr_inp_shortcode_input'>" . esc_html__( 'Shortcode:', 'insert-php' ) . '</label>';
781 echo "<div style='position: relative; display: inline-flex; align-items: center; width: 100%;'>";
782 echo "<span class='dashicons dashicons-clipboard' style='position: absolute; top:10px; left: 8px; pointer-events: none; color: #2271b1;'></span>";
783 echo "<input type='text' name='wbcr_inp_shortcode_input' class='wbcr_inp_shortcode_input' value='" . esc_attr( $shortcode ) . "' readonly='readonly' style='cursor: pointer; padding-left: 35px; width: 100%;'>";
784 echo '</div></div>';
785 echo "<div class='wbcr_inp_whereuse_input_container'><label for='wbcr_inp_whereuse_input'>" . esc_html__( 'Location:', 'insert-php' ) . '</label>';
786 echo "<input type='text' name='wbcr_inp_whereuse_input' class='wbcr_inp_whereuse_input' value='" . esc_attr( $value ) . "' readonly='readonly'>";
787 echo '</div>';
788 }
789
790 /**
791 * Get grouped filter parameters for conditions
792 *
793 * @return array<array<string, mixed>> Grouped filter parameters
794 */
795 private function get_grouped_filter_params() {
796 $grouped_filter_params = [
797 [
798 'id' => 'user',
799 'title' => __( 'User', 'insert-php' ),
800 'items' => [
801 [
802 'id' => 'user-role',
803 'title' => __( 'Role', 'insert-php' ),
804 'type' => 'select',
805 'values' => [
806 'type' => 'ajax',
807 'action' => 'wbcr_inp_ajax_get_user_roles',
808 ],
809 'description' => __( 'A role of the user who views your website. The role "guest" is applied to unregistered users.', 'insert-php' ),
810 ],
811 [
812 'id' => 'user-registered',
813 'title' => __( 'Registration Date', 'insert-php' ),
814 'type' => 'date',
815 'description' => __( 'The date when the user who views your website was registered. For unregistered users this date always equals to 1 Jan 1970.', 'insert-php' ),
816 ],
817 [
818 'id' => 'user-cookie-name',
819 'title' => __( 'Cookie Name', 'insert-php' ),
820 'type' => 'text',
821 'onlyEquals' => true,
822 'description' => __( 'Determines whether the user\'s browser has a cookie with a given name.', 'insert-php' ),
823 ],
824 ],
825 ],
826 [
827 'id' => 'location',
828 'title' => __( 'Location', 'insert-php' ),
829 'items' => [
830 [
831 'id' => 'location-page',
832 'title' => __( 'Page URL', 'insert-php' ),
833 'type' => 'text',
834 'description' => __( 'The URL of the current page where a user who views your website is located.', 'insert-php' ),
835 ],
836 [
837 'id' => 'location-referrer',
838 'title' => __( 'Current Referrer', 'insert-php' ),
839 'type' => 'text',
840 'description' => __( 'A referrer URL that brought the user to the current page.', 'insert-php' ),
841 ],
842 [
843 'id' => 'location-post-type',
844 'title' => __( 'Post type', 'insert-php' ),
845 'type' => 'select',
846 'values' => [
847 'type' => 'ajax',
848 'action' => 'wbcr_inp_ajax_get_post_types',
849 ],
850 'description' => __( 'A post type of the current page.', 'insert-php' ),
851 ],
852 [
853 'id' => 'location-taxonomy',
854 'title' => __( 'Category/Tag Archive Page', 'insert-php' ),
855 'type' => 'select',
856 'values' => [
857 'type' => 'ajax',
858 'action' => 'wbcr_inp_ajax_get_taxonomies',
859 ],
860 'description' => __( 'A taxonomy archive page.', 'insert-php' ),
861 ],
862 [
863 'id' => 'page-taxonomy',
864 'title' => __( 'Categories or Tags assigned to this page', 'insert-php' ),
865 'type' => 'select',
866 'values' => [
867 'type' => 'ajax',
868 'action' => 'wbcr_inp_ajax_get_taxonomies',
869 ],
870 'description' => __( 'A taxonomy of the current page.', 'insert-php' ),
871 ],
872 [
873 'id' => 'location-some-page',
874 'title' => __( 'Page', 'insert-php' ),
875 'type' => 'select',
876 'values' => [
877 'type' => 'ajax',
878 'action' => 'wbcr_inp_ajax_get_page_list',
879 ],
880 'description' => __( 'Select from a list of specific pages.', 'insert-php' ),
881 ],
882 ],
883 ],
884 [
885 'id' => 'technology',
886 'title' => __( 'Technology', 'insert-php' ) . ' (PRO)',
887 'items' => [
888 [
889 'id' => 'technology-addblocker',
890 'title' => __( 'Addblocker', 'insert-php' ),
891 'type' => 'disabled',
892 'description' => __( 'Determines whether the user uses an ad blocker on the website.', 'insert-php' ),
893 ],
894 [
895 'id' => 'technology-browser',
896 'title' => __( 'Browser', 'insert-php' ),
897 'type' => 'disabled',
898 'description' => __( 'Determines whether the user uses the selected browser.', 'insert-php' ),
899 ],
900 [
901 'id' => 'technology-use-cookie',
902 'title' => __( 'Use cookie', 'insert-php' ),
903 'type' => 'disabled',
904 'description' => __( 'Determines whether the user uses cookies on the website.', 'insert-php' ),
905 ],
906 [
907 'id' => 'technology-use-javascript',
908 'title' => __( 'Use JavaScript', 'insert-php' ),
909 'type' => 'disabled',
910 'description' => __( 'Determines whether the user uses JavaScript on the website.', 'insert-php' ),
911 ],
912 [
913 'id' => 'technology-operating-system',
914 'title' => __( 'Operating system', 'insert-php' ),
915 'type' => 'disabled',
916 'description' => __( 'Determines whether the user uses the selected OS.', 'insert-php' ),
917 ],
918 [
919 'id' => 'technology-device-type',
920 'title' => __( 'Device type', 'insert-php' ),
921 'type' => 'disabled',
922 'description' => __( 'Determines whether the user uses the selected device type.', 'insert-php' ),
923 ],
924 ],
925 ],
926 [
927 'id' => 'auditory',
928 'title' => __( 'Auditory', 'insert-php' ) . ' (PRO)',
929 'items' => [
930 [
931 'id' => 'auditory-country',
932 'title' => __( 'User country', 'insert-php' ),
933 'type' => 'disabled',
934 'description' => __( 'Geolocation', 'insert-php' ),
935 ],
936 [
937 'id' => 'auditory-viewing',
938 'title' => __( 'Viewing depth', 'insert-php' ),
939 'type' => 'disabled',
940 'description' => __( 'Pages viewed in current session', 'insert-php' ),
941 ],
942 [
943 'id' => 'auditory-attendance',
944 'title' => __( 'By Date', 'insert-php' ),
945 'type' => 'disabled',
946 'description' => __( 'Time period when the user visited', 'insert-php' ),
947 ],
948 [
949 'id' => 'auditory-visits',
950 'title' => __( 'Total number of visits', 'insert-php' ),
951 'type' => 'disabled',
952 'description' => __( 'Total visits by this user', 'insert-php' ),
953 ],
954 ],
955 ],
956 ];
957
958 return apply_filters( 'wbcr/inp/visibility/filter_params', $grouped_filter_params );
959 }
960
961 /**
962 * Get location options for base-options dropdown.
963 *
964 * @return array<int, array{title: string, items: array<int, array{value: string, label: string, disabled: bool}>}>
965 */
966 private function get_location_options() {
967 /**
968 * Insertion Locations instance.
969 *
970 * @var WINP_Insertion_Locations
971 */
972 $winp_snippets_locations = new WINP_Insertion_Locations();
973
974 $snippet_type = WINP_Helper::get_snippet_type();
975
976 // Build location groups.
977 $location_groups = [];
978
979 // Add Everywhere group (not for TEXT/AD types).
980 if ( WINP_SNIPPET_TYPE_TEXT !== $snippet_type && WINP_SNIPPET_TYPE_AD !== $snippet_type ) {
981 $everywhere_items = $winp_snippets_locations->getInsertionForOptions( 'everywhere' );
982 if ( ! empty( $everywhere_items ) ) {
983 $location_groups[] = [
984 'title' => __( 'Everywhere', 'insert-php' ),
985 'items' => $this->format_location_items( $everywhere_items ),
986 ];
987 }
988 }
989
990 // Add Posts group.
991 $posts_items = $winp_snippets_locations->getInsertionForOptions( 'posts' );
992 if ( ! empty( $posts_items ) ) {
993 $location_groups[] = [
994 'title' => __( 'Posts, Pages, Custom post types', 'insert-php' ),
995 'items' => $this->format_location_items( $posts_items ),
996 ];
997 }
998
999 // Add Pages group.
1000 $pages_items = $winp_snippets_locations->getInsertionForOptions( 'pages' );
1001 if ( ! empty( $pages_items ) ) {
1002 $location_groups[] = [
1003 'title' => __( 'Categories, Archives, Tags, Taxonomies', 'insert-php' ),
1004 'items' => $this->format_location_items( $pages_items ),
1005 ];
1006 }
1007
1008 // Add WooCommerce group if available.
1009 $woocommerce_items = $winp_snippets_locations->getInsertionForOptions( 'woocommerce' );
1010 if ( ! empty( $woocommerce_items ) ) {
1011 $location_groups[] = [
1012 'title' => __( 'WooCommerce Locations', 'insert-php' ),
1013 'items' => $this->format_location_items( $woocommerce_items, ! defined( 'WINP_PLUGIN_ACTIVE' ) || ! class_exists( 'WooCommerce' ) ),
1014 ];
1015 }
1016
1017 // Add Custom group if available.
1018 $custom_items = $winp_snippets_locations->getInsertionForOptions( 'custom' );
1019 if ( ! empty( $custom_items ) ) {
1020 $location_groups[] = [
1021 'title' => __( 'Custom Action Hook', 'insert-php' ),
1022 'items' => $this->format_location_items( $custom_items ),
1023 ];
1024 }
1025
1026 return $location_groups;
1027 }
1028
1029 /**
1030 * Format location items for dropdown
1031 *
1032 * @param array<int, array{0: string, 1: string, 2: string, 3: bool}> $items Location items - array of [$key, $label, $hint, $requiresLocationNumber].
1033 * @param bool $disabled Whether items should be disabled.
1034 *
1035 * @return array<int, array{value: string, label: string, disabled: bool, requiresLocationNumber: bool}>
1036 */
1037 private function format_location_items( $items, $disabled = false ) {
1038 $formatted = [];
1039
1040 foreach ( $items as $item ) {
1041 $formatted[] = [
1042 'value' => $item[0],
1043 'label' => $item[1],
1044 'requiresLocationNumber' => $item[3],
1045 'disabled' => $disabled,
1046 ];
1047 }
1048
1049 return $formatted;
1050 }
1051
1052 /**
1053 * On saving snippet - save meta options before factory form saves
1054 *
1055 * @param int $post_id Post ID.
1056 *
1057 * @return void
1058 */
1059 public function on_saving_snippet( $post_id ) {
1060 // Only process snippet post type.
1061 if ( get_post_type( $post_id ) !== WINP_SNIPPETS_POST_TYPE ) {
1062 return;
1063 }
1064
1065 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
1066 return;
1067 }
1068
1069 // Check if user has permissions to save.
1070 if ( ! current_user_can( 'edit_post', $post_id ) ) {
1071 return;
1072 }
1073
1074 // Don't save any meta during revision restoration.
1075 // During restore, WordPress doesn't send form data, so we should let wp_restore_post_revision handle it.
1076 $action = WINP_HTTP::get( 'action', '', 'sanitize_key' );
1077 $revision_id = WINP_HTTP::get( 'revision', 0, 'absint' );
1078 $is_restoring = ( 'restore' === $action && ! empty( $revision_id ) );
1079
1080 if ( $is_restoring ) {
1081 return;
1082 }
1083
1084 // Skip if this is an auto-draft (initial post creation).
1085 $post_status = get_post_status( $post_id );
1086 if ( 'auto-draft' === $post_status ) {
1087 // For auto-draft, save snippet type from URL parameter if available.
1088 $url_snippet_type = WINP_HTTP::get( 'winp_item', '', 'sanitize_key' );
1089 if ( ! empty( $url_snippet_type ) ) {
1090 WINP_Helper::updateMetaOption( $post_id, 'snippet_type', $url_snippet_type );
1091 }
1092 return;
1093 }
1094
1095 // Skip if no form data is present (e.g., when trashing/untrashing).
1096 $posted_snippet_type = WINP_HTTP::post( 'wbcr_inp_snippet_type', null );
1097 if ( null === $posted_snippet_type ) {
1098 return;
1099 }
1100
1101 $editor_nonce = WINP_HTTP::post( WINP_SNIPPETS_POST_TYPE, '', true );
1102 $conditions_nonce = WINP_HTTP::post( 'wbcr_inp_snippet_conditions_metabox_nonce', '', true );
1103
1104 $has_valid_editor_nonce = is_string( $editor_nonce )
1105 && ! empty( $editor_nonce )
1106 && wp_verify_nonce( $editor_nonce, basename( __FILE__ ) );
1107 $has_valid_conditions_nonce = is_string( $conditions_nonce )
1108 && ! empty( $conditions_nonce )
1109 && wp_verify_nonce( $conditions_nonce, 'wbcr_inp_snippet_' . $post_id . '_conditions_metabox' );
1110
1111 if ( ! $has_valid_editor_nonce && ! $has_valid_conditions_nonce ) {
1112 return;
1113 }
1114
1115 $location = WINP_HTTP::post( 'wbcr_inp_snippet_location', 'header', true );
1116 WINP_Helper::updateMetaOption( $post_id, 'snippet_location', $location );
1117
1118 $p_number = WINP_HTTP::post( 'wbcr_inp_snippet_p_number', '0', true );
1119 WINP_Helper::updateMetaOption( $post_id, 'snippet_p_number', $p_number );
1120
1121 $scope = WINP_HTTP::post( 'wbcr_inp_snippet_scope', 'shortcode', true );
1122 WINP_Helper::updateMetaOption( $post_id, 'snippet_scope', $scope );
1123
1124 $type = WINP_HTTP::post( 'wbcr_inp_snippet_type', WINP_SNIPPET_TYPE_PHP, true );
1125 WINP_Helper::updateMetaOption( $post_id, 'snippet_type', $type );
1126
1127 $linking = WINP_HTTP::post( 'wbcr_inp_snippet_linking', '', true );
1128 WINP_Helper::updateMetaOption( $post_id, 'snippet_linking', $linking );
1129
1130 $description = WINP_HTTP::post( 'wbcr_inp_snippet_description', '', true );
1131 WINP_Helper::updateMetaOption( $post_id, 'snippet_description', $description );
1132
1133 $tags = WINP_HTTP::post( 'wbcr_inp_snippet_tags', '', true );
1134 WINP_Helper::updateMetaOption( $post_id, 'snippet_tags', $tags );
1135
1136 $wpml_lang = WINP_HTTP::post( 'wbcr_inp_snippet_wpml_lang', '', true );
1137 WINP_Helper::updateMetaOption( $post_id, 'snippet_wpml_lang', $wpml_lang );
1138
1139 $priority = WINP_Helper::getMetaOption( $post_id, 'snippet_priority', WINP_Helper::get_next_snippet_priority() );
1140 WINP_Helper::updateMetaOption( $post_id, 'snippet_priority', $priority );
1141
1142 $filters_raw = WINP_HTTP::post( 'wbcr_inp_snippet_filters', '', true );
1143 $filters_raw = is_string( $filters_raw ) ? $filters_raw : '';
1144 $filters = ! empty( $filters_raw ) ? json_decode( stripslashes( $filters_raw ) ) : '';
1145 WINP_Helper::updateMetaOption( $post_id, 'snippet_filters', $filters );
1146
1147 $changed_filters = WINP_HTTP::post( 'wbcr_inp_changed_filters', 0 );
1148 $changed_filters = is_numeric( $changed_filters ) ? (int) $changed_filters : 0;
1149 WINP_Helper::updateMetaOption( $post_id, 'changed_filters', $changed_filters );
1150
1151 do_action( 'wbcr/inp/base_option/on_saving_form', $post_id );
1152 }
1153
1154 /**
1155 * After saving snippet - validate code and activate if needed
1156 *
1157 * @param int $post_id Post ID.
1158 *
1159 * @return void
1160 */
1161 public function after_saving_snippet( $post_id ) {
1162 // Only process snippet post type.
1163 if ( get_post_type( $post_id ) !== WINP_SNIPPETS_POST_TYPE ) {
1164 return;
1165 }
1166
1167 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
1168 return;
1169 }
1170
1171 // Check if user has permissions to save.
1172 if ( ! current_user_can( 'edit_post', $post_id ) ) {
1173 return;
1174 }
1175
1176 // Skip validation and activation for auto-drafts.
1177 $post_status = get_post_status( $post_id );
1178 if ( 'auto-draft' === $post_status ) {
1179 return;
1180 }
1181
1182 $is_default_activate = get_option( 'wbcr_inp_activate_by_default', true );
1183 $snippet_scope = WINP_HTTP::post( 'wbcr_inp_snippet_scope', null, true );
1184 $snippet_type = WINP_Helper::get_snippet_type( $post_id );
1185 $post_content = get_post_field( 'post_content', $post_id );
1186
1187 if ( WINP_SNIPPET_TYPE_TEXT !== $snippet_type && WINP_SNIPPET_TYPE_AD !== $snippet_type ) {
1188 $snippet_content = ! empty( $post_content ) ? WINP_Plugin::app()->get_execute_object()->prepareCode( $post_content, $post_id ) : '';
1189 } else {
1190 $snippet_content = $post_content;
1191 }
1192
1193 WINP_Helper::updateMetaOption( $post_id, 'snippet_activate', false );
1194
1195 $validate = true;
1196
1197 if ( 'evrywhere' === $snippet_scope || 'auto' === $snippet_scope ) {
1198 if ( WINP_SNIPPET_TYPE_TEXT !== $snippet_type && WINP_SNIPPET_TYPE_AD !== $snippet_type && WINP_SNIPPET_TYPE_CSS !== $snippet_type && WINP_SNIPPET_TYPE_JS !== $snippet_type && WINP_SNIPPET_TYPE_HTML !== $snippet_type ) {
1199 $validate = $this->validate_code( $snippet_content, $snippet_type );
1200 } else {
1201 $validate = true;
1202 }
1203 }
1204
1205 if ( $validate ) {
1206 // Activate snippet if default activation is enabled and user has permission.
1207 if ( $is_default_activate && WINP_Plugin::app()->current_user_car() ) {
1208 WINP_Helper::updateMetaOption( $post_id, 'snippet_activate', true );
1209 }
1210 } else {
1211 // Validation failed - redirect with error.
1212 if ( ! defined( 'WP_SANDBOX_SCRAPING' ) ) {
1213 define( 'WP_SANDBOX_SCRAPING', true );
1214 }
1215 /* Display message if a parse error occurred */
1216 wp_safe_redirect(
1217 add_query_arg(
1218 [
1219 'action' => 'edit',
1220 'post' => $post_id,
1221 'wbcr_inp_save_snippet_result' => 'code-error',
1222 ],
1223 admin_url( 'post.php' )
1224 )
1225 );
1226
1227 exit;
1228 }
1229 }
1230
1231 /**
1232 * Validate the snippet code before saving to database
1233 *
1234 * @param string $snippet_code Snippet code.
1235 * @param string $snippet_type Snippet type.
1236 *
1237 * @return bool true if code is valid, false if it contains errors.
1238 */
1239 private function validate_code( $snippet_code, $snippet_type ) {
1240 $snippet_code = stripslashes( $snippet_code );
1241
1242 if ( empty( $snippet_code ) ) {
1243 return true;
1244 }
1245
1246 $redeclaration = WINP_Code_Validator::find_redeclaration( $snippet_code, $snippet_type );
1247 if ( null !== $redeclaration ) {
1248 $this->display_validation_error( $redeclaration['line'], WINP_Code_Validator::get_redeclaration_message( $redeclaration ) );
1249 return false;
1250 }
1251
1252 // Set custom error handler to catch warnings and notices.
1253 set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
1254 function ( $errno, $errstr, $errfile, $errline ) {
1255 $error_file = $errfile;
1256 // Don't let warnings/notices pass through.
1257 if ( strpos( $error_file, "eval()'d code" ) !== false ) {
1258 // Convert warnings/notices to exceptions so we can catch them.
1259 throw new ErrorException(
1260 esc_html( (string) $errstr ),
1261 0,
1262 (int) $errno,
1263 esc_html( $error_file ),
1264 (int) $errline
1265 );
1266 }
1267 return false; // Let PHP handle non-eval errors normally.
1268 }
1269 );
1270
1271 // Start output buffering to catch any output from the code.
1272 ob_start();
1273
1274 try {
1275 $result = WINP_SNIPPET_TYPE_UNIVERSAL === $snippet_type ? eval( '?> ' . $snippet_code . ' <?php ' ) : eval( $snippet_code );
1276
1277 // elimination of errors 500 in eval() functions, with the directive display_errors = off;.
1278 header( 'HTTP/1.0 200 OK' );
1279
1280 // Discard any output (echo/print statements are normal for snippets).
1281 ob_end_clean();
1282 restore_error_handler();
1283
1284 do_action( 'wbcr_inp_after_execute_snippet', get_the_ID(), $snippet_code, $result );
1285
1286 return false !== $result;
1287 } catch ( ParseError $e ) {
1288 ob_end_clean();
1289 restore_error_handler();
1290 $this->display_validation_error( $e->getLine(), $e->getMessage() );
1291 return false;
1292 } catch ( Throwable $e ) {
1293 ob_end_clean();
1294 restore_error_handler();
1295 $line = strpos( $e->getFile(), "eval()'d code" ) !== false ? $e->getLine() : 0;
1296 $this->display_validation_error( $line, $e->getMessage() );
1297 return false;
1298 }
1299 }
1300
1301 /**
1302 * Display validation error and exit
1303 *
1304 * @param int $line Line number where error occurred.
1305 * @param string $message Error message.
1306 *
1307 * @return void
1308 */
1309 private function display_validation_error( $line, $message ) {
1310 $title = __( 'Code Error Detected', 'insert-php' );
1311
1312 if ( $line > 0 ) {
1313 // translators: %d is the line number.
1314 $error_text = sprintf( __( 'This code snippet contains a fatal error on line %d:', 'insert-php' ), $line );
1315 } else {
1316 $error_text = __( 'This code snippet contains a fatal error:', 'insert-php' );
1317 }
1318 ?>
1319 <!DOCTYPE html>
1320 <html <?php language_attributes(); ?>>
1321 <head>
1322 <meta charset="<?php bloginfo( 'charset' ); ?>">
1323 <meta name="viewport" content="width=device-width, initial-scale=1.0">
1324 <title><?php echo esc_html( $title ); ?></title>
1325 <style>
1326 html {
1327 background: #f1f1f1;
1328 }
1329 body {
1330 background: #fff;
1331 border: 1px solid #ccd0d4;
1332 color: #444;
1333 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
1334 margin: 2em auto;
1335 padding: 1em 2em;
1336 max-width: 700px;
1337 -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
1338 box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
1339 }
1340 h1 {
1341 border-bottom: 1px solid #dadada;
1342 clear: both;
1343 color: #666;
1344 font-size: 24px;
1345 margin: 30px 0 0 0;
1346 padding: 0;
1347 padding-bottom: 7px;
1348 }
1349 #error-page {
1350 margin-top: 50px;
1351 }
1352 #error-page p {
1353 font-size: 14px;
1354 line-height: 1.5;
1355 margin: 25px 0 20px;
1356 }
1357 #error-page code {
1358 font-family: Consolas, Monaco, monospace;
1359 background: #f5f5f5;
1360 padding: 2px 4px;
1361 border: 1px solid #ddd;
1362 border-radius: 3px;
1363 }
1364 .error-message {
1365 background: #fff3cd;
1366 border-left: 4px solid #d63638;
1367 padding: 12px;
1368 margin: 20px 0;
1369 font-family: Consolas, Monaco, monospace;
1370 font-size: 13px;
1371 line-height: 1.6;
1372 color: #721c24;
1373 }
1374 a {
1375 color: #0073aa;
1376 text-decoration: none;
1377 }
1378 a:hover,
1379 a:active {
1380 color: #006799;
1381 }
1382 a:focus {
1383 color: #124964;
1384 -webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, 0.8);
1385 box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, 0.8);
1386 outline: none;
1387 }
1388 .button {
1389 background: #f6f7f7;
1390 border: 1px solid #0073aa;
1391 color: #0073aa;
1392 display: inline-block;
1393 text-decoration: none;
1394 font-size: 13px;
1395 line-height: 2;
1396 height: 28px;
1397 margin: 0;
1398 padding: 0 10px 1px;
1399 cursor: pointer;
1400 border-radius: 3px;
1401 white-space: nowrap;
1402 box-sizing: border-box;
1403 }
1404 .button:hover {
1405 background: #f0f0f1;
1406 border-color: #006799;
1407 color: #006799;
1408 }
1409 </style>
1410 </head>
1411 <body id="error-page">
1412 <h1><?php echo esc_html( $title ); ?></h1>
1413 <p><?php echo esc_html( $error_text ); ?></p>
1414 <div class="error-message"><?php echo esc_html( $message ); ?></div>
1415 <p><?php esc_html_e( 'Your previous snippet version is safe. Your site is unaffected.', 'insert-php' ); ?></p>
1416 <p>
1417 <?php esc_html_e( 'Click your browser\'s back button to return and fix the error.', 'insert-php' ); ?>
1418 <?php esc_html_e( 'You can also close this page to discard your changes.', 'insert-php' ); ?>
1419 </p>
1420 <p>
1421 <a href="javascript:history.back()" class="button">&larr; <?php esc_html_e( 'Return to Editor', 'insert-php' ); ?></a>
1422 </p>
1423 </body>
1424 </html>
1425 <?php
1426 exit;
1427 }
1428 }
1429
1430 new WINP_Snippet_MetaBox();
1431