plugin updates

This commit is contained in:
Tony Volpe
2024-06-17 15:33:26 -04:00
parent 3751a5a1a6
commit e4e274a9a7
2674 changed files with 0 additions and 507851 deletions

View File

@@ -1,66 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use wpdb;
/**
* The AIOSEO Helper.
*/
class Aioseo_Helper {
/**
* The WordPress database instance.
*
* @var wpdb
*/
protected $wpdb;
/**
* The wpdb helper.
*
* @var Wpdb_Helper
*/
protected $wpdb_helper;
/**
* Class constructor.
*
* @param wpdb $wpdb The WordPress database instance.
* @param Wpdb_Helper $wpdb_helper The wpdb helper.
*/
public function __construct(
wpdb $wpdb,
Wpdb_Helper $wpdb_helper
) {
$this->wpdb = $wpdb;
$this->wpdb_helper = $wpdb_helper;
}
/**
* Retrieves the AIOSEO table name along with the db prefix.
*
* @return string The AIOSEO table name along with the db prefix.
*/
public function get_table() {
return $this->wpdb->prefix . 'aioseo_posts';
}
/**
* Determines if the AIOSEO database table exists.
*
* @return bool True if the table is found.
*/
public function aioseo_exists() {
return $this->wpdb_helper->table_exists( $this->get_table() ) === true;
}
/**
* Retrieves the option where the global settings exist.
*
* @return array The option where the global settings exist.
*/
public function get_global_option() {
return \json_decode( \get_option( 'aioseo_options', '' ), true );
}
}

View File

@@ -1,98 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for author archives.
*/
class Asset_Helper {
/**
* Recursively retrieves all dependency urls of a given handle.
*
* @param string $handle The handle.
*
* @return string[] All dependency urls of the given handle.
*/
public function get_dependency_urls_by_handle( $handle ) {
$urls = [];
foreach ( $this->get_dependency_handles( $handle ) as $other_handle ) {
$urls[ $other_handle ] = $this->get_asset_url( $other_handle );
}
return $urls;
}
/**
* Recursively retrieves all dependencies of a given handle.
*
* @param string $handle The handle.
*
* @return string[] All dependencies of the given handle.
*/
public function get_dependency_handles( $handle ) {
$scripts = \wp_scripts();
if ( ! isset( $scripts->registered[ $handle ] ) ) {
return [];
}
$obj = $scripts->registered[ $handle ];
$deps = $obj->deps;
foreach ( $obj->deps as $other_handle ) {
$nested_deps = $this->get_dependency_handles( $other_handle );
if ( ! $nested_deps ) {
continue;
}
// Place nested dependencies before primary dependencies, they need to be loaded first.
$deps = \array_merge( $nested_deps, $deps );
}
// Array unique keeps the first of each element so dependencies will be as early as they're required.
return \array_values( \array_unique( $deps ) );
}
/**
* Gets the URL of a given asset.
*
* This logic is copied from WP_Scripts::do_item as unfortunately that logic is not properly isolated.
*
* @param string $handle The handle of the asset.
*
* @return string|false The URL of the asset or false if the asset does not exist.
*/
public function get_asset_url( $handle ) {
$scripts = \wp_scripts();
if ( ! isset( $scripts->registered[ $handle ] ) ) {
return false;
}
$obj = $scripts->registered[ $handle ];
if ( $obj->ver === null ) {
$ver = '';
}
else {
$ver = ( $obj->ver ) ? $obj->ver : $scripts->default_version;
}
if ( isset( $scripts->args[ $handle ] ) ) {
$ver = ( $ver ) ? $ver . '&amp;' . $scripts->args[ $handle ] : $scripts->args[ $handle ];
}
$src = $obj->src;
if ( ! \preg_match( '|^(https?:)?//|', $src ) && ! ( $scripts->content_url && \strpos( $src, $scripts->content_url ) === 0 ) ) {
$src = $scripts->base_url . $src;
}
if ( ! empty( $ver ) ) {
$src = \add_query_arg( 'ver', $ver, $src );
}
/** This filter is documented in wp-includes/class.wp-scripts.php */
return \esc_url( \apply_filters( 'script_loader_src', $src, $handle ) );
}
}

View File

@@ -1,73 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\Lib\Model;
/**
* A helper object for the cleanup of attachments.
*/
class Attachment_Cleanup_Helper {
/**
* Removes all indexables for attachments.
*
* @param bool $suppress_errors Whether to suppress db errors when running the cleanup query.
*
* @return void
*/
public function remove_attachment_indexables( $suppress_errors ) {
global $wpdb;
if ( $suppress_errors ) {
// If migrations haven't been completed successfully the following may give false errors. So suppress them.
$show_errors = $wpdb->show_errors;
$wpdb->show_errors = false;
}
$indexable_table = Model::get_table_name( 'Indexable' );
$delete_query = "DELETE FROM $indexable_table WHERE object_type = 'post' AND object_sub_type = 'attachment'";
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
$wpdb->query( $delete_query );
// phpcs:enable
if ( $suppress_errors ) {
$wpdb->show_errors = $show_errors;
}
}
/**
* Cleans all attachment links in the links table from target indexable ids.
*
* @param bool $suppress_errors Whether to suppress db errors when running the cleanup query.
*
* @return void
*/
public function clean_attachment_links_from_target_indexable_ids( $suppress_errors ) {
global $wpdb;
if ( $suppress_errors ) {
// If migrations haven't been completed successfully the following may give false errors. So suppress them.
$show_errors = $wpdb->show_errors;
$wpdb->show_errors = false;
}
$links_table = Model::get_table_name( 'SEO_Links' );
$query = "UPDATE $links_table SET target_indexable_id = NULL WHERE type = 'image-in'";
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
$wpdb->query( $query );
// phpcs:enable
if ( $suppress_errors ) {
$wpdb->show_errors = $show_errors;
}
}
}

View File

@@ -1,182 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WP_Query;
use Yoast\WP\Lib\Model;
/**
* A helper object for author archives.
*/
class Author_Archive_Helper {
/**
* The options helper.
*
* @var Options_Helper
*/
private $options_helper;
/**
* The post type helper.
*
* @var Post_Type_Helper
*/
private $post_type_helper;
/**
* Creates a new author archive helper.
*
* @param Options_Helper $options_helper The options helper.
* @param Post_Type_Helper $post_type_helper The post type helper.
*/
public function __construct(
Options_Helper $options_helper,
Post_Type_Helper $post_type_helper
) {
$this->options_helper = $options_helper;
$this->post_type_helper = $post_type_helper;
}
/**
* Gets the array of post types that are shown on an author's archive.
*
* @return array The post types that are shown on an author's archive.
*/
public function get_author_archive_post_types() {
/**
* Filters the array of post types that are shown on an author's archive.
*
* @param array $args The post types that are shown on an author archive.
*/
return \apply_filters( 'wpseo_author_archive_post_types', [ 'post' ] );
}
/**
* Returns whether the author has at least one public post.
*
* @param int $author_id The author ID.
*
* @return bool|null Whether the author has at least one public post.
*/
public function author_has_public_posts( $author_id ) {
// First check if the author has at least one public post.
$has_public_post = $this->author_has_a_public_post( $author_id );
if ( $has_public_post ) {
return true;
}
// Then check if the author has at least one post where the status is the same as the global setting.
$has_public_post_depending_on_the_global_setting = $this->author_has_a_post_with_is_public_null( $author_id );
if ( $has_public_post_depending_on_the_global_setting ) {
return null;
}
return false;
}
/**
* Returns whether the author has at least one public post.
*
* **Note**: It uses WP_Query to determine the number of posts,
* not the indexables table.
*
* @param string $user_id The user ID.
*
* @return bool Whether the author has at least one public post.
*/
public function author_has_public_posts_wp( $user_id ) {
$post_types = \array_intersect( $this->get_author_archive_post_types(), $this->post_type_helper->get_indexable_post_types() );
$public_post_stati = \array_values( \array_filter( \get_post_stati(), 'is_post_status_viewable' ) );
$args = [
'post_type' => $post_types,
'post_status' => $public_post_stati,
'author' => $user_id,
'update_post_term_cache' => false,
'update_post_meta_cache' => false,
'no_found_rows' => true,
'fields' => 'ids',
'posts_per_page' => 1,
];
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
return true;
}
return false;
}
/**
* Checks whether author archives are disabled.
*
* @return bool `true` if author archives are disabled, `false` if not.
*/
public function are_disabled() {
return $this->options_helper->get( 'disable-author' );
}
/**
* Returns whether the author has at least one public post.
*
* @codeCoverageIgnore It looks for the first ID through the ORM and converts it to a boolean.
*
* @param int $author_id The author ID.
*
* @return bool Whether the author has at least one public post.
*/
protected function author_has_a_public_post( $author_id ) {
$cache_key = 'author_has_a_public_post_' . $author_id;
$indexable_exists = \wp_cache_get( $cache_key );
if ( $indexable_exists === false ) {
$indexable_exists = Model::of_type( 'Indexable' )
->select( 'id' )
->where( 'object_type', 'post' )
->where_in( 'object_sub_type', $this->get_author_archive_post_types() )
->where( 'author_id', $author_id )
->where( 'is_public', 1 )
->find_one();
if ( $indexable_exists === false ) {
// Cache no results to prevent full table scanning on authors with no public posts.
\wp_cache_set( $cache_key, 0, '', \wp_rand( ( 2 * \HOUR_IN_SECONDS ), ( 4 * \HOUR_IN_SECONDS ) ) );
}
}
return (bool) $indexable_exists;
}
/**
* Returns whether the author has at least one post with the is public null.
*
* @codeCoverageIgnore It looks for the first ID through the ORM and converts it to a boolean.
*
* @param int $author_id The author ID.
*
* @return bool Whether the author has at least one post with the is public null.
*/
protected function author_has_a_post_with_is_public_null( $author_id ) {
$cache_key = 'author_has_a_post_with_is_public_null_' . $author_id;
$indexable_exists = \wp_cache_get( $cache_key );
if ( $indexable_exists === false ) {
$indexable_exists = Model::of_type( 'Indexable' )
->select( 'id' )
->where( 'object_type', 'post' )
->where_in( 'object_sub_type', $this->get_author_archive_post_types() )
->where( 'author_id', $author_id )
->where_null( 'is_public' )
->find_one();
if ( $indexable_exists === false ) {
// Cache no results to prevent full table scanning on authors with no is public null posts.
\wp_cache_set( $cache_key, 0, '', \wp_rand( ( 2 * \HOUR_IN_SECONDS ), ( 4 * \HOUR_IN_SECONDS ) ) );
}
}
return (bool) $indexable_exists;
}
}

View File

@@ -1,100 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WP_Block_Parser_Block;
/**
* A helper object for blocks.
*/
class Blocks_Helper {
/**
* Holds the Post_Helper instance.
*
* @var Post_Helper
*/
private $post;
/**
* Constructs a Blocks_Helper instance.
*
* @codeCoverageIgnore It handles dependencies.
*
* @param Post_Helper $post The post helper.
*/
public function __construct( Post_Helper $post ) {
$this->post = $post;
}
/**
* Returns all blocks in a given post.
*
* @param int $post_id The post id.
*
* @return array The blocks in a block-type => WP_Block_Parser_Block[] format.
*/
public function get_all_blocks_from_post( $post_id ) {
if ( ! $this->has_blocks_support() ) {
return [];
}
$post = $this->post->get_post( $post_id );
return $this->get_all_blocks_from_content( $post->post_content );
}
/**
* Returns all blocks in a given content.
*
* @param string $content The content.
*
* @return array The blocks in a block-type => WP_Block_Parser_Block[] format.
*/
public function get_all_blocks_from_content( $content ) {
if ( ! $this->has_blocks_support() ) {
return [];
}
$collection = [];
$blocks = \parse_blocks( $content );
return $this->collect_blocks( $blocks, $collection );
}
/**
* Checks if the installation has blocks support.
*
* @codeCoverageIgnore It only checks if a WordPress function exists.
*
* @return bool True when function parse_blocks exists.
*/
protected function has_blocks_support() {
return \function_exists( 'parse_blocks' );
}
/**
* Collects an array of blocks into an organised collection.
*
* @param WP_Block_Parser_Block[] $blocks The blocks.
* @param array $collection The collection.
*
* @return array The blocks in a block-type => WP_Block_Parser_Block[] format.
*/
private function collect_blocks( $blocks, $collection ) {
foreach ( $blocks as $block ) {
if ( empty( $block['blockName'] ) ) {
continue;
}
if ( ! isset( $collection[ $block['blockName'] ] ) || ! \is_array( $collection[ $block['blockName'] ] ) ) {
$collection[ $block['blockName'] ] = [];
}
$collection[ $block['blockName'] ][] = $block;
if ( isset( $block['innerBlocks'] ) ) {
$collection = $this->collect_blocks( $block['innerBlocks'], $collection );
}
}
return $collection;
}
}

View File

@@ -1,86 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for user capabilities.
*/
class Capability_Helper {
/**
* Checks if the user has at least one of the proper capabilities.
*
* @param string $capability Capability to check.
*
* @return bool True if the user has at least one of the proper rights.
*/
public function current_user_can( $capability ) {
if ( $capability === 'wpseo_manage_options' ) {
return \current_user_can( $capability );
}
return $this->has_any( [ 'wpseo_manage_options', $capability ] );
}
/**
* Retrieves the users that have the specified capability.
*
* @param string $capability The name of the capability.
*
* @return array The users that have the capability.
*/
public function get_applicable_users( $capability ) {
$applicable_roles = $this->get_applicable_roles( $capability );
if ( $applicable_roles === [] ) {
return [];
}
return \get_users( [ 'role__in' => $applicable_roles ] );
}
/**
* Retrieves the roles that have the specified capability.
*
* @param string $capability The name of the capability.
*
* @return array The names of the roles that have the capability.
*/
public function get_applicable_roles( $capability ) {
$roles = \wp_roles();
$role_names = $roles->get_names();
$applicable_roles = [];
foreach ( \array_keys( $role_names ) as $role_name ) {
$role = $roles->get_role( $role_name );
if ( ! $role ) {
continue;
}
// Add role if it has the capability.
if ( \array_key_exists( $capability, $role->capabilities ) && $role->capabilities[ $capability ] === true ) {
$applicable_roles[] = $role_name;
}
}
return $applicable_roles;
}
/**
* Checks if the current user has at least one of the supplied capabilities.
*
* @param array $capabilities Capabilities to check against.
*
* @return bool True if the user has at least one capability.
*/
private function has_any( array $capabilities ) {
foreach ( $capabilities as $capability ) {
if ( \current_user_can( $capability ) ) {
return true;
}
}
return false;
}
}

View File

@@ -1,314 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* Class Crawl_Cleanup_Helper.
*
* Used by the Crawl_Cleanup_Permalinks class.
*/
class Crawl_Cleanup_Helper {
/**
* The current page helper
*
* @var Current_Page_Helper
*/
private $current_page_helper;
/**
* The options helper.
*
* @var Options_Helper
*/
private $options_helper;
/**
* The URL helper.
*
* @var Url_Helper
*/
private $url_helper;
/**
* The Redirect Helper.
*
* @var Redirect_Helper
*/
private $redirect_helper;
/**
* Crawl Cleanup Basic integration constructor.
*
* @param Current_Page_Helper $current_page_helper The current page helper.
* @param Options_Helper $options_helper The option helper.
* @param Url_Helper $url_helper The URL helper.
* @param Redirect_Helper $redirect_helper The Redirect Helper.
*/
public function __construct(
Current_Page_Helper $current_page_helper,
Options_Helper $options_helper,
Url_Helper $url_helper,
Redirect_Helper $redirect_helper
) {
$this->current_page_helper = $current_page_helper;
$this->options_helper = $options_helper;
$this->url_helper = $url_helper;
$this->redirect_helper = $redirect_helper;
}
/**
* Checks if the current URL is not robots, sitemap, empty or user is logged in.
*
* @return bool True if the current URL is a valid URL.
*/
public function should_avoid_redirect() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We're not processing anything yet...
if ( \is_robots() || \get_query_var( 'sitemap' ) || empty( $_GET ) || \is_user_logged_in() ) {
return true;
}
return false;
}
/**
* Returns the list of the allowed extra vars.
*
* @return array The list of the allowed extra vars.
*/
public function get_allowed_extravars() {
$default_allowed_extravars = [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_term',
'utm_content',
'gclid',
'gtm_debug',
];
/**
* Filter: 'Yoast\WP\SEO\allowlist_permalink_vars' - Allows plugins to register their own variables not to clean.
*
* @since 19.2.0
*
* @param array $allowed_extravars The list of the allowed vars (empty by default).
*/
$allowed_extravars = \apply_filters( 'Yoast\WP\SEO\allowlist_permalink_vars', $default_allowed_extravars );
$clean_permalinks_extra_variables = $this->options_helper->get( 'clean_permalinks_extra_variables' );
if ( $clean_permalinks_extra_variables !== '' ) {
$allowed_extravars = \array_merge( $allowed_extravars, \explode( ',', $clean_permalinks_extra_variables ) );
}
return $allowed_extravars;
}
/**
* Gets the allowed query vars from the current URL.
*
* @param string $current_url The current URL.
* @return array is_allowed and allowed_query.
*/
public function allowed_params( $current_url ) {
// This is a Premium plugin-only function: Allows plugins to register their own variables not to clean.
$allowed_extravars = $this->get_allowed_extravars();
$allowed_query = [];
$parsed_url = \wp_parse_url( $current_url, \PHP_URL_QUERY );
$query = $this->url_helper->parse_str_params( $parsed_url );
if ( ! empty( $allowed_extravars ) ) {
foreach ( $allowed_extravars as $get ) {
$get = \trim( $get );
if ( isset( $query[ $get ] ) ) {
$allowed_query[ $get ] = \rawurlencode_deep( $query[ $get ] );
unset( $query[ $get ] );
}
}
}
return [
'query' => $query,
'allowed_query' => $allowed_query,
];
}
/**
* Returns the proper URL for singular pages.
*
* @return string The proper URL.
*/
public function singular_url() {
global $post;
$proper_url = \get_permalink( $post->ID );
$page = \get_query_var( 'page' );
if ( $page && $page !== 1 ) {
$the_post = \get_post( $post->ID );
$page_count = \substr_count( $the_post->post_content, '<!--nextpage-->' );
$proper_url = \user_trailingslashit( \trailingslashit( $proper_url ) . $page );
if ( $page > ( $page_count + 1 ) ) {
$proper_url = \user_trailingslashit( \trailingslashit( $proper_url ) . ( $page_count + 1 ) );
}
}
// Fix reply to comment links, whoever decided this should be a GET variable?
// phpcs:ignore WordPress.Security -- We know this is scary.
if ( isset( $_SERVER['REQUEST_URI'] ) && \preg_match( '`(\?replytocom=[^&]+)`', \sanitize_text_field( $_SERVER['REQUEST_URI'] ), $matches ) ) {
$proper_url .= \str_replace( '?replytocom=', '#comment-', $matches[0] );
}
unset( $matches );
return $proper_url;
}
/**
* Returns the proper URL for front page.
*
* @return string The proper URL.
*/
public function front_page_url() {
if ( $this->current_page_helper->is_home_posts_page() ) {
return \home_url( '/' );
}
if ( $this->current_page_helper->is_home_static_page() ) {
return \get_permalink( $GLOBALS['post']->ID );
}
return '';
}
/**
* Returns the proper URL for 404 page.
*
* @param string $current_url The current URL.
* @return string The proper URL.
*/
public function page_not_found_url( $current_url ) {
if ( ! \is_multisite() || \is_subdomain_install() || ! \is_main_site() ) {
return '';
}
if ( $current_url !== \home_url() . '/blog/' && $current_url !== \home_url() . '/blog' ) {
return '';
}
if ( $this->current_page_helper->is_home_static_page() ) {
return \get_permalink( \get_option( 'page_for_posts' ) );
}
return \home_url();
}
/**
* Returns the proper URL for taxonomy page.
*
* @return string The proper URL.
*/
public function taxonomy_url() {
global $wp_query;
$term = $wp_query->get_queried_object();
if ( \is_feed() ) {
return \get_term_feed_link( $term->term_id, $term->taxonomy );
}
return \get_term_link( $term, $term->taxonomy );
}
/**
* Returns the proper URL for search page.
*
* @return string The proper URL.
*/
public function search_url() {
$s = \get_search_query();
return \home_url() . '/?s=' . \rawurlencode( $s );
}
/**
* Returns the proper URL for url with page param.
*
* @param string $proper_url The proper URL.
* @return string The proper URL.
*/
public function query_var_page_url( $proper_url ) {
global $wp_query;
if ( \is_search( $proper_url ) ) {
return \home_url() . '/page/' . $wp_query->query_vars['paged'] . '/?s=' . \rawurlencode( \get_search_query() );
}
return \user_trailingslashit( \trailingslashit( $proper_url ) . 'page/' . $wp_query->query_vars['paged'] );
}
/**
* Returns true if query is with page param.
*
* @param string $proper_url The proper URL.
* @return bool is query with page param.
*/
public function is_query_var_page( $proper_url ) {
global $wp_query;
if ( empty( $proper_url ) || $wp_query->query_vars['paged'] === 0 || $wp_query->post_count === 0 ) {
return false;
}
return true;
}
/**
* Redirects clean permalink.
*
* @param string $proper_url The proper URL.
* @return void
*/
public function do_clean_redirect( $proper_url ) {
$this->redirect_helper->set_header( 'Content-Type: redirect', true );
$this->redirect_helper->remove_header( 'Content-Type' );
$this->redirect_helper->remove_header( 'Last-Modified' );
$this->redirect_helper->remove_header( 'X-Pingback' );
$message = \sprintf(
/* translators: %1$s: Yoast SEO */
\__( '%1$s: unregistered URL parameter removed. See %2$s', 'wordpress-seo' ),
'Yoast SEO',
'https://yoa.st/advanced-crawl-settings'
);
$this->redirect_helper->do_safe_redirect( $proper_url, 301, $message );
}
/**
* Gets the type of URL.
*
* @return string The type of URL.
*/
public function get_url_type() {
if ( \is_singular() ) {
return 'singular_url';
}
if ( \is_front_page() ) {
return 'front_page_url';
}
if ( $this->current_page_helper->is_posts_page() ) {
return 'page_for_posts_url';
}
if ( \is_category() || \is_tag() || \is_tax() ) {
return 'taxonomy_url';
}
if ( \is_search() ) {
return 'search_url';
}
if ( \is_404() ) {
return 'page_not_found_url';
}
return '';
}
/**
* Returns the proper URL for posts page.
*
* @return string The proper URL.
*/
public function page_for_posts_url() {
return \get_permalink( \get_option( 'page_for_posts' ) );
}
}

