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