| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 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[] = block_core_breadcrumbs_create_item( __( 'Home' ), 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[] = block_core_breadcrumbs_create_item( block_core_breadcrumbs_get_post_title( $page_for_posts ), block_core_breadcrumbs_is_paged() ); 48 } 49 if ( block_core_breadcrumbs_is_paged() ) { 50 $breadcrumb_items[] = 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[] = block_core_breadcrumbs_create_page_number_item( 'page' ); 58 } 59 } elseif ( is_search() ) { 60 // Handle search results. 61 $is_paged = 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[] = 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[] = 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 = 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 = 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, block_core_breadcrumbs_get_hierarchical_post_type_breadcrumbs( $post_id ) ); 134 } else { 135 $breadcrumb_items = array_merge( $breadcrumb_items, 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 = 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[] = 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 /** 163 * Filters the breadcrumb items array before rendering. 164 * 165 * Allows developers to modify, add, or remove breadcrumb items. 166 * 167 * @since 7.0.0 168 * 169 * @param array[] $breadcrumb_items { 170 * Array of breadcrumb item data. 171 * 172 * @type string $label The breadcrumb text. 173 * @type string $url Optional. The breadcrumb link URL. 174 * @type bool $allow_html Optional. Whether to allow HTML in the label. 175 * When true, the label will be sanitized with wp_kses_post(), 176 * allowing only safe HTML tags. When false or omitted, all HTML 177 * will be escaped with esc_html(). Default false. 178 * } 179 */ 180 $breadcrumb_items = apply_filters( 'block_core_breadcrumbs_items', $breadcrumb_items ); 181 182 if ( empty( $breadcrumb_items ) ) { 183 return ''; 184 } 185 186 $separator_attr = $attributes['separator'] ?? null; 187 $separator = is_string( $separator_attr ) ? addcslashes( $separator_attr, '\\"' ) : ''; 188 $wrapper_attributes = get_block_wrapper_attributes( 189 array( 190 'style' => '--separator: "' . $separator . '";', 191 'aria-label' => __( 'Breadcrumbs' ), 192 ) 193 ); 194 195 $breadcrumb_html = sprintf( 196 '<nav %s><ol>%s</ol></nav>', 197 $wrapper_attributes, 198 implode( 199 '', 200 array_map( 201 static function ( $item ) { 202 $label = ! empty( $item['allow_html'] ) ? wp_kses_post( $item['label'] ) : esc_html( $item['label'] ); 203 if ( ! empty( $item['url'] ) ) { 204 return '<li><a href="' . esc_url( $item['url'] ) . '">' . $label . '</a></li>'; 205 } 206 return '<li><span aria-current="page">' . $label . '</span></li>'; 207 }, 208 $breadcrumb_items 209 ) 210 ) 211 ); 212 213 return $breadcrumb_html; 214 } 215 216 /** 217 * Checks if we're on a paginated view (page 2 or higher). 218 * 219 * @since 7.0.0 220 * 221 * @return bool True if paged > 1, false otherwise. 222 */ 223 function block_core_breadcrumbs_is_paged() { 224 $paged = (int) get_query_var( 'paged' ); 225 return $paged > 1; 226 } 227 228 /** 229 * Creates a "Page X" breadcrumb item for paginated views. 230 * 231 * @since 7.0.0 232 * @param string $query_var Optional. Query variable to get current page number. Default 'paged'. 233 * @return array The "Page X" breadcrumb item data. 234 */ 235 function block_core_breadcrumbs_create_page_number_item( $query_var = 'paged' ) { 236 $paged = (int) get_query_var( $query_var ); 237 238 if ( 'cpage' === $query_var ) { 239 return array( 240 'label' => sprintf( 241 /* translators: %s: comment page number */ 242 __( 'Comments Page %s' ), 243 number_format_i18n( $paged ) 244 ), 245 ); 246 } 247 248 return array( 249 'label' => sprintf( 250 /* translators: %s: page number */ 251 __( 'Page %s' ), 252 number_format_i18n( $paged ) 253 ), 254 ); 255 } 256 257 258 /** 259 * Creates a breadcrumb item that's either a link or current page item. 260 * 261 * When paginated (is_paged is true), creates a link to page 1. 262 * Otherwise, creates a span marked as the current page. 263 * 264 * @since 7.0.0 265 * 266 * @param string $text The text content. 267 * @param bool $is_paged Whether we're on a paginated view. 268 * 269 * @return array The breadcrumb item data. 270 */ 271 function block_core_breadcrumbs_create_item( $text, $is_paged = false ) { 272 $item = array( 'label' => $text ); 273 if ( $is_paged ) { 274 $item['url'] = get_pagenum_link( 1 ); 275 } 276 return $item; 277 } 278 279 /** 280 * Gets a post title with fallback for empty titles. 281 * 282 * @since 7.0.0 283 * 284 * @param int|WP_Post $post_id_or_object The post ID or post object. 285 * 286 * @return string The post title or fallback text. 287 */ 288 function block_core_breadcrumbs_get_post_title( $post_id_or_object ) { 289 $title = get_the_title( $post_id_or_object ); 290 if ( strlen( $title ) === 0 ) { 291 $title = __( '(no title)' ); 292 } 293 return $title; 294 } 295 296 /** 297 * Generates breadcrumb items from hierarchical post type ancestors. 298 * 299 * @since 7.0.0 300 * 301 * @param int $post_id The post ID. 302 * 303 * @return array Array of breadcrumb item data. 304 */ 305 function block_core_breadcrumbs_get_hierarchical_post_type_breadcrumbs( $post_id ) { 306 $breadcrumb_items = array(); 307 $ancestors = get_post_ancestors( $post_id ); 308 $ancestors = array_reverse( $ancestors ); 309 310 foreach ( $ancestors as $ancestor_id ) { 311 $breadcrumb_items[] = array( 312 'label' => block_core_breadcrumbs_get_post_title( $ancestor_id ), 313 'url' => get_permalink( $ancestor_id ), 314 'allow_html' => true, 315 ); 316 } 317 return $breadcrumb_items; 318 } 319 320 /** 321 * Generates breadcrumb items for hierarchical term ancestors. 322 * 323 * For hierarchical taxonomies, retrieves and formats ancestor terms as breadcrumb links. 324 * 325 * @since 7.0.0 326 * 327 * @param int $term_id The term ID. 328 * @param string $taxonomy The taxonomy name. 329 * 330 * @return array Array of breadcrumb item data for ancestors. 331 */ 332 function block_core_breadcrumbs_get_term_ancestors_items( $term_id, $taxonomy ) { 333 $breadcrumb_items = array(); 334 335 // Check if taxonomy is hierarchical and add ancestor term links. 336 if ( is_taxonomy_hierarchical( $taxonomy ) ) { 337 $term_ancestors = get_ancestors( $term_id, $taxonomy, 'taxonomy' ); 338 $term_ancestors = array_reverse( $term_ancestors ); 339 foreach ( $term_ancestors as $ancestor_id ) { 340 $ancestor_term = get_term( $ancestor_id, $taxonomy ); 341 if ( $ancestor_term && ! is_wp_error( $ancestor_term ) ) { 342 $breadcrumb_items[] = array( 343 'label' => $ancestor_term->name, 344 'url' => get_term_link( $ancestor_term ), 345 ); 346 } 347 } 348 } 349 350 return $breadcrumb_items; 351 } 352 353 /** 354 * Generates breadcrumb items for archive pages. 355 * 356 * Handles taxonomy archives, post type archives, date archives, and author archives. 357 * For hierarchical taxonomies, includes ancestor terms in the breadcrumb trail. 358 * 359 * @since 7.0.0 360 * 361 * @return array Array of breadcrumb item data. 362 */ 363 function block_core_breadcrumbs_get_archive_breadcrumbs() { 364 $breadcrumb_items = array(); 365 366 // Date archive (check first since it doesn't have a queried object). 367 if ( is_date() ) { 368 $year = get_query_var( 'year' ); 369 $month = get_query_var( 'monthnum' ); 370 $day = get_query_var( 'day' ); 371 372 // Fallback to 'm' query var for plain permalinks. 373 // Plain permalinks use ?m=YYYYMMDD format instead of separate query vars. 374 if ( ! $year ) { 375 $m = get_query_var( 'm' ); 376 if ( $m ) { 377 $year = substr( $m, 0, 4 ); 378 $month = substr( $m, 4, 2 ); 379 $day = (int) substr( $m, 6, 2 ); 380 } 381 } 382 383 $is_paged = block_core_breadcrumbs_is_paged(); 384 385 if ( $year ) { 386 if ( $month ) { 387 // Year is linked if we have month. 388 $breadcrumb_items[] = array( 389 'label' => $year, 390 'url' => get_year_link( $year ), 391 ); 392 393 if ( $day ) { 394 // Month is linked if we have day. 395 $breadcrumb_items[] = array( 396 'label' => date_i18n( 'F', mktime( 0, 0, 0, $month, 1, $year ) ), 397 'url' => get_month_link( $year, $month ), 398 ); 399 // Add day (current if not paginated, link if paginated). 400 $breadcrumb_items[] = block_core_breadcrumbs_create_item( 401 $day, 402 $is_paged 403 ); 404 } else { 405 // Add month (current if not paginated, link if paginated). 406 $breadcrumb_items[] = block_core_breadcrumbs_create_item( 407 date_i18n( 'F', mktime( 0, 0, 0, $month, 1, $year ) ), 408 $is_paged 409 ); 410 } 411 } else { 412 // Add year (current if not paginated, link if paginated). 413 $breadcrumb_items[] = block_core_breadcrumbs_create_item( 414 $year, 415 $is_paged 416 ); 417 } 418 } 419 420 // Add pagination breadcrumb if on a paged date archive. 421 if ( $is_paged ) { 422 $breadcrumb_items[] = block_core_breadcrumbs_create_page_number_item(); 423 } 424 425 return $breadcrumb_items; 426 } 427 428 // For other archive types, we need a queried object. 429 $queried_object = get_queried_object(); 430 431 if ( ! $queried_object ) { 432 return array(); 433 } 434 435 $is_paged = block_core_breadcrumbs_is_paged(); 436 437 // Taxonomy archive (category, tag, custom taxonomy). 438 if ( $queried_object instanceof WP_Term ) { 439 $term = $queried_object; 440 $taxonomy = $term->taxonomy; 441 442 // Add hierarchical term ancestors if applicable. 443 $breadcrumb_items = array_merge( 444 $breadcrumb_items, 445 block_core_breadcrumbs_get_term_ancestors_items( $term->term_id, $taxonomy ) 446 ); 447 448 // Add current term (current if not paginated, link if paginated). 449 $breadcrumb_items[] = block_core_breadcrumbs_create_item( 450 $term->name, 451 $is_paged 452 ); 453 } elseif ( is_post_type_archive() ) { 454 // Post type archive. 455 $post_type = get_query_var( 'post_type' ); 456 if ( is_array( $post_type ) ) { 457 $post_type = reset( $post_type ); 458 } 459 $post_type_object = get_post_type_object( $post_type ); 460 461 /** This filter is documented in wp-includes/general-template.php */ 462 $title = apply_filters( 'post_type_archive_title', $post_type_object->labels->archives, $post_type ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 463 464 if ( $post_type_object ) { 465 // Add post type (current if not paginated, link if paginated). 466 $breadcrumb_items[] = block_core_breadcrumbs_create_item( 467 $title ? $title : $post_type_object->labels->archives, 468 $is_paged 469 ); 470 } 471 } elseif ( is_author() ) { 472 // Author archive. 473 $author = $queried_object; 474 // Add author (current if not paginated, link if paginated). 475 $breadcrumb_items[] = block_core_breadcrumbs_create_item( 476 $author->display_name, 477 $is_paged 478 ); 479 } 480 481 // Add pagination breadcrumb if on a paged archive. 482 if ( $is_paged ) { 483 $breadcrumb_items[] = block_core_breadcrumbs_create_page_number_item(); 484 } 485 486 return $breadcrumb_items; 487 } 488 489 /** 490 * Generates breadcrumb items from taxonomy terms. 491 * 492 * Finds the first publicly queryable taxonomy with terms assigned to the post 493 * and generates breadcrumb links, including hierarchical term ancestors if applicable. 494 * 495 * @since 7.0.0 496 * 497 * @param int $post_id The post ID. 498 * @param string $post_type The post type name. 499 * 500 * @return array Array of breadcrumb item data. 501 */ 502 function block_core_breadcrumbs_get_terms_breadcrumbs( $post_id, $post_type ) { 503 $breadcrumb_items = array(); 504 505 // Get public taxonomies for this post type. 506 $taxonomies = wp_filter_object_list( 507 get_object_taxonomies( $post_type, 'objects' ), 508 array( 509 'publicly_queryable' => true, 510 'show_in_rest' => true, 511 ) 512 ); 513 514 if ( empty( $taxonomies ) ) { 515 return $breadcrumb_items; 516 } 517 518 /** 519 * Filters breadcrumb settings (taxonomy and term selection) for a post or post type. 520 * 521 * Allows developers to specify which taxonomy and term should be used in the 522 * breadcrumb trail when a post type has multiple taxonomies or when a post is 523 * assigned to multiple terms within a taxonomy. 524 * 525 * @since 7.0.0 526 * 527 * @param array $settings { 528 * Array of breadcrumb settings. Default empty array. 529 * 530 * @type string $taxonomy Optional. Taxonomy slug to use for breadcrumbs. 531 * The taxonomy must be registered for the post type and have 532 * terms assigned to the post. If not found or has no terms, 533 * fall back to the first available taxonomy with terms. 534 * @type string $term Optional. Term slug to use when the post has multiple terms 535 * in the selected taxonomy. If the term is not found or not 536 * assigned to the post, fall back to the first term. If the 537 * post has only one term, that term is used regardless. 538 * } 539 * @param string $post_type The post type slug. 540 * @param int $post_id The post ID. 541 */ 542 $settings = apply_filters( 'block_core_breadcrumbs_post_type_settings', array(), $post_type, $post_id ); 543 544 $taxonomy_name = null; 545 $terms = array(); 546 547 // Try preferred taxonomy first if specified. 548 if ( ! empty( $settings['taxonomy'] ) ) { 549 foreach ( $taxonomies as $taxonomy ) { 550 if ( $taxonomy->name === $settings['taxonomy'] ) { 551 $post_terms = get_the_terms( $post_id, $taxonomy->name ); 552 if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) { 553 $taxonomy_name = $taxonomy->name; 554 $terms = $post_terms; 555 } 556 break; 557 } 558 } 559 } 560 561 // If no preferred taxonomy or it didn't have terms, find the first taxonomy with terms. 562 if ( empty( $terms ) ) { 563 foreach ( $taxonomies as $taxonomy ) { 564 $post_terms = get_the_terms( $post_id, $taxonomy->name ); 565 if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) { 566 $taxonomy_name = $taxonomy->name; 567 $terms = $post_terms; 568 break; 569 } 570 } 571 } 572 573 if ( ! empty( $terms ) ) { 574 // Select which term to use. 575 $term = reset( $terms ); 576 577 // Try preferred term if specified and post has multiple terms. 578 if ( ! empty( $settings['term'] ) && count( $terms ) > 1 ) { 579 foreach ( $terms as $candidate_term ) { 580 if ( $candidate_term->slug === $settings['term'] ) { 581 $term = $candidate_term; 582 break; 583 } 584 } 585 } 586 587 // Add hierarchical term ancestors if applicable. 588 $breadcrumb_items = array_merge( 589 $breadcrumb_items, 590 block_core_breadcrumbs_get_term_ancestors_items( $term->term_id, $taxonomy_name ) 591 ); 592 $breadcrumb_items[] = array( 593 'label' => $term->name, 594 'url' => get_term_link( $term ), 595 ); 596 } 597 return $breadcrumb_items; 598 } 599 600 /** 601 * Registers the `core/breadcrumbs` block on the server. 602 * 603 * @since 7.0.0 604 */ 605 function register_block_core_breadcrumbs() { 606 register_block_type_from_metadata( 607 __DIR__ . '/breadcrumbs', 608 array( 609 'render_callback' => 'render_block_core_breadcrumbs', 610 ) 611 ); 612 } 613 add_action( 'init', 'register_block_core_breadcrumbs' );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Sep 23 08:20:35 2026 | Cross-referenced by PHPXref |