관리-도구
편집 파일: Breadcrumbs.php
<?php namespace AIOSEO\Plugin\Common\Breadcrumbs { // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Class Breadcrumbs. * * @since 4.1.1 */ class Breadcrumbs { /** Instance of the frontend class. * * @since 4.1.1 * * @var \AIOSEO\Plugin\Common\Breadcrumbs\Frontend|\AIOSEO\Plugin\Pro\Breadcrumbs\Frontend */ public $frontend; /** * Instance of the shortcode class. * * @since 4.1.1 * * @var Shortcode */ public $shortcode; /** * Instance of the block class. * * @since 4.1.1 * * @var Block */ public $block; /** * Instance of the tags class. * * @since 4.1.1 * * @var Tags */ public $tags; /** * Array of crumbs. * * @since 4.1.1 * * @var array An array of crumbs. */ public $breadcrumbs; /** * Breadcrumbs constructor. * * @since 4.1.1 */ public function __construct() { $this->frontend = new Frontend(); $this->shortcode = new Shortcode(); $this->block = new Block(); add_action( 'widgets_init', [ $this, 'registerWidget' ] ); // Init Tags class later as we need post types registered. add_action( 'init', [ $this, 'init' ], 50 ); } public function init() { $this->tags = new Tags(); } /** * Helper to add crumbs on the breadcrumb array. * * @since 4.1.1 * * @param array $crumbs A single crumb or an array of crumbs. * @return void */ public function addCrumbs( $crumbs ) { if ( empty( $crumbs ) || ! is_array( $crumbs ) ) { return; } // If it's a single crumb put it inside an array to merge. if ( isset( $crumbs['label'] ) ) { $crumbs = [ $crumbs ]; } $this->breadcrumbs = array_merge( $this->breadcrumbs, $crumbs ); } /** * Builds a crumb array based on a type and a reference. * * @since 4.1.1 * * @param string $type The type of breadcrumb ( post, single, page, category, tag, taxonomy, postTypeArchive, date, * author, search, notFound, blog ). * @param mixed $reference The reference can be an object ( WP_Post | WP_Term | WP_Post_Type | WP_User ), an array, an int or a string. * @param array $paged A reference for a paged crumb. * @return array An array of breadcrumbs with their label, link, type and reference. */ public function buildBreadcrumbs( $type, $reference, $paged = [] ) { // Clear the breadcrumb array and build a new one. $this->breadcrumbs = []; // Add breadcrumb prefix. $this->addCrumbs( $this->getPrefixCrumb( $type, $reference ) ); // Set a home page in the beginning of the breadcrumb. $this->addCrumbs( $this->maybeGetHomePageCrumb( $type, $reference ) ); // Woocommerce shop page support. $this->addCrumbs( $this->maybeGetWooCommerceShopCrumb() ); // Blog home. if ( aioseo()->options->breadcrumbs->showBlogHome && in_array( $type, [ 'category', 'tag', 'post', 'author', 'date' ], true ) ) { $this->addCrumbs( $this->getBlogCrumb() ); } switch ( $type ) { case 'post': case 'single': $this->addCrumbs( $this->getPostArchiveCrumb( $reference ) ); $this->addCrumbs( $this->getPostTaxonomyCrumbs( $reference ) ); $this->addCrumbs( $this->getPostParentCrumbs( $reference ) ); $this->addCrumbs( $this->getPostCrumb( $reference ) ); break; case 'page': $this->addCrumbs( $this->getPostParentCrumbs( $reference, 'page' ) ); $this->addCrumbs( $this->getPostCrumb( $reference, 'page' ) ); break; case 'category': case 'tag': case 'taxonomy': $this->addCrumbs( $this->getTermTaxonomyParentCrumbs( $reference ) ); $this->addCrumbs( $this->getTermTaxonomyCrumb( $reference ) ); break; case 'postTypeArchive': $this->addCrumbs( $this->getPostTypeArchiveCrumb( $reference ) ); break; case 'date': $this->addCrumbs( $this->getDateCrumb( $reference ) ); break; case 'author': $this->addCrumbs( $this->getAuthorCrumb( $reference ) ); break; case 'blog': $this->addCrumbs( $this->getBlogCrumb() ); break; case 'search': $this->addCrumbs( $this->getSearchCrumb( $reference ) ); break; case 'notFound': $this->addCrumbs( $this->getNotFoundCrumb() ); break; case 'preview': $this->addCrumbs( $this->getPreviewCrumb( $reference ) ); break; case 'wcProduct': $this->addCrumbs( $this->getPostTaxonomyCrumbs( $reference ) ); $this->addCrumbs( $this->getPostParentCrumbs( $reference ) ); $this->addCrumbs( $this->getPostCrumb( $reference ) ); break; case 'buddypress': $this->addCrumbs( aioseo()->standalone->buddyPress->component->getCrumbs() ); break; } // 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 ); } // Remove empty crumbs. $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 = ''; if ( $homePageId ) { $label = get_the_title( $homePageId ); } if ( 0 < strlen( aioseo()->options->breadcrumbs->homepageLabel ) ) { $label = aioseo()->options->breadcrumbs->homepageLabel; } // 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; } /** * Maybe add the shop crumb to products and product categories. * * @since 4.5.5 * * @return array The shop crumb. */ public function maybeGetWooCommerceShopCrumb() { $crumb = []; if ( aioseo()->helpers->isWooCommerceShopPage() || aioseo()->helpers->isWooCommerceProductPage() || aioseo()->helpers->isWooCommerceTaxonomyPage() ) { $crumb = $this->getWooCommerceShopCrumb(); } return $crumb; } /** * Gets the shop crumb. * @see WC_Breadcrumb::prepend_shop_page() * * @since 4.5.5 * * @return array The shop crumb. */ public function getWooCommerceShopCrumb() { $crumb = []; if ( ! function_exists( 'wc_get_page_id' ) || apply_filters( 'aioseo_woocommerce_breadcrumb_hide_shop', false ) ) { return $crumb; } $shopPageId = wc_get_page_id( 'shop' ); $shopPage = get_post( $shopPageId ); // WC checks if the permalink contains the shop page in the URI, but we prefer to // always show the shop page as the first crumb if it exists and it's not the home page. if ( $shopPageId && $shopPage && aioseo()->helpers->getHomePageId() !== $shopPageId ) { $crumb = $this->makeCrumb( get_the_title( $shopPage ), get_permalink( $shopPage ), 'wcShop' ); } 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 ); } } }