View File

@@ -1,33 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* Helper class for getting information about the installed cURL version.
*/
class Curl_Helper {
/**
* Checks is cURL is installed.
*
* @return bool Returns true if cURL is installed.
*/
public function is_installed() {
return \function_exists( 'curl_version' );
}
/**
* Returns the currently installed cURL version.
*
* @return string|null Returns a string containing the cURL version, or null if cURL is not installed.
*/
public function get_version() {
$version = \curl_version();
if ( ! isset( $version['version'] ) ) {
return null;
}
return $version['version'];
}
}

View File

@@ -1,489 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WP_Post;
use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper;
/**
* A helper object for WordPress posts.
*/
class Current_Page_Helper {
/**
* The WP Query wrapper.
*
* @var WP_Query_Wrapper
*/
private $wp_query_wrapper;
/**
* Current_Page_Helper constructor.
*
* @codeCoverageIgnore It only sets dependencies.
*
* @param WP_Query_Wrapper $wp_query_wrapper The wrapper for WP_Query.
*/
public function __construct(
WP_Query_Wrapper $wp_query_wrapper
) {
$this->wp_query_wrapper = $wp_query_wrapper;
}
/**
* Returns the page type for the current request.
*
* @codeCoverageIgnore It just depends on other functions for its result.
*
* @return string Page type.
*/
public function get_page_type() {
switch ( true ) {
case $this->is_search_result():
return 'Search_Result_Page';
case $this->is_static_posts_page():
return 'Static_Posts_Page';
case $this->is_home_static_page():
return 'Static_Home_Page';
case $this->is_home_posts_page():
return 'Home_Page';
case $this->is_simple_page():
return 'Post_Type';
case $this->is_post_type_archive():
return 'Post_Type_Archive';
case $this->is_term_archive():
return 'Term_Archive';
case $this->is_author_archive():
return 'Author_Archive';
case $this->is_date_archive():
return 'Date_Archive';
case $this->is_404():
return 'Error_Page';
}
return 'Fallback';
}
/**
* Checks if the currently opened page is a simple page.
*
* @return bool Whether the currently opened page is a simple page.
*/
public function is_simple_page() {
return $this->get_simple_page_id() > 0;
}
/**
* Returns the id of the currently opened page.
*
* @return int The id of the currently opened page.
*/
public function get_simple_page_id() {
if ( \is_singular() ) {
return \get_the_ID();
}
if ( $this->is_posts_page() ) {
return \get_option( 'page_for_posts' );
}
/**
* Filter: Allow changing the default page id.
*
* @param int $page_id The default page id.
*/
return \apply_filters( 'wpseo_frontend_page_type_simple_page_id', 0 );
}
/**
* Returns the id of the currently opened author archive.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return int The id of the currently opened author archive.
*/
public function get_author_id() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->get( 'author' );
}
/**
* Returns the id of the front page.
*
* @return int The id of the front page. 0 if the front page is not a static page.
*/
public function get_front_page_id() {
if ( \get_option( 'show_on_front' ) !== 'page' ) {
return 0;
}
return (int) \get_option( 'page_on_front' );
}
/**
* Returns the id of the currently opened term archive.
*
* @return int The id of the currently opened term archive.
*/
public function get_term_id() {
$wp_query = $this->wp_query_wrapper->get_main_query();
if ( $wp_query->is_tax() || $wp_query->is_tag() || $wp_query->is_category() ) {
$queried_object = $wp_query->get_queried_object();
if ( $queried_object && ! \is_wp_error( $queried_object ) ) {
return $queried_object->term_id;
}
}
return 0;
}
/**
* Returns the post type of the main query.
*
* @return string The post type of the main query.
*/
public function get_queried_post_type() {
$wp_query = $this->wp_query_wrapper->get_main_query();
$post_type = $wp_query->get( 'post_type' );
if ( \is_array( $post_type ) ) {
$post_type = \reset( $post_type );
}
return $post_type;
}
/**
* Returns the permalink of the currently opened date archive.
* If the permalink was cached, it returns this permalink.
* If not, we call another function to get the permalink through wp_query.
*
* @return string The permalink of the currently opened date archive.
*/
public function get_date_archive_permalink() {
static $date_archive_permalink;
if ( isset( $date_archive_permalink ) ) {
return $date_archive_permalink;
}
$date_archive_permalink = $this->get_non_cached_date_archive_permalink();
return $date_archive_permalink;
}
/**
* Determine whether this is the homepage and shows posts.
*
* @return bool Whether or not the current page is the homepage that displays posts.
*/
public function is_home_posts_page() {
$wp_query = $this->wp_query_wrapper->get_main_query();
if ( ! $wp_query->is_home() ) {
return false;
}
/*
* Whether the static page's `Homepage` option is actually not set to a page.
* Otherwise WordPress proceeds to handle the homepage as a `Your latest posts` page.
*/
if ( (int) \get_option( 'page_on_front' ) === 0 ) {
return true;
}
return \get_option( 'show_on_front' ) === 'posts';
}
/**
* Determine whether this is the static frontpage.
*
* @return bool Whether or not the current page is a static frontpage.
*/
public function is_home_static_page() {
$wp_query = $this->wp_query_wrapper->get_main_query();
if ( ! $wp_query->is_front_page() ) {
return false;
}
if ( \get_option( 'show_on_front' ) !== 'page' ) {
return false;
}
return $wp_query->is_page( \get_option( 'page_on_front' ) );
}
/**
* Determine whether this is the static posts page.
*
* @return bool Whether or not the current page is a static posts page.
*/
public function is_static_posts_page() {
$wp_query = $this->wp_query_wrapper->get_main_query();
$queried_object = $wp_query->get_queried_object();
return (
$wp_query->is_posts_page
&& \is_a( $queried_object, WP_Post::class )
&& $queried_object->post_type === 'page'
);
}
/**
* Determine whether this is the statically set posts page, when it's not the frontpage.
*
* @return bool Whether or not it's a non-frontpage, statically set posts page.
*/
public function is_posts_page() {
$wp_query = $this->wp_query_wrapper->get_main_query();
if ( ! $wp_query->is_home() ) {
return false;
}
return \get_option( 'show_on_front' ) === 'page';
}
/**
* Determine whether this is a post type archive.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether nor not the current page is a post type archive.
*/
public function is_post_type_archive() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_post_type_archive();
}
/**
* Determine whether this is a term archive.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether nor not the current page is a term archive.
*/
public function is_term_archive() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_tax || $wp_query->is_tag || $wp_query->is_category;
}
/**
* Determine whether this is an attachment page.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether nor not the current page is an attachment page.
*/
public function is_attachment() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_attachment;
}
/**
* Determine whether this is an author archive.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether nor not the current page is an author archive.
*/
public function is_author_archive() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_author();
}
/**
* Determine whether this is an date archive.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether nor not the current page is an date archive.
*/
public function is_date_archive() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_date();
}
/**
* Determine whether this is a search result.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether nor not the current page is a search result.
*/
public function is_search_result() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_search();
}
/**
* Determine whether this is a 404 page.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether nor not the current page is a 404 page.
*/
public function is_404() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_404();
}
/**
* Checks if the current page is the post format archive.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether or not the current page is the post format archive.
*/
public function is_post_format_archive() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_tax( 'post_format' );
}
/**
* Determine whether this page is an taxonomy archive page for multiple terms (url: /term-1,term2/).
*
* @return bool Whether or not the current page is an archive page for multiple terms.
*/
public function is_multiple_terms_page() {
if ( ! $this->is_term_archive() ) {
return false;
}
return $this->count_queried_terms() > 1;
}
/**
* Checks whether the current page is paged.
*
* @codeCoverageIgnore This method only calls a WordPress function.
*
* @return bool Whether the current page is paged.
*/
public function is_paged() {
return \is_paged();
}
/**
* Checks if the current page is the front page.
*
* @codeCoverageIgnore It wraps WordPress functionality.
*
* @return bool Whether or not the current page is the front page.
*/
public function is_front_page() {
$wp_query = $this->wp_query_wrapper->get_main_query();
return $wp_query->is_front_page();
}
/**
* Retrieves the current admin page.
*
* @codeCoverageIgnore It only wraps a global WordPress variable.
*
* @return string The current page.
*/
public function get_current_admin_page() {
global $pagenow;
return $pagenow;
}
/**
* Check if the current opened page is a Yoast SEO page.
*
* @return bool True when current page is a yoast seo plugin page.
*/
public function is_yoast_seo_page() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only using the variable in the strpos function.
$current_page = \wp_unslash( $_GET['page'] );
return \strpos( $current_page, 'wpseo_' ) === 0;
}
return false;
}
/**
* Returns the current Yoast SEO page.
* (E.g. the `page` query variable in the URL).
*
* @return string The current Yoast SEO page.
*/
public function get_current_yoast_seo_page() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
return \sanitize_text_field( \wp_unslash( $_GET['page'] ) );
}
return '';
}
/**
* Checks if the current global post is the privacy policy page.
*
* @return bool current global post is set as privacy page
*/
public function current_post_is_privacy_policy() {
global $post;
if ( ! isset( $post->ID ) ) {
return false;
}
return \intval( $post->ID ) === \intval( \get_option( 'wp_page_for_privacy_policy', false ) );
}
/**
* Returns the permalink of the currently opened date archive.
*
* @return string The permalink of the currently opened date archive.
*/
protected function get_non_cached_date_archive_permalink() {
$date_archive_permalink = '';
$wp_query = $this->wp_query_wrapper->get_main_query();
if ( $wp_query->is_day() ) {
$date_archive_permalink = \get_day_link( $wp_query->get( 'year' ), $wp_query->get( 'monthnum' ), $wp_query->get( 'day' ) );
}
if ( $wp_query->is_month() ) {
$date_archive_permalink = \get_month_link( $wp_query->get( 'year' ), $wp_query->get( 'monthnum' ) );
}
if ( $wp_query->is_year() ) {
$date_archive_permalink = \get_year_link( $wp_query->get( 'year' ) );
}
return $date_archive_permalink;
}
/**
* Counts the total amount of queried terms.
*
* @codeCoverageIgnore This relies too much on WordPress dependencies.
*
* @return int The amoumt of queried terms.
*/
protected function count_queried_terms() {
$wp_query = $this->wp_query_wrapper->get_main_query();
$term = $wp_query->get_queried_object();
$queried_terms = $wp_query->tax_query->queried_terms;
if ( \is_null( $term ) || empty( $queried_terms[ $term->taxonomy ]['terms'] ) ) {
return 0;
}
return \count( $queried_terms[ $term->taxonomy ]['terms'] );
}
}

View File

@@ -1,120 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use DateTime;
use DateTimeZone;
use Exception;
/**
* A helper object for dates.
*/
class Date_Helper {
/**
* Convert given date string to the W3C format.
*
* If $translate is true then the given date and format string will
* be passed to date_i18n() for translation.
*
* @param string $date Date string to convert.
* @param bool $translate Whether the return date should be translated. Default false.
*
* @return string Formatted date string.
*/
public function mysql_date_to_w3c_format( $date, $translate = false ) {
return \mysql2date( \DATE_W3C, $date, $translate );
}
/**
* Formats a given date in UTC TimeZone format.
*
* @param string $date String representing the date / time.
* @param string $format The format that the passed date should be in.
*
* @return string The formatted date.
*/
public function format( $date, $format = \DATE_W3C ) {
if ( ! \is_string( $date ) ) {
return $date;
}
$immutable_date = \date_create_immutable_from_format( 'Y-m-d H:i:s', $date, new DateTimeZone( 'UTC' ) );
if ( ! $immutable_date ) {
return $date;
}
return $immutable_date->format( $format );
}
/**
* Formats the given timestamp to the needed format.
*
* @param int $timestamp The timestamp to use for the formatting.
* @param string $format The format that the passed date should be in.
*
* @return string The formatted date.
*/
public function format_timestamp( $timestamp, $format = \DATE_W3C ) {
if ( ! \is_string( $timestamp ) && ! \is_int( $timestamp ) ) {
return $timestamp;
}
$immutable_date = \date_create_immutable_from_format( 'U', $timestamp, new DateTimeZone( 'UTC' ) );
if ( ! $immutable_date ) {
return $timestamp;
}
return $immutable_date->format( $format );
}
/**
* Formats a given date in UTC TimeZone format and translate it to the set language.
*
* @param string $date String representing the date / time.
* @param string $format The format that the passed date should be in.
*
* @return string The formatted and translated date.
*/
public function format_translated( $date, $format = \DATE_W3C ) {
return \date_i18n( $format, $this->format( $date, 'U' ) );
}
/**
* Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT).
*
* @return int The current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT).
*/
public function current_time() {
return \time();
}
/**
* Check if a string is a valid datetime.
*
* @param string $datetime String input to check as valid input for DateTime class.
*
* @return bool True when datetime is valid.
*/
public function is_valid_datetime( $datetime ) {
if ( $datetime === null ) {
/*
* While not "officially" supported, `null` will be handled as `"now"` until PHP 9.0.
* @link https://3v4l.org/tYp2k
*/
return true;
}
if ( \is_string( $datetime ) && \substr( $datetime, 0, 1 ) === '-' ) {
return false;
}
try {
return new DateTime( $datetime ) !== false;
} catch ( Exception $exception ) {
return false;
}
}
}

View File

@@ -1,33 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for site environment.
*/
class Environment_Helper {
/**
* Determines if the site is running on production.
*
* @return bool True if WordPress is currently running on production, false for all other environments.
*/
public function is_production_mode() {
// phpcs:ignore Yoast.NamingConventions.ValidHookName.WrongPrefix -- existing hook.
$override = \apply_filters( 'yoast_seo_development_mode', false );
if ( $override ) {
return true;
}
return $this->get_wp_environment() === 'production';
}
/**
* Determines on which environment WordPress is running.
*
* @return string The current WordPress environment.
*/
public function get_wp_environment() {
return \wp_get_environment_type();
}
}

View File

@@ -1,179 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper to determine the status of ftc and front-end related configuration.
*/
class First_Time_Configuration_Notice_Helper {
/**
* The options' helper.
*
* @var Options_Helper
*/
private $options_helper;
/**
* The indexing helper.
*
* @var Indexing_Helper
*/
private $indexing_helper;
/**
* Whether we show the alternate mesage.
*
* @var bool
*/
private $show_alternate_message;
/**
* First_Time_Configuration_Notice_Integration constructor.
*
* @param Options_Helper $options_helper The options helper.
* @param Indexing_Helper $indexing_helper The indexing helper.
*/
public function __construct(
Options_Helper $options_helper,
Indexing_Helper $indexing_helper
) {
$this->options_helper = $options_helper;
$this->indexing_helper = $indexing_helper;
$this->show_alternate_message = false;
}
/**
* Determines whether and where the "First-time SEO Configuration" admin notice should be displayed.
*
* @return bool Whether the "First-time SEO Configuration" admin notice should be displayed.
*/
public function should_display_first_time_configuration_notice() {
if ( ! $this->options_helper->get( 'dismiss_configuration_workout_notice', false ) === false ) {
return false;
}
if ( ! $this->on_wpseo_admin_page_or_dashboard() ) {
return false;
}
return $this->first_time_configuration_not_finished();
}
/**
* Gets the first time configuration title based on the show_alternate_message boolean
*
* @return string
*/
public function get_first_time_configuration_title() {
return ( ! $this->show_alternate_message ) ? \__( 'First-time SEO configuration', 'wordpress-seo' ) : \__( 'SEO configuration', 'wordpress-seo' );
}
/**
* Determines if the first time configuration is completely finished.
*
* @return bool
*/
public function first_time_configuration_not_finished() {
if ( ! $this->user_can_do_first_time_configuration() ) {
return false;
}
if ( $this->is_first_time_configuration_finished() ) {
return false;
}
if ( $this->options_helper->get( 'first_time_install', false ) !== false ) {
return ! $this->are_site_representation_name_and_logo_set() || $this->indexing_helper->get_unindexed_count() > 0;
}
if ( $this->indexing_helper->is_initial_indexing() === false ) {
return false;
}
if ( $this->indexing_helper->is_finished_indexables_indexing() === true ) {
return false;
}
$this->show_alternate_message = true;
return ! $this->are_site_representation_name_and_logo_set();
}
/**
* Whether the user can do the first-time configuration.
*
* @return bool Whether the current user can do the first-time configuration.
*/
private function user_can_do_first_time_configuration() {
return \current_user_can( 'wpseo_manage_options' );
}
/**
* Whether the user is currently visiting one of our admin pages or the WordPress dashboard.
*
* @return bool Whether the current page is a Yoast SEO admin page
*/
private function on_wpseo_admin_page_or_dashboard() {
$pagenow = $GLOBALS['pagenow'];
// Show on the WP Dashboard.
if ( $pagenow === 'index.php' ) {
return true;
}
$page_from_get = \filter_input( \INPUT_GET, 'page' );
// Show on Yoast SEO pages, with some exceptions.
if ( $pagenow === 'admin.php' && \strpos( $page_from_get, 'wpseo' ) === 0 ) {
$exceptions = [
'wpseo_installation_successful',
'wpseo_installation_successful_free',
];
if ( ! \in_array( $page_from_get, $exceptions, true ) ) {
return true;
}
}
return false;
}
/**
* Whether all steps of the first-time configuration have been finished.
*
* @return bool Whether the first-time configuration has been finished.
*/
private function is_first_time_configuration_finished() {
$configuration_finished_steps = $this->options_helper->get( 'configuration_finished_steps', [] );
return \count( $configuration_finished_steps ) === 3;
}
/**
* Whether the site representation name and logo have been set.
*
* @return bool Whether the site representation name and logo have been set.
*/
private function are_site_representation_name_and_logo_set() {
$company_or_person = $this->options_helper->get( 'company_or_person', '' );
if ( $company_or_person === '' ) {
return false;
}
if ( $company_or_person === 'company' ) {
return ! empty( $this->options_helper->get( 'company_name' ) ) && ! empty( $this->options_helper->get( 'company_logo', '' ) );
}
return ! empty( $this->options_helper->get( 'company_or_person_user_id' ) ) && ! empty( $this->options_helper->get( 'person_logo', '' ) );
}
/**
* Getter for the show alternate message boolean.
*
* @return bool
*/
public function should_show_alternate_message() {
return $this->show_alternate_message;
}
}

View File

@@ -1,49 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for the home URL.
*/
class Home_Url_Helper {
/**
* The home url.
*
* @var string
*/
protected static $home_url;
/**
* The parsed home url.
*
* @var array
*/
protected static $parsed_home_url;
/**
* Retrieves the home url.
*
* @return string The home url.
*/
public function get() {
if ( static::$home_url === null ) {
static::$home_url = \home_url();
}
return static::$home_url;
}
/**
* Retrieves the home url that has been parsed.
*
* @return array The parsed url.
*/
public function get_parsed() {
if ( static::$parsed_home_url === null ) {
static::$parsed_home_url = \wp_parse_url( $this->get() );
}
return static::$parsed_home_url;
}
}

View File

