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 / Edit_Menu.php

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

335 lines 8.8 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\Model\Snippet;
7 use function Code_Snippets\code_snippets;
8 use function Code_Snippets\get_all_snippet_tags;
9 use function Code_Snippets\get_snippet;
10 use function Code_Snippets\Settings\get_setting;
11 use function Code_Snippets\Settings\get_settings_values;
12 use function Code_Snippets\Utils\enqueue_code_editor;
13 use const Code_Snippets\PLUGIN_FILE;
14 use const Code_Snippets\PLUGIN_VERSION;
15
16 /**
17 * This class handles the add/edit menu.
18 */
19 class Edit_Menu extends Admin_Menu {
20
21 /**
22 * Handle for JavaScript asset file.
23 */
24 private const JS_HANDLE = 'code-snippets-edit-menu';
25
26 /**
27 * Handle for CSS asset file.
28 */
29 private const CSS_HANDLE = 'code-snippets-edit';
30
31 /**
32 * The snippet object currently being edited
33 *
34 * @var Snippet|null
35 * @see Edit_Menu::load_snippet_data()
36 */
37 protected ?Snippet $snippet = null;
38
39 /**
40 * Constructor.
41 *
42 * @return void
43 */
44 public function __construct() {
45 parent::__construct(
46 'edit',
47 _x( 'Edit Snippet', 'menu label', 'code-snippets' ),
48 __( 'Edit Snippet', 'code-snippets' )
49 );
50
51 $this->remove_debug_bar_codemirror();
52 }
53
54 /**
55 * Register the admin menu
56 *
57 * @return void
58 */
59 public function register() {
60 // The page itself is always registered outside the menu, so nothing that
61 // reads the menu during `admin_menu` — menu editors and role managers
62 // among them — ever sees it. Registering it in the menu and removing it
63 // afterward is why a page that never renders in the menu could still end
64 // up in someone's saved menu.
65 $this->register_without_menu_item();
66
67 $snippet_id = $this->get_requested_snippet_id();
68
69 if ( $snippet_id ) {
70 $this->add_current_snippet_menu_item( $snippet_id );
71 }
72
73 // Create New Snippet menu.
74 $this->add_menu(
75 code_snippets()->get_menu_slug( 'add' ),
76 _x( 'Add New', 'menu label', 'code-snippets' ),
77 __( 'Create New Snippet', 'code-snippets' )
78 );
79 }
80
81 /**
82 * The snippet this request is editing, if any.
83 *
84 * Read from the request rather than the current screen, because this runs
85 * during `admin_menu`, before the screen is known.
86 *
87 * @return int Snippet ID, or 0 when not editing a specific snippet.
88 */
89 private function get_requested_snippet_id(): int {
90 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only routing parameter.
91 $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
92
93 if ( $page !== $this->slug ) {
94 return 0;
95 }
96
97 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only routing parameter.
98 return isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0;
99 }
100
101 /**
102 * Register the edit page without placing it in the admin menu.
103 *
104 * @return void
105 */
106 private function register_without_menu_item(): void {
107 $hook = add_submenu_page(
108 '',
109 $this->title,
110 $this->label,
111 code_snippets()->get_cap(),
112 $this->slug,
113 array( $this, 'render' )
114 );
115
116 if ( $hook ) {
117 add_action( 'load-' . $hook, array( $this, 'load' ) );
118 }
119 }
120
121 /**
122 * Mark the snippet being edited in the menu.
123 *
124 * Added as a plain link rather than a second registration of the page: a
125 * submenu slug doubles as the identifier WordPress checks permissions
126 * against, so carrying the ID in the slug would make `page=edit-snippet`
127 * match nothing and the screen would refuse to load.
128 *
129 * @param int $snippet_id Snippet being edited.
130 *
131 * @return void
132 */
133 private function add_current_snippet_menu_item( int $snippet_id ): void {
134 add_submenu_page(
135 $this->base_slug,
136 $this->title,
137 $this->label,
138 code_snippets()->get_cap(),
139 add_query_arg(
140 [
141 'page' => $this->slug,
142 'id' => $snippet_id,
143 ],
144 'admin.php'
145 ),
146 '',
147 1
148 );
149 }
150
151 /**
152 * Retrieve the hookname of the edit page.
153 *
154 * The page is registered without a parent, so WordPress files it under
155 * "admin_page_" rather than under the Snippets menu; deriving it from the
156 * menu slug would name a screen that does not exist.
157 *
158 * @return string
159 */
160 public function get_hookname(): string {
161 return get_plugin_page_hookname( $this->slug, '' );
162 }
163
164 /**
165 * Retrieve every hookname registered by this menu, including the separate
166 * "Add New" page, so screen-based checks recognize both editor views.
167 *
168 * @return string[]
169 */
170 public function get_hooknames(): array {
171 return [
172 $this->get_hookname(),
173 get_plugin_page_hookname( code_snippets()->get_menu_slug( 'add' ), $this->base_slug ),
174 ];
175 }
176
177 /**
178 * Executed when the menu is loaded.
179 *
180 * @return void
181 */
182 public function load() {
183 parent::load();
184
185 $this->load_snippet_data();
186 $this->ensure_correct_page();
187
188 $contextual_help = new Contextual_Help( 'edit' );
189 $contextual_help->load();
190 }
191
192 /**
193 * Disallow vising the Edit Snippet page without a valid ID.
194 *
195 * @return void
196 */
197 protected function ensure_correct_page() {
198 $screen = get_current_screen();
199 $edit_hook = $this->get_hookname() . ( $screen->in_admin( 'network' ) ? '-network' : '' );
200
201 // Disallow visiting the edit snippet page without a valid ID.
202 if (
203 $screen->base === $edit_hook
204 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
205 && ( empty( $_REQUEST['id'] ) || 0 === $this->snippet->id || null === $this->snippet->id )
206 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
207 && ! isset( $_REQUEST['preview'] )
208 ) {
209 wp_safe_redirect( code_snippets()->get_menu_url( 'add' ) );
210 exit;
211 }
212 }
213
214 /**
215 * Render the edit menu interface.
216 *
217 * @return void
218 */
219 public function render() {
220 echo '<div id="edit-snippet-container" class="wrap"></div>';
221 }
222
223 /**
224 * Load the data for the snippet currently being edited.
225 */
226 public function load_snippet_data() {
227 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
228 $edit_id = isset( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
229
230 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
231 $edit_type = isset( $_REQUEST['type'] ) ? sanitize_key( wp_unslash( $_REQUEST['type'] ) ) : '';
232
233 $this->snippet = get_snippet( $edit_id );
234
235 if ( 0 === $edit_id && $edit_type !== $this->snippet->type ) {
236 $default_scopes = [
237 'php' => 'global',
238 'css' => 'site-css',
239 'html' => 'content',
240 'js' => 'site-head-js',
241 'cond' => 'condition',
242 ];
243
244 if ( isset( $default_scopes[ $edit_type ] ) ) {
245 $this->snippet->scope = $default_scopes[ $edit_type ];
246 }
247 }
248
249 $this->snippet = apply_filters( 'code_snippets/admin/load_snippet_data', $this->snippet );
250 }
251
252 /**
253 * Enqueue assets for the edit menu
254 *
255 * @return void
256 */
257 public function enqueue_assets() {
258
259 $settings = get_settings_values();
260 $tags_enabled = $settings['general']['enable_tags'];
261 $desc_enabled = $settings['general']['enable_description'];
262
263 enqueue_code_editor( $this->snippet->type );
264
265 wp_enqueue_style(
266 self::CSS_HANDLE,
267 plugins_url( 'dist/edit.css', PLUGIN_FILE ),
268 [
269 'code-editor',
270 'wp-components',
271 ],
272 PLUGIN_VERSION
273 );
274
275 wp_enqueue_script(
276 self::JS_HANDLE,
277 plugins_url( 'dist/edit.js', PLUGIN_FILE ),
278 array_merge( [ 'code-snippets-code-editor' ], self::$script_deps ),
279 PLUGIN_VERSION,
280 [ 'in_footer' => true ]
281 );
282
283 wp_set_script_translations( self::JS_HANDLE, 'code-snippets' );
284
285 if ( $desc_enabled ) {
286 remove_editor_styles();
287 wp_enqueue_editor();
288 }
289
290 code_snippets()->localize_script( self::JS_HANDLE );
291
292 wp_localize_script(
293 self::JS_HANDLE,
294 'CODE_SNIPPETS_EDIT',
295 [
296 'snippet' => $this->snippet->get_fields(),
297 'activateByDefault' => get_setting( 'general', 'activate_by_default' ),
298 'editorTheme' => get_setting( 'editor', 'theme' ),
299 'enableDownloads' => apply_filters( 'code_snippets/enable_downloads', true ),
300 'enableDescription' => $desc_enabled,
301 'tagOptions' => apply_filters(
302 'code_snippets/tag_editor_options',
303 [
304 'enabled' => $tags_enabled,
305 'allowSpaces' => true,
306 'availableTags' => $tags_enabled ? get_all_snippet_tags() : [],
307 ]
308 ),
309 'descEditorOptions' => [
310 'rows' => $settings['general']['visual_editor_rows'],
311 ],
312 ]
313 );
314 }
315
316 /**
317 * Remove the old CodeMirror version used by the Debug Bar Console plugin that is messing up the snippet editor.
318 */
319 public function remove_debug_bar_codemirror() {
320 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
321 $current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
322
323 // Try to discern if we are on the single snippet page as good as we can at this early time.
324 $is_codemirror_page =
325 is_admin() && 'admin.php' === $GLOBALS['pagenow'] && $current_page && (
326 code_snippets()->get_menu_slug( 'edit' ) === $current_page ||
327 code_snippets()->get_menu_slug( 'settings' ) === $current_page
328 );
329
330 if ( $is_codemirror_page ) {
331 remove_action( 'debug_bar_enqueue_scripts', 'debug_bar_console_scripts' );
332 }
333 }
334 }
335