PluginProbe
Code Snippets / 4.0.0-beta.1
Code Snippets v4.0.0-beta.1
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 / Integration / Admin_Bar.php

Admin_Bar.php in Code Snippets 4.0.0-beta.1, at php/Integration/Admin_Bar.php

539 lines 16.0 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\Integration;
4
5 use Code_Snippets\Model\Snippet;
6 use Code_Snippets\REST_API\Snippets\Snippets_REST_Controller;
7 use WP_Admin_Bar;
8 use function Code_Snippets\code_snippets;
9 use function Code_Snippets\get_snippets;
10 use function Code_Snippets\Settings\are_settings_unified;
11 use function Code_Snippets\Settings\get_setting;
12 use const Code_Snippets\PLUGIN_FILE;
13 use const Code_Snippets\PLUGIN_VERSION;
14
15 /**
16 * Register Code Snippets quick links in the WordPress Admin Bar.
17 *
18 * @package Code_Snippets
19 */
20 class Admin_Bar {
21
22 /**
23 * Root node ID.
24 *
25 * @var string
26 */
27 private const ROOT_NODE_ID = 'code-snippets';
28
29 /**
30 * Active snippets pagination query arg.
31 *
32 * @var string
33 */
34 private const ACTIVE_PAGE_QUERY_ARG = 'code_snippets_ab_active_page';
35
36 /**
37 * Inactive snippets pagination query arg.
38 *
39 * @var string
40 */
41 private const INACTIVE_PAGE_QUERY_ARG = 'code_snippets_ab_inactive_page';
42
43 /**
44 * Safe mode node ID.
45 *
46 * @var string
47 */
48 private const SAFE_MODE_NODE_ID = 'code-snippets-safe-mode';
49
50 /**
51 * Script handle.
52 *
53 * @var string
54 */
55 private const SCRIPT_HANDLE = 'code-snippets-admin-bar';
56
57 /**
58 * Stylesheet handle.
59 *
60 * @var string
61 */
62 private const STYLE_HANDLE = 'code-snippets-admin-bar';
63
64 /**
65 * Class constructor.
66 */
67 public function __construct() {
68 add_action( 'admin_bar_menu', [ $this, 'register_nodes' ], 80 );
69
70 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
71 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
72 }
73
74 /**
75 * Enqueue scripts and styles for admin bar nodes.
76 *
77 * @return void
78 */
79 public function enqueue_assets(): void {
80 if ( ! is_admin_bar_showing() ) {
81 return;
82 }
83
84 wp_enqueue_style(
85 self::STYLE_HANDLE,
86 plugins_url( 'dist/admin-bar.css', PLUGIN_FILE ),
87 [ 'admin-bar', 'dashicons' ],
88 PLUGIN_VERSION
89 );
90
91 wp_enqueue_script(
92 self::SCRIPT_HANDLE,
93 plugins_url( 'dist/admin-bar.js', PLUGIN_FILE ),
94 [],
95 PLUGIN_VERSION,
96 [ 'in_footer' => true ]
97 );
98
99 wp_localize_script(
100 self::SCRIPT_HANDLE,
101 'CODE_SNIPPETS_ADMIN_BAR',
102 [
103 'restUrl' => esc_url_raw( rest_url( Snippets_REST_Controller::get_base_route() ) ),
104 'nonce' => wp_create_nonce( 'wp_rest' ),
105 'perPage' => $this->get_snippet_limit(),
106 'isNetwork' => is_network_admin(),
107 'excludeTypes' => [ 'cond' ],
108 // translators: %d: snippet identifier.
109 'snippetPlaceholder' => esc_html__( 'Snippet #%d', 'code-snippets' ),
110 'editUrlBase' => code_snippets()->get_menu_url( 'edit' ),
111 'activeNodeId' => 'wp-admin-bar-' . self::ROOT_NODE_ID . '-active-snippets',
112 'inactiveNodeId' => 'wp-admin-bar-' . self::ROOT_NODE_ID . '-inactive-snippets',
113 ]
114 );
115 }
116
117 /**
118 * Register admin bar nodes.
119 *
120 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
121 *
122 * @return void
123 */
124 public function register_nodes( WP_Admin_Bar $wp_admin_bar ): void {
125 if ( ! is_admin_bar_showing() || ! code_snippets()->current_user_can() ) {
126 return;
127 }
128
129 // Always show safe mode indicator regardless of setting.
130 $this->add_safe_mode_nodes( $wp_admin_bar, code_snippets()->evaluate_functions->is_safe_mode_active() );
131
132 // Check if admin bar menu is enabled via settings.
133 $is_enabled = get_setting( 'general', 'enable_admin_bar' );
134
135 if ( ! $is_enabled && ! apply_filters( 'code_snippets/admin_bar/enabled', false ) ) {
136 return;
137 }
138
139 $title = sprintf(
140 '<span class="ab-icon code-snippets-admin-bar-icon" aria-hidden="true"></span><span class="ab-label">%s</span>',
141 esc_html__( 'Snippets', 'code-snippets' )
142 );
143
144 $wp_admin_bar->add_node(
145 [
146 'id' => self::ROOT_NODE_ID,
147 'title' => $title,
148 'href' => code_snippets()->get_menu_url( 'manage' ),
149 ]
150 );
151
152 $this->add_quick_links( $wp_admin_bar );
153 $this->add_snippet_listings( $wp_admin_bar );
154 $this->add_safe_mode_link( $wp_admin_bar );
155 }
156
157 /**
158 * Add menu item for safe mode status.
159 *
160 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
161 * @param bool $is_safe_mode_active Whether safe mode is active.
162 *
163 * @return void
164 */
165 private function add_safe_mode_nodes( WP_Admin_Bar $wp_admin_bar, bool $is_safe_mode_active ): void {
166 if ( ! $is_safe_mode_active ) {
167 return;
168 }
169
170 $wp_admin_bar->add_node(
171 [
172 'id' => self::SAFE_MODE_NODE_ID,
173 'title' => esc_html__( 'Snippets Safe Mode Active', 'code-snippets' ),
174 'href' => 'https://snipco.de/safe-mode',
175 'parent' => 'top-secondary',
176 'meta' => [
177 'class' => 'code-snippets-safe-mode code-snippets-safe-mode-active',
178 'target' => '_blank',
179 'rel' => 'noopener noreferrer',
180 ],
181 ]
182 );
183 }
184
185 /**
186 * Add a safe mode documentation link under the Code Snippets menu.
187 *
188 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
189 *
190 * @return void
191 */
192 private function add_safe_mode_link( WP_Admin_Bar $wp_admin_bar ): void {
193 $title = sprintf(
194 '%s <span class="code-snippets-external-icon dashicons dashicons-external" aria-hidden="true"></span>',
195 esc_html__( 'Safe Mode', 'code-snippets' )
196 );
197
198 $wp_admin_bar->add_node(
199 [
200 'id' => self::ROOT_NODE_ID . '-safe-mode-doc',
201 'title' => $title,
202 'href' => 'https://snipco.de/safe-mode',
203 'parent' => self::ROOT_NODE_ID,
204 'meta' => [
205 'class' => 'code-snippets-safe-mode',
206 'target' => '_blank',
207 'rel' => 'noopener noreferrer',
208 ],
209 ]
210 );
211 }
212
213 /**
214 * Add quick links to common Code Snippets screens.
215 *
216 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
217 *
218 * @return void
219 */
220 private function add_quick_links( WP_Admin_Bar $wp_admin_bar ): void {
221 $plugin = code_snippets();
222 $is_licensed = $plugin->licensing->is_licensed();
223 $upgrade_url = self_admin_url( 'admin.php?page=code_snippets_upgrade' );
224
225 $wp_admin_bar->add_node(
226 [
227 'id' => self::ROOT_NODE_ID . '-manage',
228 'title' => esc_html_x( 'Manage', 'snippets', 'code-snippets' ),
229 'href' => $plugin->get_menu_url( 'manage' ),
230 'parent' => self::ROOT_NODE_ID,
231 ]
232 );
233
234 $statuses = [
235 'all' => _x( 'All Snippets', 'snippets', 'code-snippets' ),
236 'active' => _x( 'Active Snippets', 'snippets', 'code-snippets' ),
237 'inactive' => _x( 'Inactive Snippets', 'snippets', 'code-snippets' ),
238 ];
239
240 foreach ( $statuses as $status => $label ) {
241 $wp_admin_bar->add_node(
242 [
243 'id' => self::ROOT_NODE_ID . "-status-$status",
244 'title' => esc_html( $label ),
245 'href' => esc_url( add_query_arg( 'status', $status, $plugin->get_menu_url( 'manage' ) ) ),
246 'parent' => self::ROOT_NODE_ID . '-manage',
247 ]
248 );
249 }
250
251 $wp_admin_bar->add_node(
252 [
253 'id' => self::ROOT_NODE_ID . '-add',
254 'title' => esc_html_x( 'Add New', 'snippet', 'code-snippets' ),
255 'href' => $plugin->get_menu_url( 'add' ),
256 'parent' => self::ROOT_NODE_ID,
257 ]
258 );
259
260 $types = [
261 'php' => _x( 'Functions (PHP)', 'snippet type', 'code-snippets' ),
262 'html' => _x( 'Content (HTML)', 'snippet type', 'code-snippets' ),
263 'css' => _x( 'Styles (CSS)', 'snippet type', 'code-snippets' ),
264 'js' => _x( 'Scripts (JS)', 'snippet type', 'code-snippets' ),
265 'cond' => _x( 'Conditions (COND)', 'snippet type', 'code-snippets' ),
266 ];
267
268 $types = array_intersect_key( $types, array_flip( Snippet::get_types() ) );
269 $pro_types = [ 'css', 'js', 'cond' ];
270
271 foreach ( $types as $type => $label ) {
272 $is_disabled = in_array( $type, $pro_types, true ) && ! $is_licensed;
273
274 $url = $is_disabled ?
275 $upgrade_url :
276 add_query_arg( 'type', $type, $plugin->get_menu_url( 'add' ) );
277
278 $wp_admin_bar->add_node(
279 [
280 'id' => self::ROOT_NODE_ID . "-add-$type",
281 'title' => esc_html( $label ),
282 'href' => esc_url( $url ),
283 'parent' => self::ROOT_NODE_ID . '-add',
284 'meta' => $is_disabled ? [ 'class' => 'code-snippets-disabled' ] : [],
285 ]
286 );
287 }
288
289 $wp_admin_bar->add_node(
290 [
291 'id' => self::ROOT_NODE_ID . '-import',
292 'title' => esc_html_x( 'Import', 'snippets', 'code-snippets' ),
293 'href' => $plugin->get_menu_url( 'import' ),
294 'parent' => self::ROOT_NODE_ID,
295 ]
296 );
297
298 $wp_admin_bar->add_node(
299 [
300 'id' => self::ROOT_NODE_ID . '-settings',
301 'title' => esc_html_x( 'Settings', 'snippets', 'code-snippets' ),
302 'href' => $plugin->get_menu_url( 'settings', are_settings_unified() ? 'network' : 'admin' ),
303 'parent' => self::ROOT_NODE_ID,
304 ]
305 );
306 }
307
308 /**
309 * Add a list of snippets under the active and inactive statuses.
310 *
311 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
312 *
313 * @return void
314 */
315 private function add_snippet_listings( WP_Admin_Bar $wp_admin_bar ): void {
316 $items_per_page = $this->get_snippet_limit();
317 if ( $items_per_page < 1 ) {
318 return;
319 }
320
321 $plugin = code_snippets();
322
323 $snippets = array_filter(
324 get_snippets(),
325 static function ( Snippet $snippet ): bool {
326 return ! $snippet->trashed && ! $snippet->is_condition();
327 }
328 );
329
330 $active_snippets = array_values(
331 array_filter(
332 $snippets,
333 static function ( Snippet $snippet ): bool {
334 return $snippet->active;
335 }
336 )
337 );
338
339 $inactive_snippets = array_values(
340 array_filter(
341 $snippets,
342 static function ( Snippet $snippet ): bool {
343 return ! $snippet->active;
344 }
345 )
346 );
347
348 usort(
349 $active_snippets,
350 static function ( Snippet $a, Snippet $b ): int {
351 return strcasecmp( $a->display_name, $b->display_name );
352 }
353 );
354
355 usort(
356 $inactive_snippets,
357 static function ( Snippet $a, Snippet $b ): int {
358 return strcasecmp( $a->display_name, $b->display_name );
359 }
360 );
361
362 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
363 $active_page = isset( $_GET[ self::ACTIVE_PAGE_QUERY_ARG ] ) ? absint( $_GET[ self::ACTIVE_PAGE_QUERY_ARG ] ) : 1;
364
365 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
366 $inactive_page = isset( $_GET[ self::INACTIVE_PAGE_QUERY_ARG ] ) ? absint( $_GET[ self::INACTIVE_PAGE_QUERY_ARG ] ) : 1;
367
368 $active_total_pages = max( 1, (int) ceil( count( $active_snippets ) / $items_per_page ) );
369 $inactive_total_pages = max( 1, (int) ceil( count( $inactive_snippets ) / $items_per_page ) );
370
371 $active_page = max( 1, min( $active_page, $active_total_pages ) );
372 $inactive_page = max( 1, min( $inactive_page, $inactive_total_pages ) );
373
374 $active_offset = ( $active_page - 1 ) * $items_per_page;
375 $inactive_offset = ( $inactive_page - 1 ) * $items_per_page;
376
377 $active_page_snippets = array_slice( $active_snippets, $active_offset, $items_per_page );
378 $inactive_page_snippets = array_slice( $inactive_snippets, $inactive_offset, $items_per_page );
379
380 $wp_admin_bar->add_node(
381 [
382 'id' => self::ROOT_NODE_ID . '-active-snippets',
383 // translators: %d: number of active snippets.
384 'title' => sprintf( esc_html__( 'Active snippets (%d)', 'code-snippets' ), count( $active_snippets ) ),
385 'href' => esc_url( add_query_arg( 'status', 'active', $plugin->get_menu_url( 'manage' ) ) ),
386 'parent' => self::ROOT_NODE_ID,
387 ]
388 );
389
390 if ( $active_total_pages > 1 ) {
391 $wp_admin_bar->add_node(
392 [
393 'id' => self::ROOT_NODE_ID . '-active-pagination',
394 'title' => $this->get_pagination_controls_html( 'active', $active_page, $active_total_pages, self::ACTIVE_PAGE_QUERY_ARG ),
395 'parent' => self::ROOT_NODE_ID . '-active-snippets',
396 'meta' => [ 'class' => 'code-snippets-pagination-node' ],
397 ]
398 );
399 }
400
401 foreach ( $active_page_snippets as $snippet ) {
402 $wp_admin_bar->add_node(
403 [
404 'id' => self::ROOT_NODE_ID . '-snippet-' . $snippet->id,
405 'title' => esc_html( $this->format_snippet_title( $snippet ) ),
406 'href' => esc_url( add_query_arg( 'id', $snippet->id, $plugin->get_menu_url( 'edit' ) ) ),
407 'parent' => self::ROOT_NODE_ID . '-active-snippets',
408 'meta' => [ 'class' => 'code-snippets-snippet-item' ],
409 ]
410 );
411 }
412
413 $wp_admin_bar->add_node(
414 [
415 'id' => self::ROOT_NODE_ID . '-inactive-snippets',
416 // translators: %d: number of inactive snippets.
417 'title' => sprintf( esc_html__( 'Inactive snippets (%d)', 'code-snippets' ), count( $inactive_snippets ) ),
418 'href' => esc_url( add_query_arg( 'status', 'inactive', $plugin->get_menu_url( 'manage' ) ) ),
419 'parent' => self::ROOT_NODE_ID,
420 ]
421 );
422
423 if ( $inactive_total_pages > 1 ) {
424 $wp_admin_bar->add_node(
425 [
426 'id' => self::ROOT_NODE_ID . '-inactive-pagination',
427 'title' => $this->get_pagination_controls_html( 'inactive', $inactive_page, $inactive_total_pages, self::INACTIVE_PAGE_QUERY_ARG ),
428 'parent' => self::ROOT_NODE_ID . '-inactive-snippets',
429 'meta' => [ 'class' => 'code-snippets-pagination-node' ],
430 ]
431 );
432 }
433
434 foreach ( $inactive_page_snippets as $snippet ) {
435 $wp_admin_bar->add_node(
436 [
437 'id' => self::ROOT_NODE_ID . '-snippet-' . $snippet->id,
438 'title' => esc_html( $this->format_snippet_title( $snippet ) ),
439 'href' => esc_url( add_query_arg( 'id', $snippet->id, $plugin->get_menu_url( 'edit' ) ) ),
440 'parent' => self::ROOT_NODE_ID . '-inactive-snippets',
441 'meta' => [ 'class' => 'code-snippets-snippet-item' ],
442 ]
443 );
444 }
445 }
446
447 /**
448 * Build an admin bar snippet title including type prefix.
449 *
450 * @param Snippet $snippet Snippet object.
451 *
452 * @return string
453 */
454 private function format_snippet_title( Snippet $snippet ): string {
455 return sprintf( '(%s) %s', strtoupper( $snippet->type ), $snippet->display_name );
456 }
457
458 /**
459 * Retrieve the number of snippets to show per page in the admin bar.
460 *
461 * @return int
462 */
463 private function get_snippet_limit(): int {
464 $limit = (int) get_setting( 'general', 'admin_bar_snippet_limit' );
465
466 if ( $limit < 1 ) {
467 $limit = 20;
468 }
469
470 return max( 1, (int) apply_filters( 'code_snippets/admin_bar/snippet_limit', $limit ) );
471 }
472
473 /**
474 * Build pagination controls HTML for a snippet listing submenu.
475 *
476 * @param string $status Snippet status: "active" or "inactive".
477 * @param int $page Current page.
478 * @param int $total_pages Total pages.
479 * @param string $page_query_arg Query arg used for progressive enhancement.
480 *
481 * @return string
482 */
483 private function get_pagination_controls_html( string $status, int $page, int $total_pages, string $page_query_arg ): string {
484 $first_url = remove_query_arg( $page_query_arg );
485 $prev_url = $page > 2 ? add_query_arg( $page_query_arg, $page - 1 ) : remove_query_arg( $page_query_arg );
486 $next_url = add_query_arg( $page_query_arg, $page + 1 );
487 $last_url = add_query_arg( $page_query_arg, $total_pages );
488
489 $disabled_first = $page <= 1 ? 'true' : 'false';
490 $disabled_prev = $page <= 1 ? 'true' : 'false';
491 $disabled_next = $page >= $total_pages ? 'true' : 'false';
492 $disabled_last = $page >= $total_pages ? 'true' : 'false';
493
494 $html = sprintf(
495 '<span class="code-snippets-pagination-controls" data-status="%1$s" data-page="%2$d" data-total-pages="%3$d" data-query-arg="%15$s">' .
496 '<a class="code-snippets-pagination-button" href="%4$s" data-action="first" aria-disabled="%9$s">&laquo;</a>' .
497 '<a class="code-snippets-pagination-button" href="%5$s" data-action="prev" aria-disabled="%10$s">&lsaquo; %6$s</a>' .
498 '<span class="code-snippets-pagination-button code-snippets-pagination-page">%7$s (%2$d/%3$d)</span>' .
499 '<a class="code-snippets-pagination-button" href="%8$s" data-action="next" aria-disabled="%11$s">%12$s &rsaquo;</a>' .
500 '<a class="code-snippets-pagination-button" href="%13$s" data-action="last" aria-disabled="%14$s">&raquo;</a>' .
501 '</span>',
502 esc_attr( $status ),
503 $page,
504 $total_pages,
505 esc_url( $first_url ),
506 esc_url( $prev_url ),
507 esc_html__( 'Back', 'code-snippets' ),
508 esc_html__( 'Page', 'code-snippets' ),
509 esc_url( $next_url ),
510 $disabled_first,
511 $disabled_prev,
512 $disabled_next,
513 esc_html__( 'Next', 'code-snippets' ),
514 esc_url( $last_url ),
515 $disabled_last,
516 esc_attr( $page_query_arg )
517 );
518
519 return wp_kses(
520 $html,
521 [
522 'span' => [
523 'class' => [],
524 'data-status' => [],
525 'data-page' => [],
526 'data-total-pages' => [],
527 'data-query-arg' => [],
528 ],
529 'a' => [
530 'class' => [],
531 'href' => [],
532 'data-action' => [],
533 'aria-disabled' => [],
534 ],
535 ]
536 );
537 }
538 }
539