@@ -1,417 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WPSEO_Image_Utils;
use Yoast\WP\SEO\Models\SEO_Links;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast\WP\SEO\Repositories\SEO_Links_Repository;
/**
* A helper object for images.
*/
class Image_Helper {
/**
* Image types that are supported by Open Graph.
*
* @var array
*/
protected static $valid_image_types = [ 'image/jpeg', 'image/gif', 'image/png', 'image/webp' ];
/**
* Image extensions that are supported by Open Graph.
*
* @var array
*/
protected static $valid_image_extensions = [ 'jpeg', 'jpg', 'gif', 'png', 'webp' ];
/**
* Represents the indexables repository.
*
* @var Indexable_Repository
*/
protected $indexable_repository;
/**
* Represents the SEO Links repository.
*
* @var SEO_Links_Repository
*/
protected $seo_links_repository;
/**
* The options helper.
*
* @var Options_Helper
*/
private $options_helper;
/**
* The URL helper.
*
* @var Url_Helper
*/
private $url_helper;
/**
* Image_Helper constructor.
*
* @param Indexable_Repository $indexable_repository The indexable repository.
* @param SEO_Links_Repository $seo_links_repository The SEO Links repository.
* @param Options_Helper $options The options helper.
* @param Url_Helper $url_helper The URL helper.
*/
public function __construct(
Indexable_Repository $indexable_repository,
SEO_Links_Repository $seo_links_repository,
Options_Helper $options,
Url_Helper $url_helper
) {
$this->indexable_repository = $indexable_repository;
$this->seo_links_repository = $seo_links_repository;
$this->options_helper = $options;
$this->url_helper = $url_helper;
}
/**
* Determines whether or not the wanted attachment is considered valid.
*
* @param int $attachment_id The attachment ID to get the attachment by.
*
* @return bool Whether or not the attachment is valid.
*/
public function is_valid_attachment( $attachment_id ) {
if ( ! \wp_attachment_is_image( $attachment_id ) ) {
return false;
}
$post_mime_type = \get_post_mime_type( $attachment_id );
if ( $post_mime_type === false ) {
return false;
}
return $this->is_valid_image_type( $post_mime_type );
}
/**
* Checks if the given extension is a valid extension
*
* @param string $image_extension The image extension.
*
* @return bool True when valid.
*/
public function is_extension_valid( $image_extension ) {
return \in_array( $image_extension, static::$valid_image_extensions, true );
}
/**
* Determines whether the passed mime type is a valid image type.
*
* @param string $mime_type The detected mime type.
*
* @return bool Whether or not the attachment is a valid image type.
*/
public function is_valid_image_type( $mime_type ) {
return \in_array( $mime_type, static::$valid_image_types, true );
}
/**
* Retrieves the image source for an attachment.
*
* @param int $attachment_id The attachment.
* @param string $image_size The image size to retrieve.
*
* @return string The image url or an empty string when not found.
*/
public function get_attachment_image_source( $attachment_id, $image_size = 'full' ) {
$attachment = \wp_get_attachment_image_src( $attachment_id, $image_size );
if ( ! $attachment ) {
return '';
}
return $attachment[0];
}
/**
* Retrieves the ID of the featured image.
*
* @param int $post_id The post id to get featured image id for.
*
* @return int|bool ID when found, false when not.
*/
public function get_featured_image_id( $post_id ) {
if ( ! \has_post_thumbnail( $post_id ) ) {
return false;
}
return \get_post_thumbnail_id( $post_id );
}
/**
* Gets the image url from the content.
*
* @param int $post_id The post id to extract the images from.
*
* @return string The image url or an empty string when not found.
*/
public function get_post_content_image( $post_id ) {
$image_url = $this->get_first_usable_content_image_for_post( $post_id );
if ( $image_url === null ) {
return '';
}
return $image_url;
}
/**
* Gets the first image url of a gallery.
*
* @param int $post_id Post ID to use.
*
* @return string The image url or an empty string when not found.
*/
public function get_gallery_image( $post_id ) {
$post = \get_post( $post_id );
if ( \strpos( $post->post_content, '[gallery' ) === false ) {
return '';
}
$images = \get_post_gallery_images( $post );
if ( empty( $images ) ) {
return '';
}
return \reset( $images );
}
/**
* Gets the image url from the term content.
*
* @param int $term_id The term id to extract the images from.
*
* @return string The image url or an empty string when not found.
*/
public function get_term_content_image( $term_id ) {
$image_url = $this->get_first_content_image_for_term( $term_id );
if ( $image_url === null ) {
return '';
}
return $image_url;
}
/**
* Retrieves the caption for an attachment.
*
* @param int $attachment_id Attachment ID.
*
* @return string The caption when found, empty string when no caption is found.
*/
public function get_caption( $attachment_id ) {
$caption = \wp_get_attachment_caption( $attachment_id );
if ( ! empty( $caption ) ) {
return $caption;
}
$caption = \get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
if ( ! empty( $caption ) ) {
return $caption;
}
return '';
}
/**
* Retrieves the attachment metadata.
*
* @param int $attachment_id Attachment ID.
*
* @return array The metadata, empty array when no metadata is found.
*/
public function get_metadata( $attachment_id ) {
$metadata = \wp_get_attachment_metadata( $attachment_id );
if ( ! $metadata || ! \is_array( $metadata ) ) {
return [];
}
return $metadata;
}
/**
* Retrieves the attachment image url.
*
* @param int $attachment_id Attachment ID.
* @param string $size The size to get.
*
* @return string The url when found, empty string otherwise.
*/
public function get_attachment_image_url( $attachment_id, $size ) {
$url = \wp_get_attachment_image_url( $attachment_id, $size );
if ( ! $url ) {
return '';
}
return $url;
}
/**
* Find the right version of an image based on size.
*
* @codeCoverageIgnore - We have to write test when this method contains own code.
*
* @param int $attachment_id Attachment ID.
* @param string $size Size name.
*
* @return array|false Returns an array with image data on success, false on failure.
*/
public function get_image( $attachment_id, $size ) {
return WPSEO_Image_Utils::get_image( $attachment_id, $size );
}
/**
* Retrieves the best attachment variation for the given attachment.
*
* @codeCoverageIgnore - We have to write test when this method contains own code.
*
* @param int $attachment_id The attachment id.
*
* @return bool|string The attachment url or false when no variations found.
*/
public function get_best_attachment_variation( $attachment_id ) {
$variations = WPSEO_Image_Utils::get_variations( $attachment_id );
$variations = WPSEO_Image_Utils::filter_usable_file_size( $variations );
// If we are left without variations, there is no valid variation for this attachment.
if ( empty( $variations ) ) {
return false;
}
// The variations are ordered so the first variations is by definition the best one.
return \reset( $variations );
}
/**
* Find an attachment ID for a given URL.
*
* @param string $url The URL to find the attachment for.
* @param bool $use_link_table Whether the SEO Links table will be used to retrieve the id.
*
* @return int The found attachment ID, or 0 if none was found.
*/
public function get_attachment_by_url( $url, $use_link_table = true ) {
// Don't try to do this for external URLs.
$parsed_url = \wp_parse_url( $url );
if ( $this->url_helper->get_link_type( $parsed_url ) === SEO_Links::TYPE_EXTERNAL ) {
return 0;
}
/** The `wpseo_force_creating_and_using_attachment_indexables` filter is documented in indexable-link-builder.php */
if ( ! $this->options_helper->get( 'disable-attachment' ) || \apply_filters( 'wpseo_force_creating_and_using_attachment_indexables', false ) ) {
// Strip out the size part of an image URL.
$url = \preg_replace( '/(.*)-\d+x\d+\.(jpeg|jpg|png|gif)$/', '$1.$2', $url );
$indexable = $this->indexable_repository->find_by_permalink( $url );
if ( $indexable && $indexable->object_type === 'post' && $indexable->object_sub_type === 'attachment' ) {
return $indexable->object_id;
}
$post_id = WPSEO_Image_Utils::get_attachment_by_url( $url );
if ( $post_id !== 0 ) {
// Find the indexable, this triggers creating it so it can be found next time.
$this->indexable_repository->find_by_id_and_type( $post_id, 'post' );
}
return $post_id;
}
if ( ! $use_link_table ) {
return WPSEO_Image_Utils::get_attachment_by_url( $url );
}
$cache_key = 'attachment_seo_link_object_' . \md5( $url );
$found = false;
$link = \wp_cache_get( $cache_key, 'yoast-seo-attachment-link', false, $found );
if ( $found === false ) {
$link = $this->seo_links_repository->find_one_by_url( $url );
\wp_cache_set( $cache_key, $link, 'yoast-seo-attachment-link', \MINUTE_IN_SECONDS );
}
if ( ! \is_a( $link, SEO_Links::class ) ) {
return WPSEO_Image_Utils::get_attachment_by_url( $url );
}
return $link->target_post_id;
}
/**
* Retrieves an attachment ID for an image uploaded in the settings.
*
* Due to self::get_attachment_by_url returning 0 instead of false.
* 0 is also a possibility when no ID is available.
*
* @codeCoverageIgnore - We have to write test when this method contains own code.
*
* @param string $setting The setting the image is stored in.
*
* @return int|bool The attachment id, or false or 0 if no ID is available.
*/
public function get_attachment_id_from_settings( $setting ) {
return WPSEO_Image_Utils::get_attachment_id_from_settings( $setting );
}
/**
* Based on and image ID return array with the best variation of that image. If it's not saved to the DB, save it to an option.
*
* @param string $setting The setting name. Should be company or person.
*
* @return array|bool Array with image details when the image is found, boolean when it's not found.
*/
public function get_attachment_meta_from_settings( $setting ) {
$image_meta = $this->options_helper->get( $setting . '_meta', false );
if ( ! $image_meta ) {
$image_id = $this->options_helper->get( $setting . '_id', false );
if ( $image_id ) {
// There is not an option to put a URL in an image field in the settings anymore, only to upload it through the media manager.
// This means an attachment always exists, so doing this is only needed once.
$image_meta = $this->get_best_attachment_variation( $image_id );
if ( $image_meta ) {
$this->options_helper->set( $setting . '_meta', $image_meta );
}
}
}
return $image_meta;
}
/**
* Retrieves the first usable content image for a post.
*
* @codeCoverageIgnore - We have to write test when this method contains own code.
*
* @param int $post_id The post id to extract the images from.
*
* @return string|null
*/
protected function get_first_usable_content_image_for_post( $post_id ) {
return WPSEO_Image_Utils::get_first_usable_content_image_for_post( $post_id );
}
/**
* Gets the term's first usable content image. Null if none is available.
*
* @codeCoverageIgnore - We have to write test when this method contains own code.
*
* @param int $term_id The term id.
*
* @return string|null The image URL.
*/
protected function get_first_content_image_for_term( $term_id ) {
return WPSEO_Image_Utils::get_first_content_image_for_term( $term_id );
}
}

View File

