PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / 2.7.8
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts v2.7.8
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.8, at admin/metaboxes/snippet-metabox.php

1,438 lines 46.3 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_function_redeclaration( $snippet_code, $snippet_type );
1247 if ( null !== $redeclaration ) {
1248 $this->display_validation_error(
1249 $redeclaration['line'],
1250 sprintf(
1251 // translators: %s is the fully qualified function name.
1252 __( 'Cannot redeclare function %s(). Rename the function or guard its declaration with function_exists().', 'insert-php' ),
1253 $redeclaration['name']
1254 )
1255 );
1256 return false;
1257 }
1258
1259 // Set custom error handler to catch warnings and notices.
1260 set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
1261 function ( $errno, $errstr, $errfile, $errline ) {
1262 $error_file = $errfile;
1263 // Don't let warnings/notices pass through.
1264 if ( strpos( $error_file, "eval()'d code" ) !== false ) {
1265 // Convert warnings/notices to exceptions so we can catch them.
1266 throw new ErrorException(
1267 esc_html( (string) $errstr ),
1268 0,
1269 (int) $errno,
1270 esc_html( $error_file ),
1271 (int) $errline
1272 );
1273 }
1274 return false; // Let PHP handle non-eval errors normally.
1275 }
1276 );
1277
1278 // Start output buffering to catch any output from the code.
1279 ob_start();
1280
1281 try {
1282 $result = WINP_SNIPPET_TYPE_UNIVERSAL === $snippet_type ? eval( '?> ' . $snippet_code . ' <?php ' ) : eval( $snippet_code );
1283
1284 // elimination of errors 500 in eval() functions, with the directive display_errors = off;.
1285 header( 'HTTP/1.0 200 OK' );
1286
1287 // Discard any output (echo/print statements are normal for snippets).
1288 ob_end_clean();
1289 restore_error_handler();
1290
1291 do_action( 'wbcr_inp_after_execute_snippet', get_the_ID(), $snippet_code, $result );
1292
1293 return false !== $result;
1294 } catch ( ParseError $e ) {
1295 ob_end_clean();
1296 restore_error_handler();
1297 $this->display_validation_error( $e->getLine(), $e->getMessage() );
1298 return false;
1299 } catch ( Throwable $e ) {
1300 ob_end_clean();
1301 restore_error_handler();
1302 $line = strpos( $e->getFile(), "eval()'d code" ) !== false ? $e->getLine() : 0;
1303 $this->display_validation_error( $line, $e->getMessage() );
1304 return false;
1305 }
1306 }
1307
1308 /**
1309 * Display validation error and exit
1310 *
1311 * @param int $line Line number where error occurred.
1312 * @param string $message Error message.
1313 *
1314 * @return void
1315 */
1316 private function display_validation_error( $line, $message ) {
1317 $title = __( 'Code Error Detected', 'insert-php' );
1318
1319 if ( $line > 0 ) {
1320 // translators: %d is the line number.
1321 $error_text = sprintf( __( 'This code snippet contains a fatal error on line %d:', 'insert-php' ), $line );
1322 } else {
1323 $error_text = __( 'This code snippet contains a fatal error:', 'insert-php' );
1324 }
1325 ?>
1326 <!DOCTYPE html>
1327 <html <?php language_attributes(); ?>>
1328 <head>
1329 <meta charset="<?php bloginfo( 'charset' ); ?>">
1330 <meta name="viewport" content="width=device-width, initial-scale=1.0">
1331 <title><?php echo esc_html( $title ); ?></title>
1332 <style>
1333 html {
1334 background: #f1f1f1;
1335 }
1336 body {
1337 background: #fff;
1338 border: 1px solid #ccd0d4;
1339 color: #444;
1340 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
1341 margin: 2em auto;
1342 padding: 1em 2em;
1343 max-width: 700px;
1344 -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
1345 box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
1346 }
1347 h1 {
1348 border-bottom: 1px solid #dadada;
1349 clear: both;
1350 color: #666;
1351 font-size: 24px;
1352 margin: 30px 0 0 0;
1353 padding: 0;
1354 padding-bottom: 7px;
1355 }
1356 #error-page {
1357 margin-top: 50px;
1358 }
1359 #error-page p {
1360 font-size: 14px;
1361 line-height: 1.5;
1362 margin: 25px 0 20px;
1363 }
1364 #error-page code {
1365 font-family: Consolas, Monaco, monospace;
1366 background: #f5f5f5;
1367 padding: 2px 4px;
1368 border: 1px solid #ddd;
1369 border-radius: 3px;
1370 }
1371 .error-message {
1372 background: #fff3cd;
1373 border-left: 4px solid #d63638;
1374 padding: 12px;
1375 margin: 20px 0;
1376 font-family: Consolas, Monaco, monospace;
1377 font-size: 13px;
1378 line-height: 1.6;
1379 color: #721c24;
1380 }
1381 a {
1382 color: #0073aa;
1383 text-decoration: none;
1384 }
1385 a:hover,
1386 a:active {
1387 color: #006799;
1388 }
1389 a:focus {
1390 color: #124964;
1391 -webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, 0.8);
1392 box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, 0.8);
1393 outline: none;
1394 }
1395 .button {
1396 background: #f6f7f7;
1397 border: 1px solid #0073aa;
1398 color: #0073aa;
1399 display: inline-block;
1400 text-decoration: none;
1401 font-size: 13px;
1402 line-height: 2;
1403 height: 28px;
1404 margin: 0;
1405 padding: 0 10px 1px;
1406 cursor: pointer;
1407 border-radius: 3px;
1408 white-space: nowrap;
1409 box-sizing: border-box;
1410 }
1411 .button:hover {
1412 background: #f0f0f1;
1413 border-color: #006799;
1414 color: #006799;
1415 }
1416 </style>
1417 </head>
1418 <body id="error-page">
1419 <h1><?php echo esc_html( $title ); ?></h1>
1420 <p><?php echo esc_html( $error_text ); ?></p>
1421 <div class="error-message"><?php echo esc_html( $message ); ?></div>
1422 <p><?php esc_html_e( 'Your previous snippet version is safe. Your site is unaffected.', 'insert-php' ); ?></p>
1423 <p>
1424 <?php esc_html_e( 'Click your browser\'s back button to return and fix the error.', 'insert-php' ); ?>
1425 <?php esc_html_e( 'You can also close this page to discard your changes.', 'insert-php' ); ?>
1426 </p>
1427 <p>
1428 <a href="javascript:history.back()" class="button">&larr; <?php esc_html_e( 'Return to Editor', 'insert-php' ); ?></a>
1429 </p>
1430 </body>
1431 </html>
1432 <?php
1433 exit;
1434 }
1435 }
1436
1437 new WINP_Snippet_MetaBox();
1438