PluginProbe
Code Snippets / 2.10.0
Code Snippets v2.10.0
4.0.0-beta.2 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 All 65 releases
code-snippets / php / admin-menus / class-edit-menu.php

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

529 lines 15.7 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 /* Enqueue the code editor and other scripts and styles */
63 add_action( 'admin_enqueue_scripts', 'code_snippets_enqueue_codemirror' );
64 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_tagit' ), 9 );
65
66 /* Register action hooks */
67 if ( code_snippets_get_setting( 'general', 'enable_description' ) ) {
68 add_action( 'code_snippets/admin/single', array( $this, 'render_description_editor' ), 9 );
69 }
70
71 if ( code_snippets_get_setting( 'general', 'enable_tags' ) ) {
72 add_action( 'code_snippets/admin/single', array( $this, 'render_tags_editor' ) );
73 }
74
75 if ( code_snippets_get_setting( 'general', 'snippet_scope_enabled' ) ) {
76 add_action( 'code_snippets/admin/single', array( $this, 'render_scope_setting' ), 1 );
77 }
78
79 if ( is_network_admin() ) {
80 add_action( 'code_snippets/admin/single', array( $this, 'render_multisite_sharing_setting' ), 1 );
81 }
82
83 $this->process_actions();
84 }
85
86 /**
87 * Process data sent from the edit page
88 */
89 private function process_actions() {
90
91 /* Check for a valid nonce */
92 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'save_snippet' ) ) {
93 return;
94 }
95
96 if ( isset( $_POST['save_snippet'] ) || isset( $_POST['save_snippet_execute'] ) ||
97 isset( $_POST['save_snippet_activate'] ) || isset( $_POST['save_snippet_deactivate'] ) ) {
98 $this->save_posted_snippet();
99 }
100
101 if ( isset( $_POST['snippet_id'] ) ) {
102
103 /* Delete the snippet if the button was clicked */
104 if ( isset( $_POST['delete_snippet'] ) ) {
105 delete_snippet( $_POST['snippet_id'] );
106 wp_redirect( add_query_arg( 'result', 'delete', code_snippets()->get_menu_url( 'manage' ) ) );
107 exit;
108 }
109
110 /* Export the snippet if the button was clicked */
111 if ( isset( $_POST['export_snippet'] ) ) {
112 export_snippets( $_POST['snippet_id'] );
113 }
114
115 /* Download the snippet if the button was clicked */
116 if ( isset( $_POST['download_snippet'] ) ) {
117 export_snippets( $_POST['snippet_id'], null, 'php' );
118 }
119 }
120 }
121
122 /**
123 * Remove the sharing status from a network snippet
124 * @param int $snippet_id
125 */
126 private function unshare_network_snippet( $snippet_id ) {
127 $shared_snippets = get_site_option( 'shared_network_snippets', array() );
128
129 if ( ! in_array( $snippet_id, $shared_snippets ) ) {
130 return;
131 }
132
133 /* Remove the snippet ID from the array */
134 $shared_snippets = array_diff( $shared_snippets, array( $snippet_id ) );
135 update_site_option( 'shared_network_snippets', array_values( $shared_snippets ) );
136
137 /* Deactivate on all sites */
138 global $wpdb;
139 if ( $sites = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) ) {
140
141 foreach ( $sites as $site ) {
142 switch_to_blog( $site );
143 $active_shared_snippets = get_option( 'active_shared_network_snippets' );
144
145 if ( is_array( $active_shared_snippets ) ) {
146 $active_shared_snippets = array_diff( $active_shared_snippets, array( $snippet_id ) );
147 update_option( 'active_shared_network_snippets', $active_shared_snippets );
148 }
149 }
150
151 restore_current_blog();
152 }
153 }
154
155 private function code_error_callback( $out ) {
156 $error = error_get_last();
157
158 if ( is_null( $error ) ) {
159 return $out;
160 }
161
162 $m = '<h3>' . __( "Don't Panic", 'code-snippets' ) . '</h3>';
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 wp_redirect( add_query_arg(
287 array( 'id' => $snippet_id, 'result' => $result ),
288 code_snippets()->get_menu_url( 'edit' )
289 ) );
290 exit;
291 }
292
293 /**
294 * Add a description editor to the single snippet page
295 *
296 * @param Code_Snippet $snippet The snippet being used for this page
297 */
298 function render_description_editor( Code_Snippet $snippet ) {
299 $settings = code_snippets_get_settings();
300 $settings = $settings['description_editor'];
301 $heading = __( 'Description', 'code-snippets' );
302
303 /* Hack to remove space between heading and editor tabs */
304 if ( ! $settings['media_buttons'] && 'false' !== get_user_option( 'rich_editing' ) ) {
305 $heading = "<div>$heading</div>";
306 }
307
308 echo '<h2><label for="snippet_description">', $heading, '</label></h2>';
309
310 remove_editor_styles(); // stop custom theme styling interfering with the editor
311
312 wp_editor(
313 $snippet->desc,
314 'description',
315 apply_filters( 'code_snippets/admin/description_editor_settings', array(
316 'textarea_name' => 'snippet_description',
317 'textarea_rows' => $settings['rows'],
318 'teeny' => ! $settings['use_full_mce'],
319 'media_buttons' => $settings['media_buttons'],
320 ) )
321 );
322 }
323
324 /**
325 * Render the interface for editing snippet tags
326 *
327 * @param Code_Snippet $snippet the snippet currently being edited
328 */
329 function render_tags_editor( Code_Snippet $snippet ) {
330
331 ?>
332 <h2 style="margin: 25px 0 10px;">
333 <label for="snippet_tags" style="cursor: auto;">
334 <?php esc_html_e( 'Tags', 'code-snippets' ); ?>
335 </label>
336 </h2>
337
338 <input type="text" id="snippet_tags" name="snippet_tags" style="width: 100%;"
339 placeholder="<?php esc_html_e( 'Enter a list of tags; separated by commas', 'code-snippets' ); ?>"
340 value="<?php echo esc_attr( $snippet->tags_list ); ?>" />
341
342 <script type="text/javascript">
343 jQuery('#snippet_tags').tagit({
344 availableTags: <?php echo json_encode( get_all_snippet_tags() ); ?>,
345 allowSpaces: true,
346 removeConfirmation: true
347 });
348 </script>
349 <?php
350 }
351
352 /**
353 * Render the snippet scope setting
354 *
355 * @param Code_Snippet $snippet the snippet currently being edited
356 */
357 function render_scope_setting( Code_Snippet $snippet ) {
358
359 $icons = Code_Snippet::get_scope_icons();
360
361 $labels = array(
362 'global' => __( 'Run snippet everywhere', 'code-snippets' ),
363 'admin' => __( 'Only run in administration area', 'code-snippets' ),
364 'front-end' => __( 'Only run on site front-end', 'code-snippets' ),
365 'single-use' => __( 'Only run once', 'code-snippets' ),
366 );
367
368 echo '<h2 class="screen-reader-text">' . esc_html__( 'Scope', 'code-snippets' ) . '</h2><p class="snippet-scope">';
369
370 foreach ( Code_Snippet::get_all_scopes() as $scope ) {
371 printf( '<label><input type="radio" name="snippet_scope" value="%s"', $scope );
372 checked( $scope, $snippet->scope );
373 printf( '> <span class="dashicons dashicons-%s"></span> %s</label>', $icons[ $scope ], esc_html( $labels[ $scope ] ) );
374 }
375
376 echo '</p>';
377 }
378
379 /**
380 * Render the setting for shared network snippets
381 * @param object $snippet The snippet currently being edited
382 */
383 function render_multisite_sharing_setting( $snippet ) {
384 $shared_snippets = get_site_option( 'shared_network_snippets', array() );
385 ?>
386
387 <div class="snippet-sharing-setting">
388 <h2 class="screen-reader-text"><?php _e( 'Sharing Settings', 'code-snippets' ); ?></h2>
389 <label for="snippet_sharing">
390 <input type="checkbox" name="snippet_sharing"
391 <?php checked( in_array( $snippet->id, $shared_snippets ) ); ?>>
392 <?php esc_html_e( 'Allow this snippet to be activated on individual sites on the network', 'code-snippets' ); ?>
393 </label>
394 </div>
395
396 <?php
397 }
398
399 /**
400 * Retrieve the first error in a snippet's code
401 *
402 * @param $snippet_id
403 *
404 * @return array|bool
405 */
406 private function get_snippet_error( $snippet_id ) {
407
408 if ( ! intval( $snippet_id ) ) {
409 return false;
410 }
411
412 $snippet = get_snippet( intval( $snippet_id ) );
413
414 if ( '' === $snippet->code ) {
415 return false;
416 }
417
418 ob_start();
419 $result = eval( $snippet->code );
420 ob_end_clean();
421
422 if ( false !== $result ) {
423 return false;
424 }
425
426 $error = error_get_last();
427
428 if ( is_null( $error ) ) {
429 return false;
430 }
431
432 return $error;
433 }
434
435 /**
436 * Print the status and error messages
437 */
438 protected function print_messages() {
439
440 if ( ! isset( $_REQUEST['result'] ) ) {
441 return;
442 }
443
444 $result = $_REQUEST['result'];
445
446 if ( 'code-error' === $result ) {
447
448 if ( isset( $_REQUEST['id'] ) && $error = $this->get_snippet_error( $_REQUEST['id'] ) ) {
449
450 printf(
451 '<div id="message" class="error fade"><p>%s</p><p><strong>%s</strong></p></div>',
452 sprintf( __( 'The snippet has been deactivated due to an error on line %d:', 'code-snippets' ), $error['line'] ),
453 $error['message']
454 );
455
456 } else {
457 echo '<div id="message" class="error fade"><p>', __( 'The snippet has been deactivated due to an error in the code.', 'code-snippets' ), '</p></div>';
458 }
459
460 return;
461 }
462
463 if ( 'save-error' === $result ) {
464 echo '<div id="message" class="error fade"><p>', __( 'An error occurred when saving the snippet.', 'code-snippets' ), '</p></div>';
465 return;
466 }
467
468 $messages = array(
469 'added' => __( 'Snippet <strong>added</strong>.', 'code-snippets' ),
470 'updated' => __( 'Snippet <strong>updated</strong>.', 'code-snippets' ),
471 'added-and-activated' => __( 'Snippet <strong>added</strong> and <strong>activated</strong>.', 'code-snippets' ),
472 'updated-and-executed' => __( 'Snippet <strong>added</strong> and <strong>executed</strong>.', 'code-snippets' ),
473 'updated-and-activated' => __( 'Snippet <strong>updated</strong> and <strong>activated</strong>.', 'code-snippets' ),
474 'updated-and-deactivated' => __( 'Snippet <strong>updated</strong> and <strong>deactivated</strong>.', 'code-snippets' ),
475 );
476
477 if ( isset( $messages[ $result ] ) ) {
478 echo '<div id="message" class="updated fade"><p>', $messages[ $result ], '</p></div>';
479 }
480 }
481
482 /**
483 * Enqueue the Tag It library
484 */
485 function enqueue_tagit() {
486 $tagit_version = '2.0';
487 $url = plugin_dir_url( CODE_SNIPPETS_FILE );
488
489 /* Tag It UI */
490 wp_enqueue_script(
491 'code-snippets-tag-it',
492 $url . 'js/min/tag-it.js',
493 array(
494 'jquery-ui-core',
495 'jquery-ui-widget',
496 'jquery-ui-position',
497 'jquery-ui-autocomplete',
498 'jquery-effects-blind',
499 'jquery-effects-highlight',
500 ),
501 $tagit_version
502 );
503
504 wp_enqueue_style(
505 'code-snippets-tag-it',
506 $url . 'css/min/tagit.css',
507 false, $tagit_version
508 );
509 }
510
511 /**
512 * Remove the old CodeMirror version used by the Debug Bar Console plugin
513 * that is messing up the snippet editor
514 */
515 function remove_debug_bar_codemirror() {
516
517 /* Try to discern if we are on the single snippet page as best as we can at this early time */
518 if ( ! is_admin() || 'admin.php' !== $GLOBALS['pagenow'] ) {
519 return;
520 }
521
522 if ( ! isset( $_GET['page'] ) || code_snippets()->get_menu_slug( 'edit' ) !== $_GET['page'] && code_snippets()->get_menu_slug( 'settings' ) ) {
523 return;
524 }
525
526 remove_action( 'debug_bar_enqueue_scripts', 'debug_bar_console_scripts' );
527 }
528 }
529