PluginProbe
Code Snippets / 4.0.0-beta.2
Code Snippets v4.0.0-beta.2
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 / Settings_Menu.php

Settings_Menu.php in Code Snippets 4.0.0-beta.2, at php/Admin/Menus/Settings_Menu.php

382 lines 11.2 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\Admin\Menus;
4
5 use Code_Snippets\Admin\Contextual_Help;
6 use Code_Snippets\Settings\Settings_Fields;
7 use function Code_Snippets\code_snippets;
8 use function Code_Snippets\Settings\are_settings_unified;
9 use function Code_Snippets\Settings\do_settings_fields_with_headings;
10 use function Code_Snippets\Utils\enqueue_code_editor;
11 use function Code_Snippets\Utils\get_editor_themes;
12 use const Code_Snippets\PLUGIN_FILE;
13 use const Code_Snippets\PLUGIN_VERSION;
14 use const Code_Snippets\Settings\OPTION_GROUP;
15 use const Code_Snippets\Settings\OPTION_NAME;
16 use const Code_Snippets\Settings\CACHE_KEY;
17
18 /**
19 * This class handles the settings admin menu.
20 *
21 * @package Code_Snippets
22 */
23 class Settings_Menu extends Admin_Menu {
24
25 /**
26 * Settings page name as registered with the Settings API.
27 */
28 public const SETTINGS_PAGE = 'code-snippets';
29
30 /**
31 * Constructor.
32 */
33 public function __construct() {
34 parent::__construct(
35 'settings',
36 _x( 'Settings', 'menu label', 'code-snippets' ),
37 __( 'Snippets Settings', 'code-snippets' )
38 );
39 }
40
41 /**
42 * Register the settings menu.
43 *
44 * Deliberately bypasses the snippet view-permission gate applied by the
45 * parent. The settings page is already restricted to the core capability
46 * (`manage_options` via `get_cap()`), so it stays admin-only regardless —
47 * but it must always remain reachable so an administrator who restricts
48 * their own role's snippet permissions can never lock themselves out of the
49 * Permissions tab needed to undo it.
50 */
51 public function register() {
52 $this->add_menu( $this->slug, $this->label, $this->title );
53 }
54
55 /**
56 * The settings page is configuration territory (it includes the Permissions
57 * tab itself), so it must remain restricted to full administrators — never
58 * the broader snippet access capability that can be granted to other roles.
59 * This is also what keeps an administrator from ever losing access to the
60 * Permissions tab needed to undo a misconfiguration.
61 *
62 * @return string
63 */
64 protected function menu_cap(): string {
65 return is_multisite() && ( is_network_admin() || ! code_snippets()->is_subsite_menu_enabled() )
66 ? 'manage_network_options'
67 : 'manage_options';
68 }
69
70 /**
71 * Executed when the admin page is loaded
72 */
73 public function load() {
74 parent::load();
75
76 if ( is_network_admin() ) {
77 if ( are_settings_unified() ) {
78 $this->update_network_options();
79 } else {
80 wp_safe_redirect( code_snippets()->get_menu_url( 'settings', 'admin' ) );
81 exit;
82 }
83 }
84
85 $contextual_help = new Contextual_Help( 'settings' );
86 $contextual_help->load();
87 }
88
89 /**
90 * Enqueue the stylesheet for the settings menu
91 */
92 public function enqueue_assets() {
93 $this->enqueue_codemirror();
94 $handle = 'code-snippets-settings';
95
96 wp_enqueue_style(
97 $handle,
98 plugins_url( 'dist/settings.css', PLUGIN_FILE ),
99 array_merge( self::$style_deps, [ 'code-editor' ] ),
100 PLUGIN_VERSION
101 );
102
103 wp_enqueue_script(
104 $handle,
105 plugins_url( 'dist/settings.js', PLUGIN_FILE ),
106 array_merge( self::$script_deps, [ 'code-snippets-code-editor' ] ),
107 PLUGIN_VERSION,
108 true
109 );
110
111 wp_set_script_translations( $handle, 'code-snippets' );
112 code_snippets()->localize_script( $handle );
113
114 $this->add_codemirror_settings_script( $handle );
115
116 // Provide configuration and simple i18n for the version switch JS module.
117 wp_localize_script(
118 $handle,
119 'code_snippets_version_switch',
120 [
121 'ajaxurl' => admin_url( 'admin-ajax.php' ),
122 'nonce_switch' => wp_create_nonce( 'code_snippets_version_switch' ),
123 'nonce_refresh' => wp_create_nonce( 'code_snippets_refresh_versions' ),
124 ]
125 );
126
127 wp_localize_script(
128 $handle,
129 '__code_snippets_i18n',
130 [
131 'selectDifferent' => esc_html__( 'Please select a different version to switch to.', 'code-snippets' ),
132 'switching' => esc_html__( 'Switching…', 'code-snippets' ),
133 'processing' => esc_html__( 'Processing version switch. Please wait…', 'code-snippets' ),
134 'error' => esc_html__( 'An error occurred.', 'code-snippets' ),
135 'errorSwitch' => esc_html__( 'An error occurred while switching versions. Please try again.', 'code-snippets' ),
136 'refreshing' => esc_html__( 'Refreshing…', 'code-snippets' ),
137 'refreshed' => esc_html__( 'Refreshed!', 'code-snippets' ),
138 ]
139 );
140 }
141
142 /**
143 * Enqueue the CodeMirror scripts and styles, including all themes.
144 *
145 * @return void
146 */
147 protected function enqueue_codemirror() {
148 enqueue_code_editor( 'php' );
149 $themes = get_editor_themes();
150
151 foreach ( $themes as $theme ) {
152 wp_enqueue_style(
153 'code-snippets-editor-theme-' . $theme,
154 plugins_url( "dist/editor-themes/$theme.css", PLUGIN_FILE ),
155 [ 'code-editor' ],
156 PLUGIN_VERSION
157 );
158 }
159 }
160
161 /**
162 * Load the CodeMirror settings as an inline script variable.
163 *
164 * @param string $handle The handle of the script to which the settings will be added.
165 *
166 * @return void
167 */
168 protected function add_codemirror_settings_script( string $handle ) {
169 $field_definitions = Settings_Fields::get_field_definitions();
170 $editor_fields = [];
171
172 foreach ( $field_definitions['editor'] as $name => $field ) {
173 if ( empty( $field['codemirror'] ) ) {
174 continue;
175 }
176
177 $editor_fields[] = [
178 'name' => $name,
179 'type' => $field['type'],
180 'codemirror' => addslashes( $field['codemirror'] ),
181 ];
182 }
183
184 // Pass the saved options to the external JavaScript file.
185 $inline_script = 'var code_snippets_editor_settings = ' . wp_json_encode( $editor_fields ) . ';';
186
187 wp_add_inline_script( $handle, $inline_script, 'before' );
188 }
189
190 /**
191 * Retrieve the list of settings sections.
192 *
193 * @return array<string, array<string, mixed>>
194 */
195 private function get_sections(): array {
196 global $wp_settings_sections;
197
198 if ( ! isset( $wp_settings_sections[ self::SETTINGS_PAGE ] ) ) {
199 return array();
200 }
201
202 return (array) $wp_settings_sections[ self::SETTINGS_PAGE ];
203 }
204
205 /**
206 * Retrieve the name of the settings section currently being viewed.
207 *
208 * @param string $default_section Name of the default tab displayed.
209 *
210 * @return string
211 */
212 public function get_current_section( string $default_section = '' ): string {
213 $sections = $this->get_sections();
214
215 if ( ! $sections ) {
216 return $default_section;
217 }
218
219 // Fall back to the first registered tab. Naming a specific section here
220 // meant the page rendered empty whenever that section stopped existing.
221 $fallback = $default_section && isset( $sections[ $default_section ] )
222 ? $default_section
223 : (string) array_key_first( $sections );
224
225 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Value is matched to registered sections.
226 $active_tab = isset( $_REQUEST['section'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['section'] ) ) : $fallback;
227 return isset( $sections[ $active_tab ] ) ? $active_tab : $fallback;
228 }
229
230 /**
231 * Render the admin screen
232 */
233 public function render() {
234 $this->render_navigation();
235
236 $update_url = is_network_admin() ? add_query_arg( 'update_site_option', true ) : admin_url( 'options.php' );
237 $current_section = $this->get_current_section();
238
239 ?>
240 <div class="wrap code-snippets-settings" data-active-tab="<?php echo esc_attr( $current_section ); ?>">
241 <?php $this->render_section_tabs(); ?>
242
243 <div class="snippets-page-header">
244 <h1><?php esc_html_e( 'Snippets Settings', 'code-snippets' ); ?></h1>
245 </div>
246
247 <?php
248 if ( code_snippets()->is_compact_menu() ) {
249 $actions = [
250 _x( 'Manage', 'snippets', 'code-snippets' ) => code_snippets()->get_menu_url(),
251 _x( 'Add New', 'snippet', 'code-snippets' ) => code_snippets()->get_menu_url( 'add' ),
252 _x( 'Import', 'snippets', 'code-snippets' ) => code_snippets()->get_menu_url( 'import' ),
253 ];
254
255 echo '<p class="settings-page-actions">';
256
257 foreach ( $actions as $label => $url ) {
258 printf(
259 '<a href="%s" class="page-title-action">%s</a>',
260 esc_url( $url ),
261 esc_html( $label )
262 );
263 }
264
265 echo '</p>';
266 }
267 ?>
268
269 <hr class="wp-header-end" />
270
271 <?php settings_errors( OPTION_NAME ); ?>
272
273 <form action="<?php echo esc_url( $update_url ); ?>" method="post">
274 <input type="hidden" name="section" value="<?php echo esc_attr( $current_section ); ?>">
275 <?php
276
277 settings_fields( OPTION_GROUP );
278 $this->render_settings_sections();
279 ?>
280 <p class="submit">
281 <?php
282 submit_button( null, 'primary', 'submit', false );
283
284 submit_button(
285 __( 'Reset to Default', 'code-snippets' ),
286 'secondary',
287 sprintf( '%s[%s]', OPTION_NAME, 'reset_settings' ),
288 false
289 );
290 ?>
291 </p>
292 </form>
293 </div>
294 <?php
295 }
296
297 /**
298 * Output the settings section subnavigation bar, styled to match the
299 * subnavigation on the snippet management pages, followed by the slot
300 * that adopts the Screen Options and Help tabs.
301 */
302 protected function render_section_tabs() {
303 $sections = $this->get_sections();
304 $active_tab = $this->get_current_section();
305
306 echo '<div class="snippet-type-nav-wrapper settings-type-nav-wrapper"><nav class="snippet-type-nav settings-type-nav" id="settings-sections-tabs" aria-label="' . esc_attr__( 'Settings tabs', 'code-snippets' ) . '"><ul>';
307
308 foreach ( $sections as $section ) {
309 printf(
310 '<li><a class="snippet-type-link%s" data-section="%s" href="%s"><span>%s</span></a></li>',
311 esc_attr( $active_tab ) === $section['id'] ? ' active-type' : '',
312 esc_attr( $section['id'] ),
313 esc_url( add_query_arg( 'section', $section['id'] ) ),
314 esc_html( $section['title'] )
315 );
316 }
317
318 echo '</ul></nav></div>';
319 echo '<div id="snippets-screen-meta-slot" class="snippets-screen-meta-slot"></div>';
320 }
321
322 /**
323 * Output snippet settings sections.
324 */
325 protected function render_settings_sections() {
326 $sections = $this->get_sections();
327
328 foreach ( $sections as $section ) {
329 if ( 'license' === $section['id'] ) {
330 continue;
331 }
332
333 if ( $section['title'] ) {
334 printf(
335 '<h2 id="%s-settings" class="settings-section-title">%s</h2>' . "\n",
336 esc_attr( $section['id'] ),
337 esc_html( $section['title'] )
338 );
339 }
340
341 if ( $section['callback'] ) {
342 call_user_func( $section['callback'], $section );
343 }
344
345 printf( '<div class="settings-section %s-settings"><table class="form-table">', esc_attr( $section['id'] ) );
346
347 do_settings_fields_with_headings( self::SETTINGS_PAGE, $section['id'] );
348 echo '</table></div>';
349 }
350 }
351
352 /**
353 * Fill in for the Settings API in the Network Admin
354 */
355 public function update_network_options() {
356
357 // Ensure the settings have been saved.
358 if ( empty( $_GET['update_site_option'] ) || empty( $_POST[ OPTION_NAME ] ) ) {
359 return;
360 }
361
362 check_admin_referer( 'code-snippets-options' );
363
364 // Retrieve the saved options and save them to the database.
365 $value = map_deep( wp_unslash( $_POST[ OPTION_NAME ] ), 'sanitize_key' );
366 update_site_option( OPTION_NAME, $value );
367 wp_cache_delete( CACHE_KEY );
368
369 // Add an updated notice.
370 if ( ! count( get_settings_errors() ) ) {
371 add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'code-snippets' ), 'updated' );
372 }
373
374 set_transient( 'settings_errors', get_settings_errors(), 30 );
375
376 // Redirect back to the settings menu.
377 $redirect = add_query_arg( 'settings-updated', 'true', remove_query_arg( 'update_site_option', wp_get_referer() ) );
378 wp_safe_redirect( esc_url_raw( $redirect ) );
379 exit;
380 }
381 }
382