PluginProbe
Code Snippets / 2.13.0
Code Snippets v2.13.0
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 2.13.0, at php/admin-menus/class-edit-menu.php

561 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This class handles the add/edit menu
5 */
6 class Code_Snippets_Edit_Menu extends Code_Snippets_Admin_Menu {
7
8 /**
9 * Constructor
10 */
11 public function __construct() {
12 parent::__construct( 'edit',
13 __( 'Edit Snippet', 'code-snippets' ),
14 __( 'Edit Snippet', 'code-snippets' )
15 );
16 }
17
18 /**
19 * Register action and filter hooks
20 */
21 public function run() {
22 parent::run();
23 $this->remove_debug_bar_codemirror();
24 }
25
26 /**
27 * Register the admin menu
28 */
29 public function register() {
30
31 /* Add edit menu if we are currently editing a snippet */
32 if ( isset( $_REQUEST['page'] ) && code_snippets()->get_menu_slug( 'edit' ) === $_REQUEST['page'] ) {
33 parent::register();
34 }
35
36 /* Add New Snippet menu */
37 $this->add_menu(
38 code_snippets()->get_menu_slug( 'add' ),
39 _x( 'Add New', 'menu label', 'code-snippets' ),
40 __( 'Add New Snippet', 'code-snippets' )
41 );
42 }
43
44 /**
45 * Executed when the menu is loaded
46 */
47 public function load() {
48 parent::load();
49
50 /* Don't allow visiting the edit snippet page without a valid ID */
51 if ( code_snippets()->get_menu_slug( 'edit' ) === $_REQUEST['page'] ) {
52 if ( ! isset( $_REQUEST['id'] ) || 0 == $_REQUEST['id'] ) {
53 wp_redirect( code_snippets()->get_menu_url( 'add' ) );
54 exit;
55 }
56 }
57
58 /* Load the contextual help tabs */
59 $contextual_help = new Code_Snippets_Contextual_Help( 'edit' );
60 $contextual_help->load();
61
62 /* Register action hooks */
63 if ( code_snippets_get_setting( 'general', 'enable_description' ) ) {
64 add_action( 'code_snippets/admin/single', array( $this, 'render_description_editor' ), 9 );
65 }
66
67 if ( code_snippets_get_setting( 'general', 'enable_tags' ) ) {
68 add_action( 'code_snippets/admin/single', array( $this, 'render_tags_editor' ) );
69 }
70
71 add_action( 'code_snippets/admin/single', array( $this, 'render_priority_setting' ), 0 );
72
73 if ( code_snippets_get_setting( 'general', 'snippet_scope_enabled' ) ) {
74 add_action( 'code_snippets/admin/single', array( $this, 'render_scope_setting' ), 1 );
75 }
76
77 if ( is_network_admin() ) {
78 add_action( 'code_snippets/admin/single', array( $this, 'render_multisite_sharing_setting' ), 1 );
79 }
80
81 $this->process_actions();
82 }
83
84 /**
85 * Process data sent from the edit page
86 */
87 private function process_actions() {
88
89 /* Check for a valid nonce */
90 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'save_snippet' ) ) {
91 return;
92 }
93
94 if ( isset( $_POST['save_snippet'] ) || isset( $_POST['save_snippet_execute'] ) ||
95 isset( $_POST['save_snippet_activate'] ) || isset( $_POST['save_snippet_deactivate'] ) ) {
96 $this->save_posted_snippet();
97 }
98
99 if ( isset( $_POST['snippet_id'] ) ) {
100
101 /* Delete the snippet if the button was clicked */
102 if ( isset( $_POST['delete_snippet'] ) ) {
103 delete_snippet( $_POST['snippet_id'] );
104 wp_redirect( add_query_arg( 'result', 'delete', code_snippets()->get_menu_url( 'manage' ) ) );
105 exit;
106 }
107
108 /* Export the snippet if the button was clicked */
109 if ( isset( $_POST['export_snippet'] ) ) {
110 export_snippets( array( $_POST['snippet_id'] ) );
111 }
112
113 /* Download the snippet if the button was clicked */
114 if ( isset( $_POST['download_snippet'] ) ) {
115 download_snippets( array( $_POST['snippet_id'] ) );
116 }
117 }
118 }
119
120 /**
121 * Remove the sharing status from a network snippet
122 *
123 * @param int $snippet_id
124 */
125 private function unshare_network_snippet( $snippet_id ) {
126 $shared_snippets = get_site_option( 'shared_network_snippets', array() );
127
128 if ( ! in_array( $snippet_id, $shared_snippets ) ) {
129 return;
130 }
131
132 /* Remove the snippet ID from the array */
133 $shared_snippets = array_diff( $shared_snippets, array( $snippet_id ) );
134 update_site_option( 'shared_network_snippets', array_values( $shared_snippets ) );
135
136 /* Deactivate on all sites */
137 global $wpdb;
138 if ( $sites = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) ) {
139
140 foreach ( $sites as $site ) {
141 switch_to_blog( $site );
142 $active_shared_snippets = get_option( 'active_shared_network_snippets' );
143
144 if ( is_array( $active_shared_snippets ) ) {
145 $active_shared_snippets = array_diff( $active_shared_snippets, array( $snippet_id ) );
146 update_option( 'active_shared_network_snippets', $active_shared_snippets );
147 }
148 }
149
150 restore_current_blog();
151 }
152 }
153
154 private function code_error_callback( $out ) {
155 $error = error_get_last();
156
157 if ( is_null( $error ) ) {
158 return $out;
159 }
160
161 $m = '<h3>' . __( "Don't Panic", 'code-snippets' ) . '</h3>';
162 /* translators: %d: line where error was produced */
163 $m .= '<p>' . sprintf( __( 'The code snippet you are trying to save produced a fatal error on line %d:', 'code-snippets' ), $error['line'] ) . '</p>';
164 $m .= '<strong>' . $error['message'] . '</strong>';
165 $m .= '<p>' . __( 'The previous version of the snippet is unchanged, and the rest of this site should be functioning normally as before.', 'code-snippets' ) . '</p>';
166 $m .= '<p>' . __( 'Please use the back button in your browser to return to the previous page and try to fix the code error.', 'code-snippets' );
167 $m .= ' ' . __( '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>';
168
169 return $m;
170 }
171
172 /**
173 * Validate the snippet code before saving to database
174 *
175 * @param Code_Snippet $snippet
176 *
177 * @return bool true if code produces errors
178 */
179 private function validate_code( Code_Snippet $snippet ) {
180
181 if ( empty( $snippet->code ) ) {
182 return false;
183 }
184
185 ob_start( array( $this, 'code_error_callback' ) );
186
187 $result = eval( $snippet->code );
188
189 ob_end_clean();
190
191 do_action( 'code_snippets/after_execute_snippet', $snippet->id, $snippet->code, $result );
192
193 return false === $result;
194 }
195
196 /**
197 * Save the posted snippet data to the database and redirect
198 */
199 private function save_posted_snippet() {
200
201 /* Build snippet object from fields with 'snippet_' prefix */
202 $snippet = new Code_Snippet();
203
204 foreach ( $_POST as $field => $value ) {
205 if ( 'snippet_' === substr( $field, 0, 8 ) ) {
206
207 /* Remove the 'snippet_' prefix from field name and set it on the object */
208 $snippet->set_field( substr( $field, 8 ), stripslashes( $value ) );
209 }
210 }
211
212 if ( isset( $_POST['save_snippet_execute'] ) && 'single-use' !== $snippet->scope ) {
213 unset( $_POST['save_snippet_execute'] );
214 $_POST['save_snippet'] = 'yes';
215 }
216
217 /* Activate or deactivate the snippet before saving if we clicked the button */
218
219 if ( isset( $_POST['save_snippet_execute'] ) ) {
220 $snippet->active = 1;
221 } elseif ( isset( $_POST['snippet_sharing'] ) && 'on' === $_POST['snippet_sharing'] ) {
222 // Shared network snippets cannot be network activated
223 $snippet->active = 0;
224 unset( $_POST['save_snippet_activate'], $_POST['save_snippet_deactivate'] );
225 } elseif ( isset( $_POST['save_snippet_activate'] ) ) {
226 $snippet->active = 1;
227 } elseif ( isset( $_POST['save_snippet_deactivate'] ) ) {
228 $snippet->active = 0;
229 }
230
231 /* Deactivate snippet if code contains errors */
232 if ( $snippet->active && 'single-use' !== $snippet->scope ) {
233 if ( $code_error = $this->validate_code( $snippet ) ) {
234 $snippet->active = 0;
235 }
236 }
237
238 /* Save the snippet to the database */
239 $snippet_id = save_snippet( $snippet );
240
241 /* Update the shared network snippets if necessary */
242 if ( $snippet_id && is_network_admin() ) {
243
244 if ( isset( $_POST['snippet_sharing'] ) && 'on' === $_POST['snippet_sharing'] ) {
245 $shared_snippets = get_site_option( 'shared_network_snippets', array() );
246
247 /* Add the snippet ID to the array if it isn't already */
248 if ( ! in_array( $snippet_id, $shared_snippets ) ) {
249 $shared_snippets[] = $snippet_id;
250 update_site_option( 'shared_network_snippets', array_values( $shared_snippets ) );
251 }
252 } else {
253 $this->unshare_network_snippet( $snippet_id );
254 }
255 }
256
257 /* If the saved snippet ID is invalid, display an error message */
258 if ( ! $snippet_id || $snippet_id < 1 ) {
259 /* An error occurred */
260 wp_redirect( add_query_arg( 'result', 'save-error', code_snippets()->get_menu_url( 'add' ) ) );
261 exit;
262 }
263
264 /* Display message if a parse error occurred */
265 if ( isset( $code_error ) && $code_error ) {
266 wp_redirect( add_query_arg(
267 array( 'id' => $snippet_id, 'result' => 'code-error' ),
268 code_snippets()->get_menu_url( 'edit' )
269 ) );
270 exit;
271 }
272
273 /* Set the result depending on if the snippet was just added */
274 $result = isset( $_POST['snippet_id'] ) ? 'updated' : 'added';
275
276 /* Append a suffix if the snippet was activated or deactivated */
277 if ( isset( $_POST['save_snippet_activate'] ) ) {
278 $result .= '-and-activated';
279 } elseif ( isset( $_POST['save_snippet_deactivate'] ) ) {
280 $result .= '-and-deactivated';
281 } elseif ( isset( $_POST['save_snippet_execute'] ) ) {
282 $result .= '-and-executed';
283 }
284
285 /* Redirect to edit snippet page */
286 $redirect_uri = add_query_arg(
287 array( 'id' => $snippet_id, 'result' => $result ),
288 code_snippets()->get_menu_url( 'edit' )
289 );
290
291 if ( isset( $_POST['snippet_editor_cursor_line'], $_POST['snippet_editor_cursor_ch'] ) &&
292 is_numeric( $_POST['snippet_editor_cursor_line'] ) && is_numeric( $_POST['snippet_editor_cursor_ch'] ) ) {
293 $redirect_uri = add_query_arg( 'cursor_line', intval( $_POST['snippet_editor_cursor_line'] ), $redirect_uri );
294 $redirect_uri = add_query_arg( 'cursor_ch', intval( $_POST['snippet_editor_cursor_ch'] ), $redirect_uri );
295 }
296
297 wp_redirect( esc_url_raw( $redirect_uri ) );
298 exit;
299 }
300
301 /**
302 * Add a description editor to the single snippet page
303 *
304 * @param Code_Snippet $snippet The snippet being used for this page
305 */
306 function render_description_editor( Code_Snippet $snippet ) {
307 $settings = code_snippets_get_settings();
308 $settings = $settings['description_editor'];
309 $heading = __( 'Description', 'code-snippets' );
310
311 /* Hack to remove space between heading and editor tabs */
312 if ( ! $settings['media_buttons'] && 'false' !== get_user_option( 'rich_editing' ) ) {
313 $heading = "<div>$heading</div>";
314 }
315
316 echo '<h2><label for="snippet_description">', $heading, '</label></h2>';
317
318 remove_editor_styles(); // stop custom theme styling interfering with the editor
319
320 wp_editor(
321 $snippet->desc,
322 'description',
323 apply_filters( 'code_snippets/admin/description_editor_settings', array(
324 'textarea_name' => 'snippet_description',
325 'textarea_rows' => $settings['rows'],
326 'teeny' => ! $settings['use_full_mce'],
327 'media_buttons' => $settings['media_buttons'],
328 ) )
329 );
330 }
331
332 /**
333 * Render the interface for editing snippet tags
334 *
335 * @param Code_Snippet $snippet the snippet currently being edited
336 */
337 function render_tags_editor( Code_Snippet $snippet ) {
338
339 ?>
340 <h2 style="margin: 25px 0 10px;">
341 <label for="snippet_tags" style="cursor: auto;">
342 <?php esc_html_e( 'Tags', 'code-snippets' ); ?>
343 </label>
344 </h2>
345
346 <input type="text" id="snippet_tags" name="snippet_tags" style="width: 100%;"
347 placeholder="<?php esc_html_e( 'Enter a list of tags; separated by commas', 'code-snippets' ); ?>"
348 value="<?php echo esc_attr( $snippet->tags_list ); ?>" />
349 <?php
350 }
351
352 /**
353 * Render the snippet priority setting
354 *
355 * @param Code_Snippet $snippet the snippet currently being edited
356 */
357 public function render_priority_setting( Code_Snippet $snippet ) {
358 ?>
359 <p class="snippet-priority"
360 title="<?php esc_attr_e( 'Snippets with a lower priority number will run before those with a higher number.', 'code-snippets' ); ?>">
361 <label for="snippet_priority"><?php esc_html_e( 'Priority', 'code-snippets' ); ?></label>
362
363 <input name="snippet_priority" type="number" id="snippet_priority" value="<?php echo intval( $snippet->priority ); ?>">
364 </p>
365 <?php
366 }
367
368 /**
369 * Render the snippet scope setting
370 *
371 * @param Code_Snippet $snippet the snippet currently being edited
372 */
373 function render_scope_setting( Code_Snippet $snippet ) {
374
375 $icons = Code_Snippet::get_scope_icons();
376
377 $labels = array(
378 'global' => __( 'Run snippet everywhere', 'code-snippets' ),
379 'admin' => __( 'Only run in administration area', 'code-snippets' ),
380 'front-end' => __( 'Only run on site front-end', 'code-snippets' ),
381 'single-use' => __( 'Only run once', 'code-snippets' ),
382 );
383
384 echo '<h2 class="screen-reader-text">' . esc_html__( 'Scope', 'code-snippets' ) . '</h2><p class="snippet-scope">';
385
386 foreach ( Code_Snippet::get_all_scopes() as $scope ) {
387 printf( '<label><input type="radio" name="snippet_scope" value="%s"', $scope );
388 checked( $scope, $snippet->scope );
389 printf( '> <span class="dashicons dashicons-%s"></span> %s</label>', $icons[ $scope ], esc_html( $labels[ $scope ] ) );
390 }
391
392 echo '</p>';
393 }
394
395 /**
396 * Render the setting for shared network snippets
397 *
398 * @param object $snippet The snippet currently being edited
399 */
400 function render_multisite_sharing_setting( $snippet ) {
401 $shared_snippets = get_site_option( 'shared_network_snippets', array() );
402 ?>
403
404 <div class="snippet-sharing-setting">
405 <h2 class="screen-reader-text"><?php _e( 'Sharing Settings', 'code-snippets' ); ?></h2>
406 <label for="snippet_sharing">
407 <input type="checkbox" name="snippet_sharing"
408 <?php checked( in_array( $snippet->id, $shared_snippets ) ); ?>>
409 <?php esc_html_e( 'Allow this snippet to be activated on individual sites on the network', 'code-snippets' ); ?>
410 </label>
411 </div>
412
413 <?php
414 }
415
416 /**
417 * Retrieve the first error in a snippet's code
418 *
419 * @param $snippet_id
420 *
421 * @return array|bool
422 */
423 private function get_snippet_error( $snippet_id ) {
424
425 if ( ! intval( $snippet_id ) ) {
426 return false;
427 }
428
429 $snippet = get_snippet( intval( $snippet_id ) );
430
431 if ( '' === $snippet->code ) {
432 return false;
433 }
434
435 ob_start();
436 $result = eval( $snippet->code );
437 ob_end_clean();
438
439 if ( false !== $result ) {
440 return false;
441 }
442
443 $error = error_get_last();
444
445 if ( is_null( $error ) ) {
446 return false;
447 }
448
449 return $error;
450 }
451
452 /**
453 * Print the status and error messages
454 */
455 protected function print_messages() {
456
457 if ( ! isset( $_REQUEST['result'] ) ) {
458 return;
459 }
460
461 $result = $_REQUEST['result'];
462
463 if ( 'code-error' === $result ) {
464
465 if ( isset( $_REQUEST['id'] ) && $error = $this->get_snippet_error( $_REQUEST['id'] ) ) {
466
467 printf(
468 '<div id="message" class="error fade"><p>%s</p><p><strong>%s</strong></p></div>',
469 /* translators: %d: line of file where error originated */
470 sprintf( __( 'The snippet has been deactivated due to an error on line %d:', 'code-snippets' ), $error['line'] ),
471 $error['message']
472 );
473
474 } else {
475 echo '<div id="message" class="error fade"><p>', __( 'The snippet has been deactivated due to an error in the code.', 'code-snippets' ), '</p></div>';
476 }
477
478 return;
479 }
480
481 if ( 'save-error' === $result ) {
482 echo '<div id="message" class="error fade"><p>', __( 'An error occurred when saving the snippet.', 'code-snippets' ), '</p></div>';
483
484 return;
485 }
486
487 $messages = array(
488 'added' => __( 'Snippet <strong>added</strong>.', 'code-snippets' ),
489 'updated' => __( 'Snippet <strong>updated</strong>.', 'code-snippets' ),
490 'added-and-activated' => __( 'Snippet <strong>added</strong> and <strong>activated</strong>.', 'code-snippets' ),
491 'updated-and-executed' => __( 'Snippet <strong>added</strong> and <strong>executed</strong>.', 'code-snippets' ),
492 'updated-and-activated' => __( 'Snippet <strong>updated</strong> and <strong>activated</strong>.', 'code-snippets' ),
493 'updated-and-deactivated' => __( 'Snippet <strong>updated</strong> and <strong>deactivated</strong>.', 'code-snippets' ),
494 );
495
496 if ( isset( $messages[ $result ] ) ) {
497 echo '<div id="message" class="updated fade"><p>', $messages[ $result ], '</p></div>';
498 }
499 }
500
501 /**
502 * Enqueue assets for the edit menu
503 */
504 public function enqueue_assets() {
505 $plugin = code_snippets();
506 $rtl = is_rtl() ? '-rtl' : '';
507
508 code_snippets_enqueue_editor();
509
510 wp_enqueue_style(
511 'code-snippets-edit',
512 plugins_url( "css/min/edit{$rtl}.css", $plugin->file ),
513 array(), $plugin->version
514 );
515
516 $tags_enabled = code_snippets_get_setting( 'general', 'enable_tags' );
517
518 /* the tag-it library has a number of jQuery dependencies */
519 $tagit_deps = array(
520 'jquery', 'jquery-ui-core',
521 'jquery-ui-widget', 'jquery-ui-position', 'jquery-ui-autocomplete',
522 'jquery-effects-blind', 'jquery-effects-highlight',
523 );
524
525 wp_enqueue_script(
526 'code-snippets-edit-menu',
527 plugins_url( 'js/min/edit.js', $plugin->file ),
528 $tags_enabled ? $tagit_deps : array(),
529 $plugin->version, true
530 );
531
532 $atts = code_snippets_get_editor_atts( array(), true );
533 $inline_script = 'var code_snippets_editor_atts = ' . $atts . ';';
534
535 if ( $tags_enabled ) {
536 $snippet_tags = wp_json_encode( get_all_snippet_tags() );
537 $inline_script .= "\n" . 'var code_snippets_all_tags = ' . $snippet_tags . ';';
538 }
539
540 wp_add_inline_script( 'code-snippets-edit-menu', $inline_script, 'before' );
541 }
542
543 /**
544 * Remove the old CodeMirror version used by the Debug Bar Console plugin
545 * that is messing up the snippet editor
546 */
547 function remove_debug_bar_codemirror() {
548
549 /* Try to discern if we are on the single snippet page as best as we can at this early time */
550 if ( ! is_admin() || 'admin.php' !== $GLOBALS['pagenow'] ) {
551 return;
552 }
553
554 if ( ! isset( $_GET['page'] ) || code_snippets()->get_menu_slug( 'edit' ) !== $_GET['page'] && code_snippets()->get_menu_slug( 'settings' ) !== $_GET['page'] ) {
555 return;
556 }
557
558 remove_action( 'debug_bar_enqueue_scripts', 'debug_bar_console_scripts' );
559 }
560 }
561