PluginProbe
Code Snippets / 3.2.1
Code Snippets v3.2.1
3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 3.0.1 All 64 releases
code-snippets / php / admin-menus / class-edit-menu.php

class-edit-menu.php in Code Snippets 3.2.1, at php/admin-menus/class-edit-menu.php

860 lines 25.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Code_Snippets;
4
5 use function Code_Snippets\Settings\get_setting;
6
7 /**
8 * This class handles the add/edit menu
9 */
10 class Edit_Menu extends Admin_Menu {
11
12 /**
13 * The snippet object currently being edited
14 *
15 * @var Snippet
16 * @see Edit_Menu::load_snippet_data()
17 */
18 protected $snippet = null;
19
20 /**
21 * Constructor
22 */
23 public function __construct() {
24
25 parent::__construct(
26 'edit',
27 _x( 'Edit Snippet', 'menu label', 'code-snippets' ),
28 __( 'Edit Snippet', 'code-snippets' )
29 );
30 }
31
32 /**
33 * Register action and filter hooks
34 */
35 public function run() {
36 parent::run();
37 $this->remove_debug_bar_codemirror();
38 }
39
40 /**
41 * Register the admin menu
42 */
43 public function register() {
44 parent::register();
45
46 /* Only preserve the edit menu if we are currently editing a snippet */
47 if ( ! isset( $_REQUEST['page'] ) || $_REQUEST['page'] !== $this->slug ) {
48 remove_submenu_page( $this->base_slug, $this->slug );
49 }
50
51 /* Add New Snippet menu */
52 $this->add_menu(
53 code_snippets()->get_menu_slug( 'add' ),
54 _x( 'Add New', 'menu label', 'code-snippets' ),
55 __( 'Add New Snippet', 'code-snippets' )
56 );
57 }
58
59 /**
60 * Executed when the menu is loaded
61 */
62 public function load() {
63 parent::load();
64
65 // Retrieve the current snippet object.
66 $this->load_snippet_data();
67
68 $screen = get_current_screen();
69 $edit_hook = get_plugin_page_hookname( $this->slug, $this->base_slug );
70 if ( $screen->in_admin( 'network' ) ) {
71 $edit_hook .= '-network';
72 }
73
74 // Disallow visiting the edit snippet page without a valid ID.
75 if ( $screen->base === $edit_hook && ( empty( $_REQUEST['id'] ) || 0 === $this->snippet->id || null === $this->snippet->id ) &&
76 ! isset( $_REQUEST['preview'] ) ) {
77 wp_safe_redirect( code_snippets()->get_menu_url( 'add' ) );
78 exit;
79 }
80
81 // Process any submitted actions.
82 $this->process_actions();
83
84 // Load the contextual help tabs.
85 $contextual_help = new Contextual_Help( 'edit' );
86 $contextual_help->load();
87
88 // Register action hooks.
89 if ( get_setting( 'general', 'enable_description' ) ) {
90 add_action( 'code_snippets_edit_snippet', array( $this, 'render_description_editor' ), 9 );
91 }
92
93 if ( get_setting( 'general', 'enable_tags' ) ) {
94 add_action( 'code_snippets_edit_snippet', array( $this, 'render_tags_editor' ) );
95 }
96
97 add_action( 'code_snippets_below_editor', array( $this, 'render_priority_setting' ), 0 );
98
99 if ( is_network_admin() ) {
100 add_action( 'code_snippets_edit_snippet', array( $this, 'render_multisite_sharing_setting' ), 1 );
101 }
102
103 if ( apply_filters( 'code_snippets/extra_save_buttons', true ) ) {
104 add_action( 'code_snippets/admin/code_editor_toolbar', array( $this, 'render_extra_submit_buttons' ) );
105 }
106
107 if ( apply_filters( 'code_snippets/enable_code_direction', is_rtl() ) ) {
108 add_action( 'code_snippets/admin/code_editor_toolbar', array( $this, 'render_direction_setting' ), 11, 0 );
109 }
110
111 $this->process_actions();
112 }
113
114 /**
115 * Load the data for the snippet currently being edited
116 */
117 public function load_snippet_data() {
118 $edit_id = isset( $_REQUEST['id'] ) && intval( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
119
120 $this->snippet = get_snippet( $edit_id );
121 $snippet = $this->snippet;
122
123 if ( 0 === $edit_id && isset( $_GET['type'] ) && $_GET['type'] !== $snippet->type ) {
124 if ( 'php' === $_GET['type'] ) {
125 $snippet->scope = 'global';
126 } elseif ( 'css' === $_GET['type'] ) {
127 $snippet->scope = 'site-css';
128 } elseif ( 'html' === $_GET['type'] ) {
129 $snippet->scope = 'content';
130 } elseif ( 'js' === $_GET['type'] ) {
131 $snippet->scope = 'site-head-js';
132 }
133 }
134
135 $this->snippet = apply_filters( 'code_snippets/admin/load_snippet_data', $snippet );
136 }
137
138 /**
139 * Process data sent from the edit page
140 */
141 private function process_actions() {
142
143 /* Check for a valid nonce */
144 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'save_snippet' ) ) {
145 return;
146 }
147
148 if ( isset( $_POST['save_snippet'] ) || isset( $_POST['save_snippet_execute'] ) ||
149 isset( $_POST['save_snippet_activate'] ) || isset( $_POST['save_snippet_deactivate'] ) ) {
150 $this->save_posted_snippet();
151 }
152
153 if ( isset( $_POST['snippet_id'] ) ) {
154 $snippet_id = intval( $_POST['snippet_id'] );
155
156 /* Delete the snippet if the button was clicked */
157 if ( isset( $_POST['delete_snippet'] ) ) {
158 delete_snippet( $snippet_id );
159 wp_safe_redirect( add_query_arg( 'result', 'delete', code_snippets()->get_menu_url( 'manage' ) ) );
160 exit;
161 }
162
163 /* Export the snippet if the button was clicked */
164 if ( isset( $_POST['export_snippet'] ) ) {
165 $export = new Export_Attachment( $snippet_id );
166 $export->download_snippets_json();
167 }
168
169 /* Download the snippet if the button was clicked */
170 if ( isset( $_POST['download_snippet'] ) ) {
171 $export = new Export_Attachment( $snippet_id );
172 $export->download_snippets_code();
173 }
174
175 do_action( 'code_snippets/admin/process_actions', $snippet_id );
176 }
177 }
178
179 /**
180 * Remove the sharing status from a network snippet
181 *
182 * @param int $snippet_id Snippet ID.
183 */
184 private function unshare_network_snippet( $snippet_id ) {
185 $shared_snippets = get_site_option( 'shared_network_snippets', array() );
186
187 if ( ! in_array( $snippet_id, $shared_snippets, true ) ) {
188 return;
189 }
190
191 /* Remove the snippet ID from the array */
192 $shared_snippets = array_diff( $shared_snippets, array( $snippet_id ) );
193 update_site_option( 'shared_network_snippets', array_values( $shared_snippets ) );
194
195 /* Deactivate on all sites */
196 $sites = get_sites( [ 'fields' => 'ids' ] );
197
198 foreach ( $sites as $site ) {
199 switch_to_blog( $site );
200 $active_shared_snippets = get_option( 'active_shared_network_snippets' );
201
202 if ( is_array( $active_shared_snippets ) ) {
203 $active_shared_snippets = array_diff( $active_shared_snippets, array( $snippet_id ) );
204 update_option( 'active_shared_network_snippets', $active_shared_snippets );
205 }
206
207 clean_active_snippets_cache( code_snippets()->db->ms_table );
208 }
209
210 restore_current_blog();
211 }
212
213 /**
214 * Display a custom error message when a code error is encountered
215 *
216 * @param string $out Error message content.
217 *
218 * @return string New error message.
219 */
220 private function code_error_callback( $out ) {
221 $error = error_get_last();
222
223 if ( is_null( $error ) ) {
224 return $out;
225 }
226
227 $m = '<h3>' . esc_html__( "Don't Panic", 'code-snippets' ) . '</h3>';
228 /* translators: %d: line where error was produced */
229 $m .= '<p>' . sprintf( esc_html__( 'The code snippet you are trying to save produced a fatal error on line %d:', 'code-snippets' ), intval( $error['line'] ) ) . '</p>';
230 $m .= '<strong>' . esc_html( $error['message'] ) . '</strong>';
231 $m .= '<p>' . esc_html__( 'The previous version of the snippet is unchanged, and the rest of this site should be functioning normally as before.', 'code-snippets' ) . '</p>';
232 $m .= '<p>' . esc_html__( 'Please use the back button in your browser to return to the previous page and try to fix the code error.', 'code-snippets' );
233 $m .= ' ' . esc_html__( 'If you prefer, you can close this page and discard the changes you just made. No changes will be made to this site.', 'code-snippets' ) . '</p>';
234
235 return $m;
236 }
237
238 /**
239 * Validate the snippet code before saving to database
240 *
241 * @param Snippet $snippet Snippet object.
242 *
243 * @return bool Whether the code produces errors.
244 */
245 private function test_code( Snippet $snippet ) {
246
247 if ( empty( $snippet->code ) || 'php' !== $snippet->type ) {
248 return false;
249 }
250
251 ob_start( array( $this, 'code_error_callback' ) );
252
253 $result = eval( $snippet->code );
254
255 ob_end_clean();
256
257 do_action( 'code_snippets/after_execute_snippet', $snippet->id, $snippet->code, $result );
258
259 return false === $result;
260 }
261
262 /**
263 * Save the posted snippet data to the database and redirect
264 */
265 private function save_posted_snippet() {
266
267 /* Build snippet object from fields with 'snippet_' prefix */
268 $snippet = new Snippet();
269
270 foreach ( $_POST as $field => $value ) {
271 if ( 'snippet_' === substr( $field, 0, 8 ) ) {
272
273 /* Remove the 'snippet_' prefix from field name and set it on the object */
274 $snippet->set_field( substr( $field, 8 ), stripslashes( $value ) );
275 }
276 }
277
278 $snippet = apply_filters( 'code_snippets/save/post_set_fields', $snippet );
279
280 if ( isset( $_POST['save_snippet_execute'] ) && 'single-use' !== $snippet->scope ) {
281 unset( $_POST['save_snippet_execute'] );
282 $_POST['save_snippet'] = 'yes';
283 }
284
285 /* Activate or deactivate the snippet before saving if we clicked the button */
286
287 if ( isset( $_POST['save_snippet_execute'] ) ) {
288 $snippet->active = 1;
289 } elseif ( isset( $_POST['snippet_sharing'] ) && 'on' === $_POST['snippet_sharing'] ) {
290 // Shared network snippets cannot be network-activated.
291 $snippet->active = 0;
292 unset( $_POST['save_snippet_activate'], $_POST['save_snippet_deactivate'] );
293 } elseif ( isset( $_POST['save_snippet_activate'] ) ) {
294 $snippet->active = 1;
295 } elseif ( isset( $_POST['save_snippet_deactivate'] ) ) {
296 $snippet->active = 0;
297 }
298
299 if ( 'php' === $snippet->type ) {
300
301 /* Remove <?php and <? from beginning of snippet */
302 $snippet->code = preg_replace( '|^\s*<\?(php)?|', '', $snippet->code );
303 /* Remove ?> from end of snippet */
304 $snippet->code = preg_replace( '|\?>\s*$|', '', $snippet->code );
305
306 /* Deactivate snippet if code contains errors */
307 if ( $snippet->active && 'single-use' !== $snippet->scope ) {
308 $validator = new Validator( $snippet->code );
309 $code_error = $validator->validate();
310
311 if ( ! $code_error ) {
312 $code_error = $this->test_code( $snippet );
313 }
314
315 if ( $code_error ) {
316 $snippet->active = 0;
317 }
318 }
319 }
320
321 /* Save the snippet to the database */
322 $snippet_id = save_snippet( $snippet );
323
324 /* Update the shared network snippets if necessary */
325 if ( $snippet_id && is_network_admin() ) {
326
327 if ( isset( $_POST['snippet_sharing'] ) && 'on' === $_POST['snippet_sharing'] ) {
328 $shared_snippets = get_site_option( 'shared_network_snippets', array() );
329
330 /* Add the snippet ID to the array if it isn't already */
331 if ( ! in_array( $snippet_id, $shared_snippets, true ) ) {
332 $shared_snippets[] = $snippet_id;
333 update_site_option( 'shared_network_snippets', array_values( $shared_snippets ) );
334 }
335 } else {
336 $this->unshare_network_snippet( $snippet_id );
337 }
338 }
339
340 /* If the saved snippet ID is invalid, display an error message */
341 if ( ! $snippet_id || $snippet_id < 1 ) {
342 /* An error occurred */
343 wp_safe_redirect( add_query_arg( 'result', 'save-error', code_snippets()->get_menu_url( 'add' ) ) );
344 exit;
345 }
346
347 /* Display message if a parse error occurred */
348 if ( isset( $code_error ) && $code_error ) {
349 wp_safe_redirect(
350 add_query_arg(
351 array(
352 'id' => $snippet_id,
353 'result' => 'code-error',
354 ),
355 code_snippets()->get_menu_url( 'edit' )
356 )
357 );
358 exit;
359 }
360
361 /* Set the result depending on if the snippet was just added */
362 $result = isset( $_POST['snippet_id'] ) ? 'updated' : 'added';
363
364 /* Append a suffix if the snippet was activated or deactivated */
365 if ( isset( $_POST['save_snippet_activate'] ) ) {
366 $result .= '-and-activated';
367 } elseif ( isset( $_POST['save_snippet_deactivate'] ) ) {
368 $result .= '-and-deactivated';
369 } elseif ( isset( $_POST['save_snippet_execute'] ) ) {
370 $result .= '-and-executed';
371 }
372
373 /* Redirect to edit snippet page */
374 $redirect_uri = add_query_arg(
375 array(
376 'id' => $snippet_id,
377 'result' => $result,
378 ),
379 code_snippets()->get_menu_url( 'edit' )
380 );
381
382 wp_safe_redirect( esc_url_raw( $redirect_uri ) );
383 exit;
384 }
385
386 /**
387 * Add a description editor to the single snippet page
388 *
389 * @param Snippet $snippet The snippet being used for this page.
390 */
391 public function render_description_editor( Snippet $snippet ) {
392 $settings = Settings\get_settings_values();
393 $settings = $settings['description_editor'];
394
395 echo '<h2><label for="snippet_description">', esc_html__( 'Description', 'code-snippets' ), '</label></h2>';
396
397 remove_editor_styles(); // Stop custom theme styling interfering with the editor.
398
399 wp_editor(
400 $snippet->desc,
401 'description',
402 apply_filters(
403 'code_snippets/admin/description_editor_settings',
404 array(
405 'textarea_name' => 'snippet_description',
406 'textarea_rows' => $settings['rows'],
407 'teeny' => ! $settings['use_full_mce'],
408 'media_buttons' => $settings['media_buttons'],
409 )
410 )
411 );
412 }
413
414 /**
415 * Render the interface for editing snippet tags
416 *
417 * @param Snippet $snippet The snippet currently being edited.
418 */
419 public function render_tags_editor( Snippet $snippet ) {
420
421 ?>
422 <h2 style="margin: 25px 0 10px;">
423 <label for="snippet_tags" style="cursor: auto;">
424 <?php esc_html_e( 'Tags', 'code-snippets' ); ?>
425 </label>
426 </h2>
427
428 <input type="text" id="snippet_tags" name="snippet_tags" style="width: 100%;"
429 placeholder="<?php esc_html_e( 'Enter a list of tags; separated by commas', 'code-snippets' ); ?>"
430 value="<?php echo esc_attr( $snippet->tags_list ); ?>" />
431 <?php
432 }
433
434 /**
435 * Render the snippet priority setting
436 *
437 * @param Snippet $snippet The snippet currently being edited.
438 */
439 public function render_priority_setting( Snippet $snippet ) {
440 if ( 'html' === $snippet->type ) {
441 return;
442 }
443
444 ?>
445 <p class="snippet-priority"
446 title="<?php esc_attr_e( 'Snippets with a lower priority number will run before those with a higher number.', 'code-snippets' ); ?>">
447 <label for="snippet_priority"><?php esc_html_e( 'Priority', 'code-snippets' ); ?></label>
448
449 <input name="snippet_priority" type="number" id="snippet_priority"
450 value="<?php echo esc_attr( $snippet->priority ); ?>">
451 </p>
452 <?php
453 }
454
455 /**
456 * Render the setting for shared network snippets
457 *
458 * @param Snippet $snippet The snippet currently being edited.
459 */
460 public function render_multisite_sharing_setting( $snippet ) {
461 $shared_snippets = get_site_option( 'shared_network_snippets', array() );
462 ?>
463
464 <div class="snippet-sharing-setting">
465 <h2 class="screen-reader-text"><?php esc_html_e( 'Sharing Settings', 'code-snippets' ); ?></h2>
466 <label for="snippet_sharing">
467 <input type="checkbox" name="snippet_sharing"
468 <?php checked( in_array( $snippet->id, $shared_snippets, true ) ); ?>>
469 <?php esc_html_e( 'Allow this snippet to be activated on individual sites on the network', 'code-snippets' ); ?>
470 </label>
471 </div>
472
473 <?php
474 }
475
476 /**
477 * Render additional save buttons above the snippet editor.
478 *
479 * @param Snippet $snippet Snippet currently being edited.
480 */
481 public function render_extra_submit_buttons( Snippet $snippet ) {
482
483 $actions['save_snippet'] = array(
484 __( 'Save Changes', 'code-snippets' ),
485 __( 'Save Snippet', 'code-snippets' ),
486 );
487
488 if ( 'html' !== $snippet->type ) {
489
490 if ( 'single-use' === $snippet->scope ) {
491 $actions['save_snippet_execute'] = array(
492 __( 'Execute Once', 'code-snippets' ),
493 __( 'Save Snippet and Execute Once', 'code-snippets' ),
494 );
495
496 } elseif ( ! $snippet->shared_network || ! is_network_admin() ) {
497
498 if ( $snippet->active ) {
499 $actions['save_snippet_deactivate'] = array(
500 __( 'Deactivate', 'code-snippets' ),
501 __( 'Save Snippet and Deactivate', 'code-snippets' ),
502 );
503
504 } else {
505 $actions['save_snippet_activate'] = array(
506 __( 'Activate', 'code-snippets' ),
507 __( 'Save Snippet and Activate', 'code-snippets' ),
508 );
509 }
510 }
511 }
512
513 foreach ( $actions as $action => $labels ) {
514 $other_attributes = array(
515 'title' => $labels[1],
516 'id' => $action . '_extra',
517 );
518 submit_button( $labels[0], 'secondary small', $action, false, $other_attributes );
519 }
520 }
521
522 /**
523 * Render a control for changing the code editor text direction
524 */
525 public function render_direction_setting() {
526 ?>
527 <label class="screen-reader-text" for="snippet-code-direction">
528 <?php esc_html_e( 'Code Direction', 'code-snippets' ); ?>
529 </label>
530 <select id="snippet-code-direction">
531 <option value="ltr"><?php esc_html_e( 'LTR', 'code-snippets' ); ?></option>
532 <option value="rtl"><?php esc_html_e( 'RTL', 'code-snippets' ); ?></option>
533 </select>
534 <?php
535 }
536
537 /**
538 * Retrieve the first error in a snippet's code
539 *
540 * @param int $snippet_id Snippet ID.
541 *
542 * @return array|bool Error if execution failed, otherwise false.
543 */
544 private function get_snippet_error( $snippet_id ) {
545
546 if ( ! intval( $snippet_id ) ) {
547 return false;
548 }
549
550 $snippet = get_snippet( intval( $snippet_id ) );
551
552 if ( '' === $snippet->code ) {
553 return false;
554 }
555
556 $validator = new Validator( $snippet->code );
557 $error = $validator->validate();
558
559 if ( $error ) {
560 return $error;
561 }
562
563 ob_start();
564 $result = eval( $snippet->code );
565 ob_end_clean();
566
567 if ( false !== $result ) {
568 return false;
569 }
570
571 $error = error_get_last();
572
573 if ( is_null( $error ) ) {
574 return false;
575 }
576
577 return $error;
578 }
579
580 /**
581 * Print the status and error messages
582 */
583 protected function print_messages() {
584
585 if ( ! isset( $_REQUEST['result'] ) ) {
586 return;
587 }
588
589 $result = sanitize_key( $_REQUEST['result'] );
590
591 if ( 'code-error' === $result ) {
592 $error = isset( $_REQUEST['id'] ) ? $this->get_snippet_error( intval( $_REQUEST['id'] ) ) : false;
593
594 if ( $error ) {
595 /* translators: %d: line of file where error originated */
596 $text = __( 'The snippet has been deactivated due to an error on line %d:', 'code-snippets' );
597
598 printf(
599 '<div id="message" class="error fade"><p>%s</p><p><strong>%s</strong></p></div>',
600 sprintf( esc_html( $text ), intval( $error['line'] ) ),
601 wp_kses_post( $error['message'] )
602 );
603
604 } else {
605 echo '<div id="message" class="error fade"><p>';
606 esc_html_e( 'The snippet has been deactivated due to an error in the code.', 'code-snippets' );
607 echo '</p></div>';
608 }
609
610 return;
611 }
612
613 if ( 'save-error' === $result ) {
614 echo '<div id="message" class="error fade"><p>';
615 esc_html_e( 'An error occurred when saving the snippet.', 'code-snippets' );
616 echo '</p></div>';
617 return;
618 }
619
620 $messages = array(
621 'added' => __( 'Snippet <strong>added</strong>.', 'code-snippets' ),
622 'updated' => __( 'Snippet <strong>updated</strong>.', 'code-snippets' ),
623 'added-and-activated' => __( 'Snippet <strong>added</strong> and <strong>activated</strong>.', 'code-snippets' ),
624 'updated-and-executed' => __( 'Snippet <strong>added</strong> and <strong>executed</strong>.', 'code-snippets' ),
625 'updated-and-activated' => __( 'Snippet <strong>updated</strong> and <strong>activated</strong>.', 'code-snippets' ),
626 'updated-and-deactivated' => __( 'Snippet <strong>updated</strong> and <strong>deactivated</strong>.', 'code-snippets' ),
627 );
628
629 if ( isset( $messages[ $result ] ) ) {
630 echo '<div id="message" class="updated fade"><p>', wp_kses( $messages[ $result ], [ 'strong' => [] ] ), '</p></div>';
631
632 }
633 }
634
635 /**
636 * Enqueue assets for the edit menu
637 */
638 public function enqueue_assets() {
639 $plugin = code_snippets();
640 $rtl = is_rtl() ? '-rtl' : '';
641
642 enqueue_code_editor( $this->snippet->type );
643
644 wp_enqueue_style(
645 'code-snippets-edit',
646 plugins_url( "dist/edit$rtl.css", $plugin->file ),
647 [ 'code-editor' ],
648 $plugin->version
649 );
650
651 wp_enqueue_script(
652 'code-snippets-edit-menu',
653 plugins_url( 'dist/edit.js', $plugin->file ),
654 [ 'code-snippets-code-editor' ],
655 $plugin->version,
656 true
657 );
658
659 wp_localize_script(
660 'code-snippets-edit-menu',
661 'code_snippets_edit_i18n',
662 [
663 'missing_title_code' => esc_attr__( 'This snippet has no code or title. Continue?', 'code-snippets' ),
664 'missing_title' => esc_attr__( 'This snippet has no title. Continue?', 'code-snippets' ),
665 'missing_code' => esc_attr__( 'This snippet has no snippet code. Continue?', 'code-snippets' ),
666 ]
667 );
668
669 $this->enqueue_tag_assets();
670 }
671
672 /**
673 * Enqueue the necessary assets for the tag editor
674 */
675 protected function enqueue_tag_assets() {
676
677 if ( ! get_setting( 'general', 'enable_tags' ) ) {
678 return;
679 }
680
681 wp_enqueue_script(
682 'code-snippets-edit-menu-tags',
683 plugins_url( 'dist/tags.js', code_snippets()->file ),
684 [],
685 code_snippets()->version,
686 true
687 );
688
689 $options = apply_filters(
690 'code_snippets/tag_editor_options',
691 array(
692 'allow_spaces' => true,
693 'available_tags' => get_all_snippet_tags(),
694 )
695 );
696
697 $inline_script = 'var code_snippets_tags = ' . wp_json_encode( $options ) . ';';
698
699 wp_add_inline_script( 'code-snippets-edit-menu-tags', $inline_script, 'before' );
700 }
701
702 /**
703 * Remove the old CodeMirror version used by the Debug Bar Console plugin
704 * that is messing up the snippet editor
705 */
706 public function remove_debug_bar_codemirror() {
707
708 /* Try to discern if we are on the single snippet page as good as we can at this early time */
709 if ( ! is_admin() || 'admin.php' !== $GLOBALS['pagenow'] ) {
710 return;
711 }
712
713 if ( ! isset( $_GET['page'] ) || code_snippets()->get_menu_slug( 'edit' ) !== $_GET['page'] && code_snippets()->get_menu_slug( 'settings' ) !== $_GET['page'] ) {
714 return;
715 }
716
717 remove_action( 'debug_bar_enqueue_scripts', 'debug_bar_console_scripts' );
718 }
719
720 /**
721 * Retrieve a list of submit actions for a given snippet
722 *
723 * @param Snippet $snippet The snippet currently being edited.
724 * @param bool $extra_actions Whether to include additional actions alongside save actions.
725 *
726 * @return array Two-dimensional array with action name keyed to description.
727 */
728 public function get_actions_list( $snippet, $extra_actions = true ) {
729 $actions = [ 'save_snippet' => __( 'Save Changes', 'code-snippets' ) ];
730
731 if ( 'single-use' === $snippet->scope ) {
732 $actions['save_snippet_execute'] = __( 'Save Changes and Execute Once', 'code-snippets' );
733
734 } elseif ( ! $snippet->shared_network || ! is_network_admin() ) {
735
736 if ( $snippet->active ) {
737 $actions['save_snippet_deactivate'] = __( 'Save Changes and Deactivate', 'code-snippets' );
738 } else {
739 $actions['save_snippet_activate'] = __( 'Save Changes and Activate', 'code-snippets' );
740 }
741 }
742
743 // Make the 'Save and Activate' button the default if the setting is enabled.
744 if ( ! $snippet->active && 'single-use' !== $snippet->scope &&
745 get_setting( 'general', 'activate_by_default' ) ) {
746 $actions = array_reverse( $actions );
747 }
748
749 if ( $extra_actions && 0 !== $snippet->id ) {
750
751 if ( apply_filters( 'code_snippets/enable_downloads', true ) ) {
752 $actions['download_snippet'] = __( 'Download', 'code-snippets' );
753 }
754
755 $actions['export_snippet'] = __( 'Export', 'code-snippets' );
756 $actions['delete_snippet'] = __( 'Delete', 'code-snippets' );
757 }
758
759 return apply_filters( 'code_snippets/admin/submit_actions', $actions, $snippet, $extra_actions );
760 }
761
762 /**
763 * Render the submit buttons for a code snippet
764 *
765 * @param Snippet $snippet The snippet currently being edited.
766 * @param string $size Additional size classes to pass to button.
767 * @param bool $extra_actions Whether to include additional buttons alongside save buttons.
768 */
769 public function render_submit_buttons( $snippet, $size = '', $extra_actions = true ) {
770
771 $actions = $this->get_actions_list( $snippet, $extra_actions );
772 $type = 'primary';
773 $size = $size ? ' ' . $size : '';
774
775 foreach ( $actions as $action => $label ) {
776 $other = null;
777
778 if ( 'delete_snippet' === $action ) {
779 $other = sprintf(
780 'onclick="%s"',
781 esc_js(
782 sprintf(
783 'return confirm("%s");',
784 esc_html__( 'You are about to permanently delete this snippet.', 'code-snippets' ) . "\n" .
785 esc_html__( "'Cancel' to stop, 'OK' to delete.", 'code-snippets' )
786 )
787 )
788 );
789 }
790
791 submit_button( $label, $type . $size, $action, false, $other );
792
793 if ( 'primary' === $type ) {
794 $type = 'secondary';
795 }
796 }
797 }
798
799 /**
800 * Render a list of scopes as ratio controls
801 *
802 * @param array $scopes List of scopes to render, with scope name keyed to label.
803 */
804 public function print_scopes_list( $scopes ) {
805 $scope_icons = Snippet::get_scope_icons();
806
807 foreach ( $scopes as $scope => $label ) {
808 printf( '<label><input type="radio" name="snippet_scope" value="%s"', esc_attr( $scope ) );
809 checked( $scope, $this->snippet->scope );
810 printf( '> <span class="dashicons dashicons-%s"></span> %s</label>', esc_attr( $scope_icons[ $scope ] ), esc_html( $label ) );
811 }
812 }
813
814 /**
815 * Render a keyboard shortcut as HTML.
816 *
817 * @param array|string $modifiers Modifier keys. Can be 'Cmd', 'Ctrl', 'Shift', 'Option', 'Alt'.
818 * @param string $key Keyboard key.
819 *
820 * @return void
821 */
822 protected function render_keyboard_shortcut( $modifiers, $key ) {
823 static $keys = null;
824
825 if ( is_null( $keys ) ) {
826 $keys = array(
827 'Cmd' => _x( 'Cmd', 'keyboard key', 'code-snippets' ),
828 'Ctrl' => _x( 'Ctrl', 'keyboard key', 'code-snippets' ),
829 'Shift' => _x( 'Shift', 'keyboard key', 'code-snippets' ),
830 'Option' => _x( 'Option', 'keyboard key', 'code-snippets' ),
831 'Alt' => _x( 'Alt', 'keyboard key', 'code-snippets' ),
832 'Up' => _x( 'Up', 'keyboard key', 'code-snippets' ),
833 'Down' => _x( 'Down', 'keyboard key', 'code-snippets' ),
834 'F' => _x( 'F', 'keyboard key', 'code-snippets' ),
835 'G' => _x( 'G', 'keyboard key', 'code-snippets' ),
836 'R' => _x( 'R', 'keyboard key', 'code-snippets' ),
837 'S' => _x( 'S', 'keyboard key', 'code-snippets' ),
838 '/' => _x( '/', 'keyboard key', 'code-snippets' ),
839 );
840 }
841
842 if ( ! is_array( $modifiers ) ) {
843 $modifiers = array( $modifiers );
844 }
845
846 foreach ( $modifiers as $modifier ) {
847 if ( 'Ctrl' === $modifier || 'Cmd' === $modifier ) {
848 echo '<kbd class="pc-key">', esc_html( $keys['Ctrl'] ), '</kbd>';
849 echo '<kbd class="mac-key">', esc_html( $keys['Cmd'] ), '</kbd>&hyphen;';
850 } elseif ( 'Option' === $modifier ) {
851 echo '<span class="mac-key"><kbd class="mac-key">', esc_html( $keys['Option'] ), '</kbd>&hyphen;</span>';
852 } else {
853 echo '<kbd>', esc_html( $keys[ $modifier ] ), '</kbd>&hyphen;';
854 }
855 }
856
857 echo '<kbd>', esc_html( $keys[ $key ] ), '</kbd>';
858 }
859 }
860