=> 'user-actions',
'id' => 'edit-profile',
'title' => __( 'Edit Profile' ),
'href' => $profile_url,
)
);
}
$wp_admin_bar->add_node(
array(
'parent' => 'user-actions',
'id' => 'logout',
'title' => __( 'Log Out' ),
'href' => wp_logout_url(),
)
);
}
/**
* Adds the "Site Name" menu.
*
* @since 3.3.0
*
* @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
*/
function wp_admin_bar_site_menu( $wp_admin_bar ) {
// Don't show for logged out users.
if ( ! is_user_logged_in() ) {
return;
}
// Show only when the user is a member of this site, or they're a super admin.
if ( ! is_user_member_of_blog() && ! current_user_can( 'manage_network' ) ) {
return;
}
$blogname = get_bloginfo( 'name' );
if ( ! $blogname ) {
$blogname = preg_replace( '#^(https?://)?(www.)?#', '', get_home_url() );
}
if ( is_network_admin() ) {
/* translators: %s: Site title. */
$blogname = sprintf( __( 'Network Admin: %s' ), esc_html( get_network()->site_name ) );
} elseif ( is_user_admin() ) {
/* translators: %s: Site title. */
$blogname = sprintf( __( 'User Dashboard: %s' ), esc_html( get_network()->site_name ) );
}
$title = wp_html_excerpt( $blogname, 40, '…' );
$wp_admin_bar->add_node(
array(
'id' => 'site-name',
'title' => $title,
'href' => ( is_admin() || ! current_user_can( 'read' ) ) ? home_url( '/' ) : admin_url(),
)
);
// Create submenu items.
if ( is_admin() ) {
// Add an option to visit the site.
$wp_admin_bar->add_node(
array(
'parent' => 'site-name',
'id' => 'view-site',
'title' => __( 'Visit Site' ),
'href' => home_url( '/' ),
)
);
if ( is_blog_admin() && is_multisite() && current_user_can( 'manage_sites' ) ) {
$wp_admin_bar->add_node(
array(
'parent' => 'site-name',
'id' => 'edit-site',
'title' => __( 'Edit Site' ),
'href' => network_admin_url( 'site-info.php?id=' . get_current_blog_id() ),
)
);
}
} elseif ( current_user_can( 'read' ) ) {
// We're on the front end, link to the Dashboard.
$wp_admin_bar->add_node(
array(
'parent' => 'site-name',
'id' => 'dashboard',
'title' => __( 'Dashboard' ),
'href' => admin_url(),
)
);
// Add the appearance submenu items.
wp_admin_bar_appearance_menu( $wp_admin_bar );
}
}
/**
* Adds the "Edit site" link to the Toolbar.
*
* @since 5.9.0
*
* @global string $_wp_current_template_id
* @since 6.3.0 Added `$_wp_current_template_id` global for editing of current template directly from the admin bar.
*
* @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
*/
function wp_admin_bar_edit_site_menu( $wp_admin_bar ) {
global $_wp_current_template_id;
// Don't show if a block theme is not activated.
if ( ! wp_is_block_theme() ) {
return;
}
// Don't show for users who can't edit theme options or when in the admin.
if ( ! current_user_can( 'edit_theme_options' ) || is_admin() ) {
return;
}
$wp_admin_bar->add_node(
array(
'id' => 'site-editor',
'title' => __( 'Edit site' ),
'href' => add_query_arg(
array(
'postType' => 'wp_template',
'postId' => $_wp_current_template_id,
),
admin_url( 'site-editor.php' )
),
)
);
}
/**
* Adds the "Customize" link to the Toolbar.
*
* @since 4.3.0
*
* @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
* @global WP_Customize_Manager $wp_customize
*/
function wp_admin_bar_customize_menu( $wp_admin_bar ) {
global $wp_customize;
// Don't show if a block theme is activated and no plugins use the customizer.
if ( wp_is_block_theme() && ! has_action( 'customize_register' ) ) {
return;
}
// Don't show for users who can't access the customizer or when in the admin.
if ( ! current_user_can( 'customize' ) || is_admin() ) {
return;
}
// Don't show if the user cannot edit a given customize_changeset post currently being previewed.
if ( is_customize_preview() && $wp_customize->changeset_post_id()
&& ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $wp_customize->changeset_post_id() )
) {
return;
}
$current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
if ( is_customize_preview() && $wp_customize->changeset_uuid() ) {
$current_url = remove_query_arg( 'customize_changeset_uuid', $current_url );
}
$customize_url = add_query_arg( 'url', urlencode( $current_url ), wp_customize_url() );
if ( is_customize_preview() ) {
$customize_url = add_query_arg( array( 'changeset_uuid' => $wp_customize->changeset_uuid() ), $customize_url );
}
$wp_admin_bar->add_node(
array(
'id' => 'customize',
'title' => __( 'Customize' ),
'href' => $customize_url,
'meta' => array(
'class' => 'hide-if-no-customize',
),
)
);
add_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' );
}
/**
* Adds the "My Sites/[Site Name]" menu and all submenus.
*
* @since 3.1.0
*
* @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
*/
function wp_admin_bar_my_sites_menu( $wp_admin_bar ) {
// Don't show for logged out users or single site mode.
if ( ! is_user_logged_in() || ! is_multisite() ) {
return;
}
// Show only when the user has at least one site, or they're a super admin.
if ( count( $wp_admin_bar->user->blogs ) < 1 && ! current_user_can( 'manage_network' ) ) {
return;
}
if ( $wp_admin_bar->user->active_blog ) {
$my_sites_url = get_admin_url( $wp_admin_bar->user->active_blog->blog_id, 'my-sites.php' );
} else {
$my_sites_url = admin_url( 'my-sites.php' );
}
$wp_admin_bar->add_node(
array(
'id' => 'my-sites',
'title' => __( 'My Sites' ),
'href' => $my_sites_url,
)
);
if ( current_user_can( 'manage_network' ) ) {
$wp_admin_bar->add_group(
array(
'parent' => 'my-sites',
'id' => 'my-sites-super-admin',
)
);
$wp_admin_bar->add_node(
array(
'parent' => 'my-sites-super-admin',
'id' => 'network-admin',
'title' => __( 'Network Admin' ),
'href' => network_admin_url(),
)
);
$wp_admin_bar->add_node(
array(
'parent' => 'network-admin',
'id' => 'network-admin-d',
'title' => __( 'Dashboard' ),
'href' => network_admin_url(),
)
);
if ( current_user_can( 'manage_sites' ) ) {
$wp_admin_bar->add_node(
array(
'parent' => 'network-admin',
'id' => 'network-admin-s',
'title' => __( 'Sites' ),
'href' => network_admin_url( 'sites.php' ),
)
);
}
if ( current_user_can( 'manage_network_users' ) ) {
$wp_admin_bar->add_node(
array(
'parent' => 'network-admin',
'id' => 'network-admin-u',
'title' => __( 'Users' ),
'href' => network_admin_url( 'users.php' ),
)
);
}
if ( current_user_can( 'manage_network_themes' ) ) {
$wp_admin_bar->add_node(
array(
'parent' => 'network-admin',
'id' => 'network-admin-t',
'title' => __( 'Themes' ),
'href' => network_admin_url( 'themes.php' ),
)
);
}
if ( current_user_can( 'manage_network_plugins' ) ) {
$wp_admin_bar->add_node(
array(
'parent' => 'network-admin',
'id' => 'network-admin-p',
'title' => __( 'Plugins' ),
'href' => network_admin_url( 'plugins.php' ),
)
);
}
if ( current_user_can( 'manage_network_options' ) ) {
$wp_admin_bar->add_node(
array(
'parent' => 'network-admin',
'id' => 'network-admin-o',
'title' => __( 'Settings' ),
'href' => network_admin_url( 'settings.php' ),
)
);
}
}
// Add site links.
$wp_admin_bar->add_group(
array(
'parent' => 'my-sites',
'id' => 'my-sites-list',
'meta' => array(
'class' => current_user_can( 'manage_network' ) ? 'ab-sub-secondary' : '',
),
)
);
/**
* Filters whether to show the site icons in toolbar.
*
* Returning false to this hook is the recommended way to hide site icons in the toolbar.
* A truthy return may have negative performance impact on large multisites.
*
* @since 6.0.0
*
* @param bool $show_site_icons Whether site icons should be shown in the toolbar. Default true.
*/
$show_site_icons = apply_filters( 'wp_admin_bar_show_site_icons', true );
foreach ( (array) $wp_admin_bar->user->blogs as $blog ) {
switch_to_blog( $blog->userblog_id );
if ( true === $show_site_icons && has_site_icon() ) {
$blavatar = sprintf(
'',
esc_url( get_site_icon_url( 16 ) ),
esc_url( get_site_icon_url( 32 ) ),
( wp_lazy_loading_enabled( 'img', 'site_icon_in_toolbar' ) ? ' loading="lazy"' : '' )
);
} else {
$blavatar = '
$typography_key
",
'block.json
',
"supports.$typography_key
",
"supports.typography.$typography_key
"
),
'5.8.0'
);
_wp_array_set( $metadata['supports'], array( 'typography', $typography_key ), $support_for_key );
unset( $metadata['supports'][ $typography_key ] );
}
}
return $metadata;
}
/**
* Helper function that constructs a WP_Query args array from
* a `Query` block properties.
*
* It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks.
*
* @since 5.8.0
* @since 6.1.0 Added `query_loop_block_query_vars` filter and `parents` support in query.
*
* @param WP_Block $block Block instance.
* @param int $page Current query's page.
*
* @return array Returns the constructed WP_Query arguments.
*/
function build_query_vars_from_query_block( $block, $page ) {
$query = array(
'post_type' => 'post',
'order' => 'DESC',
'orderby' => 'date',
'post__not_in' => array(),
);
if ( isset( $block->context['query'] ) ) {
if ( ! empty( $block->context['query']['postType'] ) ) {
$post_type_param = $block->context['query']['postType'];
if ( is_post_type_viewable( $post_type_param ) ) {
$query['post_type'] = $post_type_param;
}
}
if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) {
$sticky = get_option( 'sticky_posts' );
if ( 'only' === $block->context['query']['sticky'] ) {
/*
* Passing an empty array to post__in will return have_posts() as true (and all posts will be returned).
* Logic should be used before hand to determine if WP_Query should be used in the event that the array
* being passed to post__in is empty.
*
* @see https://core.trac.wordpress.org/ticket/28099
*/
$query['post__in'] = ! empty( $sticky ) ? $sticky : array( 0 );
$query['ignore_sticky_posts'] = 1;
} else {
$query['post__not_in'] = array_merge( $query['post__not_in'], $sticky );
}
}
if ( ! empty( $block->context['query']['exclude'] ) ) {
$excluded_post_ids = array_map( 'intval', $block->context['query']['exclude'] );
$excluded_post_ids = array_filter( $excluded_post_ids );
$query['post__not_in'] = array_merge( $query['post__not_in'], $excluded_post_ids );
}
if (
isset( $block->context['query']['perPage'] ) &&
is_numeric( $block->context['query']['perPage'] )
) {
$per_page = absint( $block->context['query']['perPage'] );
$offset = 0;
if (
isset( $block->context['query']['offset'] ) &&
is_numeric( $block->context['query']['offset'] )
) {
$offset = absint( $block->context['query']['offset'] );
}
$query['offset'] = ( $per_page * ( $page - 1 ) ) + $offset;
$query['posts_per_page'] = $per_page;
}
// Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility.
if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) {
$tax_query = array();
if ( ! empty( $block->context['query']['categoryIds'] ) ) {
$tax_query[] = array(
'taxonomy' => 'category',
'terms' => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ),
'include_children' => false,
);
}
if ( ! empty( $block->context['query']['tagIds'] ) ) {
$tax_query[] = array(
'taxonomy' => 'post_tag',
'terms' => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ),
'include_children' => false,
);
}
$query['tax_query'] = $tax_query;
}
if ( ! empty( $block->context['query']['taxQuery'] ) ) {
$query['tax_query'] = array();
foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) {
if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) {
$query['tax_query'][] = array(
'taxonomy' => $taxonomy,
'terms' => array_filter( array_map( 'intval', $terms ) ),
'include_children' => false,
);
}
}
}
if (
isset( $block->context['query']['order'] ) &&
in_array( strtoupper( $block->context['query']['order'] ), array( 'ASC', 'DESC' ), true )
) {
$query['order'] = strtoupper( $block->context['query']['order'] );
}
if ( isset( $block->context['query']['orderBy'] ) ) {
$query['orderby'] = $block->context['query']['orderBy'];
}
if (
isset( $block->context['query']['author'] )
) {
if ( is_array( $block->context['query']['author'] ) ) {
$query['author__in'] = array_filter( array_map( 'intval', $block->context['query']['author'] ) );
} elseif ( is_string( $block->context['query']['author'] ) ) {
$query['author__in'] = array_filter( array_map( 'intval', explode( ',', $block->context['query']['author'] ) ) );
} elseif ( is_int( $block->context['query']['author'] ) && $block->context['query']['author'] > 0 ) {
$query['author'] = $block->context['query']['author'];
}
}
if ( ! empty( $block->context['query']['search'] ) ) {
$query['s'] = $block->context['query']['search'];
}
if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical( $query['post_type'] ) ) {
$query['post_parent__in'] = array_filter( array_map( 'intval', $block->context['query']['parents'] ) );
}
}
/**
* Filters the arguments which will be passed to `WP_Query` for the Query Loop Block.
*
* Anything to this filter should be compatible with the `WP_Query` API to form
* the query context which will be passed down to the Query Loop Block's children.
* This can help, for example, to include additional settings or meta queries not
* directly supported by the core Query Loop Block, and extend its capabilities.
*
* Please note that this will only influence the query that will be rendered on the
* front-end. The editor preview is not affected by this filter. Also, worth noting
* that the editor preview uses the REST API, so, ideally, one should aim to provide
* attributes which are also compatible with the REST API, in order to be able to
* implement identical queries on both sides.
*
* @since 6.1.0
*
* @param array $query Array containing parameters for `WP_Query` as parsed by the block context.
* @param WP_Block $block Block instance.
* @param int $page Current query's page.
*/
return apply_filters( 'query_loop_block_query_vars', $query, $block, $page );
}
/**
* Helper function that returns the proper pagination arrow HTML for
* `QueryPaginationNext` and `QueryPaginationPrevious` blocks based
* on the provided `paginationArrow` from `QueryPagination` context.
*
* It's used in QueryPaginationNext and QueryPaginationPrevious blocks.
*
* @since 5.9.0
*
* @param WP_Block $block Block instance.
* @param bool $is_next Flag for handling `next/previous` blocks.
* @return string|null The pagination arrow HTML or null if there is none.
*/
function get_query_pagination_arrow( $block, $is_next ) {
$arrow_map = array(
'none' => '',
'arrow' => array(
'next' => '→',
'previous' => '←',
),
'chevron' => array(
'next' => '»',
'previous' => '«',
),
);
if ( ! empty( $block->context['paginationArrow'] ) && array_key_exists( $block->context['paginationArrow'], $arrow_map ) && ! empty( $arrow_map[ $block->context['paginationArrow'] ] ) ) {
$pagination_type = $is_next ? 'next' : 'previous';
$arrow_attribute = $block->context['paginationArrow'];
$arrow = $arrow_map[ $block->context['paginationArrow'] ][ $pagination_type ];
$arrow_classes = "wp-block-query-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
return " ";
}
return null;
}
/**
* Helper function that constructs a comment query vars array from the passed
* block properties.
*
* It's used with the Comment Query Loop inner blocks.
*
* @since 6.0.0
*
* @param WP_Block $block Block instance.
* @return array Returns the comment query parameters to use with the
* WP_Comment_Query constructor.
*/
function build_comment_query_vars_from_block( $block ) {
$comment_args = array(
'orderby' => 'comment_date_gmt',
'order' => 'ASC',
'status' => 'approve',
'no_found_rows' => false,
);
if ( is_user_logged_in() ) {
$comment_args['include_unapproved'] = array( get_current_user_id() );
} else {
$unapproved_email = wp_get_unapproved_comment_author_email();
if ( $unapproved_email ) {
$comment_args['include_unapproved'] = array( $unapproved_email );
}
}
if ( ! empty( $block->context['postId'] ) ) {
$comment_args['post_id'] = (int) $block->context['postId'];
}
if ( get_option( 'thread_comments' ) ) {
$comment_args['hierarchical'] = 'threaded';
} else {
$comment_args['hierarchical'] = false;
}
if ( get_option( 'page_comments' ) === '1' || get_option( 'page_comments' ) === true ) {
$per_page = get_option( 'comments_per_page' );
$default_page = get_option( 'default_comments_page' );
if ( $per_page > 0 ) {
$comment_args['number'] = $per_page;
$page = (int) get_query_var( 'cpage' );
if ( $page ) {
$comment_args['paged'] = $page;
} elseif ( 'oldest' === $default_page ) {
$comment_args['paged'] = 1;
} elseif ( 'newest' === $default_page ) {
$max_num_pages = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages;
if ( 0 !== $max_num_pages ) {
$comment_args['paged'] = $max_num_pages;
}
}
// Set the `cpage` query var to ensure the previous and next pagination links are correct
// when inheriting the Discussion Settings.
if ( 0 === $page && isset( $comment_args['paged'] ) && $comment_args['paged'] > 0 ) {
set_query_var( 'cpage', $comment_args['paged'] );
}
}
}
return $comment_args;
}
/**
* Helper function that returns the proper pagination arrow HTML for
* `CommentsPaginationNext` and `CommentsPaginationPrevious` blocks based on the
* provided `paginationArrow` from `CommentsPagination` context.
*
* It's used in CommentsPaginationNext and CommentsPaginationPrevious blocks.
*
* @since 6.0.0
*
* @param WP_Block $block Block instance.
* @param string $pagination_type Optional. Type of the arrow we will be rendering.
* Accepts 'next' or 'previous'. Default 'next'.
* @return string|null The pagination arrow HTML or null if there is none.
*/
function get_comments_pagination_arrow( $block, $pagination_type = 'next' ) {
$arrow_map = array(
'none' => '',
'arrow' => array(
'next' => '→',
'previous' => '←',
),
'chevron' => array(
'next' => '»',
'previous' => '«',
),
);
if ( ! empty( $block->context['comments/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ] ) ) {
$arrow_attribute = $block->context['comments/paginationArrow'];
$arrow = $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ];
$arrow_classes = "wp-block-comments-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
return " ";
}
return null;
}
/**
* Strips all HTML from the content of footnotes, and sanitizes the ID.
* This function expects slashed data on the footnotes content.
*
* @access private
* @since 6.3.2
*
* @param string $footnotes JSON encoded string of an array containing the content and ID of each footnote.
* @return string Filtered content without any HTML on the footnote content and with the sanitized id.
*/
function _wp_filter_post_meta_footnotes( $footnotes ) {
$footnotes_decoded = json_decode( $footnotes, true );
if ( ! is_array( $footnotes_decoded ) ) {
return '';
}
$footnotes_sanitized = array();
foreach ( $footnotes_decoded as $footnote ) {
if ( ! empty( $footnote['content'] ) && ! empty( $footnote['id'] ) ) {
$footnotes_sanitized[] = array(
'id' => sanitize_key( $footnote['id'] ),
'content' => wp_unslash( wp_filter_post_kses( wp_slash( $footnote['content'] ) ) ),
);
}
}
return wp_json_encode( $footnotes_sanitized );
}
/**
* Adds the filters to filter footnotes meta field.
*
* @access private
* @since 6.3.2
*/
function _wp_footnotes_kses_init_filters() {
add_filter( 'sanitize_post_meta_footnotes', '_wp_filter_post_meta_footnotes' );
}
/**
* Removes the filters that filter footnotes meta field.
*
* @access private
* @since 6.3.2
*/
function _wp_footnotes_remove_filters() {
remove_filter( 'sanitize_post_meta_footnotes', '_wp_filter_post_meta_footnotes' );
}
/**
* Registers the filter of footnotes meta field if the user does not have unfiltered_html capability.
*
* @access private
* @since 6.3.2
*/
function _wp_footnotes_kses_init() {
_wp_footnotes_remove_filters();
if ( ! current_user_can( 'unfiltered_html' ) ) {
_wp_footnotes_kses_init_filters();
}
}
/**
* Initializes footnotes meta field filters when imported data should be filtered.
*
* This filter is the last being executed on force_filtered_html_on_import.
* If the input of the filter is true it means we are in an import situation and should
* enable kses, independently of the user capabilities.
* So in that case we call _wp_footnotes_kses_init_filters;
*
* @access private
* @since 6.3.2
*
* @param string $arg Input argument of the filter.
* @return string Input argument of the filter.
*/
function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) {
// force_filtered_html_on_import is true we need to init the global styles kses filters.
if ( $arg ) {
_wp_footnotes_kses_init_filters();
}
return $arg;
}
->addCrumbs( $this->getSearchCrumb( $reference ) );
break;
case 'notFound':
$this->addCrumbs( $this->getNotFoundCrumb() );
break;
case 'preview':
$this->addCrumbs( $this->getPreviewCrumb( $reference ) );
}
// Paged crumb.
if ( ! empty( $paged['paged'] ) ) {
$this->addCrumbs( $this->getPagedCrumb( $paged ) );
}
// Maybe remove the last crumb.
if ( ! $this->showCurrentItem( $type, $reference ) ) {
array_pop( $this->breadcrumbs );
}
$this->breadcrumbs = array_filter( $this->breadcrumbs );
return $this->breadcrumbs;
}
/**
* Gets the prefix crumb.
*
* @since 4.1.1
*
* @param string $type The type of breadcrumb.
* @param mixed $reference The breadcrumb reference.
* @return array A crumb.
*/
public function getPrefixCrumb( $type, $reference ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( 0 === strlen( aioseo()->options->breadcrumbs->breadcrumbPrefix ) ) {
return [];
}
return $this->makeCrumb( aioseo()->options->breadcrumbs->breadcrumbPrefix, '', 'prefix' );
}
/**
* Gets the 404 crumb.
*
* @since 4.1.1
*
* @return array A crumb.
*/
public function getNotFoundCrumb() {
return $this->makeCrumb( aioseo()->options->breadcrumbs->errorFormat404, '', 'notFound' );
}
/**
* Gets the search crumb.
*
* @since 4.1.1
*
* @param string $searchQuery The search query for reference.
* @return array A crumb.
*/
public function getSearchCrumb( $searchQuery ) {
return $this->makeCrumb( aioseo()->options->breadcrumbs->searchResultFormat, get_search_link( $searchQuery ), 'search', $searchQuery );
}
/**
* Gets the preview crumb.
*
* @since 4.1.5
*
* @param string $label The preview label.
* @return array A crumb.
*/
public function getPreviewCrumb( $label ) {
return $this->makeCrumb( $label, '', 'preview' );
}
/**
* Gets the post type archive crumb.
*
* @since 4.1.1
*
* @param \WP_Post_Type $postType The post type object for reference.
* @return array A crumb.
*/
public function getPostTypeArchiveCrumb( $postType ) {
return $this->makeCrumb( aioseo()->options->breadcrumbs->archiveFormat, get_post_type_archive_link( $postType->name ), 'postTypeArchive', $postType );
}
/**
* Gets a post crumb.
*
* @since 4.1.1
*
* @param \WP_Post $post A post object for reference.
* @param string $type The breadcrumb type.
* @param string $subType The breadcrumb subType.
* @return array A crumb.
*/
public function getPostCrumb( $post, $type = 'single', $subType = '' ) {
return $this->makeCrumb( get_the_title( $post ), get_permalink( $post ), $type, $post, $subType );
}
/**
* Gets the term crumb.
*
* @since 4.1.1
*
* @param \WP_Term $term The term object for reference.
* @param string $subType The breadcrumb subType.
* @return array A crumb.
*/
public function getTermTaxonomyCrumb( $term, $subType = '' ) {
return $this->makeCrumb( $term->name, get_term_link( $term ), 'taxonomy', $term, $subType );
}
/**
* Gets the paged crumb.
*
* @since 4.1.1
*
* @param array $reference The paged array for reference.
* @return array A crumb.
*/
public function getPagedCrumb( $reference ) {
return $this->makeCrumb( sprintf( '%1$s %2$s', __( 'Page', 'all-in-one-seo-pack' ), $reference['paged'] ), $reference['link'], 'paged', $reference );
}
/**
* Gets the author crumb.
*
* @since 4.1.1
*
* @param \WP_User $wpUser A WP_User object.
* @return array A crumb.
*/
public function getAuthorCrumb( $wpUser ) {
return $this->makeCrumb( $wpUser->display_name, get_author_posts_url( $wpUser->ID ), 'author', $wpUser );
}
/**
* Gets the date crumb.
*
* @since 4.1.1
*
* @param array $reference An array of year, month and day values.
* @return array A crumb.
*/
public function getDateCrumb( $reference ) {
$dateCrumb = [];
$addMonth = false;
$addYear = false;
if ( ! empty( $reference['day'] ) ) {
$addMonth = true;
$addYear = true;
$dateCrumb[] = $this->makeCrumb(
zeroise( (int) $reference['day'], 2 ),
get_day_link( $reference['year'], $reference['month'], $reference['day'] ),
'day',
$reference['day']
);
}
if ( ! empty( $reference['month'] ) || $addMonth ) {
$addYear = true;
$dateCrumb[] = $this->makeCrumb(
zeroise( (int) $reference['month'], 2 ),
get_month_link( $reference['year'], $reference['month'] ),
'month',
$reference['month']
);
}
if ( ! empty( $reference['year'] ) || $addYear ) {
$dateCrumb[] = $this->makeCrumb(
$reference['year'],
get_year_link( $reference['year'] ),
'year',
$reference['year']
);
}
return array_reverse( $dateCrumb );
}
/**
* Gets an array of crumbs parents for the term.
*
* @since 4.1.1
*
* @param \WP_Term $term A WP_Term object.
* @return array An array of parent crumbs.
*/
public function getTermTaxonomyParentCrumbs( $term ) {
$crumbs = [];
$termHierarchy = $this->getTermHierarchy( $term->term_id, $term->taxonomy );
if ( ! empty( $termHierarchy ) ) {
foreach ( $termHierarchy as $parentTermId ) {
$parentTerm = get_term( $parentTermId, $term->taxonomy );
$crumbs[] = $this->getTermTaxonomyCrumb( $parentTerm, 'parent' );
}
}
return $crumbs;
}
/**
* Helper function to create a standard crumb array.
*
* @since 4.1.1
*
* @param string $label The crumb label.
* @param string $link The crumb url.
* @param null $type The crumb type.
* @param null $reference The crumb reference.
* @param null $subType The crumb subType ( single/parent ).
* @return array A crumb array.
*/
public function makeCrumb( $label, $link = '', $type = null, $reference = null, $subType = null ) {
return [
'label' => $label,
'link' => $link,
'type' => $type,
'subType' => $subType,
'reference' => $reference
];
}
/**
* Gets a post archive crumb if it's post type has archives.
*
* @since 4.1.1
*
* @param int|\WP_Post $post An ID or a WP_Post object.
* @return array A crumb.
*/
public function getPostArchiveCrumb( $post ) {
$postType = get_post_type_object( get_post_type( $post ) );
if ( ! $postType || ! $postType->has_archive ) {
return [];
}
return $this->makeCrumb( $postType->labels->name, get_post_type_archive_link( $postType->name ), 'postTypeArchive', $postType );
}
/**
* Gets a post's taxonomy crumbs.
*
* @since 4.1.1
*
* @param int|\WP_Post $post An ID or a WP_Post object.
* @param null $taxonomy A taxonomy to use. If none is provided the first one with terms selected will be used.
* @return array An array of term crumbs.
*/
public function getPostTaxonomyCrumbs( $post, $taxonomy = null ) {
$crumbs = [];
if ( $taxonomy && ! is_array( $taxonomy ) ) {
$taxonomy = [ $taxonomy ];
}
$termHierarchy = $this->getPostTaxTermHierarchy( $post, $taxonomy );
if ( ! empty( $termHierarchy['terms'] ) ) {
foreach ( $termHierarchy['terms'] as $termId ) {
$term = get_term( $termId, $termHierarchy['taxonomy'] );
$crumbs[] = $this->makeCrumb( $term->name, get_term_link( $term, $termHierarchy['taxonomy'] ), 'taxonomy', $term, 'parent' );
}
}
return $crumbs;
}
/**
* Gets the post's parent crumbs.
*
* @since 4.1.1
*
* @param int|\WP_Post $post An ID or a WP_Post object.
* @param string $type The crumb type.
* @return array An array of the post parent crumbs.
*/
public function getPostParentCrumbs( $post, $type = 'single' ) {
$crumbs = [];
if ( ! is_post_type_hierarchical( get_post_type( $post ) ) ) {
return $crumbs;
}
$postHierarchy = $this->getPostHierarchy( $post );
if ( ! empty( $postHierarchy ) ) {
foreach ( $postHierarchy as $parentID ) {
// Do not include the Home Page.
if ( aioseo()->helpers->getHomePageId() === $parentID ) {
continue;
}
$crumbs[] = $this->getPostCrumb( get_post( $parentID ), $type, 'parent' );
}
}
return $crumbs;
}
/**
* Function to extend on pro for extra functionality.
*
* @since 4.1.1
*
* @param string $type The type of breadcrumb.
* @param mixed $reference The breadcrumb reference.
* @return bool Show current item.
*/
public function showCurrentItem( $type = null, $reference = null ) {
return apply_filters( 'aioseo_breadcrumbs_show_current_item', aioseo()->options->breadcrumbs->showCurrentItem, $type, $reference );
}
/**
* Gets a home page crumb.
*
* @since 4.1.1
*
* @param string $type The type of breadcrumb.
* @param mixed $reference The breadcrumb reference.
* @return array|void The home crumb.
*/
public function maybeGetHomePageCrumb( $type = null, $reference = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( aioseo()->options->breadcrumbs->homepageLink ) {
return $this->getHomePageCrumb();
}
}
/**
* Gets a home page crumb.
*
* @since 4.1.1
*
* @return array The home crumb.
*/
public function getHomePageCrumb() {
$homePageId = aioseo()->helpers->getHomePageId();
$label = ( 0 < strlen( aioseo()->options->breadcrumbs->homepageLabel ) ) ? aioseo()->options->breadcrumbs->homepageLabel : get_the_title( $homePageId );
// Label fallback.
if ( empty( $label ) ) {
$label = __( 'Home', 'all-in-one-seo-pack' );
}
return $this->makeCrumb( $label, get_home_url(), 'homePage', aioseo()->helpers->getHomePage() );
}
/**
* Gets the blog crumb.
*
* @since 4.1.1
*
* @return array The blog crumb.
*/
public function getBlogCrumb() {
$crumb = [];
$blogPage = aioseo()->helpers->getBlogPage();
if ( null !== $blogPage ) {
$crumb = $this->makeCrumb( $blogPage->post_title, get_permalink( $blogPage ), 'blog', $blogPage );
}
return $crumb;
}
/**
* Gets a post's term hierarchy for a list of taxonomies selecting the one that has a lengthier hierarchy.
*
* @since 4.1.1
*
* @param int|\WP_Post $post An ID or a WP_Post object.
* @param array $taxonomies An array of taxonomy names.
* @param false $skipUnselectedTerms Allow unselected terms to be filtered out from the crumbs.
* @return array An array of the taxonomy name + a term hierarchy.
*/
public function getPostTaxTermHierarchy( $post, $taxonomies = [], $skipUnselectedTerms = false ) {
// Get all taxonomies attached to the post.
if ( empty( $taxonomies ) ) {
$taxonomies = get_object_taxonomies( get_post_type( $post ), 'objects' );
$taxonomies = wp_filter_object_list( $taxonomies, [ 'public' => true ], 'and', 'name' );
}
foreach ( $taxonomies as $taxonomy ) {
$primaryTerm = aioseo()->standalone->primaryTerm->getPrimaryTerm( $post->ID, $taxonomy );
$terms = wp_get_object_terms( $post->ID, $taxonomy );
// Use the first taxonomy with terms.
if ( empty( $terms ) || is_wp_error( $terms ) ) {
continue;
}
// Determines the lengthier term hierarchy.
$termHierarchy = [];
foreach ( $terms as $term ) {
// Gets our filtered ancestors.
$ancestors = $this->getFilteredTermHierarchy( $term->term_id, $term->taxonomy, $skipUnselectedTerms ? $terms : [] );
// Merge the current term to be used in the breadcrumbs.
$ancestors = array_merge( $ancestors, [ $term->term_id ] );
// If the current term is the primary term, use it.
if ( is_a( $primaryTerm, 'WP_Term' ) && $primaryTerm->term_id === $term->term_id ) {
$termHierarchy = $ancestors;
break;
}
$termHierarchy = ( count( $termHierarchy ) < count( $ancestors ) ) ? $ancestors : $termHierarchy;
}
// Return a top to bottom hierarchy.
return [
'taxonomy' => $taxonomy,
'terms' => $termHierarchy
];
}
return [];
}
/**
* Filters a term's parent hierarchy against other terms.
*
* @since 4.1.1
*
* @param int $termId A term id.
* @param string $taxonomy The taxonomy name.
* @param array $termsToFilterAgainst Terms to filter out of the hierarchy.
* @return array The term's parent hierarchy.
*/
public function getFilteredTermHierarchy( $termId, $taxonomy, $termsToFilterAgainst = [] ) {
$ancestors = $this->getTermHierarchy( $termId, $taxonomy );
// Keep only selected terms in the hierarchy.
if ( ! empty( $termsToFilterAgainst ) ) {
// If it's a WP_Term array make it a term_id array.
if ( is_a( current( $termsToFilterAgainst ), 'WP_Term' ) ) {
$termsToFilterAgainst = wp_list_pluck( $termsToFilterAgainst, 'term_id' );
}
$ancestors = array_intersect( $ancestors, $termsToFilterAgainst );
}
return $ancestors;
}
/**
* Gets a term's parent hierarchy.
*
* @since 4.1.1
*
* @param int $termId A term id.
* @param string $taxonomy A taxonomy name.
* @return array The term parent hierarchy.
*/
public function getTermHierarchy( $termId, $taxonomy ) {
// Return a top to bottom hierarchy.
return array_reverse( get_ancestors( $termId, $taxonomy, 'taxonomy' ) );
}
/**
* Gets a post's parent hierarchy.
*
* @since 4.1.1
*
* @param int|\WP_Post $post An ID or a WP_Post object.
* @return array The post parent hierarchy.
*/
public function getPostHierarchy( $post ) {
$postId = ! empty( $post->ID ) ? $post->ID : $post;
// Return a top to bottom hierarchy.
return array_reverse( get_ancestors( $postId, '', 'post_type' ) );
}
/**
* Register our breadcrumb widget.
*
* @since 4.1.1
*
* @return void
*/
public function registerWidget() {
if ( aioseo()->helpers->canRegisterLegacyWidget( 'aioseo-breadcrumb-widget' ) ) {
register_widget( 'AIOSEO\Plugin\Common\Breadcrumbs\Widget' );
}
}
}
}
namespace {
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! function_exists( 'aioseo_breadcrumbs' ) ) {
/**
* Global function for breadcrumbs output.
*
* @since 4.1.1
*
* @param boolean $echo Echo or return the output.
* @return string|void The output.
*/
function aioseo_breadcrumbs( $echo = true ) {
return aioseo()->breadcrumbs->frontend->display( $echo );
}
}
}