423dd060 by Jeff Balicki

wordpress-seo

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 83889449
Showing 1000 changed files with 4984 additions and 0 deletions

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* A WordPress integration that listens for whether the SEO changes have been saved successfully.
*/
class WPSEO_Admin_Settings_Changed_Listener implements WPSEO_WordPress_Integration {
/**
* Have the Yoast SEO settings been saved.
*
* @var bool
*/
private static $settings_saved = false;
/**
* Registers all hooks to WordPress.
*
* @return void
*/
public function register_hooks() {
add_action( 'admin_init', [ $this, 'intercept_save_update_notification' ] );
}
/**
* Checks and overwrites the wp_settings_errors global to determine whether the Yoast SEO settings have been saved.
*/
public function intercept_save_update_notification() {
global $pagenow;
if ( $pagenow !== 'admin.php' || ! YoastSEO()->helpers->current_page->is_yoast_seo_page() ) {
return;
}
// Variable name is the same as the global that is set by get_settings_errors.
$wp_settings_errors = get_settings_errors();
foreach ( $wp_settings_errors as $key => $wp_settings_error ) {
if ( ! $this->is_settings_updated_notification( $wp_settings_error ) ) {
continue;
}
self::$settings_saved = true;
unset( $wp_settings_errors[ $key ] );
// phpcs:ignore WordPress.WP.GlobalVariablesOverride -- Overwrite the global with the list excluding the Changed saved message.
$GLOBALS['wp_settings_errors'] = $wp_settings_errors;
break;
}
}
/**
* Checks whether the settings notification is a settings_updated notification.
*
* @param array $wp_settings_error The settings object.
*
* @return bool Whether this is a settings updated settings notification.
*/
public function is_settings_updated_notification( $wp_settings_error ) {
return ! empty( $wp_settings_error['code'] ) && $wp_settings_error['code'] === 'settings_updated';
}
/**
* Get whether the settings have successfully been saved
*
* @return bool Whether the settings have successfully been saved.
*/
public function have_settings_been_saved() {
return self::$settings_saved;
}
/**
* Renders a success message if the Yoast SEO settings have been saved.
*/
public function show_success_message() {
if ( $this->have_settings_been_saved() ) {
echo '<p class="wpseo-message"><span class="dashicons dashicons-yes"></span>',
esc_html__( 'Settings saved.', 'wordpress-seo' ),
'</p>';
}
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Ajax
*/
/**
* Class WPSEO_Shortcode_Filter.
*
* Used for parsing WP shortcodes with AJAX.
*/
class WPSEO_Shortcode_Filter {
/**
* Initialize the AJAX hooks.
*/
public function __construct() {
add_action( 'wp_ajax_wpseo_filter_shortcodes', [ $this, 'do_filter' ] );
}
/**
* Parse the shortcodes.
*/
public function do_filter() {
check_ajax_referer( 'wpseo-filter-shortcodes', 'nonce' );
if ( ! isset( $_POST['data'] ) || ! is_array( $_POST['data'] ) ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: WPSEO_Utils::format_json_encode is considered safe.
wp_die( WPSEO_Utils::format_json_encode( [] ) );
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: $shortcodes is getting sanitized later, before it's used.
$shortcodes = wp_unslash( $_POST['data'] );
$parsed_shortcodes = [];
foreach ( $shortcodes as $shortcode ) {
if ( $shortcode !== sanitize_text_field( $shortcode ) ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: WPSEO_Utils::format_json_encode is considered safe.
wp_die( WPSEO_Utils::format_json_encode( [] ) );
}
$parsed_shortcodes[] = [
'shortcode' => $shortcode,
'output' => do_shortcode( $shortcode ),
];
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: WPSEO_Utils::format_json_encode is considered safe.
wp_die( WPSEO_Utils::format_json_encode( $parsed_shortcodes ) );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Ajax
*/
/**
* This class will catch the request to dismiss the target notice (set by notice_name)
* and will store the dismiss status as an user meta in the database.
*/
class Yoast_Dismissable_Notice_Ajax {
/**
* Notice type toggle value for user notices.
*
* @var string
*/
const FOR_USER = 'user_meta';
/**
* Notice type toggle value for network notices.
*
* @var string
*/
const FOR_NETWORK = 'site_option';
/**
* Notice type toggle value for site notices.
*
* @var string
*/
const FOR_SITE = 'option';
/**
* Name of the notice that will be dismissed.
*
* @var string
*/
private $notice_name;
/**
* The type of the current notice.
*
* @var string
*/
private $notice_type;
/**
* Initialize the hooks for the AJAX request.
*
* @param string $notice_name The name for the hook to catch the notice.
* @param string $notice_type The notice type.
*/
public function __construct( $notice_name, $notice_type = self::FOR_USER ) {
$this->notice_name = $notice_name;
$this->notice_type = $notice_type;
add_action( 'wp_ajax_wpseo_dismiss_' . $notice_name, [ $this, 'dismiss_notice' ] );
}
/**
* Handles the dismiss notice request.
*/
public function dismiss_notice() {
check_ajax_referer( 'wpseo-dismiss-' . $this->notice_name );
$this->save_dismissed();
wp_die( 'true' );
}
/**
* Storing the dismissed value in the database. The target location is based on the set notification type.
*/
private function save_dismissed() {
if ( $this->notice_type === self::FOR_SITE ) {
update_option( 'wpseo_dismiss_' . $this->notice_name, 1 );
return;
}
if ( $this->notice_type === self::FOR_NETWORK ) {
update_site_option( 'wpseo_dismiss_' . $this->notice_name, 1 );
return;
}
update_user_meta( get_current_user_id(), 'wpseo_dismiss_' . $this->notice_name, 1 );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Ajax
*/
/**
* Class Yoast_Plugin_Conflict_Ajax.
*/
class Yoast_Plugin_Conflict_Ajax {
/**
* Option identifier where dismissed conflicts are stored.
*
* @var string
*/
private $option_name = 'wpseo_dismissed_conflicts';
/**
* List of notification identifiers that have been dismissed.
*
* @var array
*/
private $dismissed_conflicts = [];
/**
* Initialize the hooks for the AJAX request.
*/
public function __construct() {
add_action( 'wp_ajax_wpseo_dismiss_plugin_conflict', [ $this, 'dismiss_notice' ] );
}
/**
* Handles the dismiss notice request.
*/
public function dismiss_notice() {
check_ajax_referer( 'dismiss-plugin-conflict' );
if ( ! isset( $_POST['data'] ) || ! is_array( $_POST['data'] ) ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: WPSEO_Utils::format_json_encode is considered safe.
wp_die( WPSEO_Utils::format_json_encode( [] ) );
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: $conflict_data is getting sanitized later.
$conflict_data = wp_unslash( $_POST['data'] );
$conflict_data = [
'section' => sanitize_text_field( $conflict_data['section'] ),
'plugins' => sanitize_text_field( $conflict_data['plugins'] ),
];
$this->dismissed_conflicts = $this->get_dismissed_conflicts( $conflict_data['section'] );
$this->compare_plugins( $conflict_data['plugins'] );
$this->save_dismissed_conflicts( $conflict_data['section'] );
wp_die( 'true' );
}
/**
* Getting the user option from the database.
*
* @return bool|array
*/
private function get_dismissed_option() {
return get_user_meta( get_current_user_id(), $this->option_name, true );
}
/**
* Getting the dismissed conflicts from the database
*
* @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
*
* @return array
*/
private function get_dismissed_conflicts( $plugin_section ) {
$dismissed_conflicts = $this->get_dismissed_option();
if ( is_array( $dismissed_conflicts ) && array_key_exists( $plugin_section, $dismissed_conflicts ) ) {
return $dismissed_conflicts[ $plugin_section ];
}
return [];
}
/**
* Storing the conflicting plugins as an user option in the database.
*
* @param string $plugin_section Plugin conflict type (such as Open Graph or sitemap).
*/
private function save_dismissed_conflicts( $plugin_section ) {
$dismissed_conflicts = $this->get_dismissed_option();
$dismissed_conflicts[ $plugin_section ] = $this->dismissed_conflicts;
update_user_meta( get_current_user_id(), $this->option_name, $dismissed_conflicts );
}
/**
* Loop through the plugins to compare them with the already stored dismissed plugin conflicts.
*
* @param array $posted_plugins Plugin set to check.
*/
public function compare_plugins( array $posted_plugins ) {
foreach ( $posted_plugins as $posted_plugin ) {
$this->compare_plugin( $posted_plugin );
}
}
/**
* Check if plugin is already dismissed, if not store it in the array that will be saved later.
*
* @param string $posted_plugin Plugin to check against dismissed conflicts.
*/
private function compare_plugin( $posted_plugin ) {
if ( ! in_array( $posted_plugin, $this->dismissed_conflicts, true ) ) {
$this->dismissed_conflicts[] = $posted_plugin;
}
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Capabilities
*/
/**
* Abstract Capability Manager shared code.
*/
abstract class WPSEO_Abstract_Capability_Manager implements WPSEO_Capability_Manager {
/**
* Registered capabilities.
*
* @var array
*/
protected $capabilities = [];
/**
* Registers a capability.
*
* @param string $capability Capability to register.
* @param array $roles Roles to add the capability to.
* @param bool $overwrite Optional. Use add or overwrite as registration method.
*/
public function register( $capability, array $roles, $overwrite = false ) {
if ( $overwrite || ! isset( $this->capabilities[ $capability ] ) ) {
$this->capabilities[ $capability ] = $roles;
return;
}
// Combine configurations.
$this->capabilities[ $capability ] = array_merge( $roles, $this->capabilities[ $capability ] );
// Remove doubles.
$this->capabilities[ $capability ] = array_unique( $this->capabilities[ $capability ] );
}
/**
* Returns the list of registered capabilitities.
*
* @return string[] Registered capabilities.
*/
public function get_capabilities() {
return array_keys( $this->capabilities );
}
/**
* Returns a list of WP_Role roles.
*
* The string array of role names are converted to actual WP_Role objects.
* These are needed to be able to use the API on them.
*
* @param array $roles Roles to retrieve the objects for.
*
* @return WP_Role[] List of WP_Role objects.
*/
protected function get_wp_roles( array $roles ) {
$wp_roles = array_map( 'get_role', $roles );
return array_filter( $wp_roles );
}
/**
* Filter capability roles.
*
* @param string $capability Capability to filter roles for.
* @param array $roles List of roles which can be filtered.
*
* @return array Filtered list of roles for the capability.
*/
protected function filter_roles( $capability, array $roles ) {
/**
* Filter: Allow changing roles that a capability is added to.
*
* @api array $roles The default roles to be filtered.
*/
$filtered = apply_filters( $capability . '_roles', $roles );
// Make sure we have the expected type.
if ( ! is_array( $filtered ) ) {
return [];
}
return $filtered;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Capabilities
*/
/**
* Capability Manager Factory.
*/
class WPSEO_Capability_Manager_Factory {
/**
* Returns the Manager to use.
*
* @param string $plugin_type Whether it's Free or Premium.
*
* @return WPSEO_Capability_Manager Manager to use.
*/
public static function get( $plugin_type = 'free' ) {
static $manager = [];
if ( ! array_key_exists( $plugin_type, $manager ) ) {
if ( function_exists( 'wpcom_vip_add_role_caps' ) ) {
$manager[ $plugin_type ] = new WPSEO_Capability_Manager_VIP();
}
if ( ! function_exists( 'wpcom_vip_add_role_caps' ) ) {
$manager[ $plugin_type ] = new WPSEO_Capability_Manager_WP();
}
}
return $manager[ $plugin_type ];
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Capabilities
*/
/**
* Integrates Yoast SEO capabilities with third party role manager plugins.
*
* Integrates with: Members
* Integrates with: User Role Editor
*/
class WPSEO_Capability_Manager_Integration implements WPSEO_WordPress_Integration {
/**
* Capability manager to use.
*
* @var WPSEO_Capability_Manager
*/
public $manager;
/**
* WPSEO_Capability_Manager_Integration constructor.
*
* @param WPSEO_Capability_Manager $manager The capability manager to use.
*/
public function __construct( WPSEO_Capability_Manager $manager ) {
$this->manager = $manager;
}
/**
* Registers the hooks.
*
* @return void
*/
public function register_hooks() {
add_filter( 'members_get_capabilities', [ $this, 'get_capabilities' ] );
add_action( 'members_register_cap_groups', [ $this, 'action_members_register_cap_group' ] );
add_filter( 'ure_capabilities_groups_tree', [ $this, 'filter_ure_capabilities_groups_tree' ] );
add_filter( 'ure_custom_capability_groups', [ $this, 'filter_ure_custom_capability_groups' ], 10, 2 );
}
/**
* Get the Yoast SEO capabilities.
* Optionally append them to an existing array.
*
* @param array $caps Optional existing capability list.
* @return array
*/
public function get_capabilities( array $caps = [] ) {
if ( ! did_action( 'wpseo_register_capabilities' ) ) {
do_action( 'wpseo_register_capabilities' );
}
return array_merge( $caps, $this->manager->get_capabilities() );
}
/**
* Add capabilities to its own group in the Members plugin.
*
* @see members_register_cap_group()
*/
public function action_members_register_cap_group() {
if ( ! function_exists( 'members_register_cap_group' ) ) {
return;
}
// Register the yoast group.
$args = [
'label' => esc_html__( 'Yoast SEO', 'wordpress-seo' ),
'caps' => $this->get_capabilities(),
'icon' => 'dashicons-admin-plugins',
'diff_added' => true,
];
members_register_cap_group( 'wordpress-seo', $args );
}
/**
* Adds Yoast SEO capability group in the User Role Editor plugin.
*
* @see URE_Capabilities_Groups_Manager::get_groups_tree()
*
* @param array $groups Current groups.
*
* @return array Filtered list of capabilty groups.
*/
public function filter_ure_capabilities_groups_tree( $groups = [] ) {
$groups = (array) $groups;
$groups['wordpress-seo'] = [
'caption' => 'Yoast SEO',
'parent' => 'custom',
'level' => 3,
];
return $groups;
}
/**
* Adds capabilities to the Yoast SEO group in the User Role Editor plugin.
*
* @see URE_Capabilities_Groups_Manager::get_cap_groups()
*
* @param array $groups Current capability groups.
* @param string $cap_id Capability identifier.
*
* @return array List of filtered groups.
*/
public function filter_ure_custom_capability_groups( $groups = [], $cap_id = '' ) {
if ( in_array( $cap_id, $this->get_capabilities(), true ) ) {
$groups = (array) $groups;
$groups[] = 'wordpress-seo';
}
return $groups;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Capabilities
*/
/**
* VIP implementation of the Capability Manager.
*/
final class WPSEO_Capability_Manager_VIP extends WPSEO_Abstract_Capability_Manager {
/**
* Adds the registered capabilities to the system.
*
* @return void
*/
public function add() {
$role_capabilities = [];
foreach ( $this->capabilities as $capability => $roles ) {
$role_capabilities = $this->get_role_capabilities( $role_capabilities, $capability, $roles );
}
foreach ( $role_capabilities as $role => $capabilities ) {
wpcom_vip_add_role_caps( $role, $capabilities );
}
}
/**
* Removes the registered capabilities from the system
*
* @return void
*/
public function remove() {
// Remove from any role it has been added to.
$roles = wp_roles()->get_names();
$roles = array_keys( $roles );
$role_capabilities = [];
foreach ( array_keys( $this->capabilities ) as $capability ) {
// Allow filtering of roles.
$role_capabilities = $this->get_role_capabilities( $role_capabilities, $capability, $roles );
}
foreach ( $role_capabilities as $role => $capabilities ) {
wpcom_vip_remove_role_caps( $role, $capabilities );
}
}
/**
* Returns the roles which the capability is registered on.
*
* @param array $role_capabilities List of all roles with their capabilities.
* @param string $capability Capability to filter roles for.
* @param array $roles List of default roles.
*
* @return array List of capabilities.
*/
protected function get_role_capabilities( $role_capabilities, $capability, $roles ) {
// Allow filtering of roles.
$filtered_roles = $this->filter_roles( $capability, $roles );
foreach ( $filtered_roles as $role ) {
if ( ! isset( $add_role_caps[ $role ] ) ) {
$role_capabilities[ $role ] = [];
}
$role_capabilities[ $role ][] = $capability;
}
return $role_capabilities;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Capabilities
*/
/**
* Default WordPress capability manager implementation.
*/
final class WPSEO_Capability_Manager_WP extends WPSEO_Abstract_Capability_Manager {
/**
* Adds the capabilities to the roles.
*
* @return void
*/
public function add() {
foreach ( $this->capabilities as $capability => $roles ) {
$filtered_roles = $this->filter_roles( $capability, $roles );
$wp_roles = $this->get_wp_roles( $filtered_roles );
foreach ( $wp_roles as $wp_role ) {
$wp_role->add_cap( $capability );
}
}
}
/**
* Unregisters the capabilities from the system.
*
* @return void
*/
public function remove() {
// Remove from any roles it has been added to.
$roles = wp_roles()->get_names();
$roles = array_keys( $roles );
foreach ( $this->capabilities as $capability => $_roles ) {
$registered_roles = array_unique( array_merge( $roles, $this->capabilities[ $capability ] ) );
// Allow filtering of roles.
$filtered_roles = $this->filter_roles( $capability, $registered_roles );
$wp_roles = $this->get_wp_roles( $filtered_roles );
foreach ( $wp_roles as $wp_role ) {
$wp_role->remove_cap( $capability );
}
}
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Capabilities
*/
/**
* Capability Manager interface.
*/
interface WPSEO_Capability_Manager {
/**
* Registers a capability.
*
* @param string $capability Capability to register.
* @param array $roles Roles to add the capability to.
* @param bool $overwrite Optional. Use add or overwrite as registration method.
*/
public function register( $capability, array $roles, $overwrite = false );
/**
* Adds the registerd capabilities to the system.
*/
public function add();
/**
* Removes the registered capabilities from the system.
*/
public function remove();
/**
* Returns the list of registered capabilities.
*
* @return string[] List of registered capabilities.
*/
public function get_capabilities();
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Capabilities
*/
/**
* Capability Utils collection.
*/
class WPSEO_Capability_Utils {
/**
* Checks if the user has the proper capabilities.
*
* @param string $capability Capability to check.
*
* @return bool True if the user has the proper rights.
*/
public static function current_user_can( $capability ) {
if ( $capability === 'wpseo_manage_options' ) {
return self::has( $capability );
}
return self::has_any( [ 'wpseo_manage_options', $capability ] );
}
/**
* Retrieves the users that have the specified capability.
*
* @param string $capability The name of the capability.
*
* @return array The users that have the capability.
*/
public static function get_applicable_users( $capability ) {
$applicable_roles = self::get_applicable_roles( $capability );
if ( $applicable_roles === [] ) {
return [];
}
return get_users( [ 'role__in' => $applicable_roles ] );
}
/**
* Retrieves the roles that have the specified capability.
*
* @param string $capability The name of the capability.
*
* @return array The names of the roles that have the capability.
*/
public static function get_applicable_roles( $capability ) {
$roles = wp_roles();
$role_names = $roles->get_names();
$applicable_roles = [];
foreach ( array_keys( $role_names ) as $role_name ) {
$role = $roles->get_role( $role_name );
if ( ! $role ) {
continue;
}
// Add role if it has the capability.
if ( array_key_exists( $capability, $role->capabilities ) && $role->capabilities[ $capability ] === true ) {
$applicable_roles[] = $role_name;
}
}
return $applicable_roles;
}
/**
* Checks if the current user has at least one of the supplied capabilities.
*
* @param array $capabilities Capabilities to check against.
*
* @return bool True if the user has at least one capability.
*/
protected static function has_any( array $capabilities ) {
foreach ( $capabilities as $capability ) {
if ( self::has( $capability ) ) {
return true;
}
}
return false;
}
/**
* Checks if the user has a certain capability.
*
* @param string $capability Capability to check against.
*
* @return bool True if the user has the capability.
*/
protected static function has( $capability ) {
return current_user_can( $capability );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Capabilities
*/
/**
* Capabilities registration class.
*/
class WPSEO_Register_Capabilities implements WPSEO_WordPress_Integration {
/**
* Registers the hooks.
*
* @return void
*/
public function register_hooks() {
add_action( 'wpseo_register_capabilities', [ $this, 'register' ] );
if ( is_multisite() ) {
add_action( 'user_has_cap', [ $this, 'filter_user_has_wpseo_manage_options_cap' ], 10, 4 );
}
/**
* Maybe add manage_privacy_options capability for wpseo_manager user role.
*/
add_filter( 'map_meta_cap', [ $this, 'map_meta_cap_for_seo_manager' ], 10, 2 );
}
/**
* Registers the capabilities.
*
* @return void
*/
public function register() {
$manager = WPSEO_Capability_Manager_Factory::get();
$manager->register( 'wpseo_bulk_edit', [ 'editor', 'wpseo_editor', 'wpseo_manager' ] );
$manager->register( 'wpseo_edit_advanced_metadata', [ 'editor', 'wpseo_editor', 'wpseo_manager' ] );
$manager->register( 'wpseo_manage_options', [ 'administrator', 'wpseo_manager' ] );
$manager->register( 'view_site_health_checks', [ 'wpseo_manager' ] );
}
/**
* Revokes the 'wpseo_manage_options' capability from administrator users if it should
* only be granted to network administrators.
*
* @param array $allcaps An array of all the user's capabilities.
* @param array $caps Actual capabilities being checked.
* @param array $args Optional parameters passed to has_cap(), typically object ID.
* @param WP_User $user The user object.
*
* @return array Possibly modified array of the user's capabilities.
*/
public function filter_user_has_wpseo_manage_options_cap( $allcaps, $caps, $args, $user ) {
// We only need to do something if 'wpseo_manage_options' is being checked.
if ( ! in_array( 'wpseo_manage_options', $caps, true ) ) {
return $allcaps;
}
// If the user does not have 'wpseo_manage_options' anyway, we don't need to revoke access.
if ( empty( $allcaps['wpseo_manage_options'] ) ) {
return $allcaps;
}
// If the user does not have 'delete_users', they are not an administrator.
if ( empty( $allcaps['delete_users'] ) ) {
return $allcaps;
}
$options = WPSEO_Options::get_instance();
if ( $options->get( 'access' ) === 'superadmin' && ! is_super_admin( $user->ID ) ) {
unset( $allcaps['wpseo_manage_options'] );
}
return $allcaps;
}
/**
* Maybe add manage_privacy_options capability for wpseo_manager user role.
*
* @param string[] $caps Primitive capabilities required of the user.
* @param string[] $cap Capability being checked.
*
* @return string[] Filtered primitive capabilities required of the user.
*/
public function map_meta_cap_for_seo_manager( $caps, $cap ) {
$user = wp_get_current_user();
// No multisite support.
if ( is_multisite() ) {
return $caps;
}
// User must be of role wpseo_manager.
if ( ! in_array( 'wpseo_manager', $user->roles, true ) ) {
return $caps;
}
// Remove manage_options cap requirement if requested cap is manage_privacy_options.
if ( $cap === 'manage_privacy_options' ) {
return array_diff( $caps, [ 'manage_options' ] );
}
return $caps;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Represents a way to determine the analysis worker asset location.
*/
final class WPSEO_Admin_Asset_Analysis_Worker_Location implements WPSEO_Admin_Asset_Location {
/**
* Holds the asset's location.
*
* @var WPSEO_Admin_Asset_Location
*/
private $asset_location;
/**
* Holds the asset itself.
*
* @var WPSEO_Admin_Asset
*/
private $asset;
/**
* Constructs the location of the analysis worker asset.
*
* @param string $flat_version The flat version of the asset.
* @param string $name The name of the analysis worker asset.
*/
public function __construct( $flat_version = '', $name = 'analysis-worker' ) {
if ( $flat_version === '' ) {
$asset_manager = new WPSEO_Admin_Asset_Manager();
$flat_version = $asset_manager->flatten_version( WPSEO_VERSION );
}
$analysis_worker = $name . '-' . $flat_version . '.js';
$this->asset_location = WPSEO_Admin_Asset_Manager::create_default_location();
$this->asset = new WPSEO_Admin_Asset(
[
'name' => $name,
'src' => $analysis_worker,
]
);
}
/**
* Retrieves the analysis worker asset.
*
* @return WPSEO_Admin_Asset The analysis worker asset.
*/
public function get_asset() {
return $this->asset;
}
/**
* Determines the URL of the asset on the dev server.
*
* @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
* @param string $type The type of asset. Usually JS or CSS.
*
* @return string The URL of the asset.
*/
public function get_url( WPSEO_Admin_Asset $asset, $type ) {
$scheme = wp_parse_url( $asset->get_src(), PHP_URL_SCHEME );
if ( in_array( $scheme, [ 'http', 'https' ], true ) ) {
return $asset->get_src();
}
return $this->asset_location->get_url( $asset, $type );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Changes the asset paths to dev server paths.
*/
final class WPSEO_Admin_Asset_Dev_Server_Location implements WPSEO_Admin_Asset_Location {
/**
* Holds the dev server's default URL.
*
* @var string
*/
const DEFAULT_URL = 'http://localhost:8080';
/**
* Holds the url where the server is located.
*
* @var string
*/
private $url;
/**
* Class constructor.
*
* @param string|null $url Where the dev server is located.
*/
public function __construct( $url = null ) {
if ( $url === null ) {
$url = self::DEFAULT_URL;
}
$this->url = $url;
}
/**
* Determines the URL of the asset on the dev server.
*
* @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
* @param string $type The type of asset. Usually JS or CSS.
*
* @return string The URL of the asset.
*/
public function get_url( WPSEO_Admin_Asset $asset, $type ) {
if ( $type === WPSEO_Admin_Asset::TYPE_CSS ) {
return $this->get_default_url( $asset, $type );
}
$path = sprintf( 'js/dist/%s%s.js', $asset->get_src(), $asset->get_suffix() );
return trailingslashit( $this->url ) . $path;
}
/**
* Determines the URL of the asset not using the dev server.
*
* @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
* @param string $type The type of asset.
*
* @return string The URL of the asset file.
*/
public function get_default_url( WPSEO_Admin_Asset $asset, $type ) {
$default_location = new WPSEO_Admin_Asset_SEO_Location( WPSEO_FILE );
return $default_location->get_url( $asset, $type );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Represents a way to determine an assets location.
*/
interface WPSEO_Admin_Asset_Location {
/**
* Determines the URL of the asset on the dev server.
*
* @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
* @param string $type The type of asset. Usually JS or CSS.
*
* @return string The URL of the asset.
*/
public function get_url( WPSEO_Admin_Asset $asset, $type );
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Determines the location of an asset within the SEO plugin.
*/
final class WPSEO_Admin_Asset_SEO_Location implements WPSEO_Admin_Asset_Location {
/**
* Path to the plugin file.
*
* @var string
*/
protected $plugin_file;
/**
* Whether or not to add the file suffix to the asset.
*
* @var bool
*/
protected $add_suffix = true;
/**
* The plugin file to base the asset location upon.
*
* @param string $plugin_file The plugin file string.
* @param bool $add_suffix Optional. Whether or not a file suffix should be added.
*/
public function __construct( $plugin_file, $add_suffix = true ) {
$this->plugin_file = $plugin_file;
$this->add_suffix = $add_suffix;
}
/**
* Determines the URL of the asset on the dev server.
*
* @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
* @param string $type The type of asset. Usually JS or CSS.
*
* @return string The URL of the asset.
*/
public function get_url( WPSEO_Admin_Asset $asset, $type ) {
$path = $this->get_path( $asset, $type );
if ( empty( $path ) ) {
return '';
}
return plugins_url( $path, $this->plugin_file );
}
/**
* Determines the path relative to the plugin folder of an asset.
*
* @param WPSEO_Admin_Asset $asset The asset to determine the path for.
* @param string $type The type of asset.
*
* @return string The path to the asset file.
*/
protected function get_path( WPSEO_Admin_Asset $asset, $type ) {
$relative_path = '';
$rtl_suffix = '';
switch ( $type ) {
case WPSEO_Admin_Asset::TYPE_JS:
$relative_path = 'js/dist/' . $asset->get_src();
if ( $this->add_suffix ) {
$relative_path .= $asset->get_suffix() . '.js';
}
break;
case WPSEO_Admin_Asset::TYPE_CSS:
// Path and suffix for RTL stylesheets.
if ( is_rtl() && $asset->has_rtl() ) {
$rtl_suffix = '-rtl';
}
$relative_path = 'css/dist/' . $asset->get_src() . $rtl_suffix . $asset->get_suffix() . '.css';
break;
}
return $relative_path;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Localizes JavaScript files.
*
* @codeCoverageIgnore
* @deprecated 18.0
*/
final class WPSEO_Admin_Asset_Yoast_Components_L10n {
/**
* Represents the asset manager.
*
* @var WPSEO_Admin_Asset_Manager
*/
protected $asset_manager;
/**
* WPSEO_Admin_Asset_Yoast_Components_L10n constructor.
*
* @codeCoverageIgnore
* @deprecated 18.0
*/
public function __construct() {
_deprecated_constructor( __CLASS__, '18.0' );
$this->asset_manager = new WPSEO_Admin_Asset_Manager();
}
/**
* Localizes the given script with the JavaScript translations.
*
* @codeCoverageIgnore
* @deprecated 18.0
*
* @param string $script_handle The script handle to localize for.
*
* @return void
*/
public function localize_script( $script_handle ) {
_deprecated_function( __FUNCTION__, '18.0' );
$translations = [
'yoast-components' => $this->get_translations( 'yoast-components' ),
'wordpress-seo' => $this->get_translations( 'wordpress-seojs' ),
'yoast-schema-blocks' => $this->get_translations( 'yoast-schema-blocks' ),
];
$this->asset_manager->localize_script( $script_handle, 'wpseoYoastJSL10n', $translations );
}
/**
* Returns translations necessary for JS files.
*
* @codeCoverageIgnore
* @deprecated 18.0
*
* @param string $component The component to retrieve the translations for.
* @return object|null The translations in a Jed format for JS files.
*/
protected function get_translations( $component ) {
_deprecated_function( __FUNCTION__, '18.0' );
$locale = \get_user_locale();
$file = WPSEO_PATH . 'languages/' . $component . '-' . $locale . '.json';
if ( file_exists( $file ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Retrieving a local file.
$file = file_get_contents( $file );
if ( is_string( $file ) && $file !== '' ) {
return json_decode( $file, true );
}
}
return null;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Determines the editor specific replacement variables.
*/
class WPSEO_Admin_Editor_Specific_Replace_Vars {
/**
* Holds the editor specific replacements variables.
*
* @var array The editor specific replacement variables.
*/
protected $replacement_variables = [
// Posts types.
'page' => [ 'id', 'pt_single', 'pt_plural', 'parent_title' ],
'post' => [ 'id', 'term404', 'pt_single', 'pt_plural' ],
// Custom post type.
'custom_post_type' => [ 'id', 'term404', 'pt_single', 'pt_plural', 'parent_title' ],
// Settings - archive pages.
'custom-post-type_archive' => [ 'pt_single', 'pt_plural' ],
// Taxonomies.
'category' => [ 'term_title', 'term_description', 'category_description', 'parent_title', 'term_hierarchy' ],
'post_tag' => [ 'term_title', 'term_description', 'tag_description' ],
'post_format' => [ 'term_title' ],
// Custom taxonomy.
'term-in-custom-taxonomy' => [ 'term_title', 'term_description', 'category_description', 'parent_title', 'term_hierarchy' ],
// Settings - special pages.
'search' => [ 'searchphrase' ],
];
/**
* WPSEO_Admin_Editor_Specific_Replace_Vars constructor.
*/
public function __construct() {
$this->add_for_page_types(
[ 'page', 'post', 'custom_post_type' ],
WPSEO_Custom_Fields::get_custom_fields()
);
$this->add_for_page_types(
[ 'post', 'term-in-custom-taxonomy' ],
WPSEO_Custom_Taxonomies::get_custom_taxonomies()
);
}
/**
* Retrieves the editor specific replacement variables.
*
* @return array The editor specific replacement variables.
*/
public function get() {
/**
* Filter: Adds the possibility to add extra editor specific replacement variables.
*
* @api array $replacement_variables Array of editor specific replace vars.
*/
$replacement_variables = apply_filters(
'wpseo_editor_specific_replace_vars',
$this->replacement_variables
);
if ( ! is_array( $replacement_variables ) ) {
$replacement_variables = $this->replacement_variables;
}
return array_filter( $replacement_variables, 'is_array' );
}
/**
* Retrieves the generic replacement variable names.
*
* Which are the replacement variables without the editor specific ones.
*
* @param array $replacement_variables Possibly generic replacement variables.
*
* @return array The generic replacement variable names.
*/
public function get_generic( $replacement_variables ) {
$shared_variables = array_diff(
$this->extract_names( $replacement_variables ),
$this->get_unique_replacement_variables()
);
return array_values( $shared_variables );
}
/**
* Determines the page type of the current term.
*
* @param string $taxonomy The taxonomy name.
*
* @return string The page type.
*/
public function determine_for_term( $taxonomy ) {
$replacement_variables = $this->get();
if ( array_key_exists( $taxonomy, $replacement_variables ) ) {
return $taxonomy;
}
return 'term-in-custom-taxonomy';
}
/**
* Determines the page type of the current post.
*
* @param WP_Post $post A WordPress post instance.
*
* @return string The page type.
*/
public function determine_for_post( $post ) {
if ( $post instanceof WP_Post === false ) {
return 'post';
}
$replacement_variables = $this->get();
if ( array_key_exists( $post->post_type, $replacement_variables ) ) {
return $post->post_type;
}
return 'custom_post_type';
}
/**
* Determines the page type for a post type.
*
* @param string $post_type The name of the post_type.
* @param string $fallback The page type to fall back to.
*
* @return string The page type.
*/
public function determine_for_post_type( $post_type, $fallback = 'custom_post_type' ) {
if ( ! $this->has_for_page_type( $post_type ) ) {
return $fallback;
}
return $post_type;
}
/**
* Determines the page type for an archive page.
*
* @param string $name The name of the archive.
* @param string $fallback The page type to fall back to.
*
* @return string The page type.
*/
public function determine_for_archive( $name, $fallback = 'custom-post-type_archive' ) {
$page_type = $name . '_archive';
if ( ! $this->has_for_page_type( $page_type ) ) {
return $fallback;
}
return $page_type;
}
/**
* Adds the replavement variables for the given page types.
*
* @param array $page_types Page types to add variables for.
* @param array $replacement_variables_to_add The variables to add.
*
* @return void
*/
protected function add_for_page_types( array $page_types, array $replacement_variables_to_add ) {
if ( empty( $replacement_variables_to_add ) ) {
return;
}
$replacement_variables_to_add = array_fill_keys( $page_types, $replacement_variables_to_add );
$replacement_variables = $this->replacement_variables;
$this->replacement_variables = array_merge_recursive( $replacement_variables, $replacement_variables_to_add );
}
/**
* Extracts the names from the given replacements variables.
*
* @param array $replacement_variables Replacement variables to extract the name from.
*
* @return array Extracted names.
*/
protected function extract_names( $replacement_variables ) {
$extracted_names = [];
foreach ( $replacement_variables as $replacement_variable ) {
if ( empty( $replacement_variable['name'] ) ) {
continue;
}
$extracted_names[] = $replacement_variable['name'];
}
return $extracted_names;
}
/**
* Returns whether the given page type has editor specific replace vars.
*
* @param string $page_type The page type to check.
*
* @return bool True if there are associated editor specific replace vars.
*/
protected function has_for_page_type( $page_type ) {
$replacement_variables = $this->get();
return ( ! empty( $replacement_variables[ $page_type ] ) && is_array( $replacement_variables[ $page_type ] ) );
}
/**
* Merges all editor specific replacement variables into one array and removes duplicates.
*
* @return array The list of unique editor specific replacement variables.
*/
protected function get_unique_replacement_variables() {
$merged_replacement_variables = call_user_func_array( 'array_merge', array_values( $this->get() ) );
return array_unique( $merged_replacement_variables );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Handles the Gutenberg Compatibility notification showing and hiding.
*/
class WPSEO_Admin_Gutenberg_Compatibility_Notification implements WPSEO_WordPress_Integration {
/**
* Notification ID to use.
*
* @var string
*/
private $notification_id = 'wpseo-outdated-gutenberg-plugin';
/**
* Instance of gutenberg compatibility checker.
*
* @var WPSEO_Gutenberg_Compatibility
*/
protected $compatibility_checker;
/**
* Instance of Yoast Notification Center.
*
* @var Yoast_Notification_Center
*/
protected $notification_center;
/**
* WPSEO_Admin_Gutenberg_Compatibility_Notification constructor.
*/
public function __construct() {
$this->compatibility_checker = new WPSEO_Gutenberg_Compatibility();
$this->notification_center = Yoast_Notification_Center::get();
}
/**
* Registers all hooks to WordPress.
*
* @return void
*/
public function register_hooks() {
add_action( 'admin_init', [ $this, 'manage_notification' ] );
}
/**
* Manages if the notification should be shown or removed.
*
* @return void
*/
public function manage_notification() {
/**
* Filter: 'yoast_display_gutenberg_compat_notification' - Allows developer to disable the Gutenberg compatibility
* notification.
*
* @api bool
*/
$display_notification = apply_filters( 'yoast_display_gutenberg_compat_notification', true );
if (
! $this->compatibility_checker->is_installed()
|| $this->compatibility_checker->is_fully_compatible()
|| ! $display_notification
) {
$this->notification_center->remove_notification_by_id( $this->notification_id );
return;
}
$this->add_notification();
}
/**
* Adds the notification to the notificaton center.
*
* @return void
*/
protected function add_notification() {
$level = $this->compatibility_checker->is_below_minimum() ? Yoast_Notification::ERROR : Yoast_Notification::WARNING;
$message = sprintf(
/* translators: %1$s expands to Yoast SEO, %2$s expands to the installed version, %3$s expands to Gutenberg */
__( '%1$s detected you are using version %2$s of %3$s, please update to the latest version to prevent compatibility issues.', 'wordpress-seo' ),
'Yoast SEO',
$this->compatibility_checker->get_installed_version(),
'Gutenberg'
);
$notification = new Yoast_Notification(
$message,
[
'id' => $this->notification_id,
'type' => $level,
'priority' => 1,
]
);
$this->notification_center->add_notification( $notification );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Generates the HTML for an inline Help Button and Panel.
*/
class WPSEO_Admin_Help_Panel {
/**
* Unique identifier of the element the inline help refers to, used as an identifier in the html.
*
* @var string
*/
private $id;
/**
* The Help Button text. Needs a properly escaped string.
*
* @var string
*/
private $help_button_text;
/**
* The Help Panel content. Needs a properly escaped string (might contain HTML).
*
* @var string
*/
private $help_content;
/**
* Optional Whether to print out a container div element for the Help Panel, used for styling.
*
* @var string
*/
private $wrapper;
/**
* Constructor.
*
* @param string $id Unique identifier of the element the inline help refers to, used as
* an identifier in the html.
* @param string $help_button_text The Help Button text. Needs a properly escaped string.
* @param string $help_content The Help Panel content. Needs a properly escaped string (might contain HTML).
* @param string $wrapper Optional Whether to print out a container div element for the Help Panel,
* used for styling.
* Pass a `has-wrapper` value to print out the container. Default: no container.
*/
public function __construct( $id, $help_button_text, $help_content, $wrapper = '' ) {
$this->id = $id;
$this->help_button_text = $help_button_text;
$this->help_content = $help_content;
$this->wrapper = $wrapper;
}
/**
* Returns the html for the Help Button.
*
* @return string
*/
public function get_button_html() {
if ( ! $this->id || ! $this->help_button_text || ! $this->help_content ) {
return '';
}
return sprintf(
' <button type="button" class="yoast_help yoast-help-button dashicons" id="%1$s-help-toggle" aria-expanded="false" aria-controls="%1$s-help"><span class="yoast-help-icon" aria-hidden="true"></span><span class="screen-reader-text">%2$s</span></button>',
esc_attr( $this->id ),
$this->help_button_text
);
}
/**
* Returns the html for the Help Panel.
*
* @return string
*/
public function get_panel_html() {
if ( ! $this->id || ! $this->help_button_text || ! $this->help_content ) {
return '';
}
$wrapper_start = '';
$wrapper_end = '';
if ( $this->wrapper === 'has-wrapper' ) {
$wrapper_start = '<div class="yoast-seo-help-container">';
$wrapper_end = '</div>';
}
return sprintf(
'%1$s<p id="%2$s-help" class="yoast-help-panel">%3$s</p>%4$s',
$wrapper_start,
esc_attr( $this->id ),
$this->help_content,
$wrapper_end
);
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Determines the recommended replacement variables based on the context.
*/
class WPSEO_Admin_Recommended_Replace_Vars {
/**
* The recommended replacement variables.
*
* @var array
*/
protected $recommended_replace_vars = [
// Posts types.
'page' => [ 'sitename', 'title', 'sep', 'primary_category' ],
'post' => [ 'sitename', 'title', 'sep', 'primary_category' ],
// Homepage.
'homepage' => [ 'sitename', 'sitedesc', 'sep' ],
// Custom post type.
'custom_post_type' => [ 'sitename', 'title', 'sep' ],
// Taxonomies.
'category' => [ 'sitename', 'term_title', 'sep', 'term_hierarchy' ],
'post_tag' => [ 'sitename', 'term_title', 'sep' ],
'post_format' => [ 'sitename', 'term_title', 'sep', 'page' ],
// Custom taxonomy.
'term-in-custom-taxonomy' => [ 'sitename', 'term_title', 'sep', 'term_hierarchy' ],
// Settings - archive pages.
'author_archive' => [ 'sitename', 'title', 'sep', 'page' ],
'date_archive' => [ 'sitename', 'sep', 'date', 'page' ],
'custom-post-type_archive' => [ 'sitename', 'title', 'sep', 'page' ],
// Settings - special pages.
'search' => [ 'sitename', 'searchphrase', 'sep', 'page' ],
'404' => [ 'sitename', 'sep' ],
];
/**
* Determines the page type of the current term.
*
* @param string $taxonomy The taxonomy name.
*
* @return string The page type.
*/
public function determine_for_term( $taxonomy ) {
$recommended_replace_vars = $this->get_recommended_replacevars();
if ( array_key_exists( $taxonomy, $recommended_replace_vars ) ) {
return $taxonomy;
}
return 'term-in-custom-taxonomy';
}
/**
* Determines the page type of the current post.
*
* @param WP_Post $post A WordPress post instance.
*
* @return string The page type.
*/
public function determine_for_post( $post ) {
if ( $post instanceof WP_Post === false ) {
return 'post';
}
if ( $post->post_type === 'page' && $this->is_homepage( $post ) ) {
return 'homepage';
}
$recommended_replace_vars = $this->get_recommended_replacevars();
if ( array_key_exists( $post->post_type, $recommended_replace_vars ) ) {
return $post->post_type;
}
return 'custom_post_type';
}
/**
* Determines the page type for a post type.
*
* @param string $post_type The name of the post_type.
* @param string $fallback The page type to fall back to.
*
* @return string The page type.
*/
public function determine_for_post_type( $post_type, $fallback = 'custom_post_type' ) {
$page_type = $post_type;
$recommended_replace_vars = $this->get_recommended_replacevars();
$has_recommended_replacevars = $this->has_recommended_replace_vars( $recommended_replace_vars, $page_type );
if ( ! $has_recommended_replacevars ) {
return $fallback;
}
return $page_type;
}
/**
* Determines the page type for an archive page.
*
* @param string $name The name of the archive.
* @param string $fallback The page type to fall back to.
*
* @return string The page type.
*/
public function determine_for_archive( $name, $fallback = 'custom-post-type_archive' ) {
$page_type = $name . '_archive';
$recommended_replace_vars = $this->get_recommended_replacevars();
$has_recommended_replacevars = $this->has_recommended_replace_vars( $recommended_replace_vars, $page_type );
if ( ! $has_recommended_replacevars ) {
return $fallback;
}
return $page_type;
}
/**
* Retrieves the recommended replacement variables for the given page type.
*
* @param string $page_type The page type.
*
* @return array The recommended replacement variables.
*/
public function get_recommended_replacevars_for( $page_type ) {
$recommended_replace_vars = $this->get_recommended_replacevars();
$has_recommended_replace_vars = $this->has_recommended_replace_vars( $recommended_replace_vars, $page_type );
if ( ! $has_recommended_replace_vars ) {
return [];
}
return $recommended_replace_vars[ $page_type ];
}
/**
* Retrieves the recommended replacement variables.
*
* @return array The recommended replacement variables.
*/
public function get_recommended_replacevars() {
/**
* Filter: Adds the possibility to add extra recommended replacement variables.
*
* @api array $additional_replace_vars Empty array to add the replacevars to.
*/
$recommended_replace_vars = apply_filters( 'wpseo_recommended_replace_vars', $this->recommended_replace_vars );
if ( ! is_array( $recommended_replace_vars ) ) {
return $this->recommended_replace_vars;
}
return $recommended_replace_vars;
}
/**
* Returns whether the given page type has recommended replace vars.
*
* @param array $recommended_replace_vars The recommended replace vars
* to check in.
* @param string $page_type The page type to check.
*
* @return bool True if there are associated recommended replace vars.
*/
private function has_recommended_replace_vars( $recommended_replace_vars, $page_type ) {
if ( ! isset( $recommended_replace_vars[ $page_type ] ) ) {
return false;
}
if ( ! is_array( $recommended_replace_vars[ $page_type ] ) ) {
return false;
}
return true;
}
/**
* Determines whether or not a post is the homepage.
*
* @param WP_Post $post The WordPress global post object.
*
* @return bool True if the given post is the homepage.
*/
private function is_homepage( $post ) {
if ( $post instanceof WP_Post === false ) {
return false;
}
/*
* The page on front returns a string with normal WordPress interaction, while the post ID is an int.
* This way we make sure we always compare strings.
*/
$post_id = (int) $post->ID;
$page_on_front = (int) get_option( 'page_on_front' );
return get_option( 'show_on_front' ) === 'page' && $page_on_front === $post_id;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
* @since 1.8.0
*/
/**
* Customizes user profile.
*/
class WPSEO_Admin_User_Profile {
/**
* Class constructor.
*/
public function __construct() {
add_action( 'show_user_profile', [ $this, 'user_profile' ] );
add_action( 'edit_user_profile', [ $this, 'user_profile' ] );
add_action( 'personal_options_update', [ $this, 'process_user_option_update' ] );
add_action( 'edit_user_profile_update', [ $this, 'process_user_option_update' ] );
add_action( 'update_user_meta', [ $this, 'clear_author_sitemap_cache' ], 10, 3 );
}
/**
* Clear author sitemap cache when settings are changed.
*
* @since 3.1
*
* @param int $meta_id The ID of the meta option changed.
* @param int $object_id The ID of the user.
* @param string $meta_key The key of the meta field changed.
*/
public function clear_author_sitemap_cache( $meta_id, $object_id, $meta_key ) {
if ( $meta_key === '_yoast_wpseo_profile_updated' ) {
WPSEO_Sitemaps_Cache::clear( [ 'author' ] );
}
}
/**
* Updates the user metas that (might) have been set on the user profile page.
*
* @param int $user_id User ID of the updated user.
*/
public function process_user_option_update( $user_id ) {
update_user_meta( $user_id, '_yoast_wpseo_profile_updated', time() );
if ( ! check_admin_referer( 'wpseo_user_profile_update', 'wpseo_nonce' ) ) {
return;
}
$wpseo_author_title = isset( $_POST['wpseo_author_title'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_author_title'] ) ) : '';
$wpseo_author_metadesc = isset( $_POST['wpseo_author_metadesc'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_author_metadesc'] ) ) : '';
$wpseo_noindex_author = isset( $_POST['wpseo_noindex_author'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_noindex_author'] ) ) : '';
$wpseo_content_analysis_disable = isset( $_POST['wpseo_content_analysis_disable'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_content_analysis_disable'] ) ) : '';
$wpseo_keyword_analysis_disable = isset( $_POST['wpseo_keyword_analysis_disable'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_keyword_analysis_disable'] ) ) : '';
$wpseo_inclusive_language_analysis_disable = isset( $_POST['wpseo_inclusive_language_analysis_disable'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_inclusive_language_analysis_disable'] ) ) : '';
update_user_meta( $user_id, 'wpseo_title', $wpseo_author_title );
update_user_meta( $user_id, 'wpseo_metadesc', $wpseo_author_metadesc );
update_user_meta( $user_id, 'wpseo_noindex_author', $wpseo_noindex_author );
update_user_meta( $user_id, 'wpseo_content_analysis_disable', $wpseo_content_analysis_disable );
update_user_meta( $user_id, 'wpseo_keyword_analysis_disable', $wpseo_keyword_analysis_disable );
update_user_meta( $user_id, 'wpseo_inclusive_language_analysis_disable', $wpseo_inclusive_language_analysis_disable );
}
/**
* Add the inputs needed for SEO values to the User Profile page.
*
* @param WP_User $user User instance to output for.
*/
public function user_profile( $user ) {
wp_nonce_field( 'wpseo_user_profile_update', 'wpseo_nonce' );
require_once WPSEO_PATH . 'admin/views/user-profile.php';
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Represents the utils for the admin.
*/
class WPSEO_Admin_Utils {
/**
* Gets the install URL for the passed plugin slug.
*
* @param string $slug The slug to create an install link for.
*
* @return string The install URL. Empty string if the current user doesn't have the proper capabilities.
*/
public static function get_install_url( $slug ) {
if ( ! current_user_can( 'install_plugins' ) ) {
return '';
}
return wp_nonce_url(
self_admin_url( 'update.php?action=install-plugin&plugin=' . dirname( $slug ) ),
'install-plugin_' . dirname( $slug )
);
}
/**
* Gets the activation URL for the passed plugin slug.
*
* @param string $slug The slug to create an activation link for.
*
* @return string The activation URL. Empty string if the current user doesn't have the proper capabilities.
*/
public static function get_activation_url( $slug ) {
if ( ! current_user_can( 'install_plugins' ) ) {
return '';
}
return wp_nonce_url(
self_admin_url( 'plugins.php?action=activate&plugin_status=all&paged=1&s&plugin=' . $slug ),
'activate-plugin_' . $slug
);
}
/**
* Creates a link if the passed plugin is deemend a directly-installable plugin.
*
* @param array $plugin The plugin to create the link for.
*
* @return string The link to the plugin install. Returns the title if the plugin is deemed a Premium product.
*/
public static function get_install_link( $plugin ) {
$install_url = self::get_install_url( $plugin['slug'] );
if ( $install_url === '' || ( isset( $plugin['premium'] ) && $plugin['premium'] === true ) ) {
return $plugin['title'];
}
return sprintf(
'<a href="%s">%s</a>',
$install_url,
$plugin['title']
);
}
/**
* Gets a visually hidden accessible message for links that open in a new browser tab.
*
* @return string The visually hidden accessible message.
*/
public static function get_new_tab_message() {
return sprintf(
'<span class="screen-reader-text">%s</span>',
esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' )
);
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Represents a WPSEO asset
*/
class WPSEO_Admin_Asset {
/**
* Constant used to identify file type as a JS file.
*
* @var string
*/
const TYPE_JS = 'js';
/**
* Constant used to identify file type as a CSS file.
*
* @var string
*/
const TYPE_CSS = 'css';
/**
* The name option identifier.
*
* @var string
*/
const NAME = 'name';
/**
* The source option identifier.
*
* @var string
*/
const SRC = 'src';
/**
* The dependencies option identifier.
*
* @var string
*/
const DEPS = 'deps';
/**
* The version option identifier.
*
* @var string
*/
const VERSION = 'version';
/* Style specific. */
/**
* The media option identifier.
*
* @var string
*/
const MEDIA = 'media';
/**
* The rtl option identifier.
*
* @var string
*/
const RTL = 'rtl';
/* Script specific. */
/**
* The "in footer" option identifier.
*
* @var string
*/
const IN_FOOTER = 'in_footer';
/**
* Asset identifier.
*
* @var string
*/
protected $name;
/**
* Path to the asset.
*
* @var string
*/
protected $src;
/**
* Asset dependencies.
*
* @var string|array
*/
protected $deps;
/**
* Asset version.
*
* @var string
*/
protected $version;
/**
* For CSS Assets. The type of media for which this stylesheet has been defined.
*
* See https://www.w3.org/TR/CSS2/media.html#media-types.
*
* @var string
*/
protected $media;
/**
* For JS Assets. Whether or not the script should be loaded in the footer.
*
* @var bool
*/
protected $in_footer;
/**
* For CSS Assets. Whether this stylesheet is a right-to-left stylesheet.
*
* @var bool
*/
protected $rtl;
/**
* File suffix.
*
* @var string
*/
protected $suffix;
/**
* Default asset arguments.
*
* @var array
*/
private $defaults = [
'deps' => [],
'in_footer' => true,
'rtl' => true,
'media' => 'all',
'version' => '',
'suffix' => '',
];
/**
* Constructs an instance of the WPSEO_Admin_Asset class.
*
* @param array $args The arguments for this asset.
*
* @throws InvalidArgumentException Throws when no name or src has been provided.
*/
public function __construct( array $args ) {
if ( ! isset( $args['name'] ) ) {
throw new InvalidArgumentException( 'name is a required argument' );
}
if ( ! isset( $args['src'] ) ) {
throw new InvalidArgumentException( 'src is a required argument' );
}
$args = array_merge( $this->defaults, $args );
$this->name = $args['name'];
$this->src = $args['src'];
$this->deps = $args['deps'];
$this->version = $args['version'];
$this->media = $args['media'];
$this->in_footer = $args['in_footer'];
$this->rtl = $args['rtl'];
$this->suffix = $args['suffix'];
}
/**
* Returns the asset identifier.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Returns the path to the asset.
*
* @return string
*/
public function get_src() {
return $this->src;
}
/**
* Returns the asset dependencies.
*
* @return array|string
*/
public function get_deps() {
return $this->deps;
}
/**
* Returns the asset version.
*
* @return string|null
*/
public function get_version() {
if ( ! empty( $this->version ) ) {
return $this->version;
}
return null;
}
/**
* Returns the media type for CSS assets.
*
* @return string
*/
public function get_media() {
return $this->media;
}
/**
* Returns whether a script asset should be loaded in the footer of the page.
*
* @return bool
*/
public function is_in_footer() {
return $this->in_footer;
}
/**
* Returns whether this CSS has a RTL counterpart.
*
* @return bool
*/
public function has_rtl() {
return $this->rtl;
}
/**
* Returns the file suffix.
*
* @return string
*/
public function get_suffix() {
return $this->suffix;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Bulk Editor
* @since 1.5.0
*/
/**
* Implements table for bulk description editing.
*/
class WPSEO_Bulk_Description_List_Table extends WPSEO_Bulk_List_Table {
/**
* Current type for this class will be (meta) description.
*
* @var string
*/
protected $page_type = 'description';
/**
* Settings with are used in __construct.
*
* @var array
*/
protected $settings = [
'singular' => 'wpseo_bulk_description',
'plural' => 'wpseo_bulk_descriptions',
'ajax' => true,
];
/**
* The field in the database where meta field is saved.
*
* @var string
*/
protected $target_db_field = 'metadesc';
/**
* The columns shown on the table.
*
* @return array
*/
public function get_columns() {
$columns = [
'col_existing_yoast_seo_metadesc' => __( 'Existing Yoast Meta Description', 'wordpress-seo' ),
'col_new_yoast_seo_metadesc' => __( 'New Yoast Meta Description', 'wordpress-seo' ),
];
return $this->merge_columns( $columns );
}
/**
* Parse the metadescription.
*
* @param string $column_name Column name.
* @param object $record Data object.
* @param string $attributes HTML attributes.
*
* @return string
*/
protected function parse_page_specific_column( $column_name, $record, $attributes ) {
switch ( $column_name ) {
case 'col_new_yoast_seo_metadesc':
return sprintf(
'<textarea id="%1$s" name="%1$s" class="wpseo-new-metadesc" data-id="%2$s" aria-labelledby="col_new_yoast_seo_metadesc"></textarea>',
esc_attr( 'wpseo-new-metadesc-' . $record->ID ),
esc_attr( $record->ID )
);
case 'col_existing_yoast_seo_metadesc':
// @todo Inconsistent return/echo behavior R.
// I traced the escaping of the attributes to WPSEO_Bulk_List_Table::column_attributes. Alexander.
// The output of WPSEO_Bulk_List_Table::parse_meta_data_field is properly escaped.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->parse_meta_data_field( $record->ID, $attributes );
break;
}
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Bulk Editor
* @since 1.5.0
*/
/**
* Implements table for bulk title editing.
*/
class WPSEO_Bulk_Title_Editor_List_Table extends WPSEO_Bulk_List_Table {
/**
* Current type for this class will be title.
*
* @var string
*/
protected $page_type = 'title';
/**
* Settings with are used in __construct.
*
* @var array
*/
protected $settings = [
'singular' => 'wpseo_bulk_title',
'plural' => 'wpseo_bulk_titles',
'ajax' => true,
];
/**
* The field in the database where meta field is saved.
*
* @var string
*/
protected $target_db_field = 'title';
/**
* The columns shown on the table.
*
* @return array
*/
public function get_columns() {
$columns = [
/* translators: %1$s expands to Yoast SEO */
'col_existing_yoast_seo_title' => sprintf( __( 'Existing %1$s Title', 'wordpress-seo' ), 'Yoast SEO' ),
/* translators: %1$s expands to Yoast SEO */
'col_new_yoast_seo_title' => sprintf( __( 'New %1$s Title', 'wordpress-seo' ), 'Yoast SEO' ),
];
return $this->merge_columns( $columns );
}
/**
* Parse the title columns.
*
* @param string $column_name Column name.
* @param object $record Data object.
* @param string $attributes HTML attributes.
*
* @return string
*/
protected function parse_page_specific_column( $column_name, $record, $attributes ) {
// Fill meta data if exists in $this->meta_data.
$meta_data = ( ! empty( $this->meta_data[ $record->ID ] ) ) ? $this->meta_data[ $record->ID ] : [];
switch ( $column_name ) {
case 'col_existing_yoast_seo_title':
// @todo Inconsistent return/echo behavior R.
// I traced the escaping of the attributes to WPSEO_Bulk_List_Table::column_attributes.
// The output of WPSEO_Bulk_List_Table::parse_meta_data_field is properly escaped.
// phpcs:ignore WordPress.Security.EscapeOutput
echo $this->parse_meta_data_field( $record->ID, $attributes );
break;
case 'col_new_yoast_seo_title':
return sprintf(
'<input type="text" id="%1$s" name="%1$s" class="wpseo-new-title" data-id="%2$s" aria-labelledby="col_new_yoast_seo_title" />',
'wpseo-new-title-' . $record->ID,
$record->ID
);
}
unset( $meta_data );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Collects the data from the added collection objects.
*/
class WPSEO_Collector {
/**
* Holds the collections.
*
* @var WPSEO_Collection[]
*/
protected $collections = [];
/**
* Adds a collection object to the collections.
*
* @param WPSEO_Collection $collection The collection object to add.
*/
public function add_collection( WPSEO_Collection $collection ) {
$this->collections[] = $collection;
}
/**
* Collects the data from the collection objects.
*
* @return array The collected data.
*/
public function collect() {
$data = [];
foreach ( $this->collections as $collection ) {
$data = array_merge( $data, $collection->get() );
}
return $data;
}
/**
* Returns the collected data as a JSON encoded string.
*
* @return false|string The encode string.
*/
public function get_as_json() {
return WPSEO_Utils::format_json_encode( $this->collect() );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
use Yoast\WP\SEO\Integrations\Academy_Integration;
use Yoast\WP\SEO\Integrations\Settings_Integration;
use Yoast\WP\SEO\Integrations\Support_Integration;
/**
* Class WPSEO_Admin_Pages.
*
* Class with functionality for the Yoast SEO admin pages.
*/
class WPSEO_Admin_Pages {
/**
* The option in use for the current admin page.
*
* @var string
*/
public $currentoption = 'wpseo';
/**
* Holds the asset manager.
*
* @var WPSEO_Admin_Asset_Manager
*/
private $asset_manager;
/**
* Class constructor, which basically only hooks the init function on the init hook.
*/
public function __construct() {
add_action( 'init', [ $this, 'init' ], 20 );
$this->asset_manager = new WPSEO_Admin_Asset_Manager();
}
/**
* Make sure the needed scripts are loaded for admin pages.
*/
public function init() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
if ( \in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE ], true ) ) {
// Bail, this is managed in the applicable integration.
return;
}
add_action( 'admin_enqueue_scripts', [ $this, 'config_page_scripts' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'config_page_styles' ] );
}
/**
* Loads the required styles for the config page.
*/
public function config_page_styles() {
wp_enqueue_style( 'dashboard' );
wp_enqueue_style( 'thickbox' );
wp_enqueue_style( 'global' );
wp_enqueue_style( 'wp-admin' );
$this->asset_manager->enqueue_style( 'admin-css' );
$this->asset_manager->enqueue_style( 'monorepo' );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
if ( $page === 'wpseo_licenses' ) {
$this->asset_manager->enqueue_style( 'tailwind' );
}
}
/**
* Loads the required scripts for the config page.
*/
public function config_page_scripts() {
$this->asset_manager->enqueue_script( 'settings' );
wp_enqueue_script( 'dashboard' );
wp_enqueue_script( 'thickbox' );
$alert_dismissal_action = YoastSEO()->classes->get( \Yoast\WP\SEO\Actions\Alert_Dismissal_Action::class );
$dismissed_alerts = $alert_dismissal_action->all_dismissed();
$script_data = [
'userLanguageCode' => WPSEO_Language_Utils::get_language( \get_user_locale() ),
'dismissedAlerts' => $dismissed_alerts,
'isRtl' => is_rtl(),
'isPremium' => YoastSEO()->helpers->product->is_premium(),
'webinarIntroSettingsUrl' => WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-settings' ),
'webinarIntroFirstTimeConfigUrl' => $this->get_webinar_shortlink(),
];
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, 'wpseo_workouts' ], true ) ) {
wp_enqueue_media();
$script_data['media'] = [
'choose_image' => __( 'Use Image', 'wordpress-seo' ),
];
$script_data['userEditUrl'] = add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) );
}
if ( $page === 'wpseo_tools' ) {
$this->enqueue_tools_scripts();
}
$this->asset_manager->localize_script( 'settings', 'wpseoScriptData', $script_data );
$this->asset_manager->enqueue_user_language_script();
}
/**
* Retrieves some variables that are needed for replacing variables in JS.
*
* @deprecated 20.3
* @codeCoverageIgnore
*
* @return array The replacement and recommended replacement variables.
*/
public function get_replace_vars_script_data() {
_deprecated_function( __METHOD__, 'Yoast SEO 20.3' );
$replace_vars = new WPSEO_Replace_Vars();
$recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
$editor_specific_replace_vars = new WPSEO_Admin_Editor_Specific_Replace_Vars();
$replace_vars_list = $replace_vars->get_replacement_variables_list();
return [
'replace_vars' => $replace_vars_list,
'recommended_replace_vars' => $recommended_replace_vars->get_recommended_replacevars(),
'editor_specific_replace_vars' => $editor_specific_replace_vars->get(),
'shared_replace_vars' => $editor_specific_replace_vars->get_generic( $replace_vars_list ),
'hidden_replace_vars' => $replace_vars->get_hidden_replace_vars(),
];
}
/**
* Enqueues and handles all the tool dependencies.
*/
private function enqueue_tools_scripts() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$tool = isset( $_GET['tool'] ) && is_string( $_GET['tool'] ) ? sanitize_text_field( wp_unslash( $_GET['tool'] ) ) : '';
if ( empty( $tool ) ) {
$this->asset_manager->enqueue_script( 'yoast-seo' );
}
if ( $tool === 'bulk-editor' ) {
$this->asset_manager->enqueue_script( 'bulk-editor' );
}
}
/**
* Returns the appropriate shortlink for the Webinar.
*
* @return string The shortlink for the Webinar.
*/
private function get_webinar_shortlink() {
if ( YoastSEO()->helpers->product->is_premium() ) {
return WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-first-time-config-premium' );
}
return WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-first-time-config' );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Customizer
*/
/**
* Class with functionality to support WP SEO settings in WordPress Customizer.
*/
class WPSEO_Customizer {
/**
* Holds the customize manager.
*
* @var WP_Customize_Manager
*/
protected $wp_customize;
/**
* Template for the setting IDs used for the customizer.
*
* @var string
*/
private $setting_template = 'wpseo_titles[%s]';
/**
* Default arguments for the breadcrumbs customizer settings object.
*
* @var array
*/
private $default_setting_args = [
'default' => '',
'type' => 'option',
'transport' => 'refresh',
];
/**
* Default arguments for the breadcrumbs customizer control object.
*
* @var array
*/
private $default_control_args = [
'label' => '',
'type' => 'text',
'section' => 'wpseo_breadcrumbs_customizer_section',
'settings' => '',
'context' => '',
];
/**
* Construct Method.
*/
public function __construct() {
add_action( 'customize_register', [ $this, 'wpseo_customize_register' ] );
}
/**
* Function to support WordPress Customizer.
*
* @param WP_Customize_Manager $wp_customize Manager class instance.
*/
public function wpseo_customize_register( $wp_customize ) {
if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
return;
}
$this->wp_customize = $wp_customize;
$this->breadcrumbs_section();
$this->breadcrumbs_blog_show_setting();
$this->breadcrumbs_separator_setting();
$this->breadcrumbs_home_setting();
$this->breadcrumbs_prefix_setting();
$this->breadcrumbs_archiveprefix_setting();
$this->breadcrumbs_searchprefix_setting();
$this->breadcrumbs_404_setting();
}
/**
* Add the breadcrumbs section to the customizer.
*/
private function breadcrumbs_section() {
$section_args = [
/* translators: %s is the name of the plugin */
'title' => sprintf( __( '%s Breadcrumbs', 'wordpress-seo' ), 'Yoast SEO' ),
'priority' => 999,
'active_callback' => [ $this, 'breadcrumbs_active_callback' ],
];
$this->wp_customize->add_section( 'wpseo_breadcrumbs_customizer_section', $section_args );
}
/**
* Returns whether or not the breadcrumbs are active.
*
* @return bool
*/
public function breadcrumbs_active_callback() {
return current_theme_supports( 'yoast-seo-breadcrumbs' ) || WPSEO_Options::get( 'breadcrumbs-enable' );
}
/**
* Adds the breadcrumbs show blog checkbox.
*/
private function breadcrumbs_blog_show_setting() {
$index = 'breadcrumbs-display-blog-page';
$control_args = [
'label' => __( 'Show blog page in breadcrumbs', 'wordpress-seo' ),
'type' => 'checkbox',
'active_callback' => [ $this, 'breadcrumbs_blog_show_active_cb' ],
];
$this->add_setting_and_control( $index, $control_args );
}
/**
* Returns whether or not to show the breadcrumbs blog show option.
*
* @return bool
*/
public function breadcrumbs_blog_show_active_cb() {
return get_option( 'show_on_front' ) === 'page';
}
/**
* Adds the breadcrumbs separator text field.
*/
private function breadcrumbs_separator_setting() {
$index = 'breadcrumbs-sep';
$control_args = [
'label' => __( 'Breadcrumbs separator:', 'wordpress-seo' ),
];
$id = 'wpseo-breadcrumbs-separator';
$this->add_setting_and_control( $index, $control_args, $id );
}
/**
* Adds the breadcrumbs home anchor text field.
*/
private function breadcrumbs_home_setting() {
$index = 'breadcrumbs-home';
$control_args = [
'label' => __( 'Anchor text for the homepage:', 'wordpress-seo' ),
];
$this->add_setting_and_control( $index, $control_args );
}
/**
* Adds the breadcrumbs prefix text field.
*/
private function breadcrumbs_prefix_setting() {
$index = 'breadcrumbs-prefix';
$control_args = [
'label' => __( 'Prefix for breadcrumbs:', 'wordpress-seo' ),
];
$this->add_setting_and_control( $index, $control_args );
}
/**
* Adds the breadcrumbs archive prefix text field.
*/
private function breadcrumbs_archiveprefix_setting() {
$index = 'breadcrumbs-archiveprefix';
$control_args = [
'label' => __( 'Prefix for archive pages:', 'wordpress-seo' ),
];
$this->add_setting_and_control( $index, $control_args );
}
/**
* Adds the breadcrumbs search prefix text field.
*/
private function breadcrumbs_searchprefix_setting() {
$index = 'breadcrumbs-searchprefix';
$control_args = [
'label' => __( 'Prefix for search result pages:', 'wordpress-seo' ),
];
$this->add_setting_and_control( $index, $control_args );
}
/**
* Adds the breadcrumb 404 prefix text field.
*/
private function breadcrumbs_404_setting() {
$index = 'breadcrumbs-404crumb';
$control_args = [
'label' => __( 'Breadcrumb for 404 pages:', 'wordpress-seo' ),
];
$this->add_setting_and_control( $index, $control_args );
}
/**
* Adds the customizer setting and control.
*
* @param string $index Array key index to use for the customizer setting.
* @param array $control_args Customizer control object arguments.
* Only those different from the default need to be passed.
* @param string|null $id Optional. Customizer control object ID.
* Will default to 'wpseo-' . $index.
* @param array $custom_settings Optional. Customizer setting arguments.
* Only those different from the default need to be passed.
*/
private function add_setting_and_control( $index, $control_args, $id = null, $custom_settings = [] ) {
$setting = sprintf( $this->setting_template, $index );
$control_args = array_merge( $this->default_control_args, $control_args );
$control_args['settings'] = $setting;
$settings_args = $this->default_setting_args;
if ( ! empty( $custom_settings ) ) {
$settings_args = array_merge( $settings_args, $custom_settings );
}
if ( ! isset( $id ) ) {
$id = 'wpseo-' . $index;
}
$this->wp_customize->add_setting( $setting, $settings_args );
$control = new WP_Customize_Control( $this->wp_customize, $id, $control_args );
$this->wp_customize->add_control( $control );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Represents the proxy for communicating with the database.
*/
class WPSEO_Database_Proxy {
/**
* Holds the table name.
*
* @var string
*/
protected $table_name;
/**
* Determines whether to suppress errors or not.
*
* @var bool
*/
protected $suppress_errors = true;
/**
* Determines if this table is multisite.
*
* @var bool
*/
protected $is_multisite_table = false;
/**
* Holds the last suppressed state.
*
* @var bool
*/
protected $last_suppressed_state;
/**
* Holds the WordPress database object.
*
* @var wpdb
*/
protected $database;
/**
* Sets the class attributes and registers the table.
*
* @param wpdb $database The database object.
* @param string $table_name The table name that is represented.
* @param bool $suppress_errors Should the errors be suppressed.
* @param bool $is_multisite_table Should the table be global in multisite.
*/
public function __construct( $database, $table_name, $suppress_errors = true, $is_multisite_table = false ) {
$this->table_name = $table_name;
$this->suppress_errors = (bool) $suppress_errors;
$this->is_multisite_table = (bool) $is_multisite_table;
$this->database = $database;
// If the table prefix was provided, strip it as it's handled automatically.
$table_prefix = $this->get_table_prefix();
if ( ! empty( $table_prefix ) && strpos( $this->table_name, $table_prefix ) === 0 ) {
$this->table_prefix = substr( $this->table_name, strlen( $table_prefix ) );
}
if ( ! $this->is_table_registered() ) {
$this->register_table();
}
}
/**
* Inserts data into the database.
*
* @param array $data Data to insert.
* @param array|string|null $format Formats for the data.
*
* @return false|int Total amount of inserted rows or false on error.
*/
public function insert( array $data, $format = null ) {
$this->pre_execution();
$result = $this->database->insert( $this->get_table_name(), $data, $format );
$this->post_execution();
return $result;
}
/**
* Updates data in the database.
*
* @param array $data Data to update on the table.
* @param array $where Where condition as key => value array.
* @param array|string|null $format Optional. Data prepare format.
* @param array|string|null $where_format Optional. Where prepare format.
*
* @return false|int False when the update request is invalid, int on number of rows changed.
*/
public function update( array $data, array $where, $format = null, $where_format = null ) {
$this->pre_execution();
$result = $this->database->update( $this->get_table_name(), $data, $where, $format, $where_format );
$this->post_execution();
return $result;
}
/**
* Upserts data in the database.
*
* Performs an insert into and if key is duplicate it will update the existing record.
*
* @param array $data Data to update on the table.
* @param array|null $where Unused. Where condition as key => value array.
* @param array|string|null $format Optional. Data prepare format.
* @param array|string|null $where_format Optional. Where prepare format.
*
* @return false|int False when the upsert request is invalid, int on number of rows changed.
*/
public function upsert( array $data, array $where = null, $format = null, $where_format = null ) {
if ( $where_format !== null ) {
_deprecated_argument( __METHOD__, '7.7.0', 'The where_format argument is deprecated' );
}
$this->pre_execution();
$update = [];
$keys = [];
$columns = array_keys( $data );
foreach ( $columns as $column ) {
$keys[] = '`' . $column . '`';
$update[] = sprintf( '`%1$s` = VALUES(`%1$s`)', $column );
}
$query = sprintf(
'INSERT INTO `%1$s` (%2$s) VALUES ( %3$s ) ON DUPLICATE KEY UPDATE %4$s',
$this->get_table_name(),
implode( ', ', $keys ),
implode( ', ', array_fill( 0, count( $data ), '%s' ) ),
implode( ', ', $update )
);
$result = $this->database->query(
$this->database->prepare(
$query,
array_values( $data )
)
);
$this->post_execution();
return $result;
}
/**
* Deletes a record from the database.
*
* @param array $where Where clauses for the query.
* @param array|string|null $format Formats for the data.
*
* @return false|int
*/
public function delete( array $where, $format = null ) {
$this->pre_execution();
$result = $this->database->delete( $this->get_table_name(), $where, $format );
$this->post_execution();
return $result;
}
/**
* Executes the given query and returns the results.
*
* @param string $query The query to execute.
*
* @return array|object|null The resultset
*/
public function get_results( $query ) {
$this->pre_execution();
$results = $this->database->get_results( $query );
$this->post_execution();
return $results;
}
/**
* Creates a table to the database.
*
* @param array $columns The columns to create.
* @param array $indexes The indexes to use.
*
* @return bool True when creation is successful.
*/
public function create_table( array $columns, array $indexes = [] ) {
$create_table = sprintf(
'CREATE TABLE IF NOT EXISTS %1$s ( %2$s ) %3$s',
$this->get_table_name(),
implode( ',', array_merge( $columns, $indexes ) ),
$this->database->get_charset_collate()
);
$this->pre_execution();
$is_created = (bool) $this->database->query( $create_table );
$this->post_execution();
return $is_created;
}
/**
* Checks if there is an error.
*
* @return bool Returns true when there is an error.
*/
public function has_error() {
return ( $this->database->last_error !== '' );
}
/**
* Executed before a query will be ran.
*/
protected function pre_execution() {
if ( $this->suppress_errors ) {
$this->last_suppressed_state = $this->database->suppress_errors();
}
}
/**
* Executed after a query has been ran.
*/
protected function post_execution() {
if ( $this->suppress_errors ) {
$this->database->suppress_errors( $this->last_suppressed_state );
}
}
/**
* Returns the full table name.
*
* @return string Full table name including prefix.
*/
public function get_table_name() {
return $this->get_table_prefix() . $this->table_name;
}
/**
* Returns the prefix to use for the table.
*
* @return string The table prefix depending on the database context.
*/
protected function get_table_prefix() {
if ( $this->is_multisite_table ) {
return $this->database->base_prefix;
}
return $this->database->get_blog_prefix();
}
/**
* Registers the table with WordPress.
*
* @return void
*/
protected function register_table() {
$table_name = $this->table_name;
$full_table_name = $this->get_table_name();
$this->database->$table_name = $full_table_name;
if ( $this->is_multisite_table ) {
$this->database->ms_global_tables[] = $table_name;
return;
}
$this->database->tables[] = $table_name;
}
/**
* Checks if the table has been registered with WordPress.
*
* @return bool True if the table is registered, false otherwise.
*/
protected function is_table_registered() {
if ( $this->is_multisite_table ) {
return in_array( $this->table_name, $this->database->ms_global_tables, true );
}
return in_array( $this->table_name, $this->database->tables, true );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Export
*/
/**
* Class WPSEO_Export.
*
* Class with functionality to export the WP SEO settings.
*/
class WPSEO_Export {
/**
* Holds the nonce action.
*
* @var string
*/
const NONCE_ACTION = 'wpseo_export';
/**
* Holds the export data.
*
* @var string
*/
private $export = '';
/**
* Holds whether the export was a success.
*
* @var bool
*/
public $success;
/**
* Handles the export request.
*/
public function export() {
check_admin_referer( self::NONCE_ACTION );
$this->export_settings();
$this->output();
}
/**
* Outputs the export.
*/
public function output() {
if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
esc_html_e( 'You do not have the required rights to export settings.', 'wordpress-seo' );
return;
}
echo '<p id="wpseo-settings-export-desc">';
printf(
/* translators: %1$s expands to Import settings */
esc_html__(
'Copy all these settings to another site\'s %1$s tab and click "%1$s" there.',
'wordpress-seo'
),
esc_html__(
'Import settings',
'wordpress-seo'
)
);
echo '</p>';
/* translators: %1$s expands to Yoast SEO */
echo '<label for="wpseo-settings-export" class="yoast-inline-label">' . sprintf( __( 'Your %1$s settings:', 'wordpress-seo' ), 'Yoast SEO' ) . '</label><br />';
echo '<textarea id="wpseo-settings-export" rows="20" cols="100" aria-describedby="wpseo-settings-export-desc">' . esc_textarea( $this->export ) . '</textarea>';
}
/**
* Exports the current site's WP SEO settings.
*/
private function export_settings() {
$this->export_header();
foreach ( WPSEO_Options::get_option_names() as $opt_group ) {
$this->write_opt_group( $opt_group );
}
}
/**
* Writes the header of the export.
*/
private function export_header() {
$header = sprintf(
/* translators: %1$s expands to Yoast SEO, %2$s expands to Yoast.com */
esc_html__( 'These are settings for the %1$s plugin by %2$s', 'wordpress-seo' ),
'Yoast SEO',
'Yoast.com'
);
$this->write_line( '; ' . $header );
}
/**
* Writes a line to the export.
*
* @param string $line Line string.
* @param bool $newline_first Boolean flag whether to prepend with new line.
*/
private function write_line( $line, $newline_first = false ) {
if ( $newline_first ) {
$this->export .= PHP_EOL;
}
$this->export .= $line . PHP_EOL;
}
/**
* Writes an entire option group to the export.
*
* @param string $opt_group Option group name.
*/
private function write_opt_group( $opt_group ) {
$this->write_line( '[' . $opt_group . ']', true );
$options = get_option( $opt_group );
if ( ! is_array( $options ) ) {
return;
}
foreach ( $options as $key => $elem ) {
if ( is_array( $elem ) ) {
$count = count( $elem );
for ( $i = 0; $i < $count; $i++ ) {
$elem_check = isset( $elem[ $i ] ) ? $elem[ $i ] : null;
$this->write_setting( $key . '[]', $elem_check );
}
}
else {
$this->write_setting( $key, $elem );
}
}
}
/**
* Writes a settings line to the export.
*
* @param string $key Key string.
* @param string $val Value string.
*/
private function write_setting( $key, $val ) {
if ( is_string( $val ) ) {
$val = '"' . $val . '"';
}
$this->write_line( $key . ' = ' . $val );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Exposes shortlinks in a global, so that we can pass them to our Javascript components.
*/
class WPSEO_Expose_Shortlinks implements WPSEO_WordPress_Integration {
/**
* Array containing the keys and shortlinks.
*
* @var array
*/
private $shortlinks = [
'shortlinks.advanced.allow_search_engines' => 'https://yoa.st/allow-search-engines',
'shortlinks.advanced.follow_links' => 'https://yoa.st/follow-links',
'shortlinks.advanced.meta_robots' => 'https://yoa.st/meta-robots-advanced',
'shortlinks.advanced.breadcrumbs_title' => 'https://yoa.st/breadcrumbs-title',
'shortlinks.metabox.schema.explanation' => 'https://yoa.st/400',
'shortlinks.metabox.schema.page_type' => 'https://yoa.st/402',
'shortlinks.sidebar.schema.explanation' => 'https://yoa.st/401',
'shortlinks.sidebar.schema.page_type' => 'https://yoa.st/403',
'shortlinks.focus_keyword_info' => 'https://yoa.st/focus-keyword',
'shortlinks.nofollow_sponsored' => 'https://yoa.st/nofollow-sponsored',
'shortlinks.snippet_preview_info' => 'https://yoa.st/snippet-preview',
'shortlinks.cornerstone_content_info' => 'https://yoa.st/1i9',
'shortlinks.upsell.social_preview.facebook' => 'https://yoa.st/social-preview-facebook',
'shortlinks.upsell.social_preview.twitter' => 'https://yoa.st/social-preview-twitter',
'shortlinks.upsell.sidebar.news' => 'https://yoa.st/get-news-sidebar',
'shortlinks.upsell.sidebar.focus_keyword_synonyms_link' => 'https://yoa.st/textlink-synonyms-popup-sidebar',
'shortlinks.upsell.sidebar.focus_keyword_synonyms_button' => 'https://yoa.st/keyword-synonyms-popup-sidebar',
'shortlinks.upsell.sidebar.premium_seo_analysis_button' => 'https://yoa.st/premium-seo-analysis-sidebar',
'shortlinks.upsell.sidebar.focus_keyword_additional_link' => 'https://yoa.st/textlink-keywords-popup-sidebar',
'shortlinks.upsell.sidebar.focus_keyword_additional_button' => 'https://yoa.st/add-keywords-popup-sidebar',
'shortlinks.upsell.sidebar.additional_link' => 'https://yoa.st/textlink-keywords-sidebar',
'shortlinks.upsell.sidebar.additional_button' => 'https://yoa.st/add-keywords-sidebar',
'shortlinks.upsell.sidebar.keyphrase_distribution' => 'https://yoa.st/keyphrase-distribution-sidebar',
'shortlinks.upsell.sidebar.word_complexity' => 'https://yoa.st/word-complexity-sidebar',
'shortlinks.upsell.metabox.news' => 'https://yoa.st/get-news-metabox',
'shortlinks.upsell.metabox.go_premium' => 'https://yoa.st/pe-premium-page',
'shortlinks.upsell.metabox.focus_keyword_synonyms_link' => 'https://yoa.st/textlink-synonyms-popup-metabox',
'shortlinks.upsell.metabox.focus_keyword_synonyms_button' => 'https://yoa.st/keyword-synonyms-popup',
'shortlinks.upsell.metabox.premium_seo_analysis_button' => 'https://yoa.st/premium-seo-analysis-metabox',
'shortlinks.upsell.metabox.focus_keyword_additional_link' => 'https://yoa.st/textlink-keywords-popup-metabox',
'shortlinks.upsell.metabox.focus_keyword_additional_button' => 'https://yoa.st/add-keywords-popup',
'shortlinks.upsell.metabox.additional_link' => 'https://yoa.st/textlink-keywords-metabox',
'shortlinks.upsell.metabox.additional_button' => 'https://yoa.st/add-keywords-metabox',
'shortlinks.upsell.metabox.keyphrase_distribution' => 'https://yoa.st/keyphrase-distribution-metabox',
'shortlinks.upsell.metabox.word_complexity' => 'https://yoa.st/word-complexity-metabox',
'shortlinks.upsell.gsc.create_redirect_button' => 'https://yoa.st/redirects',
'shortlinks.readability_analysis_info' => 'https://yoa.st/readability-analysis',
'shortlinks.inclusive_language_analysis_info' => 'https://yoa.st/inclusive-language-analysis',
'shortlinks.activate_premium_info' => 'https://yoa.st/activate-subscription',
'shortlinks.upsell.sidebar.morphology_upsell_metabox' => 'https://yoa.st/morphology-upsell-metabox',
'shortlinks.upsell.sidebar.morphology_upsell_sidebar' => 'https://yoa.st/morphology-upsell-sidebar',
'shortlinks.semrush.volume_help' => 'https://yoa.st/3-v',
'shortlinks.semrush.trend_help' => 'https://yoa.st/3-v',
'shortlinks.semrush.prices' => 'https://yoa.st/semrush-prices',
'shortlinks.semrush.premium_landing_page' => 'https://yoa.st/413',
'shortlinks.wincher.seo_performance' => 'https://yoa.st/wincher-integration',
'shortlinks-insights-estimated_reading_time' => 'https://yoa.st/4fd',
'shortlinks-insights-flesch_reading_ease' => 'https://yoa.st/34r',
'shortlinks-insights-flesch_reading_ease_sidebar' => 'https://yoa.st/4mf',
'shortlinks-insights-flesch_reading_ease_metabox' => 'https://yoa.st/4mg',
'shortlinks-insights-flesch_reading_ease_article' => 'https://yoa.st/34s',
'shortlinks-insights-keyword_research_link' => 'https://yoa.st/keyword-research-metabox',
'shortlinks-insights-upsell-sidebar-prominent_words' => 'https://yoa.st/prominent-words-upsell-sidebar',
'shortlinks-insights-upsell-metabox-prominent_words' => 'https://yoa.st/prominent-words-upsell-metabox',
'shortlinks-insights-upsell-elementor-prominent_words' => 'https://yoa.st/prominent-words-upsell-elementor',
'shortlinks-insights-word_count' => 'https://yoa.st/word-count',
'shortlinks-insights-upsell-sidebar-text_formality' => 'https://yoa.st/formality-upsell-sidebar',
'shortlinks-insights-upsell-metabox-text_formality' => 'https://yoa.st/formality-upsell-metabox',
'shortlinks-insights-upsell-elementor-text_formality' => 'https://yoa.st/formality-upsell-elementor',
'shortlinks-insights-text_formality_info_free' => 'https://yoa.st/formality-free',
'shortlinks-insights-text_formality_info_premium' => 'https://yoa.st/formality',
];
/**
* Registers all hooks to WordPress.
*
* @return void
*/
public function register_hooks() {
add_filter( 'wpseo_admin_l10n', [ $this, 'expose_shortlinks' ] );
}
/**
* Adds shortlinks to the passed array.
*
* @param array $input The array to add shortlinks to.
*
* @return array The passed array with the additional shortlinks.
*/
public function expose_shortlinks( $input ) {
foreach ( $this->get_shortlinks() as $key => $shortlink ) {
$input[ $key ] = WPSEO_Shortlinker::get( $shortlink );
}
$input['default_query_params'] = WPSEO_Shortlinker::get_query_params();
return $input;
}
/**
* Retrieves the shortlinks.
*
* @return array The shortlinks.
*/
private function get_shortlinks() {
if ( ! $this->is_term_edit() ) {
return $this->shortlinks;
}
$shortlinks = $this->shortlinks;
$shortlinks['shortlinks.upsell.metabox.focus_keyword_synonyms_link'] = 'https://yoa.st/textlink-synonyms-popup-metabox-term';
$shortlinks['shortlinks.upsell.metabox.focus_keyword_synonyms_button'] = 'https://yoa.st/keyword-synonyms-popup-term';
$shortlinks['shortlinks.upsell.metabox.focus_keyword_additional_link'] = 'https://yoa.st/textlink-keywords-popup-metabox-term';
$shortlinks['shortlinks.upsell.metabox.focus_keyword_additional_button'] = 'https://yoa.st/add-keywords-popup-term';
$shortlinks['shortlinks.upsell.metabox.additional_link'] = 'https://yoa.st/textlink-keywords-metabox-term';
$shortlinks['shortlinks.upsell.metabox.additional_button'] = 'https://yoa.st/add-keywords-metabox-term';
$shortlinks['shortlinks.upsell.sidebar.morphology_upsell_metabox'] = 'https://yoa.st/morphology-upsell-metabox-term';
$shortlinks['shortlinks.upsell.metabox.keyphrase_distribution'] = 'https://yoa.st/keyphrase-distribution-metabox-term';
$shortlinks['shortlinks.upsell.metabox.word_complexity'] = 'https://yoa.st/word-complexity-metabox-term';
return $shortlinks;
}
/**
* Checks if the current page is a term edit page.
*
* @return bool True when page is term edit.
*/
private function is_term_edit() {
global $pagenow;
return WPSEO_Taxonomy::is_term_edit( $pagenow );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Gutenberg_Compatibility
*/
/**
* Class WPSEO_Gutenberg_Compatibility
*/
class WPSEO_Gutenberg_Compatibility {
/**
* The currently released version of Gutenberg.
*
* @var string
*/
const CURRENT_RELEASE = '16.3.0';
/**
* The minimally supported version of Gutenberg by the plugin.
*
* @var string
*/
const MINIMUM_SUPPORTED = '16.3.0';
/**
* Holds the current version.
*
* @var string
*/
protected $current_version = '';
/**
* WPSEO_Gutenberg_Compatibility constructor.
*/
public function __construct() {
$this->current_version = $this->detect_installed_gutenberg_version();
}
/**
* Determines whether or not Gutenberg is installed.
*
* @return bool Whether or not Gutenberg is installed.
*/
public function is_installed() {
return $this->current_version !== '';
}
/**
* Determines whether or not the currently installed version of Gutenberg is below the minimum supported version.
*
* @return bool True if the currently installed version is below the minimum supported version. False otherwise.
*/
public function is_below_minimum() {
return version_compare( $this->current_version, $this->get_minimum_supported_version(), '<' );
}
/**
* Gets the currently installed version.
*
* @return string The currently installed version.
*/
public function get_installed_version() {
return $this->current_version;
}
/**
* Determines whether or not the currently installed version of Gutenberg is the latest, fully compatible version.
*
* @return bool Whether or not the currently installed version is fully compatible.
*/
public function is_fully_compatible() {
return version_compare( $this->current_version, $this->get_latest_release(), '>=' );
}
/**
* Gets the latest released version of Gutenberg.
*
* @return string The latest release.
*/
protected function get_latest_release() {
return self::CURRENT_RELEASE;
}
/**
* Gets the minimum supported version of Gutenberg.
*
* @return string The minumum supported release.
*/
protected function get_minimum_supported_version() {
return self::MINIMUM_SUPPORTED;
}
/**
* Detects the currently installed Gutenberg version.
*
* @return string The currently installed Gutenberg version. Empty if the version couldn't be detected.
*/
protected function detect_installed_gutenberg_version() {
if ( defined( 'GUTENBERG_VERSION' ) ) {
return GUTENBERG_VERSION;
}
return '';
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Loads the MyYoast proxy.
*
* This class registers a proxy page on `admin.php`. Which is reached with the `page=PAGE_IDENTIFIER` parameter.
* It will read external files and serves them like they are located locally.
*/
class WPSEO_MyYoast_Proxy implements WPSEO_WordPress_Integration {
/**
* The page identifier used in WordPress to register the MyYoast proxy page.
*
* @var string
*/
const PAGE_IDENTIFIER = 'wpseo_myyoast_proxy';
/**
* The cache control's max age. Used in the header of a successful proxy response.
*
* @var int
*/
const CACHE_CONTROL_MAX_AGE = DAY_IN_SECONDS;
/**
* Registers the hooks when the user is on the right page.
*
* @codeCoverageIgnore
*
* @return void
*/
public function register_hooks() {
if ( ! $this->is_proxy_page() ) {
return;
}
// Register the page for the proxy.
add_action( 'admin_menu', [ $this, 'add_proxy_page' ] );
add_action( 'admin_init', [ $this, 'handle_proxy_page' ] );
}
/**
* Registers the proxy page. It does not actually add a link to the dashboard.
*
* @codeCoverageIgnore
*
* @return void
*/
public function add_proxy_page() {
add_dashboard_page( '', '', 'read', self::PAGE_IDENTIFIER, '' );
}
/**
* Renders the requested proxy page and exits to prevent the WordPress UI from loading.
*
* @codeCoverageIgnore
*
* @return void
*/
public function handle_proxy_page() {
$this->render_proxy_page();
// Prevent the WordPress UI from loading.
exit;
}
/**
* Renders the requested proxy page.
*
* This is separated from the exits to be able to test it.
*
* @return void
*/
public function render_proxy_page() {
$proxy_options = $this->determine_proxy_options();
if ( $proxy_options === [] ) {
// Do not accept any other file than implemented.
$this->set_header( 'HTTP/1.0 501 Requested file not implemented' );
return;
}
// Set the headers before serving the remote file.
$this->set_header( 'Content-Type: ' . $proxy_options['content_type'] );
$this->set_header( 'Cache-Control: max-age=' . self::CACHE_CONTROL_MAX_AGE );
try {
echo $this->get_remote_url_body( $proxy_options['url'] );
}
catch ( Exception $e ) {
/*
* Reset the file headers because the loading failed.
*
* Note: Due to supporting PHP 5.2 `header_remove` can not be used here.
* Overwrite the headers instead.
*/
$this->set_header( 'Content-Type: text/plain' );
$this->set_header( 'Cache-Control: max-age=0' );
$this->set_header( 'HTTP/1.0 500 ' . $e->getMessage() );
}
}
/**
* Tries to load the given url via `wp_remote_get`.
*
* @codeCoverageIgnore
*
* @param string $url The url to load.
*
* @return string The body of the response.
*
* @throws Exception When `wp_remote_get` returned an error.
* @throws Exception When the response code is not 200.
*/
protected function get_remote_url_body( $url ) {
$response = wp_remote_get( $url );
if ( $response instanceof WP_Error ) {
throw new Exception( 'Unable to retrieve file from MyYoast' );
}
if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
throw new Exception( 'Received unexpected response from MyYoast' );
}
return wp_remote_retrieve_body( $response );
}
/**
* Determines the proxy options based on the file and plugin version arguments.
*
* When the file is known it returns an array like this:
* <code>
* $array = array(
* 'content_type' => 'the content type'
* 'url' => 'the url, possibly with the plugin version'
* )
* </code>
*
* @return array Empty for an unknown file. See format above for known files.
*/
protected function determine_proxy_options() {
if ( $this->get_proxy_file() === 'research-webworker' ) {
return [
'content_type' => 'text/javascript; charset=UTF-8',
'url' => 'https://my.yoast.com/api/downloads/file/analysis-worker?plugin_version=' . $this->get_plugin_version(),
];
}
return [];
}
/**
* Checks if the current page is the MyYoast proxy page.
*
* @codeCoverageIgnore
*
* @return bool True when the page request parameter equals the proxy page.
*/
protected function is_proxy_page() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
return $page === self::PAGE_IDENTIFIER;
}
/**
* Returns the proxy file from the HTTP request parameters.
*
* @codeCoverageIgnore
*
* @return string The sanitized file request parameter or an empty string if it does not exist.
*/
protected function get_proxy_file() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( isset( $_GET['file'] ) && is_string( $_GET['file'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
return sanitize_text_field( wp_unslash( $_GET['file'] ) );
}
return '';
}
/**
* Returns the plugin version from the HTTP request parameters.
*
* @codeCoverageIgnore
*
* @return string The sanitized plugin_version request parameter or an empty string if it does not exist.
*/
protected function get_plugin_version() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( isset( $_GET['plugin_version'] ) && is_string( $_GET['plugin_version'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$plugin_version = sanitize_text_field( wp_unslash( $_GET['plugin_version'] ) );
// Replace slashes to secure against requiring a file from another path.
return str_replace( [ '/', '\\' ], '_', $plugin_version );
}
return '';
}
/**
* Sets the HTTP header.
*
* This is a tiny helper function to enable better testing.
*
* @codeCoverageIgnore
*
* @param string $header The header to set.
*
* @return void
*/
protected function set_header( $header ) {
header( $header );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Options\Tabs
*/
/**
* Class WPSEO_Option_Tab.
*/
class WPSEO_Option_Tab {
/**
* Name of the tab.
*
* @var string
*/
private $name;
/**
* Label of the tab.
*
* @var string
*/
private $label;
/**
* Optional arguments.
*
* @var array
*/
private $arguments;
/**
* WPSEO_Option_Tab constructor.
*
* @param string $name Name of the tab.
* @param string $label Localized label of the tab.
* @param array $arguments Optional arguments.
*/
public function __construct( $name, $label, array $arguments = [] ) {
$this->name = sanitize_title( $name );
$this->label = $label;
$this->arguments = $arguments;
}
/**
* Gets the name.
*
* @return string The name.
*/
public function get_name() {
return $this->name;
}
/**
* Gets the label.
*
* @return string The label.
*/
public function get_label() {
return $this->label;
}
/**
* Retrieves whether the tab needs a save button.
*
* @return bool True whether the tabs needs a save button.
*/
public function has_save_button() {
return (bool) $this->get_argument( 'save_button', true );
}
/**
* Retrieves whether the tab hosts beta functionalities.
*
* @return bool True whether the tab hosts beta functionalities.
*/
public function is_beta() {
return (bool) $this->get_argument( 'beta', false );
}
/**
* Retrieves whether the tab hosts premium functionalities.
*
* @return bool True whether the tab hosts premium functionalities.
*/
public function is_premium() {
return (bool) $this->get_argument( 'premium', false );
}
/**
* Gets the option group.
*
* @return string The option group.
*/
public function get_opt_group() {
return $this->get_argument( 'opt_group' );
}
/**
* Retrieves the variable from the supplied arguments.
*
* @param string $variable Variable to retrieve.
* @param string|mixed $default_value Default to use when variable not found.
*
* @return mixed|string The retrieved variable.
*/
protected function get_argument( $variable, $default_value = '' ) {
return array_key_exists( $variable, $this->arguments ) ? $this->arguments[ $variable ] : $default_value;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Options\Tabs
*/
use Yoast\WP\SEO\Presenters\Admin\Beta_Badge_Presenter;
use Yoast\WP\SEO\Presenters\Admin\Premium_Badge_Presenter;
/**
* Class WPSEO_Option_Tabs_Formatter.
*/
class WPSEO_Option_Tabs_Formatter {
/**
* Retrieves the path to the view of the tab.
*
* @param WPSEO_Option_Tabs $option_tabs Option Tabs to get base from.
* @param WPSEO_Option_Tab $tab Tab to get name from.
*
* @return string
*/
public function get_tab_view( WPSEO_Option_Tabs $option_tabs, WPSEO_Option_Tab $tab ) {
return WPSEO_PATH . 'admin/views/tabs/' . $option_tabs->get_base() . '/' . $tab->get_name() . '.php';
}
/**
* Outputs the option tabs.
*
* @param WPSEO_Option_Tabs $option_tabs Option Tabs to get tabs from.
*/
public function run( WPSEO_Option_Tabs $option_tabs ) {
echo '<h2 class="nav-tab-wrapper" id="wpseo-tabs">';
foreach ( $option_tabs->get_tabs() as $tab ) {
$label = esc_html( $tab->get_label() );
if ( $tab->is_beta() ) {
$label = '<span style="margin-right:4px;">' . $label . '</span>' . new Beta_Badge_Presenter( $tab->get_name() );
}
elseif ( $tab->is_premium() ) {
$label = '<span style="margin-right:4px;">' . $label . '</span>' . new Premium_Badge_Presenter( $tab->get_name() );
}
printf(
'<a class="nav-tab" id="%1$s" href="%2$s">%3$s</a>',
esc_attr( $tab->get_name() . '-tab' ),
esc_url( '#top#' . $tab->get_name() ),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: we do this on purpose
$label
);
}
echo '</h2>';
foreach ( $option_tabs->get_tabs() as $tab ) {
$identifier = $tab->get_name();
$class = 'wpseotab ' . ( $tab->has_save_button() ? 'save' : 'nosave' );
printf( '<div id="%1$s" class="%2$s">', esc_attr( $identifier ), esc_attr( $class ) );
$tab_filter_name = sprintf( '%s_%s', $option_tabs->get_base(), $tab->get_name() );
/**
* Allows to override the content that is display on the specific option tab.
*
* @internal For internal Yoast SEO use only.
*
* @api string|null The content that should be displayed for this tab. Leave empty for default behaviour.
*
* @param WPSEO_Option_Tabs $option_tabs The registered option tabs.
* @param WPSEO_Option_Tab $tab The tab that is being displayed.
*/
$option_tab_content = apply_filters( 'wpseo_option_tab-' . $tab_filter_name, null, $option_tabs, $tab );
if ( ! empty( $option_tab_content ) ) {
echo wp_kses_post( $option_tab_content );
}
if ( empty( $option_tab_content ) ) {
// Output the settings view for all tabs.
$tab_view = $this->get_tab_view( $option_tabs, $tab );
if ( is_file( $tab_view ) ) {
$yform = Yoast_Form::get_instance();
require $tab_view;
}
}
echo '</div>';
}
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin\Options\Tabs
*/
/**
* Class WPSEO_Option_Tabs.
*/
class WPSEO_Option_Tabs {
/**
* Tabs base.
*
* @var string
*/
private $base;
/**
* The tabs in this group.
*
* @var array
*/
private $tabs = [];
/**
* Name of the active tab.
*
* @var string
*/
private $active_tab = '';
/**
* WPSEO_Option_Tabs constructor.
*
* @codeCoverageIgnore
*
* @param string $base Base of the tabs.
* @param string $active_tab Currently active tab.
*/
public function __construct( $base, $active_tab = '' ) {
$this->base = sanitize_title( $base );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$tab = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : '';
$this->active_tab = empty( $tab ) ? $active_tab : $tab;
}
/**
* Get the base.
*
* @return string
*/
public function get_base() {
return $this->base;
}
/**
* Add a tab.
*
* @param WPSEO_Option_Tab $tab Tab to add.
*
* @return $this
*/
public function add_tab( WPSEO_Option_Tab $tab ) {
$this->tabs[] = $tab;
return $this;
}
/**
* Get active tab.
*
* @return WPSEO_Option_Tab|null Get the active tab.
*/
public function get_active_tab() {
if ( empty( $this->active_tab ) ) {
return null;
}
$active_tabs = array_filter( $this->tabs, [ $this, 'is_active_tab' ] );
if ( ! empty( $active_tabs ) ) {
$active_tabs = array_values( $active_tabs );
if ( count( $active_tabs ) === 1 ) {
return $active_tabs[0];
}
}
return null;
}
/**
* Is the tab the active tab.
*
* @param WPSEO_Option_Tab $tab Tab to check for active tab.
*
* @return bool
*/
public function is_active_tab( WPSEO_Option_Tab $tab ) {
return ( $tab->get_name() === $this->active_tab );
}
/**
* Get all tabs.
*
* @return WPSEO_Option_Tab[]
*/
public function get_tabs() {
return $this->tabs;
}
/**
* Display the tabs.
*
* @param Yoast_Form $yform Yoast Form needed in the views.
*/
public function display( Yoast_Form $yform ) {
$formatter = new WPSEO_Option_Tabs_Formatter();
$formatter->run( $this, $yform );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Class WPSEO_presenter_paper.
*/
class WPSEO_Paper_Presenter {
/**
* Title of the paper.
*
* @var string
*/
private $title;
/**
* The view variables.
*
* @var array
*/
private $settings;
/**
* The path to the view file.
*
* @var string
*/
private $view_file;
/**
* WPSEO_presenter_paper constructor.
*
* @param string $title The title of the paper.
* @param string|null $view_file Optional. The path to the view file. Use the content setting
* if do not wish to use a view file.
* @param array $settings Optional. Settings for the paper.
*/
public function __construct( $title, $view_file = null, array $settings = [] ) {
$defaults = [
'paper_id' => null,
'paper_id_prefix' => 'wpseo-',
'collapsible' => false,
'collapsible_header_class' => '',
'expanded' => false,
'help_text' => '',
'title_after' => '',
'class' => '',
'content' => '',
'view_data' => [],
];
$this->settings = wp_parse_args( $settings, $defaults );
$this->title = $title;
$this->view_file = $view_file;
}
/**
* Renders the collapsible paper and returns it as a string.
*
* @return string The rendered paper.
*/
public function get_output() {
$view_variables = $this->get_view_variables();
extract( $view_variables, EXTR_SKIP );
$content = $this->settings['content'];
if ( $this->view_file !== null ) {
ob_start();
require $this->view_file;
$content = ob_get_clean();
}
ob_start();
require WPSEO_PATH . 'admin/views/paper-collapsible.php';
$rendered_output = ob_get_clean();
return $rendered_output;
}
/**
* Retrieves the view variables.
*
* @return array The view variables.
*/
private function get_view_variables() {
if ( $this->settings['help_text'] instanceof WPSEO_Admin_Help_Panel === false ) {
$this->settings['help_text'] = new WPSEO_Admin_Help_Panel( '', '', '' );
}
$view_variables = [
'class' => $this->settings['class'],
'collapsible' => $this->settings['collapsible'],
'collapsible_config' => $this->collapsible_config(),
'collapsible_header_class' => $this->settings['collapsible_header_class'],
'title_after' => $this->settings['title_after'],
'help_text' => $this->settings['help_text'],
'view_file' => $this->view_file,
'title' => $this->title,
'paper_id' => $this->settings['paper_id'],
'paper_id_prefix' => $this->settings['paper_id_prefix'],
'yform' => Yoast_Form::get_instance(),
];
return array_merge( $this->settings['view_data'], $view_variables );
}
/**
* Retrieves the collapsible config based on the settings.
*
* @return array The config.
*/
protected function collapsible_config() {
if ( empty( $this->settings['collapsible'] ) ) {
return [
'toggle_icon' => '',
'class' => '',
'expanded' => '',
];
}
if ( ! empty( $this->settings['expanded'] ) ) {
return [
'toggle_icon' => 'dashicons-arrow-up-alt2',
'class' => 'toggleable-container',
'expanded' => 'true',
];
}
return [
'toggle_icon' => 'dashicons-arrow-down-alt2',
'class' => 'toggleable-container toggleable-container-hidden',
'expanded' => 'false',
];
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Plugin_Availability
*/
/**
* Class WPSEO_Plugin_Availability
*/
class WPSEO_Plugin_Availability {
/**
* Holds the plugins.
*
* @var array
*/
protected $plugins = [];
/**
* Registers the plugins so we can access them.
*/
public function register() {
$this->register_yoast_plugins();
$this->register_yoast_plugins_status();
}
/**
* Registers all the available Yoast SEO plugins.
*/
protected function register_yoast_plugins() {
$this->plugins = [
'yoast-seo-premium' => [
'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y7' ),
'title' => 'Yoast SEO Premium',
'description' => sprintf(
/* translators: %1$s expands to Yoast SEO */
__( 'The premium version of %1$s with more features & support.', 'wordpress-seo' ),
'Yoast SEO'
),
'installed' => false,
'slug' => 'wordpress-seo-premium/wp-seo-premium.php',
'version_sync' => true,
'premium' => true,
],
'video-seo-for-wordpress-seo-by-yoast' => [
'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y8' ),
'title' => 'Video SEO',
'description' => __( 'Optimize your videos to show them off in search results and get more clicks!', 'wordpress-seo' ),
'installed' => false,
'slug' => 'wpseo-video/video-seo.php',
'version_sync' => true,
'premium' => true,
],
'yoast-news-seo' => [
'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y9' ),
'title' => 'News SEO',
'description' => __( 'Are you in Google News? Increase your traffic from Google News by optimizing for it!', 'wordpress-seo' ),
'installed' => false,
'slug' => 'wpseo-news/wpseo-news.php',
'version_sync' => true,
'premium' => true,
],
'local-seo-for-yoast-seo' => [
'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1ya' ),
'title' => 'Local SEO',
'description' => __( 'Rank better locally and in Google Maps, without breaking a sweat!', 'wordpress-seo' ),
'installed' => false,
'slug' => 'wordpress-seo-local/local-seo.php',
'version_sync' => true,
'premium' => true,
],
'yoast-woocommerce-seo' => [
'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1o0' ),
'title' => 'Yoast WooCommerce SEO',
'description' => sprintf(
/* translators: %1$s expands to Yoast SEO */
__( 'Seamlessly integrate WooCommerce with %1$s and get extra features!', 'wordpress-seo' ),
'Yoast SEO'
),
'_dependencies' => [
'WooCommerce' => [
'slug' => 'woocommerce/woocommerce.php',
],
],
'installed' => false,
'slug' => 'wpseo-woocommerce/wpseo-woocommerce.php',
'version_sync' => true,
'premium' => true,
],
];
}
/**
* Sets certain plugin properties based on WordPress' status.
*/
protected function register_yoast_plugins_status() {
foreach ( $this->plugins as $name => $plugin ) {
$plugin_slug = $plugin['slug'];
$plugin_path = WP_PLUGIN_DIR . '/' . $plugin_slug;
if ( file_exists( $plugin_path ) ) {
$plugin_data = get_plugin_data( $plugin_path, false, false );
$this->plugins[ $name ]['installed'] = true;
$this->plugins[ $name ]['version'] = $plugin_data['Version'];
$this->plugins[ $name ]['active'] = is_plugin_active( $plugin_slug );
}
}
}
/**
* Checks whether or not a plugin is known within the Yoast SEO collection.
*
* @param string $plugin The plugin to search for.
*
* @return bool Whether or not the plugin is exists.
*/
protected function plugin_exists( $plugin ) {
return isset( $this->plugins[ $plugin ] );
}
/**
* Gets all the possibly available plugins.
*
* @return array Array containing the information about the plugins.
*/
public function get_plugins() {
return $this->plugins;
}
/**
* Gets a specific plugin. Returns an empty array if it cannot be found.
*
* @param string $plugin The plugin to search for.
*
* @return array The plugin properties.
*/
public function get_plugin( $plugin ) {
if ( ! $this->plugin_exists( $plugin ) ) {
return [];
}
return $this->plugins[ $plugin ];
}
/**
* Gets the version of the plugin.
*
* @param array $plugin The information available about the plugin.
*
* @return string The version associated with the plugin.
*/
public function get_version( $plugin ) {
if ( ! isset( $plugin['version'] ) ) {
return '';
}
return $plugin['version'];
}
/**
* Checks if there are dependencies available for the plugin.
*
* @param array $plugin The information available about the plugin.
*
* @return bool Whether or not there is a dependency present.
*/
public function has_dependencies( $plugin ) {
return ( isset( $plugin['_dependencies'] ) && ! empty( $plugin['_dependencies'] ) );
}
/**
* Gets the dependencies for the plugin.
*
* @param array $plugin The information available about the plugin.
*
* @return array Array containing all the dependencies associated with the plugin.
*/
public function get_dependencies( $plugin ) {
if ( ! $this->has_dependencies( $plugin ) ) {
return [];
}
return $plugin['_dependencies'];
}
/**
* Checks if all dependencies are satisfied.
*
* @param array $plugin The information available about the plugin.
*
* @return bool Whether or not the dependencies are satisfied.
*/
public function dependencies_are_satisfied( $plugin ) {
if ( ! $this->has_dependencies( $plugin ) ) {
return true;
}
$dependencies = $this->get_dependencies( $plugin );
$installed_dependencies = array_filter( $dependencies, [ $this, 'is_dependency_available' ] );
return count( $installed_dependencies ) === count( $dependencies );
}
/**
* Checks whether or not one of the plugins is properly installed and usable.
*
* @param array $plugin The information available about the plugin.
*
* @return bool Whether or not the plugin is properly installed.
*/
public function is_installed( $plugin ) {
if ( empty( $plugin ) ) {
return false;
}
return $this->is_available( $plugin );
}
/**
* Gets all installed plugins.
*
* @return array The installed plugins.
*/
public function get_installed_plugins() {
$installed = [];
foreach ( $this->plugins as $plugin_key => $plugin ) {
if ( $this->is_installed( $plugin ) ) {
$installed[ $plugin_key ] = $plugin;
}
}
return $installed;
}
/**
* Checks for the availability of the plugin.
*
* @param array $plugin The information available about the plugin.
*
* @return bool Whether or not the plugin is available.
*/
public function is_available( $plugin ) {
return isset( $plugin['installed'] ) && $plugin['installed'] === true;
}
/**
* Checks whether a dependency is available.
*
* @param array $dependency The information about the dependency to look for.
*
* @return bool Whether or not the dependency is available.
*/
public function is_dependency_available( $dependency ) {
return isset( get_plugins()[ $dependency['slug'] ] );
}
/**
* Gets the names of the dependencies.
*
* @param array $plugin The plugin to get the dependency names from.
*
* @return array Array containing the names of the associated dependencies.
*/
public function get_dependency_names( $plugin ) {
if ( ! $this->has_dependencies( $plugin ) ) {
return [];
}
return array_keys( $plugin['_dependencies'] );
}
/**
* Gets an array of plugins that have defined dependencies.
*
* @return array Array of the plugins that have dependencies.
*/
public function get_plugins_with_dependencies() {
return array_filter( $this->plugins, [ $this, 'has_dependencies' ] );
}
/**
* Determines whether or not a plugin is active.
*
* @param string $plugin The plugin slug to check.
*
* @return bool Whether or not the plugin is active.
*/
public function is_active( $plugin ) {
return is_plugin_active( $plugin );
}
/**
* Determines whether or not a plugin is a Premium product.
*
* @param array $plugin The plugin to check.
*
* @return bool Whether or not the plugin is a Premium product.
*/
public function is_premium( $plugin ) {
return isset( $plugin['premium'] ) && $plugin['premium'] === true;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
* @since 1.7.0
*/
use Yoast\WP\SEO\Config\Conflicting_Plugins;
/**
* Contains list of conflicting plugins.
*/
class WPSEO_Plugin_Conflict extends Yoast_Plugin_Conflict {
/**
* The plugins must be grouped per section.
*
* It's possible to check for each section if there are conflicting plugin.
*
* NOTE: when changing this array, be sure to update the array in Conflicting_Plugins_Service too.
*
* @var array
*/
protected $plugins = [
// The plugin which are writing OG metadata.
'open_graph' => Conflicting_Plugins::OPEN_GRAPH_PLUGINS,
'xml_sitemaps' => Conflicting_Plugins::XML_SITEMAPS_PLUGINS,
'cloaking' => Conflicting_Plugins::CLOAKING_PLUGINS,
'seo' => Conflicting_Plugins::SEO_PLUGINS,
];
/**
* Overrides instance to set with this class as class.
*
* @param string $class_name Optional class name.
*
* @return Yoast_Plugin_Conflict
*/
public static function get_instance( $class_name = __CLASS__ ) {
return parent::get_instance( $class_name );
}
/**
* After activating any plugin, this method will be executed by a hook.
*
* If the activated plugin is conflicting with ours a notice will be shown.
*
* @param string|bool $plugin Optional plugin basename to check.
*/
public static function hook_check_for_plugin_conflicts( $plugin = false ) {
// The instance of the plugin.
$instance = self::get_instance();
// Only add the plugin as an active plugin if $plugin isn't false.
if ( $plugin && is_string( $plugin ) ) {
$instance->add_active_plugin( $instance->find_plugin_category( $plugin ), $plugin );
}
$plugin_sections = [];
// Only check for open graph problems when they are enabled.
if ( WPSEO_Options::get( 'opengraph' ) ) {
/* translators: %1$s expands to Yoast SEO, %2$s: 'Facebook' plugin name of possibly conflicting plugin with regard to creating OpenGraph output. */
$plugin_sections['open_graph'] = __( 'Both %1$s and %2$s create Open Graph output, which might make Facebook, Twitter, LinkedIn and other social networks use the wrong texts and images when your pages are being shared.', 'wordpress-seo' )
. '<br/><br/>'
. '<a class="button" href="' . admin_url( 'admin.php?page=wpseo_page_settings#/site-features#card-wpseo_social-opengraph' ) . '">'
/* translators: %1$s expands to Yoast SEO. */
. sprintf( __( 'Configure %1$s\'s Open Graph settings', 'wordpress-seo' ), 'Yoast SEO' )
. '</a>';
}
// Only check for XML conflicts if sitemaps are enabled.
if ( WPSEO_Options::get( 'enable_xml_sitemap' ) ) {
/* translators: %1$s expands to Yoast SEO, %2$s: 'Google XML Sitemaps' plugin name of possibly conflicting plugin with regard to the creation of sitemaps. */
$plugin_sections['xml_sitemaps'] = __( 'Both %1$s and %2$s can create XML sitemaps. Having two XML sitemaps is not beneficial for search engines and might slow down your site.', 'wordpress-seo' )
. '<br/><br/>'
. '<a class="button" href="' . admin_url( 'admin.php?page=wpseo_page_settings#/site-features#card-wpseo-enable_xml_sitemap' ) . '">'
/* translators: %1$s expands to Yoast SEO. */
. sprintf( __( 'Toggle %1$s\'s XML Sitemap', 'wordpress-seo' ), 'Yoast SEO' )
. '</a>';
}
/* translators: %2$s expands to 'RS Head Cleaner' plugin name of possibly conflicting plugin with regard to differentiating output between search engines and normal users. */
$plugin_sections['cloaking'] = __( 'The plugin %2$s changes your site\'s output and in doing that differentiates between search engines and normal users, a process that\'s called cloaking. We highly recommend that you disable it.', 'wordpress-seo' );
/* translators: %1$s expands to Yoast SEO, %2$s: 'SEO' plugin name of possibly conflicting plugin with regard to the creation of duplicate SEO meta. */
$plugin_sections['seo'] = __( 'Both %1$s and %2$s manage the SEO of your site. Running two SEO plugins at the same time is detrimental.', 'wordpress-seo' );
$instance->check_plugin_conflicts( $plugin_sections );
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Class WPSEO_Premium_popup.
*/
class WPSEO_Premium_Popup {
/**
* An unique identifier for the popup
*
* @var string
*/
private $identifier = '';
/**
* The heading level of the title of the popup.
*
* @var string
*/
private $heading_level = '';
/**
* The title of the popup.
*
* @var string
*/
private $title = '';
/**
* The content of the popup.
*
* @var string
*/
private $content = '';
/**
* The URL for where the button should link to.
*
* @var string
*/
private $url = '';
/**
* Wpseo_Premium_Popup constructor.
*
* @param string $identifier An unique identifier for the popup.
* @param string $heading_level The heading level for the title of the popup.
* @param string $title The title of the popup.
* @param string $content The content of the popup.
* @param string $url The URL for where the button should link to.
*/
public function __construct( $identifier, $heading_level, $title, $content, $url ) {
$this->identifier = $identifier;
$this->heading_level = $heading_level;
$this->title = $title;
$this->content = $content;
$this->url = $url;
}
/**
* Returns the premium popup as an HTML string.
*
* @param bool $popup Show this message as a popup show it straight away.
*
* @return string
*/
public function get_premium_message( $popup = true ) {
// Don't show in Premium.
if ( defined( 'WPSEO_PREMIUM_FILE' ) ) {
return '';
}
$assets_uri = trailingslashit( plugin_dir_url( WPSEO_FILE ) );
/* translators: %s expands to Yoast SEO Premium */
$cta_text = esc_html( sprintf( __( 'Get %s', 'wordpress-seo' ), 'Yoast SEO Premium' ) );
$new_tab_message = '<span class="screen-reader-text">' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>';
$caret_icon = '<span aria-hidden="true" class="yoast-button-upsell__caret"></span>';
$classes = '';
if ( $popup ) {
$classes = ' hidden';
}
$micro_copy = __( '1 year free support and updates included!', 'wordpress-seo' );
$popup = <<<EO_POPUP
<div id="wpseo-{$this->identifier}-popup" class="wpseo-premium-popup wp-clearfix$classes">
<img class="alignright wpseo-premium-popup-icon" src="{$assets_uri}packages/js/images/Yoast_SEO_Icon.svg" width="150" height="150" alt="Yoast SEO"/>
<{$this->heading_level} id="wpseo-contact-support-popup-title" class="wpseo-premium-popup-title">{$this->title}</{$this->heading_level}>
{$this->content}
<a id="wpseo-{$this->identifier}-popup-button" class="yoast-button-upsell" href="{$this->url}" target="_blank">
{$cta_text} {$new_tab_message} {$caret_icon}
</a><br/>
<small>{$micro_copy}</small>
</div>
EO_POPUP;
return $popup;
}
}
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Class WPSEO_Premium_Upsell_Admin_Block
*/
class WPSEO_Premium_Upsell_Admin_Block {
/**
* Hook to display the block on.
*
* @var string
*/
protected $hook;
/**
* Identifier to use in the dismissal functionality.
*
* @var string
*/
protected $identifier = 'premium_upsell';
/**
* Registers which hook the block will be displayed on.
*
* @param string $hook Hook to display the block on.
*/
public function __construct( $hook ) {
$this->hook = $hook;
}
/**
* Registers WordPress hooks.
*
* @return void
*/
public function register_hooks() {
add_action( $this->hook, [ $this, 'render' ] );
}
/**
* Renders the upsell block.
*
* @return void
*/
public function render() {
$url = WPSEO_Shortlinker::get( 'https://yoa.st/17h' );
$arguments = [
'<strong>' . esc_html__( 'Multiple keyphrases', 'wordpress-seo' ) . '</strong>: ' . esc_html__( 'Increase your SEO reach', 'wordpress-seo' ),
'<strong>' . esc_html__( 'No more dead links', 'wordpress-seo' ) . '</strong>: ' . esc_html__( 'Easy redirect manager', 'wordpress-seo' ),
'<strong>' . esc_html__( 'Superfast internal linking suggestions', 'wordpress-seo' ) . '</strong>',
'<strong>' . esc_html__( 'Social media preview', 'wordpress-seo' ) . '</strong>: ' . esc_html__( 'Facebook & Twitter', 'wordpress-seo' ),
'<strong>' . esc_html__( '24/7 email support', 'wordpress-seo' ) . '</strong>',
'<strong>' . esc_html__( 'No ads!', 'wordpress-seo' ) . '</strong>',
];
$arguments_html = implode( '', array_map( [ $this, 'get_argument_html' ], $arguments ) );
$class = $this->get_html_class();
/* translators: %s expands to Yoast SEO Premium */
$button_text = sprintf( esc_html__( 'Get %s', 'wordpress-seo' ), 'Yoast SEO Premium' );
$button_text .= '<span class="screen-reader-text">' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>' .
'<span aria-hidden="true" class="yoast-button-upsell__caret"></span>';
$upgrade_button = sprintf(
'<a id="%1$s" class="yoast-button-upsell" data-action="load-nfd-ctb" data-ctb-id="f6a84663-465f-4cb5-8ba5-f7a6d72224b2" href="%2$s" target="_blank">%3$s</a>',
esc_attr( 'wpseo-' . $this->identifier . '-popup-button' ),
esc_url( $url ),
$button_text
);
echo '<div class="' . esc_attr( $class ) . '">';
echo '<div>';
echo '<h2 class="' . esc_attr( $class . '--header' ) . '">' .
sprintf(
/* translators: %s expands to Yoast SEO Premium */
esc_html__( 'Upgrade to %s', 'wordpress-seo' ),
'Yoast SEO Premium'
) .
'</h2>';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in $this->get_argument_html() method.
echo '<ul class="' . esc_attr( $class . '--motivation' ) . '">' . $arguments_html . '</ul>';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in $upgrade_button and $button_text above.
echo '<p>' . $upgrade_button . '</p>';
echo '</div>';
echo '</div>';
}
/**
* Formats the argument to a HTML list item.
*
* @param string $argument The argument to format.
*
* @return string Formatted argument in HTML.
*/
protected function get_argument_html( $argument ) {
$class = $this->get_html_class();
return sprintf(
'<li><div class="%1$s">%2$s</div></li>',
esc_attr( $class . '--argument' ),
$argument
);
}
/**
* Returns the HTML base class to use.
*
* @return string The HTML base class.
*/
protected function get_html_class() {
return 'yoast_' . $this->identifier;
}
}
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.