khaihihi
This commit is contained in:
518
wp-content/plugins/wordpress-seo/inc/class-addon-manager.php
Normal file
518
wp-content/plugins/wordpress-seo/inc/class-addon-manager.php
Normal file
@@ -0,0 +1,518 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the addon manager.
|
||||
*/
|
||||
class WPSEO_Addon_Manager {
|
||||
|
||||
/**
|
||||
* Holds the name of the transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SITE_INFORMATION_TRANSIENT = 'wpseo_site_information';
|
||||
|
||||
/**
|
||||
* Holds the slug for YoastSEO free.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FREE_SLUG = 'yoast-seo-wordpress';
|
||||
|
||||
/**
|
||||
* Holds the slug for YoastSEO Premium.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PREMIUM_SLUG = 'yoast-seo-wordpress-premium';
|
||||
|
||||
/**
|
||||
* Holds the slug for Yoast News.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NEWS_SLUG = 'yoast-seo-news';
|
||||
|
||||
/**
|
||||
* Holds the slug for Video.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VIDEO_SLUG = 'yoast-seo-video';
|
||||
|
||||
/**
|
||||
* Holds the slug for WooCommerce.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const WOOCOMMERCE_SLUG = 'yoast-seo-woocommerce';
|
||||
|
||||
/**
|
||||
* Holds the slug for Local.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOCAL_SLUG = 'yoast-seo-local';
|
||||
|
||||
/**
|
||||
* The expected addon data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $addons = [
|
||||
'wp-seo-premium.php' => self::PREMIUM_SLUG,
|
||||
'wpseo-news.php' => self::NEWS_SLUG,
|
||||
'video-seo.php' => self::VIDEO_SLUG,
|
||||
'wpseo-woocommerce.php' => self::WOOCOMMERCE_SLUG,
|
||||
'local-seo.php' => self::LOCAL_SLUG,
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the site information data.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private $site_information;
|
||||
|
||||
/**
|
||||
* Hooks into WordPress.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_updates' ] );
|
||||
add_filter( 'plugins_api', [ $this, 'get_plugin_information' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subscriptions for current site.
|
||||
*
|
||||
* @return stdClass The subscriptions.
|
||||
*/
|
||||
public function get_subscriptions() {
|
||||
return $this->get_site_information()->subscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the subscription for the given slug.
|
||||
*
|
||||
* @param string $slug The plugin slug to retrieve.
|
||||
*
|
||||
* @return stdClass|false Subscription data when found, false when not found.
|
||||
*/
|
||||
public function get_subscription( $slug ) {
|
||||
foreach ( $this->get_subscriptions() as $subscription ) {
|
||||
if ( $subscription->product->slug === $slug ) {
|
||||
return $subscription;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of (subscription) slugs by the active addons.
|
||||
*
|
||||
* @return array The slugs.
|
||||
*/
|
||||
public function get_subscriptions_for_active_addons() {
|
||||
$active_addons = array_keys( $this->get_active_addons() );
|
||||
$subscription_slugs = array_map( [ $this, 'get_slug_by_plugin_file' ], $active_addons );
|
||||
$subscriptions = [];
|
||||
foreach ( $subscription_slugs as $subscription_slug ) {
|
||||
$subscriptions[ $subscription_slug ] = $this->get_subscription( $subscription_slug );
|
||||
}
|
||||
|
||||
return $subscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of versions for each addon.
|
||||
*
|
||||
* @return array The addon versions.
|
||||
*/
|
||||
public function get_installed_addons_versions() {
|
||||
$addon_versions = [];
|
||||
foreach ( $this->get_installed_addons() as $plugin_file => $installed_addon ) {
|
||||
$addon_versions[ $this->get_slug_by_plugin_file( $plugin_file ) ] = $installed_addon['Version'];
|
||||
}
|
||||
|
||||
return $addon_versions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the plugin information from the subscriptions.
|
||||
*
|
||||
* @param stdClass|false $data The result object. Default false.
|
||||
* @param string $action The type of information being requested from the Plugin Installation API.
|
||||
* @param stdClass $args Plugin API arguments.
|
||||
*
|
||||
* @return object Extended plugin data.
|
||||
*/
|
||||
public function get_plugin_information( $data, $action, $args ) {
|
||||
if ( $action !== 'plugin_information' ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( ! isset( $args->slug ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$subscription = $this->get_subscription( $args->slug );
|
||||
if ( ! $subscription || $this->has_subscription_expired( $subscription ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return $this->convert_subscription_to_plugin( $subscription );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the subscription for the given slug is valid.
|
||||
*
|
||||
* @param string $slug The plugin slug to retrieve.
|
||||
*
|
||||
* @return bool True when the subscription is valid.
|
||||
*/
|
||||
public function has_valid_subscription( $slug ) {
|
||||
$subscription = $this->get_subscription( $slug );
|
||||
|
||||
// An non-existing subscription is never valid.
|
||||
if ( $subscription === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $this->has_subscription_expired( $subscription );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are addon updates.
|
||||
*
|
||||
* @param stdClass|mixed $data The current data for update_plugins.
|
||||
*
|
||||
* @return stdClass Extended data for update_plugins.
|
||||
*/
|
||||
public function check_for_updates( $data ) {
|
||||
if ( empty( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ( $this->get_installed_addons() as $plugin_file => $installed_plugin ) {
|
||||
$subscription_slug = $this->get_slug_by_plugin_file( $plugin_file );
|
||||
$subscription = $this->get_subscription( $subscription_slug );
|
||||
|
||||
if ( ! $subscription || $this->has_subscription_expired( $subscription ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( version_compare( $installed_plugin['Version'], $subscription->product->version, '<' ) ) {
|
||||
$data->response[ $plugin_file ] = $this->convert_subscription_to_plugin( $subscription );
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a plugin expiry date has been passed.
|
||||
*
|
||||
* @param stdClass $subscription Plugin subscription.
|
||||
*
|
||||
* @return bool Has the plugin expired.
|
||||
*/
|
||||
protected function has_subscription_expired( $subscription ) {
|
||||
return ( strtotime( $subscription->expiry_date ) - time() ) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a subscription to plugin based format.
|
||||
*
|
||||
* @param stdClass $subscription The subscription to convert.
|
||||
*
|
||||
* @return stdClass The converted subscription.
|
||||
*/
|
||||
protected function convert_subscription_to_plugin( $subscription ) {
|
||||
return (object) [
|
||||
'new_version' => $subscription->product->version,
|
||||
'name' => $subscription->product->name,
|
||||
'slug' => $subscription->product->slug,
|
||||
'url' => $subscription->product->store_url,
|
||||
'last_update' => $subscription->product->last_updated,
|
||||
'homepage' => $subscription->product->store_url,
|
||||
'download_link' => $subscription->product->download,
|
||||
'package' => $subscription->product->download,
|
||||
'sections' =>
|
||||
[
|
||||
'changelog' => $subscription->product->changelog,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given plugin_file belongs to a Yoast addon.
|
||||
*
|
||||
* @param string $plugin_file Path to the plugin.
|
||||
*
|
||||
* @return bool True when plugin file is for a Yoast addon.
|
||||
*/
|
||||
protected function is_yoast_addon( $plugin_file ) {
|
||||
return $this->get_slug_by_plugin_file( $plugin_file ) !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the addon slug by given plugin file path.
|
||||
*
|
||||
* @param string $plugin_file The file path to the plugin.
|
||||
*
|
||||
* @return string The slug when found or empty string when not.
|
||||
*/
|
||||
protected function get_slug_by_plugin_file( $plugin_file ) {
|
||||
$addons = self::$addons;
|
||||
|
||||
// Yoast SEO Free isn't an addon, but we needed it in Premium to fetch translations.
|
||||
if ( WPSEO_Utils::is_yoast_seo_premium() ) {
|
||||
$addons['wp-seo.php'] = self::FREE_SLUG;
|
||||
}
|
||||
|
||||
foreach ( $addons as $addon => $addon_slug ) {
|
||||
if ( strpos( $plugin_file, $addon ) !== false ) {
|
||||
return $addon_slug;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the installed Yoast addons.
|
||||
*
|
||||
* @return array The installed plugins.
|
||||
*/
|
||||
protected function get_installed_addons() {
|
||||
return $this->filter_by_key( $this->get_plugins(), [ $this, 'is_yoast_addon' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of active addons.
|
||||
*
|
||||
* @return array The active addons.
|
||||
*/
|
||||
protected function get_active_addons() {
|
||||
return $this->filter_by_key( $this->get_installed_addons(), [ $this, 'is_plugin_active' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current sites from the API.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool|stdClass Object when request is successful. False if not.
|
||||
*/
|
||||
protected function request_current_sites() {
|
||||
$api_request = new WPSEO_MyYoast_Api_Request( 'sites/current' );
|
||||
if ( $api_request->fire() ) {
|
||||
return $api_request->get_response();
|
||||
}
|
||||
|
||||
return $this->get_site_information_default();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the transient value with the site information.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return stdClass|false The transient value.
|
||||
*/
|
||||
protected function get_site_information_transient() {
|
||||
global $pagenow;
|
||||
|
||||
// Force re-check on license & dashboard pages.
|
||||
$current_page = $this->get_current_page();
|
||||
// Check whether the licenses are valid or whether we need to show notifications.
|
||||
$exclude_cache = ( $current_page === 'wpseo_licenses' || $current_page === 'wpseo_dashboard' );
|
||||
|
||||
// Also do a fresh request on Plugins & Core Update pages.
|
||||
$exclude_cache = $exclude_cache || $pagenow === 'plugins.php';
|
||||
$exclude_cache = $exclude_cache || $pagenow === 'update-core.php';
|
||||
|
||||
if ( $exclude_cache ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_transient( self::SITE_INFORMATION_TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current page.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The current page.
|
||||
*/
|
||||
protected function get_current_page() {
|
||||
return filter_input( INPUT_GET, 'page' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the site information transient.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param stdClass $site_information The site information to save.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_site_information_transient( $site_information ) {
|
||||
set_transient( self::SITE_INFORMATION_TRANSIENT, $site_information, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all installed WordPress plugins.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The plugins.
|
||||
*/
|
||||
protected function get_plugins() {
|
||||
return get_plugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given plugin file belongs to an active plugin.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $plugin_file The file path to the plugin.
|
||||
*
|
||||
* @return bool True when plugin is active.
|
||||
*/
|
||||
protected function is_plugin_active( $plugin_file ) {
|
||||
return is_plugin_active( $plugin_file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with no subscriptions.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return stdClass Site information.
|
||||
*/
|
||||
protected function get_site_information_default() {
|
||||
return (object) [
|
||||
'url' => WPSEO_Utils::get_home_url(),
|
||||
'subscriptions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any installed addons.
|
||||
*
|
||||
* @return bool True when there are installed Yoast addons.
|
||||
*/
|
||||
protected function has_installed_addons() {
|
||||
$installed_addons = $this->get_installed_addons();
|
||||
|
||||
return ! empty( $installed_addons );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the given array by its keys.
|
||||
*
|
||||
* This method is temporary. When WordPress has minimal PHP 5.6 support we can change this to:
|
||||
*
|
||||
* array_filter( $array_to_filter, $filter, ARRAY_FILTER_USE_KEY )
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $array_to_filter The array to filter.
|
||||
* @param callable $callback The filter callback.
|
||||
*
|
||||
* @return array The filtered array,
|
||||
*/
|
||||
private function filter_by_key( $array_to_filter, $callback ) {
|
||||
$keys_to_filter = array_filter( array_keys( $array_to_filter ), $callback );
|
||||
$filtered_array = [];
|
||||
foreach ( $keys_to_filter as $filtered_key ) {
|
||||
$filtered_array[ $filtered_key ] = $array_to_filter[ $filtered_key ];
|
||||
}
|
||||
|
||||
return $filtered_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the plugin API response.
|
||||
*
|
||||
* @param object $site_information Site information as received from the API.
|
||||
*
|
||||
* @return object Mapped site information.
|
||||
*/
|
||||
protected function map_site_information( $site_information ) {
|
||||
return (object) [
|
||||
'url' => $site_information->url,
|
||||
'subscriptions' => array_map( [ $this, 'map_subscription' ], $site_information->subscriptions ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a plugin subscription.
|
||||
*
|
||||
* @param object $subscription Subscription information as received from the API.
|
||||
*
|
||||
* @return object Mapped subscription.
|
||||
*/
|
||||
protected function map_subscription( $subscription ) {
|
||||
// @codingStandardsIgnoreStart
|
||||
return (object) array(
|
||||
'renewal_url' => $subscription->renewalUrl,
|
||||
'expiry_date' => $subscription->expiryDate,
|
||||
'product' => (object) array(
|
||||
'version' => $subscription->product->version,
|
||||
'name' => $subscription->product->name,
|
||||
'slug' => $subscription->product->slug,
|
||||
'last_updated' => $subscription->product->lastUpdated,
|
||||
'store_url' => $subscription->product->storeUrl,
|
||||
// Ternary operator is necessary because download can be undefined.
|
||||
'download' => isset( $subscription->product->download ) ? $subscription->product->download : null,
|
||||
'changelog' => $subscription->product->changelog,
|
||||
),
|
||||
);
|
||||
// @codingStandardsIgnoreStop
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the site information.
|
||||
*
|
||||
* @return stdClass The site information.
|
||||
*/
|
||||
private function get_site_information() {
|
||||
if ( ! $this->has_installed_addons() ) {
|
||||
return $this->get_site_information_default();
|
||||
}
|
||||
|
||||
if ( $this->site_information === null ) {
|
||||
$this->site_information = $this->get_site_information_transient();
|
||||
}
|
||||
|
||||
if ( $this->site_information ) {
|
||||
return $this->site_information;
|
||||
}
|
||||
|
||||
$this->site_information = $this->request_current_sites();
|
||||
if ( $this->site_information ) {
|
||||
$this->site_information = $this->map_site_information( $this->site_information );
|
||||
|
||||
$this->set_site_information_transient( $this->site_information );
|
||||
|
||||
return $this->site_information;
|
||||
}
|
||||
|
||||
return $this->get_site_information_default();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles requests to MyYoast.
|
||||
*/
|
||||
class WPSEO_MyYoast_Api_Request {
|
||||
|
||||
/**
|
||||
* The Request URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* The request parameters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $args = [
|
||||
'method' => 'GET',
|
||||
'timeout' => 5,
|
||||
'headers' => [
|
||||
'Accept-Encoding' => '*',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Contains the fetched response.
|
||||
*
|
||||
* @var stdClass
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* Contains the error message when request went wrong.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $error_message = '';
|
||||
|
||||
/**
|
||||
* The MyYoast client object.
|
||||
*
|
||||
* @var WPSEO_MyYoast_Client
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $url The request url.
|
||||
* @param array $args The request arguments.
|
||||
*/
|
||||
public function __construct( $url, array $args = [] ) {
|
||||
$this->url = 'https://my.yoast.com/api/' . $url;
|
||||
$this->args = wp_parse_args( $args, $this->args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires the request.
|
||||
*
|
||||
* @return bool True when request is successful.
|
||||
*/
|
||||
public function fire() {
|
||||
try {
|
||||
$response = $this->do_request( $this->url, $this->args );
|
||||
$this->response = $this->decode_response( $response );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Authentication exception only occurs when using Access Tokens (>= PHP 5.6).
|
||||
* In other case this exception won't be thrown.
|
||||
*
|
||||
* When authentication failed just try to get a new access token based
|
||||
* on the refresh token. If that request also has an authentication issue
|
||||
* we just invalidate the access token by removing it.
|
||||
*/
|
||||
catch ( WPSEO_MyYoast_Authentication_Exception $authentication_exception ) {
|
||||
try {
|
||||
$access_token = $this->get_access_token();
|
||||
|
||||
if ( $access_token !== false ) {
|
||||
$response = $this->do_request( $this->url, $this->args );
|
||||
$this->response = $this->decode_response( $response );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch ( WPSEO_MyYoast_Authentication_Exception $authentication_exception ) {
|
||||
$this->error_message = $authentication_exception->getMessage();
|
||||
|
||||
$this->remove_access_token( $this->get_current_user_id() );
|
||||
|
||||
return false;
|
||||
}
|
||||
catch ( WPSEO_MyYoast_Bad_Request_Exception $bad_request_exception ) {
|
||||
$this->error_message = $bad_request_exception->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch ( WPSEO_MyYoast_Bad_Request_Exception $bad_request_exception ) {
|
||||
$this->error_message = $bad_request_exception->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the error message.
|
||||
*
|
||||
* @return string The set error message.
|
||||
*/
|
||||
public function get_error_message() {
|
||||
return $this->error_message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the response.
|
||||
*
|
||||
* @return stdClass The response object.
|
||||
*/
|
||||
public function get_response() {
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the request using WordPress internals.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $url The request URL.
|
||||
* @param array $request_arguments The request arguments.
|
||||
*
|
||||
* @return string The retrieved body.
|
||||
* @throws WPSEO_MyYoast_Authentication_Exception When authentication has failed.
|
||||
* @throws WPSEO_MyYoast_Bad_Request_Exception When request is invalid.
|
||||
*/
|
||||
protected function do_request( $url, $request_arguments ) {
|
||||
$request_arguments = $this->enrich_request_arguments( $request_arguments );
|
||||
$response = wp_remote_request( $url, $request_arguments );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
throw new WPSEO_MyYoast_Bad_Request_Exception( $response->get_error_message() );
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
$response_message = wp_remote_retrieve_response_message( $response );
|
||||
|
||||
// Do nothing, response code is okay.
|
||||
if ( $response_code === 200 || strpos( $response_code, '200' ) !== false ) {
|
||||
return wp_remote_retrieve_body( $response );
|
||||
}
|
||||
|
||||
// Authentication failed, throw an exception.
|
||||
if ( strpos( $response_code, '401' ) && $this->has_oauth_support() ) {
|
||||
throw new WPSEO_MyYoast_Authentication_Exception( esc_html( $response_message ), 401 );
|
||||
}
|
||||
|
||||
throw new WPSEO_MyYoast_Bad_Request_Exception( esc_html( $response_message ), (int) $response_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the JSON encoded response.
|
||||
*
|
||||
* @param string $response The response to decode.
|
||||
*
|
||||
* @return stdClass The json decoded response.
|
||||
* @throws WPSEO_MyYoast_Invalid_JSON_Exception When decoded string is not a JSON object.
|
||||
*/
|
||||
protected function decode_response( $response ) {
|
||||
$response = json_decode( $response );
|
||||
|
||||
if ( ! is_object( $response ) ) {
|
||||
throw new WPSEO_MyYoast_Invalid_JSON_Exception(
|
||||
esc_html__( 'No JSON object was returned.', 'wordpress-seo' )
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if MyYoast tokens are allowed and adds the token to the request body.
|
||||
*
|
||||
* When tokens are disallowed it will add the url to the request body.
|
||||
*
|
||||
* @param array $request_arguments The arguments to enrich.
|
||||
*
|
||||
* @return array The enriched arguments.
|
||||
*/
|
||||
protected function enrich_request_arguments( array $request_arguments ) {
|
||||
$request_arguments = wp_parse_args( $request_arguments, [ 'headers' => [] ] );
|
||||
$addon_version_headers = $this->get_installed_addon_versions();
|
||||
|
||||
foreach ( $addon_version_headers as $addon => $version ) {
|
||||
$request_arguments['headers'][ $addon . '-version' ] = $version;
|
||||
}
|
||||
|
||||
$request_body = $this->get_request_body();
|
||||
if ( $request_body !== [] ) {
|
||||
$request_arguments['body'] = $request_body;
|
||||
}
|
||||
|
||||
return $request_arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the request body based on URL or access token support.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The request body.
|
||||
*/
|
||||
public function get_request_body() {
|
||||
if ( ! $this->has_oauth_support() ) {
|
||||
return [ 'url' => WPSEO_Utils::get_home_url() ];
|
||||
}
|
||||
|
||||
try {
|
||||
$access_token = $this->get_access_token();
|
||||
if ( $access_token ) {
|
||||
return [ 'token' => $access_token->getToken() ];
|
||||
}
|
||||
}
|
||||
// @codingStandardsIgnoreLine Generic.CodeAnalysis.EmptyStatement.DetectedCATCH -- There is nothing to do.
|
||||
catch ( WPSEO_MyYoast_Bad_Request_Exception $bad_request ) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the access token.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool|WPSEO_MyYoast_AccessToken_Interface The AccessToken when valid.
|
||||
* @throws WPSEO_MyYoast_Bad_Request_Exception When something went wrong in getting the access token.
|
||||
*/
|
||||
protected function get_access_token() {
|
||||
$client = $this->get_client();
|
||||
|
||||
if ( ! $client ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$access_token = $client->get_access_token();
|
||||
|
||||
if ( ! $access_token ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $access_token->hasExpired() ) {
|
||||
return $access_token;
|
||||
}
|
||||
|
||||
try {
|
||||
$access_token = $client
|
||||
->get_provider()
|
||||
->getAccessToken(
|
||||
'refresh_token',
|
||||
[
|
||||
'refresh_token' => $access_token->getRefreshToken(),
|
||||
]
|
||||
);
|
||||
|
||||
$client->save_access_token( $this->get_current_user_id(), $access_token );
|
||||
|
||||
return $access_token;
|
||||
}
|
||||
catch ( Exception $e ) {
|
||||
$error_code = $e->getCode();
|
||||
if ( $error_code >= 400 && $error_code < 500 ) {
|
||||
$this->remove_access_token( $this->get_current_user_id() );
|
||||
}
|
||||
|
||||
throw new WPSEO_MyYoast_Bad_Request_Exception( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an instance of the MyYoast client.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return WPSEO_MyYoast_Client Instance of the client.
|
||||
*/
|
||||
protected function get_client() {
|
||||
if ( $this->client === null ) {
|
||||
$this->client = new WPSEO_MyYoast_Client();
|
||||
}
|
||||
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the get current user id function.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return int The user id.
|
||||
*/
|
||||
protected function get_current_user_id() {
|
||||
return get_current_user_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the access token for given user id.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $user_id The user id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function remove_access_token( $user_id ) {
|
||||
if ( ! $this->has_oauth_support() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the access token entirely.
|
||||
$this->get_client()->remove_access_token( $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the installed addons as http headers.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The installed addon versions.
|
||||
*/
|
||||
protected function get_installed_addon_versions() {
|
||||
$addon_manager = new WPSEO_Addon_Manager();
|
||||
|
||||
return $addon_manager->get_installed_addons_versions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the has_access_token support method.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool False to disable the support.
|
||||
*/
|
||||
protected function has_oauth_support() {
|
||||
return false;
|
||||
|
||||
// @todo: Uncomment the following statement when we are implementing the oAuth flow.
|
||||
// return WPSEO_Utils::has_access_token_support();
|
||||
}
|
||||
}
|
||||
120
wp-content/plugins/wordpress-seo/inc/class-post-type.php
Normal file
120
wp-content/plugins/wordpress-seo/inc/class-post-type.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the post type utils.
|
||||
*/
|
||||
class WPSEO_Post_Type {
|
||||
|
||||
/**
|
||||
* 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 static 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.
|
||||
*
|
||||
* @api 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 whether the passed post type is considered accessible.
|
||||
*
|
||||
* @param string $post_type The post type to check.
|
||||
*
|
||||
* @return bool Whether or not the post type is considered accessible.
|
||||
*/
|
||||
public static function is_post_type_accessible( $post_type ) {
|
||||
return in_array( $post_type, self::get_accessible_post_types(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the request post type is public and indexable.
|
||||
*
|
||||
* @param string $post_type_name The name of the post type to lookup.
|
||||
*
|
||||
* @return bool True when post type is set to index.
|
||||
*/
|
||||
public static function is_post_type_indexable( $post_type_name ) {
|
||||
if ( WPSEO_Options::get( 'disable-' . $post_type_name, false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ( WPSEO_Options::get( 'noindex-' . $post_type_name, false ) === false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment post type from an array with post_types.
|
||||
*
|
||||
* @param array $post_types The array to filter the attachment post type from.
|
||||
*
|
||||
* @return array The filtered array.
|
||||
*/
|
||||
public static function filter_attachment_post_type( array $post_types ) {
|
||||
unset( $post_types['attachment'] );
|
||||
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the post type is enabled in the REST API.
|
||||
*
|
||||
* @param string $post_type The post type to check.
|
||||
*
|
||||
* @return bool Whether or not the post type is available in the REST API.
|
||||
*/
|
||||
public static function is_rest_enabled( $post_type ) {
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
|
||||
if ( $post_type_object === null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $post_type_object->show_in_rest === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current post type has an archive.
|
||||
*
|
||||
* Context: The has_archive value can be a string or a boolean. In most case it will be a boolean,
|
||||
* but it can be defined as a string. When it is a string the archive_slug will be overwritten to
|
||||
* define another endpoint.
|
||||
*
|
||||
* @param WP_Post_Type $post_type The post type object.
|
||||
*
|
||||
* @return bool True whether the post type has an archive.
|
||||
*/
|
||||
public static function has_archive( $post_type ) {
|
||||
return ( ! empty( $post_type->has_archive ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Yoast Metabox has been enabled for the post type.
|
||||
*
|
||||
* @param string $post_type The post type name.
|
||||
*
|
||||
* @return bool True whether the metabox is enabled.
|
||||
*/
|
||||
public static function has_metabox_enabled( $post_type ) {
|
||||
return WPSEO_Options::get( 'display-metabox-pt-' . $post_type, false );
|
||||
}
|
||||
}
|
||||
238
wp-content/plugins/wordpress-seo/inc/class-rewrite.php
Normal file
238
wp-content/plugins/wordpress-seo/inc/class-rewrite.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Frontend
|
||||
*/
|
||||
|
||||
/**
|
||||
* This code handles the category rewrites.
|
||||
*/
|
||||
class WPSEO_Rewrite {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'query_vars', [ $this, 'query_vars' ] );
|
||||
add_filter( 'category_link', [ $this, 'no_category_base' ] );
|
||||
add_filter( 'request', [ $this, 'request' ] );
|
||||
add_filter( 'category_rewrite_rules', [ $this, 'category_rewrite_rules' ] );
|
||||
|
||||
add_action( 'created_category', [ $this, 'schedule_flush' ] );
|
||||
add_action( 'edited_category', [ $this, 'schedule_flush' ] );
|
||||
add_action( 'delete_category', [ $this, 'schedule_flush' ] );
|
||||
|
||||
add_action( 'init', [ $this, 'flush' ], 999 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an option that triggers a flush on the next init.
|
||||
*
|
||||
* @since 1.2.8
|
||||
*/
|
||||
public function schedule_flush() {
|
||||
update_option( 'wpseo_flush_rewrite', 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the flush option is set, flush the rewrite rules.
|
||||
*
|
||||
* @since 1.2.8
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush() {
|
||||
if ( get_option( 'wpseo_flush_rewrite' ) ) {
|
||||
|
||||
add_action( 'shutdown', 'flush_rewrite_rules' );
|
||||
delete_option( 'wpseo_flush_rewrite' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the category link to remove the category base.
|
||||
*
|
||||
* @param string $link Unused, overridden by the function.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function no_category_base( $link ) {
|
||||
$category_base = get_option( 'category_base' );
|
||||
|
||||
if ( empty( $category_base ) ) {
|
||||
$category_base = 'category';
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove initial slash, if there is one (we remove the trailing slash
|
||||
* in the regex replacement and don't want to end up short a slash).
|
||||
*/
|
||||
if ( substr( $category_base, 0, 1 ) === '/' ) {
|
||||
$category_base = substr( $category_base, 1 );
|
||||
}
|
||||
|
||||
$category_base .= '/';
|
||||
|
||||
return preg_replace( '`' . preg_quote( $category_base, '`' ) . '`u', '', $link, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the query vars with the redirect var when stripcategorybase is active.
|
||||
*
|
||||
* @param array $query_vars Main query vars to filter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function query_vars( $query_vars ) {
|
||||
if ( WPSEO_Options::get( 'stripcategorybase' ) === true ) {
|
||||
$query_vars[] = 'wpseo_category_redirect';
|
||||
}
|
||||
|
||||
return $query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the redirect needs to be created.
|
||||
*
|
||||
* @param array $query_vars Query vars to check for existence of redirect var.
|
||||
*
|
||||
* @return array|void The query vars.
|
||||
*/
|
||||
public function request( $query_vars ) {
|
||||
if ( ! isset( $query_vars['wpseo_category_redirect'] ) ) {
|
||||
return $query_vars;
|
||||
}
|
||||
|
||||
$this->redirect( $query_vars['wpseo_category_redirect'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* This function taken and only slightly adapted from WP No Category Base plugin by Saurabh Gupta.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function category_rewrite_rules() {
|
||||
global $wp_rewrite;
|
||||
|
||||
$category_rewrite = [];
|
||||
|
||||
$taxonomy = get_taxonomy( 'category' );
|
||||
$permalink_structure = get_option( 'permalink_structure' );
|
||||
|
||||
$blog_prefix = '';
|
||||
if ( is_multisite() && ! is_subdomain_install() && is_main_site() && strpos( $permalink_structure, '/blog/' ) === 0 ) {
|
||||
$blog_prefix = 'blog/';
|
||||
}
|
||||
|
||||
$categories = get_categories( [ 'hide_empty' => false ] );
|
||||
if ( is_array( $categories ) && $categories !== [] ) {
|
||||
foreach ( $categories as $category ) {
|
||||
$category_nicename = $category->slug;
|
||||
if ( $category->parent == $category->cat_ID ) {
|
||||
// Recursive recursion.
|
||||
$category->parent = 0;
|
||||
}
|
||||
elseif ( $taxonomy->rewrite['hierarchical'] != 0 && $category->parent !== 0 ) {
|
||||
$parents = get_category_parents( $category->parent, false, '/', true );
|
||||
if ( ! is_wp_error( $parents ) ) {
|
||||
$category_nicename = $parents . $category_nicename;
|
||||
}
|
||||
unset( $parents );
|
||||
}
|
||||
|
||||
$category_rewrite = $this->add_category_rewrites( $category_rewrite, $category_nicename, $blog_prefix, $wp_rewrite->pagination_base );
|
||||
|
||||
// Adds rules for the uppercase encoded URIs.
|
||||
$category_nicename_filtered = $this->convert_encoded_to_upper( $category_nicename );
|
||||
|
||||
if ( $category_nicename_filtered !== $category_nicename ) {
|
||||
$category_rewrite = $this->add_category_rewrites( $category_rewrite, $category_nicename_filtered, $blog_prefix, $wp_rewrite->pagination_base );
|
||||
}
|
||||
}
|
||||
unset( $categories, $category, $category_nicename, $category_nicename_filtered );
|
||||
}
|
||||
|
||||
// Redirect support from Old Category Base.
|
||||
$old_base = $wp_rewrite->get_category_permastruct();
|
||||
$old_base = str_replace( '%category%', '(.+)', $old_base );
|
||||
$old_base = trim( $old_base, '/' );
|
||||
$category_rewrite[ $old_base . '$' ] = 'index.php?wpseo_category_redirect=$matches[1]';
|
||||
|
||||
return $category_rewrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds required category rewrites rules.
|
||||
*
|
||||
* @param array $rewrites The current set of rules.
|
||||
* @param string $category_name Category nicename.
|
||||
* @param string $blog_prefix Multisite blog prefix.
|
||||
* @param string $pagination_base WP_Query pagination base.
|
||||
*
|
||||
* @return array The added set of rules.
|
||||
*/
|
||||
protected function add_category_rewrites( $rewrites, $category_name, $blog_prefix, $pagination_base ) {
|
||||
$rewrite_name = $blog_prefix . '(' . $category_name . ')';
|
||||
|
||||
$rewrites[ $rewrite_name . '/(?:feed/)?(feed|rdf|rss|rss2|atom)/?$' ] = 'index.php?category_name=$matches[1]&feed=$matches[2]';
|
||||
$rewrites[ $rewrite_name . '/' . $pagination_base . '/?([0-9]{1,})/?$' ] = 'index.php?category_name=$matches[1]&paged=$matches[2]';
|
||||
$rewrites[ $rewrite_name . '/?$' ] = 'index.php?category_name=$matches[1]';
|
||||
|
||||
return $rewrites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks through category nicename and convert encoded parts
|
||||
* into uppercase using $this->encode_to_upper().
|
||||
*
|
||||
* @param string $name The encoded category URI string.
|
||||
*
|
||||
* @return string The convered URI string.
|
||||
*/
|
||||
protected function convert_encoded_to_upper( $name ) {
|
||||
// Checks if name has any encoding in it.
|
||||
if ( strpos( $name, '%' ) === false ) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
$names = explode( '/', $name );
|
||||
$names = array_map( [ $this, 'encode_to_upper' ], $names );
|
||||
|
||||
return implode( '/', $names );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the encoded URI string to uppercase.
|
||||
*
|
||||
* @param string $encoded The encoded string.
|
||||
*
|
||||
* @return string The uppercased string.
|
||||
*/
|
||||
public function encode_to_upper( $encoded ) {
|
||||
if ( strpos( $encoded, '%' ) === false ) {
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
return strtoupper( $encoded );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the "old" category URL to the new one.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $category_redirect The category page to redirect to.
|
||||
* @return void
|
||||
*/
|
||||
protected function redirect( $category_redirect ) {
|
||||
$catlink = trailingslashit( get_option( 'home' ) ) . user_trailingslashit( $category_redirect, 'category' );
|
||||
|
||||
wp_redirect( $catlink, 301, 'Yoast SEO' );
|
||||
exit;
|
||||
}
|
||||
} /* End of class */
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to load assets required for structured data blocks.
|
||||
*/
|
||||
class WPSEO_Structured_Data_Blocks implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* An instance of the WPSEO_Admin_Asset_Manager class.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* Registers hooks for Structured Data Blocks with WordPress.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_assets' ] );
|
||||
add_filter( 'block_categories', [ $this, 'add_block_category' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Structured Data Blocks are disabled.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function check_enabled() {
|
||||
/**
|
||||
* Filter: 'wpseo_enable_structured_data_blocks' - Allows disabling Yoast's schema blocks entirely.
|
||||
*
|
||||
* @api bool If false, our structured data blocks won't show.
|
||||
*/
|
||||
$enabled = apply_filters( 'wpseo_enable_structured_data_blocks', true );
|
||||
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue Gutenberg block assets for backend editor.
|
||||
*/
|
||||
public function enqueue_block_editor_assets() {
|
||||
if ( ! $this->check_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->asset_manager ) {
|
||||
$this->asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
}
|
||||
|
||||
$this->asset_manager->enqueue_script( 'structured-data-blocks' );
|
||||
$this->asset_manager->enqueue_style( 'structured-data-blocks' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the structured data blocks category to the Gutenberg categories.
|
||||
*
|
||||
* @param array $categories The current categories.
|
||||
*
|
||||
* @return array The updated categories.
|
||||
*/
|
||||
public function add_block_category( $categories ) {
|
||||
if ( $this->check_enabled() ) {
|
||||
$categories[] = [
|
||||
'slug' => 'yoast-structured-data-blocks',
|
||||
'title' => sprintf(
|
||||
/* translators: %1$s expands to Yoast. */
|
||||
__( '%1$s Structured Data Blocks', 'wordpress-seo' ),
|
||||
'Yoast'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
}
|
||||
133
wp-content/plugins/wordpress-seo/inc/class-upgrade-history.php
Normal file
133
wp-content/plugins/wordpress-seo/inc/class-upgrade-history.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internal
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class handles storing the current options for future reference.
|
||||
*
|
||||
* This should only be used during an upgrade routine.
|
||||
*/
|
||||
class WPSEO_Upgrade_History {
|
||||
|
||||
/**
|
||||
* Option to use to store/retrieve data from.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $option_name = 'wpseo_upgrade_history';
|
||||
|
||||
/**
|
||||
* WPSEO_Upgrade_History constructor.
|
||||
*
|
||||
* @param null|string $option_name Optional. Custom option to use to store/retrieve history from.
|
||||
*/
|
||||
public function __construct( $option_name = null ) {
|
||||
if ( $option_name !== null ) {
|
||||
$this->option_name = $option_name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the content of the history items currently stored.
|
||||
*
|
||||
* @return array The contents of the history option.
|
||||
*/
|
||||
public function get() {
|
||||
$data = get_option( $this->get_option_name(), [] );
|
||||
if ( ! is_array( $data ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new history entry in the storage.
|
||||
*
|
||||
* @param string $old_version The version we are upgrading from.
|
||||
* @param string $new_version The version we are upgrading to.
|
||||
* @param array $option_names The options that need to be stored.
|
||||
*/
|
||||
public function add( $old_version, $new_version, array $option_names ) {
|
||||
$option_data = [];
|
||||
if ( $option_names !== [] ) {
|
||||
$option_data = $this->get_options_data( $option_names );
|
||||
}
|
||||
|
||||
// Retrieve current history.
|
||||
$data = $this->get();
|
||||
|
||||
// Add new entry.
|
||||
$data[ time() ] = [
|
||||
'options' => $option_data,
|
||||
'old_version' => $old_version,
|
||||
'new_version' => $new_version,
|
||||
];
|
||||
|
||||
// Store the data.
|
||||
$this->set( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the data for the specified option names from the database.
|
||||
*
|
||||
* @param array $option_names The option names to retrieve.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_options_data( array $option_names ) {
|
||||
$wpdb = $this->get_wpdb();
|
||||
|
||||
$sql = $wpdb->prepare(
|
||||
'
|
||||
SELECT option_value, option_name FROM ' . $wpdb->options . ' WHERE
|
||||
option_name IN ( ' . implode( ',', array_fill( 0, count( $option_names ), '%s' ) ) . ' )
|
||||
',
|
||||
$option_names
|
||||
);
|
||||
|
||||
$results = $wpdb->get_results( $sql, ARRAY_A );
|
||||
|
||||
$data = [];
|
||||
foreach ( $results as $result ) {
|
||||
$data[ $result['option_name'] ] = maybe_unserialize( $result['option_value'] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the new history state.
|
||||
*
|
||||
* @param array $data The data to store.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set( array $data ) {
|
||||
// This should not be autoloaded!
|
||||
update_option( $this->get_option_name(), $data, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the WPDB object.
|
||||
*
|
||||
* @return wpdb The WPDB object to use.
|
||||
*/
|
||||
protected function get_wpdb() {
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the option name to store the history in.
|
||||
*
|
||||
* @return string The option name to store the history in.
|
||||
*/
|
||||
protected function get_option_name() {
|
||||
return $this->option_name;
|
||||
}
|
||||
}
|
||||
865
wp-content/plugins/wordpress-seo/inc/class-upgrade.php
Normal file
865
wp-content/plugins/wordpress-seo/inc/class-upgrade.php
Normal file
@@ -0,0 +1,865 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internal
|
||||
*/
|
||||
|
||||
/**
|
||||
* This code handles the option upgrades.
|
||||
*/
|
||||
class WPSEO_Upgrade {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$version = WPSEO_Options::get( 'version' );
|
||||
|
||||
WPSEO_Options::maybe_set_multisite_defaults( false );
|
||||
|
||||
$routines = [
|
||||
'1.5.0' => 'upgrade_15',
|
||||
'2.0' => 'upgrade_20',
|
||||
'2.1' => 'upgrade_21',
|
||||
'2.2' => 'upgrade_22',
|
||||
'2.3' => 'upgrade_23',
|
||||
'3.0' => 'upgrade_30',
|
||||
'3.3' => 'upgrade_33',
|
||||
'3.6' => 'upgrade_36',
|
||||
'4.0' => 'upgrade_40',
|
||||
'4.4' => 'upgrade_44',
|
||||
'4.7' => 'upgrade_47',
|
||||
'4.9' => 'upgrade_49',
|
||||
'5.0' => 'upgrade_50',
|
||||
'5.1' => 'upgrade_50_51',
|
||||
'5.5' => 'upgrade_55',
|
||||
'5.6' => 'upgrade_56',
|
||||
'6.1' => 'upgrade_61',
|
||||
'6.3' => 'upgrade_63',
|
||||
'7.0-RC0' => 'upgrade_70',
|
||||
'7.1-RC0' => 'upgrade_71',
|
||||
'7.3-RC0' => 'upgrade_73',
|
||||
'7.4-RC0' => 'upgrade_74',
|
||||
'7.5.3' => 'upgrade_753',
|
||||
'7.7-RC0' => 'upgrade_77',
|
||||
'7.7.2-RC0' => 'upgrade_772',
|
||||
'9.0-RC0' => 'upgrade_90',
|
||||
'10.0-RC0' => 'upgrade_100',
|
||||
'11.1-RC0' => 'upgrade_111',
|
||||
/** Reset notifications because we removed the AMP Glue plugin notification */
|
||||
'12.1-RC0' => 'clean_all_notifications',
|
||||
'12.3-RC0' => 'upgrade_123',
|
||||
'12.4-RC0' => 'upgrade_124',
|
||||
'12.8-RC0' => 'upgrade_128',
|
||||
];
|
||||
|
||||
array_walk( $routines, [ $this, 'run_upgrade_routine' ], $version );
|
||||
|
||||
if ( version_compare( $version, '12.5-RC0', '<' ) ) {
|
||||
/*
|
||||
* We have to run this by hook, because otherwise:
|
||||
* - the theme support check isn't available.
|
||||
* - the notification center notifications are not filled yet.
|
||||
*/
|
||||
add_action( 'init', [ $this, 'upgrade_125' ] );
|
||||
}
|
||||
|
||||
// Since 3.7.
|
||||
$upsell_notice = new WPSEO_Product_Upsell_Notice();
|
||||
$upsell_notice->set_upgrade_notice();
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_run_upgrade' - Runs the upgrade hook which are dependent on Yoast SEO.
|
||||
*
|
||||
* @api string - The current version of Yoast SEO
|
||||
*/
|
||||
do_action( 'wpseo_run_upgrade', $version );
|
||||
|
||||
$this->finish_up();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the upgrade routine.
|
||||
*
|
||||
* @param string $routine The method to call.
|
||||
* @param string $version The new version.
|
||||
* @param string $current_version The current set version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function run_upgrade_routine( $routine, $version, $current_version ) {
|
||||
if ( version_compare( $current_version, $version, '<' ) ) {
|
||||
$this->$routine( $current_version );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new upgrade history entry.
|
||||
*
|
||||
* @param string $current_version The old version from which we are upgrading.
|
||||
* @param string $new_version The version we are upgrading to.
|
||||
*/
|
||||
protected function add_upgrade_history( $current_version, $new_version ) {
|
||||
$upgrade_history = new WPSEO_Upgrade_History();
|
||||
$upgrade_history->add( $current_version, $new_version, array_keys( WPSEO_Options::$options ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the needed cleanup after an update, setting the DB version to latest version, flushing caches etc.
|
||||
*/
|
||||
protected function finish_up() {
|
||||
WPSEO_Options::set( 'version', WPSEO_VERSION );
|
||||
|
||||
// Just flush rewrites, always, to at least make them work after an upgrade.
|
||||
add_action( 'shutdown', 'flush_rewrite_rules' );
|
||||
|
||||
// Flush the sitemap cache.
|
||||
WPSEO_Sitemaps_Cache::clear();
|
||||
|
||||
// Make sure all our options always exist - issue #1245.
|
||||
WPSEO_Options::ensure_options_exist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the Yoast SEO 1.5 upgrade routine.
|
||||
*
|
||||
* @param string $version Current plugin version.
|
||||
*/
|
||||
private function upgrade_15( $version ) {
|
||||
// Clean up options and meta.
|
||||
WPSEO_Options::clean_up( null, $version );
|
||||
WPSEO_Meta::clean_up();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves options that moved position in WPSEO 2.0.
|
||||
*/
|
||||
private function upgrade_20() {
|
||||
/**
|
||||
* Clean up stray wpseo_ms options from the options table, option should only exist in the sitemeta table.
|
||||
* This could have been caused in many version of Yoast SEO, so deleting it for everything below 2.0.
|
||||
*/
|
||||
delete_option( 'wpseo_ms' );
|
||||
|
||||
$wpseo = $this->get_option_from_database( 'wpseo' );
|
||||
$this->save_option_setting( $wpseo, 'pinterestverify' );
|
||||
|
||||
// Re-save option to trigger sanitization.
|
||||
$this->cleanup_option_data( 'wpseo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if taxonomy terms were split and updates the corresponding taxonomy meta's accordingly.
|
||||
*/
|
||||
private function upgrade_21() {
|
||||
$taxonomies = get_option( 'wpseo_taxonomy_meta', [] );
|
||||
|
||||
if ( ! empty( $taxonomies ) ) {
|
||||
foreach ( $taxonomies as $taxonomy => $tax_metas ) {
|
||||
foreach ( $tax_metas as $term_id => $tax_meta ) {
|
||||
if ( function_exists( 'wp_get_split_term' ) ) {
|
||||
$new_term_id = wp_get_split_term( $term_id, $taxonomy );
|
||||
if ( $new_term_id !== false ) {
|
||||
$taxonomies[ $taxonomy ][ $new_term_id ] = $taxonomies[ $taxonomy ][ $term_id ];
|
||||
unset( $taxonomies[ $taxonomy ][ $term_id ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_option( 'wpseo_taxonomy_meta', $taxonomies );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs upgrade functions to Yoast SEO 2.2.
|
||||
*/
|
||||
private function upgrade_22() {
|
||||
// Unschedule our tracking.
|
||||
wp_clear_scheduled_hook( 'yoast_tracking' );
|
||||
|
||||
$this->cleanup_option_data( 'wpseo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules upgrade function to Yoast SEO 2.3.
|
||||
*/
|
||||
private function upgrade_23() {
|
||||
add_action( 'wp', [ $this, 'upgrade_23_query' ], 90 );
|
||||
add_action( 'admin_head', [ $this, 'upgrade_23_query' ], 90 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs upgrade query to Yoast SEO 2.3.
|
||||
*/
|
||||
public function upgrade_23_query() {
|
||||
$wp_query = new WP_Query( 'post_type=any&meta_key=_yoast_wpseo_sitemap-include&meta_value=never&order=ASC' );
|
||||
|
||||
if ( ! empty( $wp_query->posts ) ) {
|
||||
$options = get_option( 'wpseo_xml' );
|
||||
|
||||
$excluded_posts = [];
|
||||
if ( $options['excluded-posts'] !== '' ) {
|
||||
$excluded_posts = explode( ',', $options['excluded-posts'] );
|
||||
}
|
||||
|
||||
foreach ( $wp_query->posts as $post ) {
|
||||
if ( ! in_array( $post->ID, $excluded_posts ) ) {
|
||||
$excluded_posts[] = $post->ID;
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the meta value.
|
||||
$options['excluded-posts'] = implode( ',', $excluded_posts );
|
||||
|
||||
// Update the option.
|
||||
update_option( 'wpseo_xml', $options );
|
||||
}
|
||||
|
||||
// Remove the meta fields.
|
||||
delete_post_meta_by_key( '_yoast_wpseo_sitemap-include' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs upgrade functions to Yoast SEO 3.0.
|
||||
*/
|
||||
private function upgrade_30() {
|
||||
// Remove the meta fields for sitemap prio.
|
||||
delete_post_meta_by_key( '_yoast_wpseo_sitemap-prio' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs upgrade functions to Yoast SEO 3.3.
|
||||
*/
|
||||
private function upgrade_33() {
|
||||
// Notification dismissals have been moved to User Meta instead of global option.
|
||||
delete_option( Yoast_Notification_Center::STORAGE_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs upgrade functions to Yoast SEO 3.6.
|
||||
*/
|
||||
private function upgrade_36() {
|
||||
global $wpdb;
|
||||
|
||||
// Between 3.2 and 3.4 the sitemap options were saved with autoloading enabled.
|
||||
$wpdb->query( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE "wpseo_sitemap_%" AND autoload = "yes"' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the about notice when its still in the database.
|
||||
*/
|
||||
private function upgrade_40() {
|
||||
$center = Yoast_Notification_Center::get();
|
||||
$center->remove_notification_by_id( 'wpseo-dismiss-about' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the content-analysis-active and keyword-analysis-acive options from wpseo-titles to wpseo.
|
||||
*/
|
||||
private function upgrade_44() {
|
||||
$wpseo_titles = $this->get_option_from_database( 'wpseo_titles' );
|
||||
|
||||
$this->save_option_setting( $wpseo_titles, 'content-analysis-active', 'content_analysis_active' );
|
||||
$this->save_option_setting( $wpseo_titles, 'keyword-analysis-active', 'keyword_analysis_active' );
|
||||
|
||||
// Remove irrelevant content from the option.
|
||||
$this->cleanup_option_data( 'wpseo_titles' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the meta name for the cornerstone content. It was a public meta field and it has to be private.
|
||||
*/
|
||||
private function upgrade_47() {
|
||||
global $wpdb;
|
||||
|
||||
// The meta key has to be private, so prefix it.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'UPDATE ' . $wpdb->postmeta . ' SET meta_key = %s WHERE meta_key = "yst_is_cornerstone"',
|
||||
WPSEO_Cornerstone_Filter::META_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the 'wpseo-dismiss-about' notice for every user that still has it.
|
||||
*/
|
||||
private function upgrade_49() {
|
||||
global $wpdb;
|
||||
|
||||
/*
|
||||
* Using a filter to remove the notification for the current logged in user. The notification center is
|
||||
* initializing the notifications before the upgrade routine has been executedd and is saving the stored
|
||||
* notifications on shutdown. This causes the returning notification. By adding this filter the shutdown
|
||||
* routine on the notification center will remove the notification.
|
||||
*/
|
||||
add_filter( 'yoast_notifications_before_storage', [ $this, 'remove_about_notice' ] );
|
||||
|
||||
$meta_key = $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY;
|
||||
|
||||
$usermetas = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'
|
||||
SELECT user_id, meta_value
|
||||
FROM ' . $wpdb->usermeta . '
|
||||
WHERE meta_key = %s AND meta_value LIKE %s
|
||||
',
|
||||
$meta_key,
|
||||
'%wpseo-dismiss-about%'
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( empty( $usermetas ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $usermetas as $usermeta ) {
|
||||
$notifications = maybe_unserialize( $usermeta['meta_value'] );
|
||||
|
||||
foreach ( $notifications as $notification_key => $notification ) {
|
||||
if ( ! empty( $notification['options']['id'] ) && $notification['options']['id'] === 'wpseo-dismiss-about' ) {
|
||||
unset( $notifications[ $notification_key ] );
|
||||
}
|
||||
}
|
||||
|
||||
update_user_option( $usermeta['user_id'], Yoast_Notification_Center::STORAGE_KEY, array_values( $notifications ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the wpseo-dismiss-about notice from a list of notifications.
|
||||
*
|
||||
* @param Yoast_Notification[] $notifications The notifications to filter.
|
||||
*
|
||||
* @return Yoast_Notification[] The filtered list of notifications. Excluding the wpseo-dismiss-about notification.
|
||||
*/
|
||||
public function remove_about_notice( $notifications ) {
|
||||
foreach ( $notifications as $notification_key => $notification ) {
|
||||
if ( $notification->get_id() === 'wpseo-dismiss-about' ) {
|
||||
unset( $notifications[ $notification_key ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the yoast_seo_links table to the database.
|
||||
*/
|
||||
private function upgrade_50() {
|
||||
global $wpdb;
|
||||
|
||||
$link_installer = new WPSEO_Link_Installer();
|
||||
$link_installer->install();
|
||||
|
||||
// Trigger reindex notification.
|
||||
$notifier = new WPSEO_Link_Notifier();
|
||||
$notifier->manage_notification();
|
||||
|
||||
// Deletes the post meta value, which might created in the RC.
|
||||
$wpdb->query( 'DELETE FROM ' . $wpdb->postmeta . ' WHERE meta_key = "_yst_content_links_processed"' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal_link_count column to support improved functionality.
|
||||
*
|
||||
* @param string $version The current version to compare with.
|
||||
*/
|
||||
private function upgrade_50_51( $version ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( version_compare( $version, '5.0', '>=' ) ) {
|
||||
$count_storage = new WPSEO_Meta_Storage();
|
||||
$wpdb->query( 'ALTER TABLE ' . $count_storage->get_table_name() . ' MODIFY internal_link_count int(10) UNSIGNED NULL DEFAULT NULL' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new capabilities and roles.
|
||||
*/
|
||||
private function upgrade_55() {
|
||||
// Register roles.
|
||||
do_action( 'wpseo_register_roles' );
|
||||
WPSEO_Role_Manager_Factory::get()->add();
|
||||
|
||||
// Register capabilities.
|
||||
do_action( 'wpseo_register_capabilities' );
|
||||
WPSEO_Capability_Manager_Factory::get()->add();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates legacy license page options to the latest version.
|
||||
*/
|
||||
private function upgrade_56() {
|
||||
global $wpdb;
|
||||
|
||||
// Make sure License Server checks are on the latest server version by default.
|
||||
update_option( 'wpseo_license_server_version', WPSEO_License_Page_Manager::VERSION_BACKWARDS_COMPATIBILITY );
|
||||
|
||||
// Make sure incoming link count entries are at least 0, not NULL.
|
||||
$count_storage = new WPSEO_Meta_Storage();
|
||||
$wpdb->query( 'UPDATE ' . $count_storage->get_table_name() . ' SET incoming_link_count = 0 WHERE incoming_link_count IS NULL' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the links for the link count when there is a difference between the site and home url.
|
||||
* We've used the site url instead of the home url.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_61() {
|
||||
// When the home url is the same as the site url, just do nothing.
|
||||
if ( home_url() === site_url() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$link_storage = new WPSEO_Link_Storage();
|
||||
$wpdb->query( 'DELETE FROM ' . $link_storage->get_table_name() );
|
||||
|
||||
$meta_storage = new WPSEO_Meta_Storage();
|
||||
$wpdb->query( 'DELETE FROM ' . $meta_storage->get_table_name() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes some no longer used options for noindexing subpages and for meta keywords and its associated templates.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_63() {
|
||||
$this->cleanup_option_data( 'wpseo_titles' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the 7.0 upgrade, moves settings around, deletes several options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_70() {
|
||||
|
||||
$wpseo_permalinks = $this->get_option_from_database( 'wpseo_permalinks' );
|
||||
$wpseo_xml = $this->get_option_from_database( 'wpseo_xml' );
|
||||
$wpseo_rss = $this->get_option_from_database( 'wpseo_rss' );
|
||||
$wpseo = $this->get_option_from_database( 'wpseo' );
|
||||
$wpseo_internallinks = $this->get_option_from_database( 'wpseo_internallinks' );
|
||||
|
||||
// Move some permalink settings, then delete the option.
|
||||
$this->save_option_setting( $wpseo_permalinks, 'redirectattachment', 'disable-attachment' );
|
||||
$this->save_option_setting( $wpseo_permalinks, 'stripcategorybase' );
|
||||
|
||||
// Move one XML sitemap setting, then delete the option.
|
||||
$this->save_option_setting( $wpseo_xml, 'enablexmlsitemap', 'enable_xml_sitemap' );
|
||||
|
||||
|
||||
// Move the RSS settings to the search appearance settings, then delete the RSS option.
|
||||
$this->save_option_setting( $wpseo_rss, 'rssbefore' );
|
||||
$this->save_option_setting( $wpseo_rss, 'rssafter' );
|
||||
|
||||
$this->save_option_setting( $wpseo, 'company_logo' );
|
||||
$this->save_option_setting( $wpseo, 'company_name' );
|
||||
$this->save_option_setting( $wpseo, 'company_or_person' );
|
||||
$this->save_option_setting( $wpseo, 'person_name' );
|
||||
|
||||
// Remove the website name and altername name as we no longer need them.
|
||||
$this->cleanup_option_data( 'wpseo' );
|
||||
|
||||
// All the breadcrumbs settings have moved to the search appearance settings.
|
||||
foreach ( array_keys( $wpseo_internallinks ) as $key ) {
|
||||
$this->save_option_setting( $wpseo_internallinks, $key );
|
||||
}
|
||||
|
||||
// Convert hidden metabox options to display metabox options.
|
||||
$title_options = get_option( 'wpseo_titles' );
|
||||
|
||||
foreach ( $title_options as $key => $value ) {
|
||||
if ( strpos( $key, 'hideeditbox-tax-' ) === 0 ) {
|
||||
$taxonomy = substr( $key, strlen( 'hideeditbox-tax-' ) );
|
||||
WPSEO_Options::set( 'display-metabox-tax-' . $taxonomy, ! $value );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( strpos( $key, 'hideeditbox-' ) === 0 ) {
|
||||
$post_type = substr( $key, strlen( 'hideeditbox-' ) );
|
||||
WPSEO_Options::set( 'display-metabox-pt-' . $post_type, ! $value );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup removed options.
|
||||
delete_option( 'wpseo_xml' );
|
||||
delete_option( 'wpseo_permalinks' );
|
||||
delete_option( 'wpseo_rss' );
|
||||
delete_option( 'wpseo_internallinks' );
|
||||
|
||||
// Remove possibly present plugin conflict notice for plugin that was removed from the list of conflicting plugins.
|
||||
$yoast_plugin_conflict = WPSEO_Plugin_Conflict::get_instance();
|
||||
$yoast_plugin_conflict->clear_error( 'header-footer/plugin.php' );
|
||||
|
||||
// Moves the user meta for excluding from the XML sitemap to a noindex.
|
||||
global $wpdb;
|
||||
$wpdb->query( "UPDATE $wpdb->usermeta SET meta_key = 'wpseo_noindex_author' WHERE meta_key = 'wpseo_excludeauthorsitemap'" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the 7.1 upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_71() {
|
||||
$this->cleanup_option_data( 'wpseo_social' );
|
||||
|
||||
// Move the breadcrumbs setting and invert it.
|
||||
$title_options = $this->get_option_from_database( 'wpseo_titles' );
|
||||
|
||||
if ( array_key_exists( 'breadcrumbs-blog-remove', $title_options ) ) {
|
||||
WPSEO_Options::set( 'breadcrumbs-display-blog-page', ! $title_options['breadcrumbs-blog-remove'] );
|
||||
|
||||
$this->cleanup_option_data( 'wpseo_titles' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the 7.3 upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_73() {
|
||||
global $wpdb;
|
||||
// We've moved the cornerstone checkbox to our proper namespace.
|
||||
$wpdb->query( "UPDATE $wpdb->postmeta SET meta_key = '_yoast_wpseo_is_cornerstone' WHERE meta_key = '_yst_is_cornerstone'" );
|
||||
|
||||
// Remove the previous Whip dismissed message, as this is a new one regarding PHP 5.2.
|
||||
delete_option( 'whip_dismiss_timestamp' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 7.4 upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_74() {
|
||||
$this->remove_sitemap_validators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 7.5.3 upgrade.
|
||||
*
|
||||
* When upgrading purging media is potentially relevant.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_753() {
|
||||
// Only when attachments are not disabled.
|
||||
if ( WPSEO_Options::get( 'disable-attachment' ) === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only when attachments are not no-indexed.
|
||||
if ( WPSEO_Options::get( 'noindex-attachment' ) === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set purging relevancy.
|
||||
WPSEO_Options::set( 'is-media-purge-relevant', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 7.7 upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_77() {
|
||||
// Remove all OpenGraph content image cache.
|
||||
$this->delete_post_meta( '_yoast_wpseo_post_image_cache' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 7.7.2 upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_772() {
|
||||
if ( WPSEO_Utils::is_woocommerce_active() ) {
|
||||
$this->migrate_woocommerce_archive_setting_to_shop_page();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 9.0 upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_90() {
|
||||
global $wpdb;
|
||||
|
||||
// Invalidate all sitemap cache transients.
|
||||
WPSEO_Sitemaps_Cache_Validator::cleanup_database();
|
||||
|
||||
// Removes all scheduled tasks for hitting the sitemap index.
|
||||
wp_clear_scheduled_hook( 'wpseo_hit_sitemap_index' );
|
||||
|
||||
$wpdb->query( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE "wpseo_sitemap_%"' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 10.0 upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_100() {
|
||||
// Removes recalibration notifications.
|
||||
$this->clean_all_notifications();
|
||||
|
||||
// Removes recalibration options.
|
||||
WPSEO_Options::clean_up( 'wpseo' );
|
||||
delete_option( 'wpseo_recalibration_beta_mailinglist_subscription' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 11.1 upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upgrade_111() {
|
||||
// Set company_or_person to company when it's an invalid value.
|
||||
$company_or_person = WPSEO_Options::get( 'company_or_person', '' );
|
||||
|
||||
if ( ! in_array( $company_or_person, [ 'company', 'person' ], true ) ) {
|
||||
WPSEO_Options::set( 'company_or_person', 'company' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 12.3 upgrade.
|
||||
*
|
||||
* Removes the about notice when its still in the database.
|
||||
*/
|
||||
private function upgrade_123() {
|
||||
$plugins = [
|
||||
'yoast-seo-premium',
|
||||
'video-seo-for-wordpress-seo-by-yoast',
|
||||
'yoast-news-seo',
|
||||
'local-seo-for-yoast-seo',
|
||||
'yoast-woocommerce-seo',
|
||||
'yoast-acf-analysis',
|
||||
];
|
||||
|
||||
$center = Yoast_Notification_Center::get();
|
||||
foreach ( $plugins as $plugin ) {
|
||||
$center->remove_notification_by_id( 'wpseo-outdated-yoast-seo-plugin-' . $plugin );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 12.4 upgrade.
|
||||
*
|
||||
* Removes the Google plus defaults from the database.
|
||||
*/
|
||||
private function upgrade_124() {
|
||||
$this->cleanup_option_data( 'wpseo_social' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 12.5 upgrade.
|
||||
*/
|
||||
public function upgrade_125() {
|
||||
// Disables the force rewrite title when the theme supports it through WordPress.
|
||||
if ( WPSEO_Options::get( 'forcerewritetitle', false ) && current_theme_supports( 'title-tag' ) ) {
|
||||
WPSEO_Options::set( 'forcerewritetitle', false );
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$wpdb->query( "DELETE FROM $wpdb->usermeta WHERE meta_key = 'wp_yoast_promo_hide_premium_upsell_admin_block'" );
|
||||
|
||||
// Removes the WordPress update notification, because it is no longer necessary when WordPress 5.3 is released.
|
||||
$center = Yoast_Notification_Center::get();
|
||||
$center->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the 12.8 upgrade.
|
||||
*/
|
||||
private function upgrade_128() {
|
||||
// Re-save wpseo to make sure bf_banner_2019_dismissed key is gone.
|
||||
$this->cleanup_option_data( 'wpseo' );
|
||||
|
||||
Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-page_comments-notice' );
|
||||
Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-wordpress-upgrade' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all notifications saved in the database under 'wp_yoast_notifications'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function clean_all_notifications() {
|
||||
global $wpdb;
|
||||
delete_metadata( 'user', 0, $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY, '', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the post meta fields for a given meta key.
|
||||
*
|
||||
* @param string $meta_key The meta key.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function delete_post_meta( $meta_key ) {
|
||||
global $wpdb;
|
||||
$deleted = $wpdb->delete( $wpdb->postmeta, [ 'meta_key' => $meta_key ], [ '%s' ] );
|
||||
|
||||
if ( $deleted ) {
|
||||
wp_cache_set( 'last_changed', microtime(), 'posts' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all sitemap validators.
|
||||
*
|
||||
* This should be executed on every upgrade routine until we have removed the sitemap caching in the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function remove_sitemap_validators() {
|
||||
global $wpdb;
|
||||
|
||||
// Remove all sitemap validators.
|
||||
$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'wpseo_sitemap%validator%'" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the option value directly from the database.
|
||||
*
|
||||
* @param string $option_name Option to retrieve.
|
||||
*
|
||||
* @return array|mixed The content of the option if exists, otherwise an empty array.
|
||||
*/
|
||||
protected function get_option_from_database( $option_name ) {
|
||||
global $wpdb;
|
||||
|
||||
// Load option directly from the database, to avoid filtering and sanitization.
|
||||
$sql = $wpdb->prepare( 'SELECT option_value FROM ' . $wpdb->options . ' WHERE option_name = %s', $option_name );
|
||||
$results = $wpdb->get_results( $sql, ARRAY_A );
|
||||
if ( ! empty( $results ) ) {
|
||||
return maybe_unserialize( $results[0]['option_value'] );
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the option to make sure only relevant settings are there.
|
||||
*
|
||||
* @param string $option_name Option name save.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function cleanup_option_data( $option_name ) {
|
||||
$data = get_option( $option_name, [] );
|
||||
if ( ! is_array( $data ) || $data === [] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up the option by re-saving it.
|
||||
*
|
||||
* The option framework will remove any settings that are not configured
|
||||
* for this option, removing any migrated settings.
|
||||
*/
|
||||
update_option( $option_name, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an option setting to where it should be stored.
|
||||
*
|
||||
* @param array $source_data The option containing the value to be migrated.
|
||||
* @param string $source_setting Name of the key in the "from" option.
|
||||
* @param string|null $target_setting Name of the key in the "to" option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function save_option_setting( $source_data, $source_setting, $target_setting = null ) {
|
||||
if ( $target_setting === null ) {
|
||||
$target_setting = $source_setting;
|
||||
}
|
||||
|
||||
if ( isset( $source_data[ $source_setting ] ) ) {
|
||||
WPSEO_Options::set( $target_setting, $source_data[ $source_setting ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates WooCommerce archive settings to the WooCommerce Shop page meta-data settings.
|
||||
*
|
||||
* If no Shop page is defined, nothing will be migrated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function migrate_woocommerce_archive_setting_to_shop_page() {
|
||||
$shop_page_id = wc_get_page_id( 'shop' );
|
||||
|
||||
if ( $shop_page_id === -1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$title = WPSEO_Meta::get_value( 'title', $shop_page_id );
|
||||
|
||||
if ( empty( $title ) ) {
|
||||
$option_title = WPSEO_Options::get( 'title-ptarchive-product' );
|
||||
|
||||
WPSEO_Meta::set_value(
|
||||
'title',
|
||||
$option_title,
|
||||
$shop_page_id
|
||||
);
|
||||
|
||||
WPSEO_Options::set( 'title-ptarchive-product', '' );
|
||||
}
|
||||
|
||||
$meta_description = WPSEO_Meta::get_value( 'metadesc', $shop_page_id );
|
||||
|
||||
if ( empty( $meta_description ) ) {
|
||||
$option_metadesc = WPSEO_Options::get( 'metadesc-ptarchive-product' );
|
||||
|
||||
WPSEO_Meta::set_value(
|
||||
'metadesc',
|
||||
$option_metadesc,
|
||||
$shop_page_id
|
||||
);
|
||||
|
||||
WPSEO_Options::set( 'metadesc-ptarchive-product', '' );
|
||||
}
|
||||
|
||||
$bc_title = WPSEO_Meta::get_value( 'bctitle', $shop_page_id );
|
||||
|
||||
if ( empty( $bc_title ) ) {
|
||||
$option_bctitle = WPSEO_Options::get( 'bctitle-ptarchive-product' );
|
||||
|
||||
WPSEO_Meta::set_value(
|
||||
'bctitle',
|
||||
$option_bctitle,
|
||||
$shop_page_id
|
||||
);
|
||||
|
||||
WPSEO_Options::set( 'bctitle-ptarchive-product', '' );
|
||||
}
|
||||
|
||||
$noindex = WPSEO_Meta::get_value( 'meta-robots-noindex', $shop_page_id );
|
||||
|
||||
if ( $noindex === '0' ) {
|
||||
$option_noindex = WPSEO_Options::get( 'noindex-ptarchive-product' );
|
||||
|
||||
WPSEO_Meta::set_value(
|
||||
'meta-robots-noindex',
|
||||
$option_noindex,
|
||||
$shop_page_id
|
||||
);
|
||||
|
||||
WPSEO_Options::set( 'noindex-ptarchive-product', false );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for the Yoast SEO admin bar menu.
|
||||
*/
|
||||
class WPSEO_Admin_Bar_Menu implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* The identifier used for the menu.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MENU_IDENTIFIER = 'wpseo-menu';
|
||||
|
||||
/**
|
||||
* The identifier used for the Keyword Research submenu.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const KEYWORD_RESEARCH_SUBMENU_IDENTIFIER = 'wpseo-kwresearch';
|
||||
|
||||
/**
|
||||
* The identifier used for the Analysis submenu.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ANALYSIS_SUBMENU_IDENTIFIER = 'wpseo-analysis';
|
||||
|
||||
/**
|
||||
* The identifier used for the Settings submenu.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SETTINGS_SUBMENU_IDENTIFIER = 'wpseo-settings';
|
||||
|
||||
/**
|
||||
* The identifier used for the Network Settings submenu.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NETWORK_SETTINGS_SUBMENU_IDENTIFIER = 'wpseo-network-settings';
|
||||
|
||||
/**
|
||||
* Asset manager instance.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Sets the asset manager to use.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Manager $asset_manager Optional. Asset manager to use.
|
||||
*/
|
||||
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager = null ) {
|
||||
if ( ! $asset_manager ) {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
}
|
||||
|
||||
$this->asset_manager = $asset_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the admin bar menu.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_menu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
|
||||
// If the current user can't write posts, this is all of no use, so let's not output an admin menu.
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->add_root_menu( $wp_admin_bar );
|
||||
$this->add_keyword_research_submenu( $wp_admin_bar );
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
$this->add_analysis_submenu( $wp_admin_bar );
|
||||
}
|
||||
|
||||
if ( ! is_admin() || is_blog_admin() ) {
|
||||
$this->add_settings_submenu( $wp_admin_bar );
|
||||
}
|
||||
elseif ( is_network_admin() ) {
|
||||
$this->add_network_settings_submenu( $wp_admin_bar );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues admin bar assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
if ( ! is_admin_bar_showing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the current user can't write posts, this is all of no use, so let's not output an admin menu.
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->asset_manager->register_assets();
|
||||
$this->asset_manager->enqueue_style( 'adminbar' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( ! $this->meets_requirements() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_bar_menu', [ $this, 'add_menu' ], 95 );
|
||||
|
||||
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the requirements to use this class are met.
|
||||
*
|
||||
* @return bool True if requirements are met, false otherwise.
|
||||
*/
|
||||
public function meets_requirements() {
|
||||
if ( is_network_admin() ) {
|
||||
return WPSEO_Utils::is_plugin_network_active();
|
||||
}
|
||||
|
||||
if ( WPSEO_Options::get( 'enable_admin_bar_menu' ) !== true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! is_admin() || is_blog_admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the admin bar root menu.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_root_menu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
$title = $this->get_title();
|
||||
|
||||
$score = '';
|
||||
$settings_url = '';
|
||||
$counter = '';
|
||||
$alert_popup = '';
|
||||
|
||||
$post = $this->get_singular_post();
|
||||
if ( $post ) {
|
||||
$score = $this->get_post_score( $post );
|
||||
}
|
||||
|
||||
$term = $this->get_singular_term();
|
||||
if ( $term ) {
|
||||
$score = $this->get_term_score( $term );
|
||||
}
|
||||
|
||||
$can_manage_options = $this->can_manage_options();
|
||||
|
||||
if ( $can_manage_options ) {
|
||||
$settings_url = $this->get_settings_page_url();
|
||||
}
|
||||
|
||||
if ( empty( $score ) && ! is_network_admin() && $can_manage_options ) {
|
||||
$counter = $this->get_notification_counter();
|
||||
$alert_popup = $this->get_notification_alert_popup();
|
||||
}
|
||||
|
||||
$admin_bar_menu_args = [
|
||||
'id' => self::MENU_IDENTIFIER,
|
||||
'title' => $title . $score . $counter . $alert_popup,
|
||||
'href' => $settings_url,
|
||||
'meta' => [ 'tabindex' => ! empty( $settings_url ) ? false : '0' ],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $admin_bar_menu_args );
|
||||
|
||||
if ( ! empty( $counter ) ) {
|
||||
$admin_bar_menu_args = [
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'wpseo-notifications',
|
||||
'title' => __( 'Notifications', 'wordpress-seo' ) . $counter,
|
||||
'href' => $settings_url,
|
||||
'meta' => [ 'tabindex' => ! empty( $settings_url ) ? false : '0' ],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $admin_bar_menu_args );
|
||||
}
|
||||
|
||||
if ( ! is_network_admin() && $can_manage_options ) {
|
||||
$admin_bar_menu_args = [
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'wpseo-configuration-wizard',
|
||||
'title' => __( 'Configuration Wizard', 'wordpress-seo' ),
|
||||
'href' => admin_url( 'admin.php?page=' . WPSEO_Configuration_Page::PAGE_IDENTIFIER ),
|
||||
];
|
||||
$wp_admin_bar->add_menu( $admin_bar_menu_args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the admin bar keyword research submenu.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_keyword_research_submenu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
$adwords_url = 'https://yoa.st/keywordplanner';
|
||||
$trends_url = 'https://yoa.st/google-trends';
|
||||
|
||||
$post = $this->get_singular_post();
|
||||
if ( $post ) {
|
||||
$focus_keyword = $this->get_post_focus_keyword( $post );
|
||||
|
||||
if ( ! empty( $focus_keyword ) ) {
|
||||
$trends_url .= '#q=' . urlencode( $focus_keyword );
|
||||
}
|
||||
}
|
||||
|
||||
$menu_args = [
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => self::KEYWORD_RESEARCH_SUBMENU_IDENTIFIER,
|
||||
'title' => __( 'Keyword Research', 'wordpress-seo' ),
|
||||
'meta' => [ 'tabindex' => '0' ],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
|
||||
$submenu_items = [
|
||||
[
|
||||
'id' => 'wpseo-kwresearchtraining',
|
||||
'title' => __( 'Keyword research training', 'wordpress-seo' ),
|
||||
'href' => WPSEO_Shortlinker::get( 'https://yoa.st/wp-admin-bar' ),
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-adwordsexternal',
|
||||
'title' => __( 'Google Ads', 'wordpress-seo' ),
|
||||
'href' => $adwords_url,
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-googleinsights',
|
||||
'title' => __( 'Google Trends', 'wordpress-seo' ),
|
||||
'href' => $trends_url,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ( $submenu_items as $menu_item ) {
|
||||
$menu_args = [
|
||||
'parent' => self::KEYWORD_RESEARCH_SUBMENU_IDENTIFIER,
|
||||
'id' => $menu_item['id'],
|
||||
'title' => $menu_item['title'],
|
||||
'href' => $menu_item['href'],
|
||||
'meta' => [ 'target' => '_blank' ],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the admin bar analysis submenu.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_analysis_submenu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
$url = WPSEO_Frontend::get_instance()->canonical( false );
|
||||
$focus_keyword = '';
|
||||
|
||||
if ( ! $url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post = $this->get_singular_post();
|
||||
if ( $post ) {
|
||||
$focus_keyword = $this->get_post_focus_keyword( $post );
|
||||
}
|
||||
|
||||
$menu_args = [
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => self::ANALYSIS_SUBMENU_IDENTIFIER,
|
||||
'title' => __( 'Analyze this page', 'wordpress-seo' ),
|
||||
'meta' => [ 'tabindex' => '0' ],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
|
||||
$encoded_url = urlencode( $url );
|
||||
$submenu_items = [
|
||||
[
|
||||
'id' => 'wpseo-inlinks',
|
||||
'title' => __( 'Check links to this URL', 'wordpress-seo' ),
|
||||
'href' => 'https://search.google.com/search-console/links/drilldown?resource_id=' . urlencode( get_option( 'siteurl' ) ) . '&type=EXTERNAL&target=' . $encoded_url . '&domain=',
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-kwdensity',
|
||||
'title' => __( 'Check Keyphrase Density', 'wordpress-seo' ),
|
||||
// HTTPS not available.
|
||||
'href' => 'http://www.zippy.co.uk/keyworddensity/index.php?url=' . $encoded_url . '&keyword=' . urlencode( $focus_keyword ),
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-cache',
|
||||
'title' => __( 'Check Google Cache', 'wordpress-seo' ),
|
||||
'href' => '//webcache.googleusercontent.com/search?strip=1&q=cache:' . $encoded_url,
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-header',
|
||||
'title' => __( 'Check Headers', 'wordpress-seo' ),
|
||||
'href' => '//quixapp.com/headers/?r=' . urlencode( $url ),
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-structureddata',
|
||||
'title' => __( 'Google Structured Data Test', 'wordpress-seo' ),
|
||||
'href' => 'https://search.google.com/structured-data/testing-tool#url=' . $encoded_url,
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-facebookdebug',
|
||||
'title' => __( 'Facebook Debugger', 'wordpress-seo' ),
|
||||
'href' => '//developers.facebook.com/tools/debug/og/object?q=' . $encoded_url,
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-pinterestvalidator',
|
||||
'title' => __( 'Pinterest Rich Pins Validator', 'wordpress-seo' ),
|
||||
'href' => 'https://developers.pinterest.com/tools/url-debugger/?link=' . $encoded_url,
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-htmlvalidation',
|
||||
'title' => __( 'HTML Validator', 'wordpress-seo' ),
|
||||
'href' => '//validator.w3.org/check?uri=' . $encoded_url,
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-cssvalidation',
|
||||
'title' => __( 'CSS Validator', 'wordpress-seo' ),
|
||||
'href' => '//jigsaw.w3.org/css-validator/validator?uri=' . $encoded_url,
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-pagespeed',
|
||||
'title' => __( 'Google Page Speed Test', 'wordpress-seo' ),
|
||||
'href' => '//developers.google.com/speed/pagespeed/insights/?url=' . $encoded_url,
|
||||
],
|
||||
[
|
||||
'id' => 'wpseo-google-mobile-friendly',
|
||||
'title' => __( 'Mobile-Friendly Test', 'wordpress-seo' ),
|
||||
'href' => 'https://www.google.com/webmasters/tools/mobile-friendly/?url=' . $encoded_url,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ( $submenu_items as $menu_item ) {
|
||||
$menu_args = [
|
||||
'parent' => self::ANALYSIS_SUBMENU_IDENTIFIER,
|
||||
'id' => $menu_item['id'],
|
||||
'title' => $menu_item['title'],
|
||||
'href' => $menu_item['href'],
|
||||
'meta' => [ 'target' => '_blank' ],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the admin bar settings submenu.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_settings_submenu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
if ( ! $this->can_manage_options() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$admin_menu = new WPSEO_Admin_Menu( new WPSEO_Menu() );
|
||||
$submenu_pages = $admin_menu->get_submenu_pages();
|
||||
|
||||
$menu_args = [
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => self::SETTINGS_SUBMENU_IDENTIFIER,
|
||||
'title' => __( 'SEO Settings', 'wordpress-seo' ),
|
||||
'meta' => [ 'tabindex' => '0' ],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
|
||||
foreach ( $submenu_pages as $submenu_page ) {
|
||||
if ( ! current_user_can( $submenu_page[3] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = 'wpseo-' . str_replace( '_', '-', str_replace( 'wpseo_', '', $submenu_page[4] ) );
|
||||
if ( $id === 'wpseo-dashboard' ) {
|
||||
$id = 'wpseo-general';
|
||||
}
|
||||
|
||||
$menu_args = [
|
||||
'parent' => self::SETTINGS_SUBMENU_IDENTIFIER,
|
||||
'id' => $id,
|
||||
'title' => $submenu_page[2],
|
||||
'href' => admin_url( 'admin.php?page=' . urlencode( $submenu_page[4] ) ),
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the admin bar network settings submenu.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_network_settings_submenu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
if ( ! $this->can_manage_options() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$network_admin_menu = new WPSEO_Network_Admin_Menu( new WPSEO_Menu() );
|
||||
$submenu_pages = $network_admin_menu->get_submenu_pages();
|
||||
|
||||
$menu_args = [
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => self::NETWORK_SETTINGS_SUBMENU_IDENTIFIER,
|
||||
'title' => __( 'SEO Settings', 'wordpress-seo' ),
|
||||
'meta' => [ 'tabindex' => '0' ],
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
|
||||
foreach ( $submenu_pages as $submenu_page ) {
|
||||
if ( ! current_user_can( $submenu_page[3] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = 'wpseo-' . str_replace( '_', '-', str_replace( 'wpseo_', '', $submenu_page[4] ) );
|
||||
if ( $id === 'wpseo-dashboard' ) {
|
||||
$id = 'wpseo-general';
|
||||
}
|
||||
|
||||
$menu_args = [
|
||||
'parent' => self::NETWORK_SETTINGS_SUBMENU_IDENTIFIER,
|
||||
'id' => $id,
|
||||
'title' => $submenu_page[2],
|
||||
'href' => network_admin_url( 'admin.php?page=' . urlencode( $submenu_page[4] ) ),
|
||||
];
|
||||
$wp_admin_bar->add_menu( $menu_args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the menu title markup.
|
||||
*
|
||||
* @return string Admin bar title markup.
|
||||
*/
|
||||
protected function get_title() {
|
||||
return '<div id="yoast-ab-icon" class="ab-item yoast-logo svg"><span class="screen-reader-text">' . __( 'SEO', 'wordpress-seo' ) . '</span></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current post if in a singular post context.
|
||||
*
|
||||
* @global string $pagenow Current page identifier.
|
||||
* @global WP_Post|null $post Current post object, or null if none available.
|
||||
*
|
||||
* @return WP_Post|null Post object, or null if not in singular context.
|
||||
*/
|
||||
protected function get_singular_post() {
|
||||
global $pagenow, $post;
|
||||
|
||||
if ( ! is_singular() && ( ! is_blog_admin() || ! WPSEO_Metabox::is_post_edit( $pagenow ) ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! isset( $post ) || ! is_object( $post ) || ! $post instanceof WP_Post ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the focus keyword for a given post.
|
||||
*
|
||||
* @param WP_Post $post Post object to get its focus keyword.
|
||||
*
|
||||
* @return string Focus keyword, or empty string if none available.
|
||||
*/
|
||||
protected function get_post_focus_keyword( $post ) {
|
||||
if ( ! is_object( $post ) || ! property_exists( $post, 'ID' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_use_page_analysis' Determines if the analysis should be enabled.
|
||||
*
|
||||
* @api bool Determines if the analysis should be enabled.
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_use_page_analysis', true ) !== true ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return WPSEO_Meta::get_value( 'focuskw', $post->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the score for a given post.
|
||||
*
|
||||
* @param WP_Post $post Post object to get its score.
|
||||
*
|
||||
* @return string Score markup, or empty string if none available.
|
||||
*/
|
||||
protected function get_post_score( $post ) {
|
||||
if ( ! is_object( $post ) || ! property_exists( $post, 'ID' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( apply_filters( 'wpseo_use_page_analysis', true ) !== true ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$analysis_readability = new WPSEO_Metabox_Analysis_Readability();
|
||||
|
||||
if ( $analysis_seo->is_enabled() ) {
|
||||
return $this->get_score( WPSEO_Meta::get_value( 'linkdex', $post->ID ) );
|
||||
}
|
||||
|
||||
if ( $analysis_readability->is_enabled() ) {
|
||||
return $this->get_score( WPSEO_Meta::get_value( 'content_score', $post->ID ) );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current term if in a singular term context.
|
||||
*
|
||||
* @global string $pagenow Current page identifier.
|
||||
* @global WP_Query $wp_query Current query object.
|
||||
* @global WP_Term|null $tag Current term object, or null if none available.
|
||||
*
|
||||
* @return WP_Term|null Term object, or null if not in singular context.
|
||||
*/
|
||||
protected function get_singular_term() {
|
||||
global $pagenow, $wp_query, $tag;
|
||||
|
||||
if ( is_category() || is_tag() || is_tax() ) {
|
||||
return $wp_query->get_queried_object();
|
||||
}
|
||||
|
||||
if ( WPSEO_Taxonomy::is_term_edit( $pagenow ) && ! WPSEO_Taxonomy::is_term_overview( $pagenow ) && isset( $tag ) && is_object( $tag ) && ! is_wp_error( $tag ) ) {
|
||||
return get_term( $tag->term_id );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the score for a given term.
|
||||
*
|
||||
* @param WP_Term $term Term object to get its score.
|
||||
*
|
||||
* @return string Score markup, or empty string if none available.
|
||||
*/
|
||||
protected function get_term_score( $term ) {
|
||||
if ( ! is_object( $term ) || ! property_exists( $term, 'term_id' ) || ! property_exists( $term, 'taxonomy' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$analysis_readability = new WPSEO_Metabox_Analysis_Readability();
|
||||
|
||||
if ( $analysis_seo->is_enabled() ) {
|
||||
return $this->get_score( WPSEO_Taxonomy_Meta::get_term_meta( $term->term_id, $term->taxonomy, 'linkdex' ) );
|
||||
}
|
||||
|
||||
if ( $analysis_readability->is_enabled() ) {
|
||||
return $this->get_score( WPSEO_Taxonomy_Meta::get_term_meta( $term->term_id, $term->taxonomy, 'content_score' ) );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the SEO score and makes the score icon for the admin bar for it.
|
||||
*
|
||||
* @param int $score The 0-100 rating of the score. Can be either SEO score or content score.
|
||||
*
|
||||
* @return string Score markup.
|
||||
*/
|
||||
protected function get_score( $score ) {
|
||||
$score_class = WPSEO_Utils::translate_score( $score );
|
||||
$translated_score = WPSEO_Utils::translate_score( $score, false );
|
||||
/* translators: %s expands to the SEO score. */
|
||||
$screen_reader_text = sprintf( __( 'SEO score: %s', 'wordpress-seo' ), $translated_score );
|
||||
|
||||
$score_adminbar_element = '<div class="wpseo-score-icon adminbar-seo-score ' . $score_class . '"><span class="adminbar-seo-score-text screen-reader-text">' . $screen_reader_text . '</span></div>';
|
||||
|
||||
return $score_adminbar_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL to the main admin settings page.
|
||||
*
|
||||
* @return string Admin settings page URL.
|
||||
*/
|
||||
protected function get_settings_page_url() {
|
||||
return self_admin_url( 'admin.php?page=' . WPSEO_Admin::PAGE_IDENTIFIER );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the notification counter if in a valid context.
|
||||
*
|
||||
* @return string Notification counter markup, or empty string if not available.
|
||||
*/
|
||||
protected function get_notification_counter() {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_count = $notification_center->get_notification_count();
|
||||
|
||||
if ( ! $notification_count ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/* translators: %s: number of notifications */
|
||||
$counter_screen_reader_text = sprintf( _n( '%s notification', '%s notifications', $notification_count, 'wordpress-seo' ), number_format_i18n( $notification_count ) );
|
||||
|
||||
return sprintf( ' <div class="wp-core-ui wp-ui-notification yoast-issue-counter"><span aria-hidden="true">%d</span><span class="screen-reader-text">%s</span></div>', $notification_count, $counter_screen_reader_text );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the notification alert popup if in a valid context.
|
||||
*
|
||||
* @return string Notification alert popup markup, or empty string if not available.
|
||||
*/
|
||||
protected function get_notification_alert_popup() {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$new_notifications = $notification_center->get_new_notifications();
|
||||
$new_notifications_count = count( $new_notifications );
|
||||
|
||||
if ( ! $new_notifications_count ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$notification = sprintf(
|
||||
_n(
|
||||
'There is a new notification.',
|
||||
'There are new notifications.',
|
||||
$new_notifications_count,
|
||||
'wordpress-seo'
|
||||
),
|
||||
$new_notifications_count
|
||||
);
|
||||
|
||||
return '<div class="yoast-issue-added">' . $notification . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current user can manage options in the current context.
|
||||
*
|
||||
* @return bool True if capabilities are sufficient, false otherwise.
|
||||
*/
|
||||
protected function can_manage_options() {
|
||||
return is_network_admin() && current_user_can( 'wpseo_manage_network_options' ) || ! is_network_admin() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
/**
|
||||
* WPSEO_Content_Images.
|
||||
*/
|
||||
class WPSEO_Content_Images {
|
||||
|
||||
/**
|
||||
* Retrieves images from the post content.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
* @param \WP_Post $post The post object.
|
||||
*
|
||||
* @return array An array of images found in this post.
|
||||
*/
|
||||
public function get_images( $post_id, $post = null ) {
|
||||
return $this->get_images_from_content( $this->get_post_content( $post_id, $post ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the images from the content.
|
||||
*
|
||||
* @param string $content The post content string.
|
||||
*
|
||||
* @return array An array of image URLs.
|
||||
*/
|
||||
public function get_images_from_content( $content ) {
|
||||
if ( ! is_string( $content ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$content_images = $this->get_img_tags_from_content( $content );
|
||||
$images = array_map( [ $this, 'get_img_tag_source' ], $content_images );
|
||||
$images = array_filter( $images );
|
||||
$images = array_unique( $images );
|
||||
$images = array_values( $images ); // Reset the array keys.
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image tags from a given content string.
|
||||
*
|
||||
* @param string $content The content to search for image tags.
|
||||
*
|
||||
* @return array An array of `<img>` tags.
|
||||
*/
|
||||
private function get_img_tags_from_content( $content ) {
|
||||
if ( strpos( $content, '<img' ) === false ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
preg_match_all( '`<img [^>]+>`', $content, $matches );
|
||||
if ( isset( $matches[0] ) ) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the image URL from an image tag.
|
||||
*
|
||||
* @param string $image Image HTML element.
|
||||
*
|
||||
* @return string|bool The image URL on success, false on failure.
|
||||
*/
|
||||
private function get_img_tag_source( $image ) {
|
||||
preg_match( '`src=(["\'])(.*?)\1`', $image, $matches );
|
||||
if ( isset( $matches[2] ) ) {
|
||||
return $matches[2];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post content we want to work with.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
* @param WP_Post|array|null $post The post.
|
||||
*
|
||||
* @return string The content of the supplied post.
|
||||
*/
|
||||
private function get_post_content( $post_id, $post ) {
|
||||
if ( $post === null ) {
|
||||
$post = get_post( $post_id );
|
||||
}
|
||||
|
||||
if ( $post === null ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_pre_analysis_post_content' - Allow filtering the content before analysis.
|
||||
*
|
||||
* @api string $post_content The Post content string.
|
||||
*/
|
||||
$content = apply_filters( 'wpseo_pre_analysis_post_content', $post->post_content, $post );
|
||||
|
||||
if ( ! is_string( $content ) ) {
|
||||
$content = '';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Removes the cached images on post save.
|
||||
*
|
||||
* @deprecated 7.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $post_id The post id to remove the images from.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_cached_images( $post_id ) {
|
||||
_deprecated_function( __METHOD__, '7.7.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @deprecated 9.6
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 9.6' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
/**
|
||||
* WPSEO_Custom_Fields.
|
||||
*/
|
||||
class WPSEO_Custom_Fields {
|
||||
|
||||
/**
|
||||
* Custom fields cache.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $custom_fields = null;
|
||||
|
||||
/**
|
||||
* Retrieves the custom field names as an array.
|
||||
*
|
||||
* @link WordPress core: wp-admin/includes/template.php. Reused query from it.
|
||||
*
|
||||
* @return array The custom fields.
|
||||
*/
|
||||
public static function get_custom_fields() {
|
||||
global $wpdb;
|
||||
|
||||
// Use cached value if available.
|
||||
if ( ! is_null( self::$custom_fields ) ) {
|
||||
return self::$custom_fields;
|
||||
}
|
||||
|
||||
self::$custom_fields = [];
|
||||
|
||||
/**
|
||||
* Filters the number of custom fields to retrieve for the drop-down
|
||||
* in the Custom Fields meta box.
|
||||
*
|
||||
* @param int $limit Number of custom fields to retrieve. Default 30.
|
||||
*/
|
||||
$limit = apply_filters( 'postmeta_form_limit', 30 );
|
||||
$sql = "SELECT DISTINCT meta_key
|
||||
FROM $wpdb->postmeta
|
||||
WHERE meta_key NOT BETWEEN '_' AND '_z'
|
||||
HAVING meta_key NOT LIKE %s
|
||||
ORDER BY meta_key
|
||||
LIMIT %d";
|
||||
$fields = $wpdb->get_col( $wpdb->prepare( $sql, $wpdb->esc_like( '_' ) . '%', $limit ) );
|
||||
|
||||
if ( is_array( $fields ) ) {
|
||||
self::$custom_fields = array_map( [ 'WPSEO_Custom_Fields', 'add_custom_field_prefix' ], $fields );
|
||||
}
|
||||
|
||||
return self::$custom_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the cf_ prefix to a field.
|
||||
*
|
||||
* @param string $field The field to prefix.
|
||||
*
|
||||
* @return string The prefixed field.
|
||||
*/
|
||||
private static function add_custom_field_prefix( $field ) {
|
||||
return 'cf_' . $field;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
/**
|
||||
* WPSEO_Custom_Taxonomies.
|
||||
*/
|
||||
class WPSEO_Custom_Taxonomies {
|
||||
|
||||
/**
|
||||
* Custom taxonomies cache.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $custom_taxonomies = null;
|
||||
|
||||
/**
|
||||
* Gets the names of the custom taxonomies, prepends 'ct_' and 'ct_desc', and returns them in an array.
|
||||
*
|
||||
* @return array The custom taxonomy prefixed names.
|
||||
*/
|
||||
public static function get_custom_taxonomies() {
|
||||
// Use cached value if available.
|
||||
if ( ! is_null( self::$custom_taxonomies ) ) {
|
||||
return self::$custom_taxonomies;
|
||||
}
|
||||
|
||||
self::$custom_taxonomies = [];
|
||||
$args = [
|
||||
'public' => true,
|
||||
'_builtin' => false,
|
||||
];
|
||||
$custom_taxonomies = get_taxonomies( $args, 'names', 'and' );
|
||||
|
||||
if ( is_array( $custom_taxonomies ) ) {
|
||||
foreach ( $custom_taxonomies as $custom_taxonomy ) {
|
||||
array_push(
|
||||
self::$custom_taxonomies,
|
||||
self::add_custom_taxonomies_prefix( $custom_taxonomy ),
|
||||
self::add_custom_taxonomies_description_prefix( $custom_taxonomy )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$custom_taxonomies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the ct_ prefix to a taxonomy.
|
||||
*
|
||||
* @param string $taxonomy The taxonomy to prefix.
|
||||
*
|
||||
* @return string The prefixed taxonomy.
|
||||
*/
|
||||
private static function add_custom_taxonomies_prefix( $taxonomy ) {
|
||||
return 'ct_' . $taxonomy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the ct_desc_ prefix to a taxonomy.
|
||||
*
|
||||
* @param string $taxonomy The taxonomy to prefix.
|
||||
*
|
||||
* @return string The prefixed taxonomy.
|
||||
*/
|
||||
private static function add_custom_taxonomies_description_prefix( $taxonomy ) {
|
||||
return 'ct_desc_' . $taxonomy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Endpoint_Factory.
|
||||
*/
|
||||
class WPSEO_Endpoint_Factory {
|
||||
|
||||
/**
|
||||
* The valid HTTP methods.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $valid_http_methods = [
|
||||
'GET',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'PUT',
|
||||
'DELETE',
|
||||
];
|
||||
|
||||
/**
|
||||
* The arguments.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $args = [];
|
||||
|
||||
/**
|
||||
* The namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* The endpoint URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $endpoint;
|
||||
|
||||
/**
|
||||
* The callback to execute if the endpoint is called.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
private $callback;
|
||||
|
||||
/**
|
||||
* The permission callback to execute to determine permissions.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
private $permission_callback;
|
||||
|
||||
/**
|
||||
* The HTTP method to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $method;
|
||||
|
||||
/**
|
||||
* WPSEO_Endpoint_Factory constructor.
|
||||
*
|
||||
* @param string $namespace The endpoint's namespace.
|
||||
* @param string $endpoint The endpoint's URL.
|
||||
* @param callable $callback The callback function to execute.
|
||||
* @param callable $permission_callback The permission callback to execute to determine permissions.
|
||||
* @param string $method The HTTP method to use. Defaults to GET.
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception The invalid argument exception.
|
||||
*/
|
||||
public function __construct( $namespace, $endpoint, $callback, $permission_callback, $method = WP_REST_Server::READABLE ) {
|
||||
if ( ! WPSEO_Validator::is_string( $namespace ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $namespace, 'namespace' );
|
||||
}
|
||||
|
||||
$this->namespace = $namespace;
|
||||
|
||||
if ( ! WPSEO_Validator::is_string( $endpoint ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $endpoint, 'endpoint' );
|
||||
}
|
||||
|
||||
$this->endpoint = $endpoint;
|
||||
|
||||
if ( ! is_callable( $callback ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_callable_parameter( $callback, 'callback' );
|
||||
}
|
||||
|
||||
$this->callback = $callback;
|
||||
|
||||
if ( ! is_callable( $permission_callback ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_callable_parameter( $permission_callback, 'callback' );
|
||||
}
|
||||
|
||||
$this->permission_callback = $permission_callback;
|
||||
|
||||
$this->method = $this->validate_method( $method );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated arguments.
|
||||
*
|
||||
* @return array The arguments.
|
||||
*/
|
||||
public function get_arguments() {
|
||||
return $this->args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not there are any arguments present.
|
||||
*
|
||||
* @return bool Whether or not any arguments are present.
|
||||
*/
|
||||
public function has_arguments() {
|
||||
return count( $this->args ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the endpoint with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$config = [
|
||||
'methods' => $this->method,
|
||||
'callback' => $this->callback,
|
||||
'permission_callback' => $this->permission_callback,
|
||||
];
|
||||
|
||||
if ( $this->has_arguments() ) {
|
||||
$config['args'] = $this->args;
|
||||
}
|
||||
|
||||
register_rest_route( $this->namespace, $this->endpoint, $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the method parameter.
|
||||
*
|
||||
* @param string $method The set method parameter.
|
||||
*
|
||||
* @return string The validated method.
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception The invalid argument exception.
|
||||
* @throws InvalidArgumentException The invalid argument exception.
|
||||
*/
|
||||
protected function validate_method( $method ) {
|
||||
if ( ! WPSEO_Validator::is_string( $method ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $method, 'method' );
|
||||
}
|
||||
|
||||
if ( ! in_array( $method, $this->valid_http_methods, true ) ) {
|
||||
throw new InvalidArgumentException( sprintf( '%s is not a valid HTTP method', $method ) );
|
||||
}
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an argument to the endpoint.
|
||||
*
|
||||
* @param string $name The name of the argument.
|
||||
* @param string $description The description associated with the argument.
|
||||
* @param string $type The type of value that can be assigned to the argument.
|
||||
* @param bool $required Whether or not it's a required argument. Defaults to true.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_argument( $name, $description, $type, $required = true ) {
|
||||
if ( in_array( $name, array_keys( $this->args ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->args[ $name ] = [
|
||||
'description' => $description,
|
||||
'type' => $type,
|
||||
'required' => $required,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internal
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class containing method for WPSEO Features.
|
||||
*/
|
||||
class WPSEO_Features {
|
||||
|
||||
/**
|
||||
* Checks if the premium constant exists to make sure if plugin is the premium one.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_premium() {
|
||||
return ( defined( 'WPSEO_PREMIUM_FILE' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if using the free version of the plugin.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_free() {
|
||||
return ! $this->is_premium();
|
||||
}
|
||||
}
|
||||
454
wp-content/plugins/wordpress-seo/inc/class-wpseo-image-utils.php
Normal file
454
wp-content/plugins/wordpress-seo/inc/class-wpseo-image-utils.php
Normal file
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
/**
|
||||
* WPSEO_Image_Utils.
|
||||
*/
|
||||
class WPSEO_Image_Utils {
|
||||
|
||||
/**
|
||||
* Find an attachment ID for a given URL.
|
||||
*
|
||||
* @param string $url The URL to find the attachment for.
|
||||
*
|
||||
* @return int The found attachment ID, or 0 if none was found.
|
||||
*/
|
||||
public static function get_attachment_by_url( $url ) {
|
||||
/*
|
||||
* As get_attachment_by_url won't work on resized versions of images,
|
||||
* we strip out the size part of an image URL.
|
||||
*/
|
||||
$url = preg_replace( '/(.*)-\d+x\d+\.(jpg|png|gif)$/', '$1.$2', $url );
|
||||
|
||||
// Don't try to do this for external URLs.
|
||||
if ( strpos( $url, get_site_url() ) !== 0 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( function_exists( 'wpcom_vip_attachment_url_to_postid' ) ) {
|
||||
// @codeCoverageIgnoreStart -- We can't test this properly.
|
||||
return (int) wpcom_vip_attachment_url_to_postid( $url );
|
||||
// @codeCoverageIgnoreEnd -- The rest we _can_ test.
|
||||
}
|
||||
|
||||
return self::attachment_url_to_postid( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the attachment_url_to_postid with use of WP Cache.
|
||||
*
|
||||
* @param string $url The attachment URL for which we want to know the Post ID.
|
||||
*
|
||||
* @return int The Post ID belonging to the attachment, 0 if not found.
|
||||
*/
|
||||
protected static function attachment_url_to_postid( $url ) {
|
||||
$cache_key = sprintf( 'yoast_attachment_url_post_id_%s', md5( $url ) );
|
||||
|
||||
// Set the ID based on the hashed URL in the cache.
|
||||
$id = wp_cache_get( $cache_key );
|
||||
|
||||
if ( $id === 'not_found' ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ID is found in cache, return.
|
||||
if ( $id !== false ) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.VIP.RestrictedFunctions -- We use the WP COM version if we can, see above.
|
||||
$id = attachment_url_to_postid( $url );
|
||||
|
||||
if ( empty( $id ) ) {
|
||||
wp_cache_set( $cache_key, 'not_found', '', ( 12 * HOUR_IN_SECONDS + wp_rand( 0, ( 4 * HOUR_IN_SECONDS ) ) ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We have the Post ID, but it's not in the cache yet. We do that here and return.
|
||||
wp_cache_set( $cache_key, $id, '', ( 24 * HOUR_IN_SECONDS + wp_rand( 0, ( 12 * HOUR_IN_SECONDS ) ) ) );
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the image data.
|
||||
*
|
||||
* @param array $image Image array with URL and metadata.
|
||||
* @param int $attachment_id Attachment ID.
|
||||
*
|
||||
* @return false|array $image {
|
||||
* Array of image data
|
||||
*
|
||||
* @type string $alt Image's alt text.
|
||||
* @type string $alt Image's alt text.
|
||||
* @type int $width Width of image.
|
||||
* @type int $height Height of image.
|
||||
* @type string $type Image's MIME type.
|
||||
* @type string $url Image's URL.
|
||||
* @type int $filesize The file size in bytes, if already set.
|
||||
* }
|
||||
*/
|
||||
public static function get_data( $image, $attachment_id ) {
|
||||
if ( ! is_array( $image ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deals with non-set keys and values being null or false.
|
||||
if ( empty( $image['width'] ) || empty( $image['height'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image['id'] = $attachment_id;
|
||||
$image['alt'] = self::get_alt_tag( $attachment_id );
|
||||
$image['pixels'] = ( (int) $image['width'] * (int) $image['height'] );
|
||||
|
||||
if ( ! isset( $image['type'] ) ) {
|
||||
$image['type'] = get_post_mime_type( $attachment_id );
|
||||
}
|
||||
|
||||
// Keep only the keys we need, and nothing else.
|
||||
return array_intersect_key( $image, array_flip( [ 'id', 'alt', 'path', 'width', 'height', 'pixels', 'type', 'size', 'url', 'filesize' ] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a size version of an image to see if it's not too heavy.
|
||||
*
|
||||
* @param array $image Image to check the file size of.
|
||||
*
|
||||
* @return bool True when the image is within limits, false if not.
|
||||
*/
|
||||
public static function has_usable_file_size( $image ) {
|
||||
if ( ! is_array( $image ) || $image === [] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_image_image_weight_limit' - Determines what the maximum weight
|
||||
* (in bytes) of an image is allowed to be, default is 2 MB.
|
||||
*
|
||||
* @api int - The maximum weight (in bytes) of an image.
|
||||
*/
|
||||
$max_size = apply_filters( 'wpseo_image_image_weight_limit', 2097152 );
|
||||
|
||||
// We cannot check without a path, so assume it's fine.
|
||||
if ( ! isset( $image['path'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ( self::get_file_size( $image ) <= $max_size );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the right version of an image based on size.
|
||||
*
|
||||
* @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 static function get_image( $attachment_id, $size ) {
|
||||
$image = false;
|
||||
if ( $size === 'full' ) {
|
||||
$image = self::get_full_size_image_data( $attachment_id );
|
||||
}
|
||||
|
||||
if ( ! $image ) {
|
||||
$image = image_get_intermediate_size( $attachment_id, $size );
|
||||
$image['size'] = $size;
|
||||
}
|
||||
|
||||
if ( ! $image ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::get_data( $image, $attachment_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image data for the full size image.
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
*
|
||||
* @return array|false Array when there is a full size image. False if not.
|
||||
*/
|
||||
protected static function get_full_size_image_data( $attachment_id ) {
|
||||
$image = wp_get_attachment_metadata( $attachment_id );
|
||||
if ( ! is_array( $image ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image['url'] = wp_get_attachment_image_url( $attachment_id, 'full' );
|
||||
$image['path'] = get_attached_file( $attachment_id );
|
||||
$image['size'] = 'full';
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the full file path for a given image file.
|
||||
*
|
||||
* @param string $path The relative file path.
|
||||
*
|
||||
* @return string The full file path.
|
||||
*/
|
||||
public static function get_absolute_path( $path ) {
|
||||
static $uploads;
|
||||
|
||||
if ( $uploads === null ) {
|
||||
$uploads = wp_get_upload_dir();
|
||||
}
|
||||
|
||||
// Add the uploads basedir if the path does not start with it.
|
||||
if ( empty( $uploads['error'] ) && strpos( $path, $uploads['basedir'] ) !== 0 ) {
|
||||
return $uploads['basedir'] . DIRECTORY_SEPARATOR . ltrim( $path, DIRECTORY_SEPARATOR );
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relative path of the image.
|
||||
*
|
||||
* @param string $img Image URL.
|
||||
*
|
||||
* @return string The expanded image URL.
|
||||
*/
|
||||
public static function get_relative_path( $img ) {
|
||||
if ( $img[0] !== '/' ) {
|
||||
return $img;
|
||||
}
|
||||
|
||||
// If it's a relative URL, it's relative to the domain, not necessarily to the WordPress install, we
|
||||
// want to preserve domain name and URL scheme (http / https) though.
|
||||
$parsed_url = wp_parse_url( home_url() );
|
||||
$img = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $img;
|
||||
|
||||
return $img;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image file size.
|
||||
*
|
||||
* @param array $image An image array object.
|
||||
*
|
||||
* @return int The file size in bytes.
|
||||
*/
|
||||
public static function get_file_size( $image ) {
|
||||
if ( isset( $image['filesize'] ) ) {
|
||||
return $image['filesize'];
|
||||
}
|
||||
|
||||
// If the file size for the file is over our limit, we're going to go for a smaller version.
|
||||
// @todo Save the filesize to the image metadata.
|
||||
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- If file size doesn't properly return, we'll not fail.
|
||||
return @filesize( self::get_absolute_path( $image['path'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the different image variations for consideration.
|
||||
*
|
||||
* @param int $attachment_id The attachment to return the variations for.
|
||||
*
|
||||
* @return array The different variations possible for this attachment ID.
|
||||
*/
|
||||
public static function get_variations( $attachment_id ) {
|
||||
$variations = [];
|
||||
|
||||
foreach ( self::get_sizes() as $size ) {
|
||||
$variation = self::get_image( $attachment_id, $size );
|
||||
|
||||
// The get_image function returns false if the size doesn't exist for this attachment.
|
||||
if ( $variation ) {
|
||||
$variations[] = $variation;
|
||||
}
|
||||
}
|
||||
|
||||
return $variations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check original size of image. If original image is too small, return false, else return true.
|
||||
*
|
||||
* Filters a list of variations by a certain set of usable dimensions.
|
||||
*
|
||||
* @param array $usable_dimensions {
|
||||
* The parameters to check against.
|
||||
*
|
||||
* @type int $min_width Minimum width of image.
|
||||
* @type int $max_width Maximum width of image.
|
||||
* @type int $min_height Minimum height of image.
|
||||
* @type int $max_height Maximum height of image.
|
||||
* }
|
||||
* @param array $variations The variations that should be considered.
|
||||
*
|
||||
* @return array Whether a variation is fit for display or not.
|
||||
*/
|
||||
public static function filter_usable_dimensions( $usable_dimensions, $variations ) {
|
||||
$filtered = [];
|
||||
|
||||
foreach ( $variations as $variation ) {
|
||||
$dimensions = $variation;
|
||||
|
||||
if ( self::has_usable_dimensions( $dimensions, $usable_dimensions ) ) {
|
||||
$filtered[] = $variation;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a list of variations by (disk) file size.
|
||||
*
|
||||
* @param array $variations The variations to consider.
|
||||
*
|
||||
* @return array The validations that pass the required file size limits.
|
||||
*/
|
||||
public static function filter_usable_file_size( $variations ) {
|
||||
foreach ( $variations as $variation ) {
|
||||
// We return early to prevent measuring the file size of all the variations.
|
||||
if ( self::has_usable_file_size( $variation ) ) {
|
||||
return [ $variation ];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the internal WP image file sizes.
|
||||
*
|
||||
* @return array $image_sizes An array of image sizes.
|
||||
*/
|
||||
public static function get_sizes() {
|
||||
/**
|
||||
* Filter: 'wpseo_image_sizes' - Determines which image sizes we'll loop through to get an appropriate image.
|
||||
*
|
||||
* @api array - The array of image sizes to loop through.
|
||||
*/
|
||||
return apply_filters( 'wpseo_image_sizes', [ 'full', 'large', 'medium_large' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs an image alt text.
|
||||
*
|
||||
* @param int $attachment_id The attachment ID.
|
||||
*
|
||||
* @return string The image alt text.
|
||||
*/
|
||||
public static function get_alt_tag( $attachment_id ) {
|
||||
return (string) get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an img sizes up to the parameters.
|
||||
*
|
||||
* @param array $dimensions The image values.
|
||||
* @param array $usable_dimensions The parameters to check against.
|
||||
*
|
||||
* @return bool True if the image has usable measurements, false if not.
|
||||
*/
|
||||
private static function has_usable_dimensions( $dimensions, $usable_dimensions ) {
|
||||
foreach ( [ 'width', 'height' ] as $param ) {
|
||||
$minimum = $usable_dimensions[ 'min_' . $param ];
|
||||
$maximum = $usable_dimensions[ 'max_' . $param ];
|
||||
|
||||
$current = $dimensions[ $param ];
|
||||
if ( ( $current < $minimum ) || ( $current > $maximum ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the post's first usable content image. Null if none is available.
|
||||
*
|
||||
* @param int $post_id The post id.
|
||||
*
|
||||
* @return string|null The image URL.
|
||||
*/
|
||||
public static function get_first_usable_content_image_for_post( $post_id = null ) {
|
||||
$post = get_post( $post_id );
|
||||
|
||||
// We know get_post() returns the post or null.
|
||||
if ( ! $post ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$image_finder = new WPSEO_Content_Images();
|
||||
$images = $image_finder->get_images( $post->ID, $post );
|
||||
|
||||
return self::get_first_image( $images );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the term's first usable content image. Null if none is available.
|
||||
*
|
||||
* @param int $term_id The term id.
|
||||
*
|
||||
* @return string|null The image URL.
|
||||
*/
|
||||
public static function get_first_content_image_for_term( $term_id ) {
|
||||
$term_description = term_description( $term_id );
|
||||
|
||||
// We know term_description() returns a string which may be empty.
|
||||
if ( $term_description === '' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$image_finder = new WPSEO_Content_Images();
|
||||
$images = $image_finder->get_images_from_content( $term_description );
|
||||
|
||||
return self::get_first_image( $images );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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 static function get_attachment_id_from_settings( $setting ) {
|
||||
$image_id = WPSEO_Options::get( $setting . '_id', false );
|
||||
if ( ! $image_id ) {
|
||||
$image = WPSEO_Options::get( $setting, false );
|
||||
if ( $image ) {
|
||||
// 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_id = self::get_attachment_by_url( $image );
|
||||
WPSEO_Options::set( $setting . '_id', $image_id );
|
||||
}
|
||||
}
|
||||
|
||||
return $image_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the first possible image url from an array of images.
|
||||
*
|
||||
* @param array $images The array to extract image url from.
|
||||
*
|
||||
* @return string|null The extracted image url when found, null when not found.
|
||||
*/
|
||||
protected static function get_first_image( $images ) {
|
||||
if ( ! is_array( $images ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$images = array_filter( $images );
|
||||
if ( empty( $images ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return reset( $images );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
* @since 3.6
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class checks if the wpseo option doesn't exists. In the case it doesn't it will set a property that is
|
||||
* accessible via a method to check if the installation is fresh.
|
||||
*/
|
||||
class WPSEO_Installation {
|
||||
|
||||
/**
|
||||
* Checks if Yoast SEO is installed for the first time.
|
||||
*/
|
||||
public function __construct() {
|
||||
$is_first_install = $this->is_first_install();
|
||||
|
||||
if ( $is_first_install && WPSEO_Utils::is_api_available() ) {
|
||||
add_action( 'wpseo_activate', [ $this, 'set_first_install_options' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the option doesn't exist, it should be a new install.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_first_install() {
|
||||
return ( get_option( 'wpseo' ) === false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options on first install for showing the installation notice and disabling of the settings pages.
|
||||
*/
|
||||
public function set_first_install_options() {
|
||||
$options = get_option( 'wpseo' );
|
||||
|
||||
$options['show_onboarding_notice'] = true;
|
||||
$options['first_activated_on'] = time();
|
||||
|
||||
update_option( 'wpseo', $options );
|
||||
}
|
||||
}
|
||||
997
wp-content/plugins/wordpress-seo/inc/class-wpseo-meta.php
Normal file
997
wp-content/plugins/wordpress-seo/inc/class-wpseo-meta.php
Normal file
@@ -0,0 +1,997 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
* @since 1.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class implements defaults and value validation for all WPSEO Post Meta values.
|
||||
*
|
||||
* Some guidelines:
|
||||
* - To update a meta value, you can just use update_post_meta() with the full (prefixed) meta key
|
||||
* or the convenience method WPSEO_Meta::set_value() with the internal key.
|
||||
* All updates will be automatically validated.
|
||||
* Meta values will only be saved to the database if they are *not* the same as the default to
|
||||
* keep database load low.
|
||||
* - To retrieve a WPSEO meta value, you **must** use WPSEO_Meta::get_value() which will always return a
|
||||
* string value, either the saved value or the default.
|
||||
* This method can also retrieve a complete set of WPSEO meta values for one specific post, see
|
||||
* the method documentation for the parameters.
|
||||
*
|
||||
* {@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.}}
|
||||
*
|
||||
* {@internal All WP native get_meta() results get cached internally, so no need to cache locally.}}
|
||||
* {@internal Use $key when the key is the WPSEO internal name (without prefix), $meta_key when it
|
||||
* includes the prefix.}}
|
||||
*/
|
||||
class WPSEO_Meta {
|
||||
|
||||
/**
|
||||
* Prefix for all WPSEO meta values in the database.
|
||||
*
|
||||
* {@internal If at any point this would change, quite apart from an upgrade routine,
|
||||
* this also will need to be changed in the wpml-config.xml file.}}
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $meta_prefix = '_yoast_wpseo_';
|
||||
|
||||
/**
|
||||
* Prefix for all WPSEO meta value form field names and ids.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $form_prefix = 'yoast_wpseo_';
|
||||
|
||||
/**
|
||||
* Allowed length of the meta description.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $meta_length = 156;
|
||||
|
||||
/**
|
||||
* Reason the meta description is not the default length.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $meta_length_reason = '';
|
||||
|
||||
/**
|
||||
* Meta box field definitions for the meta box form.
|
||||
*
|
||||
* {@internal
|
||||
* - Titles, help texts, description text and option labels are added via a translate_meta_boxes() method
|
||||
* in the relevant child classes (WPSEO_Metabox and WPSEO_Social_admin) as they are only needed there.
|
||||
* - Beware: even though the meta keys are divided into subsets, they still have to be uniquely named!}}
|
||||
*
|
||||
* @var array $meta_fields
|
||||
* Array format:
|
||||
* (required) 'type' => (string) field type. i.e. text / textarea / checkbox /
|
||||
* radio / select / multiselect / upload etc.
|
||||
* (required) 'title' => (string) table row title.
|
||||
* (recommended) 'default_value' => (string|array) default value for the field.
|
||||
* IMPORTANT:
|
||||
* - if the field has options, the default has to be the
|
||||
* key of one of the options.
|
||||
* - if the field is a text field, the default **has** to be
|
||||
* an empty string as otherwise the user can't save
|
||||
* an empty value/delete the meta value.
|
||||
* - if the field is a checkbox, the only valid values
|
||||
* are 'on' or 'off'.
|
||||
* (semi-required) 'options' => (array) options for used with (multi-)select and radio
|
||||
* fields, required if that's the field type.
|
||||
* key = (string) value which will be saved to db.
|
||||
* value = (string) text label for the option.
|
||||
* (optional) 'autocomplete' => (bool) whether autocomplete is on for text fields,
|
||||
* defaults to true.
|
||||
* (optional) 'class' => (string) classname(s) to add to the actual <input> tag.
|
||||
* (optional) 'description' => (string) description to show underneath the field.
|
||||
* (optional) 'expl' => (string) label for a checkbox.
|
||||
* (optional) 'help' => (string) help text to show on mouse over ? image.
|
||||
* (optional) 'rows' => (int) number of rows for a textarea, defaults to 3.
|
||||
* (optional) 'placeholder' => (string) Currently only used by add-on plugins.
|
||||
* (optional) 'serialized' => (bool) whether the value is expected to be serialized,
|
||||
* i.e. an array or object, defaults to false.
|
||||
* Currently only used by add-on plugins.
|
||||
*/
|
||||
public static $meta_fields = [
|
||||
'general' => [
|
||||
'focuskw' => [
|
||||
'type' => 'hidden',
|
||||
'title' => '',
|
||||
],
|
||||
'title' => [
|
||||
'type' => 'hidden',
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '',
|
||||
'description' => '', // Translation added later.
|
||||
'help' => '', // Translation added later.
|
||||
],
|
||||
'metadesc' => [
|
||||
'type' => 'hidden',
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '',
|
||||
'class' => 'metadesc',
|
||||
'rows' => 2,
|
||||
'description' => '', // Translation added later.
|
||||
'help' => '', // Translation added later.
|
||||
],
|
||||
'linkdex' => [
|
||||
'type' => 'hidden',
|
||||
'title' => 'linkdex',
|
||||
'default_value' => '0',
|
||||
'description' => '',
|
||||
],
|
||||
'content_score' => [
|
||||
'type' => 'hidden',
|
||||
'title' => 'content_score',
|
||||
'default_value' => '0',
|
||||
'description' => '',
|
||||
],
|
||||
'is_cornerstone' => [
|
||||
'type' => 'hidden',
|
||||
'title' => 'is_cornerstone',
|
||||
'default_value' => 'false',
|
||||
'description' => '',
|
||||
],
|
||||
],
|
||||
'advanced' => [
|
||||
'meta-robots-noindex' => [
|
||||
'type' => 'select',
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '0', // = post-type default.
|
||||
'options' => [
|
||||
'0' => '', // Post type default - translation added later.
|
||||
'2' => '', // Index - translation added later.
|
||||
'1' => '', // No-index - translation added later.
|
||||
],
|
||||
],
|
||||
'meta-robots-nofollow' => [
|
||||
'type' => 'radio',
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '0', // = follow.
|
||||
'options' => [
|
||||
'0' => '', // Follow - translation added later.
|
||||
'1' => '', // No-follow - translation added later.
|
||||
],
|
||||
],
|
||||
'meta-robots-adv' => [
|
||||
'type' => 'multiselect',
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '',
|
||||
'description' => '', // Translation added later.
|
||||
'options' => [
|
||||
'noimageindex' => '', // Translation added later.
|
||||
'noarchive' => '', // Translation added later.
|
||||
'nosnippet' => '', // Translation added later.
|
||||
],
|
||||
],
|
||||
'bctitle' => [
|
||||
'type' => 'text',
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '',
|
||||
'description' => '', // Translation added later.
|
||||
],
|
||||
'canonical' => [
|
||||
'type' => 'text',
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '',
|
||||
'description' => '', // Translation added later.
|
||||
],
|
||||
'redirect' => [
|
||||
'type' => 'text',
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '',
|
||||
'description' => '', // Translation added later.
|
||||
],
|
||||
],
|
||||
'social' => [],
|
||||
/* Fields we should validate & save, but not show on any form. */
|
||||
'non_form' => [
|
||||
'linkdex' => [
|
||||
'type' => null,
|
||||
'default_value' => '0',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Helper property - reverse index of the definition array.
|
||||
*
|
||||
* Format: [full meta key including prefix] => array
|
||||
* ['subset'] => (string) primary index
|
||||
* ['key'] => (string) internal key
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $fields_index = [];
|
||||
|
||||
/**
|
||||
* Helper property - array containing only the defaults in the format:
|
||||
* [full meta key including prefix] => (string) default value
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $defaults = [];
|
||||
|
||||
/**
|
||||
* Helper property to define the social network meta field definitions - networks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $social_networks = [
|
||||
'opengraph' => 'opengraph',
|
||||
'twitter' => 'twitter',
|
||||
];
|
||||
|
||||
/**
|
||||
* Helper property to define the social network meta field definitions - fields and their type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $social_fields = [
|
||||
'title' => 'text',
|
||||
'description' => 'textarea',
|
||||
'image' => 'upload',
|
||||
'image-id' => 'hidden',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register our actions and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
|
||||
foreach ( self::$social_networks as $option => $network ) {
|
||||
if ( WPSEO_Options::get( $option, false ) === true ) {
|
||||
foreach ( self::$social_fields as $box => $type ) {
|
||||
self::$meta_fields['social'][ $network . '-' . $box ] = [
|
||||
'type' => $type,
|
||||
'title' => '', // Translation added later.
|
||||
'default_value' => '',
|
||||
'description' => '', // Translation added later.
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $option, $network, $box, $type );
|
||||
|
||||
/**
|
||||
* Allow add-on plugins to register their meta fields for management by this class.
|
||||
* Calls to add_filter() must be made before plugins_loaded prio 14.
|
||||
*/
|
||||
$extra_fields = apply_filters( 'add_extra_wpseo_meta_fields', [] );
|
||||
if ( is_array( $extra_fields ) ) {
|
||||
self::$meta_fields = self::array_merge_recursive_distinct( $extra_fields, self::$meta_fields );
|
||||
}
|
||||
unset( $extra_fields );
|
||||
|
||||
foreach ( self::$meta_fields as $subset => $field_group ) {
|
||||
foreach ( $field_group as $key => $field_def ) {
|
||||
|
||||
register_meta(
|
||||
'post',
|
||||
self::$meta_prefix . $key,
|
||||
[ 'sanitize_callback' => [ __CLASS__, 'sanitize_post_meta' ] ]
|
||||
);
|
||||
|
||||
// Set the $fields_index property for efficiency.
|
||||
self::$fields_index[ self::$meta_prefix . $key ] = [
|
||||
'subset' => $subset,
|
||||
'key' => $key,
|
||||
];
|
||||
|
||||
// Set the $defaults property for efficiency.
|
||||
if ( isset( $field_def['default_value'] ) ) {
|
||||
self::$defaults[ self::$meta_prefix . $key ] = $field_def['default_value'];
|
||||
}
|
||||
else {
|
||||
// Meta will always be a string, so let's make the meta meta default also a string.
|
||||
self::$defaults[ self::$meta_prefix . $key ] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $subset, $field_group, $key, $field_def );
|
||||
|
||||
add_filter( 'update_post_metadata', [ __CLASS__, 'remove_meta_if_default' ], 10, 5 );
|
||||
add_filter( 'add_post_metadata', [ __CLASS__, 'dont_save_meta_if_default' ], 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the meta box form field definitions for the given tab and post type.
|
||||
*
|
||||
* @param string $tab Tab for which to retrieve the field definitions.
|
||||
* @param string $post_type Post type of the current post.
|
||||
*
|
||||
* @return array Array containing the meta box field definitions.
|
||||
*/
|
||||
public static function get_meta_field_defs( $tab, $post_type = 'post' ) {
|
||||
if ( ! isset( self::$meta_fields[ $tab ] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$field_defs = self::$meta_fields[ $tab ];
|
||||
|
||||
switch ( $tab ) {
|
||||
case 'non-form':
|
||||
// Prevent non-form fields from being passed to forms.
|
||||
$field_defs = [];
|
||||
break;
|
||||
|
||||
case 'advanced':
|
||||
global $post;
|
||||
|
||||
if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) && WPSEO_Options::get( 'disableadvanced_meta' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$post_type = '';
|
||||
if ( isset( $post->post_type ) ) {
|
||||
$post_type = $post->post_type;
|
||||
}
|
||||
elseif ( ! isset( $post->post_type ) && isset( $_GET['post_type'] ) ) {
|
||||
$post_type = sanitize_text_field( $_GET['post_type'] );
|
||||
}
|
||||
|
||||
if ( $post_type === '' ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/* Adjust the no-index text strings based on the post type. */
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
|
||||
$field_defs['meta-robots-noindex']['title'] = sprintf( $field_defs['meta-robots-noindex']['title'], $post_type_object->labels->singular_name );
|
||||
$field_defs['meta-robots-noindex']['options']['0'] = sprintf( $field_defs['meta-robots-noindex']['options']['0'], ( ( WPSEO_Options::get( 'noindex-' . $post_type, false ) === true ) ? $field_defs['meta-robots-noindex']['options']['1'] : $field_defs['meta-robots-noindex']['options']['2'] ), $post_type_object->label );
|
||||
$field_defs['meta-robots-nofollow']['title'] = sprintf( $field_defs['meta-robots-nofollow']['title'], $post_type_object->labels->singular_name );
|
||||
|
||||
/* Don't show the breadcrumb title field if breadcrumbs aren't enabled. */
|
||||
if ( WPSEO_Options::get( 'breadcrumbs-enable', false ) !== true && ! current_theme_supports( 'yoast-seo-breadcrumbs' ) ) {
|
||||
unset( $field_defs['bctitle'] );
|
||||
}
|
||||
|
||||
global $post;
|
||||
|
||||
if ( empty( $post->ID ) || ( ! empty( $post->ID ) && self::get_value( 'redirect', $post->ID ) === '' ) ) {
|
||||
unset( $field_defs['redirect'] );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the WPSEO metabox form field definitions for a tab.
|
||||
* {tab} can be 'general', 'advanced' or 'social'.
|
||||
*
|
||||
* @param array $field_defs Metabox form field definitions.
|
||||
* @param string $post_type Post type of the post the metabox is for, defaults to 'post'.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
return apply_filters( 'wpseo_metabox_entries_' . $tab, $field_defs, $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the post meta values.
|
||||
*
|
||||
* @param mixed $meta_value The new value.
|
||||
* @param string $meta_key The full meta key (including prefix).
|
||||
*
|
||||
* @return string Validated meta value.
|
||||
*/
|
||||
public static function sanitize_post_meta( $meta_value, $meta_key ) {
|
||||
$field_def = self::$meta_fields[ self::$fields_index[ $meta_key ]['subset'] ][ self::$fields_index[ $meta_key ]['key'] ];
|
||||
$clean = self::$defaults[ $meta_key ];
|
||||
|
||||
switch ( true ) {
|
||||
case ( $meta_key === self::$meta_prefix . 'linkdex' ):
|
||||
$int = WPSEO_Utils::validate_int( $meta_value );
|
||||
if ( $int !== false && $int >= 0 ) {
|
||||
$clean = strval( $int ); // Convert to string to make sure default check works.
|
||||
}
|
||||
break;
|
||||
|
||||
case ( $field_def['type'] === 'checkbox' ):
|
||||
// Only allow value if it's one of the predefined options.
|
||||
if ( in_array( $meta_value, [ 'on', 'off' ], true ) ) {
|
||||
$clean = $meta_value;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case ( $field_def['type'] === 'select' || $field_def['type'] === 'radio' ):
|
||||
// Only allow value if it's one of the predefined options.
|
||||
if ( isset( $field_def['options'][ $meta_value ] ) ) {
|
||||
$clean = $meta_value;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case ( $field_def['type'] === 'multiselect' && $meta_key === self::$meta_prefix . 'meta-robots-adv' ):
|
||||
$clean = self::validate_meta_robots_adv( $meta_value );
|
||||
break;
|
||||
|
||||
|
||||
case ( $field_def['type'] === 'text' && $meta_key === self::$meta_prefix . 'canonical' ):
|
||||
case ( $field_def['type'] === 'text' && $meta_key === self::$meta_prefix . 'redirect' ):
|
||||
// Validate as url(-part).
|
||||
$url = WPSEO_Utils::sanitize_url( $meta_value );
|
||||
if ( $url !== '' ) {
|
||||
$clean = $url;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case ( $field_def['type'] === 'upload' && in_array( $meta_key, [ self::$meta_prefix . 'opengraph-image', self::$meta_prefix . 'twitter-image' ], true ) ):
|
||||
// Validate as url.
|
||||
$url = WPSEO_Utils::sanitize_url( $meta_value, [ 'http', 'https', 'ftp', 'ftps' ] );
|
||||
if ( $url !== '' ) {
|
||||
$clean = $url;
|
||||
}
|
||||
break;
|
||||
|
||||
case ( $field_def['type'] === 'hidden' && $meta_key === self::$meta_prefix . 'is_cornerstone' ):
|
||||
$clean = $meta_value;
|
||||
|
||||
/*
|
||||
* This used to be a checkbox, then became a hidden input.
|
||||
* To make sure the value remains consistent, we cast 'true' to '1'.
|
||||
*/
|
||||
if ( $meta_value === 'true' ) {
|
||||
$clean = '1';
|
||||
}
|
||||
break;
|
||||
|
||||
case ( $field_def['type'] === 'textarea' ):
|
||||
if ( is_string( $meta_value ) ) {
|
||||
// Remove line breaks and tabs.
|
||||
// @todo [JRF => Yoast] Verify that line breaks and the likes aren't allowed/recommended in meta header fields.
|
||||
$meta_value = str_replace( [ "\n", "\r", "\t", ' ' ], ' ', $meta_value );
|
||||
$clean = WPSEO_Utils::sanitize_text_field( trim( $meta_value ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case ( $field_def['type'] === 'multiselect' ):
|
||||
$clean = $meta_value;
|
||||
break;
|
||||
|
||||
|
||||
case ( $field_def['type'] === 'text' ):
|
||||
default:
|
||||
if ( is_string( $meta_value ) ) {
|
||||
$clean = WPSEO_Utils::sanitize_text_field( trim( $meta_value ) );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$clean = apply_filters( 'wpseo_sanitize_post_meta_' . $meta_key, $clean, $meta_value, $field_def, $meta_key );
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a meta-robots-adv meta value.
|
||||
*
|
||||
* @todo [JRF => Yoast] Verify that this logic for the prioritisation is correct.
|
||||
*
|
||||
* @param array|string $meta_value The value to validate.
|
||||
*
|
||||
* @return string Clean value.
|
||||
*/
|
||||
public static function validate_meta_robots_adv( $meta_value ) {
|
||||
$clean = self::$meta_fields['advanced']['meta-robots-adv']['default_value'];
|
||||
$options = self::$meta_fields['advanced']['meta-robots-adv']['options'];
|
||||
|
||||
if ( is_string( $meta_value ) ) {
|
||||
$meta_value = explode( ',', $meta_value );
|
||||
}
|
||||
|
||||
if ( is_array( $meta_value ) && $meta_value !== [] ) {
|
||||
$meta_value = array_map( 'trim', $meta_value );
|
||||
|
||||
// Individual selected entries.
|
||||
$cleaning = [];
|
||||
foreach ( $meta_value as $value ) {
|
||||
if ( isset( $options[ $value ] ) ) {
|
||||
$cleaning[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $cleaning !== [] ) {
|
||||
$clean = implode( ',', $cleaning );
|
||||
}
|
||||
unset( $cleaning, $value );
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent saving of default values and remove potential old value from the database if replaced by a default.
|
||||
*
|
||||
* @param bool $check The current status to allow updating metadata for the given type.
|
||||
* @param int $object_id ID of the current object for which the meta is being updated.
|
||||
* @param string $meta_key The full meta key (including prefix).
|
||||
* @param string $meta_value New meta value.
|
||||
* @param string $prev_value The old meta value.
|
||||
*
|
||||
* @return null|bool True = stop saving, null = continue saving.
|
||||
*/
|
||||
public static function remove_meta_if_default( $check, $object_id, $meta_key, $meta_value, $prev_value = '' ) {
|
||||
/* If it's one of our meta fields, check against default. */
|
||||
if ( isset( self::$fields_index[ $meta_key ] ) && self::meta_value_is_default( $meta_key, $meta_value ) === true ) {
|
||||
if ( $prev_value !== '' ) {
|
||||
delete_post_meta( $object_id, $meta_key, $prev_value );
|
||||
}
|
||||
else {
|
||||
delete_post_meta( $object_id, $meta_key );
|
||||
}
|
||||
|
||||
return true; // Stop saving the value.
|
||||
}
|
||||
|
||||
return $check; // Go on with the normal execution (update) in meta.php.
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent adding of default values to the database.
|
||||
*
|
||||
* @param bool $check The current status to allow adding metadata for the given type.
|
||||
* @param int $object_id ID of the current object for which the meta is being added.
|
||||
* @param string $meta_key The full meta key (including prefix).
|
||||
* @param string $meta_value New meta value.
|
||||
*
|
||||
* @return null|bool True = stop saving, null = continue saving.
|
||||
*/
|
||||
public static function dont_save_meta_if_default( $check, $object_id, $meta_key, $meta_value ) {
|
||||
/* If it's one of our meta fields, check against default. */
|
||||
if ( isset( self::$fields_index[ $meta_key ] ) && self::meta_value_is_default( $meta_key, $meta_value ) === true ) {
|
||||
return true; // Stop saving the value.
|
||||
}
|
||||
|
||||
return $check; // Go on with the normal execution (add) in meta.php.
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given meta value the same as the default value ?
|
||||
*
|
||||
* @param string $meta_key The full meta key (including prefix).
|
||||
* @param mixed $meta_value The value to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function meta_value_is_default( $meta_key, $meta_value ) {
|
||||
return ( isset( self::$defaults[ $meta_key ] ) && $meta_value === self::$defaults[ $meta_key ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.}}
|
||||
*
|
||||
* @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 static function get_value( $key, $postid = 0 ) {
|
||||
global $post;
|
||||
|
||||
$postid = absint( $postid );
|
||||
if ( $postid === 0 ) {
|
||||
if ( ( isset( $post ) && is_object( $post ) ) && ( isset( $post->post_status ) && $post->post_status !== 'auto-draft' ) ) {
|
||||
$postid = $post->ID;
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
$custom = get_post_custom( $postid ); // Array of strings or empty array.
|
||||
|
||||
if ( isset( $custom[ self::$meta_prefix . $key ][0] ) ) {
|
||||
$unserialized = maybe_unserialize( $custom[ self::$meta_prefix . $key ][0] );
|
||||
if ( $custom[ self::$meta_prefix . $key ][0] === $unserialized ) {
|
||||
return $custom[ self::$meta_prefix . $key ][0];
|
||||
}
|
||||
|
||||
if ( isset( self::$fields_index[ self::$meta_prefix . $key ] ) ) {
|
||||
$field_def = self::$meta_fields[ self::$fields_index[ self::$meta_prefix . $key ]['subset'] ][ self::$fields_index[ self::$meta_prefix . $key ]['key'] ];
|
||||
if ( isset( $field_def['serialized'] ) && $field_def['serialized'] === true ) {
|
||||
// Ok, serialize value expected/allowed.
|
||||
return $unserialized;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Meta was either not found or found, but object/array while not allowed to be.
|
||||
if ( isset( self::$defaults[ self::$meta_prefix . $key ] ) ) {
|
||||
return self::$defaults[ self::$meta_prefix . $key ];
|
||||
}
|
||||
|
||||
/*
|
||||
* Shouldn't ever happen, means not one of our keys as there will always be a default available
|
||||
* for all our keys.
|
||||
*/
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a meta value for a post.
|
||||
*
|
||||
* @param string $key The internal key of the meta value to change (without prefix).
|
||||
* @param mixed $meta_value The value to set the meta to.
|
||||
* @param int $post_id The ID of the post to change the meta for.
|
||||
*
|
||||
* @return bool Whether the value was changed.
|
||||
*/
|
||||
public static function set_value( $key, $meta_value, $post_id ) {
|
||||
/*
|
||||
* Slash the data, because `update_metadata` will unslash it and we have already unslashed it.
|
||||
* Related issue: https://github.com/Yoast/YoastSEO.js/issues/2158
|
||||
*/
|
||||
$meta_value = wp_slash( $meta_value );
|
||||
|
||||
return update_post_meta( $post_id, self::$meta_prefix . $key, $meta_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 change the meta for.
|
||||
*
|
||||
* @return bool Whether the value was changed.
|
||||
*/
|
||||
public static function delete( $key, $post_id ) {
|
||||
return delete_post_meta( $post_id, self::$meta_prefix . $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for imports, this functions imports the value of $old_metakey into $new_metakey for those post
|
||||
* where no WPSEO meta data has been set.
|
||||
* Optionally deletes the $old_metakey values.
|
||||
*
|
||||
* @param string $old_metakey The old key of the meta value.
|
||||
* @param string $new_metakey The new key, usually the WPSEO meta key (including prefix).
|
||||
* @param bool $delete_old Whether to delete the old meta key/value-sets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function replace_meta( $old_metakey, $new_metakey, $delete_old = false ) {
|
||||
global $wpdb;
|
||||
|
||||
/*
|
||||
* Get only those rows where no wpseo meta values exist for the same post
|
||||
* (with the exception of linkdex as that will be set independently of whether the post has been edited).
|
||||
*
|
||||
* {@internal Query is pretty well optimized this way.}}
|
||||
*/
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT `a`.*
|
||||
FROM {$wpdb->postmeta} AS a
|
||||
WHERE `a`.`meta_key` = %s
|
||||
AND NOT EXISTS (
|
||||
SELECT DISTINCT `post_id` , count( `meta_id` ) AS count
|
||||
FROM {$wpdb->postmeta} AS b
|
||||
WHERE `a`.`post_id` = `b`.`post_id`
|
||||
AND `meta_key` LIKE %s
|
||||
AND `meta_key` <> %s
|
||||
GROUP BY `post_id`
|
||||
)
|
||||
;",
|
||||
$old_metakey,
|
||||
$wpdb->esc_like( self::$meta_prefix . '%' ),
|
||||
self::$meta_prefix . 'linkdex'
|
||||
);
|
||||
$oldies = $wpdb->get_results( $query );
|
||||
|
||||
if ( is_array( $oldies ) && $oldies !== [] ) {
|
||||
foreach ( $oldies as $old ) {
|
||||
update_post_meta( $old->post_id, $new_metakey, $old->meta_value );
|
||||
}
|
||||
}
|
||||
|
||||
// Delete old keys.
|
||||
if ( $delete_old === true ) {
|
||||
delete_post_meta_by_key( $old_metakey );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* General clean-up of the saved meta values.
|
||||
* - Remove potentially lingering old meta keys;
|
||||
* - Remove all default and invalid values.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clean_up() {
|
||||
global $wpdb;
|
||||
|
||||
/*
|
||||
* Clean up '_yoast_wpseo_meta-robots'.
|
||||
*
|
||||
* Retrieve all '_yoast_wpseo_meta-robots' meta values and convert if no new values found.
|
||||
*
|
||||
* {@internal Query is pretty well optimized this way.}}
|
||||
*
|
||||
* @todo [JRF => Yoast] Find out all possible values which the old '_yoast_wpseo_meta-robots' could contain
|
||||
* to convert the data correctly.
|
||||
*/
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT `a`.*
|
||||
FROM {$wpdb->postmeta} AS a
|
||||
WHERE `a`.`meta_key` = %s
|
||||
AND NOT EXISTS (
|
||||
SELECT DISTINCT `post_id` , count( `meta_id` ) AS count
|
||||
FROM {$wpdb->postmeta} AS b
|
||||
WHERE `a`.`post_id` = `b`.`post_id`
|
||||
AND ( `meta_key` = %s
|
||||
OR `meta_key` = %s )
|
||||
GROUP BY `post_id`
|
||||
)
|
||||
;",
|
||||
self::$meta_prefix . 'meta-robots',
|
||||
self::$meta_prefix . 'meta-robots-noindex',
|
||||
self::$meta_prefix . 'meta-robots-nofollow'
|
||||
);
|
||||
$oldies = $wpdb->get_results( $query );
|
||||
|
||||
if ( is_array( $oldies ) && $oldies !== [] ) {
|
||||
foreach ( $oldies as $old ) {
|
||||
$old_values = explode( ',', $old->meta_value );
|
||||
foreach ( $old_values as $value ) {
|
||||
if ( $value === 'noindex' ) {
|
||||
update_post_meta( $old->post_id, self::$meta_prefix . 'meta-robots-noindex', 1 );
|
||||
}
|
||||
elseif ( $value === 'nofollow' ) {
|
||||
update_post_meta( $old->post_id, self::$meta_prefix . 'meta-robots-nofollow', 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $query, $oldies, $old, $old_values, $value );
|
||||
|
||||
// Delete old keys.
|
||||
delete_post_meta_by_key( self::$meta_prefix . 'meta-robots' );
|
||||
|
||||
/*
|
||||
* Remove all default values and (most) invalid option values.
|
||||
* Invalid option values for the multiselect (meta-robots-adv) field will be dealt with seperately.
|
||||
*
|
||||
* {@internal Some of the defaults have changed in v1.5, but as the defaults will
|
||||
* be removed and new defaults will now automatically be passed when no
|
||||
* data found, this update is automatic (as long as we remove the old
|
||||
* values which we do in the below routine).}}
|
||||
*
|
||||
* {@internal Unfortunately we can't use the normal delete_meta() with key/value combination
|
||||
* as '' (empty string) values will be ignored and would result in all metas
|
||||
* with that key being deleted, not just the empty fields.
|
||||
* Still, the below implementation is largely based on the delete_meta() function.}}
|
||||
*/
|
||||
$query = [];
|
||||
|
||||
foreach ( self::$meta_fields as $subset => $field_group ) {
|
||||
foreach ( $field_group as $key => $field_def ) {
|
||||
if ( ! isset( $field_def['default_value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $field_def['options'] ) && is_array( $field_def['options'] ) && $field_def['options'] !== [] ) {
|
||||
$valid = $field_def['options'];
|
||||
// Remove the default value from the valid options.
|
||||
unset( $valid[ $field_def['default_value'] ] );
|
||||
$valid = array_keys( $valid );
|
||||
|
||||
$query[] = $wpdb->prepare(
|
||||
"( meta_key = %s AND meta_value NOT IN ( '" . implode( "','", esc_sql( $valid ) ) . "' ) )",
|
||||
self::$meta_prefix . $key
|
||||
);
|
||||
unset( $valid );
|
||||
}
|
||||
elseif ( is_string( $field_def['default_value'] ) && $field_def['default_value'] !== '' ) {
|
||||
$query[] = $wpdb->prepare(
|
||||
'( meta_key = %s AND meta_value = %s )',
|
||||
self::$meta_prefix . $key,
|
||||
$field_def['default_value']
|
||||
);
|
||||
}
|
||||
else {
|
||||
$query[] = $wpdb->prepare(
|
||||
"( meta_key = %s AND meta_value = '' )",
|
||||
self::$meta_prefix . $key
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $subset, $field_group, $key, $field_def );
|
||||
|
||||
$query = "SELECT meta_id FROM {$wpdb->postmeta} WHERE " . implode( ' OR ', $query ) . ';';
|
||||
$meta_ids = $wpdb->get_col( $query );
|
||||
|
||||
if ( is_array( $meta_ids ) && $meta_ids !== [] ) {
|
||||
// WP native action.
|
||||
do_action( 'delete_post_meta', $meta_ids, null, null, null );
|
||||
|
||||
$query = "DELETE FROM {$wpdb->postmeta} WHERE meta_id IN( " . implode( ',', $meta_ids ) . ' )';
|
||||
$count = $wpdb->query( $query );
|
||||
|
||||
if ( $count ) {
|
||||
foreach ( $meta_ids as $object_id ) {
|
||||
wp_cache_delete( $object_id, 'post_meta' );
|
||||
}
|
||||
|
||||
// WP native action.
|
||||
do_action( 'deleted_post_meta', $meta_ids, null, null, null );
|
||||
}
|
||||
}
|
||||
unset( $query, $meta_ids, $count, $object_id );
|
||||
|
||||
/*
|
||||
* Deal with the multiselect (meta-robots-adv) field.
|
||||
*
|
||||
* Removes invalid option combinations, such as 'none,noarchive'.
|
||||
*
|
||||
* Default values have already been removed, so we should have a small result set and
|
||||
* (hopefully) even smaller set of invalid results.
|
||||
*/
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT meta_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s",
|
||||
self::$meta_prefix . 'meta-robots-adv'
|
||||
);
|
||||
$oldies = $wpdb->get_results( $query );
|
||||
|
||||
if ( is_array( $oldies ) && $oldies !== [] ) {
|
||||
foreach ( $oldies as $old ) {
|
||||
$clean = self::validate_meta_robots_adv( $old->meta_value );
|
||||
|
||||
if ( $clean !== $old->meta_value ) {
|
||||
if ( $clean !== self::$meta_fields['advanced']['meta-robots-adv']['default_value'] ) {
|
||||
update_metadata_by_mid( 'post', $old->meta_id, $clean );
|
||||
}
|
||||
else {
|
||||
delete_metadata_by_mid( 'post', $old->meta_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $query, $oldies, $old, $clean );
|
||||
|
||||
do_action( 'wpseo_meta_clean_up' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively merge a variable number of arrays, using the left array as base,
|
||||
* giving priority to the right array.
|
||||
*
|
||||
* Difference with native array_merge_recursive():
|
||||
* array_merge_recursive converts values with duplicate keys to arrays rather than
|
||||
* overwriting the value in the first array with the duplicate value in the second array.
|
||||
*
|
||||
* array_merge_recursive_distinct does not change the data types of the values in the arrays.
|
||||
* Matching keys' values in the second array overwrite those in the first array, as is the
|
||||
* case with array_merge.
|
||||
*
|
||||
* Freely based on information found on http://www.php.net/manual/en/function.array-merge-recursive.php
|
||||
*
|
||||
* {@internal Should be moved to a general utility class.}}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function array_merge_recursive_distinct() {
|
||||
|
||||
$arrays = func_get_args();
|
||||
if ( count( $arrays ) < 2 ) {
|
||||
if ( $arrays === [] ) {
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
return $arrays[0];
|
||||
}
|
||||
}
|
||||
|
||||
$merged = array_shift( $arrays );
|
||||
|
||||
foreach ( $arrays as $array ) {
|
||||
foreach ( $array as $key => $value ) {
|
||||
if ( is_array( $value ) && ( isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) ) {
|
||||
$merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value );
|
||||
}
|
||||
else {
|
||||
$merged[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
unset( $key, $value );
|
||||
}
|
||||
|
||||
return $merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the total of all the keywords being used for posts except the given one.
|
||||
*
|
||||
* @param string $keyword The keyword to be counted.
|
||||
* @param integer $post_id The is of the post to which the keyword belongs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function keyword_usage( $keyword, $post_id ) {
|
||||
|
||||
if ( empty( $keyword ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = [
|
||||
'meta_query' => [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => '_yoast_wpseo_focuskw',
|
||||
'value' => $keyword,
|
||||
],
|
||||
],
|
||||
'post__not_in' => [ $post_id ],
|
||||
'fields' => 'ids',
|
||||
'post_type' => 'any',
|
||||
|
||||
/*
|
||||
* We only need to return zero, one or two results:
|
||||
* - Zero: keyword hasn't been used before
|
||||
* - One: Keyword has been used once before
|
||||
* - Two or more: Keyword has been used twice before
|
||||
*/
|
||||
'posts_per_page' => 2,
|
||||
];
|
||||
|
||||
// If Yoast SEO Premium is active, get the additional keywords as well.
|
||||
if ( WPSEO_Utils::is_yoast_seo_premium() ) {
|
||||
$query['meta_query'][] = [
|
||||
'key' => '_yoast_wpseo_focuskeywords',
|
||||
'value' => sprintf( '"keyword":"%s"', $keyword ),
|
||||
'compare' => 'LIKE',
|
||||
];
|
||||
}
|
||||
|
||||
$get_posts = new WP_Query( $query );
|
||||
|
||||
return $get_posts->posts;
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Get a value from $_POST for a given key.
|
||||
*
|
||||
* Returns the $_POST value if exists, returns an empty string if key does not exist.
|
||||
*
|
||||
* @deprecated 9.6
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $key Key of the value to get from $_POST.
|
||||
*
|
||||
* @return string Returns $_POST value, which will be a string the majority of the time.
|
||||
* Will return empty string if key does not exists in $_POST.
|
||||
*/
|
||||
public static function get_post_value( $key ) {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 9.6' );
|
||||
|
||||
// @codingStandardsIgnoreLine
|
||||
return ( array_key_exists( $key, $_POST ) ) ? $_POST[ $key ] : '';
|
||||
}
|
||||
} /* End of class */
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a post's primary term.
|
||||
*/
|
||||
class WPSEO_Primary_Term {
|
||||
|
||||
/**
|
||||
* Taxonomy name for the term.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $taxonomy_name;
|
||||
|
||||
/**
|
||||
* Post ID for the term.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $post_ID;
|
||||
|
||||
/**
|
||||
* The taxonomy this term is part of.
|
||||
*
|
||||
* @param string $taxonomy_name Taxonomy name for the term.
|
||||
* @param int $post_id Post ID for the term.
|
||||
*/
|
||||
public function __construct( $taxonomy_name, $post_id ) {
|
||||
$this->taxonomy_name = $taxonomy_name;
|
||||
$this->post_ID = $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary term ID.
|
||||
*
|
||||
* @return int|bool
|
||||
*/
|
||||
public function get_primary_term() {
|
||||
$primary_term = get_post_meta( $this->post_ID, WPSEO_Meta::$meta_prefix . 'primary_' . $this->taxonomy_name, true );
|
||||
|
||||
$terms = $this->get_terms();
|
||||
|
||||
if ( ! in_array( $primary_term, wp_list_pluck( $terms, 'term_id' ) ) ) {
|
||||
$primary_term = false;
|
||||
}
|
||||
|
||||
$primary_term = (int) $primary_term;
|
||||
return ( $primary_term ) ? ( $primary_term ) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new primary term ID.
|
||||
*
|
||||
* @param int $new_primary_term New primary term ID.
|
||||
*/
|
||||
public function set_primary_term( $new_primary_term ) {
|
||||
update_post_meta( $this->post_ID, WPSEO_Meta::$meta_prefix . 'primary_' . $this->taxonomy_name, $new_primary_term );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the terms for the current post ID.
|
||||
* When $terms is not an array, set $terms to an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_terms() {
|
||||
$terms = get_the_terms( $this->post_ID, $this->taxonomy_name );
|
||||
|
||||
if ( ! is_array( $terms ) ) {
|
||||
$terms = [];
|
||||
}
|
||||
|
||||
return $terms;
|
||||
}
|
||||
}
|
||||
289
wp-content/plugins/wordpress-seo/inc/class-wpseo-rank.php
Normal file
289
wp-content/plugins/wordpress-seo/inc/class-wpseo-rank.php
Normal file
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holder for SEO Rank information.
|
||||
*/
|
||||
class WPSEO_Rank {
|
||||
|
||||
/**
|
||||
* Constant used for determining a bad SEO rating.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BAD = 'bad';
|
||||
|
||||
/**
|
||||
* Constant used for determining an OK SEO rating.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OK = 'ok';
|
||||
|
||||
/**
|
||||
* Constant used for determining a good SEO rating.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const GOOD = 'good';
|
||||
|
||||
/**
|
||||
* Constant used for determining that no focus keyphrase is set.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NO_FOCUS = 'na';
|
||||
|
||||
/**
|
||||
* Constant used for determining that this content is not indexed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NO_INDEX = 'noindex';
|
||||
|
||||
/**
|
||||
* All possible ranks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $ranks = [
|
||||
self::BAD,
|
||||
self::OK,
|
||||
self::GOOD,
|
||||
self::NO_FOCUS,
|
||||
self::NO_INDEX,
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the translation from seo score slug to actual score range.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $ranges = [
|
||||
self::NO_FOCUS => [
|
||||
'start' => 0,
|
||||
'end' => 0,
|
||||
],
|
||||
self::BAD => [
|
||||
'start' => 1,
|
||||
'end' => 40,
|
||||
],
|
||||
self::OK => [
|
||||
'start' => 41,
|
||||
'end' => 70,
|
||||
],
|
||||
self::GOOD => [
|
||||
'start' => 71,
|
||||
'end' => 100,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The current rank.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rank;
|
||||
|
||||
/**
|
||||
* WPSEO_Rank constructor.
|
||||
*
|
||||
* @param int $rank The actual rank.
|
||||
*/
|
||||
public function __construct( $rank ) {
|
||||
if ( ! in_array( $rank, self::$ranks, true ) ) {
|
||||
$rank = self::BAD;
|
||||
}
|
||||
|
||||
$this->rank = $rank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the saved rank for this rank.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_rank() {
|
||||
return $this->rank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSS class for this rank.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_css_class() {
|
||||
$labels = [
|
||||
self::NO_FOCUS => 'na',
|
||||
self::NO_INDEX => 'noindex',
|
||||
self::BAD => 'bad',
|
||||
self::OK => 'ok',
|
||||
self::GOOD => 'good',
|
||||
];
|
||||
|
||||
return $labels[ $this->rank ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a label for this rank.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_label() {
|
||||
$labels = [
|
||||
self::NO_FOCUS => __( 'Not available', 'wordpress-seo' ),
|
||||
self::NO_INDEX => __( 'No index', 'wordpress-seo' ),
|
||||
self::BAD => __( 'Needs improvement', 'wordpress-seo' ),
|
||||
self::OK => __( 'OK', 'wordpress-seo' ),
|
||||
self::GOOD => __( 'Good', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
return $labels[ $this->rank ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a label for use in a drop down.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_drop_down_label() {
|
||||
$labels = [
|
||||
self::NO_FOCUS => sprintf(
|
||||
/* translators: %s expands to the SEO score */
|
||||
__( 'SEO: %s', 'wordpress-seo' ),
|
||||
__( 'No Focus Keyphrase', 'wordpress-seo' )
|
||||
),
|
||||
self::BAD => sprintf(
|
||||
/* translators: %s expands to the SEO score */
|
||||
__( 'SEO: %s', 'wordpress-seo' ),
|
||||
__( 'Needs improvement', 'wordpress-seo' )
|
||||
),
|
||||
self::OK => sprintf(
|
||||
/* translators: %s expands to the SEO score */
|
||||
__( 'SEO: %s', 'wordpress-seo' ),
|
||||
__( 'OK', 'wordpress-seo' )
|
||||
),
|
||||
self::GOOD => sprintf(
|
||||
/* translators: %s expands to the SEO score */
|
||||
__( 'SEO: %s', 'wordpress-seo' ),
|
||||
__( 'Good', 'wordpress-seo' )
|
||||
),
|
||||
self::NO_INDEX => sprintf(
|
||||
/* translators: %s expands to the SEO score */
|
||||
__( 'SEO: %s', 'wordpress-seo' ),
|
||||
__( 'Post Noindexed', 'wordpress-seo' )
|
||||
),
|
||||
];
|
||||
|
||||
return $labels[ $this->rank ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the drop down labels for the readability score.
|
||||
*
|
||||
* @return string The readability rank label.
|
||||
*/
|
||||
public function get_drop_down_readability_labels() {
|
||||
$labels = [
|
||||
self::BAD => sprintf(
|
||||
/* translators: %s expands to the readability score */
|
||||
__( 'Readability: %s', 'wordpress-seo' ),
|
||||
__( 'Needs improvement', 'wordpress-seo' )
|
||||
),
|
||||
self::OK => sprintf(
|
||||
/* translators: %s expands to the readability score */
|
||||
__( 'Readability: %s', 'wordpress-seo' ),
|
||||
__( 'OK', 'wordpress-seo' )
|
||||
),
|
||||
self::GOOD => sprintf(
|
||||
/* translators: %s expands to the readability score */
|
||||
__( 'Readability: %s', 'wordpress-seo' ),
|
||||
__( 'Good', 'wordpress-seo' )
|
||||
),
|
||||
];
|
||||
|
||||
return $labels[ $this->rank ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the starting score for this rank.
|
||||
*
|
||||
* @return int The start score.
|
||||
*/
|
||||
public function get_starting_score() {
|
||||
// No index does not have a starting score.
|
||||
if ( self::NO_INDEX === $this->rank ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return self::$ranges[ $this->rank ]['start'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ending score for this rank.
|
||||
*
|
||||
* @return int The end score.
|
||||
*/
|
||||
public function get_end_score() {
|
||||
// No index does not have an end score.
|
||||
if ( self::NO_INDEX === $this->rank ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return self::$ranges[ $this->rank ]['end'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a rank for a specific numeric score.
|
||||
*
|
||||
* @param int $score The score to determine a rank for.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function from_numeric_score( $score ) {
|
||||
// Set up the default value.
|
||||
$rank = new self( self::BAD );
|
||||
|
||||
foreach ( self::$ranges as $rank_index => $range ) {
|
||||
if ( $range['start'] <= $score && $score <= $range['end'] ) {
|
||||
$rank = new self( $rank_index );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $rank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all possible SEO Ranks.
|
||||
*
|
||||
* @return WPSEO_Rank[]
|
||||
*/
|
||||
public static function get_all_ranks() {
|
||||
return array_map( [ 'WPSEO_Rank', 'create_rank' ], self::$ranks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all possible Readability Ranks.
|
||||
*
|
||||
* @return WPSEO_Rank[]
|
||||
*/
|
||||
public static function get_all_readability_ranks() {
|
||||
return array_map( [ 'WPSEO_Rank', 'create_rank' ], [ self::BAD, self::OK, self::GOOD ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a numeric rank into a WPSEO_Rank object, for use in functional array_* functions.
|
||||
*
|
||||
* @param string $rank SEO Rank.
|
||||
*
|
||||
* @return WPSEO_Rank
|
||||
*/
|
||||
private static function create_rank( $rank ) {
|
||||
return new self( $rank );
|
||||
}
|
||||
}
|
||||
1398
wp-content/plugins/wordpress-seo/inc/class-wpseo-replace-vars.php
Normal file
1398
wp-content/plugins/wordpress-seo/inc/class-wpseo-replace-vars.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
* @since 7.7
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Replacement_Variable.
|
||||
*
|
||||
* This class stores the data of a single snippet variable.
|
||||
*/
|
||||
class WPSEO_Replacement_Variable {
|
||||
|
||||
/**
|
||||
* The variable to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $variable;
|
||||
|
||||
/**
|
||||
* The label of the replacement variable.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The description of the replacement variable.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* WPSEO_Replacement_Variable constructor.
|
||||
*
|
||||
* @param string $variable The variable that is replaced.
|
||||
* @param string $label The label of the replacement variable.
|
||||
* @param string $description The description of the replacement variable.
|
||||
*
|
||||
* @return \WPSEO_Replacement_Variable
|
||||
*/
|
||||
public function __construct( $variable, $label, $description ) {
|
||||
$this->variable = $variable;
|
||||
$this->label = $label;
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the variable to use.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_variable() {
|
||||
return $this->variable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label of the replacement variable.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_label() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the replacement variable.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return $this->description;
|
||||
}
|
||||
}
|
||||
136
wp-content/plugins/wordpress-seo/inc/class-wpseo-shortlinker.php
Normal file
136
wp-content/plugins/wordpress-seo/inc/class-wpseo-shortlinker.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helps with creating shortlinks in the plugin.
|
||||
*/
|
||||
class WPSEO_Shortlinker {
|
||||
|
||||
/**
|
||||
* Collects the additional data necessary for the shortlink.
|
||||
*
|
||||
* @return array The shortlink data.
|
||||
*/
|
||||
protected function collect_additional_shortlink_data() {
|
||||
return [
|
||||
'php_version' => $this->get_php_version(),
|
||||
'platform' => 'wordpress',
|
||||
'platform_version' => $GLOBALS['wp_version'],
|
||||
'software' => $this->get_software(),
|
||||
'software_version' => WPSEO_VERSION,
|
||||
'days_active' => $this->get_days_active(),
|
||||
'user_language' => $this->get_user_language(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_shortlink( $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 static function get( $url ) {
|
||||
$shortlinker = new self();
|
||||
|
||||
return $shortlinker->build_shortlink( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes a version of the URL with a utm_content with the current version.
|
||||
*
|
||||
* @param string $url The URL to build upon.
|
||||
*/
|
||||
public static function show( $url ) {
|
||||
echo esc_url( self::get( $url ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the shortlink's query params.
|
||||
*
|
||||
* @return array The shortlink's query params.
|
||||
*/
|
||||
public static function get_query_params() {
|
||||
$shortlinker = new self();
|
||||
|
||||
return $shortlinker->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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get our software and whether it's active or not.
|
||||
*
|
||||
* @return string The software name + activation state.
|
||||
*/
|
||||
private function get_software() {
|
||||
if ( WPSEO_Utils::is_yoast_seo_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.
|
||||
*/
|
||||
private function get_days_active() {
|
||||
$date_activated = WPSEO_Options::get( 'first_activated_on' );
|
||||
$datediff = ( time() - $date_activated );
|
||||
$days = (int) round( $datediff / DAY_IN_SECONDS );
|
||||
switch ( $days ) {
|
||||
case 0:
|
||||
case 1:
|
||||
$cohort = '0-1';
|
||||
break;
|
||||
case ( $days < 5 ):
|
||||
$cohort = '2-5';
|
||||
break;
|
||||
case ( $days < 30 ):
|
||||
$cohort = '6-30';
|
||||
break;
|
||||
default:
|
||||
$cohort = '30plus';
|
||||
}
|
||||
return $cohort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's language.
|
||||
*
|
||||
* @return string The user's language.
|
||||
*/
|
||||
private function get_user_language() {
|
||||
if ( function_exists( 'get_user_locale' ) ) {
|
||||
return get_user_locale();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class that generates interesting statistics about things.
|
||||
*/
|
||||
class WPSEO_Statistics {
|
||||
|
||||
/**
|
||||
* Returns the post count for a certain SEO rank.
|
||||
*
|
||||
* @todo Merge/DRY this with the logic virtually the same in WPSEO_Metabox::column_sort_orderby().
|
||||
*
|
||||
* @param WPSEO_Rank $rank The SEO rank to get the post count for.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_post_count( $rank ) {
|
||||
if ( WPSEO_Rank::NO_FOCUS === $rank->get_rank() ) {
|
||||
$posts = [
|
||||
'meta_query' => [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'focuskw',
|
||||
'value' => 'needs-a-value-anyway',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif ( WPSEO_Rank::NO_INDEX === $rank->get_rank() ) {
|
||||
$posts = [
|
||||
'meta_key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
|
||||
'meta_value' => '1',
|
||||
'compare' => '=',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$posts = [
|
||||
'meta_key' => WPSEO_Meta::$meta_prefix . 'linkdex',
|
||||
'meta_value' => [ $rank->get_starting_score(), $rank->get_end_score() ],
|
||||
'meta_compare' => 'BETWEEN',
|
||||
'meta_type' => 'NUMERIC',
|
||||
];
|
||||
}
|
||||
|
||||
$posts['fields'] = 'ids';
|
||||
$posts['post_status'] = 'publish';
|
||||
|
||||
if ( current_user_can( 'edit_others_posts' ) === false ) {
|
||||
$posts['author'] = get_current_user_id();
|
||||
}
|
||||
|
||||
$posts = new WP_Query( $posts );
|
||||
|
||||
return (int) $posts->found_posts;
|
||||
}
|
||||
}
|
||||
1418
wp-content/plugins/wordpress-seo/inc/class-wpseo-utils.php
Normal file
1418
wp-content/plugins/wordpress-seo/inc/class-wpseo-utils.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Validator.
|
||||
*/
|
||||
class WPSEO_Validator {
|
||||
|
||||
/**
|
||||
* Validates whether the passed variable is a boolean.
|
||||
*
|
||||
* @param mixed $variable The variable to validate.
|
||||
*
|
||||
* @return bool Whether or not the passed variable is a valid boolean.
|
||||
*/
|
||||
public static function is_boolean( $variable ) {
|
||||
if ( is_bool( $variable ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return filter_var( $variable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the passed variable is a string.
|
||||
*
|
||||
* @param mixed $variable The variable to validate.
|
||||
*
|
||||
* @return bool Whether or not the passed variable is a string.
|
||||
*/
|
||||
public static function is_string( $variable ) {
|
||||
return is_string( $variable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the passed variable is a non-empty string.
|
||||
*
|
||||
* @param mixed $variable The variable to validate.
|
||||
*
|
||||
* @return bool Whether or not the passed value is a non-empty string.
|
||||
*/
|
||||
public static function is_non_empty_string( $variable ) {
|
||||
return self::is_string( $variable ) && $variable !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the passed variable is an integer.
|
||||
*
|
||||
* @param mixed $variable The variable to validate.
|
||||
*
|
||||
* @return bool Whether or not the passed variable is an integer.
|
||||
*/
|
||||
public static function is_integer( $variable ) {
|
||||
return filter_var( $variable, FILTER_VALIDATE_INT ) || filter_var( $variable, FILTER_VALIDATE_INT ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a particular key exists within the passed dataset.
|
||||
*
|
||||
* @param array $data The dataset to search through.
|
||||
* @param string $key The key to search for.
|
||||
*
|
||||
* @return bool Whether or not the key exists.
|
||||
*/
|
||||
public static function key_exists( array $data, $key ) {
|
||||
return array_key_exists( $key, $data );
|
||||
}
|
||||
}
|
||||
81
wp-content/plugins/wordpress-seo/inc/date-helper.php
Normal file
81
wp-content/plugins/wordpress-seo/inc/date-helper.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Date helper class.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Date_Helper
|
||||
*
|
||||
* Note: Move this class with namespace to the src/helpers directory and add a class_alias for BC.
|
||||
*/
|
||||
class WPSEO_Date_Helper {
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
$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 ) {
|
||||
$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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 datatime is valid.
|
||||
*/
|
||||
public function is_valid_datetime( $datetime ) {
|
||||
if ( substr( $datetime, 0, 1 ) === '-' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return new DateTime( $datetime ) !== false;
|
||||
} catch ( Exception $exception ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Endpoint
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an implementation of the WPSEO_Endpoint interface to register one or multiple endpoints.
|
||||
*/
|
||||
class WPSEO_Endpoint_MyYoast_Connect implements WPSEO_Endpoint {
|
||||
|
||||
/**
|
||||
* The namespace to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REST_NAMESPACE = 'yoast/v1/myyoast';
|
||||
|
||||
/**
|
||||
* Registers the REST routes that are available on the endpoint.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
register_rest_route(
|
||||
self::REST_NAMESPACE,
|
||||
'connect',
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'handle_request' ],
|
||||
'permission_callback' => [ $this, 'can_retrieve_data' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not data can be retrieved for the registered endpoints.
|
||||
*
|
||||
* @param WP_REST_Request $request The current request.
|
||||
*
|
||||
* @return WP_REST_Response The response.
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ) {
|
||||
if ( $request->get_param( 'url' ) !== $this->get_home_url() ) {
|
||||
return new WP_REST_Response(
|
||||
'Bad request: URL mismatch.',
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
if ( $request->get_param( 'clientId' ) !== $this->get_client_id() ) {
|
||||
return new WP_REST_Response(
|
||||
'Bad request: ClientID mismatch.',
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
$client_secret = $request->get_param( 'clientSecret' );
|
||||
if ( empty( $client_secret ) ) {
|
||||
return new WP_REST_Response(
|
||||
'Bad request: ClientSecret missing.',
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
$this->save_secret( $client_secret );
|
||||
|
||||
return new WP_REST_Response( 'Connection successful established.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not data can be retrieved for the registered endpoints.
|
||||
*
|
||||
* @return bool Whether or not data can be retrieved.
|
||||
*/
|
||||
public function can_retrieve_data() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the client secret.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $client_secret The secret to save.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function save_secret( $client_secret ) {
|
||||
$this->get_client()->save_configuration(
|
||||
[
|
||||
'secret' => $client_secret,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current client ID.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The client ID.
|
||||
*/
|
||||
protected function get_client_id() {
|
||||
$config = $this->get_client()->get_configuration();
|
||||
|
||||
return $config['clientId'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an instance of the client.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return WPSEO_MyYoast_Client Instance of client.
|
||||
*/
|
||||
protected function get_client() {
|
||||
static $client;
|
||||
|
||||
if ( ! $client ) {
|
||||
$client = new WPSEO_MyYoast_Client();
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the method for retrieving the home URL.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string Home URL.
|
||||
*/
|
||||
protected function get_home_url() {
|
||||
return WPSEO_Utils::get_home_url();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Invalid_Argument_Exception.
|
||||
*/
|
||||
class WPSEO_Invalid_Argument_Exception extends InvalidArgumentException {
|
||||
|
||||
/**
|
||||
* Creates an invalid empty parameter exception.
|
||||
*
|
||||
* @param string $name The name of the parameter.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function empty_parameter( $name ) {
|
||||
return new self(
|
||||
sprintf(
|
||||
/* translators: %1$s expands to the parameter name. */
|
||||
__( 'The parameter `%1$s` cannot be empty.', 'wordpress-seo' ),
|
||||
$name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid parameter exception.
|
||||
*
|
||||
* @param mixed $parameter The parameter value of the field.
|
||||
* @param string $name The name of the field.
|
||||
* @param string $expected The expected type.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function invalid_parameter_type( $parameter, $name, $expected ) {
|
||||
return new self(
|
||||
sprintf(
|
||||
/* translators: %1$s expands to the parameter name. %2$s expands to the expected type and %3$s expands to the expected type. */
|
||||
__( 'Invalid type for parameter `%1$s` passed. Expected `%2$s`, but got `%3$s`', 'wordpress-seo' ),
|
||||
$name,
|
||||
$expected,
|
||||
gettype( $parameter )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid integer parameter exception.
|
||||
*
|
||||
* @param mixed $parameter The parameter value of the field.
|
||||
* @param string $name The name of the field.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function invalid_integer_parameter( $parameter, $name ) {
|
||||
return self::invalid_parameter_type( $parameter, $name, 'integer' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid string parameter exception.
|
||||
*
|
||||
* @param mixed $parameter The parameter value of the field.
|
||||
* @param string $name The name of the field.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function invalid_string_parameter( $parameter, $name ) {
|
||||
return self::invalid_parameter_type( $parameter, $name, 'string' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid boolean parameter exception.
|
||||
*
|
||||
* @param mixed $parameter The parameter value of the field.
|
||||
* @param string $name The name of the field.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function invalid_boolean_parameter( $parameter, $name ) {
|
||||
return self::invalid_parameter_type( $parameter, $name, 'boolean' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid callable parameter exception.
|
||||
*
|
||||
* @param mixed $parameter The parameter value of the field.
|
||||
* @param string $name The name of the field.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function invalid_callable_parameter( $parameter, $name ) {
|
||||
return self::invalid_parameter_type( $parameter, $name, 'callable' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid object type exception.
|
||||
*
|
||||
* @param string $type The type of the field.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function invalid_type( $type ) {
|
||||
return new self(
|
||||
sprintf(
|
||||
/* translators: %1$s expands to the object type. */
|
||||
__( 'The object type `%1$s` is invalid', 'wordpress-seo' ),
|
||||
$type
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid object subtype exception.
|
||||
*
|
||||
* @param string $subtype The invalid subtype.
|
||||
* @param string $type The parent type of the subtype.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function invalid_subtype( $subtype, $type ) {
|
||||
return new self(
|
||||
sprintf(
|
||||
/* translators: %1$s expands to the object subtype. %2$s resolved to the object type. */
|
||||
__( '`%1$s` is not a valid subtype of `%2$s`', 'wordpress-seo' ),
|
||||
$subtype,
|
||||
$type
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unknown object exception.
|
||||
*
|
||||
* @param int $id The ID that was searched for.
|
||||
* @param string $type The type of object that was being searched for.
|
||||
*
|
||||
* @return WPSEO_Invalid_Argument_Exception The exception.
|
||||
*/
|
||||
public static function unknown_object( $id, $type ) {
|
||||
return new self(
|
||||
sprintf(
|
||||
/* translators: %1$s expands to the object ID. %2$s resolved to the object type. */
|
||||
__( 'No object with ID %1$s and %2$s could be found', 'wordpress-seo' ),
|
||||
$id,
|
||||
$type
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Invalid_Indexable_Exception.
|
||||
*/
|
||||
class WPSEO_Invalid_Indexable_Exception extends InvalidArgumentException {
|
||||
|
||||
/**
|
||||
* Creates an invalid indexable exception.
|
||||
*
|
||||
* @param int $id The ID that was passed.
|
||||
*
|
||||
* @return WPSEO_Invalid_Indexable_Exception The exception.
|
||||
*/
|
||||
public static function non_existing_indexable( $id ) {
|
||||
return new self(
|
||||
sprintf(
|
||||
/* translators: %1$s expands to the indexable's ID. */
|
||||
__( 'Indexable with ID `%1$s` does not exist', 'wordpress-seo' ),
|
||||
$id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid POST request exception.
|
||||
*
|
||||
* @param int $id The ID that was passed.
|
||||
*
|
||||
* @return WPSEO_Invalid_Indexable_Exception The exception.
|
||||
*/
|
||||
public static function invalid_post_request( $id ) {
|
||||
return new self(
|
||||
sprintf(
|
||||
/* translators: %1$s expands to the indexable's ID. */
|
||||
__( 'Invalid POST request. Meta values already exist for object with ID %1$s.', 'wordpress-seo' ),
|
||||
$id
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_MyYoast_Authentication_Exception.
|
||||
*/
|
||||
class WPSEO_MyYoast_Authentication_Exception extends WPSEO_MyYoast_Bad_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_MyYoast_Bad_Request_Exception.
|
||||
*/
|
||||
class WPSEO_MyYoast_Bad_Request_Exception extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_MyYoast_Invalid_JSON_Exception.
|
||||
*/
|
||||
class WPSEO_MyYoast_Invalid_JSON_Exception extends WPSEO_MyYoast_Bad_Request_Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_REST_Request_Exception.
|
||||
*/
|
||||
class WPSEO_REST_Request_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Creates a patch failure exception.
|
||||
*
|
||||
* @param string $object_type The name of the parameter.
|
||||
* @param string $object_id The ID of the parameter.
|
||||
*
|
||||
* @return WPSEO_REST_Request_Exception The exception.
|
||||
*/
|
||||
public static function patch( $object_type, $object_id ) {
|
||||
return new self(
|
||||
sprintf(
|
||||
/* translators: %1$s expands to object type. %2$s expands to the object ID. */
|
||||
__( '%1$s with ID %2$s couldn\'t be patched', 'wordpress-seo' ),
|
||||
$object_type,
|
||||
$object_id
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the health check for paginated comments.
|
||||
*/
|
||||
class WPSEO_Health_Check_Page_Comments extends WPSEO_Health_Check {
|
||||
|
||||
/**
|
||||
* The name of the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $test = 'yoast-health-check-page-comments';
|
||||
|
||||
/**
|
||||
* Runs the test.
|
||||
*/
|
||||
public function run() {
|
||||
if ( ! $this->has_page_comments() ) {
|
||||
$this->label = esc_html__( 'Paging comments is properly disabled', 'wordpress-seo' );
|
||||
$this->status = self::STATUS_GOOD;
|
||||
$this->badge['color'] = 'blue';
|
||||
$this->description = esc_html__( 'Paging comments is disabled. As this is not needed in 999 out of 1000 cases, we recommend to keep it disabled.', 'wordpress-seo' );
|
||||
$this->add_yoast_signature();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->label = esc_html__( 'Paging comments is enabled', 'wordpress-seo' );
|
||||
$this->status = self::STATUS_RECOMMENDED;
|
||||
$this->badge['color'] = 'red';
|
||||
$this->description = esc_html__(
|
||||
'Paging comments is enabled. As this is not needed in 999 out of 1000 cases, we recommend you disable it.
|
||||
To fix this, uncheck the box in front of "Break comments into pages..." on the Discussion Settings page.',
|
||||
'wordpress-seo'
|
||||
);
|
||||
$this->actions = sprintf(
|
||||
/* translators: 1: Opening tag of the link to the discussion settings page, 2: Link closing tag. */
|
||||
esc_html__( '%1$sGo to the Discussion Settings page%2$s', 'wordpress-seo' ),
|
||||
'<a href="' . esc_url( admin_url( 'options-discussion.php' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
$this->add_yoast_signature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Are page comments enabled.
|
||||
*
|
||||
* @return bool True when page comments are enabled.
|
||||
*/
|
||||
protected function has_page_comments() {
|
||||
return '1' === get_option( 'page_comments' );
|
||||
}
|
||||
}
|
||||
213
wp-content/plugins/wordpress-seo/inc/health-check.php
Normal file
213
wp-content/plugins/wordpress-seo/inc/health-check.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the abstract class for the health check.
|
||||
*/
|
||||
abstract class WPSEO_Health_Check {
|
||||
|
||||
/**
|
||||
* The health check section in which 'good' results should be shown.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_GOOD = 'good';
|
||||
|
||||
/**
|
||||
* The health check section in which 'recommended' results should be shown.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_RECOMMENDED = 'recommended';
|
||||
|
||||
/**
|
||||
* The health check section in which 'critical' results should be shown.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_CRITICAL = 'critical';
|
||||
|
||||
/**
|
||||
* The value of the section header in the Health check.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label = '';
|
||||
|
||||
/**
|
||||
* Section the result should be displayed in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $status = '';
|
||||
|
||||
/**
|
||||
* What the badge should say with a color.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $badge = [
|
||||
'label' => '',
|
||||
'color' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Additional details about the results of the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* A link or button to allow the end user to take action on the result.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $actions = '';
|
||||
|
||||
/**
|
||||
* The name of the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $test = '';
|
||||
|
||||
/**
|
||||
* Whether or not the test should be ran on AJAX as well.
|
||||
*
|
||||
* @var bool True when is async, default false.
|
||||
*/
|
||||
protected $async = false;
|
||||
|
||||
/**
|
||||
* Runs the test and returns the result.
|
||||
*/
|
||||
abstract public function run();
|
||||
|
||||
/**
|
||||
* Registers the test to WordPress.
|
||||
*/
|
||||
public function register_test() {
|
||||
if ( $this->is_async() ) {
|
||||
add_filter( 'site_status_tests', [ $this, 'add_async_test' ] );
|
||||
|
||||
add_action( 'wp_ajax_health-check-' . $this->get_test_name(), [ $this, 'get_async_test_result' ] );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'site_status_tests', [ $this, 'add_test' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the test.
|
||||
*
|
||||
* @param array $tests Array with the current tests.
|
||||
*
|
||||
* @return array The extended array.
|
||||
*/
|
||||
public function add_test( $tests ) {
|
||||
$tests['direct'][ $this->get_test_name() ] = [
|
||||
'test' => [ $this, 'get_test_result' ],
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the test in async mode.
|
||||
*
|
||||
* @param array $tests Array with the current tests.
|
||||
*
|
||||
* @return array The extended array.
|
||||
*/
|
||||
public function add_async_test( $tests ) {
|
||||
$tests['async'][ $this->get_test_name() ] = [
|
||||
'test' => $this->get_test_name(),
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the test result as an array.
|
||||
*
|
||||
* @return array The formatted test result.
|
||||
*/
|
||||
public function get_test_result() {
|
||||
$this->run();
|
||||
|
||||
return [
|
||||
'label' => $this->label,
|
||||
'status' => $this->status,
|
||||
'badge' => $this->get_badge(),
|
||||
'description' => $this->description,
|
||||
'actions' => $this->actions,
|
||||
'test' => $this->test,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the test result as an array.
|
||||
*/
|
||||
public function get_async_test_result() {
|
||||
wp_send_json_success( $this->get_test_result() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the badge and ensure usable values are set.
|
||||
*
|
||||
* @return array The proper formatted badge.
|
||||
*/
|
||||
protected function get_badge() {
|
||||
if ( ! is_array( $this->badge ) ) {
|
||||
$this->badge = [];
|
||||
}
|
||||
|
||||
if ( empty( $this->badge['label'] ) ) {
|
||||
$this->badge['label'] = __( 'SEO', 'wordpress-seo' );
|
||||
}
|
||||
|
||||
if ( empty( $this->badge['color'] ) ) {
|
||||
$this->badge['color'] = 'green';
|
||||
}
|
||||
|
||||
return $this->badge;
|
||||
}
|
||||
|
||||
/**
|
||||
* WordPress converts the underscores to dashes. To prevent issues we have
|
||||
* to do it as well.
|
||||
*
|
||||
* @return string The formatted testname.
|
||||
*/
|
||||
protected function get_test_name() {
|
||||
return str_replace( '_', '-', $this->test );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the health check is async.
|
||||
*
|
||||
* @return bool True when check is async.
|
||||
*/
|
||||
protected function is_async() {
|
||||
return ! empty( $this->async );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a text to the bottom of the Site Health check to indicate it is a Yoast SEO Site Health Check.
|
||||
*/
|
||||
protected function add_yoast_signature() {
|
||||
$this->actions .= sprintf(
|
||||
/* translators: 1: Start of a paragraph beginning with the Yoast icon, 2: Expands to 'Yoast SEO', 3: Paragraph closing tag. */
|
||||
esc_html__( '%1$sThis was reported by the %2$s plugin%3$s', 'wordpress-seo' ),
|
||||
'<p class="yoast-site-health__signature"><img src="' . esc_url( plugin_dir_url( WPSEO_FILE ) . 'images/Yoast_SEO_Icon.svg' ) . '" alt="" height="20" width="20" class="yoast-site-health__signature-icon">',
|
||||
'Yoast SEO',
|
||||
'</p>'
|
||||
);
|
||||
}
|
||||
}
|
||||
4
wp-content/plugins/wordpress-seo/inc/index.php
Normal file
4
wp-content/plugins/wordpress-seo/inc/index.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
/**
|
||||
* Nothing to see here.
|
||||
*/
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Indexable.
|
||||
*/
|
||||
abstract class WPSEO_Indexable {
|
||||
|
||||
/**
|
||||
* The updateable fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $updateable_fields = [];
|
||||
|
||||
/**
|
||||
* The indexable's data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* The available validators to run.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $validators = [
|
||||
'WPSEO_Object_Type_Validator',
|
||||
'WPSEO_Link_Validator',
|
||||
'WPSEO_Keyword_Validator',
|
||||
'WPSEO_Meta_Values_Validator',
|
||||
'WPSEO_OpenGraph_Validator',
|
||||
'WPSEO_Robots_Validator',
|
||||
'WPSEO_Twitter_Validator',
|
||||
];
|
||||
|
||||
/**
|
||||
* Indexable constructor.
|
||||
*
|
||||
* @param array $data The data to use to construct the indexable.
|
||||
*/
|
||||
public function __construct( $data ) {
|
||||
$this->validate_data( $data );
|
||||
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the meta value to a boolean value.
|
||||
*
|
||||
* @param string $value The value to convert.
|
||||
*
|
||||
* @return bool|null The converted value.
|
||||
*/
|
||||
protected static function get_robots_noindex_value( $value ) {
|
||||
if ( $value === '1' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $value === '2' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the advanced robot metas value contains the passed value.
|
||||
*
|
||||
* @param int $object_id The ID of the object to check.
|
||||
* @param string $value The name of the advanced robots meta value to look for.
|
||||
*
|
||||
* @return bool Whether or not the advanced robots meta values contains the passed string.
|
||||
*/
|
||||
protected static function has_advanced_meta_value( $object_id, $value ) {
|
||||
return strpos( WPSEO_Meta::get_value( 'meta-robots-adv', $object_id ), $value ) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data.
|
||||
*
|
||||
* @param array $data The data to validate.
|
||||
*
|
||||
* @return bool True if all validators have successfully validated.
|
||||
*/
|
||||
protected function validate_data( $data ) {
|
||||
foreach ( $this->validators as $validator ) {
|
||||
// This is necessary to run under PHP 5.2.
|
||||
$validator_instance = new $validator();
|
||||
|
||||
$validator_instance->validate( $data );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the data and returns a new instance.
|
||||
*
|
||||
* @param array $data The data to update into a new instance.
|
||||
*
|
||||
* @return WPSEO_Indexable A new instance with the updated data.
|
||||
*/
|
||||
abstract public function update( $data );
|
||||
|
||||
/**
|
||||
* Filters out data that isn't considered updateable and returns a valid dataset.
|
||||
*
|
||||
* @param array $data The dataset to filter.
|
||||
*
|
||||
* @return array The updateable dataset.
|
||||
*/
|
||||
public function filter_updateable_data( $data ) {
|
||||
return array_intersect_key( $data, array_flip( $this->updateable_fields ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data as an array.
|
||||
*
|
||||
* @return array The data as an array.
|
||||
*/
|
||||
public function to_array() {
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Post_Indexable.
|
||||
*/
|
||||
abstract class WPSEO_Object_Type {
|
||||
|
||||
/**
|
||||
* The ID of the object.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The type of the object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The subtype of the object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sub_type;
|
||||
|
||||
/**
|
||||
* The permalink of the object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $permalink;
|
||||
|
||||
/**
|
||||
* WPSEO_Object_Type constructor.
|
||||
*
|
||||
* @param int $id The ID of the object.
|
||||
* @param string $type The type of object.
|
||||
* @param string $subtype The subtype of the object.
|
||||
* @param string $permalink The permalink of the object.
|
||||
*/
|
||||
public function __construct( $id, $type, $subtype, $permalink ) {
|
||||
$this->id = (int) $id;
|
||||
$this->type = $type;
|
||||
$this->sub_type = $subtype;
|
||||
$this->permalink = $permalink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID.
|
||||
*
|
||||
* @return int The ID.
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type.
|
||||
*
|
||||
* @return string The type.
|
||||
*/
|
||||
public function get_type() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subtype.
|
||||
*
|
||||
* @return string The subtype.
|
||||
*/
|
||||
public function get_subtype() {
|
||||
return $this->sub_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permalink.
|
||||
*
|
||||
* @return string The permalink.
|
||||
*/
|
||||
public function get_permalink() {
|
||||
return $this->permalink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the passed type is equal to the object's type.
|
||||
*
|
||||
* @param string $type The type to check.
|
||||
*
|
||||
* @return bool Whether or not the passed type is equal.
|
||||
*/
|
||||
public function is_type( $type ) {
|
||||
return $this->type === $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the passed subtype is equal to the object's subtype.
|
||||
*
|
||||
* @param string $sub_type The subtype to check.
|
||||
*
|
||||
* @return bool Whether or not the passed subtype is equal.
|
||||
*/
|
||||
public function is_subtype( $sub_type ) {
|
||||
return $this->sub_type === $sub_type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Post_Indexable.
|
||||
*/
|
||||
class WPSEO_Post_Indexable extends WPSEO_Indexable {
|
||||
|
||||
/**
|
||||
* The updateable fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $updateable_fields = [
|
||||
'canonical',
|
||||
'title',
|
||||
'description',
|
||||
'breadcrumb_title',
|
||||
'og_title',
|
||||
'og_description',
|
||||
'og_image',
|
||||
'twitter_title',
|
||||
'twitter_description',
|
||||
'twitter_image',
|
||||
'is_robots_noindex',
|
||||
'is_robots_nofollow',
|
||||
'is_robots_noarchive',
|
||||
'is_robots_noimageindex',
|
||||
'is_robots_nosnippet',
|
||||
'primary_focus_keyword',
|
||||
'primary_focus_keyword',
|
||||
'primary_focus_keyword_score',
|
||||
'readability_score',
|
||||
'is_cornerstone',
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a new Indexable from a passed object.
|
||||
*
|
||||
* @param int $object_id The object ID to create the object for.
|
||||
*
|
||||
* @return WPSEO_Indexable The indexable.
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the passed ID is not for an object of type 'post'.
|
||||
*/
|
||||
public static function from_object( $object_id ) {
|
||||
$post = WPSEO_Post_Object_Type::from_object( $object_id );
|
||||
|
||||
$link_count = new WPSEO_Link_Column_Count();
|
||||
$link_count->set( [ $object_id ] );
|
||||
|
||||
$post_object_id = $post->get_id();
|
||||
|
||||
return new self(
|
||||
[
|
||||
'object_id' => $post_object_id,
|
||||
'object_type' => $post->get_type(),
|
||||
'object_subtype' => $post->get_subtype(),
|
||||
'permalink' => $post->get_permalink(),
|
||||
'canonical' => WPSEO_Meta::get_value( 'canonical', $post_object_id ),
|
||||
'title' => WPSEO_Meta::get_value( 'title', $post_object_id ),
|
||||
'description' => WPSEO_Meta::get_value( 'metadesc', $post_object_id ),
|
||||
'breadcrumb_title' => WPSEO_Meta::get_value( 'bctitle', $post_object_id ),
|
||||
'og_title' => WPSEO_Meta::get_value( 'opengraph-title', $post_object_id ),
|
||||
'og_description' => WPSEO_Meta::get_value( 'opengraph-description', $post_object_id ),
|
||||
'og_image' => WPSEO_Meta::get_value( 'opengraph-image', $post_object_id ),
|
||||
'twitter_title' => WPSEO_Meta::get_value( 'twitter-title', $post_object_id ),
|
||||
'twitter_description' => WPSEO_Meta::get_value( 'twitter-description', $post_object_id ),
|
||||
'twitter_image' => WPSEO_Meta::get_value( 'twitter-image', $post_object_id ),
|
||||
'is_robots_noindex' => self::get_robots_noindex_value( WPSEO_Meta::get_value( 'meta-robots-noindex', $post_object_id ) ),
|
||||
'is_robots_nofollow' => WPSEO_Meta::get_value( 'meta-robots-nofollow', $post_object_id ) === '1',
|
||||
'is_robots_noarchive' => self::has_advanced_meta_value( $post_object_id, 'noarchive' ),
|
||||
'is_robots_noimageindex' => self::has_advanced_meta_value( $post_object_id, 'noimageindex' ),
|
||||
'is_robots_nosnippet' => self::has_advanced_meta_value( $post_object_id, 'nosnippet' ),
|
||||
'primary_focus_keyword' => WPSEO_Meta::get_value( 'focuskw', $post_object_id ),
|
||||
'primary_focus_keyword_score' => (int) WPSEO_Meta::get_value( 'linkdex', $post_object_id ),
|
||||
'readability_score' => (int) WPSEO_Meta::get_value( 'content_score', $post_object_id ),
|
||||
'is_cornerstone' => WPSEO_Meta::get_value( 'is_cornerstone', $post_object_id ) === '1',
|
||||
'link_count' => (int) $link_count->get( $post_object_id ),
|
||||
'incoming_link_count' => (int) $link_count->get( $post_object_id, 'incoming_link_count' ),
|
||||
'created_at' => null,
|
||||
'updated_at' => null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the data and returns a new instance.
|
||||
*
|
||||
* @param array $data The data to update into a new instance.
|
||||
*
|
||||
* @return WPSEO_Indexable A new instance with the updated data.
|
||||
*/
|
||||
public function update( $data ) {
|
||||
$data = array_merge( $this->data, $this->filter_updateable_data( $data ) );
|
||||
|
||||
return new self( $data );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Post_Object_Type.
|
||||
*/
|
||||
class WPSEO_Post_Object_Type extends WPSEO_Object_Type {
|
||||
|
||||
/**
|
||||
* Creates a new instance based on the passed object ID.
|
||||
*
|
||||
* @param int $object_id The object ID to base the object on.
|
||||
*
|
||||
* @return WPSEO_Post_Object_Type The class instance.
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the post is null.
|
||||
*/
|
||||
public static function from_object( $object_id ) {
|
||||
$post = get_post( $object_id );
|
||||
|
||||
if ( $post === null ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::unknown_object( $object_id, 'post' );
|
||||
}
|
||||
|
||||
return new self( $object_id, 'post', get_post_type( $object_id ), get_permalink( $object_id ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Term_Indexable.
|
||||
*/
|
||||
class WPSEO_Term_Indexable extends WPSEO_Indexable {
|
||||
|
||||
/**
|
||||
* The updateable fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $updateable_fields = [
|
||||
'canonical',
|
||||
'title',
|
||||
'description',
|
||||
'breadcrumb_title',
|
||||
'og_title',
|
||||
'og_description',
|
||||
'og_image',
|
||||
'twitter_title',
|
||||
'twitter_description',
|
||||
'twitter_image',
|
||||
'is_robots_noindex',
|
||||
'primary_focus_keyword',
|
||||
'primary_focus_keyword',
|
||||
'primary_focus_keyword_score',
|
||||
'readability_score',
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a new Indexable from a passed object.
|
||||
*
|
||||
* @param int $object_id The object ID to create the object for.
|
||||
*
|
||||
* @return WPSEO_Indexable The indexable.
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the passed ID is not for an object of type 'term'.
|
||||
*/
|
||||
public static function from_object( $object_id ) {
|
||||
$term = WPSEO_Term_Object_Type::from_object( $object_id );
|
||||
|
||||
$term_object_id = $term->get_id();
|
||||
|
||||
return new self(
|
||||
[
|
||||
'object_id' => $term_object_id,
|
||||
'object_type' => $term->get_type(),
|
||||
'object_subtype' => $term->get_subtype(),
|
||||
'permalink' => $term->get_permalink(),
|
||||
'canonical' => self::get_meta_value( 'canonical', $term ),
|
||||
'title' => self::get_meta_value( 'title', $term ),
|
||||
'description' => self::get_meta_value( 'desc', $term ),
|
||||
'breadcrumb_title' => self::get_meta_value( 'bctitle', $term ),
|
||||
'og_title' => self::get_meta_value( 'opengraph-title', $term ),
|
||||
'og_description' => self::get_meta_value( 'opengraph-description', $term ),
|
||||
'og_image' => self::get_meta_value( 'opengraph-image', $term ),
|
||||
'twitter_title' => self::get_meta_value( 'twitter-title', $term ),
|
||||
'twitter_description' => self::get_meta_value( 'twitter-description', $term ),
|
||||
'twitter_image' => self::get_meta_value( 'twitter-image', $term ),
|
||||
'is_robots_noindex' => self::get_robots_noindex_value( self::get_meta_value( 'noindex', $term ) ),
|
||||
'is_robots_nofollow' => null,
|
||||
'is_robots_noarchive' => null,
|
||||
'is_robots_noimageindex' => null,
|
||||
'is_robots_nosnippet' => null,
|
||||
'primary_focus_keyword' => self::get_meta_value( 'focuskw', $term ),
|
||||
'primary_focus_keyword_score' => (int) self::get_meta_value( 'linkdex', $term ),
|
||||
'readability_score' => (int) self::get_meta_value( 'content_score', $term ),
|
||||
'is_cornerstone' => false,
|
||||
'link_count' => null,
|
||||
'incoming_link_count' => null,
|
||||
'created_at' => null,
|
||||
'updated_at' => null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the data and returns a new instance.
|
||||
*
|
||||
* @param array $data The data to update into a new instance.
|
||||
*
|
||||
* @return WPSEO_Indexable A new instance with the updated data.
|
||||
*/
|
||||
public function update( $data ) {
|
||||
$data = array_merge( $this->data, $this->filter_updateable_data( $data ) );
|
||||
|
||||
return new self( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the needed term meta field.
|
||||
*
|
||||
* @param string $field The requested field.
|
||||
* @param WPSEO_Term_Object_Type $term The term object.
|
||||
*
|
||||
* @return bool|mixed The value of the requested field.
|
||||
*/
|
||||
protected static function get_meta_value( $field, $term ) {
|
||||
return WPSEO_Taxonomy_Meta::get_term_meta( $term->get_id(), $term->get_subtype(), $field );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the meta value to a boolean value.
|
||||
*
|
||||
* @param string $value The value to convert.
|
||||
*
|
||||
* @return bool|null The converted value.
|
||||
*/
|
||||
protected static function get_robots_noindex_value( $value ) {
|
||||
if ( $value === 'noindex' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $value === 'index' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Term_Object_Type.
|
||||
*/
|
||||
class WPSEO_Term_Object_Type extends WPSEO_Object_Type {
|
||||
|
||||
/**
|
||||
* Creates a new instance based on the passed object ID.
|
||||
*
|
||||
* @param int $object_id The object ID to base the object on.
|
||||
*
|
||||
* @return WPSEO_Term_Object_Type The class instance.
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the term is null or if a WordPress error is thrown.
|
||||
*/
|
||||
public static function from_object( $object_id ) {
|
||||
$term = get_term( $object_id );
|
||||
|
||||
if ( $term === null || is_wp_error( $term ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::unknown_object( $object_id, 'term' );
|
||||
}
|
||||
|
||||
return new self( $object_id, 'term', $term->taxonomy, get_term_link( $term ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO interface file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface WPSEO_Endpoint_Validator.
|
||||
*/
|
||||
interface WPSEO_Endpoint_Validator {
|
||||
|
||||
/**
|
||||
* Validates the passed request data.
|
||||
*
|
||||
* @param array $request_data The request data to validate.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function validate( $request_data );
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Keyword_Validator.
|
||||
*/
|
||||
class WPSEO_Keyword_Validator implements WPSEO_Endpoint_Validator {
|
||||
|
||||
/**
|
||||
* Validates the keyword-related data.
|
||||
*
|
||||
* @param array $request_data The request data to validate.
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the keyword or the score is of an invalid value type.
|
||||
*/
|
||||
public function validate( $request_data ) {
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'keyword' ) && ! WPSEO_Validator::is_string( $request_data['keyword'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['keyword'], 'keyword' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'score' ) && ! WPSEO_Validator::is_integer( $request_data['score'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_integer_parameter( $request_data['score'], 'score' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Link_Validator.
|
||||
*/
|
||||
class WPSEO_Link_Validator implements WPSEO_Endpoint_Validator {
|
||||
|
||||
/**
|
||||
* Validates the link-related data.
|
||||
*
|
||||
* @param array $request_data The request data to validate.
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the link-data count or incoming count is of an invalid value type.
|
||||
*/
|
||||
public function validate( $request_data ) {
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'count' ) && ! WPSEO_Validator::is_integer( $request_data['count'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_integer_parameter( $request_data['count'], 'count' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'incoming_count' ) && ! WPSEO_Validator::is_integer( $request_data['incoming_count'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_integer_parameter( $request_data['incoming_count'], 'incoming_count' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Meta_Values_Validator.
|
||||
*/
|
||||
class WPSEO_Meta_Values_Validator implements WPSEO_Endpoint_Validator {
|
||||
|
||||
/**
|
||||
* Validates the meta values data.
|
||||
*
|
||||
* @param array $request_data The request data to validate.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if a field from the request data is of an invalid value type.
|
||||
*/
|
||||
public function validate( $request_data ) {
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'title' ) && ! WPSEO_Validator::is_string( $request_data['title'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['title'], 'title' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'metadesc' ) && ! WPSEO_Validator::is_string( $request_data['metadesc'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['metadesc'], 'metadesc' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'permalink' ) && ! WPSEO_Validator::is_string( $request_data['permalink'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['permalink'], 'permalink' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'readability_score' ) && ! WPSEO_Validator::is_integer( $request_data['readability_score'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_integer_parameter( $request_data['readability_score'], 'readability_score' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'is_cornerstone' ) && ! WPSEO_Validator::is_boolean( $request_data['is_cornerstone'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_boolean_parameter( $request_data['is_cornerstone'], 'is_cornerstone' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'canonical' ) && ! WPSEO_Validator::is_string( $request_data['canonical'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['canonical'], 'canonical' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'breadcrumb_title' ) && ! WPSEO_Validator::is_string( $request_data['breadcrumb_title'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['breadcrumb_title'], 'breadcrumb_title' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Object_Type_Validator.
|
||||
*/
|
||||
class WPSEO_Object_Type_Validator implements WPSEO_Endpoint_Validator {
|
||||
|
||||
/**
|
||||
* Validates the object_type parameter.
|
||||
*
|
||||
* @param string $object_type The object type to validate.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown is the object type is invalid.
|
||||
*/
|
||||
private static function validate_type( $object_type ) {
|
||||
if ( ! in_array( $object_type, [ 'post', 'term' ], true ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_type( $object_type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the passed subtype is valid or not.
|
||||
*
|
||||
* @param string $type The type to validate.
|
||||
* @param string $subtype The subtype to validate.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the subtype doesn't exist for the given type.
|
||||
*/
|
||||
private static function validate_subtype( $type, $subtype ) {
|
||||
if ( $type === 'post' && ! post_type_exists( $subtype ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_subtype( $subtype, $type );
|
||||
}
|
||||
|
||||
if ( $type === 'term' && ! taxonomy_exists( $subtype ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_subtype( $subtype, $type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the object type-related data.
|
||||
*
|
||||
* @param array $request_data The request data to validate.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the type or subtype are invalid.
|
||||
*/
|
||||
public function validate( $request_data ) {
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'object_type' ) ) {
|
||||
self::validate_type( $request_data['object_type'] );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'object_subtype' ) ) {
|
||||
self::validate_subtype( $request_data['object_type'], $request_data['object_subtype'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_OpenGraph_Validator.
|
||||
*/
|
||||
class WPSEO_OpenGraph_Validator implements WPSEO_Endpoint_Validator {
|
||||
|
||||
/**
|
||||
* Validates the OpenGraph-related data.
|
||||
*
|
||||
* @param array $request_data The request data to validate.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if one of the OpenGraph properties is of an invalid value type.
|
||||
*/
|
||||
public function validate( $request_data ) {
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'og_title' ) && ! WPSEO_Validator::is_string( $request_data['og_title'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['og_title'], 'og_title' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'og_description' ) && ! WPSEO_Validator::is_string( $request_data['og_description'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['og_description'], 'og_description' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'og_image' ) && ! WPSEO_Validator::is_string( $request_data['og_image'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['og_image'], 'og_image' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Robots_Validator.
|
||||
*/
|
||||
class WPSEO_Robots_Validator implements WPSEO_Endpoint_Validator {
|
||||
|
||||
/**
|
||||
* The robots keys to validate.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $robots_to_validate = [
|
||||
'is_robots_nofollow',
|
||||
'is_robots_noarchive',
|
||||
'is_robots_noimageindex',
|
||||
'is_robots_nosnippet',
|
||||
'is_robots_noindex',
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates the passed request data.
|
||||
*
|
||||
* @param array $request_data The request data to validate.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if the robots values are not a boolean type.
|
||||
*/
|
||||
public function validate( $request_data ) {
|
||||
foreach ( $this->robots_to_validate as $item ) {
|
||||
if ( ! WPSEO_Validator::key_exists( $request_data, $item ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! is_null( $request_data[ $item ] ) && ! WPSEO_Validator::is_boolean( $request_data[ $item ] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_boolean_parameter( $request_data[ $item ], $item );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Indexables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Twitter_Validator.
|
||||
*/
|
||||
class WPSEO_Twitter_Validator implements WPSEO_Endpoint_Validator {
|
||||
|
||||
/**
|
||||
* Validates the Twitter-related data.
|
||||
*
|
||||
* @param array $request_data The request data to validate.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Invalid_Argument_Exception Thrown if one of the Twitter properties is of an invalid value type.
|
||||
*/
|
||||
public function validate( $request_data ) {
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'twitter_title' ) && ! WPSEO_Validator::is_string( $request_data['twitter_title'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['twitter_title'], 'twitter_title' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'twitter_description' ) && ! WPSEO_Validator::is_string( $request_data['twitter_description'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['twitter_description'], 'twitter_description' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Validator::key_exists( $request_data, 'twitter_image' ) && ! WPSEO_Validator::is_string( $request_data['twitter_image'] ) ) {
|
||||
throw WPSEO_Invalid_Argument_Exception::invalid_string_parameter( $request_data['twitter_image'], 'twitter_image' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
/**
|
||||
* An interface for registering AJAX integrations with WordPress.
|
||||
*/
|
||||
interface WPSEO_WordPress_AJAX_Integration {
|
||||
|
||||
/**
|
||||
* Registers all AJAX hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_ajax_hooks();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO
|
||||
*/
|
||||
|
||||
if ( ! interface_exists( 'WPSEO_WordPress_Integration' ) ) {
|
||||
/**
|
||||
* An interface for registering integrations with WordPress.
|
||||
*/
|
||||
interface WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks();
|
||||
}
|
||||
}
|
||||
87
wp-content/plugins/wordpress-seo/inc/language-utils.php
Normal file
87
wp-content/plugins/wordpress-seo/inc/language-utils.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
* @since 5.9.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Group of language utility methods for use by WPSEO.
|
||||
* All methods are static, this is just a sort of namespacing class wrapper.
|
||||
*/
|
||||
class WPSEO_Language_Utils {
|
||||
|
||||
/**
|
||||
* Returns the language part of a given locale, defaults to english when the $locale is empty.
|
||||
*
|
||||
* @param string $locale The locale to get the language of.
|
||||
*
|
||||
* @return string The language part of the locale.
|
||||
*/
|
||||
public static function get_language( $locale = null ) {
|
||||
$language = 'en';
|
||||
|
||||
if ( empty( $locale ) || ! is_string( $locale ) ) {
|
||||
return $language;
|
||||
}
|
||||
|
||||
$locale_parts = explode( '_', $locale );
|
||||
|
||||
if ( ! empty( $locale_parts[0] ) && ( strlen( $locale_parts[0] ) === 2 || strlen( $locale_parts[0] ) === 3 ) ) {
|
||||
$language = $locale_parts[0];
|
||||
}
|
||||
|
||||
return $language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user locale for the language to be used in the admin.
|
||||
*
|
||||
* WordPress 4.7 introduced the ability for users to specify an Admin language
|
||||
* different from the language used on the front end. This checks if the feature
|
||||
* is available and returns the user's language, with a fallback to the site's language.
|
||||
* Can be removed when support for WordPress 4.6 will be dropped, in favor
|
||||
* of WordPress get_user_locale() that already fallbacks to the site's locale.
|
||||
*
|
||||
* @return string The locale.
|
||||
*/
|
||||
public static function get_user_locale() {
|
||||
if ( function_exists( 'get_user_locale' ) ) {
|
||||
return get_user_locale();
|
||||
}
|
||||
|
||||
return get_locale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full name for the sites' language.
|
||||
*
|
||||
* @return string The language name.
|
||||
*/
|
||||
public static function get_site_language_name() {
|
||||
require_once ABSPATH . 'wp-admin/includes/translation-install.php';
|
||||
|
||||
$translations = wp_get_available_translations();
|
||||
$locale = get_locale();
|
||||
$language = isset( $translations[ $locale ] ) ? $translations[ $locale ]['native_name'] : 'English (US)';
|
||||
|
||||
return $language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the l10n array for the knowledge graph company info missing.
|
||||
*
|
||||
* @return array The l10n array.
|
||||
*/
|
||||
public static function get_knowledge_graph_company_info_missing_l10n() {
|
||||
return [
|
||||
'URL' => esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/3r3' ) ),
|
||||
/* translators: 1: expands to a link opening tag; 2: expands to a link closing tag */
|
||||
'message' => esc_html__(
|
||||
'A company name and logo need to be set for structured data to work properly. %1$sLearn more about the importance of structured data.%2$s',
|
||||
'wordpress-seo'
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals\Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Site option for Multisite installs only
|
||||
*
|
||||
* Overloads a number of methods of the abstract class to ensure the use of the correct site_option
|
||||
* WP functions.
|
||||
*/
|
||||
class WPSEO_Option_MS extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo_ms';
|
||||
|
||||
/**
|
||||
* Option group name for use in settings forms.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $group_name = 'yoast_wpseo_multisite_options';
|
||||
|
||||
/**
|
||||
* Whether to include the option in the return for WPSEO_Options::get_all().
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $include_in_all = false;
|
||||
|
||||
/**
|
||||
* Whether this option is only for when the install is multisite.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $multisite_only = true;
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* Shouldn't be requested directly, use $this->get_defaults();
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [];
|
||||
|
||||
/**
|
||||
* Available options for the 'access' setting. Used for input validation.
|
||||
*
|
||||
* {@internal Important: Make sure the options added to the array here are in line
|
||||
* with the keys for the options set for the select box in the
|
||||
* admin/pages/network.php file.}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $allowed_access_options = [
|
||||
'admin',
|
||||
'superadmin',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( self::$instance instanceof self ) ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only run parent constructor in multisite context.
|
||||
*/
|
||||
public function __construct() {
|
||||
$allow_prefix = self::ALLOW_KEY_PREFIX;
|
||||
$this->defaults = [
|
||||
'access' => 'admin',
|
||||
'defaultblog' => '', // Numeric blog ID or empty.
|
||||
"{$allow_prefix}disableadvanced_meta" => true,
|
||||
"{$allow_prefix}onpage_indexability" => true,
|
||||
"{$allow_prefix}content_analysis_active" => true,
|
||||
"{$allow_prefix}keyword_analysis_active" => true,
|
||||
"{$allow_prefix}enable_admin_bar_menu" => true,
|
||||
"{$allow_prefix}enable_cornerstone_content" => true,
|
||||
"{$allow_prefix}enable_xml_sitemap" => true,
|
||||
"{$allow_prefix}enable_text_link_counter" => true,
|
||||
];
|
||||
|
||||
if ( is_multisite() ) {
|
||||
parent::__construct();
|
||||
|
||||
add_filter( 'admin_title', [ 'Yoast_Input_Validation', 'add_yoast_admin_document_title_errors' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filters to make sure that the option default is returned if the option is not set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_default_filters() {
|
||||
// Don't change, needs to check for false as could return prio 0 which would evaluate to false.
|
||||
if ( has_filter( 'default_site_option_' . $this->option_name, [ $this, 'get_defaults' ] ) === false ) {
|
||||
add_filter( 'default_site_option_' . $this->option_name, [ $this, 'get_defaults' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default filters.
|
||||
* Called from the validate() method to prevent failure to add new options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_default_filters() {
|
||||
remove_filter( 'default_site_option_' . $this->option_name, [ $this, 'get_defaults' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filters to make sure that the option is merged with its defaults before being returned.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_option_filters() {
|
||||
// Don't change, needs to check for false as could return prio 0 which would evaluate to false.
|
||||
if ( has_filter( 'site_option_' . $this->option_name, [ $this, 'get_option' ] ) === false ) {
|
||||
add_filter( 'site_option_' . $this->option_name, [ $this, 'get_option' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the option filters.
|
||||
* Called from the clean_up methods to make sure we retrieve the original old option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_option_filters() {
|
||||
remove_filter( 'site_option_' . $this->option_name, [ $this, 'get_option' ] );
|
||||
}
|
||||
|
||||
/* *********** METHODS influencing add_uption(), update_option() and saving from admin pages *********** */
|
||||
|
||||
/**
|
||||
* Validate the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array Validated clean value for the option to be saved to the database.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
|
||||
foreach ( $clean as $key => $value ) {
|
||||
switch ( $key ) {
|
||||
case 'access':
|
||||
if ( isset( $dirty[ $key ] ) && in_array( $dirty[ $key ], self::$allowed_access_options, true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
elseif ( function_exists( 'add_settings_error' ) ) {
|
||||
add_settings_error(
|
||||
$this->group_name, // Slug title of the setting.
|
||||
$key, // Suffix-ID for the error message box.
|
||||
/* translators: %1$s expands to the option name and %2$sexpands to Yoast SEO */
|
||||
sprintf( __( '%1$s is not a valid choice for who should be allowed access to the %2$s settings. Value reset to the default.', 'wordpress-seo' ), esc_html( sanitize_text_field( $dirty[ $key ] ) ), 'Yoast SEO' ), // The error message.
|
||||
'error' // Message type.
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'defaultblog':
|
||||
if ( isset( $dirty[ $key ] ) && ( $dirty[ $key ] !== '' && $dirty[ $key ] !== '-' ) ) {
|
||||
$int = WPSEO_Utils::validate_int( $dirty[ $key ] );
|
||||
if ( $int !== false && $int > 0 ) {
|
||||
// Check if a valid blog number has been received.
|
||||
$exists = get_blog_details( $int, false );
|
||||
if ( $exists && $exists->deleted === '0' ) {
|
||||
$clean[ $key ] = $int;
|
||||
}
|
||||
elseif ( function_exists( 'add_settings_error' ) ) {
|
||||
add_settings_error(
|
||||
$this->group_name, // Slug title of the setting.
|
||||
$key, // Suffix-ID for the error message box.
|
||||
esc_html__( 'The default blog setting must be the numeric blog id of the blog you want to use as default.', 'wordpress-seo' )
|
||||
. '<br>'
|
||||
. sprintf(
|
||||
/* translators: %s is the ID number of a blog. */
|
||||
esc_html__( 'This must be an existing blog. Blog %s does not exist or has been marked as deleted.', 'wordpress-seo' ),
|
||||
'<strong>' . esc_html( sanitize_text_field( $dirty[ $key ] ) ) . '</strong>'
|
||||
), // The error message.
|
||||
'error' // Message type.
|
||||
);
|
||||
}
|
||||
unset( $exists );
|
||||
}
|
||||
elseif ( function_exists( 'add_settings_error' ) ) {
|
||||
add_settings_error(
|
||||
$this->group_name, // Slug title of the setting.
|
||||
$key, // Suffix-ID for the error message box.
|
||||
esc_html__( 'The default blog setting must be the numeric blog id of the blog you want to use as default.', 'wordpress-seo' ) . '<br>' . esc_html__( 'No numeric value was received.', 'wordpress-seo' ), // The error message.
|
||||
'error' // Message type.
|
||||
);
|
||||
}
|
||||
unset( $int );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$clean[ $key ] = ( isset( $dirty[ $key ] ) ? WPSEO_Utils::validate_bool( $dirty[ $key ] ) : false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a given option value.
|
||||
*
|
||||
* @param array $option_value Old (not merged with defaults or filtered) option value to
|
||||
* clean according to the rules for this option.
|
||||
* @param string $current_version (optional) Version from which to upgrade, if not set,
|
||||
* version specific upgrades will be disregarded.
|
||||
* @param array $all_old_option_values (optional) Only used when importing old options to have
|
||||
* access to the real old values, in contrast to the saved ones.
|
||||
*
|
||||
* @return array Cleaned option.
|
||||
*/
|
||||
|
||||
/*
|
||||
Protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
|
||||
|
||||
return $option_value;
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals\Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Option: wpseo_social.
|
||||
*/
|
||||
class WPSEO_Option_Social extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo_social';
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* Shouldn't be requested directly, use $this->get_defaults();
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [
|
||||
// Form fields.
|
||||
'facebook_site' => '', // Text field.
|
||||
'instagram_url' => '',
|
||||
'linkedin_url' => '',
|
||||
'myspace_url' => '',
|
||||
'og_default_image' => '', // Text field.
|
||||
'og_default_image_id' => '',
|
||||
'og_frontpage_title' => '', // Text field.
|
||||
'og_frontpage_desc' => '', // Text field.
|
||||
'og_frontpage_image' => '', // Text field.
|
||||
'og_frontpage_image_id' => '',
|
||||
'opengraph' => true,
|
||||
'pinterest_url' => '',
|
||||
'pinterestverify' => '',
|
||||
'twitter' => true,
|
||||
'twitter_site' => '', // Text field.
|
||||
'twitter_card_type' => 'summary_large_image',
|
||||
'youtube_url' => '',
|
||||
'wikipedia_url' => '',
|
||||
// Form field, but not always available.
|
||||
'fbadminapp' => '', // Facebook app ID.
|
||||
];
|
||||
|
||||
/**
|
||||
* Array of sub-options which should not be overloaded with multi-site defaults.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $ms_exclude = [
|
||||
/* Privacy. */
|
||||
'pinterestverify',
|
||||
'fbadminapp',
|
||||
];
|
||||
|
||||
/**
|
||||
* Array of allowed twitter card types.
|
||||
*
|
||||
* While we only have the options summary and summary_large_image in the
|
||||
* interface now, we might change that at some point.
|
||||
*
|
||||
* {@internal Uncomment any of these to allow them in validation *and* automatically
|
||||
* add them as a choice in the options page.}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $twitter_card_types = [
|
||||
'summary' => '',
|
||||
'summary_large_image' => '',
|
||||
// 'photo' => '',
|
||||
// 'gallery' => '',
|
||||
// 'app' => '',
|
||||
// 'player' => '',
|
||||
// 'product' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Add the actions and filters for the option.
|
||||
*/
|
||||
protected function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
add_filter( 'admin_title', [ 'Yoast_Input_Validation', 'add_yoast_admin_document_title_errors' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( self::$instance instanceof self ) ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate/set strings used in the option defaults.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function translate_defaults() {
|
||||
self::$twitter_card_types['summary'] = __( 'Summary', 'wordpress-seo' );
|
||||
self::$twitter_card_types['summary_large_image'] = __( 'Summary with large image', 'wordpress-seo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array Validated clean value for the option to be saved to the database.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
|
||||
foreach ( $clean as $key => $value ) {
|
||||
switch ( $key ) {
|
||||
/* Text fields. */
|
||||
case 'og_frontpage_desc':
|
||||
case 'og_frontpage_title':
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
|
||||
$clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'og_default_image_id':
|
||||
case 'og_frontpage_image_id':
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = (int) $dirty[ $key ];
|
||||
|
||||
if ( $dirty[ $key ] === '' ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* URL text fields - no ftp allowed. */
|
||||
case 'facebook_site':
|
||||
case 'instagram_url':
|
||||
case 'linkedin_url':
|
||||
case 'myspace_url':
|
||||
case 'pinterest_url':
|
||||
case 'og_default_image':
|
||||
case 'og_frontpage_image':
|
||||
case 'youtube_url':
|
||||
case 'wikipedia_url':
|
||||
$this->validate_url( $key, $dirty, $old, $clean );
|
||||
break;
|
||||
|
||||
case 'pinterestverify':
|
||||
$this->validate_verification_string( $key, $dirty, $old, $clean );
|
||||
break;
|
||||
|
||||
/* Twitter user name. */
|
||||
case 'twitter_site':
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
|
||||
$twitter_id = sanitize_text_field( ltrim( $dirty[ $key ], '@' ) );
|
||||
|
||||
/*
|
||||
* From the Twitter documentation about twitter screen names:
|
||||
* Typically a maximum of 15 characters long, but some historical accounts
|
||||
* may exist with longer names.
|
||||
* A username can only contain alphanumeric characters (letters A-Z, numbers 0-9)
|
||||
* with the exception of underscores.
|
||||
*
|
||||
* @link https://support.twitter.com/articles/101299-why-can-t-i-register-certain-usernames
|
||||
* @link https://dev.twitter.com/docs/platform-objects/users
|
||||
*/
|
||||
if ( preg_match( '`^[A-Za-z0-9_]{1,25}$`', $twitter_id ) ) {
|
||||
$clean[ $key ] = $twitter_id;
|
||||
}
|
||||
elseif ( preg_match( '`^http(?:s)?://(?:www\.)?twitter\.com/(?P<handle>[A-Za-z0-9_]{1,25})/?$`', $twitter_id, $matches ) ) {
|
||||
$clean[ $key ] = $matches['handle'];
|
||||
}
|
||||
else {
|
||||
if ( isset( $old[ $key ] ) && $old[ $key ] !== '' ) {
|
||||
$twitter_id = sanitize_text_field( ltrim( $old[ $key ], '@' ) );
|
||||
if ( preg_match( '`^[A-Za-z0-9_]{1,25}$`', $twitter_id ) ) {
|
||||
$clean[ $key ] = $twitter_id;
|
||||
}
|
||||
}
|
||||
if ( function_exists( 'add_settings_error' ) ) {
|
||||
add_settings_error(
|
||||
$this->group_name, // Slug title of the setting.
|
||||
$key, // Suffix-ID for the error message box.
|
||||
sprintf(
|
||||
/* translators: %s expands to a twitter user name. */
|
||||
__( '%s does not seem to be a valid Twitter Username. Please correct.', 'wordpress-seo' ),
|
||||
'<strong>' . esc_html( sanitize_text_field( $dirty[ $key ] ) ) . '</strong>'
|
||||
), // The error message.
|
||||
'error' // Message type.
|
||||
);
|
||||
}
|
||||
}
|
||||
unset( $twitter_id );
|
||||
|
||||
Yoast_Input_Validation::add_dirty_value_to_settings_errors( $key, $dirty[ $key ] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'twitter_card_type':
|
||||
if ( isset( $dirty[ $key ], self::$twitter_card_types[ $dirty[ $key ] ] ) && $dirty[ $key ] !== '' ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
|
||||
/* Boolean fields. */
|
||||
case 'opengraph':
|
||||
case 'twitter':
|
||||
$clean[ $key ] = ( isset( $dirty[ $key ] ) ? WPSEO_Utils::validate_bool( $dirty[ $key ] ) : false );
|
||||
break;
|
||||
|
||||
case 'fbadminapp':
|
||||
$this->validate_facebook_app_id( $key, $dirty, $old, $clean );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a given option value.
|
||||
*
|
||||
* @param array $option_value Old (not merged with defaults or filtered) option value to
|
||||
* clean according to the rules for this option.
|
||||
* @param string $current_version Optional. Version from which to upgrade, if not set,
|
||||
* version specific upgrades will be disregarded.
|
||||
* @param array $all_old_option_values Optional. Only used when importing old options to have
|
||||
* access to the real old values, in contrast to the saved ones.
|
||||
*
|
||||
* @return array Cleaned option.
|
||||
*/
|
||||
protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
|
||||
|
||||
/* Move options from very old option to this one. */
|
||||
$old_option = null;
|
||||
if ( isset( $all_old_option_values ) ) {
|
||||
// Ok, we have an import.
|
||||
if ( isset( $all_old_option_values['wpseo_indexation'] ) && is_array( $all_old_option_values['wpseo_indexation'] ) && $all_old_option_values['wpseo_indexation'] !== [] ) {
|
||||
$old_option = $all_old_option_values['wpseo_indexation'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$old_option = get_option( 'wpseo_indexation' );
|
||||
}
|
||||
|
||||
if ( is_array( $old_option ) && $old_option !== [] ) {
|
||||
$move = [
|
||||
'opengraph',
|
||||
];
|
||||
foreach ( $move as $key ) {
|
||||
if ( isset( $old_option[ $key ] ) && ! isset( $option_value[ $key ] ) ) {
|
||||
$option_value[ $key ] = $old_option[ $key ];
|
||||
}
|
||||
}
|
||||
unset( $move, $key );
|
||||
}
|
||||
unset( $old_option );
|
||||
|
||||
|
||||
return $option_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,905 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals\Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Option: wpseo_titles.
|
||||
*/
|
||||
class WPSEO_Option_Titles extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo_titles';
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* Shouldn't be requested directly, use $this->get_defaults();
|
||||
*
|
||||
* {@internal Note: Some of the default values are added via the translate_defaults() method.}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [
|
||||
// Non-form fields, set via (ajax) function.
|
||||
'title_test' => 0,
|
||||
// Form fields.
|
||||
'forcerewritetitle' => false,
|
||||
'separator' => 'sc-dash',
|
||||
'title-home-wpseo' => '%%sitename%% %%page%% %%sep%% %%sitedesc%%', // Text field.
|
||||
'title-author-wpseo' => '', // Text field.
|
||||
'title-archive-wpseo' => '%%date%% %%page%% %%sep%% %%sitename%%', // Text field.
|
||||
'title-search-wpseo' => '', // Text field.
|
||||
'title-404-wpseo' => '', // Text field.
|
||||
|
||||
'metadesc-home-wpseo' => '', // Text area.
|
||||
'metadesc-author-wpseo' => '', // Text area.
|
||||
'metadesc-archive-wpseo' => '', // Text area.
|
||||
'rssbefore' => '', // Text area.
|
||||
'rssafter' => '', // Text area.
|
||||
|
||||
'noindex-author-wpseo' => false,
|
||||
'noindex-author-noposts-wpseo' => true,
|
||||
'noindex-archive-wpseo' => true,
|
||||
|
||||
'disable-author' => false,
|
||||
'disable-date' => false,
|
||||
'disable-post_format' => false,
|
||||
'disable-attachment' => true,
|
||||
'is-media-purge-relevant' => false,
|
||||
|
||||
'breadcrumbs-404crumb' => '', // Text field.
|
||||
'breadcrumbs-display-blog-page' => true,
|
||||
'breadcrumbs-boldlast' => false,
|
||||
'breadcrumbs-archiveprefix' => '', // Text field.
|
||||
'breadcrumbs-enable' => false,
|
||||
'breadcrumbs-home' => '', // Text field.
|
||||
'breadcrumbs-prefix' => '', // Text field.
|
||||
'breadcrumbs-searchprefix' => '', // Text field.
|
||||
'breadcrumbs-sep' => '»', // Text field.
|
||||
|
||||
'website_name' => '',
|
||||
'person_name' => '',
|
||||
'person_logo' => '',
|
||||
'person_logo_id' => 0,
|
||||
'alternate_website_name' => '',
|
||||
'company_logo' => '',
|
||||
'company_logo_id' => 0,
|
||||
'company_name' => '',
|
||||
'company_or_person' => 'company',
|
||||
'company_or_person_user_id' => false,
|
||||
|
||||
'stripcategorybase' => false,
|
||||
|
||||
/**
|
||||
* Uses enrich_defaults to add more along the lines of:
|
||||
* - 'title-' . $pt->name => ''; // Text field.
|
||||
* - 'metadesc-' . $pt->name => ''; // Text field.
|
||||
* - 'noindex-' . $pt->name => false;
|
||||
* - 'showdate-' . $pt->name => false;
|
||||
* - 'display-metabox-pt-' . $pt->name => false;
|
||||
*
|
||||
* - 'title-ptarchive-' . $pt->name => ''; // Text field.
|
||||
* - 'metadesc-ptarchive-' . $pt->name => ''; // Text field.
|
||||
* - 'bctitle-ptarchive-' . $pt->name => ''; // Text field.
|
||||
* - 'noindex-ptarchive-' . $pt->name => false;
|
||||
*
|
||||
* - 'title-tax-' . $tax->name => '''; // Text field.
|
||||
* - 'metadesc-tax-' . $tax->name => ''; // Text field.
|
||||
* - 'noindex-tax-' . $tax->name => false;
|
||||
* - 'display-metabox-tax-' . $tax->name => false;
|
||||
*/
|
||||
];
|
||||
|
||||
/**
|
||||
* Used for "caching" during pageload.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enriched_defaults = null;
|
||||
|
||||
/**
|
||||
* Array of variable option name patterns for the option.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $variable_array_key_patterns = [
|
||||
'title-',
|
||||
'metadesc-',
|
||||
'noindex-',
|
||||
'showdate-',
|
||||
'display-metabox-pt-',
|
||||
'bctitle-ptarchive-',
|
||||
'post_types-',
|
||||
'taxonomy-',
|
||||
];
|
||||
|
||||
/**
|
||||
* Array of sub-options which should not be overloaded with multi-site defaults.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $ms_exclude = [
|
||||
/* Theme dependent. */
|
||||
'title_test',
|
||||
'forcerewritetitle',
|
||||
];
|
||||
|
||||
/**
|
||||
* Add the actions and filters for the option.
|
||||
*
|
||||
* @todo [JRF => testers] Check if the extra actions below would run into problems if an option
|
||||
* is updated early on and if so, change the call to schedule these for a later action on add/update
|
||||
* instead of running them straight away.
|
||||
*/
|
||||
protected function __construct() {
|
||||
parent::__construct();
|
||||
add_action( 'update_option_' . $this->option_name, [ 'WPSEO_Utils', 'clear_cache' ] );
|
||||
add_action( 'init', [ $this, 'end_of_init' ], 999 );
|
||||
|
||||
add_action( 'registered_post_type', [ $this, 'invalidate_enrich_defaults_cache' ] );
|
||||
add_action( 'unregistered_post_type', [ $this, 'invalidate_enrich_defaults_cache' ] );
|
||||
add_action( 'registered_taxonomy', [ $this, 'invalidate_enrich_defaults_cache' ] );
|
||||
add_action( 'unregistered_taxonomy', [ $this, 'invalidate_enrich_defaults_cache' ] );
|
||||
|
||||
add_filter( 'admin_title', [ 'Yoast_Input_Validation', 'add_yoast_admin_document_title_errors' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we can recognize the right action for the double cleaning.
|
||||
*/
|
||||
public function end_of_init() {
|
||||
do_action( 'wpseo_double_clean_titles' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( self::$instance instanceof self ) ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available separator options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_separator_options() {
|
||||
$separators = wp_list_pluck( self::get_separator_option_list(), 'option' );
|
||||
|
||||
/**
|
||||
* Allow altering the array with separator options.
|
||||
*
|
||||
* @api array $separator_options Array with the separator options.
|
||||
*/
|
||||
$filtered_separators = apply_filters( 'wpseo_separator_options', $separators );
|
||||
|
||||
if ( is_array( $filtered_separators ) && $filtered_separators !== [] ) {
|
||||
$separators = array_merge( $separators, $filtered_separators );
|
||||
}
|
||||
|
||||
return $separators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available separator options aria-labels.
|
||||
*
|
||||
* @return array Array with the separator options aria-labels.
|
||||
*/
|
||||
public function get_separator_options_for_display() {
|
||||
$separators = $this->get_separator_options();
|
||||
$separator_list = self::get_separator_option_list();
|
||||
|
||||
$separator_options = [];
|
||||
|
||||
foreach ( $separators as $key => $label ) {
|
||||
$aria_label = isset( $separator_list[ $key ]['label'] ) ? $separator_list[ $key ]['label'] : '';
|
||||
|
||||
$separator_options[ $key ] = [
|
||||
'label' => $label,
|
||||
'aria_label' => $aria_label,
|
||||
];
|
||||
}
|
||||
|
||||
return $separator_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate strings used in the option defaults.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function translate_defaults() {
|
||||
/* translators: 1: Author name; 2: Site name. */
|
||||
$this->defaults['title-author-wpseo'] = sprintf( __( '%1$s, Author at %2$s', 'wordpress-seo' ), '%%name%%', '%%sitename%%' ) . ' %%page%% ';
|
||||
/* translators: %s expands to the search phrase. */
|
||||
$this->defaults['title-search-wpseo'] = sprintf( __( 'You searched for %s', 'wordpress-seo' ), '%%searchphrase%%' ) . ' %%page%% %%sep%% %%sitename%%';
|
||||
$this->defaults['title-404-wpseo'] = __( 'Page not found', 'wordpress-seo' ) . ' %%sep%% %%sitename%%';
|
||||
/* translators: 1: link to post; 2: link to blog. */
|
||||
$this->defaults['rssafter'] = sprintf( __( 'The post %1$s appeared first on %2$s.', 'wordpress-seo' ), '%%POSTLINK%%', '%%BLOGLINK%%' );
|
||||
|
||||
$this->defaults['breadcrumbs-404crumb'] = __( 'Error 404: Page not found', 'wordpress-seo' );
|
||||
$this->defaults['breadcrumbs-archiveprefix'] = __( 'Archives for', 'wordpress-seo' );
|
||||
$this->defaults['breadcrumbs-home'] = __( 'Home', 'wordpress-seo' );
|
||||
$this->defaults['breadcrumbs-searchprefix'] = __( 'You searched for', 'wordpress-seo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dynamically created default options based on available post types and taxonomies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enrich_defaults() {
|
||||
$enriched_defaults = $this->enriched_defaults;
|
||||
if ( $enriched_defaults !== null ) {
|
||||
$this->defaults += $enriched_defaults;
|
||||
return;
|
||||
}
|
||||
|
||||
$enriched_defaults = [];
|
||||
|
||||
/*
|
||||
* Retrieve all the relevant post type and taxonomy arrays.
|
||||
*
|
||||
* WPSEO_Post_Type::get_accessible_post_types() should *not* be used here.
|
||||
* These are the defaults and can be prepared for any public post type.
|
||||
*/
|
||||
$post_type_objects = get_post_types( [ 'public' => true ], 'objects' );
|
||||
|
||||
if ( $post_type_objects ) {
|
||||
/* translators: %s expands to the name of a post type (plural). */
|
||||
$archive = sprintf( __( '%s Archive', 'wordpress-seo' ), '%%pt_plural%%' );
|
||||
|
||||
foreach ( $post_type_objects as $pt ) {
|
||||
$enriched_defaults[ 'title-' . $pt->name ] = '%%title%% %%page%% %%sep%% %%sitename%%'; // Text field.
|
||||
$enriched_defaults[ 'metadesc-' . $pt->name ] = ''; // Text area.
|
||||
$enriched_defaults[ 'noindex-' . $pt->name ] = false;
|
||||
$enriched_defaults[ 'showdate-' . $pt->name ] = false;
|
||||
$enriched_defaults[ 'display-metabox-pt-' . $pt->name ] = true;
|
||||
$enriched_defaults[ 'post_types-' . $pt->name . '-maintax' ] = 0; // Select box.
|
||||
|
||||
if ( ! $pt->_builtin && WPSEO_Post_Type::has_archive( $pt ) ) {
|
||||
$enriched_defaults[ 'title-ptarchive-' . $pt->name ] = $archive . ' %%page%% %%sep%% %%sitename%%'; // Text field.
|
||||
$enriched_defaults[ 'metadesc-ptarchive-' . $pt->name ] = ''; // Text area.
|
||||
$enriched_defaults[ 'bctitle-ptarchive-' . $pt->name ] = ''; // Text field.
|
||||
$enriched_defaults[ 'noindex-ptarchive-' . $pt->name ] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$taxonomy_objects = get_taxonomies( [ 'public' => true ], 'object' );
|
||||
|
||||
if ( $taxonomy_objects ) {
|
||||
/* translators: %s expands to the variable used for term title. */
|
||||
$archives = sprintf( __( '%s Archives', 'wordpress-seo' ), '%%term_title%%' );
|
||||
|
||||
foreach ( $taxonomy_objects as $tax ) {
|
||||
$enriched_defaults[ 'title-tax-' . $tax->name ] = $archives . ' %%page%% %%sep%% %%sitename%%'; // Text field.
|
||||
$enriched_defaults[ 'metadesc-tax-' . $tax->name ] = ''; // Text area.
|
||||
$enriched_defaults[ 'display-metabox-tax-' . $tax->name ] = true;
|
||||
|
||||
$enriched_defaults[ 'noindex-tax-' . $tax->name ] = ( $tax->name === 'post_format' );
|
||||
|
||||
if ( ! $tax->_builtin ) {
|
||||
$enriched_defaults[ 'taxonomy-' . $tax->name . '-ptparent' ] = 0; // Select box;.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->enriched_defaults = $enriched_defaults;
|
||||
$this->defaults += $enriched_defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates enrich_defaults() cache.
|
||||
*
|
||||
* Called from actions:
|
||||
* - (un)registered_post_type
|
||||
* - (un)registered_taxonomy
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function invalidate_enrich_defaults_cache() {
|
||||
$this->enriched_defaults = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array Validated clean value for the option to be saved to the database.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
$allowed_post_types = $this->get_allowed_post_types();
|
||||
|
||||
foreach ( $clean as $key => $value ) {
|
||||
$switch_key = $this->get_switch_key( $key );
|
||||
|
||||
switch ( $switch_key ) {
|
||||
/* Breadcrumbs text fields. */
|
||||
case 'breadcrumbs-404crumb':
|
||||
case 'breadcrumbs-archiveprefix':
|
||||
case 'breadcrumbs-home':
|
||||
case 'breadcrumbs-prefix':
|
||||
case 'breadcrumbs-searchprefix':
|
||||
case 'breadcrumbs-sep':
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = wp_kses_post( $dirty[ $key ] );
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* Text fields.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Covers:
|
||||
* 'title-home-wpseo', 'title-author-wpseo', 'title-archive-wpseo',
|
||||
* 'title-search-wpseo', 'title-404-wpseo'
|
||||
* 'title-' . $pt->name
|
||||
* 'title-ptarchive-' . $pt->name
|
||||
* 'title-tax-' . $tax->name
|
||||
*/
|
||||
case 'website_name':
|
||||
case 'alternate_website_name':
|
||||
case 'title-':
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'company_or_person':
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
if ( in_array( $dirty[ $key ], [ 'company', 'person' ], true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
else {
|
||||
$defaults = $this->get_defaults();
|
||||
$clean[ $key ] = $defaults['company_or_person'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'company_logo':
|
||||
case 'person_logo':
|
||||
$this->validate_url( $key, $dirty, $old, $clean );
|
||||
break;
|
||||
|
||||
/*
|
||||
* Covers:
|
||||
* 'metadesc-home-wpseo', 'metadesc-author-wpseo', 'metadesc-archive-wpseo'
|
||||
* 'metadesc-' . $pt->name
|
||||
* 'metadesc-ptarchive-' . $pt->name
|
||||
* 'metadesc-tax-' . $tax->name
|
||||
* and also:
|
||||
* 'bctitle-ptarchive-' . $pt->name
|
||||
*/
|
||||
case 'metadesc-':
|
||||
case 'bctitle-ptarchive-':
|
||||
case 'company_name':
|
||||
case 'person_name':
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
|
||||
$clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] );
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* Covers: 'rssbefore', 'rssafter'
|
||||
*/
|
||||
case 'rssbefore':
|
||||
case 'rssafter':
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = wp_kses_post( $dirty[ $key ] );
|
||||
}
|
||||
break;
|
||||
|
||||
/* 'post_types-' . $pt->name . '-maintax' fields. */
|
||||
case 'post_types-':
|
||||
$post_type = str_replace( [ 'post_types-', '-maintax' ], '', $key );
|
||||
$taxonomies = get_object_taxonomies( $post_type, 'names' );
|
||||
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
if ( $taxonomies !== [] && in_array( $dirty[ $key ], $taxonomies, true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
elseif ( (string) $dirty[ $key ] === '0' || (string) $dirty[ $key ] === '' ) {
|
||||
$clean[ $key ] = 0;
|
||||
}
|
||||
elseif ( sanitize_title_with_dashes( $dirty[ $key ] ) === $dirty[ $key ] ) {
|
||||
// Allow taxonomies which may not be registered yet.
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
else {
|
||||
if ( isset( $old[ $key ] ) ) {
|
||||
$clean[ $key ] = sanitize_title_with_dashes( $old[ $key ] );
|
||||
}
|
||||
|
||||
/*
|
||||
* @todo [JRF => whomever] Maybe change the untranslated $pt name in the
|
||||
* error message to the nicely translated label ?
|
||||
*/
|
||||
add_settings_error(
|
||||
$this->group_name, // Slug title of the setting.
|
||||
$key, // Suffix-id for the error message box.
|
||||
/* translators: %s expands to a post type. */
|
||||
sprintf( __( 'Please select a valid taxonomy for post type "%s"', 'wordpress-seo' ), $post_type ), // The error message.
|
||||
'error' // Message type.
|
||||
);
|
||||
}
|
||||
}
|
||||
elseif ( isset( $old[ $key ] ) ) {
|
||||
$clean[ $key ] = sanitize_title_with_dashes( $old[ $key ] );
|
||||
}
|
||||
unset( $taxonomies, $post_type );
|
||||
break;
|
||||
|
||||
/* 'taxonomy-' . $tax->name . '-ptparent' fields. */
|
||||
case 'taxonomy-':
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
if ( $allowed_post_types !== [] && in_array( $dirty[ $key ], $allowed_post_types, true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
elseif ( (string) $dirty[ $key ] === '0' || (string) $dirty[ $key ] === '' ) {
|
||||
$clean[ $key ] = 0;
|
||||
}
|
||||
elseif ( sanitize_key( $dirty[ $key ] ) === $dirty[ $key ] ) {
|
||||
// Allow taxonomies which may not be registered yet.
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
else {
|
||||
if ( isset( $old[ $key ] ) ) {
|
||||
$clean[ $key ] = sanitize_key( $old[ $key ] );
|
||||
}
|
||||
|
||||
/*
|
||||
* @todo [JRF =? whomever] Maybe change the untranslated $tax name in the
|
||||
* error message to the nicely translated label ?
|
||||
*/
|
||||
$tax = str_replace( [ 'taxonomy-', '-ptparent' ], '', $key );
|
||||
add_settings_error(
|
||||
$this->group_name, // Slug title of the setting.
|
||||
'_' . $tax, // Suffix-ID for the error message box.
|
||||
/* translators: %s expands to a taxonomy slug. */
|
||||
sprintf( __( 'Please select a valid post type for taxonomy "%s"', 'wordpress-seo' ), $tax ), // The error message.
|
||||
'error' // Message type.
|
||||
);
|
||||
unset( $tax );
|
||||
}
|
||||
}
|
||||
elseif ( isset( $old[ $key ] ) ) {
|
||||
$clean[ $key ] = sanitize_key( $old[ $key ] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'company_or_person_user_id':
|
||||
case 'company_logo_id':
|
||||
case 'person_logo_id':
|
||||
case 'title_test': /* Integer field - not in form. */
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
$int = WPSEO_Utils::validate_int( $dirty[ $key ] );
|
||||
if ( $int !== false && $int >= 0 ) {
|
||||
$clean[ $key ] = $int;
|
||||
}
|
||||
}
|
||||
elseif ( isset( $old[ $key ] ) ) {
|
||||
$int = WPSEO_Utils::validate_int( $old[ $key ] );
|
||||
if ( $int !== false && $int >= 0 ) {
|
||||
$clean[ $key ] = $int;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* Separator field - Radio. */
|
||||
case 'separator':
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
|
||||
|
||||
// Get separator fields.
|
||||
$separator_fields = $this->get_separator_options();
|
||||
|
||||
// Check if the given separator exists.
|
||||
if ( isset( $separator_fields[ $dirty[ $key ] ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* Boolean fields.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Covers:
|
||||
* 'noindex-author-wpseo', 'noindex-author-noposts-wpseo', 'noindex-archive-wpseo'
|
||||
* 'noindex-' . $pt->name
|
||||
* 'noindex-ptarchive-' . $pt->name
|
||||
* 'noindex-tax-' . $tax->name
|
||||
* 'forcerewritetitle':
|
||||
* 'noodp':
|
||||
* 'noydir':
|
||||
* 'disable-author':
|
||||
* 'disable-date':
|
||||
* 'disable-post_format';
|
||||
* 'noindex-'
|
||||
* 'showdate-'
|
||||
* 'showdate-'. $pt->name
|
||||
* 'display-metabox-pt-'
|
||||
* 'display-metabox-pt-'. $pt->name
|
||||
* 'display-metabox-tax-'
|
||||
* 'display-metabox-tax-' . $tax->name
|
||||
* 'breadcrumbs-display-blog-page'
|
||||
* 'breadcrumbs-boldlast'
|
||||
* 'breadcrumbs-enable'
|
||||
* 'stripcategorybase'
|
||||
* 'is-media-purge-relevant'
|
||||
*/
|
||||
default:
|
||||
$clean[ $key ] = ( isset( $dirty[ $key ] ) ? WPSEO_Utils::validate_bool( $dirty[ $key ] ) : false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of the allowed post types as breadcrumb parent for a taxonomy.
|
||||
* Helper method for validation.
|
||||
*
|
||||
* {@internal Don't make static as new types may still be registered.}}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_allowed_post_types() {
|
||||
$allowed_post_types = [];
|
||||
|
||||
/*
|
||||
* WPSEO_Post_Type::get_accessible_post_types() should *not* be used here.
|
||||
*/
|
||||
$post_types = get_post_types( [ 'public' => true ], 'objects' );
|
||||
|
||||
if ( get_option( 'show_on_front' ) === 'page' && get_option( 'page_for_posts' ) > 0 ) {
|
||||
$allowed_post_types[] = 'post';
|
||||
}
|
||||
|
||||
if ( is_array( $post_types ) && $post_types !== [] ) {
|
||||
foreach ( $post_types as $type ) {
|
||||
if ( WPSEO_Post_Type::has_archive( $type ) ) {
|
||||
$allowed_post_types[] = $type->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $allowed_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a given option value.
|
||||
*
|
||||
* @param array $option_value Old (not merged with defaults or filtered) option value to
|
||||
* clean according to the rules for this option.
|
||||
* @param string $current_version Optional. Version from which to upgrade, if not set,
|
||||
* version specific upgrades will be disregarded.
|
||||
* @param array $all_old_option_values Optional. Only used when importing old options to have
|
||||
* access to the real old values, in contrast to the saved ones.
|
||||
*
|
||||
* @return array Cleaned option.
|
||||
*/
|
||||
protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
|
||||
static $original = null;
|
||||
|
||||
// Double-run this function to ensure renaming of the taxonomy options will work.
|
||||
if ( ! isset( $original )
|
||||
&& has_action( 'wpseo_double_clean_titles', [ $this, 'clean' ] ) === false
|
||||
) {
|
||||
add_action( 'wpseo_double_clean_titles', [ $this, 'clean' ] );
|
||||
$original = $option_value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Move options from very old option to this one.
|
||||
*
|
||||
* {@internal Don't rename to the 'current' names straight away as that would prevent
|
||||
* the rename/unset combi below from working.}}
|
||||
*
|
||||
* @todo [JRF] Maybe figure out a smarter way to deal with this.
|
||||
*/
|
||||
$old_option = null;
|
||||
if ( isset( $all_old_option_values ) ) {
|
||||
// Ok, we have an import.
|
||||
if ( isset( $all_old_option_values['wpseo_indexation'] ) && is_array( $all_old_option_values['wpseo_indexation'] ) && $all_old_option_values['wpseo_indexation'] !== [] ) {
|
||||
$old_option = $all_old_option_values['wpseo_indexation'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$old_option = get_option( 'wpseo_indexation' );
|
||||
}
|
||||
if ( is_array( $old_option ) && $old_option !== [] ) {
|
||||
$move = [
|
||||
'noindexauthor' => 'noindex-author',
|
||||
'disableauthor' => 'disable-author',
|
||||
'noindexdate' => 'noindex-archive',
|
||||
'noindexcat' => 'noindex-category',
|
||||
'noindextag' => 'noindex-post_tag',
|
||||
'noindexpostformat' => 'noindex-post_format',
|
||||
];
|
||||
foreach ( $move as $old => $new ) {
|
||||
if ( isset( $old_option[ $old ] ) && ! isset( $option_value[ $new ] ) ) {
|
||||
$option_value[ $new ] = $old_option[ $old ];
|
||||
}
|
||||
}
|
||||
unset( $move, $old, $new );
|
||||
}
|
||||
unset( $old_option );
|
||||
|
||||
|
||||
// Fix wrongness created by buggy version 1.2.2.
|
||||
if ( isset( $option_value['title-home'] ) && $option_value['title-home'] === '%%sitename%% - %%sitedesc%% - 12345' ) {
|
||||
$option_value['title-home-wpseo'] = '%%sitename%% - %%sitedesc%%';
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Renaming these options to avoid ever overwritting these if a (bloody stupid) user /
|
||||
* programmer would use any of the following as a custom post type or custom taxonomy:
|
||||
* 'home', 'author', 'archive', 'search', '404', 'subpages'.
|
||||
*
|
||||
* Similarly, renaming the tax options to avoid a custom post type and a taxonomy
|
||||
* with the same name occupying the same option.
|
||||
*/
|
||||
$rename = [
|
||||
'title-home' => 'title-home-wpseo',
|
||||
'title-author' => 'title-author-wpseo',
|
||||
'title-archive' => 'title-archive-wpseo',
|
||||
'title-search' => 'title-search-wpseo',
|
||||
'title-404' => 'title-404-wpseo',
|
||||
'metadesc-home' => 'metadesc-home-wpseo',
|
||||
'metadesc-author' => 'metadesc-author-wpseo',
|
||||
'metadesc-archive' => 'metadesc-archive-wpseo',
|
||||
'noindex-author' => 'noindex-author-wpseo',
|
||||
'noindex-archive' => 'noindex-archive-wpseo',
|
||||
];
|
||||
foreach ( $rename as $old => $new ) {
|
||||
if ( isset( $option_value[ $old ] ) && ! isset( $option_value[ $new ] ) ) {
|
||||
$option_value[ $new ] = $option_value[ $old ];
|
||||
unset( $option_value[ $old ] );
|
||||
}
|
||||
}
|
||||
unset( $rename, $old, $new );
|
||||
|
||||
|
||||
/*
|
||||
* {@internal This clean-up action can only be done effectively once the taxonomies
|
||||
* and post_types have been registered, i.e. at the end of the init action.}}
|
||||
*/
|
||||
if ( isset( $original ) && current_filter() === 'wpseo_double_clean_titles' || did_action( 'wpseo_double_clean_titles' ) > 0 ) {
|
||||
$rename = [
|
||||
'title-' => 'title-tax-',
|
||||
'metadesc-' => 'metadesc-tax-',
|
||||
'noindex-' => 'noindex-tax-',
|
||||
'tax-hideeditbox-' => 'hideeditbox-tax-',
|
||||
|
||||
];
|
||||
|
||||
$taxonomy_names = get_taxonomies( [ 'public' => true ], 'names' );
|
||||
$post_type_names = get_post_types( [ 'public' => true ], 'names' );
|
||||
$defaults = $this->get_defaults();
|
||||
if ( $taxonomy_names !== [] ) {
|
||||
foreach ( $taxonomy_names as $tax ) {
|
||||
foreach ( $rename as $old_prefix => $new_prefix ) {
|
||||
if (
|
||||
( isset( $original[ $old_prefix . $tax ] ) && ! isset( $original[ $new_prefix . $tax ] ) )
|
||||
&& ( ! isset( $option_value[ $new_prefix . $tax ] )
|
||||
|| ( isset( $option_value[ $new_prefix . $tax ] )
|
||||
&& $option_value[ $new_prefix . $tax ] === $defaults[ $new_prefix . $tax ] ) )
|
||||
) {
|
||||
$option_value[ $new_prefix . $tax ] = $original[ $old_prefix . $tax ];
|
||||
|
||||
/*
|
||||
* Check if there is a cpt with the same name as the tax,
|
||||
* if so, we should make sure that the old setting hasn't been removed.
|
||||
*/
|
||||
if ( ! isset( $post_type_names[ $tax ] ) && isset( $option_value[ $old_prefix . $tax ] ) ) {
|
||||
unset( $option_value[ $old_prefix . $tax ] );
|
||||
}
|
||||
else {
|
||||
if ( isset( $post_type_names[ $tax ] ) && ! isset( $option_value[ $old_prefix . $tax ] ) ) {
|
||||
$option_value[ $old_prefix . $tax ] = $original[ $old_prefix . $tax ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( $old_prefix === 'tax-hideeditbox-' ) {
|
||||
unset( $option_value[ $old_prefix . $tax ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $rename, $taxonomy_names, $post_type_names, $defaults, $tax, $old_prefix, $new_prefix );
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the values of the variable option key options are cleaned as they
|
||||
* may be retained and would not be cleaned/validated then.
|
||||
*/
|
||||
if ( is_array( $option_value ) && $option_value !== [] ) {
|
||||
foreach ( $option_value as $key => $value ) {
|
||||
$switch_key = $this->get_switch_key( $key );
|
||||
|
||||
// Similar to validation routine - any changes made there should be made here too.
|
||||
switch ( $switch_key ) {
|
||||
/* Text fields. */
|
||||
case 'title-':
|
||||
case 'metadesc-':
|
||||
case 'bctitle-ptarchive-':
|
||||
$option_value[ $key ] = WPSEO_Utils::sanitize_text_field( $value );
|
||||
break;
|
||||
|
||||
case 'separator':
|
||||
if ( ! array_key_exists( $value, $this->get_separator_options() ) ) {
|
||||
$option_value[ $key ] = false;
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* Boolean fields.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Covers:
|
||||
* 'noindex-'
|
||||
* 'showdate-'
|
||||
* 'hideeditbox-'
|
||||
*/
|
||||
default:
|
||||
$option_value[ $key ] = WPSEO_Utils::validate_bool( $value );
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset( $key, $value, $switch_key );
|
||||
}
|
||||
|
||||
return $option_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that any set option values relating to post_types and/or taxonomies are retained,
|
||||
* even when that post_type or taxonomy may not yet have been registered.
|
||||
*
|
||||
* {@internal Overrule the abstract class version of this to make sure one extra renamed
|
||||
* variable key does not get removed. IMPORTANT: keep this method in line with
|
||||
* the parent on which it is based!}}
|
||||
*
|
||||
* @param array $dirty Original option as retrieved from the database.
|
||||
* @param array $clean Filtered option where any options which shouldn't be in our option
|
||||
* have already been removed and any options which weren't set
|
||||
* have been set to their defaults.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function retain_variable_keys( $dirty, $clean ) {
|
||||
if ( ( is_array( $this->variable_array_key_patterns ) && $this->variable_array_key_patterns !== [] ) && ( is_array( $dirty ) && $dirty !== [] ) ) {
|
||||
|
||||
// Add the extra pattern.
|
||||
$patterns = $this->variable_array_key_patterns;
|
||||
$patterns[] = 'tax-hideeditbox-';
|
||||
|
||||
/**
|
||||
* Allow altering the array with variable array key patterns.
|
||||
*
|
||||
* @api array $patterns Array with the variable array key patterns.
|
||||
*/
|
||||
$patterns = apply_filters( 'wpseo_option_titles_variable_array_key_patterns', $patterns );
|
||||
|
||||
foreach ( $dirty as $key => $value ) {
|
||||
|
||||
// Do nothing if already in filtered option array.
|
||||
if ( isset( $clean[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $patterns as $pattern ) {
|
||||
if ( strpos( $key, $pattern ) === 0 ) {
|
||||
$clean[ $key ] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of separator options.
|
||||
*
|
||||
* @return array An array of the separator options.
|
||||
*/
|
||||
protected static function get_separator_option_list() {
|
||||
$separators = [
|
||||
'sc-dash' => [
|
||||
'option' => '-',
|
||||
'label' => __( 'Dash', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-ndash' => [
|
||||
'option' => '–',
|
||||
'label' => __( 'En dash', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-mdash' => [
|
||||
'option' => '—',
|
||||
'label' => __( 'Em dash', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-colon' => [
|
||||
'option' => ':',
|
||||
'label' => __( 'Colon', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-middot' => [
|
||||
'option' => '·',
|
||||
'label' => __( 'Middle dot', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-bull' => [
|
||||
'option' => '•',
|
||||
'label' => __( 'Bullet', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-star' => [
|
||||
'option' => '*',
|
||||
'label' => __( 'Asterisk', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-smstar' => [
|
||||
'option' => '⋆',
|
||||
'label' => __( 'Low asterisk', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-pipe' => [
|
||||
'option' => '|',
|
||||
'label' => __( 'Vertical bar', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-tilde' => [
|
||||
'option' => '~',
|
||||
'label' => __( 'Small tilde', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-laquo' => [
|
||||
'option' => '«',
|
||||
'label' => __( 'Left angle quotation mark', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-raquo' => [
|
||||
'option' => '»',
|
||||
'label' => __( 'Right angle quotation mark', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-lt' => [
|
||||
'option' => '<',
|
||||
'label' => __( 'Less than sign', 'wordpress-seo' ),
|
||||
],
|
||||
'sc-gt' => [
|
||||
'option' => '>',
|
||||
'label' => __( 'Greater than sign', 'wordpress-seo' ),
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Allows altering the separator options array.
|
||||
*
|
||||
* @api array $separators Array with the separator options.
|
||||
*/
|
||||
$separator_list = apply_filters( 'wpseo_separator_option_list', $separators );
|
||||
|
||||
if ( ! is_array( $separator_list ) ) {
|
||||
return $separators;
|
||||
}
|
||||
|
||||
return $separator_list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals\Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Option: wpseo.
|
||||
*/
|
||||
class WPSEO_Option_Wpseo extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo';
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* {@internal Shouldn't be requested directly, use $this->get_defaults();}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [
|
||||
// Non-form fields, set via (ajax) function.
|
||||
'ms_defaults_set' => false,
|
||||
// Non-form field, should only be set via validation routine.
|
||||
'version' => '', // Leave default as empty to ensure activation/upgrade works.
|
||||
|
||||
// Form fields.
|
||||
'disableadvanced_meta' => true,
|
||||
'onpage_indexability' => true,
|
||||
'baiduverify' => '', // Text field.
|
||||
'googleverify' => '', // Text field.
|
||||
'msverify' => '', // Text field.
|
||||
'yandexverify' => '',
|
||||
'site_type' => '', // List of options.
|
||||
'has_multiple_authors' => '',
|
||||
'environment_type' => '',
|
||||
'content_analysis_active' => true,
|
||||
'keyword_analysis_active' => true,
|
||||
'enable_admin_bar_menu' => true,
|
||||
'enable_cornerstone_content' => true,
|
||||
'enable_xml_sitemap' => true,
|
||||
'enable_text_link_counter' => true,
|
||||
'show_onboarding_notice' => false,
|
||||
'first_activated_on' => false,
|
||||
'myyoast-oauth' => [
|
||||
'config' => [
|
||||
'clientId' => null,
|
||||
'secret' => null,
|
||||
],
|
||||
'access_tokens' => [],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Sub-options which should not be overloaded with multi-site defaults.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $ms_exclude = [
|
||||
/* Privacy. */
|
||||
'baiduverify',
|
||||
'googleverify',
|
||||
'msverify',
|
||||
'yandexverify',
|
||||
];
|
||||
|
||||
/**
|
||||
* Possible values for the site_type option.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $site_types = [
|
||||
'',
|
||||
'blog',
|
||||
'shop',
|
||||
'news',
|
||||
'smallBusiness',
|
||||
'corporateOther',
|
||||
'personalOther',
|
||||
];
|
||||
|
||||
/**
|
||||
* Possible environment types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $environment_types = [
|
||||
'',
|
||||
'production',
|
||||
'staging',
|
||||
'development',
|
||||
];
|
||||
|
||||
/**
|
||||
* Possible has_multiple_authors options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $has_multiple_authors_options = [
|
||||
'',
|
||||
true,
|
||||
false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Name for an option higher in the hierarchy to override setting access.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $override_option_name = 'wpseo_ms';
|
||||
|
||||
/**
|
||||
* Add the actions and filters for the option.
|
||||
*
|
||||
* @todo [JRF => testers] Check if the extra actions below would run into problems if an option
|
||||
* is updated early on and if so, change the call to schedule these for a later action on add/update
|
||||
* instead of running them straight away.
|
||||
*
|
||||
* @return \WPSEO_Option_Wpseo
|
||||
*/
|
||||
protected function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
/* Clear the cache on update/add. */
|
||||
add_action( 'add_option_' . $this->option_name, [ 'WPSEO_Utils', 'clear_cache' ] );
|
||||
add_action( 'update_option_' . $this->option_name, [ 'WPSEO_Utils', 'clear_cache' ] );
|
||||
|
||||
add_filter( 'admin_title', [ 'Yoast_Input_Validation', 'add_yoast_admin_document_title_errors' ] );
|
||||
|
||||
/**
|
||||
* Filter the `wpseo` option defaults.
|
||||
*
|
||||
* @param array $defaults Array the defaults for the `wpseo` option attributes.
|
||||
*/
|
||||
$this->defaults = apply_filters( 'wpseo_option_wpseo_defaults', $this->defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( self::$instance instanceof self ) ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filters to make sure that the option is merged with its defaults before being returned.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_option_filters() {
|
||||
parent::add_option_filters();
|
||||
|
||||
list( $hookname, $callback, $priority ) = $this->get_verify_features_option_filter_hook();
|
||||
|
||||
if ( has_filter( $hookname, $callback ) === false ) {
|
||||
add_filter( $hookname, $callback, $priority );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the option filters.
|
||||
* Called from the clean_up methods to make sure we retrieve the original old option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_option_filters() {
|
||||
parent::remove_option_filters();
|
||||
|
||||
list( $hookname, $callback, $priority ) = $this->get_verify_features_option_filter_hook();
|
||||
|
||||
remove_filter( $hookname, $callback, $priority );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filters to make sure that the option default is returned if the option is not set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_default_filters() {
|
||||
parent::add_default_filters();
|
||||
|
||||
list( $hookname, $callback, $priority ) = $this->get_verify_features_default_option_filter_hook();
|
||||
|
||||
if ( has_filter( $hookname, $callback ) === false ) {
|
||||
add_filter( $hookname, $callback, $priority );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default filters.
|
||||
* Called from the validate() method to prevent failure to add new options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_default_filters() {
|
||||
parent::remove_default_filters();
|
||||
|
||||
list( $hookname, $callback, $priority ) = $this->get_verify_features_default_option_filter_hook();
|
||||
|
||||
remove_filter( $hookname, $callback, $priority );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array Validated clean value for the option to be saved to the database.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
|
||||
foreach ( $clean as $key => $value ) {
|
||||
switch ( $key ) {
|
||||
case 'version':
|
||||
$clean[ $key ] = WPSEO_VERSION;
|
||||
break;
|
||||
|
||||
/* Verification strings. */
|
||||
case 'baiduverify':
|
||||
case 'googleverify':
|
||||
case 'msverify':
|
||||
case 'yandexverify':
|
||||
$this->validate_verification_string( $key, $dirty, $old, $clean );
|
||||
break;
|
||||
|
||||
/*
|
||||
* Boolean dismiss warnings - not fields - may not be in form
|
||||
* (and don't need to be either as long as the default is false).
|
||||
*/
|
||||
case 'ms_defaults_set':
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = WPSEO_Utils::validate_bool( $dirty[ $key ] );
|
||||
}
|
||||
elseif ( isset( $old[ $key ] ) ) {
|
||||
$clean[ $key ] = WPSEO_Utils::validate_bool( $old[ $key ] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'site_type':
|
||||
$clean[ $key ] = $old[ $key ];
|
||||
if ( isset( $dirty[ $key ] ) && in_array( $dirty[ $key ], $this->site_types, true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'environment_type':
|
||||
$clean[ $key ] = $old[ $key ];
|
||||
if ( isset( $dirty[ $key ] ) && in_array( $dirty[ $key ], $this->environment_types, true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'has_multiple_authors':
|
||||
$clean[ $key ] = $old[ $key ];
|
||||
if ( isset( $dirty[ $key ] ) && in_array( $dirty[ $key ], $this->has_multiple_authors_options, true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'first_activated_on':
|
||||
$clean[ $key ] = false;
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
if ( $dirty[ $key ] === false || WPSEO_Utils::validate_int( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'myyoast_oauth':
|
||||
$clean[ $key ] = $old[ $key ];
|
||||
|
||||
if ( isset( $dirty[ $key ] ) ) {
|
||||
$myyoast_oauth = $dirty[ $key ];
|
||||
if ( ! is_array( $myyoast_oauth ) ) {
|
||||
$myyoast_oauth = json_decode( $dirty[ $key ], true );
|
||||
}
|
||||
|
||||
if ( is_array( $myyoast_oauth ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
/*
|
||||
* Boolean (checkbox) fields.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Covers:
|
||||
* 'disableadvanced_meta'
|
||||
* 'yoast_tracking'
|
||||
*/
|
||||
default:
|
||||
$clean[ $key ] = ( isset( $dirty[ $key ] ) ? WPSEO_Utils::validate_bool( $dirty[ $key ] ) : false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the feature variables are turned off if the network is configured so.
|
||||
*
|
||||
* @param mixed $options Value of the option to be returned. Typically an array.
|
||||
*
|
||||
* @return mixed Filtered $options value.
|
||||
*/
|
||||
public function verify_features_against_network( $options = [] ) {
|
||||
if ( ! is_array( $options ) || empty( $options ) ) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
// For the feature variables, set their values to off in case they are disabled.
|
||||
$feature_vars = [
|
||||
'disableadvanced_meta' => false,
|
||||
'onpage_indexability' => false,
|
||||
'content_analysis_active' => false,
|
||||
'keyword_analysis_active' => false,
|
||||
'enable_admin_bar_menu' => false,
|
||||
'enable_cornerstone_content' => false,
|
||||
'enable_xml_sitemap' => false,
|
||||
'enable_text_link_counter' => false,
|
||||
];
|
||||
|
||||
// We can reuse this logic from the base class with the above defaults to parse with the correct feature values.
|
||||
$options = $this->prevent_disabled_options_update( $options, $feature_vars );
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filter hook name and callback for adjusting the retrieved option value
|
||||
* against the network-allowed features.
|
||||
*
|
||||
* @return array Array where the first item is the hook name, the second is the hook callback,
|
||||
* and the third is the hook priority.
|
||||
*/
|
||||
protected function get_verify_features_option_filter_hook() {
|
||||
return [
|
||||
"option_{$this->option_name}",
|
||||
[ $this, 'verify_features_against_network' ],
|
||||
11,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filter hook name and callback for adjusting the default option value against the network-allowed features.
|
||||
*
|
||||
* @return array Array where the first item is the hook name, the second is the hook callback,
|
||||
* and the third is the hook priority.
|
||||
*/
|
||||
protected function get_verify_features_default_option_filter_hook() {
|
||||
return [
|
||||
"default_option_{$this->option_name}",
|
||||
[ $this, 'verify_features_against_network' ],
|
||||
11,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a given option value.
|
||||
*
|
||||
* @param array $option_value Old (not merged with defaults or filtered) option value to
|
||||
* clean according to the rules for this option.
|
||||
* @param string $current_version Optional. Version from which to upgrade, if not set,
|
||||
* version specific upgrades will be disregarded.
|
||||
* @param array $all_old_option_values Optional. Only used when importing old options to have
|
||||
* access to the real old values, in contrast to the saved ones.
|
||||
*
|
||||
* @return array Cleaned option.
|
||||
*/
|
||||
protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
|
||||
return $option_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,911 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals\Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* This abstract class and it's concrete classes implement defaults and value validation for
|
||||
* all WPSEO options and subkeys within options.
|
||||
*
|
||||
* Some guidelines:
|
||||
* [Retrieving options]
|
||||
* - Use the normal get_option() to retrieve an option. You will receive a complete array for the option.
|
||||
* Any subkeys which were not set, will have their default values in place.
|
||||
* - In other words, you will normally not have to check whether a subkey isset() as they will *always* be set.
|
||||
* They will also *always* be of the correct variable type.
|
||||
* The only exception to this are the options with variable option names based on post_type or taxonomy
|
||||
* as those will not always be available before the taxonomy/post_type is registered.
|
||||
* (they will be available if a value was set, they won't be if it wasn't as the class won't know
|
||||
* that a default needs to be injected).
|
||||
*
|
||||
* [Updating/Adding options]
|
||||
* - For multisite site_options, please use the WPSEO_Options::update_site_option() method.
|
||||
* - For normal options, use the normal add/update_option() functions. As long a the classes here
|
||||
* are instantiated, validation for all options and their subkeys will be automatic.
|
||||
* - On (succesfull) update of a couple of options, certain related actions will be run automatically.
|
||||
* Some examples:
|
||||
* - on change of wpseo[yoast_tracking], the cron schedule will be adjusted accordingly
|
||||
* - on change of wpseo and wpseo_title, some caches will be cleared
|
||||
*
|
||||
* [Important information about add/updating/changing these classes]
|
||||
* - Make sure that option array key names are unique across options. The WPSEO_Options::get_all()
|
||||
* method merges most options together. If any of them have non-unique names, even if they
|
||||
* are in a different option, they *will* overwrite each other.
|
||||
* - When you add a new array key in an option: make sure you add proper defaults and add the key
|
||||
* to the validation routine in the proper place or add a new validation case.
|
||||
* You don't need to do any upgrading as any option returned will always be merged with the
|
||||
* defaults, so new options will automatically be available.
|
||||
* If the default value is a string which need translating, add this to the concrete class
|
||||
* translate_defaults() method.
|
||||
* - When you remove an array key from an option: if it's important that the option is really removed,
|
||||
* add the WPSEO_Option::clean_up( $option_name ) method to the upgrade run.
|
||||
* This will re-save the option and automatically remove the array key no longer in existance.
|
||||
* - When you rename a sub-option: add it to the clean_option() routine and run that in the upgrade run.
|
||||
* - When you change the default for an option sub-key, make sure you verify that the validation routine will
|
||||
* still work the way it should.
|
||||
* Example: changing a default from '' (empty string) to 'text' with a validation routine with tests
|
||||
* for an empty string will prevent a user from saving an empty string as the real value. So the
|
||||
* test for '' with the validation routine would have to be removed in that case.
|
||||
* - If an option needs specific actions different from defined in this abstract class, you can just overrule
|
||||
* a method by defining it in the concrete class.
|
||||
*
|
||||
* @todo [JRF => testers] Double check that validation will not cause errors when called
|
||||
* from upgrade routine (some of the WP functions may not yet be available).
|
||||
*/
|
||||
abstract class WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Prefix for override option keys that allow or disallow the option key of the same name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ALLOW_KEY_PREFIX = 'allow_';
|
||||
|
||||
/**
|
||||
* Option name - MUST be set in concrete class and set to public.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $option_name;
|
||||
|
||||
/**
|
||||
* Option group name for use in settings forms.
|
||||
*
|
||||
* Will be set automagically if not set in concrete class (i.e.
|
||||
* if it confirm to the normal pattern 'yoast' . $option_name . 'options',
|
||||
* only set in conrete class if it doesn't).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $group_name;
|
||||
|
||||
/**
|
||||
* Whether to include the option in the return for WPSEO_Options::get_all().
|
||||
*
|
||||
* Also determines which options are copied over for ms_(re)set_blog().
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $include_in_all = true;
|
||||
|
||||
/**
|
||||
* Whether this option is only for when the install is multisite.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $multisite_only = false;
|
||||
|
||||
/**
|
||||
* Array of defaults for the option - MUST be set in concrete class.
|
||||
*
|
||||
* Shouldn't be requested directly, use $this->get_defaults();
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults;
|
||||
|
||||
/**
|
||||
* Array of variable option name patterns for the option - if any -.
|
||||
*
|
||||
* Set this when the option contains array keys which vary based on post_type
|
||||
* or taxonomy.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $variable_array_key_patterns;
|
||||
|
||||
/**
|
||||
* Array of sub-options which should not be overloaded with multi-site defaults.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $ms_exclude = [];
|
||||
|
||||
/**
|
||||
* Name for an option higher in the hierarchy to override setting access.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $override_option_name;
|
||||
|
||||
/**
|
||||
* Instance of this class.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
|
||||
/* *********** INSTANTIATION METHODS *********** */
|
||||
|
||||
/**
|
||||
* Add all the actions and filters for the option.
|
||||
*
|
||||
* @return \WPSEO_Option
|
||||
*/
|
||||
protected function __construct() {
|
||||
|
||||
/* Add filters which get applied to the get_options() results. */
|
||||
$this->add_default_filters(); // Return defaults if option not set.
|
||||
$this->add_option_filters(); // Merge with defaults if option *is* set.
|
||||
|
||||
|
||||
if ( $this->multisite_only !== true ) {
|
||||
/**
|
||||
* The option validation routines remove the default filters to prevent failing
|
||||
* to insert an option if it's new. Let's add them back afterwards.
|
||||
*/
|
||||
add_action( 'add_option', [ $this, 'add_default_filters' ] ); // Adding back after INSERT.
|
||||
|
||||
add_action( 'update_option', [ $this, 'add_default_filters' ] );
|
||||
|
||||
// Refills the cache when the option has been updated.
|
||||
add_action( 'update_option_' . $this->option_name, [ 'WPSEO_Options', 'clear_cache' ], 10 );
|
||||
}
|
||||
elseif ( is_multisite() ) {
|
||||
/*
|
||||
* The option validation routines remove the default filters to prevent failing
|
||||
* to insert an option if it's new. Let's add them back afterwards.
|
||||
*
|
||||
* For site_options, this method is not foolproof as these actions are not fired
|
||||
* on an insert/update failure. Please use the WPSEO_Options::update_site_option() method
|
||||
* for updating site options to make sure the filters are in place.
|
||||
*/
|
||||
add_action( 'add_site_option_' . $this->option_name, [ $this, 'add_default_filters' ] );
|
||||
add_action( 'update_site_option_' . $this->option_name, [ $this, 'add_default_filters' ] );
|
||||
|
||||
// Refills the cache when the option has been updated.
|
||||
add_action( 'update_site_option_' . $this->option_name, [ 'WPSEO_Options', 'clear_cache' ], 1, 0 );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Make sure the option will always get validated, independently of register_setting()
|
||||
* (only available on back-end).
|
||||
*/
|
||||
add_filter( 'sanitize_option_' . $this->option_name, [ $this, 'validate' ] );
|
||||
|
||||
// Flushes the rewrite rules when option is updated.
|
||||
add_action( 'update_option_' . $this->option_name, [ 'WPSEO_Utils', 'clear_rewrites' ] );
|
||||
|
||||
/* Register our option for the admin pages */
|
||||
add_action( 'admin_init', [ $this, 'register_setting' ] );
|
||||
|
||||
|
||||
/* Set option group name if not given */
|
||||
if ( ! isset( $this->group_name ) || $this->group_name === '' ) {
|
||||
$this->group_name = 'yoast_' . $this->option_name . '_options';
|
||||
}
|
||||
|
||||
/* Translate some defaults as early as possible - textdomain is loaded in init on priority 1. */
|
||||
if ( method_exists( $this, 'translate_defaults' ) ) {
|
||||
add_action( 'init', [ $this, 'translate_defaults' ], 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich defaults once custom post types and taxonomies have been registered
|
||||
* which is normally done on the init action.
|
||||
*
|
||||
* @todo [JRF/testers] Verify that none of the options which are only available after
|
||||
* enrichment are used before the enriching.
|
||||
*/
|
||||
if ( method_exists( $this, 'enrich_defaults' ) ) {
|
||||
add_action( 'init', [ $this, 'enrich_defaults' ], 99 );
|
||||
}
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* All concrete classes *must* contain the get_instance method.
|
||||
*
|
||||
* {@internal Unfortunately I can't define it as an abstract as it also *has* to be static...}}
|
||||
*/
|
||||
// abstract protected static function get_instance();
|
||||
|
||||
|
||||
/**
|
||||
* Concrete classes *may* contain a translate_defaults method.
|
||||
*/
|
||||
// abstract public function translate_defaults();
|
||||
|
||||
|
||||
/**
|
||||
* Concrete classes *may* contain a enrich_defaults method to add additional defaults once
|
||||
* all post_types and taxonomies have been registered.
|
||||
*/
|
||||
// abstract public function enrich_defaults();
|
||||
|
||||
/* *********** METHODS INFLUENCING get_option() *********** */
|
||||
|
||||
/**
|
||||
* Add filters to make sure that the option default is returned if the option is not set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_default_filters() {
|
||||
// Don't change, needs to check for false as could return prio 0 which would evaluate to false.
|
||||
if ( has_filter( 'default_option_' . $this->option_name, [ $this, 'get_defaults' ] ) === false ) {
|
||||
add_filter( 'default_option_' . $this->option_name, [ $this, 'get_defaults' ] );
|
||||
}
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Validate webmaster tools & Pinterest verification strings.
|
||||
*
|
||||
* @param string $key Key to check, by type of service.
|
||||
* @param array $dirty Dirty data with the new values.
|
||||
* @param array $old Old data.
|
||||
* @param array $clean Clean data by reference, normally the default values.
|
||||
*/
|
||||
public function validate_verification_string( $key, $dirty, $old, &$clean ) {
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
|
||||
$meta = $dirty[ $key ];
|
||||
if ( strpos( $meta, 'content=' ) ) {
|
||||
// Make sure we only have the real key, not a complete meta tag.
|
||||
preg_match( '`content=([\'"])?([^\'"> ]+)(?:\1|[ />])`', $meta, $match );
|
||||
if ( isset( $match[2] ) ) {
|
||||
$meta = $match[2];
|
||||
}
|
||||
unset( $match );
|
||||
}
|
||||
|
||||
$meta = sanitize_text_field( $meta );
|
||||
if ( $meta !== '' ) {
|
||||
$regex = '`^[A-Fa-f0-9_-]+$`';
|
||||
$service = '';
|
||||
|
||||
switch ( $key ) {
|
||||
case 'baiduverify':
|
||||
$regex = '`^[A-Za-z0-9_-]+$`';
|
||||
$service = 'Baidu Webmaster tools';
|
||||
break;
|
||||
|
||||
case 'googleverify':
|
||||
$regex = '`^[A-Za-z0-9_-]+$`';
|
||||
$service = 'Google Webmaster tools';
|
||||
break;
|
||||
|
||||
case 'msverify':
|
||||
$service = 'Bing Webmaster tools';
|
||||
break;
|
||||
|
||||
case 'pinterestverify':
|
||||
$service = 'Pinterest';
|
||||
break;
|
||||
|
||||
case 'yandexverify':
|
||||
$service = 'Yandex Webmaster tools';
|
||||
break;
|
||||
}
|
||||
|
||||
if ( preg_match( $regex, $meta ) ) {
|
||||
$clean[ $key ] = $meta;
|
||||
}
|
||||
else {
|
||||
// Restore the previous value, if any.
|
||||
if ( isset( $old[ $key ] ) && preg_match( $regex, $old[ $key ] ) ) {
|
||||
$clean[ $key ] = $old[ $key ];
|
||||
}
|
||||
|
||||
if ( function_exists( 'add_settings_error' ) ) {
|
||||
add_settings_error(
|
||||
$this->group_name, // Slug title of the setting.
|
||||
$key, // Suffix-ID for the error message box. WordPress prepends `setting-error-`.
|
||||
/* translators: 1: Verification string from user input; 2: Service name. */
|
||||
sprintf( __( '%1$s does not seem to be a valid %2$s verification string. Please correct.', 'wordpress-seo' ), '<strong>' . esc_html( $meta ) . '</strong>', $service ), // The error message.
|
||||
'error' // CSS class for the WP notice, either the legacy 'error' / 'updated' or the new `notice-*` ones.
|
||||
);
|
||||
}
|
||||
|
||||
Yoast_Input_Validation::add_dirty_value_to_settings_errors( $key, $meta );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an option as a valid URL. Prints out a WordPress settings error
|
||||
* notice if the URL is invalid.
|
||||
*
|
||||
* @param string $key Key to check, by type of URL setting.
|
||||
* @param array $dirty Dirty data with the new values.
|
||||
* @param array $old Old data.
|
||||
* @param array $clean Clean data by reference, normally the default values.
|
||||
*/
|
||||
public function validate_url( $key, $dirty, $old, &$clean ) {
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
|
||||
|
||||
$submitted_url = trim( htmlspecialchars( $dirty[ $key ], ENT_COMPAT, get_bloginfo( 'charset' ), true ) );
|
||||
$validated_url = filter_var( WPSEO_Utils::sanitize_url( $submitted_url ), FILTER_VALIDATE_URL );
|
||||
|
||||
if ( $validated_url === false ) {
|
||||
if ( function_exists( 'add_settings_error' ) ) {
|
||||
add_settings_error(
|
||||
// Slug title of the setting.
|
||||
$this->group_name,
|
||||
// Suffix-ID for the error message box. WordPress prepends `setting-error-`.
|
||||
$key,
|
||||
// The error message.
|
||||
sprintf(
|
||||
/* translators: %s expands to an invalid URL. */
|
||||
__( '%s does not seem to be a valid url. Please correct.', 'wordpress-seo' ),
|
||||
'<strong>' . esc_html( $submitted_url ) . '</strong>'
|
||||
),
|
||||
// Message type.
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
// Restore the previous URL value, if any.
|
||||
if ( isset( $old[ $key ] ) && $old[ $key ] !== '' ) {
|
||||
$url = WPSEO_Utils::sanitize_url( $old[ $key ] );
|
||||
if ( $url !== '' ) {
|
||||
$clean[ $key ] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
Yoast_Input_Validation::add_dirty_value_to_settings_errors( $key, $submitted_url );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The URL format is valid, let's sanitize it.
|
||||
$url = WPSEO_Utils::sanitize_url( $validated_url );
|
||||
|
||||
if ( $url !== '' ) {
|
||||
$clean[ $key ] = $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a Facebook App ID.
|
||||
*
|
||||
* @param string $key Key to check, in this case: the Facebook App ID field name.
|
||||
* @param array $dirty Dirty data with the new values.
|
||||
* @param array $old Old data.
|
||||
* @param array $clean Clean data by reference, normally the default values.
|
||||
*/
|
||||
public function validate_facebook_app_id( $key, $dirty, $old, &$clean ) {
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
|
||||
$url = 'https://graph.facebook.com/' . $dirty[ $key ];
|
||||
|
||||
$response = wp_remote_get( $url );
|
||||
// These filters are used in the tests.
|
||||
/**
|
||||
* Filter: 'validate_facebook_app_id_api_response_code' - Allows to filter the Faceboook API response code.
|
||||
*
|
||||
* @api int $response_code The Facebook API response header code.
|
||||
*/
|
||||
$response_code = apply_filters( 'validate_facebook_app_id_api_response_code', wp_remote_retrieve_response_code( $response ) );
|
||||
/**
|
||||
* Filter: 'validate_facebook_app_id_api_response_body' - Allows to filter the Faceboook API response body.
|
||||
*
|
||||
* @api string $response_body The Facebook API JSON response body.
|
||||
*/
|
||||
$response_body = apply_filters( 'validate_facebook_app_id_api_response_body', wp_remote_retrieve_body( $response ) );
|
||||
$response_object = json_decode( $response_body );
|
||||
|
||||
/*
|
||||
* When the request is successful the response code will be 200 and
|
||||
* the response object will contain an `id` property.
|
||||
*/
|
||||
if ( $response_code === 200 && isset( $response_object->id ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore the previous value, if any.
|
||||
if ( isset( $old[ $key ] ) && $old[ $key ] !== '' ) {
|
||||
$clean[ $key ] = $old[ $key ];
|
||||
}
|
||||
|
||||
if ( function_exists( 'add_settings_error' ) ) {
|
||||
add_settings_error(
|
||||
$this->group_name, // Slug title of the setting.
|
||||
$key, // Suffix-ID for the error message box. WordPress prepends `setting-error-`.
|
||||
sprintf(
|
||||
/* translators: %s expands to an invalid Facebook App ID. */
|
||||
__( '%s does not seem to be a valid Facebook App ID. Please correct.', 'wordpress-seo' ),
|
||||
'<strong>' . esc_html( $dirty[ $key ] ) . '</strong>'
|
||||
), // The error message.
|
||||
'error' // CSS class for the WP notice, either the legacy 'error' / 'updated' or the new `notice-*` ones.
|
||||
);
|
||||
}
|
||||
|
||||
Yoast_Input_Validation::add_dirty_value_to_settings_errors( $key, $dirty[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default filters.
|
||||
* Called from the validate() method to prevent failure to add new options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_default_filters() {
|
||||
remove_filter( 'default_option_' . $this->option_name, [ $this, 'get_defaults' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enriched default value for an option.
|
||||
*
|
||||
* Checks if the concrete class contains an enrich_defaults() method and if so, runs it.
|
||||
*
|
||||
* {@internal The enrich_defaults method is used to set defaults for variable array keys
|
||||
* in an option, such as array keys depending on post_types and/or taxonomies.}}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_defaults() {
|
||||
if ( method_exists( $this, 'translate_defaults' ) ) {
|
||||
$this->translate_defaults();
|
||||
}
|
||||
|
||||
if ( method_exists( $this, 'enrich_defaults' ) ) {
|
||||
$this->enrich_defaults();
|
||||
}
|
||||
|
||||
return apply_filters( 'wpseo_defaults', $this->defaults, $this->option_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filters to make sure that the option is merged with its defaults before being returned.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_option_filters() {
|
||||
// Don't change, needs to check for false as could return prio 0 which would evaluate to false.
|
||||
if ( has_filter( 'option_' . $this->option_name, [ $this, 'get_option' ] ) === false ) {
|
||||
add_filter( 'option_' . $this->option_name, [ $this, 'get_option' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the option filters.
|
||||
* Called from the clean_up methods to make sure we retrieve the original old option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_option_filters() {
|
||||
remove_filter( 'option_' . $this->option_name, [ $this, 'get_option' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge an option with its default values.
|
||||
*
|
||||
* This method should *not* be called directly!!! It is only meant to filter the get_option() results.
|
||||
*
|
||||
* @param mixed $options Option value.
|
||||
*
|
||||
* @return mixed Option merged with the defaults for that option.
|
||||
*/
|
||||
public function get_option( $options = null ) {
|
||||
$filtered = $this->array_filter_merge( $options );
|
||||
|
||||
/*
|
||||
* If the option contains variable option keys, make sure we don't remove those settings
|
||||
* - even if the defaults are not complete yet.
|
||||
* Unfortunately this means we also won't be removing the settings for post types or taxonomies
|
||||
* which are no longer in the WP install, but rather that than the other way around.
|
||||
*/
|
||||
if ( isset( $this->variable_array_key_patterns ) ) {
|
||||
$filtered = $this->retain_variable_keys( $options, $filtered );
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/* *********** METHODS influencing add_uption(), update_option() and saving from admin pages. *********** */
|
||||
|
||||
/**
|
||||
* Register (whitelist) the option for the configuration pages.
|
||||
* The validation callback is already registered separately on the sanitize_option hook,
|
||||
* so no need to double register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_setting() {
|
||||
if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->multisite_only === true ) {
|
||||
$network_settings_api = Yoast_Network_Settings_API::get();
|
||||
if ( $network_settings_api->meets_requirements() ) {
|
||||
$network_settings_api->register_setting( $this->group_name, $this->option_name );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
register_setting( $this->group_name, $this->option_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the option
|
||||
*
|
||||
* @param mixed $option_value The unvalidated new value for the option.
|
||||
*
|
||||
* @return array Validated new value for the option.
|
||||
*/
|
||||
public function validate( $option_value ) {
|
||||
$clean = $this->get_defaults();
|
||||
|
||||
/* Return the defaults if the new value is empty. */
|
||||
if ( ! is_array( $option_value ) || $option_value === [] ) {
|
||||
return $clean;
|
||||
}
|
||||
|
||||
$option_value = array_map( [ 'WPSEO_Utils', 'trim_recursive' ], $option_value );
|
||||
|
||||
$old = $this->get_original_option();
|
||||
if ( ! is_array( $old ) ) {
|
||||
$old = [];
|
||||
}
|
||||
$old = array_merge( $clean, $old );
|
||||
|
||||
$clean = $this->validate_option( $option_value, $clean, $old );
|
||||
|
||||
// Prevent updates to variables that are disabled via the override option.
|
||||
$clean = $this->prevent_disabled_options_update( $clean, $old );
|
||||
|
||||
/* Retain the values for variable array keys even when the post type/taxonomy is not yet registered. */
|
||||
if ( isset( $this->variable_array_key_patterns ) ) {
|
||||
$clean = $this->retain_variable_keys( $option_value, $clean );
|
||||
}
|
||||
|
||||
$this->remove_default_filters();
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a specific option key is disabled.
|
||||
*
|
||||
* This is determined by whether an override option is available with a key that equals the given key prefixed
|
||||
* with 'allow_'.
|
||||
*
|
||||
* @param string $key Option key.
|
||||
*
|
||||
* @return bool True if option key is disabled, false otherwise.
|
||||
*/
|
||||
public function is_disabled( $key ) {
|
||||
$override_option = $this->get_override_option();
|
||||
if ( empty( $override_option ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset( $override_option[ self::ALLOW_KEY_PREFIX . $key ] ) && ! $override_option[ self::ALLOW_KEY_PREFIX . $key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* All concrete classes must contain a validate_option() method which validates all
|
||||
* values within the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*/
|
||||
abstract protected function validate_option( $dirty, $clean, $old );
|
||||
|
||||
/* *********** METHODS for ADDING/UPDATING/UPGRADING the option. *********** */
|
||||
|
||||
/**
|
||||
* Retrieve the real old value (unmerged with defaults).
|
||||
*
|
||||
* @return array|bool The original option value (which can be false if the option doesn't exist).
|
||||
*/
|
||||
protected function get_original_option() {
|
||||
$this->remove_default_filters();
|
||||
$this->remove_option_filters();
|
||||
|
||||
// Get (unvalidated) array, NOT merged with defaults.
|
||||
if ( $this->multisite_only !== true ) {
|
||||
$option_value = get_option( $this->option_name );
|
||||
}
|
||||
else {
|
||||
$option_value = get_site_option( $this->option_name );
|
||||
}
|
||||
|
||||
$this->add_option_filters();
|
||||
$this->add_default_filters();
|
||||
|
||||
return $option_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the option if it doesn't exist for some strange reason.
|
||||
*
|
||||
* @uses WPSEO_Option::get_original_option()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_add_option() {
|
||||
if ( $this->get_original_option() === false ) {
|
||||
if ( $this->multisite_only !== true ) {
|
||||
update_option( $this->option_name, $this->get_defaults() );
|
||||
}
|
||||
else {
|
||||
$this->update_site_option( $this->get_defaults() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a site_option.
|
||||
*
|
||||
* {@internal This special method is only needed for multisite options, but very needed indeed there.
|
||||
* The order in which certain functions and hooks are run is different between
|
||||
* get_option() and get_site_option() which means in practice that the removing
|
||||
* of the default filters would be done too late and the re-adding of the default
|
||||
* filters might not be done at all.
|
||||
* Aka: use the WPSEO_Options::update_site_option() method (which calls this method)
|
||||
* for safely adding/updating multisite options.}}
|
||||
*
|
||||
* @param mixed $value The new value for the option.
|
||||
*
|
||||
* @return bool Whether the update was succesfull.
|
||||
*/
|
||||
public function update_site_option( $value ) {
|
||||
if ( $this->multisite_only === true && is_multisite() ) {
|
||||
$this->remove_default_filters();
|
||||
$result = update_site_option( $this->option_name, $value );
|
||||
$this->add_default_filters();
|
||||
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the real old value (unmerged with defaults), clean and re-save the option.
|
||||
*
|
||||
* @uses WPSEO_Option::get_original_option()
|
||||
* @uses WPSEO_Option::import()
|
||||
*
|
||||
* @param string $current_version Optional. Version from which to upgrade, if not set,
|
||||
* version specific upgrades will be disregarded.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clean( $current_version = null ) {
|
||||
$option_value = $this->get_original_option();
|
||||
$this->import( $option_value, $current_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean and re-save the option.
|
||||
*
|
||||
* @uses clean_option() method from concrete class if it exists.
|
||||
*
|
||||
* @todo [JRF/whomever] Figure out a way to show settings error during/after the upgrade - maybe
|
||||
* something along the lines of:
|
||||
* -> add them to a property in this class
|
||||
* -> if that property isset at the end of the routine and add_settings_error function does not exist,
|
||||
* save as transient (or update the transient if one already exists)
|
||||
* -> next time an admin is in the WP back-end, show the errors and delete the transient or only delete it
|
||||
* once the admin has dismissed the message (add ajax function)
|
||||
* Important: all validation routines which add_settings_errors would need to be changed for this to work
|
||||
*
|
||||
* @param array $option_value Option value to be imported.
|
||||
* @param string $current_version Optional. Version from which to upgrade, if not set,
|
||||
* version specific upgrades will be disregarded.
|
||||
* @param array $all_old_option_values Optional. Only used when importing old options to
|
||||
* have access to the real old values, in contrast to
|
||||
* the saved ones.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function import( $option_value, $current_version = null, $all_old_option_values = null ) {
|
||||
if ( $option_value === false ) {
|
||||
$option_value = $this->get_defaults();
|
||||
}
|
||||
elseif ( is_array( $option_value ) && method_exists( $this, 'clean_option' ) ) {
|
||||
$option_value = $this->clean_option( $option_value, $current_version, $all_old_option_values );
|
||||
}
|
||||
|
||||
/*
|
||||
* Save the cleaned value - validation will take care of cleaning out array keys which
|
||||
* should no longer be there.
|
||||
*/
|
||||
if ( $this->multisite_only !== true ) {
|
||||
update_option( $this->option_name, $option_value );
|
||||
}
|
||||
else {
|
||||
$this->update_site_option( $this->option_name, $option_value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the variable array key patterns for an options class.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_patterns() {
|
||||
return (array) $this->variable_array_key_patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the option name.
|
||||
*
|
||||
* @return string The set option name.
|
||||
*/
|
||||
public function get_option_name() {
|
||||
return $this->option_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Concrete classes *may* contain a clean_option method which will clean out old/renamed
|
||||
* values within the option.
|
||||
*/
|
||||
// abstract public function clean_option( $option_value, $current_version = null, $all_old_option_values = null );
|
||||
/* *********** HELPER METHODS for internal use. *********** */
|
||||
|
||||
/**
|
||||
* Helper method - Combines a fixed array of default values with an options array
|
||||
* while filtering out any keys which are not in the defaults array.
|
||||
*
|
||||
* @todo [JRF] - shouldn't this be a straight array merge ? at the end of the day, the validation
|
||||
* removes any invalid keys on save.
|
||||
*
|
||||
* @param array $options Optional. Current options. If not set, the option defaults
|
||||
* for the $option_key will be returned.
|
||||
*
|
||||
* @return array Combined and filtered options array.
|
||||
*/
|
||||
protected function array_filter_merge( $options = null ) {
|
||||
|
||||
$defaults = $this->get_defaults();
|
||||
|
||||
if ( ! isset( $options ) || $options === false || $options === [] ) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
$options = (array) $options;
|
||||
|
||||
/*
|
||||
$filtered = array();
|
||||
|
||||
if ( $defaults !== array() ) {
|
||||
foreach ( $defaults as $key => $default_value ) {
|
||||
// @todo should this walk through array subkeys ?
|
||||
$filtered[ $key ] = ( isset( $options[ $key ] ) ? $options[ $key ] : $default_value );
|
||||
}
|
||||
}
|
||||
*/
|
||||
$filtered = array_merge( $defaults, $options );
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets updated values for variables that are disabled via the override option back to their previous values.
|
||||
*
|
||||
* @param array $updated Updated option value.
|
||||
* @param array $old Old option value.
|
||||
*
|
||||
* @return array Updated option value, with all disabled variables set to their old values.
|
||||
*/
|
||||
protected function prevent_disabled_options_update( $updated, $old ) {
|
||||
$override_option = $this->get_override_option();
|
||||
if ( empty( $override_option ) ) {
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/*
|
||||
* This loop could as well call `is_disabled( $key )` for each iteration,
|
||||
* however this would be worse performance-wise.
|
||||
*/
|
||||
foreach ( $old as $key => $value ) {
|
||||
if ( isset( $override_option[ self::ALLOW_KEY_PREFIX . $key ] ) && ! $override_option[ self::ALLOW_KEY_PREFIX . $key ] ) {
|
||||
$updated[ $key ] = $old[ $key ];
|
||||
}
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the override option, if available.
|
||||
*
|
||||
* An override option contains values that may determine access to certain sub-variables
|
||||
* of this option.
|
||||
*
|
||||
* Only regular options in multisite can have override options, which in that case
|
||||
* would be network options.
|
||||
*
|
||||
* @return array Override option value, or empty array if unavailable.
|
||||
*/
|
||||
protected function get_override_option() {
|
||||
if ( empty( $this->override_option_name ) || $this->multisite_only === true || ! is_multisite() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return get_site_option( $this->override_option_name, [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that any set option values relating to post_types and/or taxonomies are retained,
|
||||
* even when that post_type or taxonomy may not yet have been registered.
|
||||
*
|
||||
* {@internal The wpseo_titles concrete class overrules this method. Make sure that any
|
||||
* changes applied here, also get ported to that version.}}
|
||||
*
|
||||
* @param array $dirty Original option as retrieved from the database.
|
||||
* @param array $clean Filtered option where any options which shouldn't be in our option
|
||||
* have already been removed and any options which weren't set
|
||||
* have been set to their defaults.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function retain_variable_keys( $dirty, $clean ) {
|
||||
if ( ( is_array( $this->variable_array_key_patterns ) && $this->variable_array_key_patterns !== [] ) && ( is_array( $dirty ) && $dirty !== [] ) ) {
|
||||
foreach ( $dirty as $key => $value ) {
|
||||
|
||||
// Do nothing if already in filtered options.
|
||||
if ( isset( $clean[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $this->variable_array_key_patterns as $pattern ) {
|
||||
|
||||
if ( strpos( $key, $pattern ) === 0 ) {
|
||||
$clean[ $key ] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given array key conforms to one of the variable array key patterns for this option.
|
||||
*
|
||||
* @usedby validate_option() methods for options with variable array keys.
|
||||
*
|
||||
* @param string $key Array key to check.
|
||||
*
|
||||
* @return string Pattern if it conforms, original array key if it doesn't or if the option
|
||||
* does not have variable array keys.
|
||||
*/
|
||||
protected function get_switch_key( $key ) {
|
||||
if ( ! isset( $this->variable_array_key_patterns ) || ( ! is_array( $this->variable_array_key_patterns ) || $this->variable_array_key_patterns === [] ) ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
foreach ( $this->variable_array_key_patterns as $pattern ) {
|
||||
if ( strpos( $key, $pattern ) === 0 ) {
|
||||
return $pattern;
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals\Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Overal Option Management class.
|
||||
*
|
||||
* Instantiates all the options and offers a number of utility methods to work with the options.
|
||||
*/
|
||||
class WPSEO_Options {
|
||||
|
||||
/**
|
||||
* The option values.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
protected static $option_values = null;
|
||||
|
||||
/**
|
||||
* Options this class uses.
|
||||
*
|
||||
* @var array Array format: (string) option_name => (string) name of concrete class for the option.
|
||||
*/
|
||||
public static $options = [
|
||||
'wpseo' => 'WPSEO_Option_Wpseo',
|
||||
'wpseo_titles' => 'WPSEO_Option_Titles',
|
||||
'wpseo_social' => 'WPSEO_Option_Social',
|
||||
'wpseo_ms' => 'WPSEO_Option_MS',
|
||||
'wpseo_taxonomy_meta' => 'WPSEO_Taxonomy_Meta',
|
||||
];
|
||||
|
||||
/**
|
||||
* Array of instantiated option objects.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $option_instances = [];
|
||||
|
||||
/**
|
||||
* Array with the option names.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $option_names = [];
|
||||
|
||||
/**
|
||||
* Instance of this class.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Instantiate all the WPSEO option management classes.
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->register_hooks();
|
||||
|
||||
foreach ( static::$options as $option_name => $option_class ) {
|
||||
static::register_option( call_user_func( [ $option_class, 'get_instance' ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our hooks.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'registered_taxonomy', [ $this, 'clear_cache' ] );
|
||||
add_action( 'unregistered_taxonomy', [ $this, 'clear_cache' ] );
|
||||
add_action( 'registered_post_type', [ $this, 'clear_cache' ] );
|
||||
add_action( 'unregistered_post_type', [ $this, 'clear_cache' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( static::$instance instanceof self ) ) {
|
||||
static::$instance = new self();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an option to the options list.
|
||||
*
|
||||
* @param WPSEO_Option $option_instance Instance of the option.
|
||||
*/
|
||||
public static function register_option( WPSEO_Option $option_instance ) {
|
||||
$option_name = $option_instance->get_option_name();
|
||||
|
||||
if ( $option_instance->multisite_only && ! static::is_multisite() ) {
|
||||
unset( static::$options[ $option_name ], static::$option_names[ $option_name ] );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$is_already_registered = array_key_exists( $option_name, static::$options );
|
||||
if ( ! $is_already_registered ) {
|
||||
static::$options[ $option_name ] = get_class( $option_instance );
|
||||
}
|
||||
|
||||
if ( $option_instance->include_in_all === true ) {
|
||||
static::$option_names[ $option_name ] = $option_name;
|
||||
}
|
||||
|
||||
static::$option_instances[ $option_name ] = $option_instance;
|
||||
|
||||
if ( ! $is_already_registered ) {
|
||||
static::clear_cache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the group name of an option for use in the settings form.
|
||||
*
|
||||
* @param string $option_name The option for which you want to retrieve the option group name.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public static function get_group_name( $option_name ) {
|
||||
if ( isset( static::$option_instances[ $option_name ] ) ) {
|
||||
return static::$option_instances[ $option_name ]->group_name;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function get_default( $option_name, $key ) {
|
||||
if ( isset( static::$option_instances[ $option_name ] ) ) {
|
||||
$defaults = static::$option_instances[ $option_name ]->get_defaults();
|
||||
if ( isset( $defaults[ $key ] ) ) {
|
||||
return $defaults[ $key ];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a site_option.
|
||||
*
|
||||
* @param string $option_name The option name of the option to save.
|
||||
* @param mixed $value The new value for the option.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function update_site_option( $option_name, $value ) {
|
||||
if ( is_multisite() && isset( static::$option_instances[ $option_name ] ) ) {
|
||||
return static::$option_instances[ $option_name ]->update_site_option( $value );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instantiated option instance.
|
||||
*
|
||||
* @param string $option_name The option for which you want to retrieve the instance.
|
||||
*
|
||||
* @return object|bool
|
||||
*/
|
||||
public static function get_option_instance( $option_name ) {
|
||||
if ( isset( static::$option_instances[ $option_name ] ) ) {
|
||||
return static::$option_instances[ $option_name ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an array of the options which should be included in get_all() and reset().
|
||||
*
|
||||
* @return array Array of option names.
|
||||
*/
|
||||
public static function get_option_names() {
|
||||
$option_names = array_values( static::$option_names );
|
||||
if ( $option_names === [] ) {
|
||||
foreach ( static::$option_instances as $option_name => $option_object ) {
|
||||
if ( $option_object->include_in_all === true ) {
|
||||
$option_names[] = $option_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: wpseo_options - Allow developers to change the option name to include.
|
||||
*
|
||||
* @api array The option names to include in get_all and reset().
|
||||
*/
|
||||
return apply_filters( 'wpseo_options', $option_names );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the options for the SEO plugin in one go.
|
||||
*
|
||||
* @return array Array combining the values of all the options.
|
||||
*/
|
||||
public static function get_all() {
|
||||
static::$option_values = static::get_options( static::get_option_names() );
|
||||
|
||||
return static::$option_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve one or more options for the SEO plugin.
|
||||
*
|
||||
* @param array $option_names An array of option names of the options you want to get.
|
||||
*
|
||||
* @return array Array combining the values of the requested options.
|
||||
*/
|
||||
public static function get_options( array $option_names ) {
|
||||
$options = [];
|
||||
$option_names = array_filter( $option_names, 'is_string' );
|
||||
foreach ( $option_names as $option_name ) {
|
||||
if ( isset( static::$option_instances[ $option_name ] ) ) {
|
||||
$option = static::get_option( $option_name );
|
||||
$options = array_merge( $options, $option );
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single option for the SEO plugin.
|
||||
*
|
||||
* @param string $option_name The name of the option you want to get.
|
||||
*
|
||||
* @return array Array containing the requested option.
|
||||
*/
|
||||
public static function get_option( $option_name ) {
|
||||
$option = null;
|
||||
if ( is_string( $option_name ) && ! empty( $option_name ) ) {
|
||||
if ( isset( static::$option_instances[ $option_name ] ) ) {
|
||||
if ( static::$option_instances[ $option_name ]->multisite_only !== true ) {
|
||||
$option = get_option( $option_name );
|
||||
}
|
||||
else {
|
||||
$option = get_site_option( $option_name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single field from any option for the SEO plugin. Keys are always unique.
|
||||
*
|
||||
* @param string $key The key it should return.
|
||||
* @param mixed $default The default value that should be returned if the key isn't set.
|
||||
*
|
||||
* @return mixed|null Returns value if found, $default if not.
|
||||
*/
|
||||
public static function get( $key, $default = null ) {
|
||||
if ( static::$option_values === null ) {
|
||||
static::prime_cache();
|
||||
}
|
||||
if ( isset( static::$option_values[ $key ] ) ) {
|
||||
return static::$option_values[ $key ];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the cache to null.
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
static::$option_values = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primes our cache.
|
||||
*/
|
||||
private static function prime_cache() {
|
||||
static::$option_values = static::get_all();
|
||||
static::$option_values = static::add_ms_option( static::$option_values );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single field from an option for the SEO plugin.
|
||||
*
|
||||
* @param string $key The key to set.
|
||||
* @param mixed $value The value to set.
|
||||
*
|
||||
* @return mixed|null Returns value if found, $default if not.
|
||||
*/
|
||||
public static function set( $key, $value ) {
|
||||
$lookup_table = static::get_lookup_table();
|
||||
|
||||
if ( isset( $lookup_table[ $key ] ) ) {
|
||||
return static::save_option( $lookup_table[ $key ], $key, $value );
|
||||
}
|
||||
|
||||
$patterns = static::get_pattern_table();
|
||||
foreach ( $patterns as $pattern => $option ) {
|
||||
if ( strpos( $key, $pattern ) === 0 ) {
|
||||
return static::save_option( $option, $key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
static::$option_values[ $key ] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option only if it's been auto-loaded.
|
||||
*
|
||||
* @param string $option The option to retrieve.
|
||||
* @param bool|mixed $default A default value to return.
|
||||
*
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public static function get_autoloaded_option( $option, $default = false ) {
|
||||
$value = wp_cache_get( $option, 'options' );
|
||||
if ( $value === false ) {
|
||||
$passed_default = func_num_args() > 1;
|
||||
|
||||
return apply_filters( "default_option_{$option}", $default, $option, $passed_default );
|
||||
}
|
||||
|
||||
return apply_filters( "option_{$option}", maybe_unserialize( $value ), $option );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the clean up routine for one or all options.
|
||||
*
|
||||
* @param array|string $option_name Optional. the option you want to clean or an array of
|
||||
* option names for the options you want to clean.
|
||||
* If not set, all options will be cleaned.
|
||||
* @param string $current_version Optional. Version from which to upgrade, if not set,
|
||||
* version specific upgrades will be disregarded.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clean_up( $option_name = null, $current_version = null ) {
|
||||
if ( isset( $option_name ) && is_string( $option_name ) && $option_name !== '' ) {
|
||||
if ( isset( static::$option_instances[ $option_name ] ) ) {
|
||||
static::$option_instances[ $option_name ]->clean( $current_version );
|
||||
}
|
||||
}
|
||||
elseif ( isset( $option_name ) && is_array( $option_name ) && $option_name !== [] ) {
|
||||
foreach ( $option_name as $option ) {
|
||||
if ( isset( static::$option_instances[ $option ] ) ) {
|
||||
static::$option_instances[ $option ]->clean( $current_version );
|
||||
}
|
||||
}
|
||||
unset( $option );
|
||||
}
|
||||
else {
|
||||
foreach ( static::$option_instances as $instance ) {
|
||||
$instance->clean( $current_version );
|
||||
}
|
||||
unset( $instance );
|
||||
|
||||
// If we've done a full clean-up, we can safely remove this really old option.
|
||||
delete_option( 'wpseo_indexation' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that all options exist in the database and add any which don't.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function ensure_options_exist() {
|
||||
foreach ( static::$option_instances as $instance ) {
|
||||
$instance->maybe_add_option();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize some options on first install/activate/reset.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function initialize() {
|
||||
/* Force WooThemes to use Yoast SEO data. */
|
||||
if ( function_exists( 'woo_version_init' ) ) {
|
||||
update_option( 'seo_woo_use_third_party_data', 'true' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all options to their default values and rerun some tests.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset() {
|
||||
if ( ! is_multisite() ) {
|
||||
$option_names = static::get_option_names();
|
||||
if ( is_array( $option_names ) && $option_names !== [] ) {
|
||||
foreach ( $option_names as $option_name ) {
|
||||
delete_option( $option_name );
|
||||
update_option( $option_name, get_option( $option_name ) );
|
||||
}
|
||||
}
|
||||
unset( $option_names );
|
||||
}
|
||||
else {
|
||||
// Reset MS blog based on network default blog setting.
|
||||
static::reset_ms_blog( get_current_blog_id() );
|
||||
}
|
||||
|
||||
static::initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize default values for a new multisite blog.
|
||||
*
|
||||
* @param bool $force_init Whether to always do the initialization routine (title/desc test).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_set_multisite_defaults( $force_init = false ) {
|
||||
$option = get_option( 'wpseo' );
|
||||
|
||||
if ( is_multisite() ) {
|
||||
if ( $option['ms_defaults_set'] === false ) {
|
||||
static::reset_ms_blog( get_current_blog_id() );
|
||||
static::initialize();
|
||||
}
|
||||
elseif ( $force_init === true ) {
|
||||
static::initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all options for a specific multisite blog to their default values based upon a
|
||||
* specified default blog if one was chosen on the network page or the plugin defaults if it was not.
|
||||
*
|
||||
* @param int|string $blog_id Blog id of the blog for which to reset the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset_ms_blog( $blog_id ) {
|
||||
if ( is_multisite() ) {
|
||||
$options = get_site_option( 'wpseo_ms' );
|
||||
$option_names = static::get_option_names();
|
||||
|
||||
if ( is_array( $option_names ) && $option_names !== [] ) {
|
||||
$base_blog_id = $blog_id;
|
||||
if ( $options['defaultblog'] !== '' && $options['defaultblog'] !== 0 ) {
|
||||
$base_blog_id = $options['defaultblog'];
|
||||
}
|
||||
|
||||
foreach ( $option_names as $option_name ) {
|
||||
delete_blog_option( $blog_id, $option_name );
|
||||
|
||||
$new_option = get_blog_option( $base_blog_id, $option_name );
|
||||
|
||||
/* Remove sensitive, theme dependent and site dependent info. */
|
||||
if ( isset( static::$option_instances[ $option_name ] ) && static::$option_instances[ $option_name ]->ms_exclude !== [] ) {
|
||||
foreach ( static::$option_instances[ $option_name ]->ms_exclude as $key ) {
|
||||
unset( $new_option[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $option_name === 'wpseo' ) {
|
||||
$new_option['ms_defaults_set'] = true;
|
||||
}
|
||||
|
||||
update_blog_option( $blog_id, $option_name, $new_option );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the option to the database.
|
||||
*
|
||||
* @param string $wpseo_options_group_name The name for the wpseo option group in the database.
|
||||
* @param string $option_name The name for the option to set.
|
||||
* @param mixed $option_value The value for the option.
|
||||
*
|
||||
* @return boolean Returns true if the option is successfully saved in the database.
|
||||
*/
|
||||
public static function save_option( $wpseo_options_group_name, $option_name, $option_value ) {
|
||||
$options = static::get_option( $wpseo_options_group_name );
|
||||
$options[ $option_name ] = $option_value;
|
||||
|
||||
if ( isset( static::$option_instances[ $wpseo_options_group_name ] ) && static::$option_instances[ $wpseo_options_group_name ]->multisite_only === true ) {
|
||||
static::update_site_option( $wpseo_options_group_name, $options );
|
||||
}
|
||||
else {
|
||||
update_option( $wpseo_options_group_name, $options );
|
||||
}
|
||||
|
||||
// Check if everything got saved properly.
|
||||
$saved_option = static::get_option( $wpseo_options_group_name );
|
||||
|
||||
// Clear our cache.
|
||||
static::clear_cache();
|
||||
|
||||
return $saved_option[ $option_name ] === $options[ $option_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the multisite options to the option stack if relevant.
|
||||
*
|
||||
* @param array $option The currently present options settings.
|
||||
*
|
||||
* @return array Options possibly including multisite.
|
||||
*/
|
||||
protected static function add_ms_option( $option ) {
|
||||
if ( ! is_multisite() ) {
|
||||
return $option;
|
||||
}
|
||||
|
||||
$ms_option = static::get_option( 'wpseo_ms' );
|
||||
if ( $ms_option === null ) {
|
||||
return $option;
|
||||
}
|
||||
|
||||
return array_merge( $option, $ms_option );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if installation is multisite.
|
||||
*
|
||||
* @return bool True when is multisite.
|
||||
*/
|
||||
protected static function is_multisite() {
|
||||
static $is_multisite;
|
||||
|
||||
if ( $is_multisite === null ) {
|
||||
$is_multisite = is_multisite();
|
||||
}
|
||||
|
||||
return $is_multisite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a lookup table to find in which option_group a key is stored.
|
||||
*
|
||||
* @return array The lookup table.
|
||||
*/
|
||||
private static function get_lookup_table() {
|
||||
$lookup_table = [];
|
||||
|
||||
|
||||
foreach ( array_keys( static::$options ) as $option_name ) {
|
||||
$full_option = static::get_option( $option_name );
|
||||
foreach ( $full_option as $key => $value ) {
|
||||
$lookup_table[ $key ] = $option_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $lookup_table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a lookup table to find in which option_group a key is stored.
|
||||
*
|
||||
* @return array The lookup table.
|
||||
*/
|
||||
private static function get_pattern_table() {
|
||||
$pattern_table = [];
|
||||
foreach ( static::$options as $option_name => $option_class ) {
|
||||
$instance = call_user_func( [ $option_class, 'get_instance' ] );
|
||||
foreach ( $instance->get_patterns() as $key ) {
|
||||
$pattern_table[ $key ] = $option_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $pattern_table;
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Fills our option cache.
|
||||
*
|
||||
* @deprecated 12.8.1
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function fill_cache() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 12.8.1', '::clear_cache' );
|
||||
static::clear_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inadvertent removal of the fallback to default values from the breadcrumbs.
|
||||
*
|
||||
* @since 1.5.2.3
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function bring_back_breadcrumb_defaults() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 7.0' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals\Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Option: wpseo_taxonomy_meta.
|
||||
*/
|
||||
class WPSEO_Taxonomy_Meta extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo_taxonomy_meta';
|
||||
|
||||
/**
|
||||
* Whether to include the option in the return for WPSEO_Options::get_all().
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $include_in_all = false;
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* Shouldn't be requested directly, use $this->get_defaults();
|
||||
*
|
||||
* {@internal Important: in contrast to most defaults, the below array format is
|
||||
* very bare. The real option is in the format [taxonomy_name][term_id][...]
|
||||
* where [...] is any of the $defaults_per_term options shown below.
|
||||
* This is of course taken into account in the below methods.}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [];
|
||||
|
||||
/**
|
||||
* Option name - same as $option_name property, but now also available to static methods.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $name;
|
||||
|
||||
/**
|
||||
* Array of defaults for individual taxonomy meta entries.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $defaults_per_term = [
|
||||
'wpseo_title' => '',
|
||||
'wpseo_desc' => '',
|
||||
'wpseo_canonical' => '',
|
||||
'wpseo_bctitle' => '',
|
||||
'wpseo_noindex' => 'default',
|
||||
'wpseo_focuskw' => '',
|
||||
'wpseo_linkdex' => '',
|
||||
'wpseo_content_score' => '',
|
||||
'wpseo_focuskeywords' => '[]',
|
||||
'wpseo_keywordsynonyms' => '[]',
|
||||
|
||||
// Social fields.
|
||||
'wpseo_opengraph-title' => '',
|
||||
'wpseo_opengraph-description' => '',
|
||||
'wpseo_opengraph-image' => '',
|
||||
'wpseo_opengraph-image-id' => '',
|
||||
'wpseo_twitter-title' => '',
|
||||
'wpseo_twitter-description' => '',
|
||||
'wpseo_twitter-image' => '',
|
||||
'wpseo_twitter-image-id' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Available index options.
|
||||
*
|
||||
* Used for form generation and input validation.
|
||||
*
|
||||
* {@internal Labels (translation) added on admin_init via WPSEO_Taxonomy::translate_meta_options().}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $no_index_options = [
|
||||
'default' => '',
|
||||
'index' => '',
|
||||
'noindex' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Add the actions and filters for the option.
|
||||
*
|
||||
* @todo [JRF => testers] Check if the extra actions below would run into problems if an option
|
||||
* is updated early on and if so, change the call to schedule these for a later action on add/update
|
||||
* instead of running them straight away.
|
||||
*/
|
||||
protected function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
self::$name = $this->option_name;
|
||||
|
||||
/* On succesfull update/add of the option, flush the W3TC cache. */
|
||||
add_action( 'add_option_' . $this->option_name, [ 'WPSEO_Utils', 'flush_w3tc_cache' ] );
|
||||
add_action( 'update_option_' . $this->option_name, [ 'WPSEO_Utils', 'flush_w3tc_cache' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( self::$instance instanceof self ) ) {
|
||||
self::$instance = new self();
|
||||
self::$name = self::$instance->option_name;
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra default options received from a filter.
|
||||
*/
|
||||
public function enrich_defaults() {
|
||||
$extra_defaults_per_term = apply_filters( 'wpseo_add_extra_taxmeta_term_defaults', [] );
|
||||
if ( is_array( $extra_defaults_per_term ) ) {
|
||||
self::$defaults_per_term = array_merge( $extra_defaults_per_term, self::$defaults_per_term );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method - Combines a fixed array of default values with an options array
|
||||
* while filtering out any keys which are not in the defaults array.
|
||||
*
|
||||
* @param string $option_key Option name of the option we're doing the merge for.
|
||||
* @param array $options Optional. Current options. If not set, the option defaults
|
||||
* for the $option_key will be returned.
|
||||
*
|
||||
* @return array Combined and filtered options array.
|
||||
*/
|
||||
|
||||
/*
|
||||
Public function array_filter_merge( $option_key, $options = null ) {
|
||||
|
||||
$defaults = $this->get_defaults( $option_key );
|
||||
|
||||
if ( ! isset( $options ) || $options === false ) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/ *
|
||||
{@internal Adding the defaults to all taxonomy terms each time the option is retrieved
|
||||
will be quite inefficient if there are a lot of taxonomy terms.
|
||||
As long as taxonomy_meta is only retrieved via methods in this class, we shouldn't need this.}}
|
||||
|
||||
$options = (array) $options;
|
||||
$filtered = array();
|
||||
|
||||
if ( $options !== array() ) {
|
||||
foreach ( $options as $taxonomy => $terms ) {
|
||||
if ( is_array( $terms ) && $terms !== array() ) {
|
||||
foreach ( $terms as $id => $term_meta ) {
|
||||
foreach ( self::$defaults_per_term as $name => $default ) {
|
||||
if ( isset( $options[ $taxonomy ][ $id ][ $name ] ) ) {
|
||||
$filtered[ $taxonomy ][ $id ][ $name ] = $options[ $taxonomy ][ $id ][ $name ];
|
||||
}
|
||||
else {
|
||||
$filtered[ $name ] = $default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $taxonomy, $terms, $id, $term_meta, $name, $default );
|
||||
}
|
||||
// end of may be remove.
|
||||
|
||||
return $filtered;
|
||||
* /
|
||||
|
||||
return (array) $options;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validate the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array Validated clean value for the option to be saved to the database.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
/*
|
||||
* Prevent complete validation (which can be expensive when there are lots of terms)
|
||||
* if only one item has changed and has already been validated.
|
||||
*/
|
||||
if ( isset( $dirty['wpseo_already_validated'] ) && $dirty['wpseo_already_validated'] === true ) {
|
||||
unset( $dirty['wpseo_already_validated'] );
|
||||
|
||||
return $dirty;
|
||||
}
|
||||
|
||||
foreach ( $dirty as $taxonomy => $terms ) {
|
||||
/* Don't validate taxonomy - may not be registered yet and we don't want to remove valid ones. */
|
||||
if ( is_array( $terms ) && $terms !== [] ) {
|
||||
foreach ( $terms as $term_id => $meta_data ) {
|
||||
/* Only validate term if the taxonomy exists. */
|
||||
if ( taxonomy_exists( $taxonomy ) && get_term_by( 'id', $term_id, $taxonomy ) === false ) {
|
||||
/* Is this term id a special case ? */
|
||||
if ( has_filter( 'wpseo_tax_meta_special_term_id_validation_' . $term_id ) !== false ) {
|
||||
$clean[ $taxonomy ][ $term_id ] = apply_filters( 'wpseo_tax_meta_special_term_id_validation_' . $term_id, $meta_data, $taxonomy, $term_id );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( is_array( $meta_data ) && $meta_data !== [] ) {
|
||||
/* Validate meta data. */
|
||||
$old_meta = self::get_term_meta( $term_id, $taxonomy );
|
||||
$meta_data = self::validate_term_meta_data( $meta_data, $old_meta );
|
||||
if ( $meta_data !== [] ) {
|
||||
$clean[ $taxonomy ][ $term_id ] = $meta_data;
|
||||
}
|
||||
}
|
||||
|
||||
// Deal with special cases (for when taxonomy doesn't exist yet).
|
||||
if ( ! isset( $clean[ $taxonomy ][ $term_id ] ) && has_filter( 'wpseo_tax_meta_special_term_id_validation_' . $term_id ) !== false ) {
|
||||
$clean[ $taxonomy ][ $term_id ] = apply_filters( 'wpseo_tax_meta_special_term_id_validation_' . $term_id, $meta_data, $taxonomy, $term_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the meta data for one individual term and removes default values (no need to save those).
|
||||
*
|
||||
* @param array $meta_data New values.
|
||||
* @param array $old_meta The original values.
|
||||
*
|
||||
* @return array Validated and filtered value.
|
||||
*/
|
||||
public static function validate_term_meta_data( $meta_data, $old_meta ) {
|
||||
|
||||
$clean = self::$defaults_per_term;
|
||||
$meta_data = array_map( [ 'WPSEO_Utils', 'trim_recursive' ], $meta_data );
|
||||
|
||||
if ( ! is_array( $meta_data ) || $meta_data === [] ) {
|
||||
return $clean;
|
||||
}
|
||||
|
||||
foreach ( $clean as $key => $value ) {
|
||||
switch ( $key ) {
|
||||
|
||||
case 'wpseo_noindex':
|
||||
if ( isset( $meta_data[ $key ] ) ) {
|
||||
if ( isset( self::$no_index_options[ $meta_data[ $key ] ] ) ) {
|
||||
$clean[ $key ] = $meta_data[ $key ];
|
||||
}
|
||||
}
|
||||
elseif ( isset( $old_meta[ $key ] ) ) {
|
||||
// Retain old value if field currently not in use.
|
||||
$clean[ $key ] = $old_meta[ $key ];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wpseo_canonical':
|
||||
if ( isset( $meta_data[ $key ] ) && $meta_data[ $key ] !== '' ) {
|
||||
$url = WPSEO_Utils::sanitize_url( $meta_data[ $key ] );
|
||||
if ( $url !== '' ) {
|
||||
$clean[ $key ] = $url;
|
||||
}
|
||||
unset( $url );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wpseo_bctitle':
|
||||
if ( isset( $meta_data[ $key ] ) ) {
|
||||
$clean[ $key ] = WPSEO_Utils::sanitize_text_field( $meta_data[ $key ] );
|
||||
}
|
||||
elseif ( isset( $old_meta[ $key ] ) ) {
|
||||
// Retain old value if field currently not in use.
|
||||
$clean[ $key ] = $old_meta[ $key ];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wpseo_keywordsynonyms':
|
||||
if ( isset( $meta_data[ $key ] ) && is_string( $meta_data[ $key ] ) ) {
|
||||
// The data is stringified JSON. Use `json_decode` and `json_encode` around the sanitation.
|
||||
$input = json_decode( $meta_data[ $key ], true );
|
||||
$sanitized = array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], $input );
|
||||
$clean[ $key ] = WPSEO_Utils::format_json_encode( $sanitized );
|
||||
}
|
||||
elseif ( isset( $old_meta[ $key ] ) ) {
|
||||
// Retain old value if field currently not in use.
|
||||
$clean[ $key ] = $old_meta[ $key ];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wpseo_focuskeywords':
|
||||
if ( isset( $meta_data[ $key ] ) && is_string( $meta_data[ $key ] ) ) {
|
||||
// The data is stringified JSON. Use `json_decode` and `json_encode` around the sanitation.
|
||||
$input = json_decode( $meta_data[ $key ], true );
|
||||
|
||||
// This data has two known keys: `keyword` and `score`.
|
||||
$sanitized = [];
|
||||
foreach ( $input as $entry ) {
|
||||
$sanitized[] = [
|
||||
'keyword' => WPSEO_Utils::sanitize_text_field( $entry['keyword'] ),
|
||||
'score' => WPSEO_Utils::sanitize_text_field( $entry['score'] ),
|
||||
];
|
||||
}
|
||||
|
||||
$clean[ $key ] = WPSEO_Utils::format_json_encode( $sanitized );
|
||||
}
|
||||
elseif ( isset( $old_meta[ $key ] ) ) {
|
||||
// Retain old value if field currently not in use.
|
||||
$clean[ $key ] = $old_meta[ $key ];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wpseo_focuskw':
|
||||
case 'wpseo_title':
|
||||
case 'wpseo_desc':
|
||||
case 'wpseo_linkdex':
|
||||
default:
|
||||
if ( isset( $meta_data[ $key ] ) && is_string( $meta_data[ $key ] ) ) {
|
||||
$clean[ $key ] = WPSEO_Utils::sanitize_text_field( $meta_data[ $key ] );
|
||||
}
|
||||
|
||||
if ( $key === 'wpseo_focuskw' ) {
|
||||
$search = [
|
||||
'<',
|
||||
'>',
|
||||
'`',
|
||||
'<',
|
||||
'>',
|
||||
'`',
|
||||
];
|
||||
|
||||
$clean[ $key ] = str_replace( $search, '', $clean[ $key ] );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$clean[ $key ] = apply_filters( 'wpseo_sanitize_tax_meta_' . $key, $clean[ $key ], ( isset( $meta_data[ $key ] ) ? $meta_data[ $key ] : null ), ( isset( $old_meta[ $key ] ) ? $old_meta[ $key ] : null ) );
|
||||
}
|
||||
|
||||
// Only save the non-default values.
|
||||
return array_diff_assoc( $clean, self::$defaults_per_term );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a given option value.
|
||||
* - Convert old option values to new
|
||||
* - Fixes strings which were escaped (should have been sanitized - escaping is for output)
|
||||
*
|
||||
* @param array $option_value Old (not merged with defaults or filtered) option value to
|
||||
* clean according to the rules for this option.
|
||||
* @param string $current_version Optional. Version from which to upgrade, if not set,
|
||||
* version specific upgrades will be disregarded.
|
||||
* @param array $all_old_option_values Optional. Only used when importing old options to have
|
||||
* access to the real old values, in contrast to the saved ones.
|
||||
*
|
||||
* @return array Cleaned option.
|
||||
*/
|
||||
protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
|
||||
|
||||
/* Clean up old values and remove empty arrays. */
|
||||
if ( is_array( $option_value ) && $option_value !== [] ) {
|
||||
|
||||
foreach ( $option_value as $taxonomy => $terms ) {
|
||||
|
||||
if ( is_array( $terms ) && $terms !== [] ) {
|
||||
|
||||
foreach ( $terms as $term_id => $meta_data ) {
|
||||
if ( ! is_array( $meta_data ) || $meta_data === [] ) {
|
||||
// Remove empty term arrays.
|
||||
unset( $option_value[ $taxonomy ][ $term_id ] );
|
||||
}
|
||||
else {
|
||||
foreach ( $meta_data as $key => $value ) {
|
||||
|
||||
switch ( $key ) {
|
||||
case 'noindex':
|
||||
if ( $value === 'on' ) {
|
||||
// Convert 'on' to 'noindex'.
|
||||
$option_value[ $taxonomy ][ $term_id ][ $key ] = 'noindex';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'canonical':
|
||||
case 'wpseo_bctitle':
|
||||
case 'wpseo_title':
|
||||
case 'wpseo_desc':
|
||||
case 'wpseo_linkdex':
|
||||
// @todo [JRF => whomever] Needs checking, I don't have example data [JRF].
|
||||
if ( $value !== '' ) {
|
||||
// Fix incorrectly saved (encoded) canonical urls and texts.
|
||||
$option_value[ $taxonomy ][ $term_id ][ $key ] = wp_specialchars_decode( stripslashes( $value ), ENT_QUOTES );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// @todo [JRF => whomever] Needs checking, I don't have example data [JRF].
|
||||
if ( $value !== '' ) {
|
||||
// Fix incorrectly saved (escaped) text strings.
|
||||
$option_value[ $taxonomy ][ $term_id ][ $key ] = wp_specialchars_decode( $value, ENT_QUOTES );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Remove empty taxonomy arrays.
|
||||
unset( $option_value[ $taxonomy ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $option_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $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 static function get_term_meta( $term, $taxonomy, $meta = null ) {
|
||||
/* Figure out the term id. */
|
||||
if ( is_int( $term ) ) {
|
||||
$term = get_term_by( 'id', $term, $taxonomy );
|
||||
}
|
||||
elseif ( is_string( $term ) ) {
|
||||
$term = get_term_by( 'slug', $term, $taxonomy );
|
||||
}
|
||||
|
||||
if ( is_object( $term ) && isset( $term->term_id ) ) {
|
||||
$term_id = $term->term_id;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tax_meta = self::get_term_tax_meta( $term_id, $taxonomy );
|
||||
|
||||
/*
|
||||
* Either return the complete array or a single value from it or false if the value does not exist
|
||||
* (shouldn't happen after merge with defaults, indicates typo in request).
|
||||
*/
|
||||
if ( ! isset( $meta ) ) {
|
||||
return $tax_meta;
|
||||
}
|
||||
|
||||
|
||||
if ( isset( $tax_meta[ 'wpseo_' . $meta ] ) ) {
|
||||
return $tax_meta[ 'wpseo_' . $meta ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current queried object and return the meta value.
|
||||
*
|
||||
* @param string $meta The meta field that is needed.
|
||||
*
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public static function get_meta_without_term( $meta ) {
|
||||
$term = $GLOBALS['wp_query']->get_queried_object();
|
||||
if ( ! $term || empty( $term->taxonomy ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::get_term_meta( $term, $term->taxonomy, $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving the values for the given term_id.
|
||||
*
|
||||
* @param int $term_id ID of the term to save data for.
|
||||
* @param string $taxonomy The taxonomy the term belongs to.
|
||||
* @param array $meta_values The values that will be saved.
|
||||
*/
|
||||
public static function set_values( $term_id, $taxonomy, array $meta_values ) {
|
||||
/* Validate the post values */
|
||||
$old = self::get_term_meta( $term_id, $taxonomy );
|
||||
$clean = self::validate_term_meta_data( $meta_values, $old );
|
||||
|
||||
self::save_clean_values( $term_id, $taxonomy, $clean );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting a single value to the term meta.
|
||||
*
|
||||
* @param int $term_id ID of the term to save data for.
|
||||
* @param string $taxonomy The taxonomy the term belongs to.
|
||||
* @param string $meta_key The target meta key to store the value in.
|
||||
* @param string $meta_value The value of the target meta key.
|
||||
*/
|
||||
public static function set_value( $term_id, $taxonomy, $meta_key, $meta_value ) {
|
||||
|
||||
if ( substr( strtolower( $meta_key ), 0, 6 ) !== 'wpseo_' ) {
|
||||
$meta_key = 'wpseo_' . $meta_key;
|
||||
}
|
||||
|
||||
self::set_values( $term_id, $taxonomy, [ $meta_key => $meta_value ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the keyword usages in the metas for the taxonomies/terms.
|
||||
*
|
||||
* @param string $keyword The keyword to look for.
|
||||
* @param string $current_term_id The current term id.
|
||||
* @param string $current_taxonomy The current taxonomy name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_keyword_usage( $keyword, $current_term_id, $current_taxonomy ) {
|
||||
$tax_meta = self::get_tax_meta();
|
||||
|
||||
|
||||
$found = [];
|
||||
// @todo Check for terms of all taxonomies, not only the current taxonomy.
|
||||
foreach ( $tax_meta as $taxonomy_name => $terms ) {
|
||||
foreach ( $terms as $term_id => $meta_values ) {
|
||||
$is_current = ( $current_taxonomy === $taxonomy_name && (string) $current_term_id === (string) $term_id );
|
||||
if ( ! $is_current && ! empty( $meta_values['wpseo_focuskw'] ) && $meta_values['wpseo_focuskw'] === $keyword ) {
|
||||
$found[] = $term_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ $keyword => $found ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving the values for the given term_id.
|
||||
*
|
||||
* @param int $term_id ID of the term to save data for.
|
||||
* @param string $taxonomy The taxonomy the term belongs to.
|
||||
* @param array $clean Array with clean values.
|
||||
*/
|
||||
private static function save_clean_values( $term_id, $taxonomy, array $clean ) {
|
||||
$tax_meta = self::get_tax_meta();
|
||||
|
||||
/* Add/remove the result to/from the original option value. */
|
||||
if ( $clean !== [] ) {
|
||||
$tax_meta[ $taxonomy ][ $term_id ] = $clean;
|
||||
}
|
||||
else {
|
||||
unset( $tax_meta[ $taxonomy ][ $term_id ] );
|
||||
if ( isset( $tax_meta[ $taxonomy ] ) && $tax_meta[ $taxonomy ] === [] ) {
|
||||
unset( $tax_meta[ $taxonomy ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent complete array validation.
|
||||
$tax_meta['wpseo_already_validated'] = true;
|
||||
|
||||
self::save_tax_meta( $tax_meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the meta from the options.
|
||||
*
|
||||
* @return void|array
|
||||
*/
|
||||
private static function get_tax_meta() {
|
||||
return get_option( self::$name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving the tax meta values to the database.
|
||||
*
|
||||
* @param array $tax_meta Array with the meta values for taxonomy.
|
||||
*/
|
||||
private static function save_tax_meta( $tax_meta ) {
|
||||
update_option( self::$name, $tax_meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the taxonomy meta for the given term_id and taxonomy.
|
||||
*
|
||||
* @param int $term_id The id of the term.
|
||||
* @param string $taxonomy Name of the taxonomy to which the term is attached.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_term_tax_meta( $term_id, $taxonomy ) {
|
||||
$tax_meta = self::get_tax_meta();
|
||||
|
||||
/* If we have data for the term, merge with defaults for complete array, otherwise set defaults. */
|
||||
if ( isset( $tax_meta[ $taxonomy ][ $term_id ] ) ) {
|
||||
return array_merge( self::$defaults_per_term, $tax_meta[ $taxonomy ][ $term_id ] );
|
||||
}
|
||||
|
||||
return self::$defaults_per_term;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
|
||||
|
||||
/**
|
||||
* Sitemap provider for author archives.
|
||||
*/
|
||||
class WPSEO_Author_Sitemap_Provider implements WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* The date helper.
|
||||
*
|
||||
* @var WPSEO_Date_Helper
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
/**
|
||||
* WPSEO_Author_Sitemap_Provider constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->date = new WPSEO_Date_Helper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
// If the author archives have been disabled, we don't do anything.
|
||||
if ( WPSEO_Options::get( 'disable-author', false ) || WPSEO_Options::get( 'noindex-author-wpseo', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $type === 'author';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the links for the sitemap index.
|
||||
*
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
|
||||
if ( ! $this->handles_type( 'author' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// @todo Consider doing this less often / when necessary. R.
|
||||
$this->update_user_meta();
|
||||
|
||||
$has_exclude_filter = has_filter( 'wpseo_sitemap_exclude_author' );
|
||||
|
||||
$query_arguments = [];
|
||||
|
||||
if ( ! $has_exclude_filter ) { // We only need full users if legacy filter(s) hooked to exclusion logic. R.
|
||||
$query_arguments['fields'] = 'ID';
|
||||
}
|
||||
|
||||
$users = $this->get_users( $query_arguments );
|
||||
|
||||
if ( $has_exclude_filter ) {
|
||||
$users = $this->exclude_users( $users );
|
||||
$users = wp_list_pluck( $users, 'ID' );
|
||||
}
|
||||
|
||||
if ( empty( $users ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$index = [];
|
||||
$page = 1;
|
||||
$user_pages = array_chunk( $users, $max_entries );
|
||||
|
||||
if ( count( $user_pages ) === 1 ) {
|
||||
$page = '';
|
||||
}
|
||||
|
||||
foreach ( $user_pages as $users_page ) {
|
||||
|
||||
$user_id = array_shift( $users_page ); // Time descending, first user on page is most recently updated.
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
$index[] = [
|
||||
'loc' => WPSEO_Sitemaps_Router::get_base_url( 'author-sitemap' . $page . '.xml' ),
|
||||
'lastmod' => ( $user->_yoast_wpseo_profile_updated ) ? $this->date->format_timestamp( $user->_yoast_wpseo_profile_updated ) : null,
|
||||
];
|
||||
|
||||
$page++;
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve users, taking account of all necessary exclusions.
|
||||
*
|
||||
* @param array $arguments Arguments to add.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_users( $arguments = [] ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$defaults = [
|
||||
'who' => 'authors',
|
||||
'meta_key' => '_yoast_wpseo_profile_updated',
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'DESC',
|
||||
'meta_query' => [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'key' => $wpdb->get_blog_prefix() . 'user_level',
|
||||
'value' => '0',
|
||||
'compare' => '!=',
|
||||
],
|
||||
[
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => 'wpseo_noindex_author',
|
||||
'value' => 'on',
|
||||
'compare' => '!=',
|
||||
],
|
||||
[
|
||||
'key' => 'wpseo_noindex_author',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-author-noposts-wpseo', true ) ) {
|
||||
$defaults['who'] = ''; // Otherwise it cancels out next argument.
|
||||
$author_archive = new Author_Archive_Helper();
|
||||
$defaults['has_published_posts'] = $author_archive->get_author_archive_post_types();
|
||||
}
|
||||
|
||||
return get_users( array_merge( $defaults, $arguments ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get set of sitemap link data.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
* @param int $current_page Current page of the sitemap.
|
||||
*
|
||||
* @throws OutOfBoundsException When an invalid page is requested.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
|
||||
$links = [];
|
||||
|
||||
if ( ! $this->handles_type( 'author' ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$user_criteria = [
|
||||
'offset' => ( ( $current_page - 1 ) * $max_entries ),
|
||||
'number' => $max_entries,
|
||||
];
|
||||
|
||||
$users = $this->get_users( $user_criteria );
|
||||
|
||||
// Throw an exception when there are no users in the sitemap.
|
||||
if ( count( $users ) === 0 ) {
|
||||
throw new OutOfBoundsException( 'Invalid sitemap page requested' );
|
||||
}
|
||||
|
||||
$users = $this->exclude_users( $users );
|
||||
if ( empty( $users ) ) {
|
||||
$users = [];
|
||||
}
|
||||
|
||||
$time = time();
|
||||
|
||||
foreach ( $users as $user ) {
|
||||
|
||||
$author_link = get_author_posts_url( $user->ID );
|
||||
|
||||
if ( empty( $author_link ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mod = $time;
|
||||
|
||||
if ( isset( $user->_yoast_wpseo_profile_updated ) ) {
|
||||
$mod = $user->_yoast_wpseo_profile_updated;
|
||||
}
|
||||
|
||||
$url = [
|
||||
'loc' => $author_link,
|
||||
'mod' => date( DATE_W3C, $mod ),
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
'chf' => 'daily',
|
||||
'pri' => 1,
|
||||
];
|
||||
|
||||
/** This filter is documented at inc/sitemaps/class-post-type-sitemap-provider.php */
|
||||
$url = apply_filters( 'wpseo_sitemap_entry', $url, 'user', $user );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
$links[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update any users that don't have last profile update timestamp.
|
||||
*
|
||||
* @return int Count of users updated.
|
||||
*/
|
||||
protected function update_user_meta() {
|
||||
|
||||
$user_criteria = [
|
||||
'who' => 'authors',
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => '_yoast_wpseo_profile_updated',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
];
|
||||
$users = get_users( $user_criteria );
|
||||
|
||||
$time = time();
|
||||
|
||||
foreach ( $users as $user ) {
|
||||
update_user_meta( $user->ID, '_yoast_wpseo_profile_updated', $time );
|
||||
}
|
||||
|
||||
return count( $users );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap legacy filter to deduplicate calls.
|
||||
*
|
||||
* @param array $users Array of user objects to filter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function exclude_users( $users ) {
|
||||
|
||||
/**
|
||||
* Filter the authors, included in XML sitemap.
|
||||
*
|
||||
* @param array $users Array of user objects to filter.
|
||||
*/
|
||||
return apply_filters( 'wpseo_sitemap_exclude_author', $users );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,706 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap provider for author archives.
|
||||
*/
|
||||
class WPSEO_Post_Type_Sitemap_Provider implements WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* Holds image parser instance.
|
||||
*
|
||||
* @var WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected static $image_parser;
|
||||
|
||||
/**
|
||||
* Holds instance of classifier for a link.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected static $classifier;
|
||||
|
||||
/**
|
||||
* Determines whether images should be included in the XML sitemap.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $include_images;
|
||||
|
||||
/**
|
||||
* Set up object properties for data reuse.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'save_post', [ $this, 'save_post' ] );
|
||||
|
||||
/**
|
||||
* Filter - Allows excluding images from the XML sitemap.
|
||||
*
|
||||
* @param bool unsigned True to include, false to exclude.
|
||||
*/
|
||||
$this->include_images = apply_filters( 'wpseo_xml_sitemap_include_images', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Image Parser.
|
||||
*
|
||||
* @return WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected function get_image_parser() {
|
||||
if ( ! isset( self::$image_parser ) ) {
|
||||
self::$image_parser = new WPSEO_Sitemap_Image_Parser();
|
||||
}
|
||||
|
||||
return self::$image_parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Classifier for a link.
|
||||
*
|
||||
* @return WPSEO_Link_Type_Classifier
|
||||
*/
|
||||
protected function get_classifier() {
|
||||
if ( ! isset( self::$classifier ) ) {
|
||||
self::$classifier = new WPSEO_Link_Type_Classifier( home_url() );
|
||||
}
|
||||
|
||||
return self::$classifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
|
||||
return post_type_exists( $type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sitemap links.
|
||||
*
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
global $wpdb;
|
||||
|
||||
$post_types = WPSEO_Post_Type::get_accessible_post_types();
|
||||
$post_types = array_filter( $post_types, [ $this, 'is_valid_post_type' ] );
|
||||
$last_modified_times = WPSEO_Sitemaps::get_last_modified_gmt( $post_types, true );
|
||||
$index = [];
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
|
||||
$total_count = $this->get_post_type_count( $post_type );
|
||||
|
||||
$max_pages = 1;
|
||||
if ( $total_count > $max_entries ) {
|
||||
$max_pages = (int) ceil( $total_count / $max_entries );
|
||||
}
|
||||
|
||||
$all_dates = [];
|
||||
|
||||
if ( $max_pages > 1 ) {
|
||||
$post_statuses = array_map( 'esc_sql', WPSEO_Sitemaps::get_post_statuses( $post_type ) );
|
||||
|
||||
$sql = "
|
||||
SELECT post_modified_gmt
|
||||
FROM ( SELECT @rownum:=0 ) init
|
||||
JOIN {$wpdb->posts} USE INDEX( type_status_date )
|
||||
WHERE post_status IN ('" . implode( "','", $post_statuses ) . "')
|
||||
AND post_type = %s
|
||||
AND ( @rownum:=@rownum+1 ) %% %d = 0
|
||||
ORDER BY post_modified_gmt ASC
|
||||
";
|
||||
|
||||
$all_dates = $wpdb->get_col( $wpdb->prepare( $sql, $post_type, $max_entries ) );
|
||||
}
|
||||
|
||||
for ( $page_counter = 0; $page_counter < $max_pages; $page_counter++ ) {
|
||||
|
||||
$current_page = ( $max_pages > 1 ) ? ( $page_counter + 1 ) : '';
|
||||
$date = false;
|
||||
|
||||
if ( empty( $current_page ) || $current_page === $max_pages ) {
|
||||
|
||||
if ( ! empty( $last_modified_times[ $post_type ] ) ) {
|
||||
$date = $last_modified_times[ $post_type ];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$date = $all_dates[ $page_counter ];
|
||||
}
|
||||
|
||||
$index[] = [
|
||||
'loc' => WPSEO_Sitemaps_Router::get_base_url( $post_type . '-sitemap' . $current_page . '.xml' ),
|
||||
'lastmod' => $date,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get set of sitemap link data.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
* @param int $current_page Current page of the sitemap.
|
||||
*
|
||||
* @throws OutOfBoundsException When an invalid page is requested.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
|
||||
$links = [];
|
||||
$post_type = $type;
|
||||
|
||||
if ( ! $this->is_valid_post_type( $post_type ) ) {
|
||||
throw new OutOfBoundsException( 'Invalid sitemap page requested' );
|
||||
}
|
||||
|
||||
$steps = min( 100, $max_entries );
|
||||
$offset = ( $current_page > 1 ) ? ( ( $current_page - 1 ) * $max_entries ) : 0;
|
||||
$total = ( $offset + $max_entries );
|
||||
|
||||
$post_type_entries = $this->get_post_type_count( $post_type );
|
||||
|
||||
if ( $total > $post_type_entries ) {
|
||||
$total = $post_type_entries;
|
||||
}
|
||||
|
||||
if ( $current_page === 1 ) {
|
||||
$links = array_merge( $links, $this->get_first_links( $post_type ) );
|
||||
}
|
||||
|
||||
// If total post type count is lower than the offset, an invalid page is requested.
|
||||
if ( $post_type_entries < $offset ) {
|
||||
throw new OutOfBoundsException( 'Invalid sitemap page requested' );
|
||||
}
|
||||
|
||||
if ( $post_type_entries === 0 ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$posts_to_exclude = $this->get_excluded_posts( $type );
|
||||
|
||||
while ( $total > $offset ) {
|
||||
|
||||
$posts = $this->get_posts( $post_type, $steps, $offset );
|
||||
|
||||
$offset += $steps;
|
||||
|
||||
if ( empty( $posts ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
|
||||
if ( in_array( $post->ID, $posts_to_exclude, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( WPSEO_Meta::get_value( 'meta-robots-noindex', $post->ID ) === '1' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $this->get_url( $post );
|
||||
|
||||
if ( ! isset( $url['loc'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter URL entry before it gets added to the sitemap.
|
||||
*
|
||||
* @param array $url Array of URL parts.
|
||||
* @param string $type URL type.
|
||||
* @param object $post Data object for the URL.
|
||||
*/
|
||||
$url = apply_filters( 'wpseo_sitemap_entry', $url, 'post', $post );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
$links[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $post, $url );
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for relevant post type before invalidation.
|
||||
*
|
||||
* @param int $post_id Post ID to possibly invalidate for.
|
||||
*/
|
||||
public function save_post( $post_id ) {
|
||||
|
||||
if ( $this->is_valid_post_type( get_post_type( $post_id ) ) ) {
|
||||
WPSEO_Sitemaps_Cache::invalidate_post( $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if post type should be present in sitemaps.
|
||||
*
|
||||
* @param string $post_type Post type string to check for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_post_type( $post_type ) {
|
||||
if ( ! WPSEO_Post_Type::is_post_type_accessible( $post_type ) || ! WPSEO_Post_Type::is_post_type_indexable( $post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter decision if post type is excluded from the XML sitemap.
|
||||
*
|
||||
* @param bool $exclude Default false.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_sitemap_exclude_post_type', false, $post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list with the excluded post ids.
|
||||
*
|
||||
* @param string $post_type Post type.
|
||||
*
|
||||
* @return array Array with post ids to exclude.
|
||||
*/
|
||||
protected function get_excluded_posts( $post_type ) {
|
||||
$excluded_posts_ids = [];
|
||||
|
||||
$page_on_front_id = ( $post_type === 'page' ) ? (int) get_option( 'page_on_front' ) : 0;
|
||||
if ( $page_on_front_id > 0 ) {
|
||||
$excluded_posts_ids[] = $page_on_front_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_exclude_from_sitemap_by_post_ids' - Allow extending and modifying the posts to exclude.
|
||||
*
|
||||
* @api array $posts_to_exclude The posts to exclude.
|
||||
*/
|
||||
$excluded_posts_ids = apply_filters( 'wpseo_exclude_from_sitemap_by_post_ids', $excluded_posts_ids );
|
||||
if ( ! is_array( $excluded_posts_ids ) ) {
|
||||
$excluded_posts_ids = [];
|
||||
}
|
||||
|
||||
$excluded_posts_ids = array_map( 'intval', $excluded_posts_ids );
|
||||
|
||||
$page_for_posts_id = ( $post_type === 'page' ) ? (int) get_option( 'page_for_posts' ) : 0;
|
||||
if ( $page_for_posts_id > 0 ) {
|
||||
$excluded_posts_ids[] = $page_for_posts_id;
|
||||
}
|
||||
|
||||
return array_unique( $excluded_posts_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of posts for post type.
|
||||
*
|
||||
* @param string $post_type Post type to retrieve count for.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_post_type_count( $post_type ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
/**
|
||||
* Filter JOIN query part for type count of post type.
|
||||
*
|
||||
* @param string $join SQL part, defaults to empty string.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
$join_filter = apply_filters( 'wpseo_typecount_join', '', $post_type );
|
||||
|
||||
/**
|
||||
* Filter WHERE query part for type count of post type.
|
||||
*
|
||||
* @param string $where SQL part, defaults to empty string.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
$where_filter = apply_filters( 'wpseo_typecount_where', '', $post_type );
|
||||
|
||||
$where = $this->get_sql_where_clause( $post_type );
|
||||
|
||||
$sql = "
|
||||
SELECT COUNT({$wpdb->posts}.ID)
|
||||
FROM {$wpdb->posts}
|
||||
{$join_filter}
|
||||
{$where}
|
||||
{$where_filter}
|
||||
";
|
||||
|
||||
return (int) $wpdb->get_var( $sql );
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces set of links to prepend at start of first sitemap page.
|
||||
*
|
||||
* @param string $post_type Post type to produce links for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_first_links( $post_type ) {
|
||||
|
||||
$links = [];
|
||||
$archive_url = false;
|
||||
|
||||
if ( $post_type === 'page' ) {
|
||||
|
||||
$page_on_front_id = (int) get_option( 'page_on_front' );
|
||||
if ( $page_on_front_id > 0 ) {
|
||||
$front_page = $this->get_url(
|
||||
get_post( $page_on_front_id )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $front_page ) ) {
|
||||
$front_page = [
|
||||
'loc' => WPSEO_Utils::home_url(),
|
||||
];
|
||||
}
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
$front_page['chf'] = 'daily';
|
||||
$front_page['pri'] = 1;
|
||||
|
||||
$links[] = $front_page;
|
||||
}
|
||||
elseif ( $post_type !== 'page' ) {
|
||||
/**
|
||||
* Filter the URL Yoast SEO uses in the XML sitemap for this post type archive.
|
||||
*
|
||||
* @param string $archive_url The URL of this archive
|
||||
* @param string $post_type The post type this archive is for.
|
||||
*/
|
||||
$archive_url = apply_filters(
|
||||
'wpseo_sitemap_post_type_archive_link',
|
||||
$this->get_post_type_archive_link( $post_type ),
|
||||
$post_type
|
||||
);
|
||||
}
|
||||
|
||||
if ( $archive_url ) {
|
||||
|
||||
$links[] = [
|
||||
'loc' => $archive_url,
|
||||
'mod' => WPSEO_Sitemaps::get_last_modified_gmt( $post_type ),
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
'chf' => 'daily',
|
||||
'pri' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL for a post type archive.
|
||||
*
|
||||
* @since 5.3
|
||||
*
|
||||
* @param string $post_type Post type.
|
||||
*
|
||||
* @return string|bool URL or false if it should be excluded.
|
||||
*/
|
||||
protected function get_post_type_archive_link( $post_type ) {
|
||||
|
||||
$pt_archive_page_id = -1;
|
||||
|
||||
if ( $post_type === 'post' ) {
|
||||
|
||||
if ( get_option( 'show_on_front' ) === 'posts' ) {
|
||||
return WPSEO_Utils::home_url();
|
||||
}
|
||||
|
||||
$pt_archive_page_id = (int) get_option( 'page_for_posts' );
|
||||
|
||||
// Post archive should be excluded if posts page isn't set.
|
||||
if ( $pt_archive_page_id <= 0 ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $this->is_post_type_archive_indexable( $post_type, $pt_archive_page_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_post_type_archive_link( $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a post type archive is indexable.
|
||||
*
|
||||
* @since 11.5
|
||||
*
|
||||
* @param string $post_type Post type.
|
||||
* @param int $archive_page_id The page id.
|
||||
*
|
||||
* @return bool True when post type archive is indexable.
|
||||
*/
|
||||
protected function is_post_type_archive_indexable( $post_type, $archive_page_id = -1 ) {
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-ptarchive-' . $post_type, false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the page which is dedicated to this post type archive.
|
||||
*
|
||||
* @since 9.3
|
||||
*
|
||||
* @param string $archive_page_id The post_id of the page.
|
||||
* @param string $post_type The post type this archive is for.
|
||||
*/
|
||||
$archive_page_id = (int) apply_filters( 'wpseo_sitemap_page_for_post_type_archive', $archive_page_id, $post_type );
|
||||
|
||||
if ( $archive_page_id > 0 && WPSEO_Meta::get_value( 'meta-robots-noindex', $archive_page_id ) === '1' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve set of posts with optimized query routine.
|
||||
*
|
||||
* @param string $post_type Post type to retrieve.
|
||||
* @param int $count Count of posts to retrieve.
|
||||
* @param int $offset Starting offset.
|
||||
*
|
||||
* @return object[]
|
||||
*/
|
||||
protected function get_posts( $post_type, $count, $offset ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
static $filters = [];
|
||||
|
||||
if ( ! isset( $filters[ $post_type ] ) ) {
|
||||
// Make sure you're wpdb->preparing everything you throw into this!!
|
||||
$filters[ $post_type ] = [
|
||||
/**
|
||||
* Filter JOIN query part for the post type.
|
||||
*
|
||||
* @param string $join SQL part, defaults to false.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
'join' => apply_filters( 'wpseo_posts_join', false, $post_type ),
|
||||
|
||||
/**
|
||||
* Filter WHERE query part for the post type.
|
||||
*
|
||||
* @param string $where SQL part, defaults to false.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
'where' => apply_filters( 'wpseo_posts_where', false, $post_type ),
|
||||
];
|
||||
}
|
||||
|
||||
$join_filter = $filters[ $post_type ]['join'];
|
||||
$where_filter = $filters[ $post_type ]['where'];
|
||||
$where = $this->get_sql_where_clause( $post_type );
|
||||
|
||||
/*
|
||||
* Optimized query per this thread:
|
||||
* {@link http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-performance-suggestion}.
|
||||
* Also see {@link http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/}.
|
||||
*/
|
||||
$sql = "
|
||||
SELECT l.ID, post_title, post_content, post_name, post_parent, post_author, post_status, post_modified_gmt, post_date, post_date_gmt
|
||||
FROM (
|
||||
SELECT {$wpdb->posts}.ID
|
||||
FROM {$wpdb->posts}
|
||||
{$join_filter}
|
||||
{$where}
|
||||
{$where_filter}
|
||||
ORDER BY {$wpdb->posts}.post_modified ASC LIMIT %d OFFSET %d
|
||||
)
|
||||
o JOIN {$wpdb->posts} l ON l.ID = o.ID
|
||||
";
|
||||
|
||||
$posts = $wpdb->get_results( $wpdb->prepare( $sql, $count, $offset ) );
|
||||
|
||||
$post_ids = [];
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
$post->post_type = $post_type;
|
||||
$post->filter = 'sample';
|
||||
$post->ID = (int) $post->ID;
|
||||
$post->post_parent = (int) $post->post_parent;
|
||||
$post->post_author = (int) $post->post_author;
|
||||
$post_ids[] = $post->ID;
|
||||
}
|
||||
|
||||
update_meta_cache( 'post', $post_ids );
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an SQL where clause for a given post type.
|
||||
*
|
||||
* @param string $post_type Post type slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_sql_where_clause( $post_type ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$join = '';
|
||||
$post_statuses = array_map( 'esc_sql', WPSEO_Sitemaps::get_post_statuses( $post_type ) );
|
||||
$status_where = "{$wpdb->posts}.post_status IN ('" . implode( "','", $post_statuses ) . "')";
|
||||
|
||||
// Based on WP_Query->get_posts(). R.
|
||||
if ( $post_type === 'attachment' ) {
|
||||
$join = " LEFT JOIN {$wpdb->posts} AS p2 ON ({$wpdb->posts}.post_parent = p2.ID) ";
|
||||
$parent_statuses = array_diff( $post_statuses, [ 'inherit' ] );
|
||||
$status_where = "p2.post_status IN ('" . implode( "','", $parent_statuses ) . "') AND p2.post_password = ''";
|
||||
}
|
||||
|
||||
$where_clause = "
|
||||
{$join}
|
||||
WHERE {$status_where}
|
||||
AND {$wpdb->posts}.post_type = %s
|
||||
AND {$wpdb->posts}.post_password = ''
|
||||
AND {$wpdb->posts}.post_date != '0000-00-00 00:00:00'
|
||||
";
|
||||
|
||||
return $wpdb->prepare( $where_clause, $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce array of URL parts for given post object.
|
||||
*
|
||||
* @param object $post Post object to get URL parts for.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function get_url( $post ) {
|
||||
|
||||
$url = [];
|
||||
|
||||
/**
|
||||
* Filter the URL Yoast SEO uses in the XML sitemap.
|
||||
*
|
||||
* Note that only absolute local URLs are allowed as the check after this removes external URLs.
|
||||
*
|
||||
* @param string $url URL to use in the XML sitemap
|
||||
* @param object $post Post object for the URL.
|
||||
*/
|
||||
$url['loc'] = apply_filters( 'wpseo_xml_sitemap_post_url', get_permalink( $post ), $post );
|
||||
|
||||
/*
|
||||
* Do not include external URLs.
|
||||
*
|
||||
* {@link https://wordpress.org/plugins/page-links-to/} can rewrite permalinks to external URLs.
|
||||
*/
|
||||
if ( $this->get_classifier()->classify( $url['loc'] ) === WPSEO_Link::TYPE_EXTERNAL ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$modified = max( $post->post_modified_gmt, $post->post_date_gmt );
|
||||
|
||||
if ( $modified !== '0000-00-00 00:00:00' ) {
|
||||
$url['mod'] = $modified;
|
||||
}
|
||||
|
||||
$url['chf'] = 'daily'; // Deprecated, kept for backwards data compat. R.
|
||||
|
||||
$canonical = WPSEO_Meta::get_value( 'canonical', $post->ID );
|
||||
|
||||
if ( $canonical !== '' && $canonical !== $url['loc'] ) {
|
||||
/*
|
||||
* Let's assume that if a canonical is set for this page and it's different from
|
||||
* the URL of this post, that page is either already in the XML sitemap OR is on
|
||||
* an external site, either way, we shouldn't include it here.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
unset( $canonical );
|
||||
|
||||
$url['pri'] = 1; // Deprecated, kept for backwards data compat. R.
|
||||
|
||||
if ( $this->include_images ) {
|
||||
$url['images'] = $this->get_image_parser()->get_images( $post );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Get all the options.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function get_options() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 7.0', 'WPSEO_Options::get' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Home URL.
|
||||
*
|
||||
* @deprecated 11.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_home_url() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 11.5', 'WPSEO_Utils::home_url' );
|
||||
|
||||
return WPSEO_Utils::home_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get front page ID.
|
||||
*
|
||||
* @deprecated 11.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_page_on_front_id() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 11.5' );
|
||||
|
||||
return (int) get_option( 'page_on_front' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page for posts ID.
|
||||
*
|
||||
* @deprecated 11.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_page_for_posts_id() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 11.5' );
|
||||
|
||||
return (int) get_option( 'page_for_posts' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap Cache Data object, manages sitemap data stored in cache.
|
||||
*/
|
||||
class WPSEO_Sitemap_Cache_Data implements WPSEO_Sitemap_Cache_Data_Interface, Serializable {
|
||||
|
||||
/**
|
||||
* Sitemap XML data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $sitemap = '';
|
||||
|
||||
/**
|
||||
* Status of the sitemap, usable or not.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $status = self::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Set the sitemap XML data
|
||||
*
|
||||
* @param string $sitemap XML Content of the sitemap.
|
||||
*/
|
||||
public function set_sitemap( $sitemap ) {
|
||||
|
||||
if ( ! is_string( $sitemap ) ) {
|
||||
$sitemap = '';
|
||||
}
|
||||
|
||||
$this->sitemap = $sitemap;
|
||||
|
||||
/*
|
||||
* Empty sitemap is not usable.
|
||||
*/
|
||||
if ( ! empty( $sitemap ) ) {
|
||||
$this->set_status( self::OK );
|
||||
}
|
||||
else {
|
||||
$this->set_status( self::ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status of the sitemap, is it usable.
|
||||
*
|
||||
* @param bool|string $valid Is the sitemap valid or not.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_status( $valid ) {
|
||||
|
||||
if ( self::OK === $valid ) {
|
||||
$this->status = self::OK;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( self::ERROR === $valid ) {
|
||||
$this->status = self::ERROR;
|
||||
$this->sitemap = '';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->status = self::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the sitemap usable.
|
||||
*
|
||||
* @return bool True if usable, False if bad or unknown.
|
||||
*/
|
||||
public function is_usable() {
|
||||
|
||||
return self::OK === $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the XML content of the sitemap.
|
||||
*
|
||||
* @return string The content of the sitemap.
|
||||
*/
|
||||
public function get_sitemap() {
|
||||
|
||||
return $this->sitemap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of the sitemap.
|
||||
*
|
||||
* @return string Status of the sitemap, 'ok'/'error'/'unknown'.
|
||||
*/
|
||||
public function get_status() {
|
||||
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* String representation of object.
|
||||
*
|
||||
* @link http://php.net/manual/en/serializable.serialize.php
|
||||
*
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @return string The string representation of the object or null.
|
||||
*/
|
||||
public function serialize() {
|
||||
|
||||
$data = [
|
||||
'status' => $this->status,
|
||||
'xml' => $this->sitemap,
|
||||
];
|
||||
|
||||
return serialize( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the object.
|
||||
*
|
||||
* @link http://php.net/manual/en/serializable.unserialize.php
|
||||
*
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @param string $serialized The string representation of the object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unserialize( $serialized ) {
|
||||
|
||||
$data = unserialize( $serialized );
|
||||
$this->set_sitemap( $data['xml'] );
|
||||
$this->set_status( $data['status'] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parses images from the given post.
|
||||
*/
|
||||
class WPSEO_Sitemap_Image_Parser {
|
||||
|
||||
/**
|
||||
* Holds the home_url() value to speed up loops.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $home_url = '';
|
||||
|
||||
/**
|
||||
* Holds site URL hostname.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $host = '';
|
||||
|
||||
/**
|
||||
* Holds site URL protocol.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $scheme = 'http';
|
||||
|
||||
/**
|
||||
* Cached set of attachments for multiple posts.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attachments = [];
|
||||
|
||||
/**
|
||||
* Holds blog charset value for use in DOM parsing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Set up URL properties for reuse.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->home_url = home_url();
|
||||
$parsed_home = wp_parse_url( $this->home_url );
|
||||
|
||||
if ( ! empty( $parsed_home['host'] ) ) {
|
||||
$this->host = str_replace( 'www.', '', $parsed_home['host'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $parsed_home['scheme'] ) ) {
|
||||
$this->scheme = $parsed_home['scheme'];
|
||||
}
|
||||
|
||||
$this->charset = esc_attr( get_bloginfo( 'charset' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get set of image data sets for the given post.
|
||||
*
|
||||
* @param object $post Post object to get images for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_images( $post ) {
|
||||
|
||||
$images = [];
|
||||
|
||||
if ( ! is_object( $post ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post->ID );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
|
||||
$src = $this->get_absolute_url( $this->image_url( $thumbnail_id ) );
|
||||
$alt = WPSEO_Image_Utils::get_alt_tag( $thumbnail_id );
|
||||
$title = get_post_field( 'post_title', $thumbnail_id );
|
||||
$images[] = $this->get_image_item( $post, $src, $title, $alt );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_sitemap_content_before_parse_html_images' - Filters the post content
|
||||
* before it is parsed for images.
|
||||
*
|
||||
* @param string $content The raw/unprocessed post content.
|
||||
*/
|
||||
$content = apply_filters( 'wpseo_sitemap_content_before_parse_html_images', $post->post_content );
|
||||
|
||||
$unfiltered_images = $this->parse_html_images( $content );
|
||||
|
||||
foreach ( $unfiltered_images as $image ) {
|
||||
$images[] = $this->get_image_item( $post, $image['src'], $image['title'], $image['alt'] );
|
||||
}
|
||||
|
||||
foreach ( $this->parse_galleries( $content, $post->ID ) as $attachment ) {
|
||||
|
||||
$src = $this->get_absolute_url( $this->image_url( $attachment->ID ) );
|
||||
$alt = WPSEO_Image_Utils::get_alt_tag( $attachment->ID );
|
||||
|
||||
$images[] = $this->get_image_item( $post, $src, $attachment->post_title, $alt );
|
||||
}
|
||||
|
||||
if ( $post->post_type === 'attachment' && wp_attachment_is_image( $post ) ) {
|
||||
|
||||
$src = $this->get_absolute_url( $this->image_url( $post->ID ) );
|
||||
$alt = WPSEO_Image_Utils::get_alt_tag( $post->ID );
|
||||
|
||||
$images[] = $this->get_image_item( $post, $src, $post->post_title, $alt );
|
||||
}
|
||||
|
||||
foreach ( $images as $key => $image ) {
|
||||
|
||||
if ( empty( $image['src'] ) ) {
|
||||
unset( $images[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter images to be included for the post in XML sitemap.
|
||||
*
|
||||
* @param array $images Array of image items.
|
||||
* @param int $post_id ID of the post.
|
||||
*/
|
||||
$images = apply_filters( 'wpseo_sitemap_urlimages', $images, $post->ID );
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the images in the term description.
|
||||
*
|
||||
* @param object $term Term to get images from description for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_term_images( $term ) {
|
||||
|
||||
$images = $this->parse_html_images( $term->description );
|
||||
|
||||
foreach ( $this->parse_galleries( $term->description ) as $attachment ) {
|
||||
|
||||
$images[] = [
|
||||
'src' => $this->get_absolute_url( $this->image_url( $attachment->ID ) ),
|
||||
'title' => $attachment->post_title,
|
||||
'alt' => WPSEO_Image_Utils::get_alt_tag( $attachment->ID ),
|
||||
];
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse `<img />` tags in content.
|
||||
*
|
||||
* @param string $content Content string to parse.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parse_html_images( $content ) {
|
||||
|
||||
$images = [];
|
||||
|
||||
if ( ! class_exists( 'DOMDocument' ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
// Prevent DOMDocument from bubbling warnings about invalid HTML.
|
||||
libxml_use_internal_errors( true );
|
||||
|
||||
$post_dom = new DOMDocument();
|
||||
$post_dom->loadHTML( '<?xml encoding="' . $this->charset . '">' . $content );
|
||||
|
||||
// Clear the errors, so they don't get kept in memory.
|
||||
libxml_clear_errors();
|
||||
|
||||
/** @var DOMElement $img */
|
||||
foreach ( $post_dom->getElementsByTagName( 'img' ) as $img ) {
|
||||
|
||||
$src = $img->getAttribute( 'src' );
|
||||
|
||||
if ( empty( $src ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $img->getAttribute( 'class' );
|
||||
|
||||
if ( // This detects WP-inserted images, which we need to upsize. R.
|
||||
! empty( $class )
|
||||
&& ( false === strpos( $class, 'size-full' ) )
|
||||
&& preg_match( '|wp-image-(?P<id>\d+)|', $class, $matches )
|
||||
&& get_post_status( $matches['id'] )
|
||||
) {
|
||||
$src = $this->image_url( $matches['id'] );
|
||||
}
|
||||
|
||||
$src = $this->get_absolute_url( $src );
|
||||
|
||||
if ( strpos( $src, $this->host ) === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $src !== esc_url( $src ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$images[] = [
|
||||
'src' => $src,
|
||||
'title' => $img->getAttribute( 'title' ),
|
||||
'alt' => $img->getAttribute( 'alt' ),
|
||||
];
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse gallery shortcodes in a given content.
|
||||
*
|
||||
* @param string $content Content string.
|
||||
* @param int $post_id Optional. ID of post being parsed.
|
||||
*
|
||||
* @return array Set of attachment objects.
|
||||
*/
|
||||
protected function parse_galleries( $content, $post_id = 0 ) {
|
||||
|
||||
$attachments = [];
|
||||
$galleries = $this->get_content_galleries( $content );
|
||||
|
||||
foreach ( $galleries as $gallery ) {
|
||||
|
||||
$id = $post_id;
|
||||
|
||||
if ( ! empty( $gallery['id'] ) ) {
|
||||
$id = intval( $gallery['id'] );
|
||||
}
|
||||
|
||||
// Forked from core gallery_shortcode() to have exact same logic. R.
|
||||
if ( ! empty( $gallery['ids'] ) ) {
|
||||
$gallery['include'] = $gallery['ids'];
|
||||
}
|
||||
|
||||
$gallery_attachments = $this->get_gallery_attachments( $id, $gallery );
|
||||
|
||||
$attachments = array_merge( $attachments, $gallery_attachments );
|
||||
}
|
||||
|
||||
return array_unique( $attachments, SORT_REGULAR );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves galleries from the passed content.
|
||||
*
|
||||
* Forked from core to skip executing shortcodes for performance.
|
||||
*
|
||||
* @param string $content Content to parse for shortcodes.
|
||||
*
|
||||
* @return array A list of arrays, each containing gallery data.
|
||||
*/
|
||||
protected function get_content_galleries( $content ) {
|
||||
|
||||
$galleries = [];
|
||||
|
||||
if ( ! preg_match_all( '/' . get_shortcode_regex( [ 'gallery' ] ) . '/s', $content, $matches, PREG_SET_ORDER ) ) {
|
||||
return $galleries;
|
||||
}
|
||||
|
||||
foreach ( $matches as $shortcode ) {
|
||||
|
||||
$attributes = shortcode_parse_atts( $shortcode[3] );
|
||||
|
||||
if ( $attributes === '' ) { // Valid shortcode without any attributes. R.
|
||||
$attributes = [];
|
||||
}
|
||||
|
||||
$galleries[] = $attributes;
|
||||
}
|
||||
|
||||
return $galleries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image item array with filters applied.
|
||||
*
|
||||
* @param WP_Post $post Post object for the context.
|
||||
* @param string $src Image URL.
|
||||
* @param string $title Optional image title.
|
||||
* @param string $alt Optional image alt text.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_image_item( $post, $src, $title = '', $alt = '' ) {
|
||||
|
||||
$image = [];
|
||||
|
||||
/**
|
||||
* Filter image URL to be included in XML sitemap for the post.
|
||||
*
|
||||
* @param string $src Image URL.
|
||||
* @param object $post Post object.
|
||||
*/
|
||||
$image['src'] = apply_filters( 'wpseo_xml_sitemap_img_src', $src, $post );
|
||||
|
||||
if ( ! empty( $title ) ) {
|
||||
$image['title'] = $title;
|
||||
}
|
||||
|
||||
if ( ! empty( $alt ) ) {
|
||||
$image['alt'] = $alt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter image data to be included in XML sitemap for the post.
|
||||
*
|
||||
* @param array $image {
|
||||
* Array of image data.
|
||||
*
|
||||
* @type string $src Image URL.
|
||||
* @type string $title Image title attribute (optional).
|
||||
* @type string $alt Image alt attribute (optional).
|
||||
* }
|
||||
*
|
||||
* @param object $post Post object.
|
||||
*/
|
||||
return apply_filters( 'wpseo_xml_sitemap_img', $image, $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attached image URL with filters applied. Adapted from core for speed.
|
||||
*
|
||||
* @param int $post_id ID of the post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function image_url( $post_id ) {
|
||||
|
||||
static $uploads;
|
||||
|
||||
if ( empty( $uploads ) ) {
|
||||
$uploads = wp_upload_dir();
|
||||
}
|
||||
|
||||
if ( $uploads['error'] !== false ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$file = get_post_meta( $post_id, '_wp_attached_file', true );
|
||||
|
||||
if ( empty( $file ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check that the upload base exists in the file location.
|
||||
if ( strpos( $file, $uploads['basedir'] ) === 0 ) {
|
||||
$src = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
|
||||
}
|
||||
elseif ( strpos( $file, 'wp-content/uploads' ) !== false ) {
|
||||
$src = $uploads['baseurl'] . substr( $file, ( strpos( $file, 'wp-content/uploads' ) + 18 ) );
|
||||
}
|
||||
else {
|
||||
// It's a newly uploaded file, therefore $file is relative to the baseurl.
|
||||
$src = $uploads['baseurl'] . '/' . $file;
|
||||
}
|
||||
|
||||
return apply_filters( 'wp_get_attachment_url', $src, $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make absolute URL for domain or protocol-relative one.
|
||||
*
|
||||
* @param string $src URL to process.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_absolute_url( $src ) {
|
||||
|
||||
if ( empty( $src ) || ! is_string( $src ) ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
if ( WPSEO_Utils::is_url_relative( $src ) === true ) {
|
||||
|
||||
if ( $src[0] !== '/' ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
// The URL is relative, we'll have to make it absolute.
|
||||
return $this->home_url . $src;
|
||||
}
|
||||
|
||||
if ( strpos( $src, 'http' ) !== 0 ) {
|
||||
// Protocol relative URL, we add the scheme as the standard requires a protocol.
|
||||
return $this->scheme . ':' . $src;
|
||||
}
|
||||
|
||||
return $src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachments for a gallery.
|
||||
*
|
||||
* @param int $id The post ID.
|
||||
* @param array $gallery The gallery config.
|
||||
*
|
||||
* @return array The selected attachments.
|
||||
*/
|
||||
protected function get_gallery_attachments( $id, $gallery ) {
|
||||
|
||||
// When there are attachments to include.
|
||||
if ( ! empty( $gallery['include'] ) ) {
|
||||
return $this->get_gallery_attachments_for_included( $gallery['include'] );
|
||||
}
|
||||
|
||||
// When $id is empty, just return empty array.
|
||||
if ( empty( $id ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->get_gallery_attachments_for_parent( $id, $gallery );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachments for the given ID.
|
||||
*
|
||||
* @param int $id The post ID.
|
||||
* @param array $gallery The gallery config.
|
||||
*
|
||||
* @return array The selected attachments.
|
||||
*/
|
||||
protected function get_gallery_attachments_for_parent( $id, $gallery ) {
|
||||
$query = [
|
||||
'posts_per_page' => -1,
|
||||
'post_parent' => $id,
|
||||
];
|
||||
|
||||
// When there are posts that should be excluded from result set.
|
||||
if ( ! empty( $gallery['exclude'] ) ) {
|
||||
$query['post__not_in'] = wp_parse_id_list( $gallery['exclude'] );
|
||||
}
|
||||
|
||||
return $this->get_attachments( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with attachments for the post IDs that will be included.
|
||||
*
|
||||
* @param array $include Array with IDs to include.
|
||||
*
|
||||
* @return array The found attachments.
|
||||
*/
|
||||
protected function get_gallery_attachments_for_included( $include ) {
|
||||
$ids_to_include = wp_parse_id_list( $include );
|
||||
$attachments = $this->get_attachments(
|
||||
[
|
||||
'posts_per_page' => count( $ids_to_include ),
|
||||
'post__in' => $ids_to_include,
|
||||
]
|
||||
);
|
||||
|
||||
$gallery_attachments = [];
|
||||
foreach ( $attachments as $key => $val ) {
|
||||
$gallery_attachments[ $val->ID ] = $val;
|
||||
}
|
||||
|
||||
return $gallery_attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachments.
|
||||
*
|
||||
* @param array $args Array with query args.
|
||||
*
|
||||
* @return array The found attachments.
|
||||
*/
|
||||
protected function get_attachments( $args ) {
|
||||
$default_args = [
|
||||
'post_status' => 'inherit',
|
||||
'post_type' => 'attachment',
|
||||
'post_mime_type' => 'image',
|
||||
|
||||
// Defaults taken from function get_posts.
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'meta_key' => '',
|
||||
'meta_value' => '',
|
||||
'suppress_filters' => true,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
|
||||
$args = wp_parse_args( $args, $default_args );
|
||||
|
||||
$get_attachments = new WP_Query();
|
||||
return $get_attachments->query( $args );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\XML Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class that handles the Admin side of XML sitemaps.
|
||||
*/
|
||||
class WPSEO_Sitemaps_Admin {
|
||||
|
||||
/**
|
||||
* Post_types that are being imported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $importing_post_types = [];
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'transition_post_status', [ $this, 'status_transition' ], 10, 3 );
|
||||
add_action( 'admin_footer', [ $this, 'status_transition_bulk_finished' ] );
|
||||
|
||||
WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'wpseo_titles', '' );
|
||||
WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'wpseo', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked into transition_post_status. Will initiate search engine pings
|
||||
* if the post is being published, is a post type that a sitemap is built for
|
||||
* and is a post that is included in sitemaps.
|
||||
*
|
||||
* @param string $new_status New post status.
|
||||
* @param string $old_status Old post status.
|
||||
* @param \WP_Post $post Post object.
|
||||
*/
|
||||
public function status_transition( $new_status, $old_status, $post ) {
|
||||
if ( $new_status !== 'publish' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( defined( 'WP_IMPORTING' ) ) {
|
||||
$this->status_transition_bulk( $new_status, $old_status, $post );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$post_type = get_post_type( $post );
|
||||
|
||||
wp_cache_delete( 'lastpostmodified:gmt:' . $post_type, 'timeinfo' ); // #17455.
|
||||
|
||||
// Not something we're interested in.
|
||||
if ( $post_type === 'nav_menu_item' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the post type is excluded in options, we can stop.
|
||||
if ( WPSEO_Options::get( 'noindex-' . $post_type, false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_allow_xml_sitemap_ping' - Check if pinging is not allowed (allowed by default).
|
||||
*
|
||||
* @api boolean $allow_ping The boolean that is set to true by default.
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_allow_xml_sitemap_ping', true ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( defined( 'YOAST_SEO_PING_IMMEDIATELY' ) && YOAST_SEO_PING_IMMEDIATELY ) {
|
||||
WPSEO_Sitemaps::ping_search_engines();
|
||||
}
|
||||
elseif ( ! wp_next_scheduled( 'wpseo_ping_search_engines' ) ) {
|
||||
wp_schedule_single_event( ( time() + 300 ), 'wpseo_ping_search_engines' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* While bulk importing, just save unique post_types.
|
||||
*
|
||||
* When importing is done, if we have a post_type that is saved in the sitemap
|
||||
* try to ping the search engines.
|
||||
*
|
||||
* @param string $new_status New post status.
|
||||
* @param string $old_status Old post status.
|
||||
* @param \WP_Post $post Post object.
|
||||
*/
|
||||
private function status_transition_bulk( $new_status, $old_status, $post ) {
|
||||
$this->importing_post_types[] = get_post_type( $post );
|
||||
$this->importing_post_types = array_unique( $this->importing_post_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* After import finished, walk through imported post_types and update info.
|
||||
*/
|
||||
public function status_transition_bulk_finished() {
|
||||
if ( ! defined( 'WP_IMPORTING' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $this->importing_post_types ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ping_search_engines = false;
|
||||
|
||||
foreach ( $this->importing_post_types as $post_type ) {
|
||||
wp_cache_delete( 'lastpostmodified:gmt:' . $post_type, 'timeinfo' ); // #17455.
|
||||
|
||||
// Just have the cache deleted for nav_menu_item.
|
||||
if ( $post_type === 'nav_menu_item' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-' . $post_type, false ) === false ) {
|
||||
$ping_search_engines = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to do.
|
||||
if ( $ping_search_engines === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WP_CACHE ) {
|
||||
do_action( 'wpseo_hit_sitemap_index' );
|
||||
}
|
||||
|
||||
WPSEO_Sitemaps::ping_search_engines();
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Find sitemaps residing on disk as they will block our rewrite.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function delete_sitemaps() {
|
||||
_deprecated_function( 'WPSEO_Sitemaps_Admin::delete_sitemaps', '7.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find sitemaps residing on disk as they will block our rewrite.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function detect_blocking_filesystem_sitemaps() {
|
||||
_deprecated_function( 'WPSEO_Sitemaps_Admin::delete_sitemaps', '7.0' );
|
||||
}
|
||||
} /* End of class */
|
||||
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles storage keys for sitemaps caching and invalidation.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
class WPSEO_Sitemaps_Cache_Validator {
|
||||
|
||||
/**
|
||||
* Prefix of the transient key for sitemap caches.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STORAGE_KEY_PREFIX = 'yst_sm_';
|
||||
|
||||
/**
|
||||
* Name of the option that holds the global validation value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VALIDATION_GLOBAL_KEY = 'wpseo_sitemap_cache_validator_global';
|
||||
|
||||
/**
|
||||
* The format which creates the key of the option that holds the type validation value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VALIDATION_TYPE_KEY_FORMAT = 'wpseo_sitemap_%s_cache_validator';
|
||||
|
||||
/**
|
||||
* Get the cache key for a certain type and page.
|
||||
*
|
||||
* A type of cache would be something like 'page', 'post' or 'video'.
|
||||
*
|
||||
* Example key format for sitemap type "post", page 1: wpseo_sitemap_post_1:akfw3e_23azBa .
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param null|string $type The type to get the key for. Null or self::SITEMAP_INDEX_TYPE for index cache.
|
||||
* @param int $page The page of cache to get the key for.
|
||||
*
|
||||
* @return bool|string The key where the cache is stored on. False if the key could not be generated.
|
||||
*/
|
||||
public static function get_storage_key( $type = null, $page = 1 ) {
|
||||
|
||||
// Using SITEMAP_INDEX_TYPE for sitemap index cache.
|
||||
$type = is_null( $type ) ? WPSEO_Sitemaps::SITEMAP_INDEX_TYPE : $type;
|
||||
|
||||
$global_cache_validator = self::get_validator();
|
||||
$type_cache_validator = self::get_validator( $type );
|
||||
|
||||
$prefix = self::STORAGE_KEY_PREFIX;
|
||||
$postfix = sprintf( '_%d:%s_%s', $page, $global_cache_validator, $type_cache_validator );
|
||||
|
||||
try {
|
||||
$type = self::truncate_type( $type, $prefix, $postfix );
|
||||
} catch ( OutOfBoundsException $exception ) {
|
||||
// Maybe do something with the exception, for now just mark as invalid.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build key.
|
||||
$full_key = $prefix . $type . $postfix;
|
||||
|
||||
return $full_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the type is over length make sure we compact it so we don't have any database problems.
|
||||
*
|
||||
* When there are more 'extremely long' post types, changes are they have variations in either the start or ending.
|
||||
* Because of this, we cut out the excess in the middle which should result in less chance of collision.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type The type of sitemap to be used.
|
||||
* @param string $prefix The part before the type in the cache key. Only the length is used.
|
||||
* @param string $postfix The part after the type in the cache key. Only the length is used.
|
||||
*
|
||||
* @return string The type with a safe length to use
|
||||
*
|
||||
* @throws OutOfRangeException When there is less than 15 characters of space for a key that is originally longer.
|
||||
*/
|
||||
public static function truncate_type( $type, $prefix = '', $postfix = '' ) {
|
||||
/*
|
||||
* This length has been restricted by the database column length of 64 in the past.
|
||||
* The prefix added by WordPress is '_transient_' because we are saving to a transient.
|
||||
* We need to use a timeout on the transient, otherwise the values get autoloaded, this adds
|
||||
* another restriction to the length.
|
||||
*/
|
||||
$max_length = 45; // 64 - 19 ('_transient_timeout_')
|
||||
$max_length -= strlen( $prefix );
|
||||
$max_length -= strlen( $postfix );
|
||||
|
||||
if ( strlen( $type ) > $max_length ) {
|
||||
|
||||
if ( $max_length < 15 ) {
|
||||
/*
|
||||
* If this happens the most likely cause is a page number that is too high.
|
||||
*
|
||||
* So this would not happen unintentionally.
|
||||
* Either by trying to cause a high server load, finding backdoors or misconfiguration.
|
||||
*/
|
||||
throw new OutOfRangeException(
|
||||
__(
|
||||
'Trying to build the sitemap cache key, but the postfix and prefix combination leaves too little room to do this. You are probably requesting a page that is way out of the expected range.',
|
||||
'wordpress-seo'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$half = ( $max_length / 2 );
|
||||
|
||||
$first_part = substr( $type, 0, ( ceil( $half ) - 1 ) );
|
||||
$last_part = substr( $type, ( 1 - floor( $half ) ) );
|
||||
|
||||
$type = $first_part . '..' . $last_part;
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate sitemap cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param null|string $type The type to get the key for. Null for all caches.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate_storage( $type = null ) {
|
||||
|
||||
// Global validator gets cleared when no type is provided.
|
||||
$old_validator = null;
|
||||
|
||||
// Get the current type validator.
|
||||
if ( ! is_null( $type ) ) {
|
||||
$old_validator = self::get_validator( $type );
|
||||
}
|
||||
|
||||
// Refresh validator.
|
||||
self::create_validator( $type );
|
||||
|
||||
if ( ! wp_using_ext_object_cache() ) {
|
||||
// Clean up current cache from the database.
|
||||
self::cleanup_database( $type, $old_validator );
|
||||
}
|
||||
|
||||
// External object cache pushes old and unretrieved items out by itself so we don't have to do anything for that.
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup invalidated database cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param null|string $type The type of sitemap to clear cache for.
|
||||
* @param null|string $validator The validator to clear cache of.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function cleanup_database( $type = null, $validator = null ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ( is_null( $type ) ) {
|
||||
// Clear all cache if no type is provided.
|
||||
$like = sprintf( '%s%%', self::STORAGE_KEY_PREFIX );
|
||||
}
|
||||
else {
|
||||
// Clear type cache for all type keys.
|
||||
$like = sprintf( '%1$s%2$s_%%', self::STORAGE_KEY_PREFIX, $type );
|
||||
}
|
||||
|
||||
/*
|
||||
* Add slashes to the LIKE "_" single character wildcard.
|
||||
*
|
||||
* We can't use `esc_like` here because we need the % in the query.
|
||||
*/
|
||||
$where = [];
|
||||
$where[] = sprintf( "option_name LIKE '%s'", addcslashes( '_transient_' . $like, '_' ) );
|
||||
$where[] = sprintf( "option_name LIKE '%s'", addcslashes( '_transient_timeout_' . $like, '_' ) );
|
||||
|
||||
// Delete transients.
|
||||
$query = sprintf( 'DELETE FROM %1$s WHERE %2$s', $wpdb->options, implode( ' OR ', $where ) );
|
||||
$wpdb->query( $query );
|
||||
|
||||
wp_cache_delete( 'alloptions', 'options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current cache validator.
|
||||
*
|
||||
* Without the type the global validator is returned.
|
||||
* This can invalidate -all- keys in cache at once.
|
||||
*
|
||||
* With the type parameter the validator for that specific type can be invalidated.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Provide a type for a specific type validator, empty for global validator.
|
||||
*
|
||||
* @return null|string The validator for the supplied type.
|
||||
*/
|
||||
public static function get_validator( $type = '' ) {
|
||||
|
||||
$key = self::get_validator_key( $type );
|
||||
|
||||
$current = get_option( $key, null );
|
||||
if ( ! is_null( $current ) ) {
|
||||
return $current;
|
||||
}
|
||||
|
||||
if ( self::create_validator( $type ) ) {
|
||||
return self::get_validator( $type );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache validator option key for the specified type.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Provide a type for a specific type validator, empty for global validator.
|
||||
*
|
||||
* @return string Validator to be used to generate the cache key.
|
||||
*/
|
||||
public static function get_validator_key( $type = '' ) {
|
||||
|
||||
if ( empty( $type ) ) {
|
||||
return self::VALIDATION_GLOBAL_KEY;
|
||||
}
|
||||
|
||||
return sprintf( self::VALIDATION_TYPE_KEY_FORMAT, $type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the cache validator value.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Provide a type for a specific type validator, empty for global validator.
|
||||
*
|
||||
* @return bool True if validator key has been saved as option.
|
||||
*/
|
||||
public static function create_validator( $type = '' ) {
|
||||
|
||||
$key = self::get_validator_key( $type );
|
||||
|
||||
// Generate new validator.
|
||||
$microtime = microtime();
|
||||
|
||||
// Remove space.
|
||||
list( $milliseconds, $seconds ) = explode( ' ', $microtime );
|
||||
|
||||
// Transients are purged every 24h.
|
||||
$seconds = ( $seconds % DAY_IN_SECONDS );
|
||||
$milliseconds = intval( substr( $milliseconds, 2, 3 ), 10 );
|
||||
|
||||
// Combine seconds and milliseconds and convert to integer.
|
||||
$validator = intval( $seconds . '' . $milliseconds, 10 );
|
||||
|
||||
// Apply base 61 encoding.
|
||||
$compressed = self::convert_base10_to_base61( $validator );
|
||||
|
||||
return update_option( $key, $compressed, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode to base61 format.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* This is base64 (numeric + alpha + alpha upper case) without the 0.
|
||||
*
|
||||
* @param int $base10 The number that has to be converted to base 61.
|
||||
*
|
||||
* @return string Base 61 converted string.
|
||||
*
|
||||
* @throws InvalidArgumentException When the input is not an integer.
|
||||
*/
|
||||
public static function convert_base10_to_base61( $base10 ) {
|
||||
|
||||
if ( ! is_int( $base10 ) ) {
|
||||
throw new InvalidArgumentException( __( 'Expected an integer as input.', 'wordpress-seo' ) );
|
||||
}
|
||||
|
||||
// Characters that will be used in the conversion.
|
||||
$characters = '123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$length = strlen( $characters );
|
||||
|
||||
$remainder = $base10;
|
||||
$output = '';
|
||||
|
||||
do {
|
||||
// Building from right to left in the result.
|
||||
$index = ( $remainder % $length );
|
||||
|
||||
// Prepend the character to the output.
|
||||
$output = $characters[ $index ] . $output;
|
||||
|
||||
// Determine the remainder after removing the applied number.
|
||||
$remainder = floor( $remainder / $length );
|
||||
|
||||
// Keep doing it until we have no remainder left.
|
||||
} while ( $remainder );
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles sitemaps caching and invalidation.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
class WPSEO_Sitemaps_Cache {
|
||||
|
||||
/**
|
||||
* Holds the options that, when updated, should cause the cache to clear.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $cache_clear = [];
|
||||
|
||||
/**
|
||||
* Mirror of enabled status for static calls.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_enabled = false;
|
||||
|
||||
/**
|
||||
* Holds the flag to clear all cache.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $clear_all = false;
|
||||
|
||||
/**
|
||||
* Holds the array of types to clear.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $clear_types = [];
|
||||
|
||||
/**
|
||||
* Hook methods for invalidation on necessary events.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'init', [ $this, 'init' ] );
|
||||
|
||||
add_action( 'deleted_term_relationships', [ __CLASS__, 'invalidate' ] );
|
||||
|
||||
add_action( 'update_option', [ __CLASS__, 'clear_on_option_update' ] );
|
||||
|
||||
add_action( 'edited_terms', [ __CLASS__, 'invalidate_helper' ], 10, 2 );
|
||||
add_action( 'clean_term_cache', [ __CLASS__, 'invalidate_helper' ], 10, 2 );
|
||||
add_action( 'clean_object_term_cache', [ __CLASS__, 'invalidate_helper' ], 10, 2 );
|
||||
|
||||
add_action( 'user_register', [ __CLASS__, 'invalidate_author' ] );
|
||||
add_action( 'delete_user', [ __CLASS__, 'invalidate_author' ] );
|
||||
|
||||
add_action( 'shutdown', [ __CLASS__, 'clear_queued' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup context for static calls.
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
self::$is_enabled = $this->is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* If cache is enabled.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_enabled() {
|
||||
|
||||
/**
|
||||
* Filter if XML sitemap transient cache is enabled.
|
||||
*
|
||||
* @param bool $unsigned Enable cache or not, defaults to true.
|
||||
*/
|
||||
return apply_filters( 'wpseo_enable_xml_sitemap_transient_caching', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sitemap page from cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page Page number to retrieve.
|
||||
*
|
||||
* @return string|boolean
|
||||
*/
|
||||
public function get_sitemap( $type, $page ) {
|
||||
|
||||
$transient_key = WPSEO_Sitemaps_Cache_Validator::get_storage_key( $type, $page );
|
||||
if ( $transient_key === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_transient( $transient_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sitemap that is cached.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page Page number to retrieve.
|
||||
*
|
||||
* @return null|WPSEO_Sitemap_Cache_Data Null on no cache found otherwise object containing sitemap and meta data.
|
||||
*/
|
||||
public function get_sitemap_data( $type, $page ) {
|
||||
|
||||
$sitemap = $this->get_sitemap( $type, $page );
|
||||
|
||||
if ( empty( $sitemap ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Unserialize Cache Data object (is_serialized doesn't recognize classes).
|
||||
if ( is_string( $sitemap ) && strpos( $sitemap, 'C:24:"WPSEO_Sitemap_Cache_Data"' ) === 0 ) {
|
||||
|
||||
$sitemap = unserialize( $sitemap );
|
||||
}
|
||||
|
||||
// What we expect it to be if it is set.
|
||||
if ( $sitemap instanceof WPSEO_Sitemap_Cache_Data_Interface ) {
|
||||
return $sitemap;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the sitemap page from cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page Page number to store.
|
||||
* @param string $sitemap Sitemap body to store.
|
||||
* @param bool $usable Is this a valid sitemap or a cache of an invalid sitemap.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function store_sitemap( $type, $page, $sitemap, $usable = true ) {
|
||||
|
||||
$transient_key = WPSEO_Sitemaps_Cache_Validator::get_storage_key( $type, $page );
|
||||
|
||||
if ( $transient_key === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = ( $usable ) ? WPSEO_Sitemap_Cache_Data::OK : WPSEO_Sitemap_Cache_Data::ERROR;
|
||||
|
||||
$sitemap_data = new WPSEO_Sitemap_Cache_Data();
|
||||
$sitemap_data->set_sitemap( $sitemap );
|
||||
$sitemap_data->set_status( $status );
|
||||
|
||||
return set_transient( $transient_key, $sitemap_data, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache transients for index and specific type.
|
||||
*
|
||||
* Always deletes the main index sitemaps cache, as that's always invalidated by any other change.
|
||||
*
|
||||
* @since 1.5.4
|
||||
* @since 3.2 Changed from function wpseo_invalidate_sitemap_cache() to method in this class.
|
||||
*
|
||||
* @param string $type Sitemap type to invalidate.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate( $type ) {
|
||||
|
||||
self::clear( [ $type ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to invalidate in hooks where type is passed as second argument.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param int $unused Unused term ID value.
|
||||
* @param string $type Taxonomy to invalidate.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate_helper( $unused, $type ) {
|
||||
|
||||
if (
|
||||
WPSEO_Options::get( 'noindex-' . $type ) === false ||
|
||||
WPSEO_Options::get( 'noindex-tax-' . $type ) === false
|
||||
) {
|
||||
self::invalidate( $type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate sitemap cache for authors.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*
|
||||
* @return bool True if the sitemap was properly invalidated. False otherwise.
|
||||
*/
|
||||
public static function invalidate_author( $user_id ) {
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
|
||||
if ( $user === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( current_action() === 'user_register' ) {
|
||||
update_user_meta( $user_id, '_yoast_wpseo_profile_updated', time() );
|
||||
}
|
||||
|
||||
if ( empty( $user->roles ) || in_array( 'subscriber', $user->roles, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self::invalidate( 'author' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate sitemap cache for the post type of a post.
|
||||
*
|
||||
* Don't invalidate for revisions.
|
||||
*
|
||||
* @since 1.5.4
|
||||
* @since 3.2 Changed from function wpseo_invalidate_sitemap_cache_on_save_post() to method in this class.
|
||||
*
|
||||
* @param int $post_id Post ID to invalidate type for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate_post( $post_id ) {
|
||||
|
||||
if ( wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::invalidate( get_post_type( $post_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache transients for given sitemaps types or all by default.
|
||||
*
|
||||
* @since 1.8.0
|
||||
* @since 3.2 Moved from WPSEO_Utils to this class.
|
||||
*
|
||||
* @param array $types Set of sitemap types to delete cache transients for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear( $types = [] ) {
|
||||
|
||||
if ( ! self::$is_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No types provided, clear all.
|
||||
if ( empty( $types ) ) {
|
||||
self::$clear_all = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Always invalidate the index sitemap as well.
|
||||
if ( ! in_array( WPSEO_Sitemaps::SITEMAP_INDEX_TYPE, $types ) ) {
|
||||
array_unshift( $types, WPSEO_Sitemaps::SITEMAP_INDEX_TYPE );
|
||||
}
|
||||
|
||||
foreach ( $types as $type ) {
|
||||
if ( ! in_array( $type, self::$clear_types ) ) {
|
||||
self::$clear_types[] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate storage for cache types queued to clear.
|
||||
*/
|
||||
public static function clear_queued() {
|
||||
|
||||
if ( self::$clear_all ) {
|
||||
|
||||
WPSEO_Sitemaps_Cache_Validator::invalidate_storage();
|
||||
self::$clear_all = false;
|
||||
self::$clear_types = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( self::$clear_types as $type ) {
|
||||
WPSEO_Sitemaps_Cache_Validator::invalidate_storage( $type );
|
||||
}
|
||||
|
||||
self::$clear_types = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a hook that when given option is updated, the cache is cleared.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @param string $type Sitemap type.
|
||||
*/
|
||||
public static function register_clear_on_option_update( $option, $type = '' ) {
|
||||
|
||||
self::$cache_clear[ $option ] = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the transient cache when a given option is updated, if that option has been registered before.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $option The option name that's being updated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_on_option_update( $option ) {
|
||||
|
||||
if ( array_key_exists( $option, self::$cache_clear ) ) {
|
||||
|
||||
if ( empty( self::$cache_clear[ $option ] ) ) {
|
||||
// Clear all caches.
|
||||
self::clear();
|
||||
}
|
||||
else {
|
||||
// Clear specific provided type(s).
|
||||
$types = (array) self::$cache_clear[ $option ];
|
||||
self::clear( $types );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Renders XML output for sitemaps.
|
||||
*/
|
||||
class WPSEO_Sitemaps_Renderer {
|
||||
|
||||
/**
|
||||
* XSL stylesheet for styling a sitemap for web browsers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $stylesheet = '';
|
||||
|
||||
/**
|
||||
* Holds the get_bloginfo( 'charset' ) value to reuse for performance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Holds charset of output, might be converted.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $output_charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* If data encoding needs to be converted for output.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $needs_conversion = false;
|
||||
|
||||
/**
|
||||
* The date helper.
|
||||
*
|
||||
* @var WPSEO_Date_Helper
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
/**
|
||||
* Set up object properties.
|
||||
*/
|
||||
public function __construct() {
|
||||
$stylesheet_url = preg_replace( '/(^http[s]?:)/', '', $this->get_xsl_url() );
|
||||
$this->stylesheet = '<?xml-stylesheet type="text/xsl" href="' . esc_url( $stylesheet_url ) . '"?>';
|
||||
$this->charset = get_bloginfo( 'charset' );
|
||||
$this->output_charset = $this->charset;
|
||||
$this->date = new WPSEO_Date_Helper();
|
||||
|
||||
if (
|
||||
$this->charset !== 'UTF-8'
|
||||
&& function_exists( 'mb_list_encodings' )
|
||||
&& in_array( $this->charset, mb_list_encodings(), true )
|
||||
) {
|
||||
$this->output_charset = 'UTF-8';
|
||||
}
|
||||
|
||||
$this->needs_conversion = $this->output_charset !== $this->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the sitemap index.
|
||||
*
|
||||
* @param array $links Set of sitemaps index links.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_index( $links ) {
|
||||
|
||||
$xml = '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
||||
|
||||
foreach ( $links as $link ) {
|
||||
$xml .= $this->sitemap_index_url( $link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to append sitemaps to the index.
|
||||
*
|
||||
* @param string $index String to append to sitemaps index, defaults to empty.
|
||||
*/
|
||||
$xml .= apply_filters( 'wpseo_sitemap_index', '' );
|
||||
$xml .= '</sitemapindex>';
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the sitemap.
|
||||
*
|
||||
* @param array $links Set of sitemap links.
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $current_page Current sitemap page number.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_sitemap( $links, $type, $current_page ) {
|
||||
|
||||
$urlset = '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" '
|
||||
. 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd '
|
||||
. 'http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" '
|
||||
. 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
||||
|
||||
/**
|
||||
* Filters the `urlset` for a sitemap by type.
|
||||
*
|
||||
* @api string $urlset The output for the sitemap's `urlset`.
|
||||
*/
|
||||
$xml = apply_filters( "wpseo_sitemap_{$type}_urlset", $urlset );
|
||||
|
||||
foreach ( $links as $url ) {
|
||||
$xml .= $this->sitemap_url( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to add extra URLs to the XML sitemap by type.
|
||||
*
|
||||
* Only runs for the first page, not on all.
|
||||
*
|
||||
* @param string $content String content to add, defaults to empty.
|
||||
*/
|
||||
if ( $current_page === 1 ) {
|
||||
$xml .= apply_filters( "wpseo_sitemap_{$type}_content", '' );
|
||||
}
|
||||
|
||||
$xml .= '</urlset>';
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce final XML output with debug information.
|
||||
*
|
||||
* @param string $sitemap Sitemap XML.
|
||||
* @param boolean $transient Transient cache flag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_output( $sitemap, $transient ) {
|
||||
|
||||
$output = '<?xml version="1.0" encoding="' . esc_attr( $this->output_charset ) . '"?>';
|
||||
|
||||
if ( $this->stylesheet ) {
|
||||
/**
|
||||
* Filter the stylesheet URL for the XML sitemap.
|
||||
*
|
||||
* @param string $stylesheet Stylesheet URL.
|
||||
*/
|
||||
$output .= apply_filters( 'wpseo_stylesheet_url', $this->stylesheet ) . "\n";
|
||||
}
|
||||
|
||||
$output .= $sitemap;
|
||||
$output .= "\n<!-- XML Sitemap generated by Yoast SEO -->";
|
||||
$output .= $this->get_debug( $transient );
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get charset for the output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_output_charset() {
|
||||
return $this->output_charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom stylesheet for this sitemap. Set to empty to just remove the default stylesheet.
|
||||
*
|
||||
* @param string $stylesheet Full XML-stylesheet declaration.
|
||||
*/
|
||||
public function set_stylesheet( $stylesheet ) {
|
||||
$this->stylesheet = $stylesheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the `<sitemap>` tag for a given URL.
|
||||
*
|
||||
* @param array $url Array of parts that make up this entry.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function sitemap_index_url( $url ) {
|
||||
|
||||
$date = null;
|
||||
|
||||
if ( ! empty( $url['lastmod'] ) ) {
|
||||
$date = $this->date->format( $url['lastmod'] );
|
||||
}
|
||||
|
||||
$url['loc'] = htmlspecialchars( $url['loc'], ENT_COMPAT, $this->output_charset, false );
|
||||
|
||||
$output = "\t<sitemap>\n";
|
||||
$output .= "\t\t<loc>" . $url['loc'] . "</loc>\n";
|
||||
$output .= empty( $date ) ? '' : "\t\t<lastmod>" . htmlspecialchars( $date, ENT_COMPAT, $this->output_charset, false ) . "</lastmod>\n";
|
||||
$output .= "\t</sitemap>\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the `<url>` tag for a given URL.
|
||||
*
|
||||
* Public access for backwards compatibility reasons.
|
||||
*
|
||||
* @param array $url Array of parts that make up this entry.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sitemap_url( $url ) {
|
||||
|
||||
$date = null;
|
||||
|
||||
|
||||
if ( ! empty( $url['mod'] ) ) {
|
||||
// Create a DateTime object date in the correct timezone.
|
||||
$date = $this->date->format( $url['mod'] );
|
||||
}
|
||||
|
||||
$url['loc'] = htmlspecialchars( $url['loc'], ENT_COMPAT, $this->output_charset, false );
|
||||
|
||||
$output = "\t<url>\n";
|
||||
$output .= "\t\t<loc>" . $this->encode_url_rfc3986( $url['loc'] ) . "</loc>\n";
|
||||
$output .= empty( $date ) ? '' : "\t\t<lastmod>" . htmlspecialchars( $date, ENT_COMPAT, $this->output_charset, false ) . "</lastmod>\n";
|
||||
|
||||
if ( empty( $url['images'] ) ) {
|
||||
$url['images'] = [];
|
||||
}
|
||||
|
||||
foreach ( $url['images'] as $img ) {
|
||||
|
||||
if ( empty( $img['src'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$output .= "\t\t<image:image>\n";
|
||||
$output .= "\t\t\t<image:loc>" . esc_html( $this->encode_url_rfc3986( $img['src'] ) ) . "</image:loc>\n";
|
||||
|
||||
if ( ! empty( $img['title'] ) ) {
|
||||
|
||||
$title = $img['title'];
|
||||
|
||||
if ( $this->needs_conversion ) {
|
||||
$title = mb_convert_encoding( $title, $this->output_charset, $this->charset );
|
||||
}
|
||||
|
||||
$title = _wp_specialchars( html_entity_decode( $title, ENT_QUOTES, $this->output_charset ) );
|
||||
$output .= "\t\t\t<image:title><![CDATA[{$title}]]></image:title>\n";
|
||||
}
|
||||
|
||||
if ( ! empty( $img['alt'] ) ) {
|
||||
|
||||
$alt = $img['alt'];
|
||||
|
||||
if ( $this->needs_conversion ) {
|
||||
$alt = mb_convert_encoding( $alt, $this->output_charset, $this->charset );
|
||||
}
|
||||
|
||||
$alt = _wp_specialchars( html_entity_decode( $alt, ENT_QUOTES, $this->output_charset ) );
|
||||
$output .= "\t\t\t<image:caption><![CDATA[{$alt}]]></image:caption>\n";
|
||||
}
|
||||
|
||||
$output .= "\t\t</image:image>\n";
|
||||
}
|
||||
unset( $img, $title, $alt );
|
||||
|
||||
$output .= "\t</url>\n";
|
||||
|
||||
/**
|
||||
* Filters the output for the sitemap URL tag.
|
||||
*
|
||||
* @api string $output The output for the sitemap url tag.
|
||||
*
|
||||
* @param array $url The sitemap URL array on which the output is based.
|
||||
*/
|
||||
return apply_filters( 'wpseo_sitemap_url', $output, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply some best effort conversion to comply with RFC3986.
|
||||
*
|
||||
* @param string $url URL to encode.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function encode_url_rfc3986( $url ) {
|
||||
|
||||
if ( filter_var( $url, FILTER_VALIDATE_URL ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$path = wp_parse_url( $url, PHP_URL_PATH );
|
||||
|
||||
if ( ! empty( $path ) && $path !== '/' ) {
|
||||
$encoded_path = explode( '/', $path );
|
||||
|
||||
// First decode the path, to prevent double encoding.
|
||||
$encoded_path = array_map( 'rawurldecode', $encoded_path );
|
||||
|
||||
$encoded_path = array_map( 'rawurlencode', $encoded_path );
|
||||
$encoded_path = implode( '/', $encoded_path );
|
||||
$encoded_path = str_replace( '%7E', '~', $encoded_path ); // PHP < 5.3.
|
||||
|
||||
$url = str_replace( $path, $encoded_path, $url );
|
||||
}
|
||||
|
||||
$query = wp_parse_url( $url, PHP_URL_QUERY );
|
||||
|
||||
if ( ! empty( $query ) ) {
|
||||
|
||||
parse_str( $query, $parsed_query );
|
||||
|
||||
if ( defined( 'PHP_QUERY_RFC3986' ) ) { // PHP 5.4+.
|
||||
$parsed_query = http_build_query( $parsed_query, null, '&', PHP_QUERY_RFC3986 );
|
||||
}
|
||||
else {
|
||||
$parsed_query = http_build_query( $parsed_query, null, '&' );
|
||||
$parsed_query = str_replace( '+', '%20', $parsed_query );
|
||||
$parsed_query = str_replace( '%7E', '~', $parsed_query );
|
||||
}
|
||||
|
||||
$url = str_replace( $query, $parsed_query, $url );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the XSL URL that should be used in the current environment
|
||||
*
|
||||
* When home_url and site_url are not the same, the home_url should be used.
|
||||
* This is because the XSL needs to be served from the same domain, protocol and port
|
||||
* as the XML file that is loading it.
|
||||
*
|
||||
* @return string The XSL URL that needs to be used.
|
||||
*/
|
||||
protected function get_xsl_url() {
|
||||
if ( home_url() !== site_url() ) {
|
||||
return home_url( 'main-sitemap.xsl' );
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallback to circumvent a cross-domain security problem when the XLS file is
|
||||
* loaded from a different (sub)domain.
|
||||
*/
|
||||
if ( strpos( plugins_url(), home_url() ) !== 0 ) {
|
||||
return home_url( 'main-sitemap.xsl' );
|
||||
}
|
||||
|
||||
return plugin_dir_url( WPSEO_FILE ) . 'css/main-sitemap.xsl';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds debugging information to the output.
|
||||
*
|
||||
* @param bool $transient Transient cache was used or not.
|
||||
*
|
||||
* @return string Information about the functionality used to build the sitemap.
|
||||
*/
|
||||
protected function get_debug( $transient ) {
|
||||
$debug = defined( 'YOAST_SEO_DEBUG_SITEMAPS' ) && YOAST_SEO_DEBUG_SITEMAPS === true;
|
||||
if ( ! $debug ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$memory_used = number_format( ( memory_get_peak_usage() / 1048576 ), 2 );
|
||||
$queries_run = ( $transient ) ? 'Served from transient cache' : 'Queries executed ' . absint( $GLOBALS['wpdb']->num_queries );
|
||||
|
||||
$output = "\n<!-- {$memory_used}MB | {$queries_run} -->";
|
||||
|
||||
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
|
||||
$queries = print_r( $GLOBALS['wpdb']->queries, true );
|
||||
$output .= "\n<!-- {$queries} -->";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rewrite setup and handling for sitemaps functionality.
|
||||
*/
|
||||
class WPSEO_Sitemaps_Router {
|
||||
|
||||
/**
|
||||
* Sets up init logic.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'init', [ $this, 'init' ], 1 );
|
||||
add_filter( 'redirect_canonical', [ $this, 'redirect_canonical' ] );
|
||||
add_action( 'template_redirect', [ $this, 'template_redirect' ], 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up rewrite rules.
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
global $wp;
|
||||
|
||||
$wp->add_query_var( 'sitemap' );
|
||||
$wp->add_query_var( 'sitemap_n' );
|
||||
$wp->add_query_var( 'yoast-sitemap-xsl' );
|
||||
|
||||
add_rewrite_rule( 'sitemap_index\.xml$', 'index.php?sitemap=1', 'top' );
|
||||
add_rewrite_rule( '([^/]+?)-sitemap([0-9]+)?\.xml$', 'index.php?sitemap=$matches[1]&sitemap_n=$matches[2]', 'top' );
|
||||
add_rewrite_rule( '([a-z]+)?-?sitemap\.xsl$', 'index.php?yoast-sitemap-xsl=$matches[1]', 'top' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop trailing slashes on sitemap.xml URLs.
|
||||
*
|
||||
* @param string $redirect The redirect URL currently determined.
|
||||
*
|
||||
* @return bool|string $redirect
|
||||
*/
|
||||
public function redirect_canonical( $redirect ) {
|
||||
|
||||
if ( get_query_var( 'sitemap' ) || get_query_var( 'yoast-sitemap-xsl' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects sitemap.xml to sitemap_index.xml.
|
||||
*/
|
||||
public function template_redirect() {
|
||||
if ( ! $this->needs_sitemap_index_redirect() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_redirect( home_url( '/sitemap_index.xml' ), 301, 'Yoast SEO' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current request needs to be redirected to sitemap_index.xml.
|
||||
*
|
||||
* @global WP_Query $wp_query Current query.
|
||||
*
|
||||
* @return bool True if redirect is needed, false otherwise.
|
||||
*/
|
||||
public function needs_sitemap_index_redirect() {
|
||||
global $wp_query;
|
||||
|
||||
$protocol = 'http://';
|
||||
if ( ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ) {
|
||||
$protocol = 'https://';
|
||||
}
|
||||
|
||||
$domain = '';
|
||||
if ( isset( $_SERVER['SERVER_NAME'] ) ) {
|
||||
$domain = sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) );
|
||||
}
|
||||
|
||||
$path = '';
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
$path = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
}
|
||||
|
||||
// Due to different environment configurations, we need to check both SERVER_NAME and HTTP_HOST.
|
||||
$check_urls = [ $protocol . $domain . $path ];
|
||||
if ( ! empty( $_SERVER['HTTP_HOST'] ) ) {
|
||||
$check_urls[] = $protocol . sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) . $path;
|
||||
}
|
||||
|
||||
return $wp_query->is_404 && in_array( home_url( '/sitemap.xml' ), $check_urls, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create base URL for the sitemap.
|
||||
*
|
||||
* @param string $page Page to append to the base URL.
|
||||
*
|
||||
* @return string base URL (incl page)
|
||||
*/
|
||||
public static function get_base_url( $page ) {
|
||||
|
||||
global $wp_rewrite;
|
||||
|
||||
$base = $wp_rewrite->using_index_permalinks() ? 'index.php/' : '/';
|
||||
|
||||
/**
|
||||
* Filter the base URL of the sitemaps.
|
||||
*
|
||||
* @param string $base The string that should be added to home_url() to make the full base URL.
|
||||
*/
|
||||
$base = apply_filters( 'wpseo_sitemaps_base_url', $base );
|
||||
|
||||
/*
|
||||
* Get the scheme from the configured home URL instead of letting WordPress
|
||||
* determine the scheme based on the requested URI.
|
||||
*/
|
||||
return home_url( $base . $page, wp_parse_url( get_option( 'home' ), PHP_URL_SCHEME ) );
|
||||
}
|
||||
}
|
||||
645
wp-content/plugins/wordpress-seo/inc/sitemaps/class-sitemaps.php
Normal file
645
wp-content/plugins/wordpress-seo/inc/sitemaps/class-sitemaps.php
Normal file
@@ -0,0 +1,645 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Sitemaps.
|
||||
*
|
||||
* @todo This class could use a general description with some explanation on sitemaps. OR.
|
||||
*/
|
||||
class WPSEO_Sitemaps {
|
||||
/**
|
||||
* Sitemap index identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SITEMAP_INDEX_TYPE = '1';
|
||||
|
||||
/**
|
||||
* Content of the sitemap to output.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sitemap = '';
|
||||
|
||||
/**
|
||||
* Flag to indicate if this is an invalid or empty sitemap.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $bad_sitemap = false;
|
||||
|
||||
/**
|
||||
* Whether or not the XML sitemap was served from a transient or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $transient = false;
|
||||
|
||||
/**
|
||||
* HTTP protocol to use in headers.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $http_protocol = 'HTTP/1.1';
|
||||
|
||||
/**
|
||||
* Holds the n variable.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $current_page = 1;
|
||||
|
||||
/**
|
||||
* The sitemaps router.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Router
|
||||
*/
|
||||
public $router;
|
||||
|
||||
/**
|
||||
* The sitemap renderer.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Renderer
|
||||
*/
|
||||
public $renderer;
|
||||
|
||||
/**
|
||||
* The sitemap cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Cache
|
||||
*/
|
||||
public $cache;
|
||||
|
||||
/**
|
||||
* The sitemap providers.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemap_Provider[]
|
||||
*/
|
||||
public $providers;
|
||||
|
||||
/**
|
||||
* The date helper.
|
||||
*
|
||||
* @var WPSEO_Date_Helper
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'after_setup_theme', [ $this, 'init_sitemaps_providers' ] );
|
||||
add_action( 'after_setup_theme', [ $this, 'reduce_query_load' ], 99 );
|
||||
add_action( 'pre_get_posts', [ $this, 'redirect' ], 1 );
|
||||
add_action( 'wpseo_hit_sitemap_index', [ $this, 'hit_sitemap_index' ] );
|
||||
add_action( 'wpseo_ping_search_engines', [ __CLASS__, 'ping_search_engines' ] );
|
||||
|
||||
$this->router = new WPSEO_Sitemaps_Router();
|
||||
$this->renderer = new WPSEO_Sitemaps_Renderer();
|
||||
$this->cache = new WPSEO_Sitemaps_Cache();
|
||||
$this->date = new WPSEO_Date_Helper();
|
||||
|
||||
if ( ! empty( $_SERVER['SERVER_PROTOCOL'] ) ) {
|
||||
$this->http_protocol = sanitize_text_field( wp_unslash( $_SERVER['SERVER_PROTOCOL'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize sitemap providers classes.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
public function init_sitemaps_providers() {
|
||||
|
||||
$this->providers = [
|
||||
new WPSEO_Post_Type_Sitemap_Provider(),
|
||||
new WPSEO_Taxonomy_Sitemap_Provider(),
|
||||
new WPSEO_Author_Sitemap_Provider(),
|
||||
];
|
||||
|
||||
$external_providers = apply_filters( 'wpseo_sitemaps_providers', [] );
|
||||
|
||||
foreach ( $external_providers as $provider ) {
|
||||
if ( is_object( $provider ) && $provider instanceof WPSEO_Sitemap_Provider ) {
|
||||
$this->providers[] = $provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the current request URI, if we can determine it's probably an XML sitemap, kill loading the widgets.
|
||||
*/
|
||||
public function reduce_query_load() {
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return;
|
||||
}
|
||||
$request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
$extension = substr( $request_uri, -4 );
|
||||
if ( stripos( $request_uri, 'sitemap' ) !== false && in_array( $extension, [ '.xml', '.xsl' ], true ) ) {
|
||||
remove_all_actions( 'widgets_init' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register your own sitemap. Call this during 'init'.
|
||||
*
|
||||
* @param string $name The name of the sitemap.
|
||||
* @param callback $function Function to build your sitemap.
|
||||
* @param string $rewrite Optional. Regular expression to match your sitemap with.
|
||||
*/
|
||||
public function register_sitemap( $name, $function, $rewrite = '' ) {
|
||||
add_action( 'wpseo_do_sitemap_' . $name, $function );
|
||||
if ( ! empty( $rewrite ) ) {
|
||||
add_rewrite_rule( $rewrite, 'index.php?sitemap=' . $name, 'top' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register your own XSL file. Call this during 'init'.
|
||||
*
|
||||
* @since 1.4.23
|
||||
*
|
||||
* @param string $name The name of the XSL file.
|
||||
* @param callback $function Function to build your XSL file.
|
||||
* @param string $rewrite Optional. Regular expression to match your sitemap with.
|
||||
*/
|
||||
public function register_xsl( $name, $function, $rewrite = '' ) {
|
||||
add_action( 'wpseo_xsl_' . $name, $function );
|
||||
if ( ! empty( $rewrite ) ) {
|
||||
add_rewrite_rule( $rewrite, 'index.php?yoast-sitemap-xsl=' . $name, 'top' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sitemap current page to allow creating partial sitemaps with WP-CLI
|
||||
* in a one-off process.
|
||||
*
|
||||
* @param integer $current_page The part that should be generated.
|
||||
*/
|
||||
public function set_n( $current_page ) {
|
||||
if ( is_scalar( $current_page ) && intval( $current_page ) > 0 ) {
|
||||
$this->current_page = intval( $current_page );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sitemap content to display after you have generated it.
|
||||
*
|
||||
* @param string $sitemap The generated sitemap to output.
|
||||
*/
|
||||
public function set_sitemap( $sitemap ) {
|
||||
$this->sitemap = $sitemap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set as true to make the request 404. Used stop the display of empty sitemaps or invalid requests.
|
||||
*
|
||||
* @param bool $bool Is this a bad request. True or false.
|
||||
*/
|
||||
public function set_bad_sitemap( $bool ) {
|
||||
$this->bad_sitemap = (bool) $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent stupid plugins from running shutdown scripts when we're obviously not outputting HTML.
|
||||
*
|
||||
* @since 1.4.16
|
||||
*/
|
||||
public function sitemap_close() {
|
||||
remove_all_actions( 'wp_footer' );
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hijack requests for potential sitemaps and XSL files.
|
||||
*
|
||||
* @param \WP_Query $query Main query instance.
|
||||
*/
|
||||
public function redirect( $query ) {
|
||||
|
||||
if ( ! $query->is_main_query() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$yoast_sitemap_xsl = get_query_var( 'yoast-sitemap-xsl' );
|
||||
|
||||
if ( ! empty( $yoast_sitemap_xsl ) ) {
|
||||
/*
|
||||
* This is a method to provide the XSL via the home_url.
|
||||
* Needed when the site_url and home_url are not the same.
|
||||
* Loading the XSL needs to come from the same domain, protocol and port as the XML.
|
||||
*
|
||||
* Whenever home_url and site_url are the same, the file can be loaded directly.
|
||||
*/
|
||||
$this->xsl_output( $yoast_sitemap_xsl );
|
||||
$this->sitemap_close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$type = get_query_var( 'sitemap' );
|
||||
|
||||
if ( empty( $type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_n( get_query_var( 'sitemap_n' ) );
|
||||
|
||||
if ( ! $this->get_sitemap_from_cache( $type, $this->current_page ) ) {
|
||||
$this->build_sitemap( $type );
|
||||
}
|
||||
|
||||
if ( $this->bad_sitemap ) {
|
||||
$query->set_404();
|
||||
status_header( 404 );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->output();
|
||||
$this->sitemap_close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get the sitemap from cache.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page_number The page number to retrieve.
|
||||
*
|
||||
* @return bool If the sitemap has been retrieved from cache.
|
||||
*/
|
||||
private function get_sitemap_from_cache( $type, $page_number ) {
|
||||
|
||||
$this->transient = false;
|
||||
|
||||
if ( $this->cache->is_enabled() !== true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires before the attempt to retrieve XML sitemap from the transient cache.
|
||||
*
|
||||
* @param WPSEO_Sitemaps $sitemaps Sitemaps object.
|
||||
*/
|
||||
do_action( 'wpseo_sitemap_stylesheet_cache_' . $type, $this );
|
||||
|
||||
$sitemap_cache_data = $this->cache->get_sitemap_data( $type, $page_number );
|
||||
|
||||
// No cache was found, refresh it because cache is enabled.
|
||||
if ( empty( $sitemap_cache_data ) ) {
|
||||
return $this->refresh_sitemap_cache( $type, $page_number );
|
||||
}
|
||||
|
||||
// Cache object was found, parse information.
|
||||
$this->transient = true;
|
||||
|
||||
$this->sitemap = $sitemap_cache_data->get_sitemap();
|
||||
$this->bad_sitemap = ! $sitemap_cache_data->is_usable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and save sitemap to cache.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page_number The page number to save to.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function refresh_sitemap_cache( $type, $page_number ) {
|
||||
$this->set_n( $page_number );
|
||||
$this->build_sitemap( $type );
|
||||
|
||||
return $this->cache->store_sitemap( $type, $page_number, $this->sitemap, ! $this->bad_sitemap );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to build the requested sitemap.
|
||||
*
|
||||
* Sets $bad_sitemap if this isn't for the root sitemap, a post type or taxonomy.
|
||||
*
|
||||
* @param string $type The requested sitemap's identifier.
|
||||
*/
|
||||
public function build_sitemap( $type ) {
|
||||
|
||||
/**
|
||||
* Filter the type of sitemap to build.
|
||||
*
|
||||
* @param string $type Sitemap type, determined by the request.
|
||||
*/
|
||||
$type = apply_filters( 'wpseo_build_sitemap_post_type', $type );
|
||||
|
||||
if ( $type === '1' ) {
|
||||
$this->build_root_map();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$entries_per_page = $this->get_entries_per_page();
|
||||
|
||||
foreach ( $this->providers as $provider ) {
|
||||
if ( ! $provider->handles_type( $type ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$links = $provider->get_sitemap_links( $type, $entries_per_page, $this->current_page );
|
||||
} catch ( OutOfBoundsException $exception ) {
|
||||
$this->bad_sitemap = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sitemap = $this->renderer->get_sitemap( $links, $type, $this->current_page );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( has_action( 'wpseo_do_sitemap_' . $type ) ) {
|
||||
/**
|
||||
* Fires custom handler, if hooked to generate sitemap for the type.
|
||||
*/
|
||||
do_action( 'wpseo_do_sitemap_' . $type );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->bad_sitemap = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the root sitemap (example.com/sitemap_index.xml) which lists sub-sitemaps for other content types.
|
||||
*/
|
||||
public function build_root_map() {
|
||||
|
||||
$links = [];
|
||||
$entries_per_page = $this->get_entries_per_page();
|
||||
|
||||
foreach ( $this->providers as $provider ) {
|
||||
$links = array_merge( $links, $provider->get_index_links( $entries_per_page ) );
|
||||
}
|
||||
|
||||
if ( empty( $links ) ) {
|
||||
$this->bad_sitemap = true;
|
||||
$this->sitemap = '';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sitemap = $this->renderer->get_index( $links );
|
||||
}
|
||||
|
||||
/**
|
||||
* Spits out the XSL for the XML sitemap.
|
||||
*
|
||||
* @param string $type Type to output.
|
||||
*
|
||||
* @since 1.4.13
|
||||
*/
|
||||
public function xsl_output( $type ) {
|
||||
|
||||
if ( $type !== 'main' ) {
|
||||
|
||||
/**
|
||||
* Fires for the output of XSL for XML sitemaps, other than type "main".
|
||||
*/
|
||||
do_action( 'wpseo_xsl_' . $type );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
header( $this->http_protocol . ' 200 OK', true, 200 );
|
||||
// Prevent the search engines from indexing the XML Sitemap.
|
||||
header( 'X-Robots-Tag: noindex, follow', true );
|
||||
header( 'Content-Type: text/xml' );
|
||||
|
||||
// Make the browser cache this file properly.
|
||||
$expires = YEAR_IN_SECONDS;
|
||||
header( 'Pragma: public' );
|
||||
header( 'Cache-Control: maxage=' . $expires );
|
||||
header( 'Expires: ' . $this->date->format_timestamp( ( time() + $expires ), 'D, d M Y H:i:s' ) . ' GMT' );
|
||||
|
||||
readfile( WPSEO_PATH . 'css/main-sitemap.xsl' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Spit out the generated sitemap.
|
||||
*/
|
||||
public function output() {
|
||||
$this->send_headers();
|
||||
echo $this->renderer->get_output( $this->sitemap, $this->transient );
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to the sitemap index to cache it before the arrival of the search engines.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function hit_sitemap_index() {
|
||||
if ( ! $this->cache->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_remote_get( WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GMT modification date for the last modified post in the post type.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string|array $post_types Post type or array of types.
|
||||
* @param bool $return_all Flag to return array of values.
|
||||
*
|
||||
* @return string|array|false
|
||||
*/
|
||||
public static function get_last_modified_gmt( $post_types, $return_all = false ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
static $post_type_dates = null;
|
||||
|
||||
if ( ! is_array( $post_types ) ) {
|
||||
$post_types = [ $post_types ];
|
||||
}
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
if ( ! isset( $post_type_dates[ $post_type ] ) ) { // If we hadn't seen post type before. R.
|
||||
$post_type_dates = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_null( $post_type_dates ) ) {
|
||||
|
||||
$post_type_dates = [];
|
||||
$post_type_names = WPSEO_Post_Type::get_accessible_post_types();
|
||||
|
||||
if ( ! empty( $post_type_names ) ) {
|
||||
$post_statuses = array_map( 'esc_sql', self::get_post_statuses() );
|
||||
|
||||
$sql = "
|
||||
SELECT post_type, MAX(post_modified_gmt) AS date
|
||||
FROM $wpdb->posts
|
||||
WHERE post_status IN ('" . implode( "','", $post_statuses ) . "')
|
||||
AND post_type IN ('" . implode( "','", $post_type_names ) . "')
|
||||
GROUP BY post_type
|
||||
ORDER BY post_modified_gmt DESC
|
||||
";
|
||||
|
||||
foreach ( $wpdb->get_results( $sql ) as $obj ) {
|
||||
$post_type_dates[ $obj->post_type ] = $obj->date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dates = array_intersect_key( $post_type_dates, array_flip( $post_types ) );
|
||||
|
||||
if ( count( $dates ) > 0 ) {
|
||||
if ( $return_all ) {
|
||||
return $dates;
|
||||
}
|
||||
|
||||
return max( $dates );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the modification date for the last modified post in the post type.
|
||||
*
|
||||
* @param array $post_types Post types to get the last modification date for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_last_modified( $post_types ) {
|
||||
return $this->date->format( self::get_last_modified_gmt( $post_types ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify search engines of the updated sitemap.
|
||||
*
|
||||
* @param string|null $url Optional URL to make the ping for.
|
||||
*/
|
||||
public static function ping_search_engines( $url = null ) {
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_allow_xml_sitemap_ping' - Check if pinging is not allowed (allowed by default)
|
||||
*
|
||||
* @api boolean $allow_ping The boolean that is set to true by default.
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_allow_xml_sitemap_ping', true ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( get_option( 'blog_public' ) === '0' ) { // Don't ping if blog is not public.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $url ) ) {
|
||||
$url = urlencode( WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ) );
|
||||
}
|
||||
|
||||
// Ping Google and Bing.
|
||||
wp_remote_get( 'https://www.google.com/ping?sitemap=' . $url, [ 'blocking' => false ] );
|
||||
wp_remote_get( 'https://www.bing.com/ping?sitemap=' . $url, [ 'blocking' => false ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum number of entries per XML sitemap.
|
||||
*
|
||||
* @return int The maximum number of entries.
|
||||
*/
|
||||
protected function get_entries_per_page() {
|
||||
/**
|
||||
* Filter the maximum number of entries per XML sitemap.
|
||||
*
|
||||
* After changing the output of the filter, make sure that you disable and enable the
|
||||
* sitemaps to make sure the value is picked up for the sitemap cache.
|
||||
*
|
||||
* @param int $entries The maximum number of entries per XML sitemap.
|
||||
*/
|
||||
$entries = (int) apply_filters( 'wpseo_sitemap_entries_per_page', 1000 );
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post statuses for post_type or the root sitemap.
|
||||
*
|
||||
* @param string $type Provide a type for a post_type sitemap, SITEMAP_INDEX_TYPE for the root sitemap.
|
||||
*
|
||||
* @since 10.2
|
||||
*
|
||||
* @return array List of post statuses.
|
||||
*/
|
||||
public static function get_post_statuses( $type = self::SITEMAP_INDEX_TYPE ) {
|
||||
/**
|
||||
* Filter post status list for sitemap query for the post type.
|
||||
*
|
||||
* @param array $post_statuses Post status list, defaults to array( 'publish' ).
|
||||
* @param string $type Post type or SITEMAP_INDEX_TYPE.
|
||||
*/
|
||||
$post_statuses = apply_filters( 'wpseo_sitemap_post_statuses', [ 'publish' ], $type );
|
||||
|
||||
if ( ! is_array( $post_statuses ) || empty( $post_statuses ) ) {
|
||||
$post_statuses = [ 'publish' ];
|
||||
}
|
||||
|
||||
if ( ( $type === self::SITEMAP_INDEX_TYPE || $type === 'attachment' )
|
||||
&& ! in_array( 'inherit', $post_statuses, true )
|
||||
) {
|
||||
$post_statuses[] = 'inherit';
|
||||
}
|
||||
|
||||
return $post_statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends all the required HTTP Headers.
|
||||
*/
|
||||
private function send_headers() {
|
||||
if ( headers_sent() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = [
|
||||
$this->http_protocol . ' 200 OK' => 200,
|
||||
// Prevent the search engines from indexing the XML Sitemap.
|
||||
'X-Robots-Tag: noindex, follow' => '',
|
||||
'Content-Type: text/xml; charset=' . esc_attr( $this->renderer->get_output_charset() ) => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter the HTTP headers we send before an XML sitemap.
|
||||
*
|
||||
* @param array $headers The HTTP headers we're going to send out.
|
||||
*/
|
||||
$headers = apply_filters( 'wpseo_sitemap_http_headers', $headers );
|
||||
|
||||
foreach ( $headers as $header => $status ) {
|
||||
if ( is_numeric( $status ) ) {
|
||||
header( $header, true, $status );
|
||||
continue;
|
||||
}
|
||||
header( $header, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap provider for author archives.
|
||||
*/
|
||||
class WPSEO_Taxonomy_Sitemap_Provider implements WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* Holds image parser instance.
|
||||
*
|
||||
* @var WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected static $image_parser;
|
||||
|
||||
/**
|
||||
* Determines whether images should be included in the XML sitemap.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $include_images;
|
||||
|
||||
/**
|
||||
* Set up object properties for data reuse.
|
||||
*/
|
||||
public function __construct() {
|
||||
/**
|
||||
* Filter - Allows excluding images from the XML sitemap.
|
||||
*
|
||||
* @param bool unsigned True to include, false to exclude.
|
||||
*/
|
||||
$this->include_images = apply_filters( 'wpseo_xml_sitemap_include_images', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
|
||||
$taxonomy = get_taxonomy( $type );
|
||||
|
||||
if ( $taxonomy === false || ! $this->is_valid_taxonomy( $taxonomy->name ) || ! $taxonomy->public ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the links for the sitemap.
|
||||
*
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
|
||||
$taxonomies = get_taxonomies( [ 'public' => true ], 'objects' );
|
||||
|
||||
if ( empty( $taxonomies ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$taxonomy_names = array_filter( array_keys( $taxonomies ), [ $this, 'is_valid_taxonomy' ] );
|
||||
$taxonomies = array_intersect_key( $taxonomies, array_flip( $taxonomy_names ) );
|
||||
|
||||
// Retrieve all the taxonomies and their terms so we can do a proper count on them.
|
||||
/**
|
||||
* Filter the setting of excluding empty terms from the XML sitemap.
|
||||
*
|
||||
* @param boolean $exclude Defaults to true.
|
||||
* @param array $taxonomy_names Array of names for the taxonomies being processed.
|
||||
*/
|
||||
$hide_empty = apply_filters( 'wpseo_sitemap_exclude_empty_terms', true, $taxonomy_names );
|
||||
|
||||
$all_taxonomies = [];
|
||||
|
||||
foreach ( $taxonomy_names as $taxonomy_name ) {
|
||||
/**
|
||||
* Filter the setting of excluding empty terms from the XML sitemap for a specific taxonomy.
|
||||
*
|
||||
* @param boolean $exclude Defaults to the sitewide setting.
|
||||
* @param string $taxonomy_name The name of the taxonomy being processed.
|
||||
*/
|
||||
$hide_empty_tax = apply_filters( 'wpseo_sitemap_exclude_empty_terms_taxonomy', $hide_empty, $taxonomy_name );
|
||||
|
||||
$term_args = [
|
||||
'hide_empty' => $hide_empty_tax,
|
||||
'fields' => 'ids',
|
||||
];
|
||||
$taxonomy_terms = get_terms( $taxonomy_name, $term_args );
|
||||
|
||||
if ( count( $taxonomy_terms ) > 0 ) {
|
||||
$all_taxonomies[ $taxonomy_name ] = $taxonomy_terms;
|
||||
}
|
||||
}
|
||||
|
||||
$index = [];
|
||||
|
||||
foreach ( $taxonomies as $tax_name => $tax ) {
|
||||
|
||||
if ( ! isset( $all_taxonomies[ $tax_name ] ) ) { // No eligible terms found.
|
||||
continue;
|
||||
}
|
||||
|
||||
$total_count = ( isset( $all_taxonomies[ $tax_name ] ) ) ? count( $all_taxonomies[ $tax_name ] ) : 1;
|
||||
$max_pages = 1;
|
||||
|
||||
if ( $total_count > $max_entries ) {
|
||||
$max_pages = (int) ceil( $total_count / $max_entries );
|
||||
}
|
||||
|
||||
$last_modified_gmt = WPSEO_Sitemaps::get_last_modified_gmt( $tax->object_type );
|
||||
|
||||
for ( $page_counter = 0; $page_counter < $max_pages; $page_counter++ ) {
|
||||
|
||||
$current_page = ( $max_pages > 1 ) ? ( $page_counter + 1 ) : '';
|
||||
|
||||
if ( ! is_array( $tax->object_type ) || count( $tax->object_type ) === 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$terms = array_splice( $all_taxonomies[ $tax_name ], 0, $max_entries );
|
||||
|
||||
if ( ! $terms ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$args = [
|
||||
'post_type' => $tax->object_type,
|
||||
'tax_query' => [
|
||||
[
|
||||
'taxonomy' => $tax_name,
|
||||
'terms' => $terms,
|
||||
],
|
||||
],
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'posts_per_page' => 1,
|
||||
];
|
||||
$query = new WP_Query( $args );
|
||||
|
||||
if ( $query->have_posts() ) {
|
||||
$date = $query->posts[0]->post_modified_gmt;
|
||||
}
|
||||
else {
|
||||
$date = $last_modified_gmt;
|
||||
}
|
||||
|
||||
$index[] = [
|
||||
'loc' => WPSEO_Sitemaps_Router::get_base_url( $tax_name . '-sitemap' . $current_page . '.xml' ),
|
||||
'lastmod' => $date,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get set of sitemap link data.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
* @param int $current_page Current page of the sitemap.
|
||||
*
|
||||
* @throws OutOfBoundsException When an invalid page is requested.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
global $wpdb;
|
||||
|
||||
$links = [];
|
||||
if ( ! $this->handles_type( $type ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$taxonomy = get_taxonomy( $type );
|
||||
|
||||
$steps = $max_entries;
|
||||
$offset = ( $current_page > 1 ) ? ( ( $current_page - 1 ) * $max_entries ) : 0;
|
||||
|
||||
/** This filter is documented in inc/sitemaps/class-taxonomy-sitemap-provider.php */
|
||||
$hide_empty = apply_filters( 'wpseo_sitemap_exclude_empty_terms', true, [ $taxonomy->name ] );
|
||||
/** This filter is documented in inc/sitemaps/class-taxonomy-sitemap-provider.php */
|
||||
$hide_empty_tax = apply_filters( 'wpseo_sitemap_exclude_empty_terms_taxonomy', $hide_empty, $taxonomy->name );
|
||||
$terms = get_terms( $taxonomy->name, [ 'hide_empty' => $hide_empty_tax ] );
|
||||
|
||||
// If the total term count is lower than the offset, we are on an invalid page.
|
||||
if ( count( $terms ) < $offset ) {
|
||||
throw new OutOfBoundsException( 'Invalid sitemap page requested' );
|
||||
}
|
||||
|
||||
$terms = array_splice( $terms, $offset, $steps );
|
||||
if ( empty( $terms ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$post_statuses = array_map( 'esc_sql', WPSEO_Sitemaps::get_post_statuses() );
|
||||
|
||||
// Grab last modified date.
|
||||
$sql = "
|
||||
SELECT MAX(p.post_modified_gmt) AS lastmod
|
||||
FROM $wpdb->posts AS p
|
||||
INNER JOIN $wpdb->term_relationships AS term_rel
|
||||
ON term_rel.object_id = p.ID
|
||||
INNER JOIN $wpdb->term_taxonomy AS term_tax
|
||||
ON term_tax.term_taxonomy_id = term_rel.term_taxonomy_id
|
||||
AND term_tax.taxonomy = %s
|
||||
AND term_tax.term_id = %d
|
||||
WHERE p.post_status IN ('" . implode( "','", $post_statuses ) . "')
|
||||
AND p.post_password = ''
|
||||
";
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_exclude_from_sitemap_by_term_ids' - Allow excluding terms by ID.
|
||||
*
|
||||
* @api array $terms_to_exclude The terms to exclude.
|
||||
*/
|
||||
$terms_to_exclude = apply_filters( 'wpseo_exclude_from_sitemap_by_term_ids', [] );
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
|
||||
if ( in_array( $term->term_id, $terms_to_exclude, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = [];
|
||||
|
||||
$tax_noindex = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'noindex' );
|
||||
|
||||
if ( $tax_noindex === 'noindex' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url['loc'] = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'canonical' );
|
||||
|
||||
if ( ! is_string( $url['loc'] ) || $url['loc'] === '' ) {
|
||||
$url['loc'] = get_term_link( $term, $term->taxonomy );
|
||||
}
|
||||
|
||||
$url['mod'] = $wpdb->get_var( $wpdb->prepare( $sql, $term->taxonomy, $term->term_id ) );
|
||||
|
||||
if ( $this->include_images ) {
|
||||
$url['images'] = $this->get_image_parser()->get_term_images( $term );
|
||||
}
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
$url['chf'] = 'daily';
|
||||
$url['pri'] = 1;
|
||||
|
||||
/** This filter is documented at inc/sitemaps/class-post-type-sitemap-provider.php */
|
||||
$url = apply_filters( 'wpseo_sitemap_entry', $url, 'term', $term );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
$links[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if taxonomy by name is valid to appear in sitemaps.
|
||||
*
|
||||
* @param string $taxonomy_name Taxonomy name to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_taxonomy( $taxonomy_name ) {
|
||||
|
||||
if ( WPSEO_Options::get( "noindex-tax-{$taxonomy_name}" ) === true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( in_array( $taxonomy_name, [ 'link_category', 'nav_menu' ], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $taxonomy_name === 'post_format' && WPSEO_Options::get( 'disable-post_format', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to exclude the taxonomy from the XML sitemap.
|
||||
*
|
||||
* @param boolean $exclude Defaults to false.
|
||||
* @param string $taxonomy_name Name of the taxonomy to exclude..
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_sitemap_exclude_taxonomy', false, $taxonomy_name ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Image Parser.
|
||||
*
|
||||
* @return WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected function get_image_parser() {
|
||||
if ( ! isset( self::$image_parser ) ) {
|
||||
self::$image_parser = new WPSEO_Sitemap_Image_Parser();
|
||||
}
|
||||
|
||||
return self::$image_parser;
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Get all the options.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function get_options() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 7.0', 'WPSEO_Options::get' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cache Data interface.
|
||||
*/
|
||||
interface WPSEO_Sitemap_Cache_Data_Interface {
|
||||
|
||||
/**
|
||||
* Status for normal, usable sitemap.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OK = 'ok';
|
||||
|
||||
/**
|
||||
* Status for unusable sitemap.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ERROR = 'error';
|
||||
|
||||
/**
|
||||
* Status for unusable sitemap because it cannot be identified.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const UNKNOWN = 'unknown';
|
||||
|
||||
/**
|
||||
* Set the content of the sitemap.
|
||||
*
|
||||
* @param string $sitemap The XML content of the sitemap.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_sitemap( $sitemap );
|
||||
|
||||
/**
|
||||
* Set the status of the sitemap.
|
||||
*
|
||||
* @param bool|string $usable True/False or 'ok'/'error' for status.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_status( $usable );
|
||||
|
||||
/**
|
||||
* Builds the sitemap.
|
||||
*
|
||||
* @return string The XML content of the sitemap.
|
||||
*/
|
||||
public function get_sitemap();
|
||||
|
||||
/**
|
||||
* Get the status of this sitemap.
|
||||
*
|
||||
* @return string Status 'ok', 'error' or 'unknown'.
|
||||
*/
|
||||
public function get_status();
|
||||
|
||||
/**
|
||||
* Is the sitemap content usable ?
|
||||
*
|
||||
* @return bool True if the sitemap is usable, False if not.
|
||||
*/
|
||||
public function is_usable();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap Provider interface.
|
||||
*/
|
||||
interface WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function handles_type( $type );
|
||||
|
||||
/**
|
||||
* Get set of sitemaps index link data.
|
||||
*
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries );
|
||||
|
||||
/**
|
||||
* Get set of sitemap link data.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
* @param int $current_page Current page of the sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page );
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Deprecated
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds help tabs.
|
||||
*
|
||||
* @deprecated 7.6.0
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $tabs Current help center tabs.
|
||||
*
|
||||
* @return array List containing all the additional tabs.
|
||||
*/
|
||||
function yoast_add_meta_options_help_center_tabs( $tabs ) {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.6.0', 'WPSEO_Help_Center_Template_Variables_Tab::add_meta_options_help_center_tabs' );
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds template variables to the help center.
|
||||
*
|
||||
* @deprecated 7.6.0
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The content for the template variables tab.
|
||||
*/
|
||||
function wpseo_add_template_variables_helpcenter() {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.6.0' );
|
||||
|
||||
return '';
|
||||
}
|
||||
258
wp-content/plugins/wordpress-seo/inc/wpseo-functions.php
Normal file
258
wp-content/plugins/wordpress-seo/inc/wpseo-functions.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
if ( ! defined( 'WPSEO_VERSION' ) ) {
|
||||
header( 'Status: 403 Forbidden' );
|
||||
header( 'HTTP/1.1 403 Forbidden' );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'initialize_wpseo_front' ) ) {
|
||||
/**
|
||||
* Wraps frontend class.
|
||||
*/
|
||||
function initialize_wpseo_front() {
|
||||
WPSEO_Frontend::get_instance();
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'yoast_breadcrumb' ) ) {
|
||||
/**
|
||||
* Template tag for breadcrumbs.
|
||||
*
|
||||
* @param string $before What to show before the breadcrumb.
|
||||
* @param string $after What to show after the breadcrumb.
|
||||
* @param bool $display Whether to display the breadcrumb (true) or return it (false).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function yoast_breadcrumb( $before = '', $after = '', $display = true ) {
|
||||
$breadcrumbs_enabled = current_theme_supports( 'yoast-seo-breadcrumbs' );
|
||||
if ( ! $breadcrumbs_enabled ) {
|
||||
$breadcrumbs_enabled = WPSEO_Options::get( 'breadcrumbs-enable', false );
|
||||
}
|
||||
|
||||
if ( $breadcrumbs_enabled ) {
|
||||
return WPSEO_Breadcrumbs::breadcrumb( $before, $after, $display );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'yoast_get_primary_term_id' ) ) {
|
||||
/**
|
||||
* Get the primary term ID.
|
||||
*
|
||||
* @param string $taxonomy Optional. The taxonomy to get the primary term ID for. Defaults to category.
|
||||
* @param null|int|WP_Post $post Optional. Post to get the primary term ID for.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
function yoast_get_primary_term_id( $taxonomy = 'category', $post = null ) {
|
||||
$post = get_post( $post );
|
||||
|
||||
$primary_term = new WPSEO_Primary_Term( $taxonomy, $post->ID );
|
||||
return $primary_term->get_primary_term();
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'yoast_get_primary_term' ) ) {
|
||||
/**
|
||||
* Get the primary term name.
|
||||
*
|
||||
* @param string $taxonomy Optional. The taxonomy to get the primary term for. Defaults to category.
|
||||
* @param null|int|WP_Post $post Optional. Post to get the primary term for.
|
||||
*
|
||||
* @return string Name of the primary term.
|
||||
*/
|
||||
function yoast_get_primary_term( $taxonomy = 'category', $post = null ) {
|
||||
$primary_term_id = yoast_get_primary_term_id( $taxonomy, $post );
|
||||
|
||||
$term = get_term( $primary_term_id );
|
||||
if ( ! is_wp_error( $term ) && ! empty( $term ) ) {
|
||||
return $term->name;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace `%%variable_placeholders%%` with their real value based on the current requested page/post/cpt.
|
||||
*
|
||||
* @param string $string The string to replace the variables in.
|
||||
* @param object $args The object some of the replacement values might come from,
|
||||
* could be a post, taxonomy or term.
|
||||
* @param array $omit Variables that should not be replaced by this function.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function wpseo_replace_vars( $string, $args, $omit = [] ) {
|
||||
$replacer = new WPSEO_Replace_Vars();
|
||||
|
||||
return $replacer->replace( $string, $args, $omit );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new variable replacement.
|
||||
*
|
||||
* This function is for use by other plugins/themes to easily add their own additional variables to replace.
|
||||
* This function should be called from a function on the 'wpseo_register_extra_replacements' action hook.
|
||||
* The use of this function is preferred over the older 'wpseo_replacements' filter as a way to add new replacements.
|
||||
* The 'wpseo_replacements' filter should still be used to adjust standard WPSEO replacement values.
|
||||
* The function can not be used to replace standard WPSEO replacement value functions and will thrown a warning
|
||||
* if you accidently try.
|
||||
* To avoid conflicts with variables registered by WPSEO and other themes/plugins, try and make the
|
||||
* name of your variable unique. Variable names also can not start with "%%cf_" or "%%ct_" as these are reserved
|
||||
* for the standard WPSEO variable variables 'cf_<custom-field-name>', 'ct_<custom-tax-name>' and
|
||||
* 'ct_desc_<custom-tax-name>'.
|
||||
* The replacement function will be passed the undelimited name (i.e. stripped of the %%) of the variable
|
||||
* to replace in case you need it.
|
||||
*
|
||||
* Example code:
|
||||
* <code>
|
||||
* <?php
|
||||
* function retrieve_var1_replacement( $var1 ) {
|
||||
* return 'your replacement value';
|
||||
* }
|
||||
*
|
||||
* function register_my_plugin_extra_replacements() {
|
||||
* wpseo_register_var_replacement( '%%myvar1%%', 'retrieve_var1_replacement', 'advanced', 'this is a help text for myvar1' );
|
||||
* wpseo_register_var_replacement( 'myvar2', array( 'class', 'method_name' ), 'basic', 'this is a help text for myvar2' );
|
||||
* }
|
||||
* add_action( 'wpseo_register_extra_replacements', 'register_my_plugin_extra_replacements' );
|
||||
* ?>
|
||||
* </code>
|
||||
*
|
||||
* @since 1.5.4
|
||||
*
|
||||
* @param string $var The name of the variable to replace, i.e. '%%var%%'.
|
||||
* Note: the surrounding %% are optional, name can only contain [A-Za-z0-9_-].
|
||||
* @param mixed $replace_function Function or method to call to retrieve the replacement value for the variable.
|
||||
* Uses the same format as add_filter/add_action function parameter and
|
||||
* should *return* the replacement value. DON'T echo it.
|
||||
* @param string $type Type of variable: 'basic' or 'advanced', defaults to 'advanced'.
|
||||
* @param string $help_text Help text to be added to the help tab for this variable.
|
||||
*
|
||||
* @return bool Whether the replacement function was successfully registered.
|
||||
*/
|
||||
function wpseo_register_var_replacement( $var, $replace_function, $type = 'advanced', $help_text = '' ) {
|
||||
return WPSEO_Replace_Vars::register_replacement( $var, $replace_function, $type, $help_text );
|
||||
}
|
||||
|
||||
/**
|
||||
* WPML plugin support: Set titles for custom types / taxonomies as translatable.
|
||||
*
|
||||
* It adds new keys to a wpml-config.xml file for a custom post type title, metadesc,
|
||||
* title-ptarchive and metadesc-ptarchive fields translation.
|
||||
* Documentation: http://wpml.org/documentation/support/language-configuration-files/
|
||||
*
|
||||
* @global $sitepress
|
||||
*
|
||||
* @param array $config WPML configuration data to filter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function wpseo_wpml_config( $config ) {
|
||||
global $sitepress;
|
||||
|
||||
if ( ( is_array( $config ) && isset( $config['wpml-config']['admin-texts']['key'] ) ) && ( is_array( $config['wpml-config']['admin-texts']['key'] ) && $config['wpml-config']['admin-texts']['key'] !== [] ) ) {
|
||||
$admin_texts = $config['wpml-config']['admin-texts']['key'];
|
||||
foreach ( $admin_texts as $k => $val ) {
|
||||
if ( $val['attr']['name'] === 'wpseo_titles' ) {
|
||||
$translate_cp = array_keys( $sitepress->get_translatable_documents() );
|
||||
if ( is_array( $translate_cp ) && $translate_cp !== [] ) {
|
||||
foreach ( $translate_cp as $post_type ) {
|
||||
$admin_texts[ $k ]['key'][]['attr']['name'] = 'title-' . $post_type;
|
||||
$admin_texts[ $k ]['key'][]['attr']['name'] = 'metadesc-' . $post_type;
|
||||
$admin_texts[ $k ]['key'][]['attr']['name'] = 'title-ptarchive-' . $post_type;
|
||||
$admin_texts[ $k ]['key'][]['attr']['name'] = 'metadesc-ptarchive-' . $post_type;
|
||||
|
||||
$translate_tax = $sitepress->get_translatable_taxonomies( false, $post_type );
|
||||
if ( is_array( $translate_tax ) && $translate_tax !== [] ) {
|
||||
foreach ( $translate_tax as $taxonomy ) {
|
||||
$admin_texts[ $k ]['key'][]['attr']['name'] = 'title-tax-' . $taxonomy;
|
||||
$admin_texts[ $k ]['key'][]['attr']['name'] = 'metadesc-tax-' . $taxonomy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$config['wpml-config']['admin-texts']['key'] = $admin_texts;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
add_filter( 'icl_wpml_config_array', 'wpseo_wpml_config' );
|
||||
|
||||
/**
|
||||
* Yoast SEO breadcrumb shortcode.
|
||||
* [wpseo_breadcrumb]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function wpseo_shortcode_yoast_breadcrumb() {
|
||||
return yoast_breadcrumb( '', '', false );
|
||||
}
|
||||
|
||||
add_shortcode( 'wpseo_breadcrumb', 'wpseo_shortcode_yoast_breadcrumb' );
|
||||
|
||||
if ( ! extension_loaded( 'ctype' ) || ! function_exists( 'ctype_digit' ) ) {
|
||||
/**
|
||||
* Emulate PHP native ctype_digit() function for when the ctype extension would be disabled *sigh*.
|
||||
* Only emulates the behaviour for when the input is a string, does not handle integer input as ascii value.
|
||||
*
|
||||
* @param string $string String input to validate.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function ctype_digit( $string ) {
|
||||
$return = false;
|
||||
if ( ( is_string( $string ) && $string !== '' ) && preg_match( '`^\d+$`', $string ) === 1 ) {
|
||||
$return = true;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the taxonomy meta is updated when a taxonomy term is split.
|
||||
*
|
||||
* @link https://make.wordpress.org/core/2015/02/16/taxonomy-term-splitting-in-4-2-a-developer-guide/ Article explaining the taxonomy term splitting in WP 4.2.
|
||||
*
|
||||
* @param string $old_term_id Old term id of the taxonomy term that was splitted.
|
||||
* @param string $new_term_id New term id of the taxonomy term that was splitted.
|
||||
* @param string $term_taxonomy_id Term taxonomy id for the taxonomy that was affected.
|
||||
* @param string $taxonomy The taxonomy that the taxonomy term was splitted for.
|
||||
*/
|
||||
function wpseo_split_shared_term( $old_term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
|
||||
$tax_meta = get_option( 'wpseo_taxonomy_meta', [] );
|
||||
|
||||
if ( ! empty( $tax_meta[ $taxonomy ][ $old_term_id ] ) ) {
|
||||
$tax_meta[ $taxonomy ][ $new_term_id ] = $tax_meta[ $taxonomy ][ $old_term_id ];
|
||||
unset( $tax_meta[ $taxonomy ][ $old_term_id ] );
|
||||
update_option( 'wpseo_taxonomy_meta', $tax_meta );
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'split_shared_term', 'wpseo_split_shared_term', 10, 4 );
|
||||
|
||||
/**
|
||||
* Get all WPSEO related capabilities.
|
||||
*
|
||||
* @since 8.3
|
||||
* @return array
|
||||
*/
|
||||
function wpseo_get_capabilities() {
|
||||
if ( ! did_action( 'wpseo_register_capabilities' ) ) {
|
||||
do_action( 'wpseo_register_capabilities' );
|
||||
}
|
||||
return WPSEO_Capability_Manager_Factory::get()->get_capabilities();
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
if ( ! defined( 'WPSEO_VERSION' ) ) {
|
||||
header( 'Status: 403 Forbidden' );
|
||||
header( 'HTTP/1.1 403 Forbidden' );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the admin bar.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_initialize_admin_bar() {
|
||||
$admin_bar_menu = new WPSEO_Admin_Bar_Menu();
|
||||
$admin_bar_menu->register_hooks();
|
||||
}
|
||||
add_action( 'wp_loaded', 'wpseo_initialize_admin_bar' );
|
||||
|
||||
/**
|
||||
* Allows editing of the meta fields through weblog editors like Marsedit.
|
||||
*
|
||||
* @param array $required_capabilities Capabilities that must all be true to allow action.
|
||||
* @param array $capabilities Array of capabilities to be checked, unused here.
|
||||
* @param array $args List of arguments for the specific capabilities to be checked.
|
||||
*
|
||||
* @return array $required_capabilities Filtered capabilities.
|
||||
*/
|
||||
function allow_custom_field_edits( $required_capabilities, $capabilities, $args ) {
|
||||
if ( ! in_array( $args[0], [ 'edit_post_meta', 'add_post_meta' ], true ) ) {
|
||||
return $required_capabilities;
|
||||
}
|
||||
|
||||
// If this is provided, it is the post ID.
|
||||
if ( empty( $args[2] ) ) {
|
||||
return $required_capabilities;
|
||||
}
|
||||
|
||||
// If this is provided, it is the custom field.
|
||||
if ( empty( $args[3] ) ) {
|
||||
return $required_capabilities;
|
||||
}
|
||||
|
||||
// If the meta key is part of the plugin, grant capabilities accordingly.
|
||||
if ( strpos( $args[3], WPSEO_Meta::$meta_prefix ) === 0 && current_user_can( 'edit_post', $args[2] ) ) {
|
||||
$required_capabilities[ $args[0] ] = true;
|
||||
}
|
||||
|
||||
return $required_capabilities;
|
||||
}
|
||||
|
||||
add_filter( 'user_has_cap', 'allow_custom_field_edits', 0, 3 );
|
||||
|
||||
/* ********************* DEPRECATED FUNCTIONS ********************* */
|
||||
|
||||
/**
|
||||
* Adds an SEO admin bar menu to the site admin, with several options.
|
||||
*
|
||||
* If the current user is an admin they can also go straight to several settings menus from here.
|
||||
*
|
||||
* @deprecated 7.9 Use WPSEO_Admin_Bar_Menu::add_menu() instead.
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_admin_bar_menu() {
|
||||
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.9', 'WPSEO_Admin_Bar_Menu::add_menu()' );
|
||||
|
||||
// Only use this admin bar menu for the site admin.
|
||||
if ( is_admin() && ! is_blog_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WPSEO_Options::get( 'enable_admin_bar_menu', true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wp_admin_bar;
|
||||
|
||||
$admin_bar_menu = new WPSEO_Admin_Bar_Menu();
|
||||
$admin_bar_menu->add_menu( $wp_admin_bar );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SEO score element for the admin bar.
|
||||
*
|
||||
* @deprecated 7.9
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function wpseo_adminbar_seo_score() {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.9', null );
|
||||
|
||||
$rating = WPSEO_Meta::get_value( 'linkdex', get_the_ID() );
|
||||
|
||||
return wpseo_adminbar_score( $rating );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content score element for the adminbar.
|
||||
*
|
||||
* @deprecated 7.9
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function wpseo_adminbar_content_score() {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.9', null );
|
||||
|
||||
$rating = WPSEO_Meta::get_value( 'content_score', get_the_ID() );
|
||||
|
||||
return wpseo_adminbar_score( $rating );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SEO score element for the adminbar.
|
||||
*
|
||||
* @deprecated 7.9
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function wpseo_tax_adminbar_seo_score() {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.9', null );
|
||||
|
||||
$rating = 0;
|
||||
|
||||
if ( is_tax() || is_category() || is_tag() ) {
|
||||
$rating = WPSEO_Taxonomy_Meta::get_meta_without_term( 'linkdex' );
|
||||
}
|
||||
|
||||
return wpseo_adminbar_score( $rating );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Content score element for the adminbar.
|
||||
*
|
||||
* @deprecated 7.9
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function wpseo_tax_adminbar_content_score() {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.9', null );
|
||||
|
||||
$rating = 0;
|
||||
|
||||
if ( is_tax() || is_category() || is_tag() ) {
|
||||
$rating = WPSEO_Taxonomy_Meta::get_meta_without_term( 'content_score' );
|
||||
}
|
||||
|
||||
return wpseo_adminbar_score( $rating );
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes The SEO score and makes the score icon for the adminbar with it.
|
||||
*
|
||||
* @deprecated 7.9
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $score The 0-100 rating of the score. Can be either SEO score or content score.
|
||||
*
|
||||
* @return string $score_adminbar_element
|
||||
*/
|
||||
function wpseo_adminbar_score( $score ) {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.9', null );
|
||||
|
||||
$score = WPSEO_Utils::translate_score( $score );
|
||||
|
||||
$score_adminbar_element = '<div class="wpseo-score-icon adminbar-seo-score ' . $score . '"><span class="adminbar-seo-score-text screen-reader-text"></span></div>';
|
||||
|
||||
return $score_adminbar_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue CSS to format the Yoast SEO adminbar item.
|
||||
*
|
||||
* @deprecated 7.9 Use WPSEO_Admin_Bar_Menu::enqueue_assets() instead.
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
function wpseo_admin_bar_style() {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.9', 'WPSEO_Admin_Bar_Menu::enqueue_assets()' );
|
||||
|
||||
if ( ! is_admin_bar_showing() || WPSEO_Options::get( 'enable_admin_bar_menu' ) !== true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_admin() && ! is_blog_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$admin_bar_menu = new WPSEO_Admin_Bar_Menu();
|
||||
$admin_bar_menu->enqueue_assets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if the advanced settings are enabled.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
function wpseo_advanced_settings_enabled() {
|
||||
_deprecated_function( __FUNCTION__, 'WPSEO 7.0', null );
|
||||
}
|
||||
Reference in New Issue
Block a user