PluginProbe
Gutenberg / 22.3.0
Gutenberg v22.3.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / build / scripts / block-library / breadcrumbs.php

breadcrumbs.php in Gutenberg 22.3.0, at build/scripts/block-library/breadcrumbs.php

585 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side rendering of the `core/breadcrumbs` block.
4 *
5 * @package WordPress
6 */
7
8 /**
9 * Renders the `core/breadcrumbs` block on the server.
10 *
11 * @since 7.0.0
12 *
13 * @param array $attributes Block attributes.
14 * @param string $content Block default content.
15 * @param WP_Block $block Block instance.
16 *
17 * @return string Returns the post breadcrumb for hierarchical post types.
18 */
19 function gutenberg_render_block_core_breadcrumbs( $attributes, $content, $block ) {
20 $is_front_page = is_front_page();
21
22 if ( ! $attributes['showOnHomePage'] && $is_front_page ) {
23 return '';
24 }
25
26 $is_home = is_home();
27 $page_for_posts = get_option( 'page_for_posts' );
28 $breadcrumb_items = array();
29
30 if ( $attributes['showHomeItem'] ) {
31 // We make `home` a link if not on front page, or if front page
32 // is set to a custom page and is paged.
33 if ( ! $is_front_page || ( 'page' === get_option( 'show_on_front' ) && (int) get_query_var( 'page' ) > 1 ) ) {
34 $breadcrumb_items[] = array(
35 'label' => __( 'Home' ),
36 'url' => home_url( '/' ),
37 );
38 } else {
39 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item( __( 'Home' ), gutenberg_block_core_breadcrumbs_is_paged() );
40 }
41 }
42
43 // Handle home.
44 if ( $is_home ) {
45 // These checks are explicitly nested in order not to execute the `else` branch.
46 if ( $page_for_posts ) {
47 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item( gutenberg_block_core_breadcrumbs_get_post_title( $page_for_posts ), gutenberg_block_core_breadcrumbs_is_paged() );
48 }
49 if ( gutenberg_block_core_breadcrumbs_is_paged() ) {
50 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
51 }
52 } elseif ( $is_front_page ) {
53 // Handle front page.
54 // This check is explicitly nested in order not to execute the `else` branch.
55 // If front page is set to custom page and is paged, add the page number.
56 if ( (int) get_query_var( 'page' ) > 1 ) {
57 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item( 'page' );
58 }
59 } elseif ( is_search() ) {
60 // Handle search results.
61 $is_paged = gutenberg_block_core_breadcrumbs_is_paged();
62 /* translators: %s: search query */
63 $text = sprintf( __( 'Search results for: "%s"' ), wp_trim_words( get_search_query(), 10 ) );
64 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item( $text, $is_paged );
65 // Add the "Page X" as the current page if paginated.
66 if ( $is_paged ) {
67 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
68 }
69 } elseif ( is_404() ) {
70 // Handle 404 pages.
71 $breadcrumb_items[] = array(
72 'label' => __( 'Page not found' ),
73 );
74 } elseif ( is_archive() ) {
75 // Handle archive pages (taxonomy, post type, date, author archives).
76 $archive_breadcrumbs = gutenberg_block_core_breadcrumbs_get_archive_breadcrumbs();
77 if ( ! empty( $archive_breadcrumbs ) ) {
78 $breadcrumb_items = array_merge( $breadcrumb_items, $archive_breadcrumbs );
79 }
80 } else {
81 // Handle single post/page breadcrumbs.
82 if ( ! isset( $block->context['postId'] ) || ! isset( $block->context['postType'] ) ) {
83 return '';
84 }
85
86 $post_id = $block->context['postId'];
87 $post_type = $block->context['postType'];
88
89 $post = get_post( $post_id );
90 if ( ! $post ) {
91 return '';
92 }
93
94 // For non-hierarchical post types with parents (e.g., attachments), build trail for the parent.
95 $post_parent = $post->post_parent;
96 $parent_post = null;
97 if ( ! is_post_type_hierarchical( $post_type ) && $post_parent ) {
98 $parent_post = get_post( $post_parent );
99 if ( $parent_post ) {
100 $post_id = $parent_post->ID;
101 $post_type = $parent_post->post_type;
102 $post_parent = $parent_post->post_parent;
103 }
104 }
105
106 // Determine breadcrumb type.
107 // Some non-hierarchical post types (e.g., attachments) can have parents.
108 // Use hierarchical breadcrumbs if a parent exists, otherwise use taxonomy breadcrumbs.
109 $show_terms = false;
110 if ( ! is_post_type_hierarchical( $post_type ) && ! $post_parent ) {
111 $show_terms = true;
112 } elseif ( empty( get_object_taxonomies( $post_type, 'objects' ) ) ) {
113 $show_terms = false;
114 } else {
115 $show_terms = $attributes['prefersTaxonomy'];
116 }
117
118 // Add post type archive link if applicable.
119 $post_type_object = get_post_type_object( $post_type );
120 $archive_link = get_post_type_archive_link( $post_type );
121 if ( $archive_link && untrailingslashit( home_url() ) !== untrailingslashit( $archive_link ) ) {
122 $label = $post_type_object->labels->archives;
123 if ( 'post' === $post_type && $page_for_posts ) {
124 $label = gutenberg_block_core_breadcrumbs_get_post_title( $page_for_posts );
125 }
126 $breadcrumb_items[] = array(
127 'label' => $label,
128 'url' => $archive_link,
129 );
130 }
131 // Build breadcrumb trail based on hierarchical structure or taxonomy terms.
132 if ( ! $show_terms ) {
133 $breadcrumb_items = array_merge( $breadcrumb_items, gutenberg_block_core_breadcrumbs_get_hierarchical_post_type_breadcrumbs( $post_id ) );
134 } else {
135 $breadcrumb_items = array_merge( $breadcrumb_items, gutenberg_block_core_breadcrumbs_get_terms_breadcrumbs( $post_id, $post_type ) );
136 }
137
138 // Add post title: linked when viewing a paginated page, plain text otherwise.
139 $is_paged = (int) get_query_var( 'page' ) > 1 || (int) get_query_var( 'cpage' ) > 1;
140 $title = gutenberg_block_core_breadcrumbs_get_post_title( $post );
141
142 if ( $is_paged ) {
143 $breadcrumb_items[] = array(
144 'label' => $title,
145 'url' => get_permalink( $post ),
146 'allow_html' => true,
147 );
148 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item( (int) get_query_var( 'cpage' ) > 1 ? 'cpage' : 'page' );
149 } else {
150 $breadcrumb_items[] = array(
151 'label' => $title,
152 'allow_html' => true,
153 );
154 }
155 }
156
157 // Remove current item if disabled.
158 if ( ! $attributes['showCurrentItem'] && ! empty( $breadcrumb_items ) ) {
159 array_pop( $breadcrumb_items );
160 }
161
162 if ( empty( $breadcrumb_items ) ) {
163 return '';
164 }
165
166 $wrapper_attributes = get_block_wrapper_attributes(
167 array(
168 'style' => '--separator: "' . addcslashes( $attributes['separator'], '\\"' ) . '";',
169 'aria-label' => __( 'Breadcrumbs' ),
170 )
171 );
172
173 $breadcrumb_html = sprintf(
174 '<nav %s><ol>%s</ol></nav>',
175 $wrapper_attributes,
176 implode(
177 '',
178 array_map(
179 static function ( $item ) {
180 $label = ! empty( $item['allow_html'] ) ? wp_kses_post( $item['label'] ) : esc_html( $item['label'] );
181 if ( ! empty( $item['url'] ) ) {
182 return '<li><a href="' . esc_url( $item['url'] ) . '">' . $label . '</a></li>';
183 }
184 return '<li><span aria-current="page">' . $label . '</span></li>';
185 },
186 $breadcrumb_items
187 )
188 )
189 );
190
191 return $breadcrumb_html;
192 }
193
194 /**
195 * Checks if we're on a paginated view (page 2 or higher).
196 *
197 * @since 7.0.0
198 *
199 * @return bool True if paged > 1, false otherwise.
200 */
201 function gutenberg_block_core_breadcrumbs_is_paged() {
202 $paged = (int) get_query_var( 'paged' );
203 return $paged > 1;
204 }
205
206 /**
207 * Creates a "Page X" breadcrumb item for paginated views.
208 *
209 * @since 7.0.0
210 * @param string $query_var Optional. Query variable to get current page number. Default 'paged'.
211 * @return array The "Page X" breadcrumb item data.
212 */
213 function gutenberg_block_core_breadcrumbs_create_page_number_item( $query_var = 'paged' ) {
214 $paged = (int) get_query_var( $query_var );
215
216 if ( 'cpage' === $query_var ) {
217 return array(
218 'label' => sprintf(
219 /* translators: %s: comment page number */
220 __( 'Comments Page %s' ),
221 number_format_i18n( $paged )
222 ),
223 );
224 }
225
226 return array(
227 'label' => sprintf(
228 /* translators: %s: page number */
229 __( 'Page %s' ),
230 number_format_i18n( $paged )
231 ),
232 );
233 }
234
235
236 /**
237 * Creates a breadcrumb item that's either a link or current page item.
238 *
239 * When paginated (is_paged is true), creates a link to page 1.
240 * Otherwise, creates a span marked as the current page.
241 *
242 * @since 7.0.0
243 *
244 * @param string $text The text content.
245 * @param bool $is_paged Whether we're on a paginated view.
246 *
247 * @return array The breadcrumb item data.
248 */
249 function gutenberg_block_core_breadcrumbs_create_item( $text, $is_paged = false ) {
250 $item = array( 'label' => $text );
251 if ( $is_paged ) {
252 $item['url'] = get_pagenum_link( 1 );
253 }
254 return $item;
255 }
256
257 /**
258 * Gets a post title with fallback for empty titles.
259 *
260 * @since 7.0.0
261 *
262 * @param int|WP_Post $post_id_or_object The post ID or post object.
263 *
264 * @return string The post title or fallback text.
265 */
266 function gutenberg_block_core_breadcrumbs_get_post_title( $post_id_or_object ) {
267 $title = get_the_title( $post_id_or_object );
268 if ( strlen( $title ) === 0 ) {
269 $title = __( '(no title)' );
270 }
271 return $title;
272 }
273
274 /**
275 * Generates breadcrumb items from hierarchical post type ancestors.
276 *
277 * @since 7.0.0
278 *
279 * @param int $post_id The post ID.
280 *
281 * @return array Array of breadcrumb item data.
282 */
283 function gutenberg_block_core_breadcrumbs_get_hierarchical_post_type_breadcrumbs( $post_id ) {
284 $breadcrumb_items = array();
285 $ancestors = get_post_ancestors( $post_id );
286 $ancestors = array_reverse( $ancestors );
287
288 foreach ( $ancestors as $ancestor_id ) {
289 $breadcrumb_items[] = array(
290 'label' => gutenberg_block_core_breadcrumbs_get_post_title( $ancestor_id ),
291 'url' => get_permalink( $ancestor_id ),
292 'allow_html' => true,
293 );
294 }
295 return $breadcrumb_items;
296 }
297
298 /**
299 * Generates breadcrumb items for hierarchical term ancestors.
300 *
301 * For hierarchical taxonomies, retrieves and formats ancestor terms as breadcrumb links.
302 *
303 * @since 7.0.0
304 *
305 * @param int $term_id The term ID.
306 * @param string $taxonomy The taxonomy name.
307 *
308 * @return array Array of breadcrumb item data for ancestors.
309 */
310 function gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term_id, $taxonomy ) {
311 $breadcrumb_items = array();
312
313 // Check if taxonomy is hierarchical and add ancestor term links.
314 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
315 $term_ancestors = get_ancestors( $term_id, $taxonomy, 'taxonomy' );
316 $term_ancestors = array_reverse( $term_ancestors );
317 foreach ( $term_ancestors as $ancestor_id ) {
318 $ancestor_term = get_term( $ancestor_id, $taxonomy );
319 if ( $ancestor_term && ! is_wp_error( $ancestor_term ) ) {
320 $breadcrumb_items[] = array(
321 'label' => $ancestor_term->name,
322 'url' => get_term_link( $ancestor_term ),
323 );
324 }
325 }
326 }
327
328 return $breadcrumb_items;
329 }
330
331 /**
332 * Generates breadcrumb items for archive pages.
333 *
334 * Handles taxonomy archives, post type archives, date archives, and author archives.
335 * For hierarchical taxonomies, includes ancestor terms in the breadcrumb trail.
336 *
337 * @since 7.0.0
338 *
339 * @return array Array of breadcrumb item data.
340 */
341 function gutenberg_block_core_breadcrumbs_get_archive_breadcrumbs() {
342 $breadcrumb_items = array();
343
344 // Date archive (check first since it doesn't have a queried object).
345 if ( is_date() ) {
346 $year = get_query_var( 'year' );
347 $month = get_query_var( 'monthnum' );
348 $day = get_query_var( 'day' );
349
350 // Fallback to 'm' query var for plain permalinks.
351 // Plain permalinks use ?m=YYYYMMDD format instead of separate query vars.
352 if ( ! $year ) {
353 $m = get_query_var( 'm' );
354 if ( $m ) {
355 $year = substr( $m, 0, 4 );
356 $month = substr( $m, 4, 2 );
357 $day = (int) substr( $m, 6, 2 );
358 }
359 }
360
361 $is_paged = gutenberg_block_core_breadcrumbs_is_paged();
362
363 if ( $year ) {
364 if ( $month ) {
365 // Year is linked if we have month.
366 $breadcrumb_items[] = array(
367 'label' => $year,
368 'url' => get_year_link( $year ),
369 );
370
371 if ( $day ) {
372 // Month is linked if we have day.
373 $breadcrumb_items[] = array(
374 'label' => date_i18n( 'F', mktime( 0, 0, 0, $month, 1, $year ) ),
375 'url' => get_month_link( $year, $month ),
376 );
377 // Add day (current if not paginated, link if paginated).
378 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
379 $day,
380 $is_paged
381 );
382 } else {
383 // Add month (current if not paginated, link if paginated).
384 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
385 date_i18n( 'F', mktime( 0, 0, 0, $month, 1, $year ) ),
386 $is_paged
387 );
388 }
389 } else {
390 // Add year (current if not paginated, link if paginated).
391 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
392 $year,
393 $is_paged
394 );
395 }
396 }
397
398 // Add pagination breadcrumb if on a paged date archive.
399 if ( $is_paged ) {
400 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
401 }
402
403 return $breadcrumb_items;
404 }
405
406 // For other archive types, we need a queried object.
407 $queried_object = get_queried_object();
408
409 if ( ! $queried_object ) {
410 return array();
411 }
412
413 $is_paged = gutenberg_block_core_breadcrumbs_is_paged();
414
415 // Taxonomy archive (category, tag, custom taxonomy).
416 if ( $queried_object instanceof WP_Term ) {
417 $term = $queried_object;
418 $taxonomy = $term->taxonomy;
419
420 // Add hierarchical term ancestors if applicable.
421 $breadcrumb_items = array_merge(
422 $breadcrumb_items,
423 gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term->term_id, $taxonomy )
424 );
425
426 // Add current term (current if not paginated, link if paginated).
427 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
428 $term->name,
429 $is_paged
430 );
431 } elseif ( is_post_type_archive() ) {
432 // Post type archive.
433 $post_type = get_query_var( 'post_type' );
434 if ( is_array( $post_type ) ) {
435 $post_type = reset( $post_type );
436 }
437 $post_type_object = get_post_type_object( $post_type );
438 if ( $post_type_object ) {
439 // Add post type (current if not paginated, link if paginated).
440 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
441 $post_type_object->labels->archives,
442 $is_paged
443 );
444 }
445 } elseif ( is_author() ) {
446 // Author archive.
447 $author = $queried_object;
448 // Add author (current if not paginated, link if paginated).
449 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
450 $author->display_name,
451 $is_paged
452 );
453 }
454
455 // Add pagination breadcrumb if on a paged archive.
456 if ( $is_paged ) {
457 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
458 }
459
460 return $breadcrumb_items;
461 }
462
463 /**
464 * Generates breadcrumb items from taxonomy terms.
465 *
466 * Finds the first publicly queryable taxonomy with terms assigned to the post
467 * and generates breadcrumb links, including hierarchical term ancestors if applicable.
468 *
469 * @since 7.0.0
470 *
471 * @param int $post_id The post ID.
472 * @param string $post_type The post type name.
473 *
474 * @return array Array of breadcrumb item data.
475 */
476 function gutenberg_block_core_breadcrumbs_get_terms_breadcrumbs( $post_id, $post_type ) {
477 $breadcrumb_items = array();
478
479 // Get public taxonomies for this post type.
480 $taxonomies = wp_filter_object_list(
481 get_object_taxonomies( $post_type, 'objects' ),
482 array(
483 'publicly_queryable' => true,
484 'show_in_rest' => true,
485 )
486 );
487
488 if ( empty( $taxonomies ) ) {
489 return $breadcrumb_items;
490 }
491
492 /**
493 * Filters breadcrumb settings on a per-post-type basis.
494 *
495 * Allow developers to customize breadcrumb behavior for specific post types.
496 *
497 * @since 7.0.0
498 *
499 * @param array $settings {
500 * Array of breadcrumb settings. Default empty array.
501 *
502 * @type string $taxonomy Optional. Taxonomy slug to use for breadcrumbs.
503 * The taxonomy must be registered for the post type and have
504 * terms assigned to the post. If not found or has no terms,
505 * fall back to the first available taxonomy with terms.
506 * @type string $term Optional. Term slug to use when the post has multiple terms
507 * in the selected taxonomy. If the term is not found or not
508 * assigned to the post, fall back to the first term. If the
509 * post has only one term, that term is used regardless.
510 * }
511 * @param string $post_type The post type slug.
512 */
513 $settings = apply_filters( 'block_core_breadcrumbs_post_type_settings', array(), $post_type );
514
515 $taxonomy_name = null;
516 $terms = array();
517
518 // Try preferred taxonomy first if specified.
519 if ( ! empty( $settings['taxonomy'] ) ) {
520 foreach ( $taxonomies as $taxonomy ) {
521 if ( $taxonomy->name === $settings['taxonomy'] ) {
522 $post_terms = get_the_terms( $post_id, $taxonomy->name );
523 if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) {
524 $taxonomy_name = $taxonomy->name;
525 $terms = $post_terms;
526 }
527 break;
528 }
529 }
530 }
531
532 // If no preferred taxonomy or it didn't have terms, find the first taxonomy with terms.
533 if ( empty( $terms ) ) {
534 foreach ( $taxonomies as $taxonomy ) {
535 $post_terms = get_the_terms( $post_id, $taxonomy->name );
536 if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) {
537 $taxonomy_name = $taxonomy->name;
538 $terms = $post_terms;
539 break;
540 }
541 }
542 }
543
544 if ( ! empty( $terms ) ) {
545 // Select which term to use.
546 $term = reset( $terms );
547
548 // Try preferred term if specified and post has multiple terms.
549 if ( ! empty( $settings['term'] ) && count( $terms ) > 1 ) {
550 foreach ( $terms as $candidate_term ) {
551 if ( $candidate_term->slug === $settings['term'] ) {
552 $term = $candidate_term;
553 break;
554 }
555 }
556 }
557
558 // Add hierarchical term ancestors if applicable.
559 $breadcrumb_items = array_merge(
560 $breadcrumb_items,
561 gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term->term_id, $taxonomy_name )
562 );
563 $breadcrumb_items[] = array(
564 'label' => $term->name,
565 'url' => get_term_link( $term ),
566 );
567 }
568 return $breadcrumb_items;
569 }
570
571 /**
572 * Registers the `core/breadcrumbs` block on the server.
573 *
574 * @since 7.0.0
575 */
576 function gutenberg_register_block_core_breadcrumbs() {
577 register_block_type_from_metadata(
578 __DIR__ . '/breadcrumbs',
579 array(
580 'render_callback' => 'gutenberg_render_block_core_breadcrumbs',
581 )
582 );
583 }
584 add_action( 'init', 'gutenberg_register_block_core_breadcrumbs', 20 );
585