@@ -1,58 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* The Import Cursor Helper.
*/
class Import_Cursor_Helper {
/**
* The Options_Helper.
*
* @var Options_Helper
*/
public $options;
/**
* Class constructor.
*
* @param Options_Helper $options The options helper.
*/
public function __construct(
Options_Helper $options
) {
$this->options = $options;
}
/**
* Returns the stored cursor value.
*
* @param string $cursor_id The cursor id.
* @param mixed $default_value The default value if no cursor has been set yet.
*
* @return int The stored cursor value.
*/
public function get_cursor( $cursor_id, $default_value = 0 ) {
$import_cursors = $this->options->get( 'import_cursors', [] );
return ( isset( $import_cursors[ $cursor_id ] ) ) ? $import_cursors[ $cursor_id ] : $default_value;
}
/**
* Stores the current cursor value.
*
* @param string $cursor_id The cursor id.
* @param int $last_imported_id The id of the lastly imported entry.
*
* @return void
*/
public function set_cursor( $cursor_id, $last_imported_id ) {
$current_cursors = $this->options->get( 'import_cursors', [] );
if ( ! isset( $current_cursors[ $cursor_id ] ) || $current_cursors[ $cursor_id ] < $last_imported_id ) {
$current_cursors[ $cursor_id ] = $last_imported_id;
$this->options->set( 'import_cursors', $current_cursors );
}
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* The Import Helper.
*/
class Import_Helper {
/**
* Flattens a multidimensional array of settings. Recursive.
*
* @param array $array_to_flatten The array to be flattened.
* @param string $key_prefix The key to be used as a prefix.
*
* @return array The flattened array.
*/
public function flatten_settings( $array_to_flatten, $key_prefix = '' ) {
$result = [];
foreach ( $array_to_flatten as $key => $value ) {
if ( \is_array( $value ) ) {
$result = \array_merge( $result, $this->flatten_settings( $value, $key_prefix . '/' . $key ) );
}
else {
$result[ $key_prefix . '/' . $key ] = $value;
}
}
return $result;
}
}

View File

@@ -1,265 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action;
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action;
use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action;
use Yoast\WP\SEO\Config\Indexing_Reasons;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
/**
* A helper object for indexables.
*/
class Indexable_Helper {
/**
* Represents the indexable repository.
*
* @var Indexable_Repository
*/
protected $repository;
/**
* Represents the options helper.
*
* @var Options_Helper
*/
protected $options_helper;
/**
* Represents the environment helper.
*
* @var Environment_Helper
*/
protected $environment_helper;
/**
* Represents the indexing helper.
*
* @var Indexing_Helper
*/
protected $indexing_helper;
/**
* Default values of certain columns.
*
* @var array
*/
protected $default_values = [
'title' => [
'default_value' => null,
],
'description' => [
'default_value' => null,
],
'open_graph_title' => [
'default_value' => null,
],
'open_graph_description' => [
'default_value' => null,
],
'twitter_title' => [
'default_value' => null,
],
'twitter_description' => [
'default_value' => null,
],
'canonical' => [
'default_value' => null,
],
'primary_focus_keyword' => [
'default_value' => null,
],
'is_robots_noindex' => [
'default_value' => null,
],
'is_robots_nofollow' => [
'default_value' => false,
],
'is_robots_noarchive' => [
'default_value' => null,
],
'is_robots_noimageindex' => [
'default_value' => null,
],
'is_robots_nosnippet' => [
'default_value' => null,
],
];
/**
* Indexable_Helper constructor.
*
* @param Options_Helper $options_helper The options helper.
* @param Environment_Helper $environment_helper The environment helper.
* @param Indexing_Helper $indexing_helper The indexing helper.
*/
public function __construct( Options_Helper $options_helper, Environment_Helper $environment_helper, Indexing_Helper $indexing_helper ) {
$this->options_helper = $options_helper;
$this->environment_helper = $environment_helper;
$this->indexing_helper = $indexing_helper;
}
/**
* Sets the indexable repository. Done to avoid circular dependencies.
*
* @required
*
* @param Indexable_Repository $repository The indexable repository.
*
* @return void
*/
public function set_indexable_repository( Indexable_Repository $repository ) {
$this->repository = $repository;
}
/**
* Returns the page type of an indexable.
*
* @param Indexable $indexable The indexable.
*
* @return string|false The page type. False if it could not be determined.
*/
public function get_page_type_for_indexable( $indexable ) {
switch ( $indexable->object_type ) {
case 'post':
$front_page_id = (int) \get_option( 'page_on_front' );
if ( $indexable->object_id === $front_page_id ) {
return 'Static_Home_Page';
}
$posts_page_id = (int) \get_option( 'page_for_posts' );
if ( $indexable->object_id === $posts_page_id ) {
return 'Static_Posts_Page';
}
return 'Post_Type';
case 'term':
return 'Term_Archive';
case 'user':
return 'Author_Archive';
case 'home-page':
return 'Home_Page';
case 'post-type-archive':
return 'Post_Type_Archive';
case 'date-archive':
return 'Date_Archive';
case 'system-page':
if ( $indexable->object_sub_type === 'search-result' ) {
return 'Search_Result_Page';
}
if ( $indexable->object_sub_type === '404' ) {
return 'Error_Page';
}
}
return false;
}
/**
* Resets the permalinks of the indexables.
*
* @param string|null $type The type of the indexable.
* @param string|null $subtype The subtype. Can be null.
* @param string $reason The reason that the permalink has been changed.
*
* @return void
*/
public function reset_permalink_indexables( $type = null, $subtype = null, $reason = Indexing_Reasons::REASON_PERMALINK_SETTINGS ) {
$result = $this->repository->reset_permalink( $type, $subtype );
$this->indexing_helper->set_reason( $reason );
if ( $result !== false && $result > 0 ) {
\delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_COUNT_TRANSIENT );
\delete_transient( Indexable_Post_Type_Archive_Indexation_Action::UNINDEXED_COUNT_TRANSIENT );
\delete_transient( Indexable_Term_Indexation_Action::UNINDEXED_COUNT_TRANSIENT );
}
}
/**
* Determines whether indexing indexables is appropriate at this time.
*
* @return bool Whether the indexables should be indexed.
*/
public function should_index_indexables() {
// Currently, the only reason to index is when we're on a production website.
$should_index = $this->environment_helper->is_production_mode();
/**
* Filter: 'Yoast\WP\SEO\should_index_indexables' - Allow developers to enable / disable
* creating indexables. Warning: overriding
* the intended action may cause problems when moving from a staging to a
* production environment because indexable permalinks may get set incorrectly.
*
* @since 18.2
*
* @param bool $should_index Whether the site's indexables should be created.
*/
return (bool) \apply_filters( 'Yoast\WP\SEO\should_index_indexables', $should_index );
}
/**
* Returns whether or not dynamic permalinks should be used.
*
* @return bool Whether or not the dynamic permalinks should be used.
*/
public function dynamic_permalinks_enabled() {
/**
* Filters the value of the `dynamic_permalinks` option.
*
* @param bool $value The value of the `dynamic_permalinks` option.
*/
return (bool) \apply_filters( 'wpseo_dynamic_permalinks_enabled', $this->options_helper->get( 'dynamic_permalinks', false ) );
}
/**
* Sets a boolean to indicate that the indexing of the indexables has completed.
*
* @return void
*/
public function finish_indexing() {
$this->options_helper->set( 'indexables_indexing_completed', true );
}
/**
* Checks whether the indexable has default values in given fields.
*
* @param Indexable $indexable The Yoast indexable that we're checking.
* @param array $fields The Yoast indexable fields that we're checking against.
*
* @return bool Whether the indexable has default values.
*/
public function check_if_default_indexable( $indexable, $fields ) {
foreach ( $fields as $field ) {
$is_default = $this->check_if_default_field( $indexable, $field );
if ( ! $is_default ) {
break;
}
}
return $is_default;
}
/**
* Checks if an indexable field contains the default value.
*
* @param Indexable $indexable The Yoast indexable that we're checking.
* @param string $field The field that we're checking.
*
* @return bool True if default value.
*/
public function check_if_default_field( $indexable, $field ) {
$defaults = $this->default_values;
if ( ! isset( $defaults[ $field ] ) ) {
return false;
}
if ( $indexable->$field === $defaults[ $field ]['default_value'] ) {
return true;
}
return false;
}
}

View File

@@ -1,232 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\SEO\Models\Indexable;
/**
* A helper object to map indexable data to postmeta.
*/
class Indexable_To_Postmeta_Helper {
/**
* The Meta helper.
*
* @var Meta_Helper
*/
public $meta;
/**
* The map of yoast to post meta.
*
* @var array
*/
protected $yoast_to_postmeta = [
'title' => [
'post_meta_key' => 'title',
'map_method' => 'simple_map',
],
'description' => [
'post_meta_key' => 'metadesc',
'map_method' => 'simple_map',
],
'open_graph_title' => [
'post_meta_key' => 'opengraph-title',
'map_method' => 'simple_map',
],
'open_graph_description' => [
'post_meta_key' => 'opengraph-description',
'map_method' => 'simple_map',
],
'twitter_title' => [
'post_meta_key' => 'twitter-title',
'map_method' => 'simple_map',
],
'twitter_description' => [
'post_meta_key' => 'twitter-description',
'map_method' => 'simple_map',
],
'canonical' => [
'post_meta_key' => 'canonical',
'map_method' => 'simple_map',
],
'primary_focus_keyword' => [
'post_meta_key' => 'focuskw',
'map_method' => 'simple_map',
],
'open_graph_image' => [
'post_meta_key' => 'opengraph-image',
'map_method' => 'social_image_map',
],
'open_graph_image_id' => [
'post_meta_key' => 'opengraph-image-id',
'map_method' => 'social_image_map',
],
'twitter_image' => [
'post_meta_key' => 'twitter-image',
'map_method' => 'social_image_map',
],
'twitter_image_id' => [
'post_meta_key' => 'twitter-image-id',
'map_method' => 'social_image_map',
],
'is_robots_noindex' => [
'post_meta_key' => 'meta-robots-noindex',
'map_method' => 'noindex_map',
],
'is_robots_nofollow' => [
'post_meta_key' => 'meta-robots-nofollow',
'map_method' => 'nofollow_map',
],
'meta_robots_adv' => [
'post_meta_key' => 'meta-robots-adv',
'map_method' => 'robots_adv_map',
],
];
/**
* Indexable_To_Postmeta_Helper constructor.
*
* @param Meta_Helper $meta The Meta helper.
*/
public function __construct( Meta_Helper $meta ) {
$this->meta = $meta;
}
/**
* Creates postmeta from a Yoast indexable.
*
* @param Indexable $indexable The Yoast indexable.
*
* @return void
*/
public function map_to_postmeta( $indexable ) {
foreach ( $this->yoast_to_postmeta as $indexable_column => $map_info ) {
\call_user_func( [ $this, $map_info['map_method'] ], $indexable, $map_info['post_meta_key'], $indexable_column );
}
}
/**
* Uses a simple set_value for non-empty data.
*
* @param Indexable $indexable The Yoast indexable.
* @param string $post_meta_key The post_meta key that will be populated.
* @param string $indexable_column The indexable data that will be mapped to post_meta.
*
* @return void
*/
public function simple_map( $indexable, $post_meta_key, $indexable_column ) {
if ( empty( $indexable->{$indexable_column} ) ) {
return;
}
$this->meta->set_value( $post_meta_key, $indexable->{$indexable_column}, $indexable->object_id );
}
/**
* Map social image data only if social image is explicitly set.
*
* @param Indexable $indexable The Yoast indexable.
* @param string $post_meta_key The post_meta key that will be populated.
* @param string $indexable_column The indexable data that will be mapped to post_meta.
*
* @return void
*/
public function social_image_map( $indexable, $post_meta_key, $indexable_column ) {
if ( empty( $indexable->{$indexable_column} ) ) {
return;
}
switch ( $indexable_column ) {
case 'open_graph_image':
case 'open_graph_image_id':
$source = $indexable->open_graph_image_source;
break;
case 'twitter_image':
case 'twitter_image_id':
$source = $indexable->twitter_image_source;
break;
}
// Map the social image data only when the social image is explicitly set.
if ( $source === 'set-by-user' || $source === 'imported' ) {
$value = (string) $indexable->{$indexable_column};
$this->meta->set_value( $post_meta_key, $value, $indexable->object_id );
}
}
/**
* Deletes the noindex post_meta key if no noindex in the indexable. Populates the post_meta key appropriately if there is noindex in the indexable.
*
* @param Indexable $indexable The Yoast indexable.
* @param string $post_meta_key The post_meta key that will be populated.
*
* @return void
*/
public function noindex_map( $indexable, $post_meta_key ) {
if ( \is_null( $indexable->is_robots_noindex ) ) {
$this->meta->delete( $post_meta_key, $indexable->object_id );
return;
}
if ( $indexable->is_robots_noindex === false ) {
$this->meta->set_value( $post_meta_key, 2, $indexable->object_id );
}
if ( $indexable->is_robots_noindex === true ) {
$this->meta->set_value( $post_meta_key, 1, $indexable->object_id );
}
}
/**
* Deletes the nofollow post_meta key if no nofollow in the indexable or if nofollow is false. Populates the post_meta key appropriately if there is a true nofollow in the indexable.
*
* @param Indexable $indexable The Yoast indexable.
* @param string $post_meta_key The post_meta key that will be populated.
*
* @return void
*/
public function nofollow_map( $indexable, $post_meta_key ) {
if ( \is_null( $indexable->is_robots_nofollow ) || $indexable->is_robots_nofollow === false ) {
$this->meta->delete( $post_meta_key, $indexable->object_id );
}
if ( $indexable->is_robots_nofollow === true ) {
$this->meta->set_value( $post_meta_key, 1, $indexable->object_id );
}
}
/**
* Deletes the nofollow post_meta key if no nofollow in the indexable or if nofollow is false. Populates the post_meta key appropriately if there is a true nofollow in the indexable.
*
* @param Indexable $indexable The Yoast indexable.
* @param string $post_meta_key The post_meta key that will be populated.
*
* @return void
*/
public function robots_adv_map( $indexable, $post_meta_key ) {
$adv_settings_to_be_imported = [];
$no_adv_settings = true;
if ( $indexable->is_robots_noimageindex === true ) {
$adv_settings_to_be_imported[] = 'noimageindex';
$no_adv_settings = false;
}
if ( $indexable->is_robots_noarchive === true ) {
$adv_settings_to_be_imported[] = 'noarchive';
$no_adv_settings = false;
}
if ( $indexable->is_robots_nosnippet === true ) {
$adv_settings_to_be_imported[] = 'nosnippet';
$no_adv_settings = false;
}
if ( $no_adv_settings === true ) {
$this->meta->delete( $post_meta_key, $indexable->object_id );
return;
}
$this->meta->set_value( $post_meta_key, \implode( ',', $adv_settings_to_be_imported ), $indexable->object_id );
}
}

View File

@@ -1,397 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action;
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action;
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action;
use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action;
use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface;
use Yoast\WP\SEO\Actions\Indexing\Limited_Indexing_Action_Interface;
use Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action;
use Yoast\WP\SEO\Actions\Indexing\Term_Link_Indexing_Action;
use Yoast\WP\SEO\Config\Indexing_Reasons;
use Yoast\WP\SEO\Integrations\Admin\Indexing_Notification_Integration;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast_Notification_Center;
/**
* A helper object for indexing.
*/
class Indexing_Helper {
/**
* The options helper.
*
* @var Options_Helper
*/
protected $options_helper;
/**
* The date helper.
*
* @var Date_Helper
*/
protected $date_helper;
/**
* The notification center.
*
* @var Yoast_Notification_Center
*/
protected $notification_center;
/**
* The indexation actions.
*
* @var Indexation_Action_Interface[]|Limited_Indexing_Action_Interface[]
*/
protected $indexing_actions;
/**
* The indexation actions that can be done in the background.
*
* @var Indexation_Action_Interface[]|Limited_Indexing_Action_Interface[]
*/
protected $background_indexing_actions;
/**
* The indexable repository.
*
* @var Indexable_Repository
*/
protected $indexable_repository;
/**
* Indexing_Helper constructor.
*
* @param Options_Helper $options_helper The options helper.
* @param Date_Helper $date_helper The date helper.
* @param Yoast_Notification_Center $notification_center The notification center.
*/
public function __construct(
Options_Helper $options_helper,
Date_Helper $date_helper,
Yoast_Notification_Center $notification_center
) {
$this->options_helper = $options_helper;
$this->date_helper = $date_helper;
$this->notification_center = $notification_center;
}
/**
* Sets the actions.
*
* @required
*
* @param Indexable_Post_Indexation_Action $post_indexation The post indexing action.
* @param Indexable_Term_Indexation_Action $term_indexation The term indexing action.
* @param Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation The posttype indexing action.
* @param Indexable_General_Indexation_Action $general_indexation The general indexing (homepage etc) action.
* @param Post_Link_Indexing_Action $post_link_indexing_action The post crosslink indexing action.
* @param Term_Link_Indexing_Action $term_link_indexing_action The term crossling indexing action.
*
* @return void
*/
public function set_indexing_actions(
Indexable_Post_Indexation_Action $post_indexation,
Indexable_Term_Indexation_Action $term_indexation,
Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation,
Indexable_General_Indexation_Action $general_indexation,
Post_Link_Indexing_Action $post_link_indexing_action,
Term_Link_Indexing_Action $term_link_indexing_action
) {
$this->indexing_actions = [
$post_indexation,
$term_indexation,
$post_type_archive_indexation,
$general_indexation,
$post_link_indexing_action,
$term_link_indexing_action,
];
// Coincidentally, the background indexing actions are the same with the Free indexing actions for now.
$this->background_indexing_actions = $this->indexing_actions;
}
/**
* Sets the indexable repository for the indexing helper class.
*
* @required
*
* @param Indexable_Repository $indexable_repository The indexable repository.
*
* @return void
*/
public function set_indexable_repository(
Indexable_Repository $indexable_repository
) {
$this->indexable_repository = $indexable_repository;
}
/**
* Prepares the indexing process by setting several database options and removing the indexing notification.
*
* @return void
*/
public function prepare() {
$this->set_first_time( false );
$this->set_started( $this->date_helper->current_time() );
$this->remove_indexing_notification();
// Do not set_reason here; if the process is cancelled, the reason to start indexing is still valid.
}
/**
* Sets several database options when the indexing process is finished.
*
* @return void
*/
public function complete() {
$this->set_reason( '' );
$this->set_started( null );
}
/**
* Sets appropriate flags when the indexing process fails.
*
* @return void
*/
public function indexing_failed() {
$this->set_reason( Indexing_Reasons::REASON_INDEXING_FAILED );
$this->set_started( null );
}
/**
* Sets the indexing reason.
*
* @param string $reason The indexing reason.
*
* @return void
*/
public function set_reason( $reason ) {
$this->options_helper->set( 'indexing_reason', $reason );
$this->remove_indexing_notification();
}
/**
* Removes any pre-existing notification, so that a new notification (with a possible new reason) can be added.
*
* @return void
*/
protected function remove_indexing_notification() {
$this->notification_center->remove_notification_by_id(
Indexing_Notification_Integration::NOTIFICATION_ID
);
}
/**
* Determines whether an indexing reason has been set in the options.
*
* @return bool Whether an indexing reason has been set in the options.
*/
public function has_reason() {
$reason = $this->get_reason();
return ! empty( $reason );
}
/**
* Returns the indexing reason. The reason why the site-wide indexing process should be run.
*
* @return string The indexing reason, defaults to the empty string if no reason has been set.
*/
public function get_reason() {
return $this->options_helper->get( 'indexing_reason', '' );
}
/**
* Sets the start time when the indexing process has started but not completed.
*
* @param int|bool $timestamp The start time when the indexing process has started but not completed, false otherwise.
*
* @return void
*/
public function set_started( $timestamp ) {
$this->options_helper->set( 'indexing_started', $timestamp );
}
/**
* Gets the start time when the indexing process has started but not completed.
*
* @return int|bool The start time when the indexing process has started but not completed, false otherwise.
*/
public function get_started() {
return $this->options_helper->get( 'indexing_started' );
}
/**
* Sets a boolean that indicates whether or not a site still has to be indexed for the first time.
*
* @param bool $is_first_time_indexing Whether or not a site still has to be indexed for the first time.
*
* @return void
*/
public function set_first_time( $is_first_time_indexing ) {
$this->options_helper->set( 'indexing_first_time', $is_first_time_indexing );
}
/**
* Gets a boolean that indicates whether or not the site still has to be indexed for the first time.
*
* @return bool Whether the site still has to be indexed for the first time.
*/
public function is_initial_indexing() {
return $this->options_helper->get( 'indexing_first_time', true );
}
/**
* Gets a boolean that indicates whether or not the indexing of the indexables has completed.
*
* @return bool Whether the indexing of the indexables has completed.
*/
public function is_finished_indexables_indexing() {
return $this->options_helper->get( 'indexables_indexing_completed', false );
}
/**
* Returns the total number of unindexed objects.
*
* @return int The total number of unindexed objects.
*/
public function get_unindexed_count() {
$unindexed_count = 0;
foreach ( $this->indexing_actions as $indexing_action ) {
$unindexed_count += $indexing_action->get_total_unindexed();
}
return $unindexed_count;
}
/**
* Returns the amount of un-indexed posts expressed in percentage, which will be needed to set a threshold.
*
* @param int $unindexed_count The number of unindexed objects.
*
* @return int The amount of unindexed posts expressed in percentage.
*/
public function get_unindexed_percentage( $unindexed_count ) {
// Gets the amount of indexed objects in the site.
$indexed_count = $this->indexable_repository->get_total_number_of_indexables();
// The total amount of objects in the site.
$total_objects_count = ( $indexed_count + $unindexed_count );
return ( ( $unindexed_count / $total_objects_count ) * 100 );
}
/**
* Returns whether the SEO optimization button should show.
*
* @return bool Whether the SEO optimization button should show.
*/
public function should_show_optimization_button() {
// Gets the amount of unindexed objects in the site.
$unindexed_count = $this->get_filtered_unindexed_count();
// If the amount of unidexed posts is <10 don't show configuration button.
if ( $unindexed_count <= 10 ) {
return false;
}
// If the amount of unidexed posts is >10, but the total amount of unidexed posts is ≤4% of the total amount of objects in the site, don't show configuration button.
if ( $this->get_unindexed_percentage( $unindexed_count ) <= 4 ) {
return false;
}
return true;
}
/**
* Returns the total number of unindexed objects and applies a filter for third party integrations.
*
* @return int The total number of unindexed objects.
*/
public function get_filtered_unindexed_count() {
$unindexed_count = $this->get_unindexed_count();
/**
* Filter: 'wpseo_indexing_get_unindexed_count' - Allow changing the amount of unindexed objects.
*
* @param int $unindexed_count The amount of unindexed objects.
*/
return \apply_filters( 'wpseo_indexing_get_unindexed_count', $unindexed_count );
}
/**
* Returns a limited number of unindexed objects.
*
* @param int $limit Limit the number of unindexed objects that are counted.
* @param Indexation_Action_Interface[]|Limited_Indexing_Action_Interface[] $actions The actions whose counts will be calculated.
*
* @return int The total number of unindexed objects.
*/
public function get_limited_unindexed_count( $limit, $actions = [] ) {
$unindexed_count = 0;
if ( empty( $actions ) ) {
$actions = $this->indexing_actions;
}
foreach ( $actions as $action ) {
$unindexed_count += $action->get_limited_unindexed_count( $limit - $unindexed_count + 1 );
if ( $unindexed_count > $limit ) {
return $unindexed_count;
}
}
return $unindexed_count;
}
/**
* Returns the total number of unindexed objects and applies a filter for third party integrations.
*
* @param int $limit Limit the number of unindexed objects that are counted.
*
* @return int The total number of unindexed objects.
*/
public function get_limited_filtered_unindexed_count( $limit ) {
$unindexed_count = $this->get_limited_unindexed_count( $limit, $this->indexing_actions );
if ( $unindexed_count > $limit ) {
return $unindexed_count;
}
/**
* Filter: 'wpseo_indexing_get_limited_unindexed_count' - Allow changing the amount of unindexed objects,
* and allow for a maximum number of items counted to improve performance.
*
* @param int $unindexed_count The amount of unindexed objects.
* @param int|false $limit Limit the number of unindexed objects that need to be counted.
* False if it doesn't need to be limited.
*/
return \apply_filters( 'wpseo_indexing_get_limited_unindexed_count', $unindexed_count, $limit );
}
/**
* Returns the total number of unindexed objects that can be indexed in the background and applies a filter for third party integrations.
*
* @param int $limit Limit the number of unindexed objects that are counted.
*
* @return int The total number of unindexed objects that can be indexed in the background.
*/
public function get_limited_filtered_unindexed_count_background( $limit ) {
$unindexed_count = $this->get_limited_unindexed_count( $limit, $this->background_indexing_actions );
if ( $unindexed_count > $limit ) {
return $unindexed_count;
}
/**
* Filter: 'wpseo_indexing_get_limited_unindexed_count_background' - Allow changing the amount of unindexed objects that can be indexed in the background,
* and allow for a maximum number of items counted to improve performance.
*
* @param int $unindexed_count The amount of unindexed objects.
* @param int|false $limit Limit the number of unindexed objects that need to be counted.
* False if it doesn't need to be limited.
*/
return \apply_filters( 'wpseo_indexing_get_limited_unindexed_count_background', $unindexed_count, $limit );
}
}

View File

@@ -1,83 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WPSEO_Language_Utils;
use Yoast\WP\SEO\Config\Researcher_Languages;
/**
* A helper object for language features.
*/
class Language_Helper {
/**
* The languages with inclusive language analysis support.
*
* @var string[]
*/
public static $languages_with_inclusive_language_support = [ 'en' ];
/**
* Checks whether word form recognition is active for the used language.
*
* @param string $language The used language.
*
* @return bool Whether word form recognition is active for the used language.
*/
public function is_word_form_recognition_active( $language ) {
$supported_languages = [ 'de', 'en', 'es', 'fr', 'it', 'nl', 'ru', 'id', 'pt', 'pl', 'ar', 'sv', 'he', 'hu', 'nb', 'tr', 'cs', 'sk', 'el', 'ja' ];
return \in_array( $language, $supported_languages, true );
}
/**
* Checks whether the given language has function word support.
* (E.g. function words are used or filtered out for this language when doing some SEO and readability assessments).
*
* @param string $language The language to check.
*
* @return bool Whether the language has function word support.
*/
public function has_function_word_support( $language ) {
$supported_languages = [ 'en', 'de', 'nl', 'fr', 'es', 'it', 'pt', 'ru', 'pl', 'sv', 'id', 'he', 'ar', 'hu', 'nb', 'tr', 'cs', 'sk', 'fa', 'el', 'ja' ];
return \in_array( $language, $supported_languages, true );
}
/**
* Checks whether the given language has inclusive language support.
*
* @param string $language The language to check if inclusive language is supported.
*
* @return bool Whether the language has inclusive language support.
*/
public function has_inclusive_language_support( $language ) {
return \in_array( $language, self::$languages_with_inclusive_language_support, true );
}
/**
* Checks whether we have a specific researcher for the current locale and returns that language.
* If there is no researcher for the current locale, returns default as the researcher.
*
* @return string The language to use to select a researcher.
*/
public function get_researcher_language() {
$researcher_language = WPSEO_Language_Utils::get_language( \get_locale() );
$supported_languages = Researcher_Languages::SUPPORTED_LANGUAGES;
if ( ! \in_array( $researcher_language, $supported_languages, true ) ) {
$researcher_language = 'default';
}
return $researcher_language;
}
/**
* Returns The site language code without region
* (e.g. 'en' for 'en_US' or 'en_GB').
*
* @return string The site language code without region.
*/
public function get_language() {
return WPSEO_Language_Utils::get_language( \get_locale() );
}
}

View File

@@ -1,79 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WPSEO_Meta;
use WPSEO_Taxonomy_Meta;
/**
* A helper object for meta.
*/
class Meta_Helper {
/**
* Get a custom post meta value.
*
* Returns the default value if the meta value has not been set.
*
* {@internal Unfortunately there isn't a filter available to hook into before returning
* the results for get_post_meta(), get_post_custom() and the likes. That
* would have been the preferred solution.}}
*
* @codeCoverageIgnore We have to write test when this method contains own code.
*
* @param string $key Internal key of the value to get (without prefix).
* @param int $postid Post ID of the post to get the value for.
*
* @return string All 'normal' values returned from get_post_meta() are strings.
* Objects and arrays are possible, but not used by this plugin
* and therefore discarted (except when the special 'serialized' field def
* value is set to true - only used by add-on plugins for now).
* Will return the default value if no value was found.
* Will return empty string if no default was found (not one of our keys) or
* if the post does not exist.
*/
public function get_value( $key, $postid = 0 ) {
return WPSEO_Meta::get_value( $key, $postid );
}
/**
* Retrieve a taxonomy term's meta value(s).
*
* @param mixed $term Term to get the meta value for
* either (string) term name, (int) term id or (object) term.
* @param string $taxonomy Name of the taxonomy to which the term is attached.
* @param string|null $meta Optional. Meta value to get (without prefix).
*
* @return mixed|bool Value for the $meta if one is given, might be the default.
* If no meta is given, an array of all the meta data for the term.
* False if the term does not exist or the $meta provided is invalid.
*/
public function get_term_value( $term, $taxonomy, $meta = null ) {
return WPSEO_Taxonomy_Meta::get_term_meta( $term, $taxonomy, $meta );
}
/**
* Set a custom post meta value.
*
* @param string $key Internal key of the value to set (without prefix).
* @param mixed $meta_value The value to set the meta value to.
* @param int $post_id Post ID of the post to set the value for.
*
* @return bool Whether the value was changed.
*/
public function set_value( $key, $meta_value, $post_id ) {
return WPSEO_Meta::set_value( $key, $meta_value, $post_id );
}
/**
* Deletes a meta value for a post.
*
* @param string $key The internal key of the meta value to change (without prefix).
* @param int $post_id The ID of the post to delete the meta for.
*
* @return bool Whether the delete was successful or not.
*/
public function delete( $key, $post_id ) {
return WPSEO_Meta::delete( $key, $post_id );
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast_Notification;
use Yoast_Notification_Center;
/**
* A helper object for notifications.
*/
class Notification_Helper {
/**
* Restores a notification (wrapper function).
*
* @codeCoverageIgnore
*
* @param Yoast_Notification $notification The notification to restore.
*
* @return bool True if restored, false otherwise.
*/
public function restore_notification( Yoast_Notification $notification ) {
return Yoast_Notification_Center::restore_notification( $notification );
}
}

View File

@@ -1,111 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Open_Graph;
use Yoast\WP\SEO\Helpers\Image_Helper as Base_Image_Helper;
use Yoast\WP\SEO\Helpers\Url_Helper;
/**
* A helper object for Open Graph images.
*/
class Image_Helper {
/**
* The URL helper.
*
* @var Url_Helper
*/
private $url;
/**
* The base image helper.
*
* @var Base_Image_Helper
*/
private $image;
/**
* Image_Helper constructor.
*
* @codeCoverageIgnore
*
* @param Url_Helper $url The url helper.
* @param Base_Image_Helper $image The image helper.
*/
public function __construct( Url_Helper $url, Base_Image_Helper $image ) {
$this->url = $url;
$this->image = $image;
}
/**
* Determines whether the passed URL is considered valid.
*
* @deprecated 22.4
* @codeCoverageIgnore
*
* @param array<array<string,string|int>> $image The image array.
*
* @return bool Whether or not the URL is a valid image.
*/
public function is_image_url_valid( array $image ) {
\_deprecated_function( __METHOD__, 'Yoast SEO 22.4' );
if ( empty( $image['url'] ) || ! \is_string( $image['url'] ) ) {
return false;
}
$image_extension = $this->url->get_extension_from_url( $image['url'] );
$is_valid = $this->image->is_extension_valid( $image_extension );
/**
* Filter: 'wpseo_opengraph_is_valid_image_url' - Allows extra validation for an image url.
*
* @param bool $is_valid Current validation result.
* @param string $url The image url to validate.
*/
return (bool) \apply_filters( 'wpseo_opengraph_is_valid_image_url', $is_valid, $image['url'] );
}
/**
* Retrieves the overridden image size value.
*
* @return string|null The image size when overriden by filter or null when not.
*/
public function get_override_image_size() {
/**
* Filter: 'wpseo_opengraph_image_size' - Allow overriding the image size used
* for Open Graph sharing. If this filter is used, the defined size will always be
* used for the og:image. The image will still be rejected if it is too small.
*
* Only use this filter if you manually want to determine the best image size
* for the `og:image` tag.
*
* Use the `wpseo_image_sizes` filter if you want to use our logic. That filter
* can be used to add an image size that needs to be taken into consideration
* within our own logic.
*
* @param string|false $size Size string.
*/
return \apply_filters( 'wpseo_opengraph_image_size', null );
}
/**
* Retrieves the image data by a given attachment id.
*
* @param int $attachment_id The attachment id.
*
* @return array<string,string|int>|false The image data when found, `false` when not.
*/
public function get_image_by_id( $attachment_id ) {
if ( ! $this->image->is_valid_attachment( $attachment_id ) ) {
return false;
}
$override_image_size = $this->get_override_image_size();
if ( $override_image_size ) {
return $this->image->get_image( $attachment_id, $override_image_size );
}
return $this->image->get_best_attachment_variation( $attachment_id );
}
}

View File

@@ -1,85 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Open_Graph;
/**
* A helper object for the filtering of values.
*/
class Values_Helper {
/**
* Filters the Open Graph title.
*
* @param string $title The default title.
* @param string $object_type The object type.
* @param string $object_subtype The object subtype.
*
* @return string The open graph title.
*/
public function get_open_graph_title( $title, $object_type, $object_subtype ) {
/**
* Allow changing the Open Graph title.
*
* @param string $title The default title.
* @param string $object_subtype The object subtype.
*/
return \apply_filters( 'Yoast\WP\SEO\open_graph_title_' . $object_type, $title, $object_subtype );
}
/**
* Filters the Open Graph description.
*
* @param string $description The default description.
* @param string $object_type The object type.
* @param string $object_subtype The object subtype.
*
* @return string The open graph description.
*/
public function get_open_graph_description( $description, $object_type, $object_subtype ) {
/**
* Allow changing the Open Graph description.
*
* @param string $description The default description.
* @param string $object_subtype The object subtype.
*/
return \apply_filters( 'Yoast\WP\SEO\open_graph_description_' . $object_type, $description, $object_subtype );
}
/**
* Filters the Open Graph image ID.
*
* @param int $image_id The default image ID.
* @param string $object_type The object type.
* @param string $object_subtype The object subtype.
*
* @return string The open graph image ID.
*/
public function get_open_graph_image_id( $image_id, $object_type, $object_subtype ) {
/**
* Allow changing the Open Graph image ID.
*
* @param int $image_id The default image ID.
* @param string $object_subtype The object subtype.
*/
return \apply_filters( 'Yoast\WP\SEO\open_graph_image_id_' . $object_type, $image_id, $object_subtype );
}
/**
* Filters the Open Graph image URL.
*
* @param string $image The default image URL.
* @param string $object_type The object type.
* @param string $object_subtype The object subtype.
*
* @return string The open graph image URL.
*/
public function get_open_graph_image( $image, $object_type, $object_subtype ) {
/**
* Allow changing the Open Graph image URL.
*
* @param string $image The default image URL.
* @param string $object_subtype The object subtype.
*/
return \apply_filters( 'Yoast\WP\SEO\open_graph_image_' . $object_type, $image, $object_subtype );
}
}

View File

@@ -1,141 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WPSEO_Option_Social;
use WPSEO_Option_Titles;
use WPSEO_Options;
/**
* A helper object for options.
*/
class Options_Helper {
/**
* Retrieves a single field from any option for the SEO plugin. Keys are always unique.
*
* @codeCoverageIgnore We have to write test when this method contains own code.
*
* @param string $key The key it should return.
* @param mixed $default_value The default value that should be returned if the key isn't set.
*
* @return mixed|null Returns value if found, $default_value if not.
*/
public function get( $key, $default_value = null ) {
return WPSEO_Options::get( $key, $default_value );
}
/**
* Sets a single field to the options.
*
* @param string $key The key to set.
* @param mixed $value The value to set.
*
* @return mixed|null Returns value if found.
*/
public function set( $key, $value ) {
return WPSEO_Options::set( $key, $value );
}
/**
* Get a specific default value for an option.
*
* @param string $option_name The option for which you want to retrieve a default.
* @param string $key The key within the option who's default you want.
*
* @return mixed The default value.
*/
public function get_default( $option_name, $key ) {
return WPSEO_Options::get_default( $option_name, $key );
}
/**
* Retrieves the title separator.
*
* @return string The title separator.
*/
public function get_title_separator() {
$default = $this->get_default( 'wpseo_titles', 'separator' );
// Get the titles option and the separator options.
$separator = $this->get( 'separator' );
$seperator_options = $this->get_separator_options();
// This should always be set, but just to be sure.
if ( isset( $seperator_options[ $separator ] ) ) {
// Set the new replacement.
$replacement = $seperator_options[ $separator ];
}
elseif ( isset( $seperator_options[ $default ] ) ) {
$replacement = $seperator_options[ $default ];
}
else {
$replacement = \reset( $seperator_options );
}
/**
* Filter: 'wpseo_replacements_filter_sep' - Allow customization of the separator character(s).
*
* @param string $replacement The current separator.
*/
return \apply_filters( 'wpseo_replacements_filter_sep', $replacement );
}
/**
* Retrieves a default value from the option titles.
*
* @param string $option_titles_key The key of the option title you wish to get.
*
* @return string The option title.
*/
public function get_title_default( $option_titles_key ) {
$default_titles = $this->get_title_defaults();
if ( ! empty( $default_titles[ $option_titles_key ] ) ) {
return $default_titles[ $option_titles_key ];
}
return '';
}
/**
* Retrieves the default option titles.
*
* @codeCoverageIgnore We have to write test when this method contains own code.
*
* @return array The title defaults.
*/
protected function get_title_defaults() {
return WPSEO_Option_Titles::get_instance()->get_defaults();
}
/**
* Get the available separator options.
*
* @return array
*/
protected function get_separator_options() {
return WPSEO_Option_Titles::get_instance()->get_separator_options();
}
/**
* Checks whether a social URL is valid, with empty strings being valid social URLs.
*
* @param string $url The url to be checked.
*
* @return bool Whether the URL is valid.
*/
public function is_social_url_valid( $url ) {
return $url === '' || WPSEO_Option_Social::get_instance()->validate_social_url( $url );
}
/**
* Checks whether a twitter id is valid, with empty strings being valid twitter id.
*
* @param string $twitter_id The twitter id to be checked.
*
* @return bool Whether the twitter id is valid.
*/
public function is_twitter_id_valid( $twitter_id ) {
return empty( $twitter_id ) || WPSEO_Option_Social::get_instance()->validate_twitter_id( $twitter_id, false );
}
}

View File

@@ -1,207 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper;
use Yoast\WP\SEO\Wrappers\WP_Rewrite_Wrapper;
/**
* A helper object for pagination.
*
* Used for the canonical URL and the rel "next" and "prev" meta tags.
*/
class Pagination_Helper {
/**
* Holds the WP rewrite wrapper instance.
*
* @var WP_Rewrite_Wrapper WP_Rewrite wrapper.
*/
protected $wp_rewrite_wrapper;
/**
* Holds the WP query wrapper instance.
*
* @var WP_Query_Wrapper WP_Query wrapper.
*/
protected $wp_query_wrapper;
/**
* Pagination_Helper constructor.
*
* @param WP_Rewrite_Wrapper $wp_rewrite_wrapper The rewrite wrapper.
* @param WP_Query_Wrapper $wp_query_wrapper The query wrapper.
*/
public function __construct(
WP_Rewrite_Wrapper $wp_rewrite_wrapper,
WP_Query_Wrapper $wp_query_wrapper
) {
$this->wp_rewrite_wrapper = $wp_rewrite_wrapper;
$this->wp_query_wrapper = $wp_query_wrapper;
}
/**
* Checks whether adjacent rel links are disabled.
*
* @return bool Whether adjacent rel links are disabled or not.
*/
public function is_rel_adjacent_disabled() {
/**
* Filter: 'wpseo_disable_adjacent_rel_links' - Allows disabling of Yoast adjacent links if this is being handled by other code.
*
* @param bool $links_generated Indicates if other code has handled adjacent links.
*/
return \apply_filters( 'wpseo_disable_adjacent_rel_links', false );
}
/**
* Builds a paginated URL.
*
* @param string $url The un-paginated URL of the current archive.
* @param string $page The page number to add on to $url for the $link tag.
* @param bool $add_pagination_base Optional. Whether to add the pagination base (`page`) to the url.
* @param string $pagination_query_name Optional. The name of the query argument that holds the current page.
*
* @return string The paginated URL.
*/
public function get_paginated_url( $url, $page, $add_pagination_base = true, $pagination_query_name = 'page' ) {
$wp_rewrite = $this->wp_rewrite_wrapper->get();
$key_query_loop = $this->get_key_query_loop();
if ( $key_query_loop ) {
$pagination_query_name = $key_query_loop;
}
if ( $wp_rewrite->using_permalinks() && ! $key_query_loop ) {
$url_parts = \wp_parse_url( $url );
$has_url_params = \array_key_exists( 'query', $url_parts );
if ( $has_url_params ) {
// We need to first remove the query params, before potentially adding the pagination parts.
\wp_parse_str( $url_parts['query'], $query_parts );
$url = \trailingslashit( \remove_query_arg( \array_keys( $query_parts ), $url ) );
if ( $add_pagination_base ) {
$url .= \trailingslashit( $wp_rewrite->pagination_base );
}
// We can now re-add the query params, after appending the last pagination parts.
return \add_query_arg( $query_parts, \user_trailingslashit( $url . $page ) );
}
$url = \trailingslashit( $url );
if ( $add_pagination_base ) {
$url .= \trailingslashit( $wp_rewrite->pagination_base );
}
return \user_trailingslashit( $url . $page );
}
return \add_query_arg( $pagination_query_name, $page, \user_trailingslashit( $url ) );
}
/**
* Gets the number of archive pages.
*
* @return int The number of archive pages.
*/
public function get_number_of_archive_pages() {
$wp_query = $this->wp_query_wrapper->get_query();
return (int) $wp_query->max_num_pages;
}
/**
* Returns the current page for paged archives.
*
* @return int The current archive page.
*/
public function get_current_archive_page_number() {
$wp_query = $this->wp_query_wrapper->get_main_query();
$page_number = (int) $wp_query->get( 'paged' );
if ( $page_number > 1 ) {
return $page_number;
}
$query_loop_page_number = $this->get_page_number_from_query_loop();
if ( $query_loop_page_number ) {
return $query_loop_page_number;
}
return 0;
}
/**
* Returns the current page for paged post types.
*
* @return int The current post page.
*/
public function get_current_post_page_number() {
$wp_query = $this->wp_query_wrapper->get_main_query();
$query_loop_page_number = $this->get_page_number_from_query_loop();
if ( $query_loop_page_number ) {
return $query_loop_page_number;
}
return (int) $wp_query->get( 'page' );
}
/**
* Returns the current page number.
*
* @return int The current page number.
*/
public function get_current_page_number() {
// Get the page number for an archive page.
$page_number = \get_query_var( 'paged', 1 );
if ( $page_number > 1 ) {
return $page_number;
}
$query_loop_page_number = $this->get_page_number_from_query_loop();
if ( $query_loop_page_number ) {
return $query_loop_page_number;
}
// Get the page number for a page in a paginated post.
return \get_query_var( 'page', 1 );
}
/**
* Returns the key of the query loop.
*
* @return string The key of the query loop.
*/
public function get_key_query_loop() {
$regex_pattern = '/^query-\d+-page$/';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- not form data.
foreach ( $_GET as $key => $value ) {
if ( \preg_match( $regex_pattern, $key ) ) {
return $key;
}
}
return '';
}
/**
* Returns the page number from the query loop.
*
* @return string The page number from the query loop.
*/
public function get_page_number_from_query_loop() {
$key_query_loop = $this->get_key_query_loop();
if ( $key_query_loop ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.NonceVerification.Recommended -- Validated in get_key_query_loop().
$page_number = (int) $_GET[ $key_query_loop ];
if ( $page_number > 1 ) {
return $page_number;
}
}
return '';
}
}

View File

@@ -1,46 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\SEO\Models\Indexable;
/**
* A helper object for permalinks.
*/
class Permalink_Helper {
/**
* Retrieves the permalink for an indexable.
*
* @param Indexable $indexable The indexable.
*
* @return string|null The permalink.
*/
public function get_permalink_for_indexable( $indexable ) {
switch ( true ) {
case $indexable->object_type === 'post':
if ( $indexable->object_sub_type === 'attachment' ) {
return \wp_get_attachment_url( $indexable->object_id );
}
return \get_permalink( $indexable->object_id );
case $indexable->object_type === 'home-page':
return \home_url( '/' );
case $indexable->object_type === 'term':
$term = \get_term( $indexable->object_id );
if ( $term === null || \is_wp_error( $term ) ) {
return null;
}
return \get_term_link( $term, $term->taxonomy );
case $indexable->object_type === 'system-page' && $indexable->object_sub_type === 'search-page':
return \get_search_link();
case $indexable->object_type === 'post-type-archive':
return \get_post_type_archive_link( $indexable->object_sub_type );
case $indexable->object_type === 'user':
return \get_author_posts_url( $indexable->object_id );
}
return null;
}
}

View File

@@ -1,228 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WP_Post;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
/**
* A helper object for post related things.
*/
class Post_Helper {
/**
* Holds the String_Helper instance.
*
* @var String_Helper
*/
private $string;
/**
* Holds the Post_Type_Helper instance.
*
* @var Post_Type_Helper
*/
private $post_type;
/**
* Represents the indexables repository.
*
* @var Indexable_Repository
*/
private $repository;
/**
* Post_Helper constructor.
*
* @codeCoverageIgnore It only sets dependencies.
*
* @param String_Helper $string_helper The string helper.
* @param Post_Type_Helper $post_type_helper The string helper.
*/
public function __construct(
String_Helper $string_helper,
Post_Type_Helper $post_type_helper
) {
$this->string = $string_helper;
$this->post_type = $post_type_helper;
}
/**
* Sets the indexable repository. Done to avoid circular dependencies.
*
* @required
*
* @param Indexable_Repository $repository The indexable repository.
*
* @return void
*/
public function set_indexable_repository( Indexable_Repository $repository ) {
$this->repository = $repository;
}
/**
* Removes all shortcode tags from the given content.
*
* @codeCoverageIgnore It only wraps a WordPress function.
*
* @param string $content Content to remove all the shortcode tags from.
*
* @return string Content without shortcode tags.
*/
public function strip_shortcodes( $content ) {
return \strip_shortcodes( $content );
}
/**
* Retrieves the post excerpt (without tags).
*
* @codeCoverageIgnore It only wraps another helper method.
*
* @param int $post_id Post ID.
*
* @return string Post excerpt (without tags).
*/
public function get_the_excerpt( $post_id ) {
return $this->string->strip_all_tags( \get_the_excerpt( $post_id ) );
}
/**
* Retrieves the post type of the current post.
*
* @codeCoverageIgnore It only wraps a WordPress function.
*
* @param WP_Post|null $post The post.
*
* @return string|false Post type on success, false on failure.
*/
public function get_post_type( $post = null ) {
return \get_post_type( $post );
}
/**
* Retrieves the post title with fallback to `No title`.
*
* @param int $post_id Optional. Post ID.
*
* @return string The post title with fallback to `No title`.
*/
public function get_post_title_with_fallback( $post_id = 0 ) {
$post_title = \get_the_title( $post_id );
if ( $post_title ) {
return $post_title;
}
return \__( 'No title', 'wordpress-seo' );
}
/**
* Retrieves post data given a post ID.
*
* @codeCoverageIgnore It wraps a WordPress function.
*
* @param int $post_id Post ID.
*
* @return WP_Post|null The post.
*/
public function get_post( $post_id ) {
return \get_post( $post_id );
}
/**
* Updates the has_public_posts field on attachments for a post_parent.
*
* An attachment is represented by their post parent when:
* - The attachment has a post parent.
* - The attachment inherits the post status.
*
* @codeCoverageIgnore It relies too much on dependencies.
*
* @param int $post_parent Post ID.
* @param int $has_public_posts Whether the parent is public.
*
* @return bool Whether the update was successful.
*/
public function update_has_public_posts_on_attachments( $post_parent, $has_public_posts ) {
$query = $this->repository->query()
->select( 'id' )
->where( 'object_type', 'post' )
->where( 'object_sub_type', 'attachment' )
->where( 'post_status', 'inherit' )
->where( 'post_parent', $post_parent );
if ( $has_public_posts !== null ) {
$query->where_raw( '( has_public_posts IS NULL OR has_public_posts <> %s )', [ $has_public_posts ] );
}
else {
$query->where_not_null( 'has_public_posts' );
}
$results = $query->find_array();
if ( empty( $results ) ) {
return true;
}
$updated = $this->repository->query()
->set( 'has_public_posts', $has_public_posts )
->where_id_in( \wp_list_pluck( $results, 'id' ) )
->update_many();
return $updated !== false;
}
/**
* Determines if the post can be indexed.
*
* @param int $post_id Post ID to check.
*
* @return bool True if the post can be indexed.
*/
public function is_post_indexable( $post_id ) {
// Don't index posts which are not public (i.e. viewable).
$post_type = \get_post_type( $post_id );
if ( ! $this->post_type->is_of_indexable_post_type( $post_type ) ) {
return false;
}
// Don't index excluded post statuses.
if ( \in_array( \get_post_status( $post_id ), $this->get_excluded_post_statuses(), true ) ) {
return false;
}
// Don't index revisions of posts.
if ( \wp_is_post_revision( $post_id ) ) {
return false;
}
// Don't index autosaves that are not caught by the auto-draft check.
if ( \wp_is_post_autosave( $post_id ) ) {
return false;
}
return true;
}
/**
* Retrieves the list of excluded post statuses.
*
* @return array The excluded post statuses.
*/
public function get_excluded_post_statuses() {
return [ 'auto-draft' ];
}
/**
* Retrieves the list of public posts statuses.
*
* @return array The public post statuses.
*/
public function get_public_post_statuses() {
/**
* Filter: 'wpseo_public_post_statuses' - List of public post statuses.
*
* @param array $post_statuses Post status list, defaults to array( 'publish' ).
*/
return \apply_filters( 'wpseo_public_post_statuses', [ 'publish' ] );
}
}

View File

@@ -1,251 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WP_Post_Type;
/**
* A helper object for post types.
*/
class Post_Type_Helper {
/**
* The options helper.
*
* @var Options_Helper
*/
protected $options_helper;
/**
* Post_Type_Helper constructor.
*
* @param Options_Helper $options_helper The options helper.
*/
public function __construct( Options_Helper $options_helper ) {
$this->options_helper = $options_helper;
}
/**
* Checks if the request post type is public and indexable.
*
* @codeCoverageIgnore We have to write test when this method contains own code.
*
* @param string $post_type_name The name of the post type to lookup.
*
* @return bool True when post type is set to index.
*/
public function is_indexable( $post_type_name ) {
if ( $this->options_helper->get( 'disable-' . $post_type_name, false ) ) {
return false;
}
return ( $this->options_helper->get( 'noindex-' . $post_type_name, false ) === false );
}
/**
* Checks if the request post type has the Yoast Metabox enabled.
*
* @param string $post_type_name The name of the post type to lookup.
*
* @return bool True if metabox is enabled.
*/
public function has_metabox( $post_type_name ) {
return ( $this->options_helper->get( 'display-metabox-pt-' . $post_type_name, true ) === true );
}
/**
* Returns an array with the public post types.
*
* @codeCoverageIgnore It only wraps a WordPress function.
*
* @param string $output The output type to use.
*
* @return array Array with all the public post_types.
*/
public function get_public_post_types( $output = 'names' ) {
return \get_post_types( [ 'public' => true ], $output );
}
/**
* Returns an array with the accessible post types.
*
* An accessible post type is a post type that is public and isn't set as no-index (robots).
*
* @return array Array with all the accessible post_types.
*/
public function get_accessible_post_types() {
$post_types = \get_post_types( [ 'public' => true ] );
$post_types = \array_filter( $post_types, 'is_post_type_viewable' );
/**
* Filter: 'wpseo_accessible_post_types' - Allow changing the accessible post types.
*
* @param array $post_types The public post types.
*/
$post_types = \apply_filters( 'wpseo_accessible_post_types', $post_types );
// When the array gets messed up somewhere.
if ( ! \is_array( $post_types ) ) {
return [];
}
return $post_types;
}
/**
* Returns an array of post types that are excluded from being indexed for the
* indexables.
*
* @return array The excluded post types.
*/
public function get_excluded_post_types_for_indexables() {
/**
* Filter: 'wpseo_indexable_excluded_post_types' - Allows excluding posts of a certain post
* type from being saved to the indexable table.
*
* @param array $excluded_post_types The currently excluded post types that indexables will not be created for.
*/
$excluded_post_types = \apply_filters( 'wpseo_indexable_excluded_post_types', [] );
// Failsafe, to always make sure that `excluded_post_types` is an array.
if ( ! \is_array( $excluded_post_types ) ) {
return [];
}
return $excluded_post_types;
}
/**
* Checks if the post type is excluded.
*
* @param string $post_type The post type to check.
*
* @return bool If the post type is exclude.
*/
public function is_excluded( $post_type ) {
return \in_array( $post_type, $this->get_excluded_post_types_for_indexables(), true );
}
/**
* Checks if the post type with the given name has an archive page.
*
* @param WP_Post_Type|string $post_type The name of the post type to check.
*
* @return bool True when the post type has an archive page.
*/
public function has_archive( $post_type ) {
if ( \is_string( $post_type ) ) {
$post_type = \get_post_type_object( $post_type );
}
return ( ! empty( $post_type->has_archive ) );
}
/**
* Returns the post types that should be indexed.
*
* @return array The post types that should be indexed.
*/
public function get_indexable_post_types() {
$public_post_types = $this->get_public_post_types();
$excluded_post_types = $this->get_excluded_post_types_for_indexables();
$included_post_types = \array_diff( $public_post_types, $excluded_post_types );
return $this->filter_included_post_types( $included_post_types );
}
/**
* Returns all indexable post types with archive pages.
*
* @return array All post types which are indexable and have archive pages.
*/
public function get_indexable_post_archives() {
return \array_filter( $this->get_indexable_post_type_objects(), [ $this, 'has_archive' ] );
}
/**
* Filters the post types that are included to be indexed.
*
* @param array $included_post_types The post types that are included to be indexed.
*
* @return array The filtered post types that are included to be indexed.
*/
protected function filter_included_post_types( $included_post_types ) {
/**
* Filter: 'wpseo_indexable_forced_included_post_types' - Allows force including posts of a certain post
* type to be saved to the indexable table.
*
* @param array $included_post_types The currently included post types that indexables will be created for.
*/
$filtered_included_post_types = \apply_filters( 'wpseo_indexable_forced_included_post_types', $included_post_types );
if ( ! \is_array( $filtered_included_post_types ) ) {
// If the filter got misused, let's return the unfiltered array.
return \array_values( $included_post_types );
}
// Add sanity check to make sure everything is an actual post type.
foreach ( $filtered_included_post_types as $key => $post_type ) {
if ( ! \post_type_exists( $post_type ) ) {
unset( $filtered_included_post_types[ $key ] );
}
}
// `array_values`, to make sure that the keys are reset.
return \array_values( $filtered_included_post_types );
}
/**
* Checks if the given post type should be indexed.
*
* @param string $post_type The post type that is checked.
*
* @return bool
*/
public function is_of_indexable_post_type( $post_type ) {
$public_types = $this->get_indexable_post_types();
if ( ! \in_array( $post_type, $public_types, true ) ) {
return false;
}
return true;
}
/**
* Checks if the archive of a post type is indexable.
*
* @param string $post_type The post type to check.
*
* @return bool if the archive is indexable.
*/
public function is_post_type_archive_indexable( $post_type ) {
$public_type_objects = $this->get_indexable_post_archives();
$public_types = \array_map(
static function ( $post_type_object ) {
return $post_type_object->name;
},
$public_type_objects
);
return \in_array( $post_type, $public_types, true );
}
/**
* Returns an array of complete post type objects for all indexable post types.
*
* @return array List of indexable post type objects.
*/
public function get_indexable_post_type_objects() {
$post_type_objects = [];
$indexable_post_types = $this->get_indexable_post_types();
foreach ( $indexable_post_types as $post_type ) {
$post_type_object = \get_post_type_object( $post_type );
if ( ! empty( $post_type_object ) ) {
$post_type_objects[ $post_type ] = $post_type_object;
}
}
return $post_type_objects;
}
}

View File

@@ -1,47 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use stdClass;
/**
* A helper object for primary terms.
*/
class Primary_Term_Helper {
/**
* Generate the primary term taxonomies.
*
* @param int $post_id ID of the post.
*
* @return array The taxonomies.
*/
public function get_primary_term_taxonomies( $post_id ) {
$post_type = \get_post_type( $post_id );
$all_taxonomies = \get_object_taxonomies( $post_type, 'objects' );
$all_taxonomies = \array_filter( $all_taxonomies, [ $this, 'filter_hierarchical_taxonomies' ] );
/**
* Filters which taxonomies for which the user can choose the primary term.
*
* @param array $taxonomies An array of taxonomy objects that are primary_term enabled.
* @param string $post_type The post type for which to filter the taxonomies.
* @param array $all_taxonomies All taxonomies for this post types, even ones that don't have primary term
* enabled.
*/
$taxonomies = (array) \apply_filters( 'wpseo_primary_term_taxonomies', $all_taxonomies, $post_type, $all_taxonomies );
return $taxonomies;
}
/**
* Returns whether or not a taxonomy is hierarchical.
*
* @param stdClass $taxonomy Taxonomy object.
*
* @return bool True for hierarchical taxonomy.
*/
protected function filter_hierarchical_taxonomies( $taxonomy ) {
return (bool) $taxonomy->hierarchical;
}
}

View File

@@ -1,62 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for the Yoast products.
*/
class Product_Helper {
/**
* Gets the product name.
*
* @return string
*/
public function get_product_name() {
if ( $this->is_premium() ) {
return 'Yoast SEO Premium';
}
return 'Yoast SEO';
}
/**
* Gets the product name in the head section.
*
* @return string
*/
public function get_name() {
return $this->get_product_name() . ' plugin';
}
/**
* Checks if the installed version is Yoast SEO Premium.
*
* @return bool True when is premium.
*/
public function is_premium() {
return \defined( 'WPSEO_PREMIUM_FILE' );
}
/**
* Gets the Premium version if defined, returns null otherwise.
*
* @return string|null The Premium version or null when premium version is not defined.
*/
public function get_premium_version() {
if ( \defined( 'WPSEO_PREMIUM_VERSION' ) ) {
return \WPSEO_PREMIUM_VERSION;
}
return null;
}
/**
* Gets the version.
*
* @return string The version.
*/
public function get_version() {
return \WPSEO_VERSION;
}
}

View File

@@ -1,70 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for redirects.
*/
class Redirect_Helper {
/**
* Wraps wp_redirect to allow testing for redirects.
*
* @codeCoverageIgnore It only wraps a WordPress function.
*
* @param string $location The path to redirect to.
* @param int $status The status code to use.
* @param string $reason The reason for the redirect.
*
* @return void
*/
public function do_unsafe_redirect( $location, $status = 302, $reason = 'Yoast SEO' ) {
// phpcs:ignore WordPress.Security.SafeRedirect -- intentional, function has been renamed to make unsafe more clear.
\wp_redirect( $location, $status, $reason );
exit;
}
/**
* Wraps wp_safe_redirect to allow testing for safe redirects.
*
* @codeCoverageIgnore It only wraps a WordPress function.
*
* @param string $location The path to redirect to.
* @param int $status The status code to use.
* @param string $reason The reason for the redirect.
*
* @return void
*/
public function do_safe_redirect( $location, $status = 302, $reason = 'Yoast SEO' ) {
\wp_safe_redirect( $location, $status, $reason );
exit;
}
/**
* Sets a header.
* This is a tiny helper function to enable better testing.
*
* @codeCoverageIgnore It only wraps a WordPress function.
*
* @param string $header The header to set.
*
* @return void
*/
public function set_header( $header ) {
\header( $header );
}
/**
* Removes a header.
* This is a tiny helper function to enable better testing.
*
* @codeCoverageIgnore It only wraps a WordPress function.
*
* @param string $header The header to remove.
*
* @return void
*/
public function remove_header( $header ) {
\header_remove( $header );
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for the request state.
*/
class Request_Helper {
/**
* Checks if the current request is a REST request.
*
* @return bool True when the current request is a REST request.
*/
public function is_rest_request() {
return \defined( 'REST_REQUEST' ) && \REST_REQUEST === true;
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* Represents a file helper.
*/
class Require_File_Helper {
/**
* Activates the plugin based on the given plugin file.
*
* @param string $path The path to the required file.
*/
public function require_file_once( $path ) {
require_once $path;
}
}

View File

@@ -1,78 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\SEO\Models\Indexable;
/**
* A helper object for the robots meta tag.
*/
class Robots_Helper {
/**
* Holds the Post_Type_Helper.
*
* @var Post_Type_Helper
*/
protected $post_type_helper;
/**
* Holds the Taxonomy_Helper.
*
* @var Taxonomy_Helper
*/
protected $taxonomy_helper;
/**
* Constructs a Score_Helper.
*
* @param Post_Type_Helper $post_type_helper The Post_Type_Helper.
* @param Taxonomy_Helper $taxonomy_helper The Taxonomy_Helper.
*/
public function __construct(
Post_Type_Helper $post_type_helper,
Taxonomy_Helper $taxonomy_helper
) {
$this->post_type_helper = $post_type_helper;
$this->taxonomy_helper = $taxonomy_helper;
}
/**
* Retrieves whether the Indexable is indexable.
*
* @param Indexable $indexable The Indexable.
*
* @return bool Whether the Indexable is indexable.
*/
public function is_indexable( Indexable $indexable ) {
if ( $indexable->is_robots_noindex === null ) {
// No individual value set, check the global setting.
switch ( $indexable->object_type ) {
case 'post':
return $this->post_type_helper->is_indexable( $indexable->object_sub_type );
case 'term':
return $this->taxonomy_helper->is_indexable( $indexable->object_sub_type );
}
}
return $indexable->is_robots_noindex === false;
}
/**
* Sets the robots index to noindex.
*
* @param array $robots The current robots value.
*
* @return array The altered robots string.
*/
public function set_robots_no_index( $robots ) {
if ( ! \is_array( $robots ) ) {
\_deprecated_argument( __METHOD__, '14.1', '$robots has to be a key-value paired array.' );
return $robots;
}
$robots['index'] = 'noindex';
return $robots;
}
}

View File

@@ -1,108 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\SEO\Values\Robots\User_Agent_List;
/**
* A helper object for the robots txt file.
*/
class Robots_Txt_Helper {
/**
* Holds a list of user agents with directives.
*
* @var User_Agent_List
*/
protected $robots_txt_user_agents;
/**
* Holds an array with absolute URLs of sitemaps.
*
* @var array
*/
protected $robots_txt_sitemaps;
/**
* Constructor for Robots_Txt_Helper.
*/
public function __construct() {
$this->robots_txt_user_agents = new User_Agent_List();
$this->robots_txt_sitemaps = [];
}
/**
* Add a disallow rule for a specific user agent if it does not exist yet.
*
* @param string $user_agent The user agent to add the disallow rule to.
* @param string $path The path to add as a disallow rule.
*
* @return void
*/
public function add_disallow( $user_agent, $path ) {
$user_agent_container = $this->robots_txt_user_agents->get_user_agent( $user_agent );
$user_agent_container->add_disallow_directive( $path );
}
/**
* Add an allow rule for a specific user agent if it does not exist yet.
*
* @param string $user_agent The user agent to add the allow rule to.
* @param string $path The path to add as a allow rule.
*
* @return void
*/
public function add_allow( $user_agent, $path ) {
$user_agent_container = $this->robots_txt_user_agents->get_user_agent( $user_agent );
$user_agent_container->add_allow_directive( $path );
}
/**
* Add sitemap to robots.txt if it does not exist yet.
*
* @param string $absolute_path The absolute path to the sitemap to add.
*
* @return void
*/
public function add_sitemap( $absolute_path ) {
if ( ! \in_array( $absolute_path, $this->robots_txt_sitemaps, true ) ) {
$this->robots_txt_sitemaps[] = $absolute_path;
}
}
/**
* Get all registered disallow directives per user agent.
*
* @return array The registered disallow directives per user agent.
*/
public function get_disallow_directives() {
return $this->robots_txt_user_agents->get_disallow_directives();
}
/**
* Get all registered allow directives per user agent.
*
* @return array The registered allow directives per user agent.
*/
public function get_allow_directives() {
return $this->robots_txt_user_agents->get_allow_directives();
}
/**
* Get all registered sitemap rules.
*
* @return array The registered sitemap rules.
*/
public function get_sitemap_rules() {
return $this->robots_txt_sitemaps;
}
/**
* Get all registered user agents
*
* @return array The registered user agents.
*/
public function get_robots_txt_user_agents() {
return $this->robots_txt_user_agents->get_user_agents();
}
}

View File

@@ -1,39 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WPSEO_Utils;
/**
* A helper object for sanitization.
*/
class Sanitization_Helper {
/**
* Emulate the WP native sanitize_text_field function in a %%variable%% safe way.
*
* @codeCoverageIgnore We have to write test when this method contains own code.
*
* @param string $value String value to sanitize.
*
* @return string The sanitized string.
*/
public function sanitize_text_field( $value ) {
return WPSEO_Utils::sanitize_text_field( $value );
}
/**
* Sanitize a url for saving to the database.
* Not to be confused with the old native WP function.
*
* @codeCoverageIgnore We have to write test when this method contains own code.
*
* @param string $value String URL value to sanitize.
* @param array $allowed_protocols Optional set of allowed protocols.
*
* @return string The sanitized URL.
*/
public function sanitize_url( $value, $allowed_protocols = [ 'http', 'https' ] ) {
return WPSEO_Utils::sanitize_url( $value, $allowed_protocols );
}
}

View File

@@ -1,35 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Schema;
/**
* Class Article_Helper.
*/
class Article_Helper {
/**
* Determines whether a given post type should have Article schema.
*
* @param string|null $post_type Post type to check.
*
* @return bool True if it has Article schema, false if not.
*/
public function is_article_post_type( $post_type = null ) {
if ( \is_null( $post_type ) ) {
$post_type = \get_post_type();
}
return $this->is_author_supported( $post_type );
}
/**
* Checks whether author is supported for the passed object sub type.
*
* @param string $object_sub_type The sub type of the object to check author support for.
*
* @return bool True if author is supported for the passed object sub type.
*/
public function is_author_supported( $object_sub_type ) {
return \post_type_supports( $object_sub_type, 'author' );
}
}

View File

@@ -1,76 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Schema;
/**
* Class HTML_Helper.
*/
class HTML_Helper {
/**
* Sanitizes a HTML string by stripping all tags except headings, breaks, lists, links, paragraphs and formatting.
*
* @param string $html The original HTML.
*
* @return string The sanitized HTML.
*/
public function sanitize( $html ) {
if ( ! $this->is_non_empty_string_or_stringable( $html ) ) {
if ( \is_int( $html ) || \is_float( $html ) ) {
return (string) $html;
}
return '';
}
return \strip_tags( $html, '<h1><h2><h3><h4><h5><h6><br><ol><ul><li><a><p><b><strong><i><em>' );
}
/**
* Strips the tags in a smart way.
*
* @param string $html The original HTML.
*
* @return string The sanitized HTML.
*/
public function smart_strip_tags( $html ) {
if ( ! $this->is_non_empty_string_or_stringable( $html ) ) {
if ( \is_int( $html ) || \is_float( $html ) ) {
return (string) $html;
}
return '';
}
// Replace all new lines with spaces.
$html = \preg_replace( '/(\r|\n)/', ' ', $html );
// Replace <br> tags with spaces.
$html = \preg_replace( '/<br(\s*)?\/?>/i', ' ', $html );
// Replace closing </p> and other tags with the same tag with a space after it, so we don't end up connecting words when we remove them later.
$html = \preg_replace( '/<\/(p|div|h\d)>/i', '</$1> ', $html );
// Replace list items with list identifiers so it still looks natural.
$html = \preg_replace( '/(<li[^>]*>)/i', '$1• ', $html );
// Strip tags.
$html = \wp_strip_all_tags( $html );
// Replace multiple spaces with one space.
$html = \preg_replace( '!\s+!', ' ', $html );
return \trim( $html );
}
/**
* Verifies that the received input is either a string or stringable object.
*
* @param string $html The original HTML.
*
* @return bool
*/
private function is_non_empty_string_or_stringable( $html ) {
return ( \is_string( $html ) || ( \is_object( $html ) && \method_exists( $html, '__toString' ) ) ) && ! empty( $html );
}
}

View File

@@ -1,29 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Schema;
use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Context\Meta_Tags_Context;
/**
* Schema utility functions.
*/
class ID_Helper {
/**
* Retrieve a users Schema ID.
*
* @param int $user_id The ID of the User you need a Schema ID for.
* @param Meta_Tags_Context $context A value object with context variables.
*
* @return string The user's schema ID.
*/
public function get_user_schema_id( $user_id, $context ) {
$user = \get_userdata( $user_id );
if ( \is_a( $user, 'WP_User' ) ) {
return $context->site_url . Schema_IDs::PERSON_HASH . \wp_hash( $user->user_login . $user_id );
}
return '';
}
}

View File

@@ -1,205 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Schema;
use Yoast\WP\SEO\Helpers\Image_Helper as Main_Image_Helper;
/**
* Class Image_Helper.
*/
class Image_Helper {
/**
* The HTML helper.
*
* @var HTML_Helper
*/
private $html;
/**
* The language helper.
*
* @var Language_Helper
*/
private $language;
/**
* Represents the main image helper.
*
* @var Main_Image_Helper
*/
private $image;
/**
* Image_Helper constructor.
*
* @codeCoverageIgnore It handles dependencies.
*
* @param HTML_Helper $html The HTML helper.
* @param Language_Helper $language The language helper.
* @param Main_Image_Helper $image The 'main' image helper.
*/
public function __construct( HTML_Helper $html, Language_Helper $language, Main_Image_Helper $image ) {
$this->html = $html;
$this->language = $language;
$this->image = $image;
}
/**
* Find an image based on its URL and generate a Schema object for it.
*
* @param string $schema_id The `@id` to use for the returned image.
* @param string $url The image URL to base our object on.
* @param string $caption An optional caption.
* @param bool $add_hash Whether a hash will be added as a suffix in the @id.
* @param bool $use_link_table Whether the SEO Links table will be used to retrieve the id.
*
* @return array Schema ImageObject array.
*/
public function generate_from_url( $schema_id, $url, $caption = '', $add_hash = false, $use_link_table = true ) {
$attachment_id = $this->image->get_attachment_by_url( $url, $use_link_table );
if ( $attachment_id > 0 ) {
return $this->generate_from_attachment_id( $schema_id, $attachment_id, $caption, $add_hash );
}
return $this->simple_image_object( $schema_id, $url, $caption, $add_hash );
}
/**
* Retrieve data about an image from the database and use it to generate a Schema object.
*
* @param string $schema_id The `@id` to use for the returned image.
* @param int $attachment_id The attachment to retrieve data from.
* @param string $caption The caption string, if there is one.
* @param bool $add_hash Whether a hash will be added as a suffix in the @id.
*
* @return array Schema ImageObject array.
*/
public function generate_from_attachment_id( $schema_id, $attachment_id, $caption = '', $add_hash = false ) {
$data = $this->generate_object();
$url = $this->image->get_attachment_image_url( $attachment_id, 'full' );
$id_suffix = ( $add_hash ) ? \md5( $url ) : '';
$data['@id'] = $schema_id . $id_suffix;
$data['url'] = $url;
$data['contentUrl'] = $url;
$data = $this->add_image_size( $data, $attachment_id );
$data = $this->add_caption( $data, $attachment_id, $caption );
return $data;
}
/**
* Retrieve data about an image from the database and use it to generate a Schema object.
*
* @param string $schema_id The `@id` to use for the returned image.
* @param array $attachment_meta The attachment metadata.
* @param string $caption The caption string, if there is one.
* @param bool $add_hash Whether a hash will be added as a suffix in the @id.
*
* @return array Schema ImageObject array.
*/
public function generate_from_attachment_meta( $schema_id, $attachment_meta, $caption = '', $add_hash = false ) {
$data = $this->generate_object();
$id_suffix = ( $add_hash ) ? \md5( $attachment_meta['url'] ) : '';
$data['@id'] = $schema_id . $id_suffix;
$data['url'] = $attachment_meta['url'];
$data['contentUrl'] = $data['url'];
$data['width'] = $attachment_meta['width'];
$data['height'] = $attachment_meta['height'];
if ( ! empty( $caption ) ) {
$data['caption'] = $this->html->smart_strip_tags( $caption );
}
return $data;
}
/**
* If we can't find $url in our database, we output a simple ImageObject.
*
* @param string $schema_id The `@id` to use for the returned image.
* @param string $url The image URL.
* @param string $caption A caption, if set.
* @param bool $add_hash Whether a hash will be added as a suffix in the @id.
*
* @return array Schema ImageObject array.
*/
public function simple_image_object( $schema_id, $url, $caption = '', $add_hash = false ) {
$data = $this->generate_object();
$id_suffix = ( $add_hash ) ? \md5( $url ) : '';
$data['@id'] = $schema_id . $id_suffix;
$data['url'] = $url;
$data['contentUrl'] = $url;
if ( ! empty( $caption ) ) {
$data['caption'] = $this->html->smart_strip_tags( $caption );
}
return $data;
}
/**
* Retrieves an image's caption if set, or uses the alt tag if that's set.
*
* @param array $data An ImageObject Schema array.
* @param int $attachment_id Attachment ID.
* @param string $caption The caption string, if there is one.
*
* @return array An imageObject with width and height set if available.
*/
private function add_caption( $data, $attachment_id, $caption = '' ) {
if ( $caption !== '' ) {
$data['caption'] = $caption;
return $data;
}
$caption = $this->image->get_caption( $attachment_id );
if ( ! empty( $caption ) ) {
$data['caption'] = $this->html->smart_strip_tags( $caption );
return $data;
}
return $data;
}
/**
* Generates our bare bone ImageObject.
*
* @return array an empty ImageObject
*/
private function generate_object() {
$data = [
'@type' => 'ImageObject',
];
$data = $this->language->add_piece_language( $data );
return $data;
}
/**
* Adds image's width and height.
*
* @param array $data An ImageObject Schema array.
* @param int $attachment_id Attachment ID.
*
* @return array An imageObject with width and height set if available.
*/
private function add_image_size( $data, $attachment_id ) {
$image_meta = $this->image->get_metadata( $attachment_id );
if ( empty( $image_meta['width'] ) || empty( $image_meta['height'] ) ) {
return $data;
}
$data['width'] = $image_meta['width'];
$data['height'] = $image_meta['height'];
return $data;
}
}

View File

@@ -1,32 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Schema;
/**
* Class Language_Helper.
*/
class Language_Helper {
/**
* Adds the `inLanguage` property to a Schema piece.
*
* Must use one of the language codes from the IETF BCP 47 standard. The
* language tag syntax is made of one or more subtags separated by a hyphen
* e.g. "en", "en-US", "zh-Hant-CN".
*
* @param array $data The Schema piece data.
*
* @return array The Schema piece data with added language property
*/
public function add_piece_language( $data ) {
/**
* Filter: 'wpseo_schema_piece_language' - Allow changing the Schema piece language.
*
* @param string $type The Schema piece language.
* @param array $data The Schema piece data.
*/
$data['inLanguage'] = \apply_filters( 'wpseo_schema_piece_language', \get_bloginfo( 'language' ), $data );
return $data;
}
}

View File

@@ -1,135 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Schema;
use Closure;
use WPSEO_Replace_Vars;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Context\Meta_Tags_Context;
use Yoast\WP\SEO\Helpers\Date_Helper;
use Yoast\WP\SEO\Presentations\Indexable_Presentation;
/**
* Registers the Schema replace variables and exposes a method to replace variables on a Schema graph.
*/
class Replace_Vars_Helper {
use No_Conditionals;
/**
* The replace vars.
*
* @var WPSEO_Replace_Vars
*/
protected $replace_vars;
/**
* The Schema ID helper.
*
* @var ID_Helper
*/
protected $id_helper;
/**
* The date helper.
*
* @var Date_Helper
*/
protected $date_helper;
/**
* Replace_Vars_Helper constructor.
*
* @param WPSEO_Replace_Vars $replace_vars The replace vars.
* @param ID_Helper $id_helper The Schema ID helper.
* @param Date_Helper $date_helper The date helper.
*/
public function __construct(
WPSEO_Replace_Vars $replace_vars,
ID_Helper $id_helper,
Date_Helper $date_helper
) {
$this->replace_vars = $replace_vars;
$this->id_helper = $id_helper;
$this->date_helper = $date_helper;
}
/**
* Replaces the variables.
*
* @param array $schema_data The Schema data.
* @param Indexable_Presentation $presentation The indexable presentation.
*
* @return array The array with replaced vars.
*/
public function replace( array $schema_data, Indexable_Presentation $presentation ) {
foreach ( $schema_data as $key => $value ) {
if ( \is_array( $value ) ) {
$schema_data[ $key ] = $this->replace( $value, $presentation );
continue;
}
$schema_data[ $key ] = $this->replace_vars->replace( $value, $presentation->source );
}
return $schema_data;
}
/**
* Registers the Schema-related replace vars.
*
* @param Meta_Tags_Context $context The meta tags context.
*
* @return void
*/
public function register_replace_vars( $context ) {
$replace_vars = [
'main_schema_id' => $context->main_schema_id,
'author_id' => $this->id_helper->get_user_schema_id( $context->indexable->author_id, $context ),
'person_id' => $context->site_url . Schema_IDs::PERSON_HASH,
'primary_image_id' => $context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH,
'webpage_id' => $context->main_schema_id,
'website_id' => $context->site_url . Schema_IDs::WEBSITE_HASH,
'organization_id' => $context->site_url . Schema_IDs::ORGANIZATION_HASH,
];
if ( $context->post ) {
// Post does not always exist, e.g. on term pages.
$replace_vars['post_date'] = $this->date_helper->format( $context->post->post_date, \DATE_ATOM );
}
foreach ( $replace_vars as $var => $value ) {
$this->register_replacement( $var, $value );
}
}
/**
* Registers a replace var and its value.
*
* @param string $variable The replace variable.
* @param string $value The value that the variable should be replaced with.
*
* @return void
*/
protected function register_replacement( $variable, $value ) {
$this->replace_vars->safe_register_replacement(
$variable,
$this->get_identity_function( $value )
);
}
/**
* Returns an anonymous function that in turn just returns the given value.
*
* @param mixed $value The value that the function should return.
*
* @return Closure A function that returns the given value.
*/
protected function get_identity_function( $value ) {
return static function () use ( $value ) {
return $value;
};
}
}

View File

@@ -1,98 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WPSEO_Rank;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Presenters\Score_Icon_Presenter;
/**
* A helper object for score icons.
*/
class Score_Icon_Helper {
/**
* Holds the Robots_Helper.
*
* @var Robots_Helper
*/
protected $robots_helper;
/**
* Constructs a Score_Helper.
*
* @param Robots_Helper $robots_helper The Robots_Helper.
*/
public function __construct( Robots_Helper $robots_helper ) {
$this->robots_helper = $robots_helper;
}
/**
* Creates a Score_Icon_Presenter for the readability analysis.
*
* @param int $score The readability analysis score.
* @param string $extra_class Optional. Any extra class.
*
* @return Score_Icon_Presenter The Score_Icon_Presenter.
*/
public function for_readability( $score, $extra_class = '' ) {
$rank = WPSEO_Rank::from_numeric_score( (int) $score );
$class = $rank->get_css_class();
if ( $extra_class ) {
$class .= " $extra_class";
}
return new Score_Icon_Presenter( $rank->get_label(), $class );
}
/**
* Creates a Score_Icon_Presenter for the inclusive language analysis.
*
* @param int $score The inclusive language analysis score.
* @param string $extra_class Optional. Any extra class.
*
* @return Score_Icon_Presenter The Score_Icon_Presenter.
*/
public function for_inclusive_language( $score, $extra_class = '' ) {
$rank = WPSEO_Rank::from_numeric_score( (int) $score );
$class = $rank->get_css_class();
if ( $extra_class ) {
$class .= " $extra_class";
}
return new Score_Icon_Presenter( $rank->get_inclusive_language_label(), $class );
}
/**
* Creates a Score_Icon_Presenter for the SEO analysis from an indexable.
*
* @param Indexable|false $indexable The Indexable.
* @param string $extra_class Optional. Any extra class.
* @param string $no_index_title Optional. Override the title when not indexable.
*
* @return Score_Icon_Presenter The Score_Icon_Presenter.
*/
public function for_seo( $indexable, $extra_class = '', $no_index_title = '' ) {
$is_indexable = $indexable && $this->robots_helper->is_indexable( $indexable );
if ( ! $is_indexable ) {
$rank = new WPSEO_Rank( WPSEO_Rank::NO_INDEX );
$title = empty( $no_index_title ) ? $rank->get_label() : $no_index_title;
}
elseif ( empty( $indexable && $indexable->primary_focus_keyword ) ) {
$rank = new WPSEO_Rank( WPSEO_Rank::BAD );
$title = \__( 'Focus keyphrase not set', 'wordpress-seo' );
}
else {
$rank = WPSEO_Rank::from_numeric_score( ( $indexable ) ? $indexable->primary_focus_keyword_score : 0 );
$title = $rank->get_label();
}
$class = $rank->get_css_class();
if ( $extra_class ) {
$class .= " $extra_class";
}
return new Score_Icon_Presenter( $title, $class );
}
}

View File

@@ -1,152 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* Helper to get shortlinks for Yoast SEO.
*/
class Short_Link_Helper {
/**
* The options helper.
*
* @var Options_Helper
*/
protected $options_helper;
/**
* The product helper.
*
* @var Product_Helper
*/
protected $product_helper;
/**
* Short_Link_Helper constructor.
*
* @param Options_Helper $options_helper The options helper.
* @param Product_Helper $product_helper The product helper.
*/
public function __construct(
Options_Helper $options_helper,
Product_Helper $product_helper
) {
$this->options_helper = $options_helper;
$this->product_helper = $product_helper;
}
/**
* Builds a URL to use in the plugin as shortlink.
*
* @param string $url The URL to build upon.
*
* @return string The final URL.
*/
public function build( $url ) {
return \add_query_arg( $this->collect_additional_shortlink_data(), $url );
}
/**
* Returns a version of the URL with a utm_content with the current version.
*
* @param string $url The URL to build upon.
*
* @return string The final URL.
*/
public function get( $url ) {
return $this->build( $url );
}
/**
* Echoes a version of the URL with a utm_content with the current version.
*
* @param string $url The URL to build upon.
*
* @return void
*/
public function show( $url ) {
echo \esc_url( $this->get( $url ) );
}
/**
* Gets the shortlink's query params.
*
* @return array The shortlink's query params.
*/
public function get_query_params() {
return $this->collect_additional_shortlink_data();
}
/**
* Gets the current site's PHP version, without the extra info.
*
* @return string The PHP version.
*/
private function get_php_version() {
$version = \explode( '.', \PHP_VERSION );
return (int) $version[0] . '.' . (int) $version[1];
}
/**
* Gets the current site's platform version.
*
* @return string The wp_version.
*/
protected function get_platform_version() {
return $GLOBALS['wp_version'];
}
/**
* Collects the additional data necessary for the shortlink.
*
* @return array The shortlink data.
*/
protected function collect_additional_shortlink_data() {
$data = [
'php_version' => $this->get_php_version(),
'platform' => 'wordpress',
'platform_version' => $this->get_platform_version(),
'software' => $this->get_software(),
'software_version' => \WPSEO_VERSION,
'days_active' => $this->get_days_active(),
'user_language' => \get_user_locale(),
];
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$admin_page = \sanitize_text_field( \wp_unslash( $_GET['page'] ) );
if ( ! empty( $admin_page ) ) {
$data['screen'] = $admin_page;
}
}
return $data;
}
/**
* Get our software and whether it's active or not.
*
* @return string The software name.
*/
protected function get_software() {
if ( $this->product_helper->is_premium() ) {
return 'premium';
}
return 'free';
}
/**
* Gets the number of days the plugin has been active.
*
* @return int The number of days the plugin is active.
*/
protected function get_days_active() {
$date_activated = $this->options_helper->get( 'first_activated_on' );
$datediff = ( \time() - $date_activated );
return (int) \round( $datediff / \DAY_IN_SECONDS );
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for site options.
*/
class Site_Helper {
/**
* Retrieves the site name.
*
* @return string
*/
public function get_site_name() {
return \wp_strip_all_tags( \get_bloginfo( 'name' ), true );
}
/**
* Checks if the current installation is a multisite and there has been a switch
* between the set multisites.
*
* @return bool True when there was a switch between the multisites.
*/
public function is_multisite_and_switched() {
return \is_multisite() && \ms_is_switched();
}
}

View File

@@ -1,330 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* Class Social_Profiles_Helper.
*/
class Social_Profiles_Helper {
/**
* The fields for the person social profiles payload.
*
* @var array
*/
private $person_social_profile_fields = [
'facebook' => 'get_non_valid_url',
'instagram' => 'get_non_valid_url',
'linkedin' => 'get_non_valid_url',
'myspace' => 'get_non_valid_url',
'pinterest' => 'get_non_valid_url',
'soundcloud' => 'get_non_valid_url',
'tumblr' => 'get_non_valid_url',
'twitter' => 'get_non_valid_twitter',
'youtube' => 'get_non_valid_url',
'wikipedia' => 'get_non_valid_url',
];
/**
* The fields for the organization social profiles payload.
*
* @var array
*/
private $organization_social_profile_fields = [
'facebook_site' => 'get_non_valid_url',
'twitter_site' => 'get_non_valid_twitter',
'other_social_urls' => 'get_non_valid_url_array',
];
/**
* The Options_Helper instance.
*
* @var Options_Helper
*/
protected $options_helper;
/**
* Social_Profiles_Helper constructor.
*
* @param Options_Helper $options_helper The WPSEO options helper.
*/
public function __construct( Options_Helper $options_helper ) {
$this->options_helper = $options_helper;
}
/**
* Gets the person social profile fields supported by us.
*
* @return array The social profile fields.
*/
public function get_person_social_profile_fields() {
/**
* Filter: Allow changes to the social profiles fields available for a person.
*
* @param array $person_social_profile_fields The social profile fields.
*/
$person_social_profile_fields = \apply_filters( 'wpseo_person_social_profile_fields', $this->person_social_profile_fields );
return (array) $person_social_profile_fields;
}
/**
* Gets the organization social profile fields supported by us.
*
* @return array The organization profile fields.
*/
public function get_organization_social_profile_fields() {
/**
* Filter: Allow changes to the social profiles fields available for an organization.
*
* @param array $organization_social_profile_fields The social profile fields.
*/
$organization_social_profile_fields = \apply_filters( 'wpseo_organization_social_profile_fields', $this->organization_social_profile_fields );
return (array) $organization_social_profile_fields;
}
/**
* Gets the person social profiles stored in the database.
*
* @param int $person_id The id of the person.
*
* @return array The person's social profiles.
*/
public function get_person_social_profiles( $person_id ) {
$social_profile_fields = \array_keys( $this->get_person_social_profile_fields() );
$person_social_profiles = \array_combine( $social_profile_fields, \array_fill( 0, \count( $social_profile_fields ), '' ) );
// If no person has been selected, $person_id is set to false.
if ( \is_numeric( $person_id ) ) {
foreach ( \array_keys( $person_social_profiles ) as $field_name ) {
$value = \get_user_meta( $person_id, $field_name, true );
// If $person_id is an integer but does not represent a valid user, get_user_meta returns false.
if ( ! \is_bool( $value ) ) {
$person_social_profiles[ $field_name ] = $value;
}
}
}
return $person_social_profiles;
}
/**
* Gets the organization social profiles stored in the database.
*
* @return array<string, string> The social profiles for the organization.
*/
public function get_organization_social_profiles() {
$organization_social_profiles_fields = \array_keys( $this->get_organization_social_profile_fields() );
$organization_social_profiles = [];
foreach ( $organization_social_profiles_fields as $field_name ) {
$default_value = '';
if ( $field_name === 'other_social_urls' ) {
$default_value = [];
}
$social_profile_value = $this->options_helper->get( $field_name, $default_value );
if ( $field_name === 'other_social_urls' ) {
$other_social_profiles = \array_map( '\urldecode', \array_filter( $social_profile_value ) );
$organization_social_profiles['other_social_urls'] = $other_social_profiles;
continue;
}
if ( $field_name === 'twitter_site' && $social_profile_value !== '' ) {
$organization_social_profiles[ $field_name ] = 'https://x.com/' . $social_profile_value;
continue;
}
$organization_social_profiles[ $field_name ] = \urldecode( $social_profile_value );
}
return $organization_social_profiles;
}
/**
* Stores the values for the person's social profiles.
*
* @param int $person_id The id of the person to edit.
* @param array $social_profiles The array of the person's social profiles to be set.
*
* @return string[] An array of field names which failed to be saved in the db.
*/
public function set_person_social_profiles( $person_id, $social_profiles ) {
$failures = [];
$person_social_profile_fields = $this->get_person_social_profile_fields();
// First validate all social profiles, before even attempting to save them.
foreach ( $person_social_profile_fields as $field_name => $validation_method ) {
if ( ! isset( $social_profiles[ $field_name ] ) ) {
// Just skip social profiles that were not passed.
continue;
}
if ( $social_profiles[ $field_name ] === '' ) {
continue;
}
$value_to_validate = $social_profiles[ $field_name ];
$failures = \array_merge( $failures, \call_user_func( [ $this, $validation_method ], $value_to_validate, $field_name ) );
}
if ( ! empty( $failures ) ) {
return $failures;
}
// All social profiles look good, now let's try to save them.
foreach ( \array_keys( $person_social_profile_fields ) as $field_name ) {
if ( ! isset( $social_profiles[ $field_name ] ) ) {
// Just skip social profiles that were not passed.
continue;
}
$social_profiles[ $field_name ] = $this->sanitize_social_field( $social_profiles[ $field_name ] );
\update_user_meta( $person_id, $field_name, $social_profiles[ $field_name ] );
}
return $failures;
}
/**
* Stores the values for the organization's social profiles.
*
* @param array $social_profiles An array with the social profiles values to be saved in the db.
*
* @return string[] An array of field names which failed to be saved in the db.
*/
public function set_organization_social_profiles( $social_profiles ) {
$failures = [];
$organization_social_profile_fields = $this->get_organization_social_profile_fields();
// First validate all social profiles, before even attempting to save them.
foreach ( $organization_social_profile_fields as $field_name => $validation_method ) {
if ( ! isset( $social_profiles[ $field_name ] ) ) {
// Just skip social profiles that were not passed.
continue;
}
$social_profiles[ $field_name ] = $this->sanitize_social_field( $social_profiles[ $field_name ] );
$value_to_validate = $social_profiles[ $field_name ];
$failures = \array_merge( $failures, \call_user_func( [ $this, $validation_method ], $value_to_validate, $field_name ) );
}
if ( ! empty( $failures ) ) {
return $failures;
}
// All social profiles look good, now let's try to save them.
foreach ( \array_keys( $organization_social_profile_fields ) as $field_name ) {
if ( ! isset( $social_profiles[ $field_name ] ) ) {
// Just skip social profiles that were not passed.
continue;
}
// Remove empty strings in Other Social URLs.
if ( $field_name === 'other_social_urls' ) {
$other_social_urls = \array_filter(
$social_profiles[ $field_name ],
static function ( $other_social_url ) {
return $other_social_url !== '';
}
);
$social_profiles[ $field_name ] = \array_values( $other_social_urls );
}
$result = $this->options_helper->set( $field_name, $social_profiles[ $field_name ] );
if ( ! $result ) {
/**
* The value for Twitter might have been sanitised from URL to username.
* If so, $result will be false. We should check if the option value is part of the received value.
*/
if ( $field_name === 'twitter_site' ) {
$current_option = $this->options_helper->get( $field_name );
if ( ! \strpos( $social_profiles[ $field_name ], 'twitter.com/' . $current_option ) && ! \strpos( $social_profiles[ $field_name ], 'x.com/' . $current_option ) ) {
$failures[] = $field_name;
}
}
else {
$failures[] = $field_name;
}
}
}
if ( ! empty( $failures ) ) {
return $failures;
}
return [];
}
/**
* Returns a sanitized social field.
*
* @param string|array $social_field The social field to sanitize.
*
* @return string|array The sanitized social field.
*/
protected function sanitize_social_field( $social_field ) {
if ( \is_array( $social_field ) ) {
foreach ( $social_field as $key => $value ) {
$social_field[ $key ] = \sanitize_text_field( $value );
}
return $social_field;
}
return \sanitize_text_field( $social_field );
}
/**
* Checks if url is not valid and returns the name of the setting if it's not.
*
* @param string $url The url to be validated.
* @param string $url_setting The name of the setting to be updated with the url.
*
* @return array An array with the setting that the non-valid url is about to update.
*/
protected function get_non_valid_url( $url, $url_setting ) {
if ( $this->options_helper->is_social_url_valid( $url ) ) {
return [];
}
return [ $url_setting ];
}
/**
* Checks if urls in an array are not valid and return the name of the setting if one of them is not, including the non-valid url's index in the array
*
* @param array $urls The urls to be validated.
* @param string $urls_setting The name of the setting to be updated with the urls.
*
* @return array An array with the settings that the non-valid urls are about to update, suffixed with a dash-separated index of the positions of those settings, eg. other_social_urls-2.
*/
protected function get_non_valid_url_array( $urls, $urls_setting ) {
$non_valid_url_array = [];
foreach ( $urls as $key => $url ) {
if ( ! $this->options_helper->is_social_url_valid( $url ) ) {
$non_valid_url_array[] = $urls_setting . '-' . $key;
}
}
return $non_valid_url_array;
}
/**
* Checks if the twitter value is not valid and returns the name of the setting if it's not.
*
* @param array $twitter_site The twitter value to be validated.
* @param string $twitter_setting The name of the twitter setting to be updated with the value.
*
* @return array An array with the setting that the non-valid twitter value is about to update.
*/
protected function get_non_valid_twitter( $twitter_site, $twitter_setting ) {
if ( $this->options_helper->is_twitter_id_valid( $twitter_site, false ) ) {
return [];
}
return [ $twitter_setting ];
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for string operations.
*/
class String_Helper {
/**
* Strips all HTML tags including script and style.
*
* @param string $text The text to strip the tags from.
*
* @return string The processed string.
*/
public function strip_all_tags( $text ) {
return \wp_strip_all_tags( $text );
}
/**
* Standardize whitespace in a string.
*
* Replace line breaks, carriage returns, tabs with a space, then remove double spaces.
*
* @param string $text Text input to standardize.
*
* @return string
*/
public function standardize_whitespace( $text ) {
return \trim( \str_replace( ' ', ' ', \str_replace( [ "\t", "\n", "\r", "\f" ], ' ', $text ) ) );
}
/**
* First strip out registered and enclosing shortcodes using native WordPress strip_shortcodes function.
* Then strip out the shortcodes with a filthy regex, because people don't properly register their shortcodes.
*
* @param string $text Input string that might contain shortcodes.
*
* @return string String without shortcodes.
*/
public function strip_shortcode( $text ) {
return \preg_replace( '`\[[^\]]+\]`s', '', \strip_shortcodes( $text ) );
}
}

View File

@@ -1,182 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WP_Taxonomy;
use WP_Term;
use WPSEO_Taxonomy_Meta;
/**
* A helper object for terms.
*/
class Taxonomy_Helper {
/**
* The options helper.
*
* @var Options_Helper
*/
private $options;
/**
* The string helper.
*
* @var String_Helper
*/
private $string;
/**
* Taxonomy_Helper constructor.
*
* @codeCoverageIgnore It only sets dependencies.
*
* @param Options_Helper $options The options helper.
* @param String_Helper $string_helper The string helper.
*/
public function __construct( Options_Helper $options, String_Helper $string_helper ) {
$this->options = $options;
$this->string = $string_helper;
}
/**
* Checks if the requested term is indexable.
*
* @param string $taxonomy The taxonomy slug.
*
* @return bool True when taxonomy is set to index.
*/
public function is_indexable( $taxonomy ) {
return ! $this->options->get( 'noindex-tax-' . $taxonomy, false );
}
/**
* Returns an array with the public taxonomies.
*
* @param string $output The output type to use.
*
* @return string[]|WP_Taxonomy[] Array with all the public taxonomies.
* The type depends on the specified output variable.
*/
public function get_public_taxonomies( $output = 'names' ) {
return \get_taxonomies( [ 'public' => true ], $output );
}
/**
* Retrieves the term description (without tags).
*
* @param int $term_id Term ID.
*
* @return string Term description (without tags).
*/
public function get_term_description( $term_id ) {
return $this->string->strip_all_tags( \term_description( $term_id ) );
}
/**
* Retrieves the taxonomy term's meta values.
*
* @codeCoverageIgnore We have to write test when this method contains own code.
*
* @param WP_Term $term Term to get the meta value for.
*
* @return array|bool Array of all the meta data for the term.
* False if the term does not exist or the $meta provided is invalid.
*/
public function get_term_meta( $term ) {
return WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, null );
}
/**
* Gets the passed taxonomy's slug.
*
* @param string $taxonomy The name of the taxonomy.
*
* @return string The slug for the taxonomy. Returns the taxonomy's name if no slug could be found.
*/
public function get_taxonomy_slug( $taxonomy ) {
$taxonomy_object = \get_taxonomy( $taxonomy );
if ( $taxonomy_object && \property_exists( $taxonomy_object, 'rewrite' ) && \is_array( $taxonomy_object->rewrite ) && isset( $taxonomy_object->rewrite['slug'] ) ) {
return $taxonomy_object->rewrite['slug'];
}
return \strtolower( $taxonomy_object->name );
}
/**
* Returns an array with the custom taxonomies.
*
* @param string $output The output type to use.
*
* @return string[]|WP_Taxonomy[] Array with all the custom taxonomies.
* The type depends on the specified output variable.
*/
public function get_custom_taxonomies( $output = 'names' ) {
return \get_taxonomies( [ '_builtin' => false ], $output );
}
/**
* Returns an array of taxonomies that are excluded from being indexed for the
* indexables.
*
* @return array The excluded taxonomies.
*/
public function get_excluded_taxonomies_for_indexables() {
/**
* Filter: 'wpseo_indexable_excluded_taxonomies' - Allow developers to prevent a certain taxonomy
* from being saved to the indexable table.
*
* @param array $excluded_taxonomies The currently excluded taxonomies.
*/
$excluded_taxonomies = \apply_filters( 'wpseo_indexable_excluded_taxonomies', [] );
// Failsafe, to always make sure that `excluded_taxonomies` is an array.
if ( ! \is_array( $excluded_taxonomies ) ) {
return [];
}
return $excluded_taxonomies;
}
/**
* Checks if the taxonomy is excluded.
*
* @param string $taxonomy The taxonomy to check.
*
* @return bool If the taxonomy is excluded.
*/
public function is_excluded( $taxonomy ) {
return \in_array( $taxonomy, $this->get_excluded_taxonomies_for_indexables(), true );
}
/**
* This builds a list of indexable taxonomies.
*
* @return array The indexable taxonomies.
*/
public function get_indexable_taxonomies() {
$public_taxonomies = $this->get_public_taxonomies();
$excluded_taxonomies = $this->get_excluded_taxonomies_for_indexables();
// `array_values`, to make sure that the keys are reset.
return \array_values( \array_diff( $public_taxonomies, $excluded_taxonomies ) );
}
/**
* Returns an array of complete taxonomy objects for all indexable taxonomies.
*
* @return array List of indexable indexables objects.
*/
public function get_indexable_taxonomy_objects() {
$taxonomy_objects = [];
$indexable_taxonomies = $this->get_indexable_taxonomies();
foreach ( $indexable_taxonomies as $taxonomy ) {
$taxonomy_object = \get_taxonomy( $taxonomy );
if ( ! empty( $taxonomy_object ) ) {
$taxonomy_objects[ $taxonomy ] = $taxonomy_object;
}
}
return $taxonomy_objects;
}
}

View File

@@ -1,58 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers\Twitter;
use Yoast\WP\SEO\Helpers\Image_Helper as Base_Image_Helper;
/**
* A helper object for Twitter images.
*/
class Image_Helper {
/**
* The base image helper.
*
* @var Base_Image_Helper
*/
private $image;
/**
* Image_Helper constructor.
*
* @codeCoverageIgnore
*
* @param Base_Image_Helper $image The image helper.
*/
public function __construct( Base_Image_Helper $image ) {
$this->image = $image;
}
/**
* The image size to use for Twitter.
*
* @return string Image size string.
*/
public function get_image_size() {
/**
* Filter: 'wpseo_twitter_image_size' - Allow changing the Twitter Card image size.
*
* @param string $featured_img Image size string.
*/
return (string) \apply_filters( 'wpseo_twitter_image_size', 'full' );
}
/**
* Retrieves an image url by its id.
*
* @param int $image_id The image id.
*
* @return string The image url. Empty string if the attachment is not valid.
*/
public function get_by_id( $image_id ) {
if ( ! $this->image->is_valid_attachment( $image_id ) ) {
return '';
}
return $this->image->get_attachment_image_source( $image_id, $this->get_image_size() );
}
}

View File

@@ -1,295 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use Yoast\WP\SEO\Models\SEO_Links;
/**
* A helper object for URLs.
*/
class Url_Helper {
/**
* Retrieve home URL with proper trailing slash.
*
* @param string $path Path relative to home URL.
* @param string|null $scheme Scheme to apply.
*
* @return string Home URL with optional path, appropriately slashed if not.
*/
public function home( $path = '', $scheme = null ) {
$home_url = \home_url( $path, $scheme );
if ( ! empty( $path ) ) {
return $home_url;
}
$home_path = \wp_parse_url( $home_url, \PHP_URL_PATH );
if ( $home_path === '/' ) { // Home at site root, already slashed.
return $home_url;
}
if ( \is_null( $home_path ) ) { // Home at site root, always slash.
return \trailingslashit( $home_url );
}
if ( \is_string( $home_path ) ) { // Home in subdirectory, slash if permalink structure has slash.
return \user_trailingslashit( $home_url );
}
return $home_url;
}
/**
* Determines whether the plugin is active for the entire network.
*
* @return bool Whether or not the plugin is network-active.
*/
public function is_plugin_network_active() {
static $network_active = null;
if ( ! \is_multisite() ) {
return false;
}
// If a cached result is available, bail early.
if ( $network_active !== null ) {
return $network_active;
}
$network_active_plugins = \wp_get_active_network_plugins();
// Consider MU plugins and network-activated plugins as network-active.
$network_active = \strpos( \wp_normalize_path( \WPSEO_FILE ), \wp_normalize_path( \WPMU_PLUGIN_DIR ) ) === 0
|| \in_array( \WP_PLUGIN_DIR . '/' . \WPSEO_BASENAME, $network_active_plugins, true );
return $network_active;
}
/**
* Retrieve network home URL if plugin is network-activated, or home url otherwise.
*
* @return string Home URL with optional path, appropriately slashed if not.
*/
public function network_safe_home_url() {
/**
* Action: 'wpseo_home_url' - Allows overriding of the home URL.
*/
\do_action( 'wpseo_home_url' );
// If the plugin is network-activated, use the network home URL.
if ( self::is_plugin_network_active() ) {
return \network_home_url();
}
return \home_url();
}
/**
* Check whether a url is relative.
*
* @param string $url URL string to check.
*
* @return bool True when url is relative.
*/
public function is_relative( $url ) {
return ( \strpos( $url, 'http' ) !== 0 && \strpos( $url, '//' ) !== 0 );
}
/**
* Gets the path from the passed URL.
*
* @param string $url The URL to get the path from.
*
* @return string The path of the URL. Returns an empty string if URL parsing fails.
*/
public function get_url_path( $url ) {
if ( \is_string( $url ) === false
&& ( \is_object( $url ) === false || \method_exists( $url, '__toString' ) === false )
) {
return '';
}
return (string) \wp_parse_url( $url, \PHP_URL_PATH );
}
/**
* Gets the host from the passed URL.
*
* @param string $url The URL to get the host from.
*
* @return string The host of the URL. Returns an empty string if URL parsing fails.
*/
public function get_url_host( $url ) {
if ( \is_string( $url ) === false
&& ( \is_object( $url ) === false || \method_exists( $url, '__toString' ) === false )
) {
return '';
}
return (string) \wp_parse_url( $url, \PHP_URL_HOST );
}
/**
* Determines the file extension of the given url.
*
* @param string $url The URL.
*
* @return string The extension.
*/
public function get_extension_from_url( $url ) {
$path = $this->get_url_path( $url );
if ( $path === '' ) {
return '';
}
$parts = \explode( '.', $path );
if ( empty( $parts ) || \count( $parts ) === 1 ) {
return '';
}
return \end( $parts );
}
/**
* Ensures that the given url is an absolute url.
*
* @param string $url The url that needs to be absolute.
*
* @return string The absolute url.
*/
public function ensure_absolute_url( $url ) {
if ( ! \is_string( $url ) || $url === '' ) {
return $url;
}
if ( $this->is_relative( $url ) === true ) {
return $this->build_absolute_url( $url );
}
return $url;
}
/**
* Parse the home URL setting to find the base URL for relative URLs.
*
* @param string|null $path Optional path string.
*
* @return string
*/
public function build_absolute_url( $path = null ) {
$path = \wp_parse_url( $path, \PHP_URL_PATH );
$url_parts = \wp_parse_url( \home_url() );
$base_url = \trailingslashit( $url_parts['scheme'] . '://' . $url_parts['host'] );
if ( ! \is_null( $path ) ) {
$base_url .= \ltrim( $path, '/' );
}
return $base_url;
}
/**
* Returns the link type.
*
* @param array $url The URL, as parsed by wp_parse_url.
* @param array|null $home_url Optional. The home URL, as parsed by wp_parse_url. Used to avoid reparsing the home_url.
* @param bool $is_image Whether or not the link is an image.
*
* @return string The link type.
*/
public function get_link_type( $url, $home_url = null, $is_image = false ) {
// If there is no scheme and no host the link is always internal.
// Beware, checking just the scheme isn't enough as a link can be //yoast.com for instance.
if ( empty( $url['scheme'] ) && empty( $url['host'] ) ) {
return ( $is_image ) ? SEO_Links::TYPE_INTERNAL_IMAGE : SEO_Links::TYPE_INTERNAL;
}
// If there is a scheme but it's not http(s) then the link is always external.
if ( \array_key_exists( 'scheme', $url ) && ! \in_array( $url['scheme'], [ 'http', 'https' ], true ) ) {
return ( $is_image ) ? SEO_Links::TYPE_EXTERNAL_IMAGE : SEO_Links::TYPE_EXTERNAL;
}
if ( \is_null( $home_url ) ) {
$home_url = \wp_parse_url( \home_url() );
}
// When the base host is equal to the host.
if ( isset( $url['host'] ) && $url['host'] !== $home_url['host'] ) {
return ( $is_image ) ? SEO_Links::TYPE_EXTERNAL_IMAGE : SEO_Links::TYPE_EXTERNAL;
}
// There is no base path and thus all URLs of the same domain are internal.
if ( empty( $home_url['path'] ) ) {
return ( $is_image ) ? SEO_Links::TYPE_INTERNAL_IMAGE : SEO_Links::TYPE_INTERNAL;
}
// When there is a path and it matches the start of the url.
if ( isset( $url['path'] ) && \strpos( $url['path'], $home_url['path'] ) === 0 ) {
return ( $is_image ) ? SEO_Links::TYPE_INTERNAL_IMAGE : SEO_Links::TYPE_INTERNAL;
}
return ( $is_image ) ? SEO_Links::TYPE_EXTERNAL_IMAGE : SEO_Links::TYPE_EXTERNAL;
}
/**
* Recreate current URL.
*
* @param bool $with_request_uri Whether we want the REQUEST_URI appended.
*
* @return string
*/
public function recreate_current_url( $with_request_uri = true ) {
$current_url = 'http';
if ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ) {
$current_url .= 's';
}
$current_url .= '://';
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- We know this is scary.
$suffix = ( $with_request_uri && isset( $_SERVER['REQUEST_URI'] ) ) ? $_SERVER['REQUEST_URI'] : '';
if ( isset( $_SERVER['SERVER_NAME'] ) && ! empty( $_SERVER['SERVER_NAME'] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- We know this is scary.
$server_name = $_SERVER['SERVER_NAME'];
}
else {
// Early return with just the path.
return $suffix;
}
$server_port = '';
if ( isset( $_SERVER['SERVER_PORT'] ) && $_SERVER['SERVER_PORT'] !== '80' && $_SERVER['SERVER_PORT'] !== '443' ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- We know this is scary.
$server_port = $_SERVER['SERVER_PORT'];
}
if ( ! empty( $server_port ) ) {
$current_url .= $server_name . ':' . $server_port . $suffix;
}
else {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- We know this is scary.
$current_url .= $server_name . $suffix;
}
return $current_url;
}
/**
* Parses a URL and returns its components, this wrapper function was created to support unit tests.
*
* @param string $parsed_url The URL to parse.
* @return array The parsed components of the URL.
*/
public function parse_str_params( $parsed_url ) {
$array = [];
// @todo parse_str changes spaces in param names into `_`, we should find a better way to support them.
\wp_parse_str( $parsed_url, $array );
return $array;
}
}

View File

@@ -1,109 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* A helper object for the user.
*/
class User_Helper {
/**
* Retrieves user meta field for a user.
*
* @param int $user_id User ID.
* @param string $key Optional. The meta key to retrieve. By default, returns data for all keys.
* @param bool $single Whether to return a single value.
*
* @return mixed Will be an array if $single is false. Will be value of meta data field if $single is true.
*/
public function get_meta( $user_id, $key = '', $single = false ) {
return \get_user_meta( $user_id, $key, $single );
}
/**
* Counts the number of posts the user has written in this post type.
*
* @param int $user_id User ID.
* @param array|string $post_type Optional. Single post type or array of post types to count the number of posts
* for. Default 'post'.
*
* @return int The number of posts the user has written in this post type.
*/
public function count_posts( $user_id, $post_type = 'post' ) {
return (int) \count_user_posts( $user_id, $post_type, true );
}
/**
* Retrieves the requested data of the author.
*
* @param string $field The user field to retrieve.
* @param int|false $user_id User ID.
*
* @return string The author's field from the current author's DB object.
*/
public function get_the_author_meta( $field, $user_id ) {
return \get_the_author_meta( $field, $user_id );
}
/**
* Retrieves the archive url of the user.
*
* @param int|false $user_id User ID.
*
* @return string The author's archive url.
*/
public function get_the_author_posts_url( $user_id ) {
return \get_author_posts_url( $user_id );
}
/**
* Retrieves the current user ID.
*
* @return int The current user's ID, or 0 if no user is logged in.
*/
public function get_current_user_id() {
return \get_current_user_id();
}
/**
* Updates user meta field for a user.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and user ID.
*
* If the meta field for the user does not exist, it will be added.
*
* @param int $user_id User ID.
* @param string $meta_key Metadata key.
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. Previous value to check before updating.
* If specified, only update existing metadata entries with
* this value. Otherwise, update all entries. Default empty.
*
* @return int|bool Meta ID if the key didn't exist, true on successful update,
* false on failure or if the value passed to the function
* is the same as the one that is already in the database.
*/
public function update_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) {
return \update_user_meta( $user_id, $meta_key, $meta_value, $prev_value );
}
/**
* Removes metadata matching criteria from a user.
*
* You can match based on the key, or key and value. Removing based on key and
* value, will keep from removing duplicate metadata with the same key. It also
* allows removing all metadata matching key, if needed.
*
* @param int $user_id User ID.
* @param string $meta_key Metadata name.
* @param mixed $meta_value Optional. Metadata value. If provided,
* rows will only be removed that match the value.
* Must be serializable if non-scalar. Default empty.
*
* @return bool True on success, false on failure.
*/
public function delete_meta( $user_id, $meta_key, $meta_value = '' ) {
return \delete_user_meta( $user_id, $meta_key, $meta_value );
}
}

View File

@@ -1,93 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use WPSEO_Shortlinker;
use Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional;
use Yoast\WP\SEO\Config\Wincher_Client;
use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception;
/**
* A helper object for Wincher matters.
*/
class Wincher_Helper {
/**
* Holds the Options Page helper instance.
*
* @var Options_Helper
*/
protected $options;
/**
* Options_Helper constructor.
*
* @param Options_Helper $options The options helper.
*/
public function __construct( Options_Helper $options ) {
$this->options = $options;
}
/**
* Checks if the integration should be active for the current user.
*
* @return bool Whether the integration is active.
*/
public function is_active() {
$conditional = new Non_Multisite_Conditional();
if ( ! $conditional->is_met() ) {
return false;
}
if ( ! \current_user_can( 'publish_posts' ) && ! \current_user_can( 'publish_pages' ) ) {
return false;
}
return (bool) $this->options->get( 'wincher_integration_active', true );
}
/**
* Checks if the user is logged in to Wincher.
*
* @return bool The Wincher login status.
*/
public function login_status() {
try {
$wincher = \YoastSEO()->classes->get( Wincher_Client::class );
} catch ( Empty_Property_Exception $e ) {
// Return false if token is malformed (empty property).
return false;
}
// Get token (and refresh it if it's expired).
try {
$wincher->get_tokens();
} catch ( Authentication_Failed_Exception $e ) {
return false;
} catch ( Empty_Token_Exception $e ) {
return false;
}
return $wincher->has_valid_tokens();
}
/**
* Returns the Wincher links that can be used to localize the global admin
* script. Mainly exists to avoid duplicating these links in multiple places
* around the code base.
*
* @return string[]
*/
public function get_admin_global_links() {
return [
'links.wincher.login' => 'https://app.wincher.com/login?utm_medium=plugin&utm_source=yoast&referer=yoast&partner=yoast',
'links.wincher.about' => WPSEO_Shortlinker::get( 'https://yoa.st/dashboard-about-wincher' ),
'links.wincher.pricing' => WPSEO_Shortlinker::get( 'https://yoa.st/wincher-popup-pricing' ),
'links.wincher.website' => WPSEO_Shortlinker::get( 'https://yoa.st/wincher-popup' ),
'links.wincher.upgrade' => WPSEO_Shortlinker::get( 'https://yoa.st/wincher-upgrade' ),
];
}
}

View File

@@ -1,71 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
/**
* Represents helper methods for WooCommerce.
*/
class Woocommerce_Helper {
/**
* Checks if WooCommerce is active.
*
* @return bool Is WooCommerce active.
*/
public function is_active() {
return \class_exists( 'WooCommerce' );
}
/**
* Returns the id of the set WooCommerce shop page.
*
* @return int The ID of the set page.
*/
public function get_shop_page_id() {
if ( ! \function_exists( 'wc_get_page_id' ) ) {
return -1;
}
return \wc_get_page_id( 'shop' );
}
/**
* Checks if the current page is a WooCommerce shop page.
*
* @return bool True when the page is a shop page.
*/
public function is_shop_page() {
if ( ! \function_exists( 'is_shop' ) ) {
return false;
}
if ( ! \is_shop() ) {
return false;
}
if ( \is_search() ) {
return false;
}
return true;
}
/**
* Checks if the current page is a WooCommerce shop page.
*
* @return bool True when the page is a shop page.
*/
public function current_post_is_terms_and_conditions_page() {
if ( ! \function_exists( 'wc_terms_and_conditions_page_id' ) ) {
return false;
}
global $post;
if ( ! isset( $post->ID ) ) {
return false;
}
return \intval( $post->ID ) === \intval( \wc_terms_and_conditions_page_id() );
}
}

View File

@@ -1,22 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
// phpcs:disable WordPress.WP.CapitalPDangit.MisspelledClassName -- It is spelled like `Wordpress_Helper` because of Yoast's naming conventions for classes, which would otherwise break dependency injection in some cases.
/**
* A helper object for WordPress matters.
*/
class Wordpress_Helper {
/**
* Returns the WordPress version.
*
* @return string The version.
*/
public function get_wordpress_version() {
global $wp_version;
return $wp_version;
}
}

View File

@@ -1,116 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use ReflectionClass;
use Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional;
use Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Plugin_Inactive_Conditional;
use Yoast\WP\SEO\Conditionals\User_Can_Publish_Posts_And_Pages_Conditional;
/**
* A helper object for WordProof integration.
*/
class Wordproof_Helper {
/**
* Holds the Current Page helper instance.
*
* @var Current_Page_Helper
*/
protected $current_page;
/**
* Holds the WooCommerce helper instance.
*
* @var Woocommerce_Helper
*/
protected $woocommerce;
/**
* Holds the Options Page helper instance.
*
* @var Options_Helper
*/
protected $options;
/**
* WordProof_Helper constructor.
*
* @param Current_Page_Helper $current_page The current page helper.
* @param Woocommerce_Helper $woocommerce The woocommerce helper.
* @param Options_Helper $options The options helper.
*/
public function __construct( Current_Page_Helper $current_page, Woocommerce_Helper $woocommerce, Options_Helper $options ) {
$this->current_page = $current_page;
$this->woocommerce = $woocommerce;
$this->options = $options;
}
/**
* Remove site options after disabling the integration.
*
* @return bool Returns if the options are deleted
*/
public function remove_site_options() {
return \delete_site_option( 'wordproof_access_token' )
&& \delete_site_option( 'wordproof_source_id' );
}
/**
* Returns if conditionals are met. If not, the integration should be disabled.
*
* @param bool $return_conditional If the conditional class name that was unmet should be returned.
*
* @return bool|string Returns if the integration should be disabled.
*/
public function integration_is_disabled( $return_conditional = false ) {
$conditionals = [ new Non_Multisite_Conditional(), new Wordproof_Plugin_Inactive_Conditional() ];
foreach ( $conditionals as $conditional ) {
if ( ! $conditional->is_met() ) {
if ( $return_conditional === true ) {
return ( new ReflectionClass( $conditional ) )->getShortName();
}
return true;
}
}
return false;
}
/**
* Returns if the WordProof integration toggle is turned on.
*
* @return bool Returns if the integration toggle is set to true if conditionals are met.
*/
public function integration_is_active() {
if ( $this->integration_is_disabled() ) {
return false;
}
return $this->options->get( 'wordproof_integration_active', true );
}
/**
* Return if WordProof should be active for this post editor page.
*
* @return bool Returns if WordProof should be active for this page.
*/
public function is_active() {
$is_wordproof_active = $this->integration_is_active();
if ( ! $is_wordproof_active ) {
return false;
}
$user_can_publish = ( new User_Can_Publish_Posts_And_Pages_Conditional() )->is_met();
if ( ! $user_can_publish ) {
return false;
}
return ( $this->current_page->current_post_is_privacy_policy() || $this->woocommerce->current_post_is_terms_and_conditions_page() );
}
}

View File

@@ -1,44 +0,0 @@
<?php
namespace Yoast\WP\SEO\Helpers;
use wpdb;
/**
* A helper object for the wpdb.
*/
class Wpdb_Helper {
/**
* The WordPress database instance.
*
* @var wpdb
*/
private $wpdb;
/**
* Constructs a Wpdb_Helper instance.
*
* @param wpdb $wpdb The WordPress database instance.
*/
public function __construct( wpdb $wpdb ) {
$this->wpdb = $wpdb;
}
/**
* Check if table exists.
*
* @param string $table The table to be checked.
*
* @return bool Whether the table exists.
*/
public function table_exists( $table ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input.
$table_exists = $this->wpdb->get_var( "SHOW TABLES LIKE '{$table}'" );
if ( \is_wp_error( $table_exists ) || \is_null( $table_exists ) ) {
return false;
}
return true;
}
}