PluginProbe
Code Snippets / 3.10.0
Code Snippets v3.10.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 / Integration / Admin_Bar.php

Admin_Bar.php in Code Snippets 3.10.0, at php/Integration/Admin_Bar.php

538 lines 15.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\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 );
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 *
162 * @return void
163 */
164 private function add_safe_mode_nodes( WP_Admin_Bar $wp_admin_bar ): void {
165 if ( ! Evaluate_Functions::is_safe_mode_active() ) {
166 return;
167 }
168
169 $wp_admin_bar->add_node(
170 [
171 'id' => self::SAFE_MODE_NODE_ID,
172 'title' => esc_html__( 'Snippets Safe Mode Active', 'code-snippets' ),
173 'href' => 'https://snipco.de/safe-mode',
174 'parent' => 'top-secondary',
175 'meta' => [
176 'class' => 'code-snippets-safe-mode code-snippets-safe-mode-active',
177 'target' => '_blank',
178 'rel' => 'noopener noreferrer',
179 ],
180 ]
181 );
182 }
183
184 /**
185 * Add a safe mode documentation link under the Code Snippets menu.
186 *
187 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
188 *
189 * @return void
190 */
191 private function add_safe_mode_link( WP_Admin_Bar $wp_admin_bar ): void {
192 $title = sprintf(
193 '%s <span class="code-snippets-external-icon dashicons dashicons-external" aria-hidden="true"></span>',
194 esc_html__( 'Safe Mode', 'code-snippets' )
195 );
196
197 $wp_admin_bar->add_node(
198 [
199 'id' => self::ROOT_NODE_ID . '-safe-mode-doc',
200 'title' => $title,
201 'href' => 'https://snipco.de/safe-mode',
202 'parent' => self::ROOT_NODE_ID,
203 'meta' => [
204 'class' => 'code-snippets-safe-mode',
205 'target' => '_blank',
206 'rel' => 'noopener noreferrer',
207 ],
208 ]
209 );
210 }
211
212 /**
213 * Add quick links to common Code Snippets screens.
214 *
215 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
216 *
217 * @return void
218 */
219 private function add_quick_links( WP_Admin_Bar $wp_admin_bar ): void {
220 $plugin = code_snippets();
221 $is_licensed = $plugin->licensing->is_licensed();
222 $upgrade_url = self_admin_url( 'admin.php?page=code_snippets_upgrade' );
223
224 $wp_admin_bar->add_node(
225 [
226 'id' => self::ROOT_NODE_ID . '-manage',
227 'title' => esc_html_x( 'Manage', 'snippets', 'code-snippets' ),
228 'href' => $plugin->get_menu_url( 'manage' ),
229 'parent' => self::ROOT_NODE_ID,
230 ]
231 );
232
233 $statuses = [
234 'all' => _x( 'All Snippets', 'snippets', 'code-snippets' ),
235 'active' => _x( 'Active Snippets', 'snippets', 'code-snippets' ),
236 'inactive' => _x( 'Inactive Snippets', 'snippets', 'code-snippets' ),
237 ];
238
239 foreach ( $statuses as $status => $label ) {
240 $wp_admin_bar->add_node(
241 [
242 'id' => self::ROOT_NODE_ID . "-status-$status",
243 'title' => esc_html( $label ),
244 'href' => esc_url( add_query_arg( 'status', $status, $plugin->get_menu_url( 'manage' ) ) ),
245 'parent' => self::ROOT_NODE_ID . '-manage',
246 ]
247 );
248 }
249
250 $wp_admin_bar->add_node(
251 [
252 'id' => self::ROOT_NODE_ID . '-add',
253 'title' => esc_html_x( 'Add New', 'snippet', 'code-snippets' ),
254 'href' => $plugin->get_menu_url( 'add' ),
255 'parent' => self::ROOT_NODE_ID,
256 ]
257 );
258
259 $types = [
260 'php' => _x( 'Functions (PHP)', 'snippet type', 'code-snippets' ),
261 'html' => _x( 'Content (HTML)', 'snippet type', 'code-snippets' ),
262 'css' => _x( 'Styles (CSS)', 'snippet type', 'code-snippets' ),
263 'js' => _x( 'Scripts (JS)', 'snippet type', 'code-snippets' ),
264 'cond' => _x( 'Conditions (COND)', 'snippet type', 'code-snippets' ),
265 ];
266
267 $types = array_intersect_key( $types, array_flip( Snippet::get_types() ) );
268 $pro_types = [ 'css', 'js', 'cond' ];
269
270 foreach ( $types as $type => $label ) {
271 $is_disabled = in_array( $type, $pro_types, true ) && ! $is_licensed;
272
273 $url = $is_disabled ?
274 $upgrade_url :
275 add_query_arg( 'type', $type, $plugin->get_menu_url( 'add' ) );
276
277 $wp_admin_bar->add_node(
278 [
279 'id' => self::ROOT_NODE_ID . "-add-$type",
280 'title' => esc_html( $label ),
281 'href' => esc_url( $url ),
282 'parent' => self::ROOT_NODE_ID . '-add',
283 'meta' => $is_disabled ? [ 'class' => 'code-snippets-disabled' ] : [],
284 ]
285 );
286 }
287
288 $wp_admin_bar->add_node(
289 [
290 'id' => self::ROOT_NODE_ID . '-import',
291 'title' => esc_html_x( 'Import', 'snippets', 'code-snippets' ),
292 'href' => $plugin->get_menu_url( 'import' ),
293 'parent' => self::ROOT_NODE_ID,
294 ]
295 );
296
297 $wp_admin_bar->add_node(
298 [
299 'id' => self::ROOT_NODE_ID . '-settings',
300 'title' => esc_html_x( 'Settings', 'snippets', 'code-snippets' ),
301 'href' => $plugin->get_menu_url( 'settings', are_settings_unified() ? 'network' : 'admin' ),
302 'parent' => self::ROOT_NODE_ID,
303 ]
304 );
305 }
306
307 /**
308 * Add a list of snippets under the active and inactive statuses.
309 *
310 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
311 *
312 * @return void
313 */
314 private function add_snippet_listings( WP_Admin_Bar $wp_admin_bar ): void {
315 $items_per_page = $this->get_snippet_limit();
316 if ( $items_per_page < 1 ) {
317 return;
318 }
319
320 $plugin = code_snippets();
321
322 $snippets = array_filter(
323 get_snippets(),
324 static function ( Snippet $snippet ): bool {
325 return ! $snippet->trashed && ! $snippet->is_condition();
326 }
327 );
328
329 $active_snippets = array_values(
330 array_filter(
331 $snippets,
332 static function ( Snippet $snippet ): bool {
333 return $snippet->active;
334 }
335 )
336 );
337
338 $inactive_snippets = array_values(
339 array_filter(
340 $snippets,
341 static function ( Snippet $snippet ): bool {
342 return ! $snippet->active;
343 }
344 )
345 );
346
347 usort(
348 $active_snippets,
349 static function ( Snippet $a, Snippet $b ): int {
350 return strcasecmp( $a->display_name, $b->display_name );
351 }
352 );
353
354 usort(
355 $inactive_snippets,
356 static function ( Snippet $a, Snippet $b ): int {
357 return strcasecmp( $a->display_name, $b->display_name );
358 }
359 );
360
361 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
362 $active_page = isset( $_GET[ self::ACTIVE_PAGE_QUERY_ARG ] ) ? absint( $_GET[ self::ACTIVE_PAGE_QUERY_ARG ] ) : 1;
363
364 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
365 $inactive_page = isset( $_GET[ self::INACTIVE_PAGE_QUERY_ARG ] ) ? absint( $_GET[ self::INACTIVE_PAGE_QUERY_ARG ] ) : 1;
366
367 $active_total_pages = max( 1, (int) ceil( count( $active_snippets ) / $items_per_page ) );
368 $inactive_total_pages = max( 1, (int) ceil( count( $inactive_snippets ) / $items_per_page ) );
369
370 $active_page = max( 1, min( $active_page, $active_total_pages ) );
371 $inactive_page = max( 1, min( $inactive_page, $inactive_total_pages ) );
372
373 $active_offset = ( $active_page - 1 ) * $items_per_page;
374 $inactive_offset = ( $inactive_page - 1 ) * $items_per_page;
375
376 $active_page_snippets = array_slice( $active_snippets, $active_offset, $items_per_page );
377 $inactive_page_snippets = array_slice( $inactive_snippets, $inactive_offset, $items_per_page );
378
379 $wp_admin_bar->add_node(
380 [
381 'id' => self::ROOT_NODE_ID . '-active-snippets',
382 // translators: %d: number of active snippets.
383 'title' => sprintf( esc_html__( 'Active snippets (%d)', 'code-snippets' ), count( $active_snippets ) ),
384 'href' => esc_url( add_query_arg( 'status', 'active', $plugin->get_menu_url( 'manage' ) ) ),
385 'parent' => self::ROOT_NODE_ID,
386 ]
387 );
388
389 if ( $active_total_pages > 1 ) {
390 $wp_admin_bar->add_node(
391 [
392 'id' => self::ROOT_NODE_ID . '-active-pagination',
393 'title' => $this->get_pagination_controls_html( 'active', $active_page, $active_total_pages, self::ACTIVE_PAGE_QUERY_ARG ),
394 'parent' => self::ROOT_NODE_ID . '-active-snippets',
395 'meta' => [ 'class' => 'code-snippets-pagination-node' ],
396 ]
397 );
398 }
399
400 foreach ( $active_page_snippets as $snippet ) {
401 $wp_admin_bar->add_node(
402 [
403 'id' => self::ROOT_NODE_ID . '-snippet-' . $snippet->id,
404 'title' => esc_html( $this->format_snippet_title( $snippet ) ),
405 'href' => esc_url( add_query_arg( 'id', $snippet->id, $plugin->get_menu_url( 'edit' ) ) ),
406 'parent' => self::ROOT_NODE_ID . '-active-snippets',
407 'meta' => [ 'class' => 'code-snippets-snippet-item' ],
408 ]
409 );
410 }
411
412 $wp_admin_bar->add_node(
413 [
414 'id' => self::ROOT_NODE_ID . '-inactive-snippets',
415 // translators: %d: number of inactive snippets.
416 'title' => sprintf( esc_html__( 'Inactive snippets (%d)', 'code-snippets' ), count( $inactive_snippets ) ),
417 'href' => esc_url( add_query_arg( 'status', 'inactive', $plugin->get_menu_url( 'manage' ) ) ),
418 'parent' => self::ROOT_NODE_ID,
419 ]
420 );
421
422 if ( $inactive_total_pages > 1 ) {
423 $wp_admin_bar->add_node(
424 [
425 'id' => self::ROOT_NODE_ID . '-inactive-pagination',
426 'title' => $this->get_pagination_controls_html( 'inactive', $inactive_page, $inactive_total_pages, self::INACTIVE_PAGE_QUERY_ARG ),
427 'parent' => self::ROOT_NODE_ID . '-inactive-snippets',
428 'meta' => [ 'class' => 'code-snippets-pagination-node' ],
429 ]
430 );
431 }
432
433 foreach ( $inactive_page_snippets as $snippet ) {
434 $wp_admin_bar->add_node(
435 [
436 'id' => self::ROOT_NODE_ID . '-snippet-' . $snippet->id,
437 'title' => esc_html( $this->format_snippet_title( $snippet ) ),
438 'href' => esc_url( add_query_arg( 'id', $snippet->id, $plugin->get_menu_url( 'edit' ) ) ),
439 'parent' => self::ROOT_NODE_ID . '-inactive-snippets',
440 'meta' => [ 'class' => 'code-snippets-snippet-item' ],
441 ]
442 );
443 }
444 }
445
446 /**
447 * Build an admin bar snippet title including type prefix.
448 *
449 * @param Snippet $snippet Snippet object.
450 *
451 * @return string
452 */
453 private function format_snippet_title( Snippet $snippet ): string {
454 return sprintf( '(%s) %s', strtoupper( $snippet->type ), $snippet->display_name );
455 }
456
457 /**
458 * Retrieve the number of snippets to show per page in the admin bar.
459 *
460 * @return int
461 */
462 private function get_snippet_limit(): int {
463 $limit = (int) get_setting( 'general', 'admin_bar_snippet_limit' );
464
465 if ( $limit < 1 ) {
466 $limit = 20;
467 }
468
469 return max( 1, (int) apply_filters( 'code_snippets/admin_bar/snippet_limit', $limit ) );
470 }
471
472 /**
473 * Build pagination controls HTML for a snippet listing submenu.
474 *
475 * @param string $status Snippet status: "active" or "inactive".
476 * @param int $page Current page.
477 * @param int $total_pages Total pages.
478 * @param string $page_query_arg Query arg used for progressive enhancement.
479 *
480 * @return string
481 */
482 private function get_pagination_controls_html( string $status, int $page, int $total_pages, string $page_query_arg ): string {
483 $first_url = remove_query_arg( $page_query_arg );
484 $prev_url = $page > 2 ? add_query_arg( $page_query_arg, $page - 1 ) : remove_query_arg( $page_query_arg );
485 $next_url = add_query_arg( $page_query_arg, $page + 1 );
486 $last_url = add_query_arg( $page_query_arg, $total_pages );
487
488 $disabled_first = $page <= 1 ? 'true' : 'false';
489 $disabled_prev = $page <= 1 ? 'true' : 'false';
490 $disabled_next = $page >= $total_pages ? 'true' : 'false';
491 $disabled_last = $page >= $total_pages ? 'true' : 'false';
492
493 $html = sprintf(
494 '<span class="code-snippets-pagination-controls" data-status="%1$s" data-page="%2$d" data-total-pages="%3$d" data-query-arg="%15$s">' .
495 '<a class="code-snippets-pagination-button" href="%4$s" data-action="first" aria-disabled="%9$s">&laquo;</a>' .
496 '<a class="code-snippets-pagination-button" href="%5$s" data-action="prev" aria-disabled="%10$s">&lsaquo; %6$s</a>' .
497 '<span class="code-snippets-pagination-button code-snippets-pagination-page">%7$s (%2$d/%3$d)</span>' .
498 '<a class="code-snippets-pagination-button" href="%8$s" data-action="next" aria-disabled="%11$s">%12$s &rsaquo;</a>' .
499 '<a class="code-snippets-pagination-button" href="%13$s" data-action="last" aria-disabled="%14$s">&raquo;</a>' .
500 '</span>',
501 esc_attr( $status ),
502 $page,
503 $total_pages,
504 esc_url( $first_url ),
505 esc_url( $prev_url ),
506 esc_html__( 'Back', 'code-snippets' ),
507 esc_html__( 'Page', 'code-snippets' ),
508 esc_url( $next_url ),
509 $disabled_first,
510 $disabled_prev,
511 $disabled_next,
512 esc_html__( 'Next', 'code-snippets' ),
513 esc_url( $last_url ),
514 $disabled_last,
515 esc_attr( $page_query_arg )
516 );
517
518 return wp_kses(
519 $html,
520 [
521 'span' => [
522 'class' => [],
523 'data-status' => [],
524 'data-page' => [],
525 'data-total-pages' => [],
526 'data-query-arg' => [],
527 ],
528 'a' => [
529 'class' => [],
530 'href' => [],
531 'data-action' => [],
532 'aria-disabled' => [],
533 ],
534 ]
535 );
536 }
537 }
538