1177 lines
32 KiB
PHP
1177 lines
32 KiB
PHP
<?php
|
|
namespace AIOSEO\Plugin\Common\Traits\Helpers;
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Contains all context related helper methods.
|
|
* This includes methods to check the context of the current request, but also get WP objects.
|
|
*
|
|
* @since 4.1.4
|
|
*/
|
|
trait WpContext {
|
|
/**
|
|
* The original main query.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @var \WP_Query
|
|
*/
|
|
public $originalQuery;
|
|
|
|
/**
|
|
* The original main post variable.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @var \WP_Post
|
|
*/
|
|
public $originalPost;
|
|
|
|
/**
|
|
* Get the home page object.
|
|
*
|
|
* @since 4.1.1
|
|
*
|
|
* @return \WP_Post|null The home page.
|
|
*/
|
|
public function getHomePage() {
|
|
$homePageId = $this->getHomePageId();
|
|
|
|
return $homePageId ? get_post( $homePageId ) : null;
|
|
}
|
|
|
|
/**
|
|
* Get the ID of the home page.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return int|false The home page ID.
|
|
*/
|
|
public function getHomePageId() {
|
|
static $homeId = null;
|
|
if ( null !== $homeId ) {
|
|
return $homeId;
|
|
}
|
|
|
|
$pageShowOnFront = ( 'page' === get_option( 'show_on_front' ) );
|
|
$pageOnFrontId = get_option( 'page_on_front' );
|
|
|
|
$homeId = $pageShowOnFront && $pageOnFrontId ? (int) $pageOnFrontId : false;
|
|
|
|
return $homeId;
|
|
}
|
|
|
|
/**
|
|
* Returns the blog page.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return \WP_Post|null The blog page.
|
|
*/
|
|
public function getBlogPage() {
|
|
$blogPageId = $this->getBlogPageId();
|
|
|
|
return $blogPageId ? get_post( $blogPageId ) : null;
|
|
}
|
|
|
|
/**
|
|
* Gets the current blog page id if it's configured.
|
|
*
|
|
* @since 4.1.1
|
|
*
|
|
* @return int|null
|
|
*/
|
|
public function getBlogPageId() {
|
|
$pageShowOnFront = ( 'page' === get_option( 'show_on_front' ) );
|
|
$blogPageId = (int) get_option( 'page_for_posts' );
|
|
|
|
return $pageShowOnFront && $blogPageId ? $blogPageId : null;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the current page is a taxonomy term archive.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return bool Whether the current page is a taxonomy term archive.
|
|
*/
|
|
public function isTaxTerm() {
|
|
$object = get_queried_object();
|
|
|
|
return $object instanceof \WP_Term;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the current page is a static one.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return bool Whether the current page is a static one.
|
|
*/
|
|
public function isStaticPage() {
|
|
return $this->isStaticHomePage() || $this->isStaticPostsPage() || $this->isWooCommerceShopPage();
|
|
}
|
|
|
|
/**
|
|
* Checks whether the current page is the static homepage.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param mixed $post Pass in an optional post to check if its the static home page.
|
|
* @return bool Whether the current page is the static homepage.
|
|
*/
|
|
public function isStaticHomePage( $post = null ) {
|
|
static $isHomePage = null;
|
|
if ( null !== $isHomePage ) {
|
|
return $isHomePage;
|
|
}
|
|
|
|
$post = aioseo()->helpers->getPost( $post );
|
|
|
|
$isHomePage = ( 'page' === get_option( 'show_on_front' ) && ! empty( $post->ID ) && (int) get_option( 'page_on_front' ) === $post->ID );
|
|
|
|
return $isHomePage;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the current page is the dynamic homepage.
|
|
*
|
|
* @since 4.2.3
|
|
*
|
|
* @return bool Whether the current page is the dynamic homepage.
|
|
*/
|
|
public function isDynamicHomePage() {
|
|
return is_front_page() && is_home();
|
|
}
|
|
|
|
/**
|
|
* Checks whether the current page is the static posts page.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return bool Whether the current page is the static posts page.
|
|
*/
|
|
public function isStaticPostsPage( $post = null ) {
|
|
static $isStaticPostsPage = null;
|
|
if ( null !== $isStaticPostsPage ) {
|
|
return $isStaticPostsPage;
|
|
}
|
|
|
|
$post = aioseo()->helpers->getPost( $post );
|
|
|
|
$isStaticPostsPage = (
|
|
( is_home() && ( 0 !== (int) get_option( 'page_for_posts' ) ) ) ||
|
|
( ! empty( $post->ID ) && (int) get_option( 'page_for_posts' ) === $post->ID )
|
|
);
|
|
|
|
return $isStaticPostsPage;
|
|
}
|
|
|
|
/**
|
|
* Checks whether current page supports meta.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return bool Whether the current page supports meta.
|
|
*/
|
|
public function supportsMeta() {
|
|
return ! is_date() && ! is_author() && ! is_search() && ! is_404();
|
|
}
|
|
|
|
/**
|
|
* Returns the current post object.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param \WP_Post|int|bool $postId The post ID.
|
|
* @return \WP_Post|null The post object.
|
|
*/
|
|
public function getPost( $postId = false ) {
|
|
$postId = is_a( $postId, 'WP_Post' ) ? $postId->ID : $postId;
|
|
|
|
if ( aioseo()->helpers->isWooCommerceShopPage( $postId ) ) {
|
|
return get_post( wc_get_page_id( 'shop' ) );
|
|
}
|
|
|
|
if ( is_front_page() || is_home() ) {
|
|
$showOnFront = 'page' === get_option( 'show_on_front' );
|
|
if ( $showOnFront ) {
|
|
if ( is_front_page() ) {
|
|
$pageOnFront = (int) get_option( 'page_on_front' );
|
|
|
|
return get_post( $pageOnFront );
|
|
} elseif ( is_home() ) {
|
|
$pageForPosts = (int) get_option( 'page_for_posts' );
|
|
|
|
return get_post( $pageForPosts );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Learnpress lessons load the course. So here we need to switch to the lesson.
|
|
$learnPressLesson = aioseo()->helpers->getLearnPressLesson();
|
|
if ( ! $postId && $learnPressLesson ) {
|
|
$postId = $learnPressLesson;
|
|
}
|
|
|
|
// Allow other plugins to filter the post ID e.g. for a special archive page.
|
|
$postId = apply_filters( 'aioseo_get_post_id', $postId );
|
|
|
|
// We need to check these conditions and cannot always return get_post() because we'll return the first post on archive pages (dynamic homepage, term pages, etc.).
|
|
|
|
if (
|
|
$this->isScreenBase( 'post' ) ||
|
|
$postId ||
|
|
is_singular()
|
|
) {
|
|
return get_post( $postId );
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the term object for the given ID or the one from the main query.
|
|
*
|
|
* @since 4.7.8
|
|
*
|
|
* @param int $termId The term ID.
|
|
* @param string $taxonomy The taxonomy.
|
|
* @return \WP_Term The term object.
|
|
*/
|
|
public function getTerm( $termId = 0, $taxonomy = '' ) {
|
|
$term = null;
|
|
if ( $termId ) {
|
|
$term = get_term( $termId, $taxonomy );
|
|
} else {
|
|
$term = get_queried_object();
|
|
}
|
|
|
|
// If the term is a Product Attribute, set its parent taxonomy to our fake
|
|
// "product_attributes" taxonomy so we can use the default settings.
|
|
if ( is_a( $term, 'WP_Term' ) && $this->isWooCommerceProductAttribute( $term->taxonomy ) ) {
|
|
$term = clone $term;
|
|
$term->taxonomy = 'product_attributes';
|
|
}
|
|
|
|
return $term;
|
|
}
|
|
|
|
/**
|
|
* Returns the current post ID.
|
|
*
|
|
* @since 4.3.1
|
|
*
|
|
* @return int|null The post ID.
|
|
*/
|
|
public function getPostId() {
|
|
$post = $this->getPost();
|
|
|
|
return is_object( $post ) && property_exists( $post, 'ID' ) ? $post->ID : null;
|
|
}
|
|
|
|
/**
|
|
* This is used as a fallback when WP conditionals (is_single, is_page, etc.) are unavailable, such as in page builder contexts.
|
|
*
|
|
* @since 4.9.6
|
|
*
|
|
* @param \WP_Post|null $postObject The post object.
|
|
* @return string The breadcrumb type ('page', 'post', 'single') or empty string.
|
|
*/
|
|
public function getBreadcrumbTypeFromPost( $postObject = null ) {
|
|
if ( ! $postObject instanceof \WP_Post ) {
|
|
global $post;
|
|
$postObject = $post;
|
|
}
|
|
|
|
if ( ! $postObject instanceof \WP_Post ) {
|
|
return '';
|
|
}
|
|
|
|
// Don't resolve a type for the front page — it's handled separately.
|
|
if ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === $postObject->ID ) {
|
|
return '';
|
|
}
|
|
|
|
if ( 'page' === $postObject->post_type ) {
|
|
return 'page';
|
|
}
|
|
|
|
if ( 'post' === $postObject->post_type ) {
|
|
return 'post';
|
|
}
|
|
|
|
return 'single';
|
|
}
|
|
|
|
/**
|
|
* Returns the post content after parsing it.
|
|
*
|
|
* @since 4.1.5
|
|
*
|
|
* @param \WP_Post|int $post The post (optional).
|
|
* @return string The post content.
|
|
*/
|
|
public function getPostContent( $post = null ) {
|
|
$post = is_a( $post, 'WP_Post' ) ? $post : $this->getPost( $post );
|
|
|
|
static $content = [];
|
|
if ( isset( $content[ $post->ID ] ) ) {
|
|
return $content[ $post->ID ];
|
|
}
|
|
|
|
// We need to process the content for page builders.
|
|
$postContent = $post->post_content;
|
|
$pageBuilder = aioseo()->helpers->getPostPageBuilderName( $post->ID );
|
|
if ( ! empty( $pageBuilder ) ) {
|
|
$postContent = aioseo()->standalone->pageBuilderIntegrations[ $pageBuilder ]->processContent( $post->ID, $postContent );
|
|
}
|
|
|
|
$postContent = is_string( $postContent ) ? $postContent : '';
|
|
|
|
$content[ $post->ID ] = $this->theContent( $postContent );
|
|
|
|
if ( apply_filters( 'aioseo_description_include_custom_fields', true, $post ) ) {
|
|
$content[ $post->ID ] .= $this->theContent( $this->getPostCustomFieldsContent( $post ) );
|
|
}
|
|
|
|
return $content[ $post->ID ];
|
|
}
|
|
|
|
/**
|
|
* Gets the content from configured custom fields.
|
|
*
|
|
* @since 4.2.7
|
|
*
|
|
* @param \WP_Post|int $post A post object or ID.
|
|
* @return string The content.
|
|
*/
|
|
public function getPostCustomFieldsContent( $post = null ) {
|
|
$post = is_a( $post, 'WP_Post' ) ? $post : $this->getPost( $post );
|
|
|
|
if ( ! aioseo()->dynamicOptions->searchAppearance->postTypes->has( $post->post_type ) ) {
|
|
return '';
|
|
}
|
|
|
|
$customFieldKeys = aioseo()->dynamicOptions->searchAppearance->postTypes->{$post->post_type}->customFields;
|
|
if ( empty( $customFieldKeys ) ) {
|
|
return '';
|
|
}
|
|
|
|
$customFieldKeys = explode( ' ', sanitize_text_field( $customFieldKeys ) );
|
|
|
|
return aioseo()->helpers->getCustomFieldsContent( $post, $customFieldKeys );
|
|
}
|
|
|
|
/**
|
|
* Returns the post content after parsing shortcodes and blocks.
|
|
* We avoid using the "the_content" hook because it breaks stuff if we call it outside the loop or main query.
|
|
* See https://developer.wordpress.org/reference/hooks/the_content/
|
|
*
|
|
* @since 4.1.5.2
|
|
*
|
|
* @param string $postContent The post content.
|
|
* @return string The parsed post content.
|
|
*/
|
|
public function theContent( $postContent ) {
|
|
if ( ! aioseo()->options->searchAppearance->advanced->runShortcodes ) {
|
|
return $postContent;
|
|
}
|
|
|
|
// Because do_blocks() and do_shortcodes() can trigger conflicts, we need to clone these objects and restore them afterwards.
|
|
// We need to clone deep to sever pointers/references because these have nested object properties.
|
|
global $wp_query, $post; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
$this->originalQuery = $this->deepClone( $wp_query ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
$this->originalPost = is_a( $post, 'WP_Post' ) ? $this->deepClone( $post ) : null;
|
|
|
|
// The order of the function calls below is intentional and should NOT change.
|
|
$postContent = $this->doBlocks( $postContent );
|
|
$postContent = wpautop( $postContent );
|
|
$postContent = @$this->doShortcodes( $postContent );
|
|
|
|
$this->restoreWpQuery();
|
|
|
|
return $postContent;
|
|
}
|
|
|
|
/**
|
|
* Returns the description based on the post content.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param \WP_Post|int $post The post (optional).
|
|
* @return string The description.
|
|
*/
|
|
public function getDescriptionFromContent( $post = null ) {
|
|
$post = is_a( $post, 'WP_Post' ) ? $post : $this->getPost( $post );
|
|
|
|
static $content = [];
|
|
if ( isset( $content[ $post->ID ] ) ) {
|
|
return $content[ $post->ID ];
|
|
}
|
|
|
|
$content[ $post->ID ] = '';
|
|
if ( ! empty( $post->post_password ) ) {
|
|
return $content[ $post->ID ];
|
|
}
|
|
|
|
$postContent = $this->getPostContent( $post );
|
|
|
|
// Strip images, captions and WP oembed wrappers (e.g. YouTube URLs) from the post content.
|
|
$postContent = preg_replace( '/(<figure.*?\/figure>|<img.*?\/>|<div.*?class="wp-block-embed__wrapper".*?>.*?<\/div>)/s', '', (string) $postContent );
|
|
$postContent = str_replace( ']]>', ']]>', (string) $postContent );
|
|
$postContent = trim( wp_strip_all_tags( strip_shortcodes( (string) $postContent ) ) );
|
|
$content[ $post->ID ] = wp_trim_words( (string) $postContent, 55, '' );
|
|
|
|
return $content[ $post->ID ];
|
|
}
|
|
|
|
/**
|
|
* Returns custom fields as a string.
|
|
*
|
|
* @since 4.0.6
|
|
*
|
|
* @param \WP_Post|int $post The post.
|
|
* @param array $keys The post meta_keys to check for values.
|
|
* @return string The custom field content.
|
|
*/
|
|
public function getCustomFieldsContent( $post = null, $keys = [] ) {
|
|
$post = is_a( $post, 'WP_Post' ) ? $post : $this->getPost( $post );
|
|
|
|
$customFieldContent = '';
|
|
|
|
$acfFields = $this->getAcfContent( $post );
|
|
foreach ( $keys as $key ) {
|
|
// Try ACF.
|
|
if ( isset( $acfFields[ $key ] ) && is_scalar( $acfFields[ $key ] ) ) {
|
|
$customFieldContent .= "$acfFields[$key] ";
|
|
continue;
|
|
}
|
|
|
|
// Fallback to post meta.
|
|
$value = get_post_meta( $post->ID, $key, true );
|
|
if ( $value && is_scalar( $value ) ) {
|
|
$customFieldContent .= $value . ' ';
|
|
}
|
|
}
|
|
|
|
return $customFieldContent;
|
|
}
|
|
|
|
/**
|
|
* Returns if the page is a special type (WooCommerce pages, Privacy page).
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param int $postId The post ID.
|
|
* @return bool If the page is special or not.
|
|
*/
|
|
public function isSpecialPage( $postId = 0 ) {
|
|
$specialPages = $this->getSpecialPageIds();
|
|
|
|
return in_array( (int) $postId, $specialPages, true );
|
|
}
|
|
|
|
/**
|
|
* Returns the ID of all special pages (e.g. homepage, blog page, WooCommerce, BuddyPress, etc.).
|
|
* This cannot be cached because the plugins need to be loaded first.
|
|
*
|
|
* @since 4.7.3
|
|
*
|
|
* @return array The IDs of all special pages.
|
|
*/
|
|
public function getSpecialPageIds() {
|
|
$pageForPostsId = (int) get_option( 'page_for_posts' );
|
|
$pageForPrivacyPolicyId = (int) get_option( 'wp_page_for_privacy_policy' );
|
|
$buddyPressPageIds = $this->getBuddyPressPageIds();
|
|
$wooCommercePageIds = array_values( $this->getWooCommercePages() );
|
|
|
|
$specialPageIds = array_merge(
|
|
[
|
|
$pageForPostsId,
|
|
$pageForPrivacyPolicyId,
|
|
],
|
|
$buddyPressPageIds,
|
|
$wooCommercePageIds
|
|
);
|
|
|
|
// Ensure all values are integers.
|
|
$specialPageIds = array_map( 'intval', $specialPageIds );
|
|
|
|
return $specialPageIds;
|
|
}
|
|
|
|
/**
|
|
* Returns whether a post is eligible for being analyzed by TruSEO.
|
|
*
|
|
* @since 4.6.1
|
|
* @version 4.7.3 Renamed from "isPageAnalysisEligible" to "isTruSeoEligible" to make it more clear.
|
|
*
|
|
* @param int $postId Post ID.
|
|
* @return bool Whether a post is eligible for being analyzed by TruSEO.
|
|
*/
|
|
public function isTruSeoEligible( $postId ) {
|
|
static $isTruSeoEnabled = null;
|
|
if ( null === $isTruSeoEnabled ) {
|
|
$isTruSeoEnabled = aioseo()->options->advanced->truSeo;
|
|
}
|
|
|
|
if ( ! $isTruSeoEnabled ) {
|
|
return false;
|
|
}
|
|
|
|
static $isPostEligible = [];
|
|
if ( isset( $isPostEligible[ $postId ] ) ) {
|
|
return $isPostEligible[ $postId ];
|
|
}
|
|
|
|
// Set the default to true.
|
|
$isPostEligible[ $postId ] = true;
|
|
|
|
$wpPost = $this->getPost( $postId );
|
|
if ( ! is_a( $wpPost, 'WP_Post' ) ) {
|
|
$isPostEligible[ $postId ] = false;
|
|
|
|
return false;
|
|
}
|
|
|
|
$eligiblePostTypes = $this->getTruSeoEligiblePostTypes();
|
|
if (
|
|
! in_array( $wpPost->post_type, $eligiblePostTypes, true ) ||
|
|
$this->isSpecialPage( $wpPost->ID )
|
|
) {
|
|
$isPostEligible[ $postId ] = false;
|
|
}
|
|
|
|
return $isPostEligible[ $postId ];
|
|
}
|
|
|
|
/**
|
|
* Returns the post types that are eligible for TruSEO analysis.
|
|
*
|
|
* @since 4.7.3
|
|
*
|
|
* @return array The post types that are eligible for TruSEO analysis.
|
|
*/
|
|
public function getTruSeoEligiblePostTypes() {
|
|
$allowedPostTypes = aioseo()->helpers->getPublicPostTypes( true );
|
|
$excludedPostTypes = [ 'attachment', 'aioseo-location', 'web-story' ];
|
|
if ( class_exists( 'bbPress' ) ) {
|
|
$excludedPostTypes = array_merge( $excludedPostTypes, [ 'forum', 'topic', 'reply' ] );
|
|
}
|
|
|
|
// Remove the excluded post types from the allowed ones.
|
|
$allowedPostTypes = array_diff( $allowedPostTypes, $excludedPostTypes );
|
|
|
|
// Now, check if the metabox is enabled and that the post type is public for each of these.
|
|
foreach ( $allowedPostTypes as $postType ) {
|
|
$postObjectType = get_post_type_object( $postType );
|
|
if ( is_a( $postObjectType, 'WP_Post_Type' ) && ! $postObjectType->public ) {
|
|
unset( $allowedPostTypes[ $postType ] );
|
|
}
|
|
|
|
$dynamicOptions = aioseo()->dynamicOptions->noConflict();
|
|
if ( ! $dynamicOptions->searchAppearance->postTypes->has( $postType, false ) || ! $dynamicOptions->{$postType}->advanced->showMetaBox ) {
|
|
// If not, unset it.
|
|
unset( $allowedPostTypes[ $postType ] );
|
|
}
|
|
}
|
|
|
|
// Considering post types get registered during various stages of the WP load process, we should not cache this.
|
|
return $allowedPostTypes;
|
|
}
|
|
|
|
/**
|
|
* Returns the page number of the current page.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return int The page number.
|
|
*/
|
|
public function getPageNumber() {
|
|
$page = get_query_var( 'page' );
|
|
if ( ! empty( $page ) ) {
|
|
return (int) $page;
|
|
}
|
|
|
|
$paged = get_query_var( 'paged' );
|
|
if ( ! empty( $paged ) ) {
|
|
return (int) $paged;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the page number for the comment page.
|
|
*
|
|
* @since 4.2.1
|
|
*
|
|
* @return int|false The page number or false if we're not on a comment page.
|
|
*/
|
|
public function getCommentPageNumber() {
|
|
$cpage = get_query_var( 'cpage', null );
|
|
if ( $this->isBlockTheme() ) {
|
|
global $wp_query; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
|
|
// For block themes we can't rely on `get_query_var()` because of {@see build_comment_query_vars_from_block()},
|
|
// so we need to check the query directly.
|
|
$cpage = $wp_query->query['cpage'] ?? null; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
}
|
|
|
|
return isset( $cpage ) ? (int) $cpage : false;
|
|
}
|
|
|
|
/**
|
|
* Check if the post passed in is a valid post, not a revision or autosave.
|
|
*
|
|
* @since 4.0.5
|
|
*
|
|
* @param \WP_Post $post The Post object to check.
|
|
* @param array $allowedPostStatuses Allowed post statuses.
|
|
* @return bool True if valid, false if not.
|
|
*/
|
|
public function isValidPost( $post, $allowedPostStatuses = [ 'publish' ] ) {
|
|
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ! is_object( $post ) ) {
|
|
$post = get_post( $post );
|
|
}
|
|
|
|
// No post, no go.
|
|
if ( empty( $post ) ) {
|
|
return false;
|
|
}
|
|
|
|
// In order to prevent recursion, we are skipping scheduled-action posts and revisions.
|
|
if (
|
|
'scheduled-action' === $post->post_type ||
|
|
'revision' === $post->post_type
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// Ensure this post has the proper post status.
|
|
if (
|
|
! in_array( $post->post_status, $allowedPostStatuses, true ) &&
|
|
! in_array( 'all', $allowedPostStatuses, true )
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the given URL is a valid attachment.
|
|
*
|
|
* @since 4.0.13
|
|
*
|
|
* @param string $url The URL.
|
|
* @return bool Whether the URL is a valid attachment.
|
|
*/
|
|
public function isValidAttachment( $url ) {
|
|
$uploadDirUrl = aioseo()->helpers->escapeRegex( $this->getWpContentUrl() );
|
|
|
|
return preg_match( "/$uploadDirUrl.*/", (string) $url );
|
|
}
|
|
|
|
/**
|
|
* Tries to convert an attachment URL into a post ID.
|
|
*
|
|
* This our own optimized version of attachment_url_to_postid().
|
|
*
|
|
* @since 4.0.13
|
|
*
|
|
* @param string $url The attachment URL.
|
|
* @return int|bool The attachment ID or false if no attachment could be found.
|
|
*/
|
|
public function attachmentUrlToPostId( $url ) {
|
|
$cacheName = 'attachment_url_to_post_id_' . sha1( "aioseo_attachment_url_to_post_id_$url" );
|
|
$cachedId = aioseo()->core->cache->get( $cacheName );
|
|
if ( $cachedId ) {
|
|
return 'none' !== $cachedId && is_numeric( $cachedId ) ? (int) $cachedId : false;
|
|
}
|
|
|
|
$path = $url;
|
|
$uploadDirInfo = wp_get_upload_dir();
|
|
|
|
$siteUrl = wp_parse_url( $uploadDirInfo['url'] );
|
|
$imagePath = wp_parse_url( $path );
|
|
|
|
// Force the protocols to match if needed.
|
|
if ( isset( $imagePath['scheme'] ) && ( $imagePath['scheme'] !== $siteUrl['scheme'] ) ) {
|
|
$path = str_replace( $imagePath['scheme'], $siteUrl['scheme'], $path );
|
|
}
|
|
|
|
if ( ! $this->isValidAttachment( $path ) ) {
|
|
aioseo()->core->cache->update( $cacheName, 'none' );
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( 0 === strpos( $path, $uploadDirInfo['baseurl'] . '/' ) ) {
|
|
$path = substr( $path, strlen( $uploadDirInfo['baseurl'] . '/' ) );
|
|
}
|
|
|
|
$results = aioseo()->core->db->start( 'postmeta' )
|
|
->select( 'post_id' )
|
|
->where( 'meta_key', '_wp_attached_file' )
|
|
->where( 'meta_value', $path )
|
|
->limit( 1 )
|
|
->run()
|
|
->result();
|
|
|
|
if ( empty( $results[0]->post_id ) ) {
|
|
aioseo()->core->cache->update( $cacheName, 'none' );
|
|
|
|
return false;
|
|
}
|
|
|
|
aioseo()->core->cache->update( $cacheName, $results[0]->post_id );
|
|
|
|
return $results[0]->post_id;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the request is a non-legacy REST API request.
|
|
* This function was copied from WooCommerce and improved.
|
|
*
|
|
* @since 4.1.2
|
|
*
|
|
* @return bool True if this is a REST API request.
|
|
*/
|
|
public function isRestApiRequest() {
|
|
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
|
return true;
|
|
}
|
|
|
|
global $wp_rewrite; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
|
|
if ( empty( $wp_rewrite ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
return false;
|
|
}
|
|
|
|
if ( empty( $_SERVER['REQUEST_URI'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$restUrl = wp_parse_url( get_rest_url() );
|
|
$restUrl = $restUrl['path'] . ( ! empty( $restUrl['query'] ) ? '?' . $restUrl['query'] : '' );
|
|
|
|
$isRestApiRequest = ( 0 === strpos( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $restUrl ) );
|
|
|
|
return apply_filters( 'aioseo_is_rest_api_request', $isRestApiRequest );
|
|
}
|
|
|
|
/**
|
|
* Checks whether the current request is an AJAX, CRON or REST request.
|
|
*
|
|
* @since 4.1.3
|
|
*
|
|
* @return bool Whether the request is an AJAX, CRON or REST request.
|
|
*/
|
|
public function isAjaxCronRestRequest() {
|
|
return wp_doing_ajax() || wp_doing_cron() || $this->isRestApiRequest();
|
|
}
|
|
|
|
/**
|
|
* Check if we are in the middle of a WP-CLI call.
|
|
*
|
|
* @since 4.2.8
|
|
*
|
|
* @return bool True if we are in the WP_CLI context.
|
|
*/
|
|
public function isDoingWpCli() {
|
|
return defined( 'WP_CLI' ) && WP_CLI;
|
|
}
|
|
|
|
/**
|
|
* Checks whether we're on the given screen.
|
|
*
|
|
* @since 4.0.7
|
|
* @version 4.3.1
|
|
*
|
|
* @param string $screenName The screen name.
|
|
* @param string $comparison Check as a prefix.
|
|
* @return bool Whether we're on the given screen.
|
|
*/
|
|
public function isScreenBase( $screenName, $comparison = '' ) {
|
|
$screen = $this->getCurrentScreen();
|
|
if ( ! $screen || ! isset( $screen->base ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( 'prefix' === $comparison ) {
|
|
return 0 === stripos( $screen->base, $screenName );
|
|
}
|
|
|
|
return $screen->base === $screenName;
|
|
}
|
|
|
|
/**
|
|
* Returns if current screen is of a post type
|
|
*
|
|
* @since 4.0.17
|
|
*
|
|
* @param string $postType Post type slug
|
|
* @return bool True if the current screen is a post type screen.
|
|
*/
|
|
public function isScreenPostType( $postType ) {
|
|
$screen = $this->getCurrentScreen();
|
|
if ( ! $screen || ! isset( $screen->post_type ) ) {
|
|
return false;
|
|
}
|
|
|
|
return $screen->post_type === $postType;
|
|
}
|
|
|
|
/**
|
|
* Returns if current screen is a post list, optionaly of a post type.
|
|
*
|
|
* @since 4.2.4
|
|
*
|
|
* @param string $postType Post type slug.
|
|
* @return bool Is a post list.
|
|
*/
|
|
public function isScreenPostList( $postType = '' ) {
|
|
$screen = $this->getCurrentScreen();
|
|
if (
|
|
! $this->isScreenBase( 'edit' ) ||
|
|
empty( $screen->post_type )
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if ( ! empty( $postType ) && $screen->post_type !== $postType ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns if current screen is a post edit screen, optionaly of a post type.
|
|
*
|
|
* @since 4.2.4
|
|
*
|
|
* @param string $postType Post type slug.
|
|
* @return bool Is a post editing screen.
|
|
*/
|
|
public function isScreenPostEdit( $postType = '' ) {
|
|
$screen = $this->getCurrentScreen();
|
|
if (
|
|
! $this->isScreenBase( 'post' ) ||
|
|
empty( $screen->post_type )
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if ( ! empty( $postType ) && $screen->post_type !== $postType ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets current admin screen.
|
|
*
|
|
* @since 4.0.17
|
|
*
|
|
* @return false|\WP_Screen|null
|
|
*/
|
|
public function getCurrentScreen() {
|
|
if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
|
|
return false;
|
|
}
|
|
|
|
return get_current_screen();
|
|
}
|
|
|
|
/**
|
|
* Checks whether the current site is a multisite subdomain.
|
|
*
|
|
* @since 4.1.9
|
|
*
|
|
* @return bool Whether the current site is a subdomain.
|
|
*/
|
|
public function isSubdomain() {
|
|
if ( ! is_multisite() ) {
|
|
return false;
|
|
}
|
|
|
|
return apply_filters( 'aioseo_multisite_subdomain', is_subdomain_install() );
|
|
}
|
|
|
|
/**
|
|
* Returns if the current page is the login or register page.
|
|
*
|
|
* @since 4.2.1
|
|
*
|
|
* @return bool Login or register page.
|
|
*/
|
|
public function isWpLoginPage() {
|
|
// We can't sanitize the filename using sanitize_file_name() here because it will cause issues with custom login pages and certain plugins/themes where this function is not defined.
|
|
$self = ! empty( $_SERVER['PHP_SELF'] ) ? sanitize_text_field( wp_unslash( $_SERVER['PHP_SELF'] ) ) : ''; // phpcs:ignore HM.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
if ( preg_match( '/wp-login\.php$|wp-register\.php$/', (string) $self ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns which type of WordPress page we're seeing.
|
|
* It will only work if {@see \WP_Query::$queried_object} has been set.
|
|
*
|
|
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#filter-hierarchy
|
|
*
|
|
* @since 4.2.8
|
|
*
|
|
* @return string|null The template type or `null` if no match.
|
|
*/
|
|
public function getTemplateType() {
|
|
static $type = null;
|
|
|
|
if ( ! empty( $type ) ) {
|
|
return $type;
|
|
}
|
|
|
|
if ( is_attachment() ) {
|
|
$type = 'attachment';
|
|
} elseif ( is_single() ) {
|
|
$type = 'single';
|
|
} elseif (
|
|
is_page() ||
|
|
$this->isStaticPostsPage() ||
|
|
$this->isWooCommerceShopPage()
|
|
) {
|
|
$type = 'page';
|
|
} elseif ( is_author() ) { // An author page is an archive page, so it needs to be checked before `is_archive()`.
|
|
$type = 'author';
|
|
} elseif (
|
|
is_tax() ||
|
|
is_category() ||
|
|
is_tag()
|
|
) { // A taxonomy term page is an archive page, so it needs to be checked before `is_archive()`.
|
|
$type = 'taxonomy';
|
|
} elseif ( is_date() ) { // A date page is an archive page, so it needs to be checked before `is_archive()`.
|
|
$type = 'date';
|
|
} elseif ( is_archive() ) {
|
|
$type = 'archive';
|
|
} elseif ( is_home() && is_front_page() ) {
|
|
$type = 'dynamic_home';
|
|
} elseif ( is_search() ) {
|
|
$type = 'search';
|
|
}
|
|
|
|
return $type;
|
|
}
|
|
|
|
/**
|
|
* Sets the given post as the queried object of the main query.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @param \WP_Post|int $wpPost The post object or ID.
|
|
* @return void
|
|
*/
|
|
public function setWpQueryPost( $wpPost ) {
|
|
$wpPost = is_a( $wpPost, 'WP_Post' ) ? $wpPost : get_post( $wpPost );
|
|
// phpcs:disable Squiz.NamingConventions.ValidVariableName
|
|
global $wp_query, $post;
|
|
$this->originalQuery = $this->deepClone( $wp_query );
|
|
$this->originalPost = is_a( $post, 'WP_Post' ) ? $this->deepClone( $post ) : null;
|
|
|
|
$wp_query->posts = [ $wpPost ];
|
|
$wp_query->post = $wpPost;
|
|
$wp_query->post_count = 1;
|
|
$wp_query->get_queried_object_id = (int) $wpPost->ID;
|
|
$wp_query->queried_object = $wpPost;
|
|
$wp_query->is_single = true;
|
|
$wp_query->is_singular = true;
|
|
|
|
if ( 'page' === $wpPost->post_type ) {
|
|
$wp_query->is_page = true;
|
|
}
|
|
// phpcs:enable Squiz.NamingConventions.ValidVariableName
|
|
|
|
$post = $wpPost;
|
|
}
|
|
|
|
/**
|
|
* Sets the given term as the queried object of the main query.
|
|
*
|
|
* @since 4.9.3
|
|
*
|
|
* @param \WP_Term|int $wpTerm The term object or ID.
|
|
* @param string $taxonomy The taxonomy name. Required if $wpTerm is an ID.
|
|
* @return void
|
|
*/
|
|
public function setWpQueryTerm( $wpTerm, $taxonomy = '' ) {
|
|
$wpTerm = is_a( $wpTerm, 'WP_Term' ) ? $wpTerm : get_term( $wpTerm, $taxonomy );
|
|
if ( ! is_a( $wpTerm, 'WP_Term' ) ) {
|
|
return;
|
|
}
|
|
|
|
// phpcs:disable Squiz.NamingConventions.ValidVariableName
|
|
global $wp_query;
|
|
$this->originalQuery = $this->deepClone( $wp_query );
|
|
|
|
$wp_query->queried_object = $wpTerm;
|
|
$wp_query->queried_object_id = (int) $wpTerm->term_id;
|
|
$wp_query->is_archive = true;
|
|
|
|
// Set the appropriate taxonomy flag.
|
|
switch ( $wpTerm->taxonomy ) {
|
|
case 'category':
|
|
$wp_query->is_category = true;
|
|
break;
|
|
case 'post_tag':
|
|
$wp_query->is_tag = true;
|
|
break;
|
|
default:
|
|
$wp_query->is_tax = true;
|
|
break;
|
|
}
|
|
// phpcs:enable Squiz.NamingConventions.ValidVariableName
|
|
}
|
|
|
|
/**
|
|
* Restores the main query back to the original query.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function restoreWpQuery() {
|
|
global $wp_query, $post; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
if ( is_a( $this->originalQuery, 'WP_Query' ) ) {
|
|
// Loop over all properties and replace the ones that have changed.
|
|
// We want to avoid replacing the entire object because it can cause issues with other plugins.
|
|
foreach ( $this->originalQuery as $key => $value ) {
|
|
if ( $value !== $wp_query->{$key} ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
$wp_query->{$key} = $value; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( is_a( $this->originalPost, 'WP_Post' ) ) {
|
|
foreach ( $this->originalPost as $key => $value ) {
|
|
if ( $value !== $post->{$key} ) {
|
|
$post->{$key} = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->originalQuery = null;
|
|
$this->originalPost = null;
|
|
}
|
|
|
|
/**
|
|
* Gets the list of theme features.
|
|
*
|
|
* @since 4.4.9
|
|
*
|
|
* @return array List of theme features.
|
|
*/
|
|
public function getThemeFeatures() {
|
|
global $_wp_theme_features; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
|
|
return isset( $_wp_theme_features ) && is_array( $_wp_theme_features ) ? $_wp_theme_features : []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
|
}
|
|
|
|
/**
|
|
* Gets the active theme version.
|
|
*
|
|
* @since 4.9.6
|
|
*
|
|
* @param bool $parent Whether to return the parent theme's version.
|
|
* @return string|null The theme version, or null if parent requested but not found.
|
|
*/
|
|
public function getThemeVersion( $parent = false ) {
|
|
$theme = wp_get_theme();
|
|
|
|
if ( $parent ) {
|
|
return ( is_child_theme() && $theme->parent() ) ? $theme->parent()->version : null;
|
|
}
|
|
|
|
return $theme->version;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the active theme is a block-based theme or not.
|
|
*
|
|
* @since 4.5.3
|
|
*
|
|
* @return bool Whether the active theme is a block-based theme or not.
|
|
*/
|
|
public function isBlockTheme() {
|
|
if ( function_exists( 'wp_is_block_theme' ) ) {
|
|
return wp_is_block_theme(); // phpcs:ignore AIOSEO.WpFunctionUse.NewFunctions.wp_is_block_themeFound, @wp-since ignore
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the website name.
|
|
*
|
|
* @since 4.6.1
|
|
*
|
|
* @return string The website name.
|
|
*/
|
|
public function getWebsiteName() {
|
|
return aioseo()->options->searchAppearance->global->schema->websiteName
|
|
? aioseo()->tags->replaceTags( aioseo()->options->searchAppearance->global->schema->websiteName )
|
|
: aioseo()->helpers->decodeHtmlEntities( get_bloginfo( 'name' ) );
|
|
}
|
|
|
|
/**
|
|
* Polyfill for {@see wp_attachment_is()} since it uses `str_starts_with()` available only in PHP 8+ or WP 5.9+.
|
|
*
|
|
* @since 4.8.5
|
|
*
|
|
* @param string $type Attachment type. Accepts `image`, `audio`, `video`, or a file extension.
|
|
* @param int|\WP_Post $post Optional. Attachment ID or object. Default is global $post.
|
|
* @return bool True if an accepted type or a matching file extension, false otherwise.
|
|
*/
|
|
public function attachmentIs( $type, $post = null ) {
|
|
$post = get_post( $post );
|
|
$file = $post ? get_attached_file( $post->ID ) : false;
|
|
if ( ! $type || ! $post || ! $file ) {
|
|
return false;
|
|
}
|
|
|
|
if ( false !== stripos( $post->post_mime_type, $type . '/' ) ) {
|
|
return true;
|
|
}
|
|
|
|
$check = wp_check_filetype( $file );
|
|
if ( empty( $check['ext'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$ext = strtolower( $check['ext'] );
|
|
|
|
if ( ! in_array( $type, [ 'image', 'audio', 'video' ], true ) ) {
|
|
return strtolower( $type ) === $ext;
|
|
}
|
|
|
|
$extensionMap = [
|
|
'image' => [ 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif', 'heic' ],
|
|
'audio' => wp_get_audio_extensions(),
|
|
'video' => wp_get_video_extensions()
|
|
];
|
|
|
|
return in_array( $ext, $extensionMap[ $type ] ?? [], true );
|
|
}
|
|
} |