first commit
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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>';
|
||||
}
|
||||
}
|
||||
}
|
||||
412
wp-content/plugins/wordpress-seo/admin/ajax.php
Normal file
412
wp-content/plugins/wordpress-seo/admin/ajax.php
Normal file
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
if ( ! defined( 'WPSEO_VERSION' ) ) {
|
||||
header( 'Status: 403 Forbidden' );
|
||||
header( 'HTTP/1.1 403 Forbidden' );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to JSON encode and echo results and then die.
|
||||
*
|
||||
* @param array $results Results array for encoding.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_ajax_json_echo_die( $results ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
|
||||
echo WPSEO_Utils::format_json_encode( $results );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used from AJAX calls, takes it variables from $_POST, dies on exit.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_set_option() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
check_ajax_referer( 'wpseo-setoption' );
|
||||
|
||||
if ( ! isset( $_POST['option'] ) || ! is_string( $_POST['option'] ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
$option = sanitize_text_field( wp_unslash( $_POST['option'] ) );
|
||||
if ( $option !== 'page_comments' ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
update_option( $option, 0 );
|
||||
exit( '1' );
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_wpseo_set_option', 'wpseo_set_option' );
|
||||
|
||||
/**
|
||||
* Since 3.2 Notifications are dismissed in the Notification Center.
|
||||
*/
|
||||
add_action( 'wp_ajax_yoast_dismiss_notification', [ 'Yoast_Notification_Center', 'ajax_dismiss_notification' ] );
|
||||
|
||||
/**
|
||||
* Function used to remove the admin notices for several purposes, dies on exit.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_set_ignore() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
check_ajax_referer( 'wpseo-ignore' );
|
||||
|
||||
if ( ! isset( $_POST['option'] ) || ! is_string( $_POST['option'] ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
$ignore_key = sanitize_text_field( wp_unslash( $_POST['option'] ) );
|
||||
WPSEO_Options::set( 'ignore_' . $ignore_key, true );
|
||||
|
||||
exit( '1' );
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_wpseo_set_ignore', 'wpseo_set_ignore' );
|
||||
|
||||
/**
|
||||
* Save an individual SEO title from the Bulk Editor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_save_title() {
|
||||
wpseo_save_what( 'title' );
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_wpseo_save_title', 'wpseo_save_title' );
|
||||
|
||||
/**
|
||||
* Save an individual meta description from the Bulk Editor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_save_description() {
|
||||
wpseo_save_what( 'metadesc' );
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_wpseo_save_metadesc', 'wpseo_save_description' );
|
||||
|
||||
/**
|
||||
* Save titles & descriptions.
|
||||
*
|
||||
* @param string $what Type of item to save (title, description).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_save_what( $what ) {
|
||||
check_ajax_referer( 'wpseo-bulk-editor' );
|
||||
|
||||
if ( ! isset( $_POST['new_value'], $_POST['wpseo_post_id'], $_POST['existing_value'] ) || ! is_string( $_POST['new_value'] ) || ! is_string( $_POST['existing_value'] ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
$new = sanitize_text_field( wp_unslash( $_POST['new_value'] ) );
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are casting the unsafe value to an integer.
|
||||
$post_id = (int) wp_unslash( $_POST['wpseo_post_id'] );
|
||||
$original = sanitize_text_field( wp_unslash( $_POST['existing_value'] ) );
|
||||
|
||||
if ( $post_id === 0 ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
$results = wpseo_upsert_new( $what, $post_id, $new, $original );
|
||||
|
||||
wpseo_ajax_json_echo_die( $results );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to update a post's meta data, returning relevant information
|
||||
* about the information updated and the results or the meta update.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param string $new_meta_value New meta value to record.
|
||||
* @param string $orig_meta_value Original meta value.
|
||||
* @param string $meta_key Meta key string.
|
||||
* @param string $return_key Return key string to use in results.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function wpseo_upsert_meta( $post_id, $new_meta_value, $orig_meta_value, $meta_key, $return_key ) {
|
||||
|
||||
$post_id = intval( $post_id );
|
||||
$sanitized_new_meta_value = wp_strip_all_tags( $new_meta_value );
|
||||
$orig_meta_value = wp_strip_all_tags( $orig_meta_value );
|
||||
|
||||
$upsert_results = [
|
||||
'status' => 'success',
|
||||
'post_id' => $post_id,
|
||||
"new_{$return_key}" => $sanitized_new_meta_value,
|
||||
"original_{$return_key}" => $orig_meta_value,
|
||||
];
|
||||
|
||||
$the_post = get_post( $post_id );
|
||||
if ( empty( $the_post ) ) {
|
||||
|
||||
$upsert_results['status'] = 'failure';
|
||||
$upsert_results['results'] = __( 'Post doesn\'t exist.', 'wordpress-seo' );
|
||||
|
||||
return $upsert_results;
|
||||
}
|
||||
|
||||
$post_type_object = get_post_type_object( $the_post->post_type );
|
||||
if ( ! $post_type_object ) {
|
||||
|
||||
$upsert_results['status'] = 'failure';
|
||||
$upsert_results['results'] = sprintf(
|
||||
/* translators: %s expands to post type. */
|
||||
__( 'Post has an invalid Content Type: %s.', 'wordpress-seo' ),
|
||||
$the_post->post_type
|
||||
);
|
||||
|
||||
return $upsert_results;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( $post_type_object->cap->edit_posts ) ) {
|
||||
|
||||
$upsert_results['status'] = 'failure';
|
||||
$upsert_results['results'] = sprintf(
|
||||
/* translators: %s expands to post type name. */
|
||||
__( 'You can\'t edit %s.', 'wordpress-seo' ),
|
||||
$post_type_object->label
|
||||
);
|
||||
|
||||
return $upsert_results;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( $post_type_object->cap->edit_others_posts ) && (int) $the_post->post_author !== get_current_user_id() ) {
|
||||
|
||||
$upsert_results['status'] = 'failure';
|
||||
$upsert_results['results'] = sprintf(
|
||||
/* translators: %s expands to the name of a post type (plural). */
|
||||
__( 'You can\'t edit %s that aren\'t yours.', 'wordpress-seo' ),
|
||||
$post_type_object->label
|
||||
);
|
||||
|
||||
return $upsert_results;
|
||||
}
|
||||
|
||||
if ( $sanitized_new_meta_value === $orig_meta_value && $sanitized_new_meta_value !== $new_meta_value ) {
|
||||
$upsert_results['status'] = 'failure';
|
||||
$upsert_results['results'] = __( 'You have used HTML in your value which is not allowed.', 'wordpress-seo' );
|
||||
|
||||
return $upsert_results;
|
||||
}
|
||||
|
||||
$res = update_post_meta( $post_id, $meta_key, $sanitized_new_meta_value );
|
||||
|
||||
$upsert_results['status'] = ( $res !== false ) ? 'success' : 'failure';
|
||||
$upsert_results['results'] = $res;
|
||||
|
||||
return $upsert_results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all titles sent from the Bulk Editor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_save_all_titles() {
|
||||
wpseo_save_all( 'title' );
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_wpseo_save_all_titles', 'wpseo_save_all_titles' );
|
||||
|
||||
/**
|
||||
* Save all description sent from the Bulk Editor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_save_all_descriptions() {
|
||||
wpseo_save_all( 'metadesc' );
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_wpseo_save_all_descriptions', 'wpseo_save_all_descriptions' );
|
||||
|
||||
/**
|
||||
* Utility function to save values.
|
||||
*
|
||||
* @param string $what Type of item so save.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_save_all( $what ) {
|
||||
check_ajax_referer( 'wpseo-bulk-editor' );
|
||||
|
||||
$results = [];
|
||||
if ( ! isset( $_POST['items'], $_POST['existingItems'] ) ) {
|
||||
wpseo_ajax_json_echo_die( $results );
|
||||
}
|
||||
|
||||
$new_values = array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], wp_unslash( (array) $_POST['items'] ) );
|
||||
$original_values = array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], wp_unslash( (array) $_POST['existingItems'] ) );
|
||||
|
||||
foreach ( $new_values as $post_id => $new_value ) {
|
||||
$original_value = $original_values[ $post_id ];
|
||||
$results[] = wpseo_upsert_new( $what, $post_id, $new_value, $original_value );
|
||||
}
|
||||
|
||||
wpseo_ajax_json_echo_die( $results );
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new value.
|
||||
*
|
||||
* @param string $what Item type (such as title).
|
||||
* @param int $post_id Post ID.
|
||||
* @param string $new_value New value to record.
|
||||
* @param string $original Original value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function wpseo_upsert_new( $what, $post_id, $new_value, $original ) {
|
||||
$meta_key = WPSEO_Meta::$meta_prefix . $what;
|
||||
|
||||
return wpseo_upsert_meta( $post_id, $new_value, $original, $meta_key, $what );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post ids where the keyword is used before as well as the types of those posts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function ajax_get_keyword_usage_and_post_types() {
|
||||
check_ajax_referer( 'wpseo-keyword-usage-and-post-types', 'nonce' );
|
||||
|
||||
if ( ! isset( $_POST['post_id'], $_POST['keyword'] ) || ! is_string( $_POST['keyword'] ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We are casting to an integer.
|
||||
$post_id = (int) wp_unslash( $_POST['post_id'] );
|
||||
|
||||
if ( $post_id === 0 || ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
$keyword = sanitize_text_field( wp_unslash( $_POST['keyword'] ) );
|
||||
|
||||
$post_ids = WPSEO_Meta::keyword_usage( $keyword, $post_id );
|
||||
|
||||
$return_object = [
|
||||
'keyword_usage' => $post_ids,
|
||||
'post_types' => WPSEO_Meta::post_types_for_ids( $post_ids ),
|
||||
];
|
||||
|
||||
wp_die(
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
|
||||
WPSEO_Utils::format_json_encode( $return_object )
|
||||
);
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_get_focus_keyword_usage_and_post_types', 'ajax_get_keyword_usage_and_post_types' );
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the keyword for the keyword doubles of the termpages.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function ajax_get_term_keyword_usage() {
|
||||
check_ajax_referer( 'wpseo-keyword-usage', 'nonce' );
|
||||
|
||||
if ( ! isset( $_POST['post_id'], $_POST['keyword'], $_POST['taxonomy'] ) || ! is_string( $_POST['keyword'] ) || ! is_string( $_POST['taxonomy'] ) ) {
|
||||
wp_die( -1 );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are casting the unsafe input to an integer.
|
||||
$post_id = (int) wp_unslash( $_POST['post_id'] );
|
||||
|
||||
if ( $post_id === 0 ) {
|
||||
wp_die( -1 );
|
||||
}
|
||||
|
||||
$keyword = sanitize_text_field( wp_unslash( $_POST['keyword'] ) );
|
||||
$taxonomy_name = sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) );
|
||||
|
||||
$taxonomy = get_taxonomy( $taxonomy_name );
|
||||
|
||||
if ( ! $taxonomy ) {
|
||||
wp_die( 0 );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
|
||||
wp_die( -1 );
|
||||
}
|
||||
|
||||
$usage = WPSEO_Taxonomy_Meta::get_keyword_usage( $keyword, $post_id, $taxonomy_name );
|
||||
|
||||
// Normalize the result so it is the same as the post keyword usage AJAX request.
|
||||
$usage = $usage[ $keyword ];
|
||||
|
||||
wp_die(
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
|
||||
WPSEO_Utils::format_json_encode( $usage )
|
||||
);
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_get_term_keyword_usage', 'ajax_get_term_keyword_usage' );
|
||||
|
||||
/**
|
||||
* Registers hooks for all AJAX integrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wpseo_register_ajax_integrations() {
|
||||
$integrations = [ new Yoast_Network_Admin() ];
|
||||
|
||||
foreach ( $integrations as $integration ) {
|
||||
$integration->register_ajax_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
wpseo_register_ajax_integrations();
|
||||
|
||||
new WPSEO_Shortcode_Filter();
|
||||
|
||||
new WPSEO_Taxonomy_Columns();
|
||||
|
||||
/* ********************* DEPRECATED FUNCTIONS ********************* */
|
||||
|
||||
/**
|
||||
* Retrieves the keyword for the keyword doubles.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function ajax_get_keyword_usage() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 20.4' );
|
||||
check_ajax_referer( 'wpseo-keyword-usage', 'nonce' );
|
||||
|
||||
if ( ! isset( $_POST['post_id'], $_POST['keyword'] ) || ! is_string( $_POST['keyword'] ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We are casting to an integer.
|
||||
$post_id = (int) wp_unslash( $_POST['post_id'] );
|
||||
|
||||
if ( $post_id === 0 || ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
$keyword = sanitize_text_field( wp_unslash( $_POST['keyword'] ) );
|
||||
|
||||
wp_die(
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
|
||||
WPSEO_Utils::format_json_encode( WPSEO_Meta::keyword_usage( $keyword, $post_id ) )
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?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
|
||||
*/
|
||||
public const FOR_USER = 'user_meta';
|
||||
|
||||
/**
|
||||
* Notice type toggle value for network notices.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FOR_NETWORK = 'site_option';
|
||||
|
||||
/**
|
||||
* Notice type toggle value for site notices.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public 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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function compare_plugin( $posted_plugin ) {
|
||||
if ( ! in_array( $posted_plugin, $this->dismissed_conflicts, true ) ) {
|
||||
$this->dismissed_conflicts[] = $posted_plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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 ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?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()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register( $capability, array $roles, $overwrite = false );
|
||||
|
||||
/**
|
||||
* Adds the registerd capabilities to the system.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add();
|
||||
|
||||
/**
|
||||
* Removes the registered capabilities from the system.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove();
|
||||
|
||||
/**
|
||||
* Returns the list of registered capabilities.
|
||||
*
|
||||
* @return string[] List of registered capabilities.
|
||||
*/
|
||||
public function get_capabilities();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
if ( ! is_array( $user->roles ) ) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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
|
||||
*/
|
||||
public 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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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 );
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class registers all the necessary styles and scripts.
|
||||
*
|
||||
* Also has methods for the enqueing of scripts and styles.
|
||||
* It automatically adds a prefix to the handle.
|
||||
*/
|
||||
class WPSEO_Admin_Asset_Manager {
|
||||
|
||||
/**
|
||||
* Prefix for naming the assets.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PREFIX = 'yoast-seo-';
|
||||
|
||||
/**
|
||||
* Class that manages the assets' location.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Location
|
||||
*/
|
||||
protected $asset_location;
|
||||
|
||||
/**
|
||||
* Prefix for naming the assets.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $prefix;
|
||||
|
||||
/**
|
||||
* Constructs a manager of assets. Needs a location to know where to register assets at.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset_Location|null $asset_location The provider of the asset location.
|
||||
* @param string $prefix The prefix for naming assets.
|
||||
*/
|
||||
public function __construct( ?WPSEO_Admin_Asset_Location $asset_location = null, $prefix = self::PREFIX ) {
|
||||
if ( $asset_location === null ) {
|
||||
$asset_location = self::create_default_location();
|
||||
}
|
||||
|
||||
$this->asset_location = $asset_location;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues scripts.
|
||||
*
|
||||
* @param string $script The name of the script to enqueue.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_script( $script ) {
|
||||
wp_enqueue_script( $this->prefix . $script );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues styles.
|
||||
*
|
||||
* @param string $style The name of the style to enqueue.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_style( $style ) {
|
||||
wp_enqueue_style( $this->prefix . $style );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the appropriate language for the user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_user_language_script() {
|
||||
$this->enqueue_script( 'language-' . YoastSEO()->helpers->language->get_researcher_language() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers scripts based on it's parameters.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset $script The script to register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_script( WPSEO_Admin_Asset $script ) {
|
||||
$url = $script->get_src() ? $this->get_url( $script, WPSEO_Admin_Asset::TYPE_JS ) : false;
|
||||
$args = [
|
||||
'in_footer' => $script->is_in_footer(),
|
||||
];
|
||||
|
||||
if ( $script->get_strategy() !== '' ) {
|
||||
$args['strategy'] = $script->get_strategy();
|
||||
}
|
||||
|
||||
wp_register_script(
|
||||
$this->prefix . $script->get_name(),
|
||||
$url,
|
||||
$script->get_deps(),
|
||||
$script->get_version(),
|
||||
$args
|
||||
);
|
||||
|
||||
if ( in_array( 'wp-i18n', $script->get_deps(), true ) ) {
|
||||
wp_set_script_translations( $this->prefix . $script->get_name(), 'wordpress-seo' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers styles based on it's parameters.
|
||||
*
|
||||
* @param WPSEO_Admin_Asset $style The style to register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_style( WPSEO_Admin_Asset $style ) {
|
||||
wp_register_style(
|
||||
$this->prefix . $style->get_name(),
|
||||
$this->get_url( $style, WPSEO_Admin_Asset::TYPE_CSS ),
|
||||
$style->get_deps(),
|
||||
$style->get_version(),
|
||||
$style->get_media()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the functions that register scripts and styles with the scripts and styles to be registered as arguments.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_assets() {
|
||||
$this->register_scripts( $this->scripts_to_be_registered() );
|
||||
$this->register_styles( $this->styles_to_be_registered() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all the scripts passed to it.
|
||||
*
|
||||
* @param array $scripts The scripts passed to it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_scripts( $scripts ) {
|
||||
foreach ( $scripts as $script ) {
|
||||
$script = new WPSEO_Admin_Asset( $script );
|
||||
$this->register_script( $script );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all the styles it receives.
|
||||
*
|
||||
* @param array $styles Styles that need to be registered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_styles( $styles ) {
|
||||
foreach ( $styles as $style ) {
|
||||
$style = new WPSEO_Admin_Asset( $style );
|
||||
$this->register_style( $style );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Localizes the script.
|
||||
*
|
||||
* @param string $handle The script handle.
|
||||
* @param string $object_name The object name.
|
||||
* @param array $data The l10n data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function localize_script( $handle, $object_name, $data ) {
|
||||
wp_localize_script( $this->prefix . $handle, $object_name, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an inline script.
|
||||
*
|
||||
* @param string $handle The script handle.
|
||||
* @param string $data The l10n data.
|
||||
* @param string $position Optional. Whether to add the inline script before the handle or after.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_inline_script( $handle, $data, $position = 'after' ) {
|
||||
wp_add_inline_script( $this->prefix . $handle, $data, $position );
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of styles that shouldn't be registered but are needed in other locations in the plugin.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function special_styles() {
|
||||
$flat_version = $this->flatten_version( WPSEO_VERSION );
|
||||
$asset_args = [
|
||||
'name' => 'inside-editor',
|
||||
'src' => 'inside-editor-' . $flat_version,
|
||||
];
|
||||
|
||||
return [ 'inside-editor' => new WPSEO_Admin_Asset( $asset_args ) ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens a version number for use in a filename.
|
||||
*
|
||||
* @param string $version The original version number.
|
||||
*
|
||||
* @return string The flattened version number.
|
||||
*/
|
||||
public function flatten_version( $version ) {
|
||||
$parts = explode( '.', $version );
|
||||
|
||||
if ( count( $parts ) === 2 && preg_match( '/^\d+$/', $parts[1] ) === 1 ) {
|
||||
$parts[] = '0';
|
||||
}
|
||||
|
||||
return implode( '', $parts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default location object for use in the admin asset manager.
|
||||
*
|
||||
* @return WPSEO_Admin_Asset_Location The location to use in the asset manager.
|
||||
*/
|
||||
public static function create_default_location() {
|
||||
if ( defined( 'YOAST_SEO_DEV_SERVER' ) && YOAST_SEO_DEV_SERVER ) {
|
||||
$url = defined( 'YOAST_SEO_DEV_SERVER_URL' ) ? YOAST_SEO_DEV_SERVER_URL : WPSEO_Admin_Asset_Dev_Server_Location::DEFAULT_URL;
|
||||
|
||||
return new WPSEO_Admin_Asset_Dev_Server_Location( $url );
|
||||
}
|
||||
|
||||
return new WPSEO_Admin_Asset_SEO_Location( WPSEO_FILE, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given script is enqueued.
|
||||
*
|
||||
* @param string $script The script to check.
|
||||
*
|
||||
* @return bool True when the script is enqueued.
|
||||
*/
|
||||
public function is_script_enqueued( $script ) {
|
||||
return wp_script_is( $this->prefix . $script );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scripts that need to be registered.
|
||||
*
|
||||
* @todo Data format is not self-documenting. Needs explanation inline. R.
|
||||
*
|
||||
* @return array The scripts that need to be registered.
|
||||
*/
|
||||
protected function scripts_to_be_registered() {
|
||||
$header_scripts = [
|
||||
'admin-global',
|
||||
'block-editor',
|
||||
'classic-editor',
|
||||
'post-edit',
|
||||
'help-scout-beacon',
|
||||
'redirect-old-features-tab',
|
||||
];
|
||||
$additional_dependencies = [
|
||||
'analysis-worker' => [ self::PREFIX . 'analysis-package' ],
|
||||
'api-client' => [ 'wp-api' ],
|
||||
'crawl-settings' => [ 'jquery' ],
|
||||
'dashboard-widget' => [ self::PREFIX . 'api-client' ],
|
||||
'wincher-dashboard-widget' => [ self::PREFIX . 'api-client' ],
|
||||
'editor-modules' => [ 'jquery' ],
|
||||
'elementor' => [
|
||||
self::PREFIX . 'api-client',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
],
|
||||
'indexation' => [
|
||||
'jquery-ui-core',
|
||||
'jquery-ui-progressbar',
|
||||
],
|
||||
'first-time-configuration' => [
|
||||
self::PREFIX . 'api-client',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
],
|
||||
'integrations-page' => [
|
||||
self::PREFIX . 'api-client',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
],
|
||||
'post-edit' => [
|
||||
self::PREFIX . 'api-client',
|
||||
self::PREFIX . 'block-editor',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
],
|
||||
'reindex-links' => [
|
||||
'jquery-ui-core',
|
||||
'jquery-ui-progressbar',
|
||||
],
|
||||
'settings' => [
|
||||
'jquery-ui-core',
|
||||
'jquery-ui-progressbar',
|
||||
self::PREFIX . 'api-client',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
],
|
||||
'term-edit' => [
|
||||
self::PREFIX . 'api-client',
|
||||
self::PREFIX . 'classic-editor',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
],
|
||||
'general-page' => [
|
||||
self::PREFIX . 'api-client',
|
||||
],
|
||||
];
|
||||
|
||||
$plugin_scripts = $this->load_generated_asset_file(
|
||||
[
|
||||
'asset_file' => __DIR__ . '/../src/generated/assets/plugin.php',
|
||||
'ext_length' => 3,
|
||||
'additional_deps' => $additional_dependencies,
|
||||
'header_scripts' => $header_scripts,
|
||||
]
|
||||
);
|
||||
$external_scripts = $this->load_generated_asset_file(
|
||||
[
|
||||
'asset_file' => __DIR__ . '/../src/generated/assets/externals.php',
|
||||
'ext_length' => 3,
|
||||
'suffix' => '-package',
|
||||
'base_dir' => 'externals/',
|
||||
'additional_deps' => $additional_dependencies,
|
||||
'header_scripts' => $header_scripts,
|
||||
]
|
||||
);
|
||||
$language_scripts = $this->load_generated_asset_file(
|
||||
[
|
||||
'asset_file' => __DIR__ . '/../src/generated/assets/languages.php',
|
||||
'ext_length' => 3,
|
||||
'suffix' => '-language',
|
||||
'base_dir' => 'languages/',
|
||||
'additional_deps' => $additional_dependencies,
|
||||
'header_scripts' => $header_scripts,
|
||||
]
|
||||
);
|
||||
$renamed_scripts = $this->load_renamed_scripts();
|
||||
|
||||
$scripts = array_merge(
|
||||
$plugin_scripts,
|
||||
$external_scripts,
|
||||
$language_scripts,
|
||||
$renamed_scripts
|
||||
);
|
||||
|
||||
$scripts['installation-success'] = [
|
||||
'name' => 'installation-success',
|
||||
'src' => 'installation-success.js',
|
||||
'deps' => [
|
||||
'wp-a11y',
|
||||
'wp-dom-ready',
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
self::PREFIX . 'components-new-package',
|
||||
self::PREFIX . 'externals-components',
|
||||
],
|
||||
'version' => $scripts['installation-success']['version'],
|
||||
];
|
||||
|
||||
$scripts['post-edit-classic'] = [
|
||||
'name' => 'post-edit-classic',
|
||||
'src' => $scripts['post-edit']['src'],
|
||||
'deps' => array_map(
|
||||
static function ( $dep ) {
|
||||
if ( $dep === self::PREFIX . 'block-editor' ) {
|
||||
return self::PREFIX . 'classic-editor';
|
||||
}
|
||||
return $dep;
|
||||
},
|
||||
$scripts['post-edit']['deps']
|
||||
),
|
||||
'in_footer' => ! in_array( 'post-edit-classic', $header_scripts, true ),
|
||||
'version' => $scripts['post-edit']['version'],
|
||||
];
|
||||
|
||||
$scripts['workouts'] = [
|
||||
'name' => 'workouts',
|
||||
'src' => 'workouts.js',
|
||||
'deps' => [
|
||||
'clipboard',
|
||||
'lodash',
|
||||
'wp-api-fetch',
|
||||
'wp-a11y',
|
||||
'wp-components',
|
||||
'wp-compose',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
self::PREFIX . 'analysis',
|
||||
self::PREFIX . 'react-select',
|
||||
self::PREFIX . 'components-new-package',
|
||||
],
|
||||
'version' => $scripts['workouts']['version'],
|
||||
];
|
||||
|
||||
// Add the current language to every script that requires the analysis package.
|
||||
foreach ( $scripts as $name => $script ) {
|
||||
if ( substr( $name, -8 ) === 'language' ) {
|
||||
continue;
|
||||
}
|
||||
if ( in_array( self::PREFIX . 'analysis-package', $script['deps'], true ) ) {
|
||||
$scripts[ $name ]['deps'][] = self::PREFIX . YoastSEO()->helpers->language->get_researcher_language() . '-language';
|
||||
}
|
||||
}
|
||||
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a generated asset file.
|
||||
*
|
||||
* @param array $args {
|
||||
* The arguments.
|
||||
*
|
||||
* @type string $asset_file The asset file to load.
|
||||
* @type int $ext_length The length of the extension, including suffix, of the filename.
|
||||
* @type string $suffix Optional. The suffix of the asset name.
|
||||
* @type array<string, string[]> $additional_deps Optional. The additional dependencies assets may have.
|
||||
* @type string $base_dir Optional. The base directory of the asset.
|
||||
* @type string[] $header_scripts Optional. The script names that should be in the header.
|
||||
* }
|
||||
*
|
||||
* @return array {
|
||||
* The scripts to be registered.
|
||||
*
|
||||
* @type string $name The name of the asset.
|
||||
* @type string $src The src of the asset.
|
||||
* @type string[] $deps The dependenies of the asset.
|
||||
* @type bool $in_footer Whether or not the asset should be in the footer.
|
||||
* }
|
||||
*/
|
||||
protected function load_generated_asset_file( $args ) {
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
[
|
||||
'suffix' => '',
|
||||
'additional_deps' => [],
|
||||
'base_dir' => '',
|
||||
'header_scripts' => [],
|
||||
]
|
||||
);
|
||||
$scripts = [];
|
||||
$assets = require $args['asset_file'];
|
||||
foreach ( $assets as $file => $data ) {
|
||||
$name = substr( $file, 0, -$args['ext_length'] );
|
||||
$name = strtolower( preg_replace( '/([A-Z])/', '-$1', $name ) );
|
||||
$name .= $args['suffix'];
|
||||
|
||||
$deps = $data['dependencies'];
|
||||
if ( isset( $args['additional_deps'][ $name ] ) ) {
|
||||
$deps = array_merge( $deps, $args['additional_deps'][ $name ] );
|
||||
}
|
||||
|
||||
$scripts[ $name ] = [
|
||||
'name' => $name,
|
||||
'src' => $args['base_dir'] . $file,
|
||||
'deps' => $deps,
|
||||
'in_footer' => ! in_array( $name, $args['header_scripts'], true ),
|
||||
'version' => $data['version'],
|
||||
];
|
||||
}
|
||||
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the scripts that should be renamed for BC.
|
||||
*
|
||||
* @return array {
|
||||
* The scripts to be registered.
|
||||
*
|
||||
* @type string $name The name of the asset.
|
||||
* @type string $src The src of the asset.
|
||||
* @type string[] $deps The dependenies of the asset.
|
||||
* @type bool $in_footer Whether or not the asset should be in the footer.
|
||||
* }
|
||||
*/
|
||||
protected function load_renamed_scripts() {
|
||||
$scripts = [];
|
||||
$renamed_scripts = [
|
||||
'admin-global-script' => 'admin-global',
|
||||
'analysis' => 'analysis-package',
|
||||
'analysis-report' => 'analysis-report-package',
|
||||
'api' => 'api-client',
|
||||
'commons' => 'commons-package',
|
||||
'edit-page' => 'edit-page-script',
|
||||
'draft-js' => 'draft-js-package',
|
||||
'feature-flag' => 'feature-flag-package',
|
||||
'helpers' => 'helpers-package',
|
||||
'jed' => 'jed-package',
|
||||
'chart.js' => 'chart.js-package',
|
||||
'network-admin-script' => 'network-admin',
|
||||
'redux' => 'redux-package',
|
||||
'replacement-variable-editor' => 'replacement-variable-editor-package',
|
||||
'search-metadata-previews' => 'search-metadata-previews-package',
|
||||
'social-metadata-forms' => 'social-metadata-forms-package',
|
||||
'styled-components' => 'styled-components-package',
|
||||
'style-guide' => 'style-guide-package',
|
||||
'yoast-components' => 'components-new-package',
|
||||
];
|
||||
|
||||
foreach ( $renamed_scripts as $original => $replacement ) {
|
||||
$scripts[] = [
|
||||
'name' => $original,
|
||||
'src' => false,
|
||||
'deps' => [ self::PREFIX . $replacement ],
|
||||
];
|
||||
}
|
||||
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the styles that need to be registered.
|
||||
*
|
||||
* @todo Data format is not self-documenting. Needs explanation inline. R.
|
||||
*
|
||||
* @return array Styles that need to be registered.
|
||||
*/
|
||||
protected function styles_to_be_registered() {
|
||||
$flat_version = $this->flatten_version( WPSEO_VERSION );
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => 'admin-css',
|
||||
'src' => 'yst_plugin_tools-' . $flat_version,
|
||||
'deps' => [ self::PREFIX . 'toggle-switch' ],
|
||||
],
|
||||
[
|
||||
'name' => 'toggle-switch',
|
||||
'src' => 'toggle-switch-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'dismissible',
|
||||
'src' => 'wpseo-dismissible-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'notifications',
|
||||
'src' => 'notifications-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'alert',
|
||||
'src' => 'alerts-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'edit-page',
|
||||
'src' => 'edit-page-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'featured-image',
|
||||
'src' => 'featured-image-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'metabox-css',
|
||||
'src' => 'metabox-' . $flat_version,
|
||||
'deps' => [
|
||||
self::PREFIX . 'admin-css',
|
||||
self::PREFIX . 'tailwind',
|
||||
'wp-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'block-editor',
|
||||
'src' => 'block-editor-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'ai-generator',
|
||||
'src' => 'ai-generator-' . $flat_version,
|
||||
'deps' => [
|
||||
self::PREFIX . 'tailwind',
|
||||
self::PREFIX . 'introductions',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'ai-fix-assessments',
|
||||
'src' => 'ai-fix-assessments-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'introductions',
|
||||
'src' => 'introductions-' . $flat_version,
|
||||
'deps' => [ self::PREFIX . 'tailwind' ],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-dashboard',
|
||||
'src' => 'dashboard-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'scoring',
|
||||
'src' => 'yst_seo_score-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'adminbar',
|
||||
'src' => 'adminbar-' . $flat_version,
|
||||
'deps' => [
|
||||
'admin-bar',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'primary-category',
|
||||
'src' => 'metabox-primary-category-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'admin-global',
|
||||
'src' => 'admin-global-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'extensions',
|
||||
'src' => 'yoast-extensions-' . $flat_version,
|
||||
'deps' => [
|
||||
'wp-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'filter-explanation',
|
||||
'src' => 'filter-explanation-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'monorepo',
|
||||
'src' => 'monorepo-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'structured-data-blocks',
|
||||
'src' => 'structured-data-blocks-' . $flat_version,
|
||||
'deps' => [
|
||||
'dashicons',
|
||||
'forms',
|
||||
'wp-edit-blocks',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'elementor',
|
||||
'src' => 'elementor-' . $flat_version,
|
||||
],
|
||||
[
|
||||
'name' => 'tailwind',
|
||||
'src' => 'tailwind-' . $flat_version,
|
||||
// Note: The RTL suffix is not added here.
|
||||
// Tailwind and our UI library provide styling that should be standalone compatible with RTL.
|
||||
// To make it easier we should use the logical properties and values when possible.
|
||||
// If there are exceptions, we can use the Tailwind modifier, e.g. `rtl:yst-space-x-reverse`.
|
||||
'rtl' => false,
|
||||
],
|
||||
[
|
||||
'name' => 'new-settings',
|
||||
'src' => 'new-settings-' . $flat_version,
|
||||
'deps' => [ self::PREFIX . 'tailwind' ],
|
||||
],
|
||||
[
|
||||
'name' => 'black-friday-banner',
|
||||
'src' => 'black-friday-banner-' . $flat_version,
|
||||
'deps' => [ self::PREFIX . 'tailwind' ],
|
||||
],
|
||||
[
|
||||
'name' => 'academy',
|
||||
'src' => 'academy-' . $flat_version,
|
||||
'deps' => [ self::PREFIX . 'tailwind' ],
|
||||
],
|
||||
[
|
||||
'name' => 'general-page',
|
||||
'src' => 'general-page-' . $flat_version,
|
||||
'deps' => [ self::PREFIX . 'tailwind' ],
|
||||
],
|
||||
[
|
||||
'name' => 'support',
|
||||
'src' => 'support-' . $flat_version,
|
||||
'deps' => [ self::PREFIX . 'tailwind' ],
|
||||
],
|
||||
[
|
||||
'name' => 'workouts',
|
||||
'src' => 'workouts-' . $flat_version,
|
||||
'deps' => [
|
||||
self::PREFIX . 'monorepo',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'first-time-configuration',
|
||||
'src' => 'first-time-configuration-' . $flat_version,
|
||||
'deps' => [ self::PREFIX . 'tailwind' ],
|
||||
],
|
||||
[
|
||||
'name' => 'inside-editor',
|
||||
'src' => 'inside-editor-' . $flat_version,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the URL of the asset.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
protected 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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
<?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.
|
||||
*
|
||||
* @param 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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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.
|
||||
*
|
||||
* @param bool $display_notification
|
||||
*/
|
||||
$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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
379
wp-content/plugins/wordpress-seo/admin/class-admin-init.php
Normal file
379
wp-content/plugins/wordpress-seo/admin/class-admin-init.php
Normal file
@@ -0,0 +1,379 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Performs the load on admin side.
|
||||
*/
|
||||
class WPSEO_Admin_Init {
|
||||
|
||||
/**
|
||||
* Holds the global `$pagenow` variable's value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $pagenow;
|
||||
|
||||
/**
|
||||
* Holds the asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $asset_manager;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$GLOBALS['wpseo_admin'] = new WPSEO_Admin();
|
||||
|
||||
$this->pagenow = $GLOBALS['pagenow'];
|
||||
|
||||
$this->asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_dismissible' ] );
|
||||
add_action( 'admin_init', [ $this, 'unsupported_php_notice' ], 15 );
|
||||
add_action( 'admin_init', [ $this, 'remove_translations_notification' ], 15 );
|
||||
add_action( 'admin_init', [ $this->asset_manager, 'register_assets' ] );
|
||||
add_action( 'admin_init', [ $this, 'show_hook_deprecation_warnings' ] );
|
||||
add_action( 'admin_init', [ 'WPSEO_Plugin_Conflict', 'hook_check_for_plugin_conflicts' ] );
|
||||
add_action( 'admin_notices', [ $this, 'permalink_settings_notice' ] );
|
||||
add_action( 'post_submitbox_misc_actions', [ $this, 'add_publish_box_section' ] );
|
||||
|
||||
$this->load_meta_boxes();
|
||||
$this->load_taxonomy_class();
|
||||
$this->load_admin_page_class();
|
||||
$this->load_admin_user_class();
|
||||
$this->load_xml_sitemaps_admin();
|
||||
$this->load_plugin_suggestions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue our styling for dismissible yoast notifications.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_dismissible() {
|
||||
$this->asset_manager->enqueue_style( 'dismissible' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any notification for incomplete translations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_translations_notification() {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->remove_notification_by_id( 'i18nModuleTranslationAssistance' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unsupported PHP version notification in the notification center.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unsupported_php_notice() {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->remove_notification_by_id( 'wpseo-dismiss-unsupported-php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest released major WordPress version from the WordPress stable-check api.
|
||||
*
|
||||
* @return float|int The latest released major WordPress version. 0 when the stable-check API doesn't respond.
|
||||
*/
|
||||
private function get_latest_major_wordpress_version() {
|
||||
$core_updates = get_core_updates( [ 'dismissed' => true ] );
|
||||
|
||||
if ( $core_updates === false ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$wp_version_latest = get_bloginfo( 'version' );
|
||||
foreach ( $core_updates as $update ) {
|
||||
if ( $update->response === 'upgrade' && version_compare( $update->version, $wp_version_latest, '>' ) ) {
|
||||
$wp_version_latest = $update->version;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the patch version and convert to a float.
|
||||
return (float) $wp_version_latest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to verify if the user is currently visiting one of our admin pages.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function on_wpseo_admin_page() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( ! isset( $_GET['page'] ) || ! is_string( $_GET['page'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->pagenow !== 'admin.php' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$current_page = sanitize_text_field( wp_unslash( $_GET['page'] ) );
|
||||
return strpos( $current_page, 'wpseo' ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we should load the meta box classes.
|
||||
*
|
||||
* @return bool true if we should load the meta box classes, false otherwise.
|
||||
*/
|
||||
private function should_load_meta_boxes() {
|
||||
/**
|
||||
* Filter: 'wpseo_always_register_metaboxes_on_admin' - Allow developers to change whether
|
||||
* the WPSEO metaboxes are only registered on the typical pages (lean loading) or always
|
||||
* registered when in admin.
|
||||
*
|
||||
* @param bool $register_metaboxes Whether to always register the metaboxes or not. Defaults to false.
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_always_register_metaboxes_on_admin', false ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we are in a post editor.
|
||||
if ( WPSEO_Metabox::is_post_overview( $this->pagenow ) || WPSEO_Metabox::is_post_edit( $this->pagenow ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we are doing an inline save.
|
||||
if ( check_ajax_referer( 'inlineeditnonce', '_inline_edit', false ) && isset( $_POST['action'] ) && sanitize_text_field( wp_unslash( $_POST['action'] ) ) === 'inline-save' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether we should load the meta box class and if so, load it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function load_meta_boxes() {
|
||||
if ( $this->should_load_meta_boxes() ) {
|
||||
$GLOBALS['wpseo_metabox'] = new WPSEO_Metabox();
|
||||
$GLOBALS['wpseo_meta_columns'] = new WPSEO_Meta_Columns();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we should load our taxonomy edit class and if so, load it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function load_taxonomy_class() {
|
||||
if (
|
||||
WPSEO_Taxonomy::is_term_edit( $this->pagenow )
|
||||
|| WPSEO_Taxonomy::is_term_overview( $this->pagenow )
|
||||
) {
|
||||
new WPSEO_Taxonomy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we should load our admin pages class and if so, load it.
|
||||
*
|
||||
* Loads admin page class for all admin pages starting with `wpseo_`.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function load_admin_user_class() {
|
||||
if ( in_array( $this->pagenow, [ 'user-edit.php', 'profile.php' ], true )
|
||||
&& current_user_can( 'edit_users' )
|
||||
) {
|
||||
new WPSEO_Admin_User_Profile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we should load our admin pages class and if so, load it.
|
||||
*
|
||||
* Loads admin page class for all admin pages starting with `wpseo_`.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function load_admin_page_class() {
|
||||
|
||||
if ( $this->on_wpseo_admin_page() ) {
|
||||
// For backwards compatabilty, this still needs a global, for now...
|
||||
$GLOBALS['wpseo_admin_pages'] = new WPSEO_Admin_Pages();
|
||||
|
||||
$page = null;
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$page = sanitize_text_field( wp_unslash( $_GET['page'] ) );
|
||||
}
|
||||
|
||||
// Only renders Yoast SEO Premium upsells when the page is a Yoast SEO page.
|
||||
if ( $page !== null && WPSEO_Utils::is_yoast_seo_free_page( $page ) ) {
|
||||
$this->register_premium_upsell_admin_block();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the plugin suggestions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function load_plugin_suggestions() {
|
||||
$suggestions = new WPSEO_Suggested_Plugins( new WPSEO_Plugin_Availability(), Yoast_Notification_Center::get() );
|
||||
$suggestions->register_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the Premium Upsell Admin Block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register_premium_upsell_admin_block() {
|
||||
if ( ! YoastSEO()->helpers->product->is_premium() ) {
|
||||
$upsell_block = new WPSEO_Premium_Upsell_Admin_Block( 'wpseo_admin_promo_footer' );
|
||||
$upsell_block->register_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See if we should start our XML Sitemaps Admin class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function load_xml_sitemaps_admin() {
|
||||
if ( WPSEO_Options::get( 'enable_xml_sitemap', false, [ 'wpseo' ] ) ) {
|
||||
new WPSEO_Sitemaps_Admin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows deprecation warnings to the user if a plugin has registered a filter we have deprecated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show_hook_deprecation_warnings() {
|
||||
global $wp_filter;
|
||||
|
||||
if ( wp_doing_ajax() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// WordPress hooks that have been deprecated since a Yoast SEO version.
|
||||
$deprecated_filters = [
|
||||
'wpseo_genesis_force_adjacent_rel_home' => [
|
||||
'version' => '9.4',
|
||||
'alternative' => null,
|
||||
],
|
||||
'wpseo_opengraph' => [
|
||||
'version' => '14.0',
|
||||
'alternative' => null,
|
||||
],
|
||||
'wpseo_twitter' => [
|
||||
'version' => '14.0',
|
||||
'alternative' => null,
|
||||
],
|
||||
'wpseo_twitter_taxonomy_image' => [
|
||||
'version' => '14.0',
|
||||
'alternative' => null,
|
||||
],
|
||||
'wpseo_twitter_metatag_key' => [
|
||||
'version' => '14.0',
|
||||
'alternative' => null,
|
||||
],
|
||||
'wp_seo_get_bc_ancestors' => [
|
||||
'version' => '14.0',
|
||||
'alternative' => 'wpseo_breadcrumb_links',
|
||||
],
|
||||
'validate_facebook_app_id_api_response_code' => [
|
||||
'version' => '15.5',
|
||||
'alternative' => null,
|
||||
],
|
||||
'validate_facebook_app_id_api_response_body' => [
|
||||
'version' => '15.5',
|
||||
'alternative' => null,
|
||||
],
|
||||
];
|
||||
|
||||
// Determine which filters have been registered.
|
||||
$deprecated_notices = array_intersect(
|
||||
array_keys( $deprecated_filters ),
|
||||
array_keys( $wp_filter )
|
||||
);
|
||||
|
||||
// Show notice for each deprecated filter or action that has been registered.
|
||||
foreach ( $deprecated_notices as $deprecated_filter ) {
|
||||
$deprecation_info = $deprecated_filters[ $deprecated_filter ];
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Only uses the hardcoded values from above.
|
||||
_deprecated_hook(
|
||||
$deprecated_filter,
|
||||
'WPSEO ' . $deprecation_info['version'],
|
||||
$deprecation_info['alternative']
|
||||
);
|
||||
// phpcs:enable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the permalink uses %postname%.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function has_postname_in_permalink() {
|
||||
return ( strpos( get_option( 'permalink_structure' ), '%postname%' ) !== false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notice on the permalink settings page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function permalink_settings_notice() {
|
||||
global $pagenow;
|
||||
|
||||
if ( $pagenow === 'options-permalink.php' ) {
|
||||
printf(
|
||||
'<div class="notice notice-warning"><p><strong>%1$s</strong><br>%2$s<br><a href="%3$s" target="_blank">%4$s</a></p></div>',
|
||||
esc_html__( 'WARNING:', 'wordpress-seo' ),
|
||||
sprintf(
|
||||
/* translators: %1$s and %2$s expand to <em> items to emphasize the word in the middle. */
|
||||
esc_html__( 'Changing your permalinks settings can seriously impact your search engine visibility. It should almost %1$s never %2$s be done on a live website.', 'wordpress-seo' ),
|
||||
'<em>',
|
||||
'</em>'
|
||||
),
|
||||
esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/why-permalinks/' ) ),
|
||||
// The link's content.
|
||||
esc_html__( 'Learn about why permalinks are important for SEO.', 'wordpress-seo' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom Yoast section within the Classic Editor publish box.
|
||||
*
|
||||
* @param WP_Post $post The current post object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_publish_box_section( $post ) {
|
||||
if ( in_array( $this->pagenow, [ 'post.php', 'post-new.php' ], true ) ) {
|
||||
?>
|
||||
<div id="yoast-seo-publishbox-section"></div>
|
||||
<?php
|
||||
/**
|
||||
* Fires after the post time/date setting in the Publish meta box.
|
||||
*
|
||||
* @param WP_Post $post The current post object.
|
||||
*/
|
||||
do_action( 'wpseo_publishbox_misc_actions', $post );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?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.
|
||||
*
|
||||
* @param 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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( '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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @deprecated 22.6
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $user_id User ID of the updated user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_user_option_update( $user_id ) {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 22.6' );
|
||||
|
||||
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.
|
||||
*
|
||||
* @deprecated 23.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_User $user User instance to output for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function user_profile( $user ) {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.4' );
|
||||
wp_nonce_field( 'wpseo_user_profile_update', 'wpseo_nonce' );
|
||||
|
||||
require_once WPSEO_PATH . 'admin/views/user-profile.php';
|
||||
}
|
||||
}
|
||||
82
wp-content/plugins/wordpress-seo/admin/class-admin-utils.php
Normal file
82
wp-content/plugins/wordpress-seo/admin/class-admin-utils.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?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>',
|
||||
/* translators: Hidden accessibility text. */
|
||||
esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' )
|
||||
);
|
||||
}
|
||||
}
|
||||
391
wp-content/plugins/wordpress-seo/admin/class-admin.php
Normal file
391
wp-content/plugins/wordpress-seo/admin/class-admin.php
Normal file
@@ -0,0 +1,391 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Integrations\Settings_Integration;
|
||||
|
||||
/**
|
||||
* Class that holds most of the admin functionality for Yoast SEO.
|
||||
*/
|
||||
class WPSEO_Admin {
|
||||
|
||||
/**
|
||||
* The page identifier used in WordPress to register the admin page.
|
||||
*
|
||||
* !DO NOT CHANGE THIS!
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PAGE_IDENTIFIER = 'wpseo_dashboard';
|
||||
|
||||
/**
|
||||
* Array of classes that add admin functionality.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $admin_features;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$integrations = [];
|
||||
|
||||
global $pagenow;
|
||||
|
||||
$wpseo_menu = new WPSEO_Menu();
|
||||
$wpseo_menu->register_hooks();
|
||||
|
||||
if ( is_multisite() ) {
|
||||
WPSEO_Options::maybe_set_multisite_defaults( false );
|
||||
}
|
||||
|
||||
add_action( 'created_category', [ $this, 'schedule_rewrite_flush' ] );
|
||||
add_action( 'edited_category', [ $this, 'schedule_rewrite_flush' ] );
|
||||
add_action( 'delete_category', [ $this, 'schedule_rewrite_flush' ] );
|
||||
|
||||
add_filter( 'wpseo_accessible_post_types', [ 'WPSEO_Post_Type', 'filter_attachment_post_type' ] );
|
||||
|
||||
add_filter( 'plugin_action_links_' . WPSEO_BASENAME, [ $this, 'add_action_link' ], 10, 2 );
|
||||
add_filter( 'network_admin_plugin_action_links_' . WPSEO_BASENAME, [ $this, 'add_action_link' ], 10, 2 );
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'config_page_scripts' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_global_style' ] );
|
||||
|
||||
add_action( 'after_switch_theme', [ $this, 'switch_theme' ] );
|
||||
add_action( 'switch_theme', [ $this, 'switch_theme' ] );
|
||||
|
||||
add_filter( 'set-screen-option', [ $this, 'save_bulk_edit_options' ], 10, 3 );
|
||||
|
||||
add_action( 'admin_init', [ 'WPSEO_Plugin_Conflict', 'hook_check_for_plugin_conflicts' ], 10, 1 );
|
||||
|
||||
add_action( 'admin_init', [ $this, 'map_manage_options_cap' ] );
|
||||
|
||||
WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'wpseo' );
|
||||
WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'home' );
|
||||
|
||||
if ( YoastSEO()->helpers->current_page->is_yoast_seo_page() ) {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
$this->initialize_cornerstone_content();
|
||||
|
||||
if ( WPSEO_Utils::is_plugin_network_active() ) {
|
||||
$integrations[] = new Yoast_Network_Admin();
|
||||
}
|
||||
|
||||
$this->admin_features = [
|
||||
'dashboard_widget' => new Yoast_Dashboard_Widget(),
|
||||
'wincher_dashboard_widget' => new Wincher_Dashboard_Widget(),
|
||||
];
|
||||
|
||||
if ( WPSEO_Metabox::is_post_overview( $pagenow ) || WPSEO_Metabox::is_post_edit( $pagenow ) ) {
|
||||
$this->admin_features['primary_category'] = new WPSEO_Primary_Term_Admin();
|
||||
}
|
||||
|
||||
$integrations[] = new WPSEO_Yoast_Columns();
|
||||
$integrations[] = new WPSEO_Statistic_Integration();
|
||||
$integrations[] = new WPSEO_Capability_Manager_Integration( WPSEO_Capability_Manager_Factory::get() );
|
||||
$integrations[] = new WPSEO_Admin_Gutenberg_Compatibility_Notification();
|
||||
$integrations[] = new WPSEO_Expose_Shortlinks();
|
||||
$integrations[] = new WPSEO_MyYoast_Proxy();
|
||||
$integrations[] = new WPSEO_Schema_Person_Upgrade_Notification();
|
||||
$integrations[] = new WPSEO_Tracking( 'https://tracking.yoast.com/stats', ( WEEK_IN_SECONDS * 2 ) );
|
||||
$integrations[] = new WPSEO_Admin_Settings_Changed_Listener();
|
||||
|
||||
$integrations = array_merge(
|
||||
$integrations,
|
||||
$this->get_admin_features(),
|
||||
$this->initialize_cornerstone_content()
|
||||
);
|
||||
|
||||
foreach ( $integrations as $integration ) {
|
||||
$integration->register_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a rewrite flush to happen at shutdown.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function schedule_rewrite_flush() {
|
||||
if ( WPSEO_Options::get( 'stripcategorybase' ) !== true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bail if this is a multisite installation and the site has been switched.
|
||||
if ( is_multisite() && ms_is_switched() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'shutdown', 'flush_rewrite_rules' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the classes for the admin features.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_admin_features() {
|
||||
return $this->admin_features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register assets needed on admin pages.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form data.
|
||||
$page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
|
||||
if ( $page === 'wpseo_licenses' ) {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_style( 'extensions' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the manage_options capability.
|
||||
*
|
||||
* @return string The capability to use.
|
||||
*/
|
||||
public function get_manage_options_cap() {
|
||||
/**
|
||||
* Filter: 'wpseo_manage_options_capability' - Allow changing the capability users need to view the settings pages.
|
||||
*
|
||||
* @param string $capability The capability.
|
||||
*/
|
||||
return apply_filters( 'wpseo_manage_options_capability', 'wpseo_manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the manage_options cap on saving an options page to wpseo_manage_options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function map_manage_options_cap() {
|
||||
// phpcs:ignore WordPress.Security -- The variable is only used in strpos and thus safe to not unslash or sanitize.
|
||||
$option_page = ! empty( $_POST['option_page'] ) ? $_POST['option_page'] : '';
|
||||
|
||||
if ( strpos( $option_page, 'yoast_wpseo' ) === 0 || strpos( $option_page, Settings_Integration::PAGE ) === 0 ) {
|
||||
add_filter( 'option_page_capability_' . $option_page, [ $this, 'get_manage_options_cap' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the ability to choose how many posts are displayed per page
|
||||
* on the bulk edit pages.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function bulk_edit_options() {
|
||||
$option = 'per_page';
|
||||
$args = [
|
||||
'label' => __( 'Posts', 'wordpress-seo' ),
|
||||
'default' => 10,
|
||||
'option' => 'wpseo_posts_per_page',
|
||||
];
|
||||
add_screen_option( $option, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the posts per page limit for bulk edit pages.
|
||||
*
|
||||
* @param int $status Status value to pass through.
|
||||
* @param string $option Option name.
|
||||
* @param int $value Count value to check.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function save_bulk_edit_options( $status, $option, $value ) {
|
||||
if ( $option && ( $value > 0 && $value < 1000 ) === 'wpseo_posts_per_page' ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds links to Premium Support and FAQ under the plugin in the plugin overview page.
|
||||
*
|
||||
* @param array $links Array of links for the plugins, adapted when the current plugin is found.
|
||||
* @param string $file The filename for the current plugin, which the filter loops through.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_action_link( $links, $file ) {
|
||||
$first_time_configuration_notice_helper = YoastSEO()->helpers->first_time_configuration_notice;
|
||||
|
||||
if ( $file === WPSEO_BASENAME && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
|
||||
if ( is_network_admin() ) {
|
||||
$settings_url = network_admin_url( 'admin.php?page=' . self::PAGE_IDENTIFIER );
|
||||
}
|
||||
else {
|
||||
$settings_url = admin_url( 'admin.php?page=' . self::PAGE_IDENTIFIER );
|
||||
}
|
||||
$settings_link = '<a href="' . esc_url( $settings_url ) . '">' . __( 'Settings', 'wordpress-seo' ) . '</a>';
|
||||
array_unshift( $links, $settings_link );
|
||||
}
|
||||
|
||||
// Add link to docs.
|
||||
$faq_link = '<a href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/1yc' ) ) . '" target="_blank">' . __( 'FAQ', 'wordpress-seo' ) . '</a>';
|
||||
array_unshift( $links, $faq_link );
|
||||
|
||||
if ( $first_time_configuration_notice_helper->first_time_configuration_not_finished() && ! is_network_admin() ) {
|
||||
$configuration_title = ( ! $first_time_configuration_notice_helper->should_show_alternate_message() ) ? 'first-time configuration' : 'SEO configuration';
|
||||
/* translators: CTA to finish the first time configuration. %s: Either first-time SEO configuration or SEO configuration. */
|
||||
$message = sprintf( __( 'Finish your %s', 'wordpress-seo' ), $configuration_title );
|
||||
$ftc_page = 'admin.php?page=wpseo_dashboard#/first-time-configuration';
|
||||
$ftc_link = '<a href="' . esc_url( admin_url( $ftc_page ) ) . '" target="_blank">' . $message . '</a>';
|
||||
array_unshift( $links, $ftc_link );
|
||||
}
|
||||
|
||||
$addon_manager = new WPSEO_Addon_Manager();
|
||||
if ( YoastSEO()->helpers->product->is_premium() ) {
|
||||
|
||||
// Remove Free 'deactivate' link if Premium is active as well. We don't want users to deactivate Free when Premium is active.
|
||||
unset( $links['deactivate'] );
|
||||
$no_deactivation_explanation = '<span style="color: #32373c">' . sprintf(
|
||||
/* translators: %s expands to Yoast SEO Premium. */
|
||||
__( 'Required by %s', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium'
|
||||
) . '</span>';
|
||||
|
||||
array_unshift( $links, $no_deactivation_explanation );
|
||||
|
||||
if ( $addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
// Add link to where premium can be activated.
|
||||
$activation_link = '<a style="font-weight: bold;" href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/activate-my-yoast' ) ) . '" target="_blank">' . __( 'Activate your subscription', 'wordpress-seo' ) . '</a>';
|
||||
array_unshift( $links, $activation_link );
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
// Add link to premium landing page.
|
||||
$premium_link = '<a style="font-weight: bold;" href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/1yb' ) ) . '" target="_blank" data-action="load-nfd-ctb" data-ctb-id="f6a84663-465f-4cb5-8ba5-f7a6d72224b2">' . __( 'Get Premium', 'wordpress-seo' ) . '</a>';
|
||||
array_unshift( $links, $premium_link );
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the (tiny) global JS needed for the plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function config_page_scripts() {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_script( 'admin-global' );
|
||||
$asset_manager->localize_script( 'admin-global', 'wpseoAdminGlobalL10n', $this->localize_admin_global_script() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the (tiny) global stylesheet needed for the plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_global_style() {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_style( 'admin-global' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the $contactmethods array and add a set of social profiles.
|
||||
*
|
||||
* These are used with the Facebook author, rel="author" and Twitter cards implementation.
|
||||
*
|
||||
* @deprecated 22.6
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array<string, string> $contactmethods Currently set contactmethods.
|
||||
*
|
||||
* @return array<string, string> Contactmethods with added contactmethods.
|
||||
*/
|
||||
public function update_contactmethods( $contactmethods ) {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 22.6' );
|
||||
|
||||
$contactmethods['facebook'] = __( 'Facebook profile URL', 'wordpress-seo' );
|
||||
$contactmethods['instagram'] = __( 'Instagram profile URL', 'wordpress-seo' );
|
||||
$contactmethods['linkedin'] = __( 'LinkedIn profile URL', 'wordpress-seo' );
|
||||
$contactmethods['myspace'] = __( 'MySpace profile URL', 'wordpress-seo' );
|
||||
$contactmethods['pinterest'] = __( 'Pinterest profile URL', 'wordpress-seo' );
|
||||
$contactmethods['soundcloud'] = __( 'SoundCloud profile URL', 'wordpress-seo' );
|
||||
$contactmethods['tumblr'] = __( 'Tumblr profile URL', 'wordpress-seo' );
|
||||
$contactmethods['twitter'] = __( 'X username (without @)', 'wordpress-seo' );
|
||||
$contactmethods['youtube'] = __( 'YouTube profile URL', 'wordpress-seo' );
|
||||
$contactmethods['wikipedia'] = __( 'Wikipedia page about you', 'wordpress-seo' ) . '<br/><small>' . __( '(if one exists)', 'wordpress-seo' ) . '</small>';
|
||||
|
||||
return $contactmethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the updated timestamp for user profiles when theme is changed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function switch_theme() {
|
||||
|
||||
$users = get_users( [ 'capability' => [ 'edit_posts' ] ] );
|
||||
|
||||
if ( is_array( $users ) && $users !== [] ) {
|
||||
foreach ( $users as $user ) {
|
||||
update_user_meta( $user->ID, '_yoast_wpseo_profile_updated', time() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Localization for the dismiss urls.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function localize_admin_global_script() {
|
||||
return array_merge(
|
||||
[
|
||||
'isRtl' => is_rtl(),
|
||||
'variable_warning' => sprintf(
|
||||
/* translators: %1$s: '%%term_title%%' variable used in titles and meta's template that's not compatible with the given template, %2$s: expands to 'HelpScout beacon' */
|
||||
__( 'Warning: the variable %1$s cannot be used in this template. See the %2$s for more info.', 'wordpress-seo' ),
|
||||
'<code>%s</code>',
|
||||
'HelpScout beacon'
|
||||
),
|
||||
/* translators: %s: expends to Yoast SEO */
|
||||
'help_video_iframe_title' => sprintf( __( '%s video tutorial', 'wordpress-seo' ), 'Yoast SEO' ),
|
||||
'scrollable_table_hint' => __( 'Scroll to see the table content.', 'wordpress-seo' ),
|
||||
'wincher_is_logged_in' => WPSEO_Options::get( 'wincher_integration_active', true ) ? YoastSEO()->helpers->wincher->login_status() : false,
|
||||
],
|
||||
YoastSEO()->helpers->wincher->get_admin_global_links()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we are on the admin dashboard page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function on_dashboard_page() {
|
||||
return $GLOBALS['pagenow'] === 'index.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the cornerstone filter.
|
||||
*
|
||||
* @return WPSEO_WordPress_Integration[] The integrations to initialize.
|
||||
*/
|
||||
protected function initialize_cornerstone_content() {
|
||||
if ( ! WPSEO_Options::get( 'enable_cornerstone_content' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'cornerstone_filter' => new WPSEO_Cornerstone_Filter(),
|
||||
];
|
||||
}
|
||||
}
|
||||
273
wp-content/plugins/wordpress-seo/admin/class-asset.php
Normal file
273
wp-content/plugins/wordpress-seo/admin/class-asset.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<?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
|
||||
*/
|
||||
public const TYPE_JS = 'js';
|
||||
|
||||
/**
|
||||
* Constant used to identify file type as a CSS file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_CSS = 'css';
|
||||
|
||||
/**
|
||||
* The name option identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const NAME = 'name';
|
||||
|
||||
/**
|
||||
* The source option identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const SRC = 'src';
|
||||
|
||||
/**
|
||||
* The dependencies option identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const DEPS = 'deps';
|
||||
|
||||
/**
|
||||
* The version option identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const VERSION = 'version';
|
||||
|
||||
/* Style specific. */
|
||||
|
||||
/**
|
||||
* The media option identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MEDIA = 'media';
|
||||
|
||||
/**
|
||||
* The rtl option identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const RTL = 'rtl';
|
||||
|
||||
/* Script specific. */
|
||||
|
||||
/**
|
||||
* The "in footer" option identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public 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 JS Assets. The script's async/defer strategy.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $strategy;
|
||||
|
||||
/**
|
||||
* 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' => '',
|
||||
'strategy' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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->strategy = $args['strategy'];
|
||||
$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 the script asset's async/defer loading strategy.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_strategy() {
|
||||
return $this->strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
54
wp-content/plugins/wordpress-seo/admin/class-collector.php
Normal file
54
wp-content/plugins/wordpress-seo/admin/class-collector.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 string|false The encode string.
|
||||
*/
|
||||
public function get_as_json() {
|
||||
return WPSEO_Utils::format_json_encode( $this->collect() );
|
||||
}
|
||||
}
|
||||
155
wp-content/plugins/wordpress-seo/admin/class-config.php
Normal file
155
wp-content/plugins/wordpress-seo/admin/class-config.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Actions\Alert_Dismissal_Action;
|
||||
use Yoast\WP\SEO\General\User_Interface\General_Page_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Academy_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Settings_Integration;
|
||||
use Yoast\WP\SEO\Integrations\Support_Integration;
|
||||
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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'] ) ) : '';
|
||||
|
||||
// Don't load the scripts for the following pages.
|
||||
$page_exceptions = in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE ], true );
|
||||
$new_dashboard_page = ( $page === General_Page_Integration::PAGE && ! is_network_admin() );
|
||||
if ( $page_exceptions || $new_dashboard_page ) {
|
||||
// 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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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( Alert_Dismissal_Action::class );
|
||||
$dismissed_alerts = $alert_dismissal_action->all_dismissed();
|
||||
|
||||
$script_data = [
|
||||
'dismissedAlerts' => $dismissed_alerts,
|
||||
'isRtl' => is_rtl(),
|
||||
'isPremium' => YoastSEO()->helpers->product->is_premium(),
|
||||
'currentPromotions' => YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(),
|
||||
'webinarIntroFirstTimeConfigUrl' => $this->get_webinar_shortlink(),
|
||||
'linkParams' => WPSEO_Shortlinker::get_query_params(),
|
||||
'pluginUrl' => plugins_url( '', WPSEO_FILE ),
|
||||
];
|
||||
|
||||
// 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['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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues and handles all the tool dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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' );
|
||||
}
|
||||
}
|
||||
309
wp-content/plugins/wordpress-seo/admin/class-database-proxy.php
Normal file
309
wp-content/plugins/wordpress-seo/admin/class-database-proxy.php
Normal file
@@ -0,0 +1,309 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Holds the table prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table_prefix;
|
||||
|
||||
/**
|
||||
* 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 int|false 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 int|false 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 int|false 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 int|false
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function pre_execution() {
|
||||
if ( $this->suppress_errors ) {
|
||||
$this->last_suppressed_state = $this->database->suppress_errors();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed after a query has been ran.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
164
wp-content/plugins/wordpress-seo/admin/class-export.php
Normal file
164
wp-content/plugins/wordpress-seo/admin/class-export.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?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
|
||||
*/
|
||||
public 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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function export() {
|
||||
check_admin_referer( self::NONCE_ACTION );
|
||||
$this->export_settings();
|
||||
$this->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the export.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 = ( $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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function write_setting( $key, $val ) {
|
||||
if ( is_string( $val ) ) {
|
||||
$val = '"' . $val . '"';
|
||||
}
|
||||
$this->write_line( $key . ' = ' . $val );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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.social' => 'https://yoa.st/social-preview-facebook',
|
||||
'shortlinks.upsell.social_preview.x' => 'https://yoa.st/social-preview-twitter',
|
||||
'shortlinks.upsell.sidebar.news' => 'https://yoa.st/get-news-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_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.sidebar.internal_linking_suggestions' => 'https://yoa.st/internal-linking-suggestions-sidebar',
|
||||
'shortlinks.upsell.sidebar.highlighting_seo_analysis' => 'https://yoa.st/highlighting-seo-analysis',
|
||||
'shortlinks.upsell.sidebar.highlighting_readability_analysis' => 'https://yoa.st/highlighting-readability-analysis',
|
||||
'shortlinks.upsell.sidebar.highlighting_inclusive_analysis' => 'https://yoa.st/highlighting-inclusive-analysis',
|
||||
'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_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_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.metabox.internal_linking_suggestions' => 'https://yoa.st/internal-linking-suggestions-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.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_button'] = 'https://yoa.st/keyword-synonyms-popup-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';
|
||||
$shortlinks['shortlinks.upsell.metabox.internal_linking_suggestions'] = 'https://yoa.st/internal-linking-suggestions-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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Gutenberg_Compatibility
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Gutenberg_Compatibility
|
||||
*/
|
||||
class WPSEO_Gutenberg_Compatibility {
|
||||
|
||||
/**
|
||||
* The currently released version of Gutenberg.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CURRENT_RELEASE = '21.0.0';
|
||||
|
||||
/**
|
||||
* The minimally supported version of Gutenberg by the plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MINIMUM_SUPPORTED = '21.0.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 '';
|
||||
}
|
||||
}
|
||||
912
wp-content/plugins/wordpress-seo/admin/class-meta-columns.php
Normal file
912
wp-content/plugins/wordpress-seo/admin/class-meta-columns.php
Normal file
@@ -0,0 +1,912 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Context\Meta_Tags_Context;
|
||||
use Yoast\WP\SEO\Helpers\Score_Icon_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Admin\Admin_Columns_Cache_Integration;
|
||||
use Yoast\WP\SEO\Surfaces\Values\Meta;
|
||||
|
||||
/**
|
||||
* Class WPSEO_Meta_Columns.
|
||||
*/
|
||||
class WPSEO_Meta_Columns {
|
||||
|
||||
/**
|
||||
* Holds the context objects for each indexable.
|
||||
*
|
||||
* @var Meta_Tags_Context[]
|
||||
*/
|
||||
protected $context = [];
|
||||
|
||||
/**
|
||||
* Holds the SEO analysis.
|
||||
*
|
||||
* @var WPSEO_Metabox_Analysis_SEO
|
||||
*/
|
||||
private $analysis_seo;
|
||||
|
||||
/**
|
||||
* Holds the readability analysis.
|
||||
*
|
||||
* @var WPSEO_Metabox_Analysis_Readability
|
||||
*/
|
||||
private $analysis_readability;
|
||||
|
||||
/**
|
||||
* Admin columns cache.
|
||||
*
|
||||
* @var Admin_Columns_Cache_Integration
|
||||
*/
|
||||
private $admin_columns_cache;
|
||||
|
||||
/**
|
||||
* Holds the Score_Icon_Helper.
|
||||
*
|
||||
* @var Score_Icon_Helper
|
||||
*/
|
||||
private $score_icon_helper;
|
||||
|
||||
/**
|
||||
* Holds the WPSEO_Admin_Asset_Manager instance.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
private $admin_asset_manager;
|
||||
|
||||
/**
|
||||
* When page analysis is enabled, just initialize the hooks.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( apply_filters( 'wpseo_use_page_analysis', true ) === true ) {
|
||||
add_action( 'admin_init', [ $this, 'setup_hooks' ] );
|
||||
}
|
||||
|
||||
$this->analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$this->analysis_readability = new WPSEO_Metabox_Analysis_Readability();
|
||||
$this->admin_columns_cache = YoastSEO()->classes->get( Admin_Columns_Cache_Integration::class );
|
||||
$this->score_icon_helper = YoastSEO()->helpers->score_icon;
|
||||
$this->admin_asset_manager = YoastSEO()->classes->get( WPSEO_Admin_Asset_Manager::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up up the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_hooks() {
|
||||
$this->set_post_type_hooks();
|
||||
|
||||
if ( $this->analysis_seo->is_enabled() ) {
|
||||
add_action( 'restrict_manage_posts', [ $this, 'posts_filter_dropdown' ] );
|
||||
}
|
||||
|
||||
if ( $this->analysis_readability->is_enabled() ) {
|
||||
add_action( 'restrict_manage_posts', [ $this, 'posts_filter_dropdown_readability' ] );
|
||||
}
|
||||
|
||||
add_filter( 'request', [ $this, 'column_sort_orderby' ] );
|
||||
add_filter( 'default_hidden_columns', [ $this, 'column_hidden' ], 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the column headings for the SEO plugin for edit posts / pages overview.
|
||||
*
|
||||
* @param array $columns Already existing columns.
|
||||
*
|
||||
* @return array Array containing the column headings.
|
||||
*/
|
||||
public function column_heading( $columns ) {
|
||||
if ( $this->display_metabox() === false ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$this->admin_asset_manager->enqueue_script( 'edit-page' );
|
||||
$this->admin_asset_manager->enqueue_style( 'edit-page' );
|
||||
|
||||
$added_columns = [];
|
||||
|
||||
if ( $this->analysis_seo->is_enabled() ) {
|
||||
$added_columns['wpseo-score'] = '<span class="yoast-column-seo-score yoast-column-header-has-tooltip" data-tooltip-text="'
|
||||
. esc_attr__( 'SEO score', 'wordpress-seo' )
|
||||
. '"><span class="screen-reader-text">'
|
||||
. __( 'SEO score', 'wordpress-seo' )
|
||||
. '</span></span>';
|
||||
}
|
||||
|
||||
if ( $this->analysis_readability->is_enabled() ) {
|
||||
$added_columns['wpseo-score-readability'] = '<span class="yoast-column-readability yoast-column-header-has-tooltip" data-tooltip-text="'
|
||||
. esc_attr__( 'Readability score', 'wordpress-seo' )
|
||||
. '"><span class="screen-reader-text">'
|
||||
. __( 'Readability score', 'wordpress-seo' )
|
||||
. '</span></span>';
|
||||
}
|
||||
|
||||
$added_columns['wpseo-title'] = __( 'SEO Title', 'wordpress-seo' );
|
||||
$added_columns['wpseo-metadesc'] = __( 'Meta Desc.', 'wordpress-seo' );
|
||||
|
||||
if ( $this->analysis_seo->is_enabled() ) {
|
||||
$added_columns['wpseo-focuskw'] = __( 'Keyphrase', 'wordpress-seo' );
|
||||
}
|
||||
|
||||
return array_merge( $columns, $added_columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the column content for the given column.
|
||||
*
|
||||
* @param string $column_name Column to display the content for.
|
||||
* @param int $post_id Post to display the column content for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function column_content( $column_name, $post_id ) {
|
||||
if ( $this->display_metabox() === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( $column_name ) {
|
||||
case 'wpseo-score':
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in render_score_indicator() method.
|
||||
echo $this->parse_column_score( $post_id );
|
||||
|
||||
return;
|
||||
|
||||
case 'wpseo-score-readability':
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in render_score_indicator() method.
|
||||
echo $this->parse_column_score_readability( $post_id );
|
||||
|
||||
return;
|
||||
|
||||
case 'wpseo-title':
|
||||
$meta = $this->get_meta( $post_id );
|
||||
if ( $meta ) {
|
||||
echo esc_html( $meta->title );
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
case 'wpseo-metadesc':
|
||||
$metadesc_val = '';
|
||||
$meta = $this->get_meta( $post_id );
|
||||
if ( $meta ) {
|
||||
$metadesc_val = $meta->meta_description;
|
||||
}
|
||||
if ( $metadesc_val === '' ) {
|
||||
echo '<span aria-hidden="true">—</span><span class="screen-reader-text">',
|
||||
/* translators: Hidden accessibility text. */
|
||||
esc_html__( 'Meta description not set.', 'wordpress-seo' ),
|
||||
'</span>';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo esc_html( $metadesc_val );
|
||||
|
||||
return;
|
||||
|
||||
case 'wpseo-focuskw':
|
||||
$focuskw_val = WPSEO_Meta::get_value( 'focuskw', $post_id );
|
||||
|
||||
if ( $focuskw_val === '' ) {
|
||||
echo '<span aria-hidden="true">—</span><span class="screen-reader-text">',
|
||||
/* translators: Hidden accessibility text. */
|
||||
esc_html__( 'Focus keyphrase not set.', 'wordpress-seo' ),
|
||||
'</span>';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo esc_html( $focuskw_val );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates which of the SEO columns are sortable.
|
||||
*
|
||||
* @param array $columns Appended with their orderby variable.
|
||||
*
|
||||
* @return array Array containing the sortable columns.
|
||||
*/
|
||||
public function column_sort( $columns ) {
|
||||
if ( $this->display_metabox() === false ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$columns['wpseo-metadesc'] = 'wpseo-metadesc';
|
||||
|
||||
if ( $this->analysis_seo->is_enabled() ) {
|
||||
$columns['wpseo-focuskw'] = 'wpseo-focuskw';
|
||||
$columns['wpseo-score'] = 'wpseo-score';
|
||||
}
|
||||
|
||||
if ( $this->analysis_readability->is_enabled() ) {
|
||||
$columns['wpseo-score-readability'] = 'wpseo-score-readability';
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the SEO title, meta description and focus keyword columns if the user hasn't chosen which columns to hide.
|
||||
*
|
||||
* @param array $hidden The hidden columns.
|
||||
*
|
||||
* @return array Array containing the columns to hide.
|
||||
*/
|
||||
public function column_hidden( $hidden ) {
|
||||
if ( ! is_array( $hidden ) ) {
|
||||
$hidden = [];
|
||||
}
|
||||
|
||||
array_push( $hidden, 'wpseo-title', 'wpseo-metadesc' );
|
||||
|
||||
if ( $this->analysis_seo->is_enabled() ) {
|
||||
$hidden[] = 'wpseo-focuskw';
|
||||
}
|
||||
|
||||
return $hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dropdown that allows filtering on the posts SEO Quality.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function posts_filter_dropdown() {
|
||||
if ( ! $this->can_display_filter() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ranks = WPSEO_Rank::get_all_ranks();
|
||||
|
||||
/* translators: Hidden accessibility text. */
|
||||
echo '<label class="screen-reader-text" for="wpseo-filter">' . esc_html__( 'Filter by SEO Score', 'wordpress-seo' ) . '</label>';
|
||||
echo '<select name="seo_filter" id="wpseo-filter">';
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
|
||||
echo $this->generate_option( '', __( 'All SEO Scores', 'wordpress-seo' ) );
|
||||
|
||||
foreach ( $ranks as $rank ) {
|
||||
$selected = selected( $this->get_current_seo_filter(), $rank->get_rank(), false );
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
|
||||
echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_label(), $selected );
|
||||
}
|
||||
|
||||
echo '</select>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dropdown that allows filtering on the posts Readability Quality.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function posts_filter_dropdown_readability() {
|
||||
if ( ! $this->can_display_filter() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ranks = WPSEO_Rank::get_all_readability_ranks();
|
||||
|
||||
/* translators: Hidden accessibility text. */
|
||||
echo '<label class="screen-reader-text" for="wpseo-readability-filter">' . esc_html__( 'Filter by Readability Score', 'wordpress-seo' ) . '</label>';
|
||||
echo '<select name="readability_filter" id="wpseo-readability-filter">';
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
|
||||
echo $this->generate_option( '', __( 'All Readability Scores', 'wordpress-seo' ) );
|
||||
|
||||
foreach ( $ranks as $rank ) {
|
||||
$selected = selected( $this->get_current_readability_filter(), $rank->get_rank(), false );
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
|
||||
echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_readability_labels(), $selected );
|
||||
}
|
||||
|
||||
echo '</select>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an <option> element.
|
||||
*
|
||||
* @param string $value The option's value.
|
||||
* @param string $label The option's label.
|
||||
* @param string $selected HTML selected attribute for an option.
|
||||
*
|
||||
* @return string The generated <option> element.
|
||||
*/
|
||||
protected function generate_option( $value, $label, $selected = '' ) {
|
||||
return '<option ' . $selected . ' value="' . esc_attr( $value ) . '">' . esc_html( $label ) . '</option>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the meta object for a given post ID.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*
|
||||
* @return Meta The meta object.
|
||||
*/
|
||||
protected function get_meta( $post_id ) {
|
||||
$indexable = $this->admin_columns_cache->get_indexable( $post_id );
|
||||
|
||||
return YoastSEO()->meta->for_indexable( $indexable, 'Post_Type' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the SEO score filter to be later used in the meta query, based on the passed SEO filter.
|
||||
*
|
||||
* @param string $seo_filter The SEO filter to use to determine what further filter to apply.
|
||||
*
|
||||
* @return array The SEO score filter.
|
||||
*/
|
||||
protected function determine_seo_filters( $seo_filter ) {
|
||||
if ( $seo_filter === WPSEO_Rank::NO_FOCUS ) {
|
||||
return $this->create_no_focus_keyword_filter();
|
||||
}
|
||||
|
||||
if ( $seo_filter === WPSEO_Rank::NO_INDEX ) {
|
||||
return $this->create_no_index_filter();
|
||||
}
|
||||
|
||||
$rank = new WPSEO_Rank( $seo_filter );
|
||||
|
||||
return $this->create_seo_score_filter( $rank->get_starting_score(), $rank->get_end_score() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the Readability score filter to the meta query, based on the passed Readability filter.
|
||||
*
|
||||
* @param string $readability_filter The Readability filter to use to determine what further filter to apply.
|
||||
*
|
||||
* @return array The Readability score filter.
|
||||
*/
|
||||
protected function determine_readability_filters( $readability_filter ) {
|
||||
if ( $readability_filter === WPSEO_Rank::NO_FOCUS ) {
|
||||
return $this->create_no_readability_scores_filter();
|
||||
}
|
||||
if ( $readability_filter === WPSEO_Rank::BAD ) {
|
||||
return $this->create_bad_readability_scores_filter();
|
||||
}
|
||||
$rank = new WPSEO_Rank( $readability_filter );
|
||||
|
||||
return $this->create_readability_score_filter( $rank->get_starting_score(), $rank->get_end_score() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a keyword filter for the meta query, based on the passed Keyword filter.
|
||||
*
|
||||
* @param string $keyword_filter The keyword filter to use.
|
||||
*
|
||||
* @return array The keyword filter.
|
||||
*/
|
||||
protected function get_keyword_filter( $keyword_filter ) {
|
||||
return [
|
||||
'post_type' => get_query_var( 'post_type', 'post' ),
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'focuskw',
|
||||
'value' => sanitize_text_field( $keyword_filter ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the passed filter is considered to be valid.
|
||||
*
|
||||
* @param mixed $filter The filter to check against.
|
||||
*
|
||||
* @return bool Whether the filter is considered valid.
|
||||
*/
|
||||
protected function is_valid_filter( $filter ) {
|
||||
return ! empty( $filter ) && is_string( $filter );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the filters and merges them into a single array.
|
||||
*
|
||||
* @return array Array containing all the applicable filters.
|
||||
*/
|
||||
protected function collect_filters() {
|
||||
$active_filters = [];
|
||||
|
||||
$seo_filter = $this->get_current_seo_filter();
|
||||
$readability_filter = $this->get_current_readability_filter();
|
||||
$current_keyword_filter = $this->get_current_keyword_filter();
|
||||
|
||||
if ( $this->is_valid_filter( $seo_filter ) ) {
|
||||
$active_filters = array_merge(
|
||||
$active_filters,
|
||||
$this->determine_seo_filters( $seo_filter )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->is_valid_filter( $readability_filter ) ) {
|
||||
$active_filters = array_merge(
|
||||
$active_filters,
|
||||
$this->determine_readability_filters( $readability_filter )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->is_valid_filter( $current_keyword_filter ) ) {
|
||||
/**
|
||||
* Adapt the meta query used to filter the post overview on keyphrase.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param array $keyphrase The keyphrase used in the filter.
|
||||
* @param array $keyword_filter The current keyword filter.
|
||||
*/
|
||||
$keyphrase_filter = apply_filters(
|
||||
'wpseo_change_keyphrase_filter_in_request',
|
||||
$this->get_keyword_filter( $current_keyword_filter ),
|
||||
$current_keyword_filter
|
||||
);
|
||||
|
||||
if ( is_array( $keyphrase_filter ) ) {
|
||||
$active_filters = array_merge(
|
||||
$active_filters,
|
||||
[ $keyphrase_filter ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt the active applicable filters on the posts overview.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param array $active_filters The current applicable filters.
|
||||
*/
|
||||
return apply_filters( 'wpseo_change_applicable_filters', $active_filters );
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the query based on the filters that are being passed.
|
||||
*
|
||||
* @param array $vars Query variables that need to be modified based on the filters.
|
||||
*
|
||||
* @return array Array containing the meta query to use for filtering the posts overview.
|
||||
*/
|
||||
public function column_sort_orderby( $vars ) {
|
||||
$collected_filters = $this->collect_filters();
|
||||
|
||||
$order_by_column = $vars['orderby'];
|
||||
if ( isset( $order_by_column ) ) {
|
||||
// Based on the selected column, create a meta query.
|
||||
$order_by = $this->filter_order_by( $order_by_column );
|
||||
|
||||
/**
|
||||
* Adapt the order by part of the query on the posts overview.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param array $order_by The current order by.
|
||||
* @param string $order_by_column The current order by column.
|
||||
*/
|
||||
$order_by = apply_filters( 'wpseo_change_order_by', $order_by, $order_by_column );
|
||||
|
||||
$vars = array_merge( $vars, $order_by );
|
||||
}
|
||||
|
||||
return $this->build_filter_query( $vars, $collected_filters );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the meta robots query values to be used within the meta query.
|
||||
*
|
||||
* @return array Array containing the query parameters regarding meta robots.
|
||||
*/
|
||||
protected function get_meta_robots_query_values() {
|
||||
return [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
|
||||
'value' => '1',
|
||||
'compare' => '!=',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the score filters to be used. If more than one is passed, it created an AND statement for the query.
|
||||
*
|
||||
* @param array $score_filters Array containing the score filters.
|
||||
*
|
||||
* @return array Array containing the score filters that need to be applied to the meta query.
|
||||
*/
|
||||
protected function determine_score_filters( $score_filters ) {
|
||||
if ( count( $score_filters ) > 1 ) {
|
||||
return array_merge( [ 'relation' => 'AND' ], $score_filters );
|
||||
}
|
||||
|
||||
return $score_filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post type from the $_GET variable.
|
||||
*
|
||||
* @return string|null The sanitized current post type or null when the variable is not set in $_GET.
|
||||
*/
|
||||
public function get_current_post_type() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['post_type'] ) && is_string( $_GET['post_type'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the SEO filter from the $_GET variable.
|
||||
*
|
||||
* @return string|null The sanitized seo filter or null when the variable is not set in $_GET.
|
||||
*/
|
||||
public function get_current_seo_filter() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['seo_filter'] ) && is_string( $_GET['seo_filter'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return sanitize_text_field( wp_unslash( $_GET['seo_filter'] ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Readability filter from the $_GET variable.
|
||||
*
|
||||
* @return string|null The sanitized readability filter or null when the variable is not set in $_GET.
|
||||
*/
|
||||
public function get_current_readability_filter() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['readability_filter'] ) && is_string( $_GET['readability_filter'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return sanitize_text_field( wp_unslash( $_GET['readability_filter'] ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the keyword filter from the $_GET variable.
|
||||
*
|
||||
* @return string|null The sanitized seo keyword filter or null when the variable is not set in $_GET.
|
||||
*/
|
||||
public function get_current_keyword_filter() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['seo_kw_filter'] ) && is_string( $_GET['seo_kw_filter'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return sanitize_text_field( wp_unslash( $_GET['seo_kw_filter'] ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the vars to create a complete filter query that can later be executed to filter out posts.
|
||||
*
|
||||
* @param array $vars Array containing the variables that will be used in the meta query.
|
||||
* @param array $filters Array containing the filters that we need to apply in the meta query.
|
||||
*
|
||||
* @return array Array containing the complete filter query.
|
||||
*/
|
||||
protected function build_filter_query( $vars, $filters ) {
|
||||
// If no filters were applied, just return everything.
|
||||
if ( count( $filters ) === 0 ) {
|
||||
return $vars;
|
||||
}
|
||||
|
||||
$result = [ 'meta_query' => [] ];
|
||||
$result['meta_query'] = array_merge( $result['meta_query'], [ $this->determine_score_filters( $filters ) ] );
|
||||
|
||||
$current_seo_filter = $this->get_current_seo_filter();
|
||||
|
||||
// This only applies for the SEO score filter because it can because the SEO score can be altered by the no-index option.
|
||||
if ( $this->is_valid_filter( $current_seo_filter ) && ! in_array( $current_seo_filter, [ WPSEO_Rank::NO_INDEX ], true ) ) {
|
||||
$result['meta_query'] = array_merge( $result['meta_query'], [ $this->get_meta_robots_query_values() ] );
|
||||
}
|
||||
|
||||
return array_merge( $vars, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Readability score filter.
|
||||
*
|
||||
* @param number $low The lower boundary of the score.
|
||||
* @param number $high The higher boundary of the score.
|
||||
*
|
||||
* @return array<array<string>> The Readability Score filter.
|
||||
*/
|
||||
protected function create_readability_score_filter( $low, $high ) {
|
||||
return [
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'content_score',
|
||||
'value' => [ $low, $high ],
|
||||
'type' => 'numeric',
|
||||
'compare' => 'BETWEEN',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SEO score filter.
|
||||
*
|
||||
* @param number $low The lower boundary of the score.
|
||||
* @param number $high The higher boundary of the score.
|
||||
*
|
||||
* @return array<array<string>> The SEO score filter.
|
||||
*/
|
||||
protected function create_seo_score_filter( $low, $high ) {
|
||||
return [
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'linkdex',
|
||||
'value' => [ $low, $high ],
|
||||
'type' => 'numeric',
|
||||
'compare' => 'BETWEEN',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a filter to retrieve posts that were set to no-index.
|
||||
*
|
||||
* @return array<array<string>> Array containin the no-index filter.
|
||||
*/
|
||||
protected function create_no_index_filter() {
|
||||
return [
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
|
||||
'value' => '1',
|
||||
'compare' => '=',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a filter to retrieve posts that have no keyword set.
|
||||
*
|
||||
* @return array<array<string>> Array containing the no focus keyword filter.
|
||||
*/
|
||||
protected function create_no_focus_keyword_filter() {
|
||||
return [
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'linkdex',
|
||||
'value' => 'needs-a-value-anyway',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a filter to retrieve posts that have not been analyzed for readability yet.
|
||||
*
|
||||
* @return array<array<string>> Array containing the no readability filter.
|
||||
*/
|
||||
protected function create_no_readability_scores_filter() {
|
||||
// We check the existence of the Estimated Reading Time, because readability scores of posts that haven't been manually saved while Yoast SEO is active, don't exist, which is also the case for posts with not enough content.
|
||||
// Meanwhile, the ERT is a solid indicator of whether a post has ever been saved (aka, analyzed), so we're using that.
|
||||
$rank = new WPSEO_Rank( WPSEO_Rank::BAD );
|
||||
return [
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'estimated-reading-time-minutes',
|
||||
'value' => 'needs-a-value-anyway',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
[
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'content_score',
|
||||
'value' => $rank->get_starting_score(),
|
||||
'type' => 'numeric',
|
||||
'compare' => '<',
|
||||
],
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'content_score',
|
||||
'value' => 'needs-a-value-anyway',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a filter to retrieve posts that have bad readability scores, including those that have not enough content to have one.
|
||||
*
|
||||
* @return array<array<string>> Array containing the bad readability filter.
|
||||
*/
|
||||
protected function create_bad_readability_scores_filter() {
|
||||
$rank = new WPSEO_Rank( WPSEO_Rank::BAD );
|
||||
return [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'content_score',
|
||||
'value' => [ $rank->get_starting_score(), $rank->get_end_score() ],
|
||||
'type' => 'numeric',
|
||||
'compare' => 'BETWEEN',
|
||||
],
|
||||
[
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'content_score',
|
||||
'value' => 'needs-a-value-anyway',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
[
|
||||
'key' => WPSEO_Meta::$meta_prefix . 'estimated-reading-time-minutes',
|
||||
'compare' => 'EXISTS',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a particular post_id is of an indexable post type.
|
||||
*
|
||||
* @param string $post_id The post ID to check.
|
||||
*
|
||||
* @return bool Whether or not it is indexable.
|
||||
*/
|
||||
protected function is_indexable( $post_id ) {
|
||||
if ( ! empty( $post_id ) && ! $this->uses_default_indexing( $post_id ) ) {
|
||||
return WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) === '2';
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( is_object( $post ) ) {
|
||||
// If the option is false, this means we want to index it.
|
||||
return WPSEO_Options::get( 'noindex-' . $post->post_type, false ) === false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given post ID uses the default indexing settings.
|
||||
*
|
||||
* @param int $post_id The post ID to check.
|
||||
*
|
||||
* @return bool Whether or not the default indexing is being used for the post.
|
||||
*/
|
||||
protected function uses_default_indexing( $post_id ) {
|
||||
return WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) === '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns filters when $order_by is matched in the if-statement.
|
||||
*
|
||||
* @param string $order_by The ID of the column by which to order the posts.
|
||||
*
|
||||
* @return array<string> Array containing the order filters.
|
||||
*/
|
||||
private function filter_order_by( $order_by ) {
|
||||
switch ( $order_by ) {
|
||||
case 'wpseo-metadesc':
|
||||
return [
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
|
||||
'meta_key' => WPSEO_Meta::$meta_prefix . 'metadesc',
|
||||
'orderby' => 'meta_value',
|
||||
];
|
||||
|
||||
case 'wpseo-focuskw':
|
||||
return [
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
|
||||
'meta_key' => WPSEO_Meta::$meta_prefix . 'focuskw',
|
||||
'orderby' => 'meta_value',
|
||||
];
|
||||
|
||||
case 'wpseo-score':
|
||||
return [
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
|
||||
'meta_key' => WPSEO_Meta::$meta_prefix . 'linkdex',
|
||||
'orderby' => 'meta_value_num',
|
||||
];
|
||||
|
||||
case 'wpseo-score-readability':
|
||||
return [
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
|
||||
'meta_key' => WPSEO_Meta::$meta_prefix . 'content_score',
|
||||
'orderby' => 'meta_value_num',
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the score column.
|
||||
*
|
||||
* @param int $post_id The ID of the post for which to show the score.
|
||||
*
|
||||
* @return string The HTML for the SEO score indicator.
|
||||
*/
|
||||
private function parse_column_score( $post_id ) {
|
||||
$meta = $this->get_meta( $post_id );
|
||||
|
||||
if ( $meta ) {
|
||||
return $this->score_icon_helper->for_seo( $meta->indexable, '', __( 'Post is set to noindex.', 'wordpress-seo' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing the readability score column.
|
||||
*
|
||||
* @param int $post_id The ID of the post for which to show the readability score.
|
||||
*
|
||||
* @return string The HTML for the readability score indicator.
|
||||
*/
|
||||
private function parse_column_score_readability( $post_id ) {
|
||||
$meta = $this->get_meta( $post_id );
|
||||
if ( $meta ) {
|
||||
return $this->score_icon_helper->for_readability( $meta->indexable->readability_score );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the hooks for the post_types.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function set_post_type_hooks() {
|
||||
$post_types = WPSEO_Post_Type::get_accessible_post_types();
|
||||
|
||||
if ( ! is_array( $post_types ) || $post_types === [] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
if ( $this->display_metabox( $post_type ) === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'column_heading' ], 10, 1 );
|
||||
add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'column_content' ], 10, 2 );
|
||||
add_action( 'manage_edit-' . $post_type . '_sortable_columns', [ $this, 'column_sort' ], 10, 2 );
|
||||
}
|
||||
|
||||
unset( $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the WPSEO_Metabox check to determine whether the metabox should be displayed either by
|
||||
* choice of the admin or because the post type is not a public post type.
|
||||
*
|
||||
* @since 7.0
|
||||
*
|
||||
* @param string|null $post_type Optional. The post type to test, defaults to the current post post_type.
|
||||
*
|
||||
* @return bool Whether or not the meta box (and associated columns etc) should be hidden.
|
||||
*/
|
||||
private function display_metabox( $post_type = null ) {
|
||||
$current_post_type = $this->get_current_post_type();
|
||||
|
||||
if ( ! isset( $post_type ) && ! empty( $current_post_type ) ) {
|
||||
$post_type = $current_post_type;
|
||||
}
|
||||
|
||||
return WPSEO_Utils::is_metabox_active( $post_type, 'post_type' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not filter dropdowns should be displayed.
|
||||
*
|
||||
* @return bool Whether or the current page can display the filter drop downs.
|
||||
*/
|
||||
public function can_display_filter() {
|
||||
if ( $GLOBALS['pagenow'] === 'upload.php' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->display_metabox() === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
if ( $screen === null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WPSEO_Post_Type::is_post_type_accessible( $screen->post_type );
|
||||
}
|
||||
}
|
||||
218
wp-content/plugins/wordpress-seo/admin/class-my-yoast-proxy.php
Normal file
218
wp-content/plugins/wordpress-seo/admin/class-my-yoast-proxy.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?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
|
||||
*/
|
||||
public const PAGE_IDENTIFIER = 'wpseo_myyoast_proxy';
|
||||
|
||||
/**
|
||||
* The cache control's max age. Used in the header of a successful proxy response.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public 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 );
|
||||
}
|
||||
}
|
||||
112
wp-content/plugins/wordpress-seo/admin/class-option-tab.php
Normal file
112
wp-content/plugins/wordpress-seo/admin/class-option-tab.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param string|null $tab_contents 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>';
|
||||
}
|
||||
}
|
||||
}
|
||||
124
wp-content/plugins/wordpress-seo/admin/class-option-tabs.php
Normal file
124
wp-content/plugins/wordpress-seo/admin/class-option-tabs.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display( Yoast_Form $yform ) {
|
||||
$formatter = new WPSEO_Option_Tabs_Formatter();
|
||||
$formatter->run( $this, $yform );
|
||||
}
|
||||
}
|
||||
141
wp-content/plugins/wordpress-seo/admin/class-paper-presenter.php
Normal file
141
wp-content/plugins/wordpress-seo/admin/class-paper-presenter.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Plugin_Availability
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
|
||||
|
||||
/**
|
||||
* Class WPSEO_Plugin_Availability
|
||||
*/
|
||||
class WPSEO_Plugin_Availability {
|
||||
|
||||
/**
|
||||
* Holds the plugins.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $plugins = [];
|
||||
|
||||
/**
|
||||
* Registers the plugins so we can access them.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$this->register_yoast_plugins();
|
||||
$this->register_yoast_plugins_status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all the available Yoast SEO plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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', // Kept for backwards compatibility, in case external code uses get_dependencies(). Deprecated in 22.4.
|
||||
'conditional' => new WooCommerce_Conditional(),
|
||||
],
|
||||
],
|
||||
'installed' => false,
|
||||
'slug' => 'wpseo-woocommerce/wpseo-woocommerce.php',
|
||||
'version_sync' => true,
|
||||
'premium' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets certain plugin properties based on WordPress' status.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 if there are dependencies available for the plugin.
|
||||
*
|
||||
* @param array $plugin The information available about the plugin.
|
||||
*
|
||||
* @return bool Whether 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 );
|
||||
$active_dependencies = array_filter( $dependencies, [ $this, 'is_dependency_active' ] );
|
||||
|
||||
return count( $active_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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 active.
|
||||
*
|
||||
* @param array<string, Conditional> $dependency The information about the dependency to look for.
|
||||
*
|
||||
* @return bool Whether or not the dependency is active.
|
||||
*/
|
||||
public function is_dependency_active( $dependency ) {
|
||||
return $dependency['conditional']->is_met();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @deprecated 23.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $plugin The plugin slug to check.
|
||||
*
|
||||
* @return bool Whether or not the plugin is active.
|
||||
*/
|
||||
public function is_active( $plugin ) {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'is_plugin_active' );
|
||||
|
||||
return is_plugin_active( $plugin );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the possibly available plugins.
|
||||
*
|
||||
* @deprecated 23.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array Array containing the information about the plugins.
|
||||
*/
|
||||
public function get_plugins() {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'WPSEO_Addon_Manager::get_addon_filenames' );
|
||||
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific plugin. Returns an empty array if it cannot be found.
|
||||
*
|
||||
* @deprecated 23.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $plugin The plugin to search for.
|
||||
*
|
||||
* @return array The plugin properties.
|
||||
*/
|
||||
public function get_plugin( $plugin ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- needed for BC reasons
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'WPSEO_Addon_Manager::get_plugin_file' );
|
||||
if ( ! isset( $this->plugins[ $plugin ] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->plugins[ $plugin ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version of the plugin.
|
||||
*
|
||||
* @deprecated 23.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $plugin The information available about the plugin.
|
||||
*
|
||||
* @return string The version associated with the plugin.
|
||||
*/
|
||||
public function get_version( $plugin ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- needed for BC reasons
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'WPSEO_Addon_Manager::get_installed_addons_versions' );
|
||||
if ( ! isset( $plugin['version'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $plugin['version'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a dependency is available.
|
||||
*
|
||||
* @deprecated 22.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @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 ) {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 22.4' );
|
||||
|
||||
return isset( get_plugins()[ $dependency['slug'] ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of the dependencies.
|
||||
*
|
||||
* @deprecated 23.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @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 ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- needed for BC reasons
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.4' );
|
||||
if ( ! $this->has_dependencies( $plugin ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_keys( $plugin['_dependencies'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a plugin is a Premium product.
|
||||
*
|
||||
* @deprecated 23.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $plugin The plugin to check.
|
||||
*
|
||||
* @return bool Whether or not the plugin is a Premium product.
|
||||
*/
|
||||
public function is_premium( $plugin ) {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.4' );
|
||||
|
||||
return isset( $plugin['premium'] ) && $plugin['premium'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all installed plugins.
|
||||
*
|
||||
* @deprecated 23.4
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The installed plugins.
|
||||
*/
|
||||
public function get_installed_plugins() {
|
||||
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.4', 'WPSEO_Addon_Manager::get_installed_addons_versions' );
|
||||
$installed = [];
|
||||
|
||||
foreach ( $this->plugins as $plugin_key => $plugin ) {
|
||||
if ( $this->is_installed( $plugin ) ) {
|
||||
$installed[ $plugin_key ] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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<string, array<string>>
|
||||
*/
|
||||
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 = self::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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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, X, 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 );
|
||||
}
|
||||
}
|
||||
105
wp-content/plugins/wordpress-seo/admin/class-premium-popup.php
Normal file
105
wp-content/plugins/wordpress-seo/admin/class-premium-popup.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?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' ) );
|
||||
/* translators: Hidden accessibility text. */
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
sprintf(
|
||||
/* translators: %1$s expands to a strong opening tag, %2$s expands to a strong closing tag. */
|
||||
esc_html__( '%1$sAI%2$s: Better SEO titles and meta descriptions, faster.', 'wordpress-seo' ),
|
||||
'<strong>',
|
||||
'</strong>'
|
||||
),
|
||||
sprintf(
|
||||
/* translators: %1$s expands to a strong opening tag, %2$s expands to a strong closing tag. */
|
||||
esc_html__( '%1$sMultiple keywords%2$s: Rank higher for more searches.', 'wordpress-seo' ),
|
||||
'<strong>',
|
||||
'</strong>'
|
||||
),
|
||||
sprintf(
|
||||
/* translators: %1$s expands to a strong opening tag, %2$s expands to a strong closing tag. */
|
||||
esc_html__( '%1$sSuper fast%2$s internal linking suggestions.', 'wordpress-seo' ),
|
||||
'<strong>',
|
||||
'</strong>'
|
||||
),
|
||||
sprintf(
|
||||
/* translators: %1$s expands to a strong opening tag, %2$s expands to a strong closing tag. */
|
||||
esc_html__( '%1$sNo more broken links%2$s: Automatic redirect manager.', 'wordpress-seo' ),
|
||||
'<strong>',
|
||||
'</strong>'
|
||||
),
|
||||
sprintf(
|
||||
/* translators: %1$s expands to a strong opening tag, %2$s expands to a strong closing tag. */
|
||||
esc_html__( '%1$sAppealing social previews%2$s people actually want to click on.', 'wordpress-seo' ),
|
||||
'<strong>',
|
||||
'</strong>'
|
||||
),
|
||||
sprintf(
|
||||
/* translators: %1$s expands to a strong opening tag, %2$s expands to a strong closing tag. */
|
||||
esc_html__( '%1$s24/7 support%2$s: Also on evenings and weekends.', 'wordpress-seo' ),
|
||||
'<strong>',
|
||||
'</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 = YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ? esc_html__( 'Upgrade now', 'wordpress-seo' ) : sprintf( esc_html__( 'Explore %s now!', 'wordpress-seo' ), 'Yoast SEO Premium' );
|
||||
/* translators: Hidden accessibility text. */
|
||||
$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 ) . '">';
|
||||
|
||||
if ( YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) {
|
||||
$bf_label = esc_html__( 'BLACK FRIDAY', 'wordpress-seo' );
|
||||
$sale_label = esc_html__( '30% OFF', 'wordpress-seo' );
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Already escaped above.
|
||||
echo "<div class='black-friday-container'><span>$sale_label</span> <span style='margin-left: auto;'>$bf_label</span> </div>";
|
||||
}
|
||||
|
||||
echo '<div class="' . esc_attr( $class . '--container' ) . '">';
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds the UI to change the primary term for a post.
|
||||
*/
|
||||
class WPSEO_Primary_Term_Admin implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'wpseo_content_meta_section_content', [ $this, 'add_input_fields' ] );
|
||||
|
||||
add_action( 'admin_footer', [ $this, 'wp_footer' ], 10 );
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current post ID.
|
||||
*
|
||||
* @return int The post ID.
|
||||
*/
|
||||
protected function get_current_id() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are casting to an integer.
|
||||
$post_id = isset( $_GET['post'] ) && is_string( $_GET['post'] ) ? (int) wp_unslash( $_GET['post'] ) : 0;
|
||||
|
||||
if ( $post_id === 0 && isset( $GLOBALS['post_ID'] ) ) {
|
||||
$post_id = (int) $GLOBALS['post_ID'];
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds hidden fields for primary taxonomies.
|
||||
*
|
||||
* @param string $content The metabox content.
|
||||
*
|
||||
* @return string The HTML content.
|
||||
*/
|
||||
public function add_input_fields( $content ) {
|
||||
$taxonomies = $this->get_primary_term_taxonomies();
|
||||
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
$content .= $this->primary_term_field( $taxonomy->name );
|
||||
$content .= wp_nonce_field( 'save-primary-term', WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_nonce', false, false );
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the HTML for a hidden field for a primary taxonomy.
|
||||
*
|
||||
* @param string $taxonomy_name The taxonomy's slug.
|
||||
*
|
||||
* @return string The HTML for a hidden primary taxonomy field.
|
||||
*/
|
||||
protected function primary_term_field( $taxonomy_name ) {
|
||||
return sprintf(
|
||||
'<input class="yoast-wpseo-primary-term" type="hidden" id="%1$s" name="%2$s" value="%3$s" />',
|
||||
esc_attr( $this->generate_field_id( $taxonomy_name ) ),
|
||||
esc_attr( $this->generate_field_name( $taxonomy_name ) ),
|
||||
esc_attr( $this->get_primary_term( $taxonomy_name ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an id for a primary taxonomy's hidden field.
|
||||
*
|
||||
* @param string $taxonomy_name The taxonomy's slug.
|
||||
*
|
||||
* @return string The field id.
|
||||
*/
|
||||
protected function generate_field_id( $taxonomy_name ) {
|
||||
return 'yoast-wpseo-primary-' . $taxonomy_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a name for a primary taxonomy's hidden field.
|
||||
*
|
||||
* @param string $taxonomy_name The taxonomy's slug.
|
||||
*
|
||||
* @return string The field id.
|
||||
*/
|
||||
protected function generate_field_name( $taxonomy_name ) {
|
||||
return WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy_name . '_term';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds primary term templates.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wp_footer() {
|
||||
$taxonomies = $this->get_primary_term_taxonomies();
|
||||
|
||||
if ( ! empty( $taxonomies ) ) {
|
||||
$this->include_js_templates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues all the assets needed for the primary term interface.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
global $pagenow;
|
||||
|
||||
if ( ! WPSEO_Metabox::is_post_edit( $pagenow ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$taxonomies = $this->get_primary_term_taxonomies();
|
||||
|
||||
// Only enqueue if there are taxonomies that need a primary term.
|
||||
if ( empty( $taxonomies ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_style( 'primary-category' );
|
||||
|
||||
$mapped_taxonomies = $this->get_mapped_taxonomies_for_js( $taxonomies );
|
||||
|
||||
$data = [
|
||||
'taxonomies' => $mapped_taxonomies,
|
||||
];
|
||||
|
||||
$asset_manager->localize_script( 'post-edit', 'wpseoPrimaryCategoryL10n', $data );
|
||||
$asset_manager->localize_script( 'post-edit-classic', 'wpseoPrimaryCategoryL10n', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the primary term.
|
||||
*
|
||||
* @param string $taxonomy_name Taxonomy name for the term.
|
||||
*
|
||||
* @return int primary term id
|
||||
*/
|
||||
protected function get_primary_term( $taxonomy_name ) {
|
||||
$primary_term = new WPSEO_Primary_Term( $taxonomy_name, $this->get_current_id() );
|
||||
|
||||
return $primary_term->get_primary_term();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the taxonomies for which the primary term selection is enabled.
|
||||
*
|
||||
* @param int|null $post_id Default current post ID.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_primary_term_taxonomies( $post_id = null ) {
|
||||
if ( $post_id === null ) {
|
||||
$post_id = $this->get_current_id();
|
||||
}
|
||||
|
||||
$taxonomies = wp_cache_get( 'primary_term_taxonomies_' . $post_id, 'wpseo' );
|
||||
if ( $taxonomies !== false ) {
|
||||
return $taxonomies;
|
||||
}
|
||||
|
||||
$taxonomies = $this->generate_primary_term_taxonomies( $post_id );
|
||||
|
||||
wp_cache_set( 'primary_term_taxonomies_' . $post_id, $taxonomies, 'wpseo' );
|
||||
|
||||
return $taxonomies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes templates file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function include_js_templates() {
|
||||
include_once WPSEO_PATH . 'admin/views/js-templates-primary-term.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the primary term taxonomies.
|
||||
*
|
||||
* @param int $post_id ID of the post.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function generate_primary_term_taxonomies( $post_id ) {
|
||||
$post_type = get_post_type( $post_id );
|
||||
$all_taxonomies = get_object_taxonomies( $post_type, 'objects' );
|
||||
$all_taxonomies = array_filter( $all_taxonomies, [ $this, 'filter_hierarchical_taxonomies' ] );
|
||||
|
||||
/**
|
||||
* Filters which taxonomies for which the user can choose the primary term.
|
||||
*
|
||||
* @param array $taxonomies An array of taxonomy objects that are primary_term enabled.
|
||||
* @param string $post_type The post type for which to filter the taxonomies.
|
||||
* @param array $all_taxonomies All taxonomies for this post types, even ones that don't have primary term
|
||||
* enabled.
|
||||
*/
|
||||
$taxonomies = (array) apply_filters( 'wpseo_primary_term_taxonomies', $all_taxonomies, $post_type, $all_taxonomies );
|
||||
|
||||
return $taxonomies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a map of taxonomies for localization.
|
||||
*
|
||||
* @param array $taxonomies The taxononmies that should be mapped.
|
||||
*
|
||||
* @return array The mapped taxonomies.
|
||||
*/
|
||||
protected function get_mapped_taxonomies_for_js( $taxonomies ) {
|
||||
return array_map( [ $this, 'map_taxonomies_for_js' ], $taxonomies );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array suitable for use in the javascript.
|
||||
*
|
||||
* @param stdClass $taxonomy The taxonomy to map.
|
||||
*
|
||||
* @return array The mapped taxonomy.
|
||||
*/
|
||||
private function map_taxonomies_for_js( $taxonomy ) {
|
||||
$primary_term = $this->get_primary_term( $taxonomy->name );
|
||||
|
||||
if ( empty( $primary_term ) ) {
|
||||
$primary_term = '';
|
||||
}
|
||||
|
||||
$terms = get_terms(
|
||||
[
|
||||
'taxonomy' => $taxonomy->name,
|
||||
'update_term_meta_cache' => false,
|
||||
'fields' => 'id=>name',
|
||||
]
|
||||
);
|
||||
|
||||
$mapped_terms_for_js = [];
|
||||
foreach ( $terms as $id => $name ) {
|
||||
$mapped_terms_for_js[] = [
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $taxonomy->labels->singular_name,
|
||||
'name' => $taxonomy->name,
|
||||
'primary' => $primary_term,
|
||||
'singularLabel' => $taxonomy->labels->singular_name,
|
||||
'fieldId' => $this->generate_field_id( $taxonomy->name ),
|
||||
'restBase' => ( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name,
|
||||
'terms' => $mapped_terms_for_js,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a taxonomy is hierarchical.
|
||||
*
|
||||
* @param stdClass $taxonomy Taxonomy object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function filter_hierarchical_taxonomies( $taxonomy ) {
|
||||
return (bool) $taxonomy->hierarchical;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the upsell notice.
|
||||
*/
|
||||
class WPSEO_Product_Upsell_Notice {
|
||||
|
||||
/**
|
||||
* Holds the name of the user meta key.
|
||||
*
|
||||
* The value of this database field holds whether the user has dismissed this notice or not.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const USER_META_DISMISSED = 'wpseo-remove-upsell-notice';
|
||||
|
||||
/**
|
||||
* Holds the option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_NAME = 'wpseo';
|
||||
|
||||
/**
|
||||
* Holds the options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Sets the options, because they always have to be there on instance.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->options = $this->get_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the notice should be added or removed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->remove_notification();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the upgrade notice.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_upgrade_notice() {
|
||||
|
||||
if ( $this->has_first_activated_on() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_first_activated_on();
|
||||
$this->add_notification();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for the upsell notice.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dismiss_notice_listener() {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are validating a nonce here.
|
||||
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'dismiss-5star-upsell' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dismiss_upsell = isset( $_GET['yoast_dismiss'] ) && is_string( $_GET['yoast_dismiss'] ) ? sanitize_text_field( wp_unslash( $_GET['yoast_dismiss'] ) ) : '';
|
||||
|
||||
if ( $dismiss_upsell !== 'upsell' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dismiss_notice();
|
||||
|
||||
if ( wp_safe_redirect( admin_url( 'admin.php?page=wpseo_dashboard' ) ) ) {
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the notice should be shown.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_add_notification() {
|
||||
return ( $this->options['first_activated_on'] < strtotime( '-2weeks' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the options has a first activated on date value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function has_first_activated_on() {
|
||||
return $this->options['first_activated_on'] !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the first activated on.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_first_activated_on() {
|
||||
$this->options['first_activated_on'] = strtotime( '-2weeks' );
|
||||
|
||||
$this->save_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notification to the notification center.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_notification() {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->add_notification( $this->get_notification() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a notification to the notification center.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function remove_notification() {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->remove_notification( $this->get_notification() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a premium upsell section if using the free plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_premium_upsell_section() {
|
||||
if ( ! YoastSEO()->helpers->product->is_premium() ) {
|
||||
return sprintf(
|
||||
/* translators: %1$s expands anchor to premium plugin page, %2$s expands to </a> */
|
||||
__( 'By the way, did you know we also have a %1$sPremium plugin%2$s? It offers advanced features, like a redirect manager and support for multiple keyphrases. It also comes with 24/7 personal support.', 'wordpress-seo' ),
|
||||
"<a href='" . WPSEO_Shortlinker::get( 'https://yoa.st/premium-notification' ) . "'>",
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the notification value.
|
||||
*
|
||||
* @return Yoast_Notification
|
||||
*/
|
||||
protected function get_notification() {
|
||||
$message = sprintf(
|
||||
/* translators: %1$s expands to Yoast SEO, %2$s is a link start tag to the plugin page on WordPress.org, %3$s is the link closing tag. */
|
||||
__( 'We\'ve noticed you\'ve been using %1$s for some time now; we hope you love it! We\'d be thrilled if you could %2$sgive us a 5 stars rating on WordPress.org%3$s!', 'wordpress-seo' ),
|
||||
'Yoast SEO',
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/rate-yoast-seo' ) . '">',
|
||||
'</a>'
|
||||
) . "\n\n";
|
||||
|
||||
$message .= sprintf(
|
||||
/* translators: %1$s is a link start tag to the bugreport guidelines on the Yoast help center, %2$s is the link closing tag. */
|
||||
__( 'If you are experiencing issues, %1$splease file a bug report%2$s and we\'ll do our best to help you out.', 'wordpress-seo' ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/bugreport' ) . '">',
|
||||
'</a>'
|
||||
) . "\n\n";
|
||||
|
||||
$message .= $this->get_premium_upsell_section() . "\n\n";
|
||||
|
||||
$message .= '<a class="button" href="' . wp_nonce_url( admin_url( '?page=' . WPSEO_Admin::PAGE_IDENTIFIER . '&yoast_dismiss=upsell' ), 'dismiss-5star-upsell' ) . '">' . __( 'Please don\'t show me this notification anymore', 'wordpress-seo' ) . '</a>';
|
||||
|
||||
$notification = new Yoast_Notification(
|
||||
$message,
|
||||
[
|
||||
'type' => Yoast_Notification::WARNING,
|
||||
'id' => 'wpseo-upsell-notice',
|
||||
'capabilities' => 'wpseo_manage_options',
|
||||
'priority' => 0.8,
|
||||
]
|
||||
);
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the notice.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_notice_dismissed() {
|
||||
return get_user_meta( get_current_user_id(), self::USER_META_DISMISSED, true ) === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the notice.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function dismiss_notice() {
|
||||
update_user_meta( get_current_user_id(), self::USER_META_DISMISSED, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set options.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_options() {
|
||||
return get_option( self::OPTION_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the options to the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function save_options() {
|
||||
update_option( self::OPTION_NAME, $this->options );
|
||||
}
|
||||
}
|
||||
158
wp-content/plugins/wordpress-seo/admin/class-remote-request.php
Normal file
158
wp-content/plugins/wordpress-seo/admin/class-remote-request.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class handles a post request being send to a given endpoint.
|
||||
*/
|
||||
class WPSEO_Remote_Request {
|
||||
|
||||
/**
|
||||
* Holds the post method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const METHOD_POST = 'post';
|
||||
|
||||
/**
|
||||
* Holds the get method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const METHOD_GET = 'get';
|
||||
|
||||
/**
|
||||
* Holds the endpoint to send the request to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = '';
|
||||
|
||||
/**
|
||||
* Holds the arguments to use in this request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $args = [
|
||||
'blocking' => false,
|
||||
'timeout' => 2,
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the response error.
|
||||
*
|
||||
* @var WP_Error|null
|
||||
*/
|
||||
protected $response_error;
|
||||
|
||||
/**
|
||||
* Holds the response body.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $response_body;
|
||||
|
||||
/**
|
||||
* Sets the endpoint and arguments.
|
||||
*
|
||||
* @param string $endpoint The endpoint to send the request to.
|
||||
* @param array $args The arguments to use in this request.
|
||||
*/
|
||||
public function __construct( $endpoint, array $args = [] ) {
|
||||
$this->endpoint = $endpoint;
|
||||
$this->args = wp_parse_args( $this->args, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request body.
|
||||
*
|
||||
* @param mixed $body The body to set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_body( $body ) {
|
||||
$this->args['body'] = $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the data to the given endpoint.
|
||||
*
|
||||
* @param string $method The type of request to send.
|
||||
*
|
||||
* @return bool True when sending data has been successful.
|
||||
*/
|
||||
public function send( $method = self::METHOD_POST ) {
|
||||
switch ( $method ) {
|
||||
case self::METHOD_POST:
|
||||
$response = $this->post();
|
||||
break;
|
||||
case self::METHOD_GET:
|
||||
$response = $this->get();
|
||||
break;
|
||||
default:
|
||||
/* translators: %1$s expands to the request method */
|
||||
$response = new WP_Error( 1, sprintf( __( 'Request method %1$s is not valid.', 'wordpress-seo' ), $method ) );
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->process_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the response error.
|
||||
*
|
||||
* @return WP_Error|null The response error.
|
||||
*/
|
||||
public function get_response_error() {
|
||||
return $this->response_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response body.
|
||||
*
|
||||
* @return mixed The response body.
|
||||
*/
|
||||
public function get_response_body() {
|
||||
return $this->response_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the given response.
|
||||
*
|
||||
* @param mixed $response The response to process.
|
||||
*
|
||||
* @return bool True when response is valid.
|
||||
*/
|
||||
protected function process_response( $response ) {
|
||||
if ( $response instanceof WP_Error ) {
|
||||
$this->response_error = $response;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->response_body = wp_remote_retrieve_body( $response );
|
||||
|
||||
return ( wp_remote_retrieve_response_code( $response ) === 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a post request to the specified endpoint with set arguments.
|
||||
*
|
||||
* @return WP_Error|array The response or WP_Error on failure.
|
||||
*/
|
||||
protected function post() {
|
||||
return wp_remote_post( $this->endpoint, $this->args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a post request to the specified endpoint with set arguments.
|
||||
*
|
||||
* @return WP_Error|array The response or WP_Error on failure.
|
||||
*/
|
||||
protected function get() {
|
||||
return wp_remote_get( $this->endpoint, $this->args );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Notifies the user to update the Search Appearance settings when the site is set to represent a Person,
|
||||
* but no person (name) has been chosen.
|
||||
*/
|
||||
class WPSEO_Schema_Person_Upgrade_Notification implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_init', [ $this, 'handle_notification' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles if the notification should be added or removed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_notification() {
|
||||
$company_or_person_user_id = WPSEO_Options::get( 'company_or_person_user_id', false );
|
||||
if ( WPSEO_Options::get( 'company_or_person' ) === 'person' && empty( $company_or_person_user_id ) ) {
|
||||
$this->add_notification();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->remove_notification();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notification to the notification center.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_notification() {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->add_notification( $this->get_notification() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a notification to the notification center.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function remove_notification() {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->remove_notification( $this->get_notification() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the notification object.
|
||||
*
|
||||
* @return Yoast_Notification
|
||||
*/
|
||||
protected function get_notification() {
|
||||
$message = sprintf(
|
||||
/* translators: %1$s is a link start tag to the Search Appearance settings, %2$s is the link closing tag. */
|
||||
__( 'You have previously set your site to represent a person. We’ve improved our functionality around Schema and the Knowledge Graph, so you should go in and %1$scomplete those settings%2$s.', 'wordpress-seo' ),
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_page_settings#/site-representation' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
|
||||
$notification = new Yoast_Notification(
|
||||
$message,
|
||||
[
|
||||
'type' => Yoast_Notification::WARNING,
|
||||
'id' => 'wpseo-schema-person-upgrade',
|
||||
'capabilities' => 'wpseo_manage_options',
|
||||
'priority' => 0.8,
|
||||
]
|
||||
);
|
||||
|
||||
return $notification;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Suggested_Plugins
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Conditional;
|
||||
|
||||
/**
|
||||
* Class WPSEO_Suggested_Plugins
|
||||
*/
|
||||
class WPSEO_Suggested_Plugins implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Holds the availability checker.
|
||||
*
|
||||
* @var WPSEO_Plugin_Availability
|
||||
*/
|
||||
protected $availability_checker;
|
||||
|
||||
/**
|
||||
* Holds the notification center.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
protected $notification_center;
|
||||
|
||||
/**
|
||||
* WPSEO_Suggested_Plugins constructor.
|
||||
*
|
||||
* @param WPSEO_Plugin_Availability $availability_checker The availability checker to use.
|
||||
* @param Yoast_Notification_Center $notification_center The notification center to add notifications to.
|
||||
*/
|
||||
public function __construct( WPSEO_Plugin_Availability $availability_checker, Yoast_Notification_Center $notification_center ) {
|
||||
$this->availability_checker = $availability_checker;
|
||||
$this->notification_center = $notification_center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_init', [ $this->availability_checker, 'register' ] );
|
||||
add_action( 'admin_init', [ $this, 'add_notifications' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds notifications (when necessary).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_notifications() {
|
||||
$checker = $this->availability_checker;
|
||||
|
||||
// Get all Yoast plugins that have dependencies.
|
||||
$plugins = $checker->get_plugins_with_dependencies();
|
||||
|
||||
foreach ( $plugins as $plugin_name => $plugin ) {
|
||||
$notification_id = 'wpseo-suggested-plugin-' . $plugin_name;
|
||||
|
||||
if ( ! $checker->dependencies_are_satisfied( $plugin ) ) {
|
||||
$this->notification_center->remove_notification_by_id( $notification_id );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $checker->is_installed( $plugin ) ) {
|
||||
$notification = $this->get_yoast_seo_suggested_plugins_notification( $notification_id, $plugin );
|
||||
$this->notification_center->add_notification( $notification );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->notification_center->remove_notification_by_id( $notification_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Yoast SEO suggested plugins notification.
|
||||
*
|
||||
* @param string $notification_id The id of the notification to be created.
|
||||
* @param array<string, string|bool|array<string, Conditional>> $plugin The plugin to retrieve the data from.
|
||||
*
|
||||
* @return Yoast_Notification The notification containing the suggested plugin.
|
||||
*/
|
||||
protected function get_yoast_seo_suggested_plugins_notification( $notification_id, $plugin ) {
|
||||
$message = $this->create_install_suggested_plugin_message( $plugin );
|
||||
|
||||
return new Yoast_Notification(
|
||||
$message,
|
||||
[
|
||||
'id' => $notification_id,
|
||||
'type' => Yoast_Notification::WARNING,
|
||||
'capabilities' => [ 'install_plugins' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message to suggest the installation of a particular plugin.
|
||||
*
|
||||
* @param array $suggested_plugin The suggested plugin.
|
||||
*
|
||||
* @return string The install suggested plugin message.
|
||||
*/
|
||||
protected function create_install_suggested_plugin_message( $suggested_plugin ) {
|
||||
/* translators: %1$s expands to an opening strong tag, %2$s expands to the dependency name, %3$s expands to a closing strong tag, %4$s expands to an opening anchor tag, %5$s expands to a closing anchor tag. */
|
||||
$message = __( 'It looks like you aren\'t using our %1$s%2$s addon%3$s. %4$sUpgrade today%5$s to unlock more tools and SEO features to make your products stand out in search results.', 'wordpress-seo' );
|
||||
$install_link = WPSEO_Admin_Utils::get_install_link( $suggested_plugin );
|
||||
|
||||
return sprintf(
|
||||
$message,
|
||||
'<strong>',
|
||||
$install_link,
|
||||
'</strong>',
|
||||
$this->create_more_information_link( $suggested_plugin['url'], $suggested_plugin['title'] ),
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a more information link that directs the user to WordPress.org Plugin repository.
|
||||
*
|
||||
* @param string $url The URL to the plugin's page.
|
||||
* @param string $name The name of the plugin.
|
||||
*
|
||||
* @return string The more information link.
|
||||
*/
|
||||
protected function create_more_information_link( $url, $name ) {
|
||||
return sprintf(
|
||||
'<a href="%s" aria-label="%s" target="_blank" rel="noopener noreferrer">',
|
||||
$url,
|
||||
/* translators: Hidden accessibility text; %1$s expands to the dependency name */
|
||||
sprintf( __( 'More information about %1$s', 'wordpress-seo' ), $name )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wincher dashboard widget.
|
||||
*/
|
||||
class Wincher_Dashboard_Widget implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Holds an instance of the admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* Wincher_Dashboard_Widget constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register WordPress hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_wincher_dashboard_assets' ] );
|
||||
add_action( 'admin_init', [ $this, 'queue_wincher_dashboard_widget' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Wincher dashboard widget if it should be shown.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function queue_wincher_dashboard_widget() {
|
||||
if ( $this->show_widget() ) {
|
||||
add_action( 'wp_dashboard_setup', [ $this, 'add_wincher_dashboard_widget' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Wincher dashboard widget to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_wincher_dashboard_widget() {
|
||||
add_filter( 'postbox_classes_dashboard_wpseo-wincher-dashboard-overview', [ $this, 'wpseo_wincher_dashboard_overview_class' ] );
|
||||
wp_add_dashboard_widget(
|
||||
'wpseo-wincher-dashboard-overview',
|
||||
/* translators: %1$s expands to Yoast SEO, %2$s to Wincher */
|
||||
sprintf( __( '%1$s / %2$s: Top Keyphrases', 'wordpress-seo' ), 'Yoast SEO', 'Wincher' ),
|
||||
[ $this, 'display_wincher_dashboard_widget' ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds CSS classes to the dashboard widget.
|
||||
*
|
||||
* @param array $classes An array of postbox CSS classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function wpseo_wincher_dashboard_overview_class( $classes ) {
|
||||
$classes[] = 'yoast wpseo-wincherdashboard-overview';
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Wincher dashboard widget.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_wincher_dashboard_widget() {
|
||||
echo '<div id="yoast-seo-wincher-dashboard-widget"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues assets for the dashboard if the current page is the dashboard.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_wincher_dashboard_assets() {
|
||||
if ( ! $this->is_dashboard_screen() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->asset_manager->localize_script( 'wincher-dashboard-widget', 'wpseoWincherDashboardWidgetL10n', $this->localize_wincher_dashboard_script() );
|
||||
$this->asset_manager->enqueue_script( 'wincher-dashboard-widget' );
|
||||
$this->asset_manager->enqueue_style( 'wp-dashboard' );
|
||||
$this->asset_manager->enqueue_style( 'monorepo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates strings used in the Wincher dashboard widget.
|
||||
*
|
||||
* @return array The translated strings.
|
||||
*/
|
||||
public function localize_wincher_dashboard_script() {
|
||||
|
||||
return [
|
||||
'wincher_is_logged_in' => YoastSEO()->helpers->wincher->login_status(),
|
||||
'wincher_website_id' => WPSEO_Options::get( 'wincher_website_id', '' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current screen is the dashboard screen.
|
||||
*
|
||||
* @return bool Whether or not this is the dashboard screen.
|
||||
*/
|
||||
private function is_dashboard_screen() {
|
||||
$current_screen = get_current_screen();
|
||||
|
||||
return ( $current_screen instanceof WP_Screen && $current_screen->id === 'dashboard' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the Wincher dashboard widget should be shown.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function show_widget() {
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$user_can_edit = $analysis_seo->is_enabled() && current_user_can( 'edit_posts' );
|
||||
$is_wincher_active = YoastSEO()->helpers->wincher->is_active();
|
||||
|
||||
return $user_can_edit && $is_wincher_active;
|
||||
}
|
||||
}
|
||||
117
wp-content/plugins/wordpress-seo/admin/class-yoast-columns.php
Normal file
117
wp-content/plugins/wordpress-seo/admin/class-yoast-columns.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the yoast columns.
|
||||
*/
|
||||
class WPSEO_Yoast_Columns implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'load-edit.php', [ $this, 'add_help_tab' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the help tab to the help center for current screen.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_help_tab() {
|
||||
$link_columns_present = $this->display_links();
|
||||
$meta_columns_present = $this->display_meta_columns();
|
||||
if ( ! ( $link_columns_present || $meta_columns_present ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$help_tab_content = sprintf(
|
||||
/* translators: %1$s: Yoast SEO */
|
||||
__( '%1$s adds several columns to this page.', 'wordpress-seo' ),
|
||||
'Yoast SEO'
|
||||
);
|
||||
|
||||
if ( $meta_columns_present ) {
|
||||
$help_tab_content .= ' ' . sprintf(
|
||||
/* translators: %1$s: Link to article about content analysis, %2$s: Anchor closing */
|
||||
__( 'We\'ve written an article about %1$show to use the SEO score and Readability score%2$s.', 'wordpress-seo' ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/16p' ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( $link_columns_present ) {
|
||||
$help_tab_content .= ' ' . sprintf(
|
||||
/* translators: %1$s: Link to article about text links, %2$s: Anchor closing tag, %3$s: Emphasis open tag, %4$s: Emphasis close tag */
|
||||
__( 'The links columns show the number of articles on this site linking %3$sto%4$s this article and the number of URLs linked %3$sfrom%4$s this article. Learn more about %1$show to use these features to improve your internal linking%2$s, which greatly enhances your SEO.', 'wordpress-seo' ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/16p' ) . '">',
|
||||
'</a>',
|
||||
'<em>',
|
||||
'</em>'
|
||||
);
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
$screen->add_help_tab(
|
||||
[
|
||||
/* translators: %s expands to Yoast */
|
||||
'title' => sprintf( __( '%s Columns', 'wordpress-seo' ), 'Yoast' ),
|
||||
'id' => 'yst-columns',
|
||||
'content' => '<p>' . $help_tab_content . '</p>',
|
||||
'priority' => 15,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post type from the $_GET variable.
|
||||
*
|
||||
* @return string The current post type.
|
||||
*/
|
||||
private function get_current_post_type() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['post_type'] ) && is_string( $_GET['post_type'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we are showing link columns on this overview page.
|
||||
* This depends on the post being accessible or not.
|
||||
*
|
||||
* @return bool Whether the linking columns are shown
|
||||
*/
|
||||
private function display_links() {
|
||||
$current_post_type = $this->get_current_post_type();
|
||||
|
||||
if ( empty( $current_post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WPSEO_Post_Type::is_post_type_accessible( $current_post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the WPSEO_Metabox check to determine whether the metabox should be displayed either by
|
||||
* choice of the admin or because the post type is not a public post type.
|
||||
*
|
||||
* @return bool Whether the meta box (and associated columns etc) should be hidden.
|
||||
*/
|
||||
private function display_meta_columns() {
|
||||
$current_post_type = $this->get_current_post_type();
|
||||
|
||||
if ( empty( $current_post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WPSEO_Utils::is_metabox_active( $current_post_type, 'post_type' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to change or add WordPress dashboard widgets.
|
||||
*/
|
||||
class Yoast_Dashboard_Widget implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Holds the cache transient key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CACHE_TRANSIENT_KEY = 'wpseo-dashboard-totals';
|
||||
|
||||
/**
|
||||
* Holds an instance of the admin asset manager.
|
||||
*
|
||||
* @var WPSEO_Admin_Asset_Manager
|
||||
*/
|
||||
protected $asset_manager;
|
||||
|
||||
/**
|
||||
* Holds the dashboard statistics.
|
||||
*
|
||||
* @var WPSEO_Statistics
|
||||
*/
|
||||
protected $statistics;
|
||||
|
||||
/**
|
||||
* Yoast_Dashboard_Widget constructor.
|
||||
*
|
||||
* @param WPSEO_Statistics|null $statistics WPSEO_Statistics instance.
|
||||
*/
|
||||
public function __construct( ?WPSEO_Statistics $statistics = null ) {
|
||||
if ( $statistics === null ) {
|
||||
$statistics = new WPSEO_Statistics();
|
||||
}
|
||||
|
||||
$this->statistics = $statistics;
|
||||
$this->asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register WordPress hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_dashboard_assets' ] );
|
||||
add_action( 'admin_init', [ $this, 'queue_dashboard_widget' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the dashboard widget if it should be shown.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function queue_dashboard_widget() {
|
||||
if ( $this->show_widget() ) {
|
||||
add_action( 'wp_dashboard_setup', [ $this, 'add_dashboard_widget' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds dashboard widget to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_dashboard_widget() {
|
||||
add_filter( 'postbox_classes_dashboard_wpseo-dashboard-overview', [ $this, 'wpseo_dashboard_overview_class' ] );
|
||||
wp_add_dashboard_widget(
|
||||
'wpseo-dashboard-overview',
|
||||
/* translators: %s is the plugin name */
|
||||
sprintf( __( '%s Posts Overview', 'wordpress-seo' ), 'Yoast SEO' ),
|
||||
[ $this, 'display_dashboard_widget' ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds CSS classes to the dashboard widget.
|
||||
*
|
||||
* @param array $classes An array of postbox CSS classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function wpseo_dashboard_overview_class( $classes ) {
|
||||
$classes[] = 'yoast wpseo-dashboard-overview';
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the dashboard widget.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_dashboard_widget() {
|
||||
echo '<div id="yoast-seo-dashboard-widget"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues assets for the dashboard if the current page is the dashboard.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_dashboard_assets() {
|
||||
if ( ! $this->is_dashboard_screen() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->asset_manager->localize_script( 'dashboard-widget', 'wpseoDashboardWidgetL10n', $this->localize_dashboard_script() );
|
||||
$this->asset_manager->enqueue_script( 'dashboard-widget' );
|
||||
$this->asset_manager->enqueue_style( 'wp-dashboard' );
|
||||
$this->asset_manager->enqueue_style( 'monorepo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates strings used in the dashboard widget.
|
||||
*
|
||||
* @return array The translated strings.
|
||||
*/
|
||||
public function localize_dashboard_script() {
|
||||
return [
|
||||
'feed_header' => sprintf(
|
||||
/* translators: %1$s resolves to Yoast.com */
|
||||
__( 'Latest blog posts on %1$s', 'wordpress-seo' ),
|
||||
'Yoast.com'
|
||||
),
|
||||
'feed_footer' => __( 'Read more like this on our SEO blog', 'wordpress-seo' ),
|
||||
'wp_version' => substr( $GLOBALS['wp_version'], 0, 3 ) . '-' . ( is_plugin_active( 'classic-editor/classic-editor.php' ) ? '1' : '0' ),
|
||||
'php_version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current screen is the dashboard screen.
|
||||
*
|
||||
* @return bool Whether or not this is the dashboard screen.
|
||||
*/
|
||||
private function is_dashboard_screen() {
|
||||
$current_screen = get_current_screen();
|
||||
|
||||
return ( $current_screen instanceof WP_Screen && $current_screen->id === 'dashboard' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the dashboard widget should be shown.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function show_widget() {
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
|
||||
return $analysis_seo->is_enabled() && current_user_can( 'edit_posts' );
|
||||
}
|
||||
}
|
||||
1105
wp-content/plugins/wordpress-seo/admin/class-yoast-form.php
Normal file
1105
wp-content/plugins/wordpress-seo/admin/class-yoast-form.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements server-side user input validation.
|
||||
*
|
||||
* @since 12.0
|
||||
*/
|
||||
class Yoast_Input_Validation {
|
||||
|
||||
/**
|
||||
* The error descriptions.
|
||||
*
|
||||
* @since 12.1
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private static $error_descriptions = [];
|
||||
|
||||
/**
|
||||
* Check whether an option group is a Yoast SEO setting.
|
||||
*
|
||||
* The normal pattern is 'yoast' . $option_name . 'options'.
|
||||
*
|
||||
* @since 12.0
|
||||
*
|
||||
* @param string $group_name The option group name.
|
||||
*
|
||||
* @return bool Whether or not it's an Yoast SEO option group.
|
||||
*/
|
||||
public static function is_yoast_option_group_name( $group_name ) {
|
||||
return ( strpos( $group_name, 'yoast' ) !== false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error message to the document title when submitting a settings
|
||||
* form and errors are returned.
|
||||
*
|
||||
* Uses the WordPress `admin_title` filter in the WPSEO_Option subclasses.
|
||||
*
|
||||
* @since 12.0
|
||||
*
|
||||
* @param string $admin_title The page title, with extra context added.
|
||||
*
|
||||
* @return string The modified or original admin title.
|
||||
*/
|
||||
public static function add_yoast_admin_document_title_errors( $admin_title ) {
|
||||
$errors = get_settings_errors();
|
||||
$error_count = 0;
|
||||
|
||||
foreach ( $errors as $error ) {
|
||||
// For now, filter the admin title only in the Yoast SEO settings pages.
|
||||
if ( self::is_yoast_option_group_name( $error['setting'] ) && $error['code'] !== 'settings_updated' ) {
|
||||
++$error_count;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $error_count > 0 ) {
|
||||
return sprintf(
|
||||
/* translators: %1$s: amount of errors, %2$s: the admin page title */
|
||||
_n( 'The form contains %1$s error. %2$s', 'The form contains %1$s errors. %2$s', $error_count, 'wordpress-seo' ),
|
||||
number_format_i18n( $error_count ),
|
||||
$admin_title
|
||||
);
|
||||
}
|
||||
|
||||
return $admin_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a specific form input field was submitted with an invalid value.
|
||||
*
|
||||
* @since 12.1
|
||||
*
|
||||
* @param string $error_code Must be the same slug-name used for the field variable and for `add_settings_error()`.
|
||||
*
|
||||
* @return bool Whether or not the submitted input field contained an invalid value.
|
||||
*/
|
||||
public static function yoast_form_control_has_error( $error_code ) {
|
||||
$errors = get_settings_errors();
|
||||
|
||||
foreach ( $errors as $error ) {
|
||||
if ( $error['code'] === $error_code ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error descriptions.
|
||||
*
|
||||
* @since 12.1
|
||||
* @deprecated 23.3
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array<string, string> $descriptions An associative array of error descriptions.
|
||||
* For each entry, the key must be the setting variable.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function set_error_descriptions( $descriptions = [] ) { // @phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Needed for BC.
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.3' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the error descriptions.
|
||||
*
|
||||
* @since 12.1
|
||||
* @deprecated 23.3
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array<string, string> An associative array of error descriptions.
|
||||
*/
|
||||
public static function get_error_descriptions() {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.3' );
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific error description.
|
||||
*
|
||||
* @since 12.1
|
||||
*
|
||||
* @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
|
||||
*
|
||||
* @return string|null The error description.
|
||||
*/
|
||||
public static function get_error_description( $error_code ) {
|
||||
if ( ! isset( self::$error_descriptions[ $error_code ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::$error_descriptions[ $error_code ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the aria-invalid HTML attribute based on the submitted invalid value.
|
||||
*
|
||||
* @since 12.1
|
||||
*
|
||||
* @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
|
||||
*
|
||||
* @return string The aria-invalid HTML attribute or empty string.
|
||||
*/
|
||||
public static function get_the_aria_invalid_attribute( $error_code ) {
|
||||
if ( self::yoast_form_control_has_error( $error_code ) ) {
|
||||
return ' aria-invalid="true"';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the aria-describedby HTML attribute based on the submitted invalid value.
|
||||
*
|
||||
* @since 12.1
|
||||
*
|
||||
* @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
|
||||
*
|
||||
* @return string The aria-describedby HTML attribute or empty string.
|
||||
*/
|
||||
public static function get_the_aria_describedby_attribute( $error_code ) {
|
||||
if ( self::yoast_form_control_has_error( $error_code ) && self::get_error_description( $error_code ) ) {
|
||||
return ' aria-describedby="' . esc_attr( $error_code ) . '-error-description"';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error description wrapped in a HTML paragraph.
|
||||
*
|
||||
* @since 12.1
|
||||
*
|
||||
* @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
|
||||
*
|
||||
* @return string The error description HTML or empty string.
|
||||
*/
|
||||
public static function get_the_error_description( $error_code ) {
|
||||
$error_description = self::get_error_description( $error_code );
|
||||
|
||||
if ( self::yoast_form_control_has_error( $error_code ) && $error_description ) {
|
||||
return '<p id="' . esc_attr( $error_code ) . '-error-description" class="yoast-input-validation__error-description">' . $error_description . '</p>';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the submitted invalid value to the WordPress `$wp_settings_errors` global.
|
||||
*
|
||||
* @since 12.1
|
||||
*
|
||||
* @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
|
||||
* @param string $dirty_value The submitted invalid value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function add_dirty_value_to_settings_errors( $error_code, $dirty_value ) {
|
||||
global $wp_settings_errors;
|
||||
|
||||
if ( ! is_array( $wp_settings_errors ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $wp_settings_errors as $index => $error ) {
|
||||
if ( $error['code'] === $error_code ) {
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride -- This is a deliberate action.
|
||||
$wp_settings_errors[ $index ]['yoast_dirty_value'] = $dirty_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an invalid submitted value.
|
||||
*
|
||||
* @since 12.1
|
||||
* @deprecated 23.3
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
|
||||
*
|
||||
* @return string The submitted invalid input field value.
|
||||
*/
|
||||
public static function get_dirty_value( $error_code ) { // @phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Needed for BC.
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.3' );
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific invalid value message.
|
||||
*
|
||||
* @since 12.1
|
||||
* @deprecated 23.3
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $error_code Code of the error set via `add_settings_error()`, normally the variable name.
|
||||
*
|
||||
* @return string The error invalid value message or empty string.
|
||||
*/
|
||||
public static function get_dirty_value_message( $error_code ) { // @phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Needed for BC.
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.3' );
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Internals
|
||||
*/
|
||||
|
||||
/**
|
||||
* Multisite utility class for network admin functionality.
|
||||
*/
|
||||
class Yoast_Network_Admin implements WPSEO_WordPress_AJAX_Integration, WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Action identifier for updating plugin network options.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const UPDATE_OPTIONS_ACTION = 'yoast_handle_network_options';
|
||||
|
||||
/**
|
||||
* Action identifier for restoring a site.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const RESTORE_SITE_ACTION = 'yoast_restore_site';
|
||||
|
||||
/**
|
||||
* Gets the available sites as choices, e.g. for a dropdown.
|
||||
*
|
||||
* @param bool $include_empty Optional. Whether to include an initial placeholder choice.
|
||||
* Default false.
|
||||
* @param bool $show_title Optional. Whether to show the title for each site. This requires
|
||||
* switching through the sites, so has performance implications for
|
||||
* sites that do not use a persistent cache.
|
||||
* Default false.
|
||||
*
|
||||
* @return array Choices as $site_id => $site_label pairs.
|
||||
*/
|
||||
public function get_site_choices( $include_empty = false, $show_title = false ) {
|
||||
$choices = [];
|
||||
|
||||
if ( $include_empty ) {
|
||||
$choices['-'] = __( 'None', 'wordpress-seo' );
|
||||
}
|
||||
|
||||
$criteria = [
|
||||
'deleted' => 0,
|
||||
'network_id' => get_current_network_id(),
|
||||
];
|
||||
$sites = get_sites( $criteria );
|
||||
|
||||
foreach ( $sites as $site ) {
|
||||
$site_name = $site->domain . $site->path;
|
||||
if ( $show_title ) {
|
||||
$site_name = $site->blogname . ' (' . $site->domain . $site->path . ')';
|
||||
}
|
||||
$choices[ $site->blog_id ] = $site->blog_id . ': ' . $site_name;
|
||||
|
||||
$site_states = $this->get_site_states( $site );
|
||||
if ( ! empty( $site_states ) ) {
|
||||
$choices[ $site->blog_id ] .= ' [' . implode( ', ', $site_states ) . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the states of a site.
|
||||
*
|
||||
* @param WP_Site $site Site object.
|
||||
*
|
||||
* @return array Array of $state_slug => $state_label pairs.
|
||||
*/
|
||||
public function get_site_states( $site ) {
|
||||
$available_states = [
|
||||
'public' => __( 'public', 'wordpress-seo' ),
|
||||
'archived' => __( 'archived', 'wordpress-seo' ),
|
||||
'mature' => __( 'mature', 'wordpress-seo' ),
|
||||
'spam' => __( 'spam', 'wordpress-seo' ),
|
||||
'deleted' => __( 'deleted', 'wordpress-seo' ),
|
||||
];
|
||||
|
||||
$site_states = [];
|
||||
foreach ( $available_states as $state_slug => $state_label ) {
|
||||
if ( $site->$state_slug === '1' ) {
|
||||
$site_states[ $state_slug ] = $state_label;
|
||||
}
|
||||
}
|
||||
|
||||
return $site_states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to update plugin network options.
|
||||
*
|
||||
* This method works similar to how option updates are handled in `wp-admin/options.php` and
|
||||
* `wp-admin/network/settings.php`.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_update_options_request() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification will happen in verify_request below.
|
||||
if ( ! isset( $_POST['network_option_group'] ) || ! is_string( $_POST['network_option_group'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification will happen in verify_request below.
|
||||
$option_group = sanitize_text_field( wp_unslash( $_POST['network_option_group'] ) );
|
||||
|
||||
if ( empty( $option_group ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->verify_request( "{$option_group}-network-options" );
|
||||
|
||||
$whitelist_options = Yoast_Network_Settings_API::get()->get_whitelist_options( $option_group );
|
||||
|
||||
if ( empty( $whitelist_options ) ) {
|
||||
add_settings_error( $option_group, 'settings_updated', __( 'You are not allowed to modify unregistered network settings.', 'wordpress-seo' ), 'error' );
|
||||
|
||||
$this->terminate_request();
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification -- Nonce verified via `verify_request()` above.
|
||||
foreach ( $whitelist_options as $option_name ) {
|
||||
$value = null;
|
||||
if ( isset( $_POST[ $option_name ] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Adding sanitize_text_field around this will break the saving of settings because it expects a string: https://github.com/Yoast/wordpress-seo/issues/12440.
|
||||
$value = wp_unslash( $_POST[ $option_name ] );
|
||||
}
|
||||
|
||||
WPSEO_Options::update_site_option( $option_name, $value );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification
|
||||
|
||||
$settings_errors = get_settings_errors();
|
||||
if ( empty( $settings_errors ) ) {
|
||||
add_settings_error( $option_group, 'settings_updated', __( 'Settings Updated.', 'wordpress-seo' ), 'updated' );
|
||||
}
|
||||
|
||||
$this->terminate_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to restore a site's default settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_restore_site_request() {
|
||||
$this->verify_request( 'wpseo-network-restore', 'restore_site_nonce' );
|
||||
|
||||
$option_group = 'wpseo_ms';
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification -- Nonce verified via `verify_request()` above.
|
||||
$site_id = ! empty( $_POST[ $option_group ]['site_id'] ) ? (int) $_POST[ $option_group ]['site_id'] : 0;
|
||||
if ( ! $site_id ) {
|
||||
add_settings_error( $option_group, 'settings_updated', __( 'No site has been selected to restore.', 'wordpress-seo' ), 'error' );
|
||||
|
||||
$this->terminate_request();
|
||||
return;
|
||||
}
|
||||
|
||||
$site = get_site( $site_id );
|
||||
if ( ! $site ) {
|
||||
/* translators: %s expands to the ID of a site within a multisite network. */
|
||||
add_settings_error( $option_group, 'settings_updated', sprintf( __( 'Site with ID %d not found.', 'wordpress-seo' ), $site_id ), 'error' );
|
||||
}
|
||||
else {
|
||||
WPSEO_Options::reset_ms_blog( $site_id );
|
||||
|
||||
/* translators: %s expands to the name of a site within a multisite network. */
|
||||
add_settings_error( $option_group, 'settings_updated', sprintf( __( '%s restored to default SEO settings.', 'wordpress-seo' ), esc_html( $site->blogname ) ), 'updated' );
|
||||
}
|
||||
|
||||
$this->terminate_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs nonce, action and option group fields for a network settings page in the plugin.
|
||||
*
|
||||
* @param string $option_group Option group name for the current page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function settings_fields( $option_group ) {
|
||||
?>
|
||||
<input type="hidden" name="network_option_group" value="<?php echo esc_attr( $option_group ); ?>" />
|
||||
<input type="hidden" name="action" value="<?php echo esc_attr( self::UPDATE_OPTIONS_ACTION ); ?>" />
|
||||
<?php
|
||||
wp_nonce_field( "$option_group-network-options" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues network admin assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_script( 'network-admin' );
|
||||
|
||||
$translations = [
|
||||
/* translators: %s: success message */
|
||||
'success_prefix' => __( 'Success: %s', 'wordpress-seo' ),
|
||||
/* translators: %s: error message */
|
||||
'error_prefix' => __( 'Error: %s', 'wordpress-seo' ),
|
||||
];
|
||||
$asset_manager->localize_script(
|
||||
'network-admin',
|
||||
'wpseoNetworkAdminGlobalL10n',
|
||||
$translations
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks in the necessary actions and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
|
||||
if ( ! $this->meets_requirements() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
|
||||
add_action( 'admin_action_' . self::UPDATE_OPTIONS_ACTION, [ $this, 'handle_update_options_request' ] );
|
||||
add_action( 'admin_action_' . self::RESTORE_SITE_ACTION, [ $this, 'handle_restore_site_request' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks in the necessary AJAX actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_ajax_hooks() {
|
||||
add_action( 'wp_ajax_' . self::UPDATE_OPTIONS_ACTION, [ $this, 'handle_update_options_request' ] );
|
||||
add_action( 'wp_ajax_' . self::RESTORE_SITE_ACTION, [ $this, 'handle_restore_site_request' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the requirements to use this class are met.
|
||||
*
|
||||
* @return bool True if requirements are met, false otherwise.
|
||||
*/
|
||||
public function meets_requirements() {
|
||||
return is_multisite() && is_network_admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the current request is valid.
|
||||
*
|
||||
* @param string $action Nonce action.
|
||||
* @param string $query_arg Optional. Nonce query argument. Default '_wpnonce'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function verify_request( $action, $query_arg = '_wpnonce' ) {
|
||||
$has_access = current_user_can( 'wpseo_manage_network_options' );
|
||||
|
||||
if ( wp_doing_ajax() ) {
|
||||
check_ajax_referer( $action, $query_arg );
|
||||
|
||||
if ( ! $has_access ) {
|
||||
wp_die( -1, 403 );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
check_admin_referer( $action, $query_arg );
|
||||
|
||||
if ( ! $has_access ) {
|
||||
wp_die( esc_html__( 'You are not allowed to perform this action.', 'wordpress-seo' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates the current request by either redirecting back or sending an AJAX response.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function terminate_request() {
|
||||
if ( wp_doing_ajax() ) {
|
||||
$settings_errors = get_settings_errors();
|
||||
|
||||
if ( ! empty( $settings_errors ) && $settings_errors[0]['type'] === 'updated' ) {
|
||||
wp_send_json_success( $settings_errors, 200 );
|
||||
}
|
||||
|
||||
wp_send_json_error( $settings_errors, 400 );
|
||||
}
|
||||
|
||||
$this->persist_settings_errors();
|
||||
$this->redirect_back( [ 'settings-updated' => 'true' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists settings errors.
|
||||
*
|
||||
* Settings errors are stored in a transient for 30 seconds so that this transient
|
||||
* can be retrieved on the next page load.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function persist_settings_errors() {
|
||||
/*
|
||||
* A regular transient is used here, since it is automatically cleared right after the redirect.
|
||||
* A network transient would be cleaner, but would require a lot of copied code from core for
|
||||
* just a minor adjustment when displaying settings errors.
|
||||
*/
|
||||
set_transient( 'settings_errors', get_settings_errors(), 30 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects back to the referer URL, with optional query arguments.
|
||||
*
|
||||
* @param array $query_args Optional. Query arguments to add to the redirect URL. Default none.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function redirect_back( $query_args = [] ) {
|
||||
$sendback = wp_get_referer();
|
||||
|
||||
if ( ! empty( $query_args ) ) {
|
||||
$sendback = add_query_arg( $query_args, $sendback );
|
||||
}
|
||||
|
||||
wp_safe_redirect( $sendback );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Network
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements a network settings API for the plugin's multisite settings.
|
||||
*/
|
||||
class Yoast_Network_Settings_API {
|
||||
|
||||
/**
|
||||
* Registered network settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $registered_settings = [];
|
||||
|
||||
/**
|
||||
* Options whitelist, keyed by option group.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $whitelist_options = [];
|
||||
|
||||
/**
|
||||
* The singleton instance of this class.
|
||||
*
|
||||
* @var Yoast_Network_Settings_API|null
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Registers a network setting and its data.
|
||||
*
|
||||
* @param string $option_group The group the network option is part of.
|
||||
* @param string $option_name The name of the network option to sanitize and save.
|
||||
* @param array $args {
|
||||
* Optional. Data used to describe the network setting when registered.
|
||||
*
|
||||
* @type callable $sanitize_callback A callback function that sanitizes the network option's value.
|
||||
* @type mixed $default Default value when calling `get_network_option()`.
|
||||
* }
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_setting( $option_group, $option_name, $args = [] ) {
|
||||
|
||||
$defaults = [
|
||||
'group' => $option_group,
|
||||
'sanitize_callback' => null,
|
||||
];
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
if ( ! isset( $this->whitelist_options[ $option_group ] ) ) {
|
||||
$this->whitelist_options[ $option_group ] = [];
|
||||
}
|
||||
|
||||
$this->whitelist_options[ $option_group ][] = $option_name;
|
||||
|
||||
if ( ! empty( $args['sanitize_callback'] ) ) {
|
||||
add_filter( "sanitize_option_{$option_name}", [ $this, 'filter_sanitize_option' ], 10, 2 );
|
||||
}
|
||||
|
||||
if ( array_key_exists( 'default', $args ) ) {
|
||||
add_filter( "default_site_option_{$option_name}", [ $this, 'filter_default_option' ], 10, 2 );
|
||||
}
|
||||
|
||||
$this->registered_settings[ $option_name ] = $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the registered settings and their data.
|
||||
*
|
||||
* @return array Array of $option_name => $data pairs.
|
||||
*/
|
||||
public function get_registered_settings() {
|
||||
return $this->registered_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the whitelisted options for a given option group.
|
||||
*
|
||||
* @param string $option_group Option group.
|
||||
*
|
||||
* @return array List of option names, or empty array if unknown option group.
|
||||
*/
|
||||
public function get_whitelist_options( $option_group ) {
|
||||
if ( ! isset( $this->whitelist_options[ $option_group ] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->whitelist_options[ $option_group ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters sanitization for a network option value.
|
||||
*
|
||||
* This method is added as a filter to `sanitize_option_{$option}` for network options that are
|
||||
* registered with a sanitize callback.
|
||||
*
|
||||
* @param string $value The sanitized option value.
|
||||
* @param string $option The option name.
|
||||
*
|
||||
* @return string The filtered sanitized option value.
|
||||
*/
|
||||
public function filter_sanitize_option( $value, $option ) {
|
||||
|
||||
if ( empty( $this->registered_settings[ $option ] ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return call_user_func( $this->registered_settings[ $option ]['sanitize_callback'], $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the default value for a network option.
|
||||
*
|
||||
* This function is added as a filter to `default_site_option_{$option}` for network options that
|
||||
* are registered with a default.
|
||||
*
|
||||
* @param mixed $default_value Existing default value to return.
|
||||
* @param string $option The option name.
|
||||
*
|
||||
* @return mixed The filtered default value.
|
||||
*/
|
||||
public function filter_default_option( $default_value, $option ) {
|
||||
|
||||
// If a default value was manually passed to the function, allow it to override.
|
||||
if ( $default_value !== false ) {
|
||||
return $default_value;
|
||||
}
|
||||
|
||||
if ( empty( $this->registered_settings[ $option ] ) ) {
|
||||
return $default_value;
|
||||
}
|
||||
|
||||
return $this->registered_settings[ $option ]['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the requirements to use this class are met.
|
||||
*
|
||||
* @return bool True if requirements are met, false otherwise.
|
||||
*/
|
||||
public function meets_requirements() {
|
||||
return is_multisite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the singleton instance of this class.
|
||||
*
|
||||
* @return Yoast_Network_Settings_API The singleton instance.
|
||||
*/
|
||||
public static function get() {
|
||||
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,960 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Notifications
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Presenters\Abstract_Presenter;
|
||||
|
||||
/**
|
||||
* Handles notifications storage and display.
|
||||
*/
|
||||
class Yoast_Notification_Center {
|
||||
|
||||
/**
|
||||
* Option name to store notifications on.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const STORAGE_KEY = 'yoast_notifications';
|
||||
|
||||
/**
|
||||
* The singleton instance of this object.
|
||||
*
|
||||
* @var Yoast_Notification_Center|null
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Holds the notifications.
|
||||
*
|
||||
* @var Yoast_Notification[][]
|
||||
*/
|
||||
private $notifications = [];
|
||||
|
||||
/**
|
||||
* Notifications there are newly added.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $new = [];
|
||||
|
||||
/**
|
||||
* Notifications that were resolved this execution.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $resolved = 0;
|
||||
|
||||
/**
|
||||
* Internal storage for transaction before notifications have been retrieved from storage.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $queued_transactions = [];
|
||||
|
||||
/**
|
||||
* Internal flag for whether notifications have been retrieved from storage.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $notifications_retrieved = false;
|
||||
|
||||
/**
|
||||
* Internal flag for whether notifications need to be updated in storage.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $notifications_need_storage = false;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*/
|
||||
private function __construct() {
|
||||
|
||||
add_action( 'init', [ $this, 'setup_current_notifications' ], 1 );
|
||||
|
||||
add_action( 'all_admin_notices', [ $this, 'display_notifications' ] );
|
||||
|
||||
add_action( 'wp_ajax_yoast_get_notifications', [ $this, 'ajax_get_notifications' ] );
|
||||
|
||||
add_action( 'wpseo_deactivate', [ $this, 'deactivate_hook' ] );
|
||||
add_action( 'shutdown', [ $this, 'update_storage' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton getter.
|
||||
*
|
||||
* @return Yoast_Notification_Center
|
||||
*/
|
||||
public static function get() {
|
||||
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss a notification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function ajax_dismiss_notification() {
|
||||
$notification_center = self::get();
|
||||
|
||||
if ( ! isset( $_POST['notification'] ) || ! is_string( $_POST['notification'] ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
$notification_id = sanitize_text_field( wp_unslash( $_POST['notification'] ) );
|
||||
|
||||
if ( empty( $notification_id ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are using the variable as a nonce.
|
||||
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), $notification_id ) ) {
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
$notification = $notification_center->get_notification_by_id( $notification_id );
|
||||
if ( ( $notification instanceof Yoast_Notification ) === false ) {
|
||||
|
||||
// Permit legacy.
|
||||
$options = [
|
||||
'id' => $notification_id,
|
||||
'dismissal_key' => $notification_id,
|
||||
];
|
||||
$notification = new Yoast_Notification( '', $options );
|
||||
}
|
||||
|
||||
if ( self::maybe_dismiss_notification( $notification ) ) {
|
||||
exit( '1' );
|
||||
}
|
||||
|
||||
exit( '-1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has dismissed a notification.
|
||||
*
|
||||
* @param Yoast_Notification $notification The notification to check for dismissal.
|
||||
* @param int|null $user_id User ID to check on.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_notification_dismissed( Yoast_Notification $notification, $user_id = null ) {
|
||||
|
||||
$user_id = self::get_user_id( $user_id );
|
||||
$dismissal_key = $notification->get_dismissal_key();
|
||||
|
||||
// This checks both the site-specific user option and the meta value.
|
||||
$current_value = get_user_option( $dismissal_key, $user_id );
|
||||
|
||||
// Migrate old user meta to user option on-the-fly.
|
||||
if ( ! empty( $current_value )
|
||||
&& metadata_exists( 'user', $user_id, $dismissal_key )
|
||||
&& update_user_option( $user_id, $dismissal_key, $current_value ) ) {
|
||||
delete_user_meta( $user_id, $dismissal_key );
|
||||
}
|
||||
|
||||
return ! empty( $current_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the notification is being dismissed.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to check dismissal of.
|
||||
* @param string $meta_value Value to set the meta value to if dismissed.
|
||||
*
|
||||
* @return bool True if dismissed.
|
||||
*/
|
||||
public static function maybe_dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) {
|
||||
|
||||
// Only persistent notifications are dismissible.
|
||||
if ( ! $notification->is_persistent() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If notification is already dismissed, we're done.
|
||||
if ( self::is_notification_dismissed( $notification ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$dismissal_key = $notification->get_dismissal_key();
|
||||
$notification_id = $notification->get_id();
|
||||
|
||||
$is_dismissing = ( $dismissal_key === self::get_user_input( 'notification' ) );
|
||||
if ( ! $is_dismissing ) {
|
||||
$is_dismissing = ( $notification_id === self::get_user_input( 'notification' ) );
|
||||
}
|
||||
|
||||
// Fallback to ?dismissal_key=1&nonce=bla when JavaScript fails.
|
||||
if ( ! $is_dismissing ) {
|
||||
$is_dismissing = ( self::get_user_input( $dismissal_key ) === '1' );
|
||||
}
|
||||
|
||||
if ( ! $is_dismissing ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_nonce = self::get_user_input( 'nonce' );
|
||||
if ( wp_verify_nonce( $user_nonce, $notification_id ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::dismiss_notification( $notification, $meta_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses a notification.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to dismiss.
|
||||
* @param string $meta_value Value to save in the dismissal.
|
||||
*
|
||||
* @return bool True if dismissed, false otherwise.
|
||||
*/
|
||||
public static function dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) {
|
||||
// Dismiss notification.
|
||||
return update_user_option( get_current_user_id(), $notification->get_dismissal_key(), $meta_value ) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a notification.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to restore.
|
||||
*
|
||||
* @return bool True if restored, false otherwise.
|
||||
*/
|
||||
public static function restore_notification( Yoast_Notification $notification ) {
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$dismissal_key = $notification->get_dismissal_key();
|
||||
|
||||
// Restore notification.
|
||||
$restored = delete_user_option( $user_id, $dismissal_key );
|
||||
|
||||
// Delete unprefixed user meta too for backward-compatibility.
|
||||
if ( metadata_exists( 'user', $user_id, $dismissal_key ) ) {
|
||||
$restored = delete_user_meta( $user_id, $dismissal_key ) && $restored;
|
||||
}
|
||||
|
||||
return $restored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear dismissal information for the specified Notification.
|
||||
*
|
||||
* When a cause is resolved, the next time it is present we want to show
|
||||
* the message again.
|
||||
*
|
||||
* @param string|Yoast_Notification $notification Notification to clear the dismissal of.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear_dismissal( $notification ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ( $notification instanceof Yoast_Notification ) {
|
||||
$dismissal_key = $notification->get_dismissal_key();
|
||||
}
|
||||
|
||||
if ( is_string( $notification ) ) {
|
||||
$dismissal_key = $notification;
|
||||
}
|
||||
|
||||
if ( empty( $dismissal_key ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove notification dismissal for all users.
|
||||
$deleted = delete_metadata( 'user', 0, $wpdb->get_blog_prefix() . $dismissal_key, '', true );
|
||||
|
||||
// Delete unprefixed user meta too for backward-compatibility.
|
||||
$deleted = delete_metadata( 'user', 0, $dismissal_key, '', true ) || $deleted;
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves notifications from the storage and merges in previous notification changes.
|
||||
*
|
||||
* The current user in WordPress is not loaded shortly before the 'init' hook, but the plugin
|
||||
* sometimes needs to add or remove notifications before that. In such cases, the transactions
|
||||
* are not actually executed, but added to a queue. That queue is then handled in this method,
|
||||
* after notifications for the current user have been set up.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_current_notifications() {
|
||||
$this->retrieve_notifications_from_storage( get_current_user_id() );
|
||||
|
||||
foreach ( $this->queued_transactions as $transaction ) {
|
||||
list( $callback, $args ) = $transaction;
|
||||
|
||||
call_user_func_array( $callback, $args );
|
||||
}
|
||||
|
||||
$this->queued_transactions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notification to the cookie.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification object instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_notification( Yoast_Notification $notification ) {
|
||||
|
||||
$callback = [ $this, __FUNCTION__ ];
|
||||
$args = func_get_args();
|
||||
if ( $this->queue_transaction( $callback, $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't add if the user can't see it.
|
||||
if ( ! $notification->display_for_current_user() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notification_id = $notification->get_id();
|
||||
$user_id = $notification->get_user_id();
|
||||
|
||||
// Empty notifications are always added.
|
||||
if ( $notification_id !== '' ) {
|
||||
|
||||
// If notification ID exists in notifications, don't add again.
|
||||
$present_notification = $this->get_notification_by_id( $notification_id, $user_id );
|
||||
if ( $present_notification !== null ) {
|
||||
$this->remove_notification( $present_notification, false );
|
||||
}
|
||||
|
||||
if ( $present_notification === null ) {
|
||||
$this->new[] = $notification_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to list.
|
||||
$this->notifications[ $user_id ][] = $notification;
|
||||
|
||||
$this->notifications_need_storage = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification by ID and user ID.
|
||||
*
|
||||
* @param string $notification_id The ID of the notification to search for.
|
||||
* @param int|null $user_id The ID of the user.
|
||||
*
|
||||
* @return Yoast_Notification|null
|
||||
*/
|
||||
public function get_notification_by_id( $notification_id, $user_id = null ) {
|
||||
$user_id = self::get_user_id( $user_id );
|
||||
|
||||
$notifications = $this->get_notifications_for_user( $user_id );
|
||||
|
||||
foreach ( $notifications as $notification ) {
|
||||
if ( $notification_id === $notification->get_id() ) {
|
||||
return $notification;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the notifications.
|
||||
*
|
||||
* @param bool $echo_as_json True when notifications should be printed directly.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_notifications( $echo_as_json = false ) {
|
||||
|
||||
// Never display notifications for network admin.
|
||||
if ( is_network_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sorted_notifications = $this->get_sorted_notifications();
|
||||
$notifications = array_filter( $sorted_notifications, [ $this, 'is_notification_persistent' ] );
|
||||
|
||||
if ( empty( $notifications ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
array_walk( $notifications, [ $this, 'remove_notification' ] );
|
||||
|
||||
$notifications = array_unique( $notifications );
|
||||
if ( $echo_as_json ) {
|
||||
$notification_json = [];
|
||||
|
||||
foreach ( $notifications as $notification ) {
|
||||
$notification_json[] = $notification->render();
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
|
||||
echo WPSEO_Utils::format_json_encode( $notification_json );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $notifications as $notification ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Temporarily disabled, see: https://github.com/Yoast/wordpress-seo-premium/issues/2510 and https://github.com/Yoast/wordpress-seo-premium/issues/2511.
|
||||
echo $notification;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove notification after it has been displayed.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to remove.
|
||||
* @param bool $resolve Resolve as fixed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_notification( Yoast_Notification $notification, $resolve = true ) {
|
||||
|
||||
$callback = [ $this, __FUNCTION__ ];
|
||||
$args = func_get_args();
|
||||
if ( $this->queue_transaction( $callback, $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$index = false;
|
||||
|
||||
// ID of the user to show the notification for, defaults to current user id.
|
||||
$user_id = $notification->get_user_id();
|
||||
$notifications = $this->get_notifications_for_user( $user_id );
|
||||
|
||||
// Match persistent Notifications by ID, non persistent by item in the array.
|
||||
if ( $notification->is_persistent() ) {
|
||||
foreach ( $notifications as $current_index => $present_notification ) {
|
||||
if ( $present_notification->get_id() === $notification->get_id() ) {
|
||||
$index = $current_index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$index = array_search( $notification, $notifications, true );
|
||||
}
|
||||
|
||||
if ( $index === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $notification->is_persistent() && $resolve ) {
|
||||
++$this->resolved;
|
||||
$this->clear_dismissal( $notification );
|
||||
}
|
||||
|
||||
unset( $notifications[ $index ] );
|
||||
$this->notifications[ $user_id ] = array_values( $notifications );
|
||||
|
||||
$this->notifications_need_storage = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a notification by its ID.
|
||||
*
|
||||
* @param string $notification_id The notification id.
|
||||
* @param bool $resolve Resolve as fixed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_notification_by_id( $notification_id, $resolve = true ) {
|
||||
$notification = $this->get_notification_by_id( $notification_id );
|
||||
|
||||
if ( $notification === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->remove_notification( $notification, $resolve );
|
||||
$this->notifications_need_storage = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification count.
|
||||
*
|
||||
* @param bool $dismissed Count dismissed notifications.
|
||||
*
|
||||
* @return int Number of notifications
|
||||
*/
|
||||
public function get_notification_count( $dismissed = false ) {
|
||||
|
||||
$notifications = $this->get_notifications_for_user( get_current_user_id() );
|
||||
$notifications = array_filter( $notifications, [ $this, 'filter_persistent_notifications' ] );
|
||||
|
||||
if ( ! $dismissed ) {
|
||||
$notifications = array_filter( $notifications, [ $this, 'filter_dismissed_notifications' ] );
|
||||
}
|
||||
|
||||
return count( $notifications );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of notifications resolved this execution.
|
||||
*
|
||||
* These notifications have been resolved and should be counted when active again.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_resolved_notification_count() {
|
||||
|
||||
return $this->resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the notifications sorted on type and priority.
|
||||
*
|
||||
* @return Yoast_Notification[] Sorted Notifications
|
||||
*/
|
||||
public function get_sorted_notifications() {
|
||||
$notifications = $this->get_notifications_for_user( get_current_user_id() );
|
||||
if ( empty( $notifications ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Sort by severity, error first.
|
||||
usort( $notifications, [ $this, 'sort_notifications' ] );
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX display notifications.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_get_notifications() {
|
||||
$echo = false;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form data.
|
||||
if ( isset( $_POST['version'] ) && is_string( $_POST['version'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are only comparing the variable in a condition.
|
||||
$echo = wp_unslash( $_POST['version'] ) === '2';
|
||||
}
|
||||
|
||||
// Display the notices.
|
||||
$this->display_notifications( $echo );
|
||||
|
||||
// AJAX die.
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove storage when the plugin is deactivated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deactivate_hook() {
|
||||
|
||||
$this->clear_notifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given user ID if it exists.
|
||||
* Otherwise, this function returns the ID of the current user.
|
||||
*
|
||||
* @param int $user_id The user ID to check.
|
||||
*
|
||||
* @return int The user ID to use.
|
||||
*/
|
||||
private static function get_user_id( $user_id ) {
|
||||
if ( $user_id ) {
|
||||
return $user_id;
|
||||
}
|
||||
return get_current_user_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the notifications on user ID.
|
||||
*
|
||||
* In other terms, it returns an associative array,
|
||||
* mapping user ID to a list of notifications for this user.
|
||||
*
|
||||
* @param Yoast_Notification[] $notifications The notifications to split.
|
||||
*
|
||||
* @return array The notifications, split on user ID.
|
||||
*/
|
||||
private function split_on_user_id( $notifications ) {
|
||||
$split_notifications = [];
|
||||
foreach ( $notifications as $notification ) {
|
||||
$split_notifications[ $notification->get_user_id() ][] = $notification;
|
||||
}
|
||||
return $split_notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save persistent notifications to storage.
|
||||
*
|
||||
* We need to be able to retrieve these so they can be dismissed at any time during the execution.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update_storage() {
|
||||
/**
|
||||
* Plugins might exit on the plugins_loaded hook.
|
||||
* This prevents the pluggable.php file from loading, as it's loaded after the plugins_loaded hook.
|
||||
* As we need functions defined in pluggable.php, make sure it's loaded.
|
||||
*/
|
||||
require_once ABSPATH . WPINC . '/pluggable.php';
|
||||
|
||||
$notifications = $this->notifications;
|
||||
|
||||
/**
|
||||
* One array of Yoast_Notifications, merged from multiple arrays.
|
||||
*
|
||||
* @var Yoast_Notification[] $merged_notifications
|
||||
*/
|
||||
$merged_notifications = [];
|
||||
if ( ! empty( $notifications ) ) {
|
||||
$merged_notifications = array_merge( ...$notifications );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'yoast_notifications_before_storage' - Allows developer to filter notifications before saving them.
|
||||
*
|
||||
* @param Yoast_Notification[] $notifications
|
||||
*/
|
||||
$filtered_merged_notifications = apply_filters( 'yoast_notifications_before_storage', $merged_notifications );
|
||||
|
||||
// The notifications were filtered and therefore need to be stored.
|
||||
if ( $merged_notifications !== $filtered_merged_notifications ) {
|
||||
$merged_notifications = $filtered_merged_notifications;
|
||||
$this->notifications_need_storage = true;
|
||||
}
|
||||
|
||||
$notifications = $this->split_on_user_id( $merged_notifications );
|
||||
|
||||
// No notifications to store, clear storage if it was previously present.
|
||||
if ( empty( $notifications ) ) {
|
||||
$this->remove_storage();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only store notifications if changes are made.
|
||||
if ( $this->notifications_need_storage ) {
|
||||
array_walk( $notifications, [ $this, 'store_notifications_for_user' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the notifications to its respective user's storage.
|
||||
*
|
||||
* @param Yoast_Notification[] $notifications The notifications to store.
|
||||
* @param int $user_id The ID of the user for which to store the notifications.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function store_notifications_for_user( $notifications, $user_id ) {
|
||||
$notifications_as_arrays = array_map( [ $this, 'notification_to_array' ], $notifications );
|
||||
update_user_option( $user_id, self::STORAGE_KEY, $notifications_as_arrays );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a way to verify present notifications.
|
||||
*
|
||||
* @return Yoast_Notification[] Registered notifications.
|
||||
*/
|
||||
public function get_notifications() {
|
||||
if ( ! $this->notifications ) {
|
||||
return [];
|
||||
}
|
||||
return array_merge( ...$this->notifications );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the notifications for the given user.
|
||||
*
|
||||
* @param int $user_id The id of the user to check.
|
||||
*
|
||||
* @return Yoast_Notification[] The notifications for the user with the given ID.
|
||||
*/
|
||||
public function get_notifications_for_user( $user_id ) {
|
||||
if ( array_key_exists( $user_id, $this->notifications ) ) {
|
||||
return $this->notifications[ $user_id ];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get newly added notifications.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_new_notifications() {
|
||||
|
||||
return array_map( [ $this, 'get_notification_by_id' ], $this->new );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information from the User input.
|
||||
*
|
||||
* Note that this function does not handle nonce verification.
|
||||
*
|
||||
* @param string $key Key to retrieve.
|
||||
*
|
||||
* @return string non-sanitized value of key if set, an empty string otherwise.
|
||||
*/
|
||||
private static function get_user_input( $key ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information and only using this variable in a comparison.
|
||||
$request_method = isset( $_SERVER['REQUEST_METHOD'] ) && is_string( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: This function does not sanitize variables.
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing -- Reason: This function does not verify a nonce.
|
||||
if ( $request_method === 'POST' ) {
|
||||
if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ) {
|
||||
return wp_unslash( $_POST[ $key ] );
|
||||
}
|
||||
}
|
||||
elseif ( isset( $_GET[ $key ] ) && is_string( $_GET[ $key ] ) ) {
|
||||
return wp_unslash( $_GET[ $key ] );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the notifications from storage and fill the relevant property.
|
||||
*
|
||||
* @param int $user_id The ID of the user to retrieve notifications for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function retrieve_notifications_from_storage( $user_id ) {
|
||||
if ( $this->notifications_retrieved ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->notifications_retrieved = true;
|
||||
|
||||
$stored_notifications = get_user_option( self::STORAGE_KEY, $user_id );
|
||||
|
||||
// Check if notifications are stored.
|
||||
if ( empty( $stored_notifications ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_array( $stored_notifications ) ) {
|
||||
$notifications = array_map( [ $this, 'array_to_notification' ], $stored_notifications );
|
||||
|
||||
// Apply array_values to ensure we get a 0-indexed array.
|
||||
$notifications = array_values( array_filter( $notifications, [ $this, 'filter_notification_current_user' ] ) );
|
||||
|
||||
$this->notifications[ $user_id ] = $notifications;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort on type then priority.
|
||||
*
|
||||
* @param Yoast_Notification $a Compare with B.
|
||||
* @param Yoast_Notification $b Compare with A.
|
||||
*
|
||||
* @return int 1, 0 or -1 for sorting offset.
|
||||
*/
|
||||
private function sort_notifications( Yoast_Notification $a, Yoast_Notification $b ) {
|
||||
|
||||
$a_type = $a->get_type();
|
||||
$b_type = $b->get_type();
|
||||
|
||||
if ( $a_type === $b_type ) {
|
||||
return WPSEO_Utils::calc( $b->get_priority(), 'compare', $a->get_priority() );
|
||||
}
|
||||
|
||||
if ( $a_type === 'error' ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( $b_type === 'error' ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear local stored notifications.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function clear_notifications() {
|
||||
|
||||
$this->notifications = [];
|
||||
$this->notifications_retrieved = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out non-persistent notifications.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to test for persistent.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function filter_persistent_notifications( Yoast_Notification $notification ) {
|
||||
|
||||
return $notification->is_persistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out dismissed notifications.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function filter_dismissed_notifications( Yoast_Notification $notification ) {
|
||||
|
||||
return ! self::maybe_dismiss_notification( $notification );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Notification to array representation.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to convert.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function notification_to_array( Yoast_Notification $notification ) {
|
||||
|
||||
$notification_data = $notification->to_array();
|
||||
|
||||
if ( isset( $notification_data['nonce'] ) ) {
|
||||
unset( $notification_data['nonce'] );
|
||||
}
|
||||
|
||||
return $notification_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert stored array to Notification.
|
||||
*
|
||||
* @param array $notification_data Array to convert to Notification.
|
||||
*
|
||||
* @return Yoast_Notification
|
||||
*/
|
||||
private function array_to_notification( $notification_data ) {
|
||||
|
||||
if ( isset( $notification_data['options']['nonce'] ) ) {
|
||||
unset( $notification_data['options']['nonce'] );
|
||||
}
|
||||
|
||||
if ( isset( $notification_data['message'] )
|
||||
&& is_subclass_of( $notification_data['message'], Abstract_Presenter::class, false )
|
||||
) {
|
||||
$notification_data['message'] = $notification_data['message']->present();
|
||||
}
|
||||
|
||||
if ( isset( $notification_data['options']['user'] ) ) {
|
||||
$notification_data['options']['user_id'] = $notification_data['options']['user']->ID;
|
||||
unset( $notification_data['options']['user'] );
|
||||
|
||||
$this->notifications_need_storage = true;
|
||||
}
|
||||
|
||||
return new Yoast_Notification(
|
||||
$notification_data['message'],
|
||||
$notification_data['options']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter notifications that should not be displayed for the current user.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to test.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function filter_notification_current_user( Yoast_Notification $notification ) {
|
||||
return $notification->display_for_current_user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given notification is persistent.
|
||||
*
|
||||
* @param Yoast_Notification $notification The notification to check.
|
||||
*
|
||||
* @return bool True when notification is not persistent.
|
||||
*/
|
||||
private function is_notification_persistent( Yoast_Notification $notification ) {
|
||||
return ! $notification->is_persistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a notification transaction for later execution if notifications are not yet set up.
|
||||
*
|
||||
* @param callable $callback Callback that performs the transaction.
|
||||
* @param array $args Arguments to pass to the callback.
|
||||
*
|
||||
* @return bool True if transaction was queued, false if it can be performed immediately.
|
||||
*/
|
||||
private function queue_transaction( $callback, $args ) {
|
||||
if ( $this->notifications_retrieved ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->add_transaction_to_queue( $callback, $args );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notification transaction to the queue for later execution.
|
||||
*
|
||||
* @param callable $callback Callback that performs the transaction.
|
||||
* @param array $args Arguments to pass to the callback.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_transaction_to_queue( $callback, $args ) {
|
||||
$this->queued_transactions[] = [ $callback, $args ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all notifications from storage.
|
||||
*
|
||||
* @return bool True when notifications got removed.
|
||||
*/
|
||||
protected function remove_storage() {
|
||||
if ( ! $this->has_stored_notifications() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delete_user_option( get_current_user_id(), self::STORAGE_KEY );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are stored notifications.
|
||||
*
|
||||
* @return bool True when there are stored notifications.
|
||||
*/
|
||||
protected function has_stored_notifications() {
|
||||
$stored_notifications = $this->get_stored_notifications();
|
||||
|
||||
return ! empty( $stored_notifications );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the stored notifications.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array|false Array with notifications or false when not set.
|
||||
*/
|
||||
protected function get_stored_notifications() {
|
||||
return get_user_option( self::STORAGE_KEY, get_current_user_id() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Notifications
|
||||
* @since 1.5.3
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements individual notification.
|
||||
*/
|
||||
class Yoast_Notification {
|
||||
|
||||
/**
|
||||
* Type of capability check.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MATCH_ALL = 'all';
|
||||
|
||||
/**
|
||||
* Type of capability check.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MATCH_ANY = 'any';
|
||||
|
||||
/**
|
||||
* Notification type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ERROR = 'error';
|
||||
|
||||
/**
|
||||
* Notification type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const WARNING = 'warning';
|
||||
|
||||
/**
|
||||
* Notification type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const UPDATED = 'updated';
|
||||
|
||||
/**
|
||||
* Options of this Notification.
|
||||
*
|
||||
* Contains optional arguments:
|
||||
*
|
||||
* - type: The notification type, i.e. 'updated' or 'error'
|
||||
* - id: The ID of the notification
|
||||
* - nonce: Security nonce to use in case of dismissible notice.
|
||||
* - priority: From 0 to 1, determines the order of Notifications.
|
||||
* - dismissal_key: Option name to save dismissal information in, ID will be used if not supplied.
|
||||
* - capabilities: Capabilities that a user must have for this Notification to show.
|
||||
* - capability_check: How to check capability pass: all or any.
|
||||
* - wpseo_page_only: Only display on wpseo page or on every page.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $options = [];
|
||||
|
||||
/**
|
||||
* Contains default values for the optional arguments.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $defaults = [
|
||||
'type' => self::UPDATED,
|
||||
'id' => '',
|
||||
'user_id' => null,
|
||||
'nonce' => null,
|
||||
'priority' => 0.5,
|
||||
'data_json' => [],
|
||||
'dismissal_key' => null,
|
||||
'capabilities' => [],
|
||||
'capability_check' => self::MATCH_ALL,
|
||||
'yoast_branding' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* The message for the notification.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* Notification class constructor.
|
||||
*
|
||||
* @param string $message Message string.
|
||||
* @param array $options Set of options.
|
||||
*/
|
||||
public function __construct( $message, $options = [] ) {
|
||||
$this->message = $message;
|
||||
$this->options = $this->normalize_options( $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve notification ID string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->options['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the user to show the notification for.
|
||||
*
|
||||
* @deprecated 21.6
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return WP_User|null The user to show this notification for.
|
||||
*/
|
||||
public function get_user() {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 21.6' );
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the id of the user to show the notification for.
|
||||
*
|
||||
* Returns the id of the current user if not user has been sent.
|
||||
*
|
||||
* @return int The user id
|
||||
*/
|
||||
public function get_user_id() {
|
||||
return ( $this->options['user_id'] ?? get_current_user_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve nonce identifier.
|
||||
*
|
||||
* @return string|null Nonce for this Notification.
|
||||
*/
|
||||
public function get_nonce() {
|
||||
if ( $this->options['id'] && empty( $this->options['nonce'] ) ) {
|
||||
$this->options['nonce'] = wp_create_nonce( $this->options['id'] );
|
||||
}
|
||||
|
||||
return $this->options['nonce'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the nonce is up to date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function refresh_nonce() {
|
||||
if ( $this->options['id'] ) {
|
||||
$this->options['nonce'] = wp_create_nonce( $this->options['id'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the notification.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type() {
|
||||
return $this->options['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Priority of the notification.
|
||||
*
|
||||
* Relative to the type.
|
||||
*
|
||||
* @return float Returns the priority between 0 and 1.
|
||||
*/
|
||||
public function get_priority() {
|
||||
return $this->options['priority'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User Meta key to check for dismissal of notification.
|
||||
*
|
||||
* @return string User Meta Option key that registers dismissal.
|
||||
*/
|
||||
public function get_dismissal_key() {
|
||||
if ( empty( $this->options['dismissal_key'] ) ) {
|
||||
return $this->options['id'];
|
||||
}
|
||||
|
||||
return $this->options['dismissal_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this Notification persistent.
|
||||
*
|
||||
* @return bool True if persistent, False if fire and forget.
|
||||
*/
|
||||
public function is_persistent() {
|
||||
$id = $this->get_id();
|
||||
|
||||
return ! empty( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the notification is relevant for the current user.
|
||||
*
|
||||
* @return bool True if a user needs to see this notification, false if not.
|
||||
*/
|
||||
public function display_for_current_user() {
|
||||
// If the notification is for the current page only, always show.
|
||||
if ( ! $this->is_persistent() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the current user doesn't match capabilities.
|
||||
return $this->match_capabilities();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the current user match required capabilities.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function match_capabilities() {
|
||||
// Super Admin can do anything.
|
||||
if ( is_multisite() && is_super_admin( $this->options['user_id'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter capabilities that enable the displaying of this notification.
|
||||
*
|
||||
* @param array $capabilities The capabilities that must be present for this notification.
|
||||
* @param Yoast_Notification $notification The notification object.
|
||||
*
|
||||
* @return array Array of capabilities or empty for no restrictions.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
$capabilities = apply_filters( 'wpseo_notification_capabilities', $this->options['capabilities'], $this );
|
||||
|
||||
// Should be an array.
|
||||
if ( ! is_array( $capabilities ) ) {
|
||||
$capabilities = (array) $capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter capability check to enable all or any capabilities.
|
||||
*
|
||||
* @param string $capability_check The type of check that will be used to determine if an capability is present.
|
||||
* @param Yoast_Notification $notification The notification object.
|
||||
*
|
||||
* @return string self::MATCH_ALL or self::MATCH_ANY.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
$capability_check = apply_filters( 'wpseo_notification_capability_check', $this->options['capability_check'], $this );
|
||||
|
||||
if ( ! in_array( $capability_check, [ self::MATCH_ALL, self::MATCH_ANY ], true ) ) {
|
||||
$capability_check = self::MATCH_ALL;
|
||||
}
|
||||
|
||||
if ( ! empty( $capabilities ) ) {
|
||||
|
||||
$has_capabilities = array_filter( $capabilities, [ $this, 'has_capability' ] );
|
||||
|
||||
switch ( $capability_check ) {
|
||||
case self::MATCH_ALL:
|
||||
return $has_capabilities === $capabilities;
|
||||
case self::MATCH_ANY:
|
||||
return ! empty( $has_capabilities );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array filter function to find matched capabilities.
|
||||
*
|
||||
* @param string $capability Capability to test.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function has_capability( $capability ) {
|
||||
$user_id = $this->options['user_id'];
|
||||
if ( ! is_numeric( $user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
if ( ! $user ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $user->has_cap( $capability );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object properties as an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_array() {
|
||||
return [
|
||||
'message' => $this->message,
|
||||
'options' => $this->options,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds string (view) behaviour to the notification.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the notification as a string.
|
||||
*
|
||||
* @return string The rendered notification.
|
||||
*/
|
||||
public function render() {
|
||||
$attributes = [];
|
||||
|
||||
// Default notification classes.
|
||||
$classes = [
|
||||
'yoast-notification',
|
||||
];
|
||||
|
||||
// Maintain WordPress visualisation of notifications when they are not persistent.
|
||||
if ( ! $this->is_persistent() ) {
|
||||
$classes[] = 'notice';
|
||||
$classes[] = $this->get_type();
|
||||
}
|
||||
|
||||
if ( ! empty( $classes ) ) {
|
||||
$attributes['class'] = implode( ' ', $classes );
|
||||
}
|
||||
|
||||
// Combined attribute key and value into a string.
|
||||
array_walk( $attributes, [ $this, 'parse_attributes' ] );
|
||||
|
||||
$message = null;
|
||||
if ( $this->options['yoast_branding'] ) {
|
||||
$message = $this->wrap_yoast_seo_icon( $this->message );
|
||||
}
|
||||
|
||||
if ( $message === null ) {
|
||||
$message = wpautop( $this->message );
|
||||
}
|
||||
|
||||
// Build the output DIV.
|
||||
return '<div ' . implode( ' ', $attributes ) . '>' . $message . '</div>' . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message for the notification.
|
||||
*
|
||||
* @return string The message.
|
||||
*/
|
||||
public function get_message() {
|
||||
return wpautop( $this->message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the message with a Yoast SEO icon.
|
||||
*
|
||||
* @param string $message The message to wrap.
|
||||
*
|
||||
* @return string The wrapped message.
|
||||
*/
|
||||
private function wrap_yoast_seo_icon( $message ) {
|
||||
$out = sprintf(
|
||||
'<img src="%1$s" height="%2$d" width="%3$d" class="yoast-seo-icon" />',
|
||||
esc_url( plugin_dir_url( WPSEO_FILE ) . 'packages/js/images/Yoast_SEO_Icon.svg' ),
|
||||
60,
|
||||
60
|
||||
);
|
||||
$out .= '<div class="yoast-seo-icon-wrap">';
|
||||
$out .= $message;
|
||||
$out .= '</div>';
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON if provided.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function get_json() {
|
||||
if ( empty( $this->options['data_json'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return WPSEO_Utils::format_json_encode( $this->options['data_json'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we only have values that we can work with.
|
||||
*
|
||||
* @param array $options Options to normalize.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function normalize_options( $options ) {
|
||||
$options = wp_parse_args( $options, $this->defaults );
|
||||
|
||||
// Should not exceed 0 or 1.
|
||||
$options['priority'] = min( 1, max( 0, $options['priority'] ) );
|
||||
|
||||
// Set default capabilities when not supplied.
|
||||
if ( empty( $options['capabilities'] ) || $options['capabilities'] === [] ) {
|
||||
$options['capabilities'] = [ 'wpseo_manage_options' ];
|
||||
}
|
||||
|
||||
// Set to the id of the current user if not supplied.
|
||||
if ( $options['user_id'] === null ) {
|
||||
$options['user_id'] = get_current_user_id();
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format HTML element attributes.
|
||||
*
|
||||
* @param string $value Attribute value.
|
||||
* @param string $key Attribute name.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function parse_attributes( &$value, $key ) {
|
||||
$value = sprintf( '%s="%s"', sanitize_key( $key ), esc_attr( $value ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Notifications
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class Yoast_Notifications.
|
||||
*/
|
||||
class Yoast_Notifications {
|
||||
|
||||
/**
|
||||
* Holds the admin page's ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ADMIN_PAGE = 'wpseo_dashboard';
|
||||
|
||||
/**
|
||||
* Total notifications count.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $notification_count = 0;
|
||||
|
||||
/**
|
||||
* All error notifications.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $errors = [];
|
||||
|
||||
/**
|
||||
* Active errors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $active_errors = [];
|
||||
|
||||
/**
|
||||
* Dismissed errors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $dismissed_errors = [];
|
||||
|
||||
/**
|
||||
* All warning notifications.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $warnings = [];
|
||||
|
||||
/**
|
||||
* Active warnings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $active_warnings = [];
|
||||
|
||||
/**
|
||||
* Dismissed warnings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $dismissed_warnings = [];
|
||||
|
||||
/**
|
||||
* Yoast_Notifications constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->add_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hooks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_hooks() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$page = sanitize_text_field( wp_unslash( $_GET['page'] ) );
|
||||
if ( $page === self::ADMIN_PAGE ) {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Needed for adminbar and Notifications page.
|
||||
add_action( 'admin_init', [ self::class, 'collect_notifications' ], 99 );
|
||||
|
||||
// Add AJAX hooks.
|
||||
add_action( 'wp_ajax_yoast_dismiss_notification', [ $this, 'ajax_dismiss_notification' ] );
|
||||
add_action( 'wp_ajax_yoast_restore_notification', [ $this, 'ajax_restore_notification' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
|
||||
$asset_manager->enqueue_style( 'notifications' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ajax request to dismiss a notification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_dismiss_notification() {
|
||||
|
||||
$notification = $this->get_notification_from_ajax_request();
|
||||
if ( $notification ) {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->maybe_dismiss_notification( $notification );
|
||||
|
||||
$this->output_ajax_response( $notification->get_type() );
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ajax request to restore a notification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_restore_notification() {
|
||||
|
||||
$notification = $this->get_notification_from_ajax_request();
|
||||
if ( $notification ) {
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->restore_notification( $notification );
|
||||
|
||||
$this->output_ajax_response( $notification->get_type() );
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create AJAX response data.
|
||||
*
|
||||
* @param string $type Notification type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function output_ajax_response( $type ) {
|
||||
|
||||
$html = $this->get_view_html( $type );
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
|
||||
echo WPSEO_Utils::format_json_encode(
|
||||
[
|
||||
'html' => $html,
|
||||
'total' => self::get_active_notification_count(),
|
||||
]
|
||||
);
|
||||
// phpcs:enable -- Reason: WPSEO_Utils::format_json_encode is safe.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML to return in the AJAX request.
|
||||
*
|
||||
* @param string $type Notification type.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
private function get_view_html( $type ) {
|
||||
|
||||
switch ( $type ) {
|
||||
case 'error':
|
||||
$view = 'errors';
|
||||
break;
|
||||
|
||||
case 'warning':
|
||||
default:
|
||||
$view = 'warnings';
|
||||
break;
|
||||
}
|
||||
|
||||
// Re-collect notifications.
|
||||
self::collect_notifications();
|
||||
|
||||
/**
|
||||
* Stops PHPStorm from nagging about this variable being unused. The variable is used in the view.
|
||||
*
|
||||
* @noinspection PhpUnusedLocalVariableInspection
|
||||
*/
|
||||
$notifications_data = self::get_template_variables();
|
||||
|
||||
ob_start();
|
||||
include WPSEO_PATH . 'admin/views/partial-notifications-' . $view . '.php';
|
||||
$html = ob_get_clean();
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Yoast Notification from the AJAX request.
|
||||
*
|
||||
* This function does not handle nonce verification.
|
||||
*
|
||||
* @return Yoast_Notification|null A Yoast_Notification on success, null on failure.
|
||||
*/
|
||||
private function get_notification_from_ajax_request() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: This function does not handle nonce verification.
|
||||
if ( ! isset( $_POST['notification'] ) || ! is_string( $_POST['notification'] ) ) {
|
||||
return null;
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: This function does not handle nonce verification.
|
||||
$notification_id = sanitize_text_field( wp_unslash( $_POST['notification'] ) );
|
||||
|
||||
if ( empty( $notification_id ) ) {
|
||||
return null;
|
||||
}
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
return $notification_center->get_notification_by_id( $notification_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the notifications and group them together.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function collect_notifications() {
|
||||
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
|
||||
$notifications = $notification_center->get_sorted_notifications();
|
||||
self::$notification_count = count( $notifications );
|
||||
|
||||
self::$errors = array_filter( $notifications, [ self::class, 'filter_error_notifications' ] );
|
||||
self::$dismissed_errors = array_filter( self::$errors, [ self::class, 'filter_dismissed_notifications' ] );
|
||||
self::$active_errors = array_diff( self::$errors, self::$dismissed_errors );
|
||||
|
||||
self::$warnings = array_filter( $notifications, [ self::class, 'filter_warning_notifications' ] );
|
||||
self::$dismissed_warnings = array_filter( self::$warnings, [ self::class, 'filter_dismissed_notifications' ] );
|
||||
self::$active_warnings = array_diff( self::$warnings, self::$dismissed_warnings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the variables needed in the views.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_template_variables() {
|
||||
|
||||
return [
|
||||
'metrics' => [
|
||||
'total' => self::$notification_count,
|
||||
'active' => self::get_active_notification_count(),
|
||||
'errors' => count( self::$errors ),
|
||||
'warnings' => count( self::$warnings ),
|
||||
],
|
||||
'errors' => [
|
||||
'dismissed' => self::$dismissed_errors,
|
||||
'active' => self::$active_errors,
|
||||
],
|
||||
'warnings' => [
|
||||
'dismissed' => self::$dismissed_warnings,
|
||||
'active' => self::$active_warnings,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of active notifications.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function get_active_notification_count() {
|
||||
|
||||
return ( count( self::$active_errors ) + count( self::$active_warnings ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out any non-errors.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to test.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function filter_error_notifications( Yoast_Notification $notification ) {
|
||||
|
||||
return $notification->get_type() === 'error';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out any non-warnings.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to test.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function filter_warning_notifications( Yoast_Notification $notification ) {
|
||||
|
||||
return $notification->get_type() !== 'error';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out any dismissed notifications.
|
||||
*
|
||||
* @param Yoast_Notification $notification Notification to test.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function filter_dismissed_notifications( Yoast_Notification $notification ) {
|
||||
|
||||
return Yoast_Notification_Center::is_notification_dismissed( $notification );
|
||||
}
|
||||
}
|
||||
|
||||
class_alias( Yoast_Notifications::class, 'Yoast_Alerts' );
|
||||
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
* @since 1.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for handling plugin conflicts.
|
||||
*/
|
||||
class Yoast_Plugin_Conflict {
|
||||
|
||||
/**
|
||||
* The plugins must be grouped per section.
|
||||
*
|
||||
* It's possible to check for each section if there are conflicting plugins.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $plugins = [];
|
||||
|
||||
/**
|
||||
* All the current active plugins will be stored in this private var.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $all_active_plugins = [];
|
||||
|
||||
/**
|
||||
* After searching for active plugins that are in $this->plugins the active plugins will be stored in this
|
||||
* property.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $active_conflicting_plugins = [];
|
||||
|
||||
/**
|
||||
* Property for holding instance of itself.
|
||||
*
|
||||
* @var Yoast_Plugin_Conflict
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* For the use of singleton pattern. Create instance of itself and return this instance.
|
||||
*
|
||||
* @param string $class_name Give the classname to initialize. If classname is
|
||||
* false (empty) it will use it's own __CLASS__.
|
||||
*
|
||||
* @return Yoast_Plugin_Conflict
|
||||
*/
|
||||
public static function get_instance( $class_name = '' ) {
|
||||
|
||||
if ( self::$instance === null ) {
|
||||
if ( ! is_string( $class_name ) || $class_name === '' ) {
|
||||
$class_name = self::class;
|
||||
}
|
||||
|
||||
self::$instance = new $class_name();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting instance, all active plugins and search for active plugins.
|
||||
*
|
||||
* Protected constructor to prevent creating a new instance of the
|
||||
* *Singleton* via the `new` operator from outside this class.
|
||||
*/
|
||||
protected function __construct() {
|
||||
// Set active plugins.
|
||||
$this->all_active_plugins = get_option( 'active_plugins' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['action'] ) && is_string( $_GET['action'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information and only comparing the variable in a condition.
|
||||
$action = wp_unslash( $_GET['action'] );
|
||||
if ( $action === 'deactivate' ) {
|
||||
$this->remove_deactivated_plugin();
|
||||
}
|
||||
}
|
||||
|
||||
// Search for active plugins.
|
||||
$this->search_active_plugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are conflicting plugins for given $plugin_section.
|
||||
*
|
||||
* @param string $plugin_section Type of plugin conflict (such as Open Graph or sitemap).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check_for_conflicts( $plugin_section ) {
|
||||
|
||||
static $sections_checked;
|
||||
|
||||
// Return early if there are no active conflicting plugins at all.
|
||||
if ( empty( $this->active_conflicting_plugins ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $sections_checked === null ) {
|
||||
$sections_checked = [];
|
||||
}
|
||||
|
||||
if ( ! in_array( $plugin_section, $sections_checked, true ) ) {
|
||||
$sections_checked[] = $plugin_section;
|
||||
return ( ! empty( $this->active_conflicting_plugins[ $plugin_section ] ) );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for given $plugin_sections for conflicts.
|
||||
*
|
||||
* @param array $plugin_sections Set of sections.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_plugin_conflicts( $plugin_sections ) {
|
||||
foreach ( $plugin_sections as $plugin_section => $readable_plugin_section ) {
|
||||
// Check for conflicting plugins and show error if there are conflicts.
|
||||
if ( $this->check_for_conflicts( $plugin_section ) ) {
|
||||
$this->set_error( $plugin_section, $readable_plugin_section );
|
||||
}
|
||||
}
|
||||
|
||||
// List of all active sections.
|
||||
$sections = array_keys( $plugin_sections );
|
||||
// List of all sections.
|
||||
$all_plugin_sections = array_keys( $this->plugins );
|
||||
|
||||
/*
|
||||
* Get all sections that are inactive.
|
||||
* These plugins need to be cleared.
|
||||
*
|
||||
* This happens when Sitemaps or OpenGraph implementations toggle active/disabled.
|
||||
*/
|
||||
$inactive_sections = array_diff( $all_plugin_sections, $sections );
|
||||
if ( ! empty( $inactive_sections ) ) {
|
||||
foreach ( $inactive_sections as $section ) {
|
||||
array_walk( $this->plugins[ $section ], [ $this, 'clear_error' ] );
|
||||
}
|
||||
}
|
||||
|
||||
// For active sections clear errors for inactive plugins.
|
||||
foreach ( $sections as $section ) {
|
||||
// By default, clear errors for all plugins of the section.
|
||||
$inactive_plugins = $this->plugins[ $section ];
|
||||
|
||||
// If there are active plugins, filter them from being cleared.
|
||||
if ( isset( $this->active_conflicting_plugins[ $section ] ) ) {
|
||||
$inactive_plugins = array_diff( $this->plugins[ $section ], $this->active_conflicting_plugins[ $section ] );
|
||||
}
|
||||
|
||||
array_walk( $inactive_plugins, [ $this, 'clear_error' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting an error on the screen.
|
||||
*
|
||||
* @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
|
||||
* @param string $readable_plugin_section This is the value for the translation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_error( $plugin_section, $readable_plugin_section ) {
|
||||
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
|
||||
foreach ( $this->active_conflicting_plugins[ $plugin_section ] as $plugin_file ) {
|
||||
|
||||
$plugin_name = $this->get_plugin_name( $plugin_file );
|
||||
|
||||
$error_message = '';
|
||||
/* translators: %1$s: 'Facebook & Open Graph' plugin name(s) of possibly conflicting plugin(s), %2$s to Yoast SEO */
|
||||
$error_message .= '<p>' . sprintf( __( 'The %1$s plugin might cause issues when used in conjunction with %2$s.', 'wordpress-seo' ), '<em>' . $plugin_name . '</em>', 'Yoast SEO' ) . '</p>';
|
||||
$error_message .= '<p>' . sprintf( $readable_plugin_section, 'Yoast SEO', $plugin_name ) . '</p>';
|
||||
|
||||
/* translators: %s: 'Facebook' plugin name of possibly conflicting plugin */
|
||||
$error_message .= '<a class="button button-primary" href="' . wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $plugin_file . '&plugin_status=all', 'deactivate-plugin_' . $plugin_file ) . '">' . sprintf( __( 'Deactivate %s', 'wordpress-seo' ), $this->get_plugin_name( $plugin_file ) ) . '</a> ';
|
||||
|
||||
$identifier = $this->get_notification_identifier( $plugin_file );
|
||||
|
||||
// Add the message to the notifications center.
|
||||
$notification_center->add_notification(
|
||||
new Yoast_Notification(
|
||||
$error_message,
|
||||
[
|
||||
'type' => Yoast_Notification::ERROR,
|
||||
'id' => 'wpseo-conflict-' . $identifier,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the notification for a plugin.
|
||||
*
|
||||
* @param string $plugin_file Clear the optional notification for this plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_error( $plugin_file ) {
|
||||
$identifier = $this->get_notification_identifier( $plugin_file );
|
||||
|
||||
$notification_center = Yoast_Notification_Center::get();
|
||||
$notification_center->remove_notification_by_id( 'wpseo-conflict-' . $identifier );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop through the $this->plugins to check if one of the plugins is active.
|
||||
*
|
||||
* This method will store the active plugins in $this->active_plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function search_active_plugins() {
|
||||
foreach ( $this->plugins as $plugin_section => $plugins ) {
|
||||
$this->check_plugins_active( $plugins, $plugin_section );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop through plugins and check if each plugin is active.
|
||||
*
|
||||
* @param array $plugins Set of plugins.
|
||||
* @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function check_plugins_active( $plugins, $plugin_section ) {
|
||||
foreach ( $plugins as $plugin ) {
|
||||
if ( $this->check_plugin_is_active( $plugin ) ) {
|
||||
$this->add_active_plugin( $plugin_section, $plugin );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given plugin exists in array with all_active_plugins.
|
||||
*
|
||||
* @param string $plugin Plugin basename string.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function check_plugin_is_active( $plugin ) {
|
||||
return in_array( $plugin, $this->all_active_plugins, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add plugin to the list of active plugins.
|
||||
*
|
||||
* This method will check first if key $plugin_section exists, if not it will create an empty array
|
||||
* If $plugin itself doesn't exist it will be added.
|
||||
*
|
||||
* @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
|
||||
* @param string $plugin Plugin basename string.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_active_plugin( $plugin_section, $plugin ) {
|
||||
if ( ! array_key_exists( $plugin_section, $this->active_conflicting_plugins ) ) {
|
||||
$this->active_conflicting_plugins[ $plugin_section ] = [];
|
||||
}
|
||||
|
||||
if ( ! in_array( $plugin, $this->active_conflicting_plugins[ $plugin_section ], true ) ) {
|
||||
$this->active_conflicting_plugins[ $plugin_section ][] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search in $this->plugins for the given $plugin.
|
||||
*
|
||||
* If there is a result it will return the plugin category.
|
||||
*
|
||||
* @param string $plugin Plugin basename string.
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
protected function find_plugin_category( $plugin ) {
|
||||
foreach ( $this->plugins as $plugin_section => $plugins ) {
|
||||
if ( in_array( $plugin, $plugins, true ) ) {
|
||||
return $plugin_section;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin name from file.
|
||||
*
|
||||
* @param string $plugin Plugin path relative to plugins directory.
|
||||
*
|
||||
* @return string|bool Plugin name or false when no name is set.
|
||||
*/
|
||||
protected function get_plugin_name( $plugin ) {
|
||||
$plugin_details = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
|
||||
|
||||
if ( $plugin_details['Name'] !== '' ) {
|
||||
return $plugin_details['Name'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When being in the deactivation process the currently deactivated plugin has to be removed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function remove_deactivated_plugin() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: On the deactivation screen the nonce is already checked by WordPress itself.
|
||||
if ( ! isset( $_GET['plugin'] ) || ! is_string( $_GET['plugin'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: On the deactivation screen the nonce is already checked by WordPress itself.
|
||||
$deactivated_plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) );
|
||||
$key_to_remove = array_search( $deactivated_plugin, $this->all_active_plugins, true );
|
||||
|
||||
if ( $key_to_remove !== false ) {
|
||||
unset( $this->all_active_plugins[ $key_to_remove ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifier from the plugin file.
|
||||
*
|
||||
* @param string $plugin_file Plugin file to get Identifier from.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_notification_identifier( $plugin_file ) {
|
||||
return md5( $plugin_file );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Endpoints
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an implementation of the WPSEO_Endpoint interface to register one or multiple endpoints.
|
||||
*/
|
||||
class WPSEO_Endpoint_File_Size implements WPSEO_Endpoint {
|
||||
|
||||
/**
|
||||
* The namespace of the REST route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const REST_NAMESPACE = 'yoast/v1';
|
||||
|
||||
/**
|
||||
* The route of the endpoint to retrieve the file size.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ENDPOINT_SINGULAR = 'file_size';
|
||||
|
||||
/**
|
||||
* The name of the capability needed to retrieve data using the endpoints.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CAPABILITY_RETRIEVE = 'manage_options';
|
||||
|
||||
/**
|
||||
* The service provider.
|
||||
*
|
||||
* @var WPSEO_File_Size_Service
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* Sets the service provider.
|
||||
*
|
||||
* @param WPSEO_File_Size_Service $service The service provider.
|
||||
*/
|
||||
public function __construct( WPSEO_File_Size_Service $service ) {
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the endpoints.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$route_args = [
|
||||
'methods' => 'GET',
|
||||
'args' => [
|
||||
'url' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The url to retrieve',
|
||||
],
|
||||
],
|
||||
'callback' => [
|
||||
$this->service,
|
||||
'get',
|
||||
],
|
||||
'permission_callback' => [
|
||||
$this,
|
||||
'can_retrieve_data',
|
||||
],
|
||||
];
|
||||
register_rest_route( self::REST_NAMESPACE, self::ENDPOINT_SINGULAR, $route_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not data can be retrieved for the registered endpoints.
|
||||
*
|
||||
* @return bool Whether or not data can be retrieved.
|
||||
*/
|
||||
public function can_retrieve_data() {
|
||||
return current_user_can( self::CAPABILITY_RETRIEVE );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Statistics
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an implementation of the WPSEO_Endpoint interface to register one or multiple endpoints.
|
||||
*/
|
||||
class WPSEO_Endpoint_Statistics implements WPSEO_Endpoint {
|
||||
|
||||
/**
|
||||
* The namespace of the REST route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const REST_NAMESPACE = 'yoast/v1';
|
||||
|
||||
/**
|
||||
* The route of the statistics endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ENDPOINT_RETRIEVE = 'statistics';
|
||||
|
||||
/**
|
||||
* The name of the capability needed to retrieve data using the endpoints.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CAPABILITY_RETRIEVE = 'read';
|
||||
|
||||
/**
|
||||
* Service to use.
|
||||
*
|
||||
* @var WPSEO_Statistics_Service
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* Constructs the WPSEO_Endpoint_Statistics class and sets the service to use.
|
||||
*
|
||||
* @param WPSEO_Statistics_Service $service Service to use.
|
||||
*/
|
||||
public function __construct( WPSEO_Statistics_Service $service ) {
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the REST routes that are available on the endpoint.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
// Register fetch config.
|
||||
$route_args = [
|
||||
'methods' => 'GET',
|
||||
'callback' => [ $this->service, 'get_statistics' ],
|
||||
'permission_callback' => [ $this, 'can_retrieve_data' ],
|
||||
];
|
||||
register_rest_route( self::REST_NAMESPACE, self::ENDPOINT_RETRIEVE, $route_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not data can be retrieved for the registered endpoints.
|
||||
*
|
||||
* @return bool Whether or not data can be retrieved.
|
||||
*/
|
||||
public function can_retrieve_data() {
|
||||
return current_user_can( self::CAPABILITY_RETRIEVE );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Endpoints
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dictates the required methods for an Endpoint implementation.
|
||||
*/
|
||||
interface WPSEO_Endpoint {
|
||||
|
||||
/**
|
||||
* Registers the routes for the endpoints.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register();
|
||||
|
||||
/**
|
||||
* Determines whether or not data can be retrieved for the registered endpoints.
|
||||
*
|
||||
* @return bool Whether or not data can be retrieved.
|
||||
*/
|
||||
public function can_retrieve_data();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Exceptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents named methods for exceptions.
|
||||
*/
|
||||
class WPSEO_File_Size_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Gets the exception for an externally hosted file.
|
||||
*
|
||||
* @param string $file_url The file url.
|
||||
*
|
||||
* @return WPSEO_File_Size_Exception Instance of the exception.
|
||||
*/
|
||||
public static function externally_hosted( $file_url ) {
|
||||
$message = sprintf(
|
||||
/* translators: %1$s expands to the requested url */
|
||||
__( 'Cannot get the size of %1$s because it is hosted externally.', 'wordpress-seo' ),
|
||||
$file_url
|
||||
);
|
||||
|
||||
return new self( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exception for when a unknown error occurs.
|
||||
*
|
||||
* @param string $file_url The file url.
|
||||
*
|
||||
* @return WPSEO_File_Size_Exception Instance of the exception.
|
||||
*/
|
||||
public static function unknown_error( $file_url ) {
|
||||
$message = sprintf(
|
||||
/* translators: %1$s expands to the requested url */
|
||||
__( 'Cannot get the size of %1$s because of unknown reasons.', 'wordpress-seo' ),
|
||||
$file_url
|
||||
);
|
||||
|
||||
return new self( $message );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Filters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Abstract_Post_Filter.
|
||||
*/
|
||||
abstract class WPSEO_Abstract_Post_Filter implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* The filter's query argument.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FILTER_QUERY_ARG = 'yoast_filter';
|
||||
|
||||
/**
|
||||
* Modify the query based on the FILTER_QUERY_ARG variable in $_GET.
|
||||
*
|
||||
* @param string $where Query variables.
|
||||
*
|
||||
* @return string The modified query.
|
||||
*/
|
||||
abstract public function filter_posts( $where );
|
||||
|
||||
/**
|
||||
* Returns the query value this filter uses.
|
||||
*
|
||||
* @return string The query value this filter uses.
|
||||
*/
|
||||
abstract public function get_query_val();
|
||||
|
||||
/**
|
||||
* Returns the total number of posts that match this filter.
|
||||
*
|
||||
* @return int The total number of posts that match this filter.
|
||||
*/
|
||||
abstract protected function get_post_total();
|
||||
|
||||
/**
|
||||
* Returns the label for this filter.
|
||||
*
|
||||
* @return string The label for this filter.
|
||||
*/
|
||||
abstract protected function get_label();
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_init', [ $this, 'add_filter_links' ], 11 );
|
||||
|
||||
add_filter( 'posts_where', [ $this, 'filter_posts' ] );
|
||||
|
||||
if ( $this->is_filter_active() ) {
|
||||
add_action( 'restrict_manage_posts', [ $this, 'render_hidden_input' ] );
|
||||
}
|
||||
|
||||
if ( $this->is_filter_active() ) {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_explanation_assets' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the filter links to the view_edit screens to give the user a filter link.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_filter_links() {
|
||||
foreach ( $this->get_post_types() as $post_type ) {
|
||||
add_filter( 'views_edit-' . $post_type, [ $this, 'add_filter_link' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the necessary assets to display a filter explanation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_explanation_assets() {
|
||||
$explanation = $this->get_explanation();
|
||||
|
||||
if ( $explanation === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$asset_manager->enqueue_script( 'filter-explanation' );
|
||||
$asset_manager->enqueue_style( 'filter-explanation' );
|
||||
$asset_manager->localize_script(
|
||||
'filter-explanation',
|
||||
'yoastFilterExplanation',
|
||||
[ 'text' => $explanation ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a filter link to the views.
|
||||
*
|
||||
* @param array<string, string> $views Array with the views.
|
||||
*
|
||||
* @return array<string, string> Array of views including the added view.
|
||||
*/
|
||||
public function add_filter_link( $views ) {
|
||||
$views[ 'yoast_' . $this->get_query_val() ] = sprintf(
|
||||
'<a href="%1$s"%2$s>%3$s</a> (%4$s)',
|
||||
esc_url( $this->get_filter_url() ),
|
||||
( $this->is_filter_active() ) ? ' class="current" aria-current="page"' : '',
|
||||
$this->get_label(),
|
||||
$this->get_post_total()
|
||||
);
|
||||
|
||||
return $views;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text explaining this filter. Null if no explanation is necessary.
|
||||
*
|
||||
* @return string|null The explanation or null.
|
||||
*/
|
||||
protected function get_explanation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a hidden input to preserve this filter's state when using sub-filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_hidden_input() {
|
||||
echo '<input type="hidden" name="' . esc_attr( self::FILTER_QUERY_ARG ) . '" value="' . esc_attr( $this->get_query_val() ) . '">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an url to edit.php with post_type and this filter as the query arguments.
|
||||
*
|
||||
* @return string The url to activate this filter.
|
||||
*/
|
||||
protected function get_filter_url() {
|
||||
$query_args = [
|
||||
self::FILTER_QUERY_ARG => $this->get_query_val(),
|
||||
'post_type' => $this->get_current_post_type(),
|
||||
];
|
||||
|
||||
return add_query_arg( $query_args, 'edit.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the filter is active.
|
||||
*
|
||||
* @return bool Whether the filter is active.
|
||||
*/
|
||||
protected function is_filter_active() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET[ self::FILTER_QUERY_ARG ] ) && is_string( $_GET[ self::FILTER_QUERY_ARG ] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
return sanitize_text_field( wp_unslash( $_GET[ self::FILTER_QUERY_ARG ] ) ) === $this->get_query_val();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current post type.
|
||||
*
|
||||
* @return string The current post type.
|
||||
*/
|
||||
protected function get_current_post_type() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['post_type'] ) && is_string( $_GET['post_type'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
|
||||
if ( ! empty( $post_type ) ) {
|
||||
return $post_type;
|
||||
}
|
||||
}
|
||||
return 'post';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post types to which this filter should be added.
|
||||
*
|
||||
* @return array The post types to which this filter should be added.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
return WPSEO_Post_Type::get_accessible_post_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the post type is supported.
|
||||
*
|
||||
* @param string $post_type Post type to check against.
|
||||
*
|
||||
* @return bool True when it is supported.
|
||||
*/
|
||||
protected function is_supported_post_type( $post_type ) {
|
||||
return in_array( $post_type, $this->get_post_types(), true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers the filter for filtering posts by cornerstone content.
|
||||
*/
|
||||
class WPSEO_Cornerstone_Filter extends WPSEO_Abstract_Post_Filter {
|
||||
|
||||
/**
|
||||
* Name of the meta value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const META_NAME = 'is_cornerstone';
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
parent::register_hooks();
|
||||
|
||||
add_filter( 'wpseo_cornerstone_post_types', [ 'WPSEO_Post_Type', 'filter_attachment_post_type' ] );
|
||||
add_filter( 'wpseo_cornerstone_post_types', [ $this, 'filter_metabox_disabled' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query value this filter uses.
|
||||
*
|
||||
* @return string The query value this filter uses.
|
||||
*/
|
||||
public function get_query_val() {
|
||||
return 'cornerstone';
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the query based on the seo_filter variable in $_GET.
|
||||
*
|
||||
* @param string $where Query variables.
|
||||
*
|
||||
* @return string The modified query.
|
||||
*/
|
||||
public function filter_posts( $where ) {
|
||||
if ( $this->is_filter_active() ) {
|
||||
global $wpdb;
|
||||
|
||||
$where .= $wpdb->prepare(
|
||||
" AND {$wpdb->posts}.ID IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = '1' ) ",
|
||||
WPSEO_Meta::$meta_prefix . self::META_NAME
|
||||
);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the post types that have the metabox disabled.
|
||||
*
|
||||
* @param array $post_types The post types to filter.
|
||||
*
|
||||
* @return array The filtered post types.
|
||||
*/
|
||||
public function filter_metabox_disabled( $post_types ) {
|
||||
$filtered_post_types = [];
|
||||
foreach ( $post_types as $post_type_key => $post_type ) {
|
||||
if ( ! WPSEO_Post_Type::has_metabox_enabled( $post_type_key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered_post_types[ $post_type_key ] = $post_type;
|
||||
}
|
||||
|
||||
return $filtered_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for this filter.
|
||||
*
|
||||
* @return string The label for this filter.
|
||||
*/
|
||||
protected function get_label() {
|
||||
return __( 'Cornerstone content', 'wordpress-seo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text explaining this filter.
|
||||
*
|
||||
* @return string|null The explanation.
|
||||
*/
|
||||
protected function get_explanation() {
|
||||
$post_type_object = get_post_type_object( $this->get_current_post_type() );
|
||||
|
||||
if ( $post_type_object === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: %1$s expands to the posttype label, %2$s expands anchor to blog post about cornerstone content, %3$s expands to </a> */
|
||||
__( 'Mark the most important %1$s as \'cornerstone content\' to improve your site structure. %2$sLearn more about cornerstone content%3$s.', 'wordpress-seo' ),
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/1i9' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total amount of articles marked as cornerstone content.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_post_total() {
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT( 1 )
|
||||
FROM {$wpdb->postmeta}
|
||||
WHERE post_id IN( SELECT ID FROM {$wpdb->posts} WHERE post_type = %s ) AND
|
||||
meta_key = %s AND meta_value = '1'
|
||||
",
|
||||
$this->get_current_post_type(),
|
||||
WPSEO_Meta::$meta_prefix . self::META_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post types to which this filter should be added.
|
||||
*
|
||||
* @return array The post types to which this filter should be added.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
/**
|
||||
* Filter: 'wpseo_cornerstone_post_types' - Filters post types to exclude the cornerstone feature for.
|
||||
*
|
||||
* @param array $post_types The accessible post types to filter.
|
||||
*/
|
||||
$post_types = apply_filters( 'wpseo_cornerstone_post_types', parent::get_post_types() );
|
||||
if ( ! is_array( $post_types ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $post_types;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Formatter
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Config\Schema_Types;
|
||||
use Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository;
|
||||
use Yoast\WP\SEO\Editors\Application\Integrations\Integration_Information_Repository;
|
||||
|
||||
/**
|
||||
* This class forces needed methods for the metabox localization.
|
||||
*/
|
||||
class WPSEO_Metabox_Formatter {
|
||||
|
||||
/**
|
||||
* Object that provides formatted values.
|
||||
*
|
||||
* @var WPSEO_Metabox_Formatter_Interface
|
||||
*/
|
||||
private $formatter;
|
||||
|
||||
/**
|
||||
* Setting the formatter property.
|
||||
*
|
||||
* @param WPSEO_Metabox_Formatter_Interface $formatter Object that provides the formatted values.
|
||||
*/
|
||||
public function __construct( WPSEO_Metabox_Formatter_Interface $formatter ) {
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values.
|
||||
*
|
||||
* @return array<string, string|array<string|int|bool>|bool|int>
|
||||
*/
|
||||
public function get_values() {
|
||||
$defaults = $this->get_defaults();
|
||||
$values = $this->formatter->get_values();
|
||||
|
||||
return ( $values + $defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array with all the values always needed by a scraper object.
|
||||
*
|
||||
* @return array<string, string|array<string|int|bool>|bool|int> Default settings for the metabox.
|
||||
*/
|
||||
private function get_defaults() {
|
||||
$schema_types = new Schema_Types();
|
||||
|
||||
$defaults = [
|
||||
'author_name' => get_the_author_meta( 'display_name' ),
|
||||
'keyword_usage' => [],
|
||||
'title_template' => '',
|
||||
'metadesc_template' => '',
|
||||
'schema' => [
|
||||
'displayFooter' => WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
|
||||
'pageTypeOptions' => $schema_types->get_page_type_options(),
|
||||
'articleTypeOptions' => $schema_types->get_article_type_options(),
|
||||
],
|
||||
'twitterCardType' => 'summary_large_image',
|
||||
/**
|
||||
* Filter to determine if the markers should be enabled or not.
|
||||
*
|
||||
* @param bool $showMarkers Should the markers being enabled. Default = true.
|
||||
*/
|
||||
'show_markers' => apply_filters( 'wpseo_enable_assessment_markers', true ),
|
||||
];
|
||||
|
||||
$integration_information_repo = YoastSEO()->classes->get( Integration_Information_Repository::class );
|
||||
|
||||
$enabled_integrations = $integration_information_repo->get_integration_information();
|
||||
$defaults = array_merge( $defaults, $enabled_integrations );
|
||||
$enabled_features_repo = YoastSEO()->classes->get( Enabled_Analysis_Features_Repository::class );
|
||||
|
||||
$enabled_features = $enabled_features_repo->get_enabled_features()->parse_to_legacy_array();
|
||||
return array_merge( $defaults, $enabled_features );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Formatter
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Editors\Application\Seo\Post_Seo_Information_Repository;
|
||||
|
||||
/**
|
||||
* This class provides data for the post metabox by return its values for localization.
|
||||
*/
|
||||
class WPSEO_Post_Metabox_Formatter implements WPSEO_Metabox_Formatter_Interface {
|
||||
|
||||
/**
|
||||
* Holds the WordPress Post.
|
||||
*
|
||||
* @var WP_Post
|
||||
*/
|
||||
private $post;
|
||||
|
||||
/**
|
||||
* The permalink to follow.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $permalink;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param WP_Post|array $post Post object.
|
||||
* @param array $options Title options to use.
|
||||
* @param string $structure The permalink to follow.
|
||||
*/
|
||||
public function __construct( $post, array $options, $structure ) {
|
||||
$this->post = $post;
|
||||
$this->permalink = $structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the social templates should be used.
|
||||
*
|
||||
* @deprecated 23.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function use_social_templates() {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 23.1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translated values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_values() {
|
||||
|
||||
$values = [
|
||||
'metaDescriptionDate' => '',
|
||||
];
|
||||
|
||||
if ( $this->post instanceof WP_Post ) {
|
||||
|
||||
/** @var Post_Seo_Information_Repository $repo */
|
||||
$repo = YoastSEO()->classes->get( Post_Seo_Information_Repository::class );
|
||||
$repo->set_post( $this->post );
|
||||
|
||||
$values_to_set = [
|
||||
'isInsightsEnabled' => $this->is_insights_enabled(),
|
||||
];
|
||||
|
||||
$values = ( $values_to_set + $values );
|
||||
$values = ( $repo->get_seo_data() + $values );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_post_edit_values' - Allows changing the values Yoast SEO uses inside the post editor.
|
||||
*
|
||||
* @param array $values The key-value map Yoast SEO uses inside the post editor.
|
||||
* @param WP_Post $post The post opened in the editor.
|
||||
*/
|
||||
return apply_filters( 'wpseo_post_edit_values', $values, $this->post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the insights feature is enabled for this post.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_insights_enabled() {
|
||||
return WPSEO_Options::get( 'enable_metabox_insights', false );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Formatter
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Editors\Application\Seo\Term_Seo_Information_Repository;
|
||||
|
||||
/**
|
||||
* This class provides data for the term metabox by return its values for localization.
|
||||
*/
|
||||
class WPSEO_Term_Metabox_Formatter implements WPSEO_Metabox_Formatter_Interface {
|
||||
|
||||
/**
|
||||
* The term the metabox formatter is for.
|
||||
*
|
||||
* @var WP_Term|stdClass
|
||||
*/
|
||||
private $term;
|
||||
|
||||
/**
|
||||
* The term's taxonomy.
|
||||
*
|
||||
* @var stdClass
|
||||
*/
|
||||
private $taxonomy;
|
||||
|
||||
/**
|
||||
* Whether we must return social templates values.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $use_social_templates = false;
|
||||
|
||||
/**
|
||||
* Array with the WPSEO_Titles options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* WPSEO_Taxonomy_Scraper constructor.
|
||||
*
|
||||
* @param stdClass $taxonomy Taxonomy.
|
||||
* @param WP_Term|stdClass $term Term.
|
||||
*/
|
||||
public function __construct( $taxonomy, $term ) {
|
||||
$this->taxonomy = $taxonomy;
|
||||
$this->term = $term;
|
||||
|
||||
$this->use_social_templates = $this->use_social_templates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the social templates should be used.
|
||||
*
|
||||
* @return bool Whether the social templates should be used.
|
||||
*/
|
||||
public function use_social_templates() {
|
||||
return WPSEO_Options::get( 'opengraph', false ) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translated values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_values() {
|
||||
$values = [];
|
||||
|
||||
// Todo: a column needs to be added on the termpages to add a filter for the keyword, so this can be used in the focus keyphrase doubles.
|
||||
if ( is_object( $this->term ) && property_exists( $this->term, 'taxonomy' ) ) {
|
||||
$values = [
|
||||
'taxonomy' => $this->term->taxonomy,
|
||||
'semrushIntegrationActive' => 0,
|
||||
'wincherIntegrationActive' => 0,
|
||||
'isInsightsEnabled' => $this->is_insights_enabled(),
|
||||
];
|
||||
|
||||
$repo = YoastSEO()->classes->get( Term_Seo_Information_Repository::class );
|
||||
$repo->set_term( $this->term );
|
||||
$values = ( $repo->get_seo_data() + $values );
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the insights feature is enabled for this taxonomy.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_insights_enabled() {
|
||||
return WPSEO_Options::get( 'enable_metabox_insights', false );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Formatter
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface to force get_values.
|
||||
*/
|
||||
interface WPSEO_Metabox_Formatter_Interface {
|
||||
|
||||
/**
|
||||
* Returns formatter values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_values();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\admin\google_search_console
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_GSC.
|
||||
*/
|
||||
class WPSEO_GSC {
|
||||
|
||||
/**
|
||||
* The option where data will be stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_WPSEO_GSC = 'wpseo-gsc';
|
||||
|
||||
/**
|
||||
* Outputs the HTML for the redirect page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display() {
|
||||
require_once WPSEO_PATH . 'admin/google_search_console/views/gsc-display.php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Google_Search_Console
|
||||
*/
|
||||
|
||||
// Admin header.
|
||||
Yoast_Form::get_instance()->admin_header( false, 'wpseo-gsc', false, 'yoast_wpseo_gsc_options' );
|
||||
|
||||
// GSC Error notification.
|
||||
$gsc_url = 'https://search.google.com/search-console/index';
|
||||
$gsc_post_url = 'https://yoa.st/google-search-console-deprecated';
|
||||
$gsc_style_alert = '
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
margin: 16px 0;
|
||||
color: #450c11;
|
||||
background: #f8d7da;
|
||||
';
|
||||
$gsc_style_alert_icon = 'display: block; margin-right: 8px;';
|
||||
$gsc_style_alert_content = 'max-width: 600px;';
|
||||
$gsc_style_alert_link = 'color: #004973;';
|
||||
$gsc_notification = sprintf(
|
||||
/* Translators: %1$s: expands to opening anchor tag, %2$s expands to closing anchor tag. */
|
||||
__( 'Google has discontinued its Crawl Errors API. Therefore, any possible crawl errors you might have cannot be displayed here anymore. %1$sRead our statement on this for further information%2$s.', 'wordpress-seo' ),
|
||||
'<a style="' . $gsc_style_alert_link . '" href="' . WPSEO_Shortlinker::get( $gsc_post_url ) . '" target="_blank" rel="noopener">',
|
||||
WPSEO_Admin_Utils::get_new_tab_message() . '</a>'
|
||||
);
|
||||
$gsc_notification .= '<br/><br/>';
|
||||
$gsc_notification .= sprintf(
|
||||
/* Translators: %1$s: expands to opening anchor tag, %2$s expands to closing anchor tag. */
|
||||
__( 'To view your current crawl errors, %1$splease visit Google Search Console%2$s.', 'wordpress-seo' ),
|
||||
'<a style="' . $gsc_style_alert_link . '" href="' . $gsc_url . '" target="_blank" rel="noopener noreferrer">',
|
||||
WPSEO_Admin_Utils::get_new_tab_message() . '</a>'
|
||||
);
|
||||
?>
|
||||
<div style="<?php echo $gsc_style_alert; ?>">
|
||||
<span style="<?php echo $gsc_style_alert_icon; ?>">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="14" viewBox="0 0 12 14" role="img" aria-hidden="true"
|
||||
focusable="false" fill="#450c11">
|
||||
<path
|
||||
d="M6 1q1.6 0 3 .8T11.2 4t.8 3-.8 3T9 12.2 6 13t-3-.8T.8 10 0 7t.8-3T3 1.8 6 1zm1 9.7V9.3 9L6.7 9H5l-.1.3V10.9l.3.1h1.6l.1-.3zm0-2.6L7 3.2v-.1L6.8 3H5 5l-.1.2.1 4.9.3.2h1.4l.2-.1Q7 8 6.9 8z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span style="<?php echo $gsc_style_alert_content; ?>"><?php echo $gsc_notification; ?></span>
|
||||
</div>
|
||||
<?php
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* This is the view for the modal box that appears when premium isn't loaded.
|
||||
*
|
||||
* @package WPSEO\Admin\Google_Search_Console
|
||||
*/
|
||||
|
||||
_deprecated_file( __FILE__, 'Yoast SEO 9.5' );
|
||||
|
||||
echo '<h1 class="wpseo-redirect-url-title">';
|
||||
printf(
|
||||
/* Translators: %s: expands to Yoast SEO Premium */
|
||||
esc_html__( 'Creating redirects is a %s feature', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium'
|
||||
);
|
||||
echo '</h1>';
|
||||
echo '<p>';
|
||||
printf(
|
||||
/* Translators: %1$s: expands to 'Yoast SEO Premium', %2$s: links to Yoast SEO Premium plugin page. */
|
||||
esc_html__( 'To be able to create a redirect and fix this issue, you need %1$s. You can buy the plugin, including one year of support and updates, on %2$s.', 'wordpress-seo' ),
|
||||
'Yoast SEO Premium',
|
||||
'<a href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/redirects' ) ) . '" target="_blank">yoast.com</a>'
|
||||
);
|
||||
echo '</p>';
|
||||
echo '<button type="button" class="button wpseo-redirect-close">' . esc_html__( 'Close', 'wordpress-seo' ) . '</button>';
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Import_Plugins_Detector.
|
||||
*
|
||||
* Class with functionality to detect whether we should import from another SEO plugin.
|
||||
*/
|
||||
class WPSEO_Import_Plugins_Detector {
|
||||
|
||||
/**
|
||||
* Plugins we need to import from.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $needs_import = [];
|
||||
|
||||
/**
|
||||
* Detects whether we need to import anything.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function detect() {
|
||||
foreach ( WPSEO_Plugin_Importers::get() as $importer_class ) {
|
||||
$importer = new $importer_class();
|
||||
$detect = new WPSEO_Import_Plugin( $importer, 'detect' );
|
||||
if ( $detect->status->status ) {
|
||||
$this->needs_import[ $importer_class ] = $importer->get_plugin_name();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Import_Plugin.
|
||||
*
|
||||
* Class with functionality to import Yoast SEO settings from other plugins.
|
||||
*/
|
||||
class WPSEO_Import_Plugin {
|
||||
|
||||
/**
|
||||
* Holds the status of and message about imports.
|
||||
*
|
||||
* @var WPSEO_Import_Status
|
||||
*/
|
||||
public $status;
|
||||
|
||||
/**
|
||||
* Class with functionality to import meta data from other plugins.
|
||||
*
|
||||
* @var WPSEO_Plugin_Importer
|
||||
*/
|
||||
protected $importer;
|
||||
|
||||
/**
|
||||
* Import class constructor.
|
||||
*
|
||||
* @param WPSEO_Plugin_Importer $importer The importer that needs to perform this action.
|
||||
* @param string $action The action to perform.
|
||||
*/
|
||||
public function __construct( WPSEO_Plugin_Importer $importer, $action ) {
|
||||
$this->importer = $importer;
|
||||
|
||||
switch ( $action ) {
|
||||
case 'cleanup':
|
||||
$this->status = $this->importer->run_cleanup();
|
||||
break;
|
||||
case 'import':
|
||||
$this->status = $this->importer->run_import();
|
||||
break;
|
||||
case 'detect':
|
||||
default:
|
||||
$this->status = $this->importer->run_detect();
|
||||
}
|
||||
|
||||
$this->status->set_msg( $this->complete_msg( $this->status->get_msg() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to replace %s with plugin name in import message.
|
||||
*
|
||||
* @param string $msg Message string.
|
||||
*
|
||||
* @return string Returns message with plugin name instead of replacement variables.
|
||||
*/
|
||||
protected function complete_msg( $msg ) {
|
||||
return sprintf( $msg, $this->importer->get_plugin_name() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Import
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Import_Settings.
|
||||
*
|
||||
* Class with functionality to import the Yoast SEO settings.
|
||||
*/
|
||||
class WPSEO_Import_Settings {
|
||||
|
||||
/**
|
||||
* Nonce action key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const NONCE_ACTION = 'wpseo-import-settings';
|
||||
|
||||
/**
|
||||
* Holds the import status instance.
|
||||
*
|
||||
* @var WPSEO_Import_Status
|
||||
*/
|
||||
public $status;
|
||||
|
||||
/**
|
||||
* Holds the old WPSEO version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $old_wpseo_version;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->status = new WPSEO_Import_Status( 'import', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the data submitted by the user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function import() {
|
||||
check_admin_referer( self::NONCE_ACTION );
|
||||
|
||||
if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST['settings_import'] ) || ! is_string( $_POST['settings_import'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: The raw content will be parsed afterwards.
|
||||
$content = wp_unslash( $_POST['settings_import'] );
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->parse_options( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the options.
|
||||
*
|
||||
* @param string $raw_options The content to parse.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parse_options( $raw_options ) {
|
||||
$options = parse_ini_string( $raw_options, true, INI_SCANNER_RAW );
|
||||
|
||||
if ( is_array( $options ) && $options !== [] ) {
|
||||
$this->import_options( $options );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->status->set_msg( __( 'Settings could not be imported:', 'wordpress-seo' ) . ' ' . __( 'No settings found.', 'wordpress-seo' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the option group and import it.
|
||||
*
|
||||
* @param string $name Name string.
|
||||
* @param array $option_group Option group data.
|
||||
* @param array $options Options data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parse_option_group( $name, $option_group, $options ) {
|
||||
// Make sure that the imported options are cleaned/converted on import.
|
||||
$option_instance = WPSEO_Options::get_option_instance( $name );
|
||||
if ( is_object( $option_instance ) && method_exists( $option_instance, 'import' ) ) {
|
||||
$option_instance->import( $option_group, $this->old_wpseo_version, $options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the options if found.
|
||||
*
|
||||
* @param array $options The options parsed from the provided settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function import_options( $options ) {
|
||||
if ( isset( $options['wpseo']['version'] ) && $options['wpseo']['version'] !== '' ) {
|
||||
$this->old_wpseo_version = $options['wpseo']['version'];
|
||||
}
|
||||
|
||||
foreach ( $options as $name => $option_group ) {
|
||||
$this->parse_option_group( $name, $option_group, $options );
|
||||
}
|
||||
|
||||
$this->status->set_msg( __( 'Settings successfully imported.', 'wordpress-seo' ) );
|
||||
$this->status->set_status( true );
|
||||
|
||||
// Reset the cached option values.
|
||||
WPSEO_Options::clear_cache();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Import
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_ImportStatus.
|
||||
*
|
||||
* Holds the status of and message about imports.
|
||||
*/
|
||||
class WPSEO_Import_Status {
|
||||
|
||||
/**
|
||||
* The import status.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $status = false;
|
||||
|
||||
/**
|
||||
* The import message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $msg = '';
|
||||
|
||||
/**
|
||||
* The type of action performed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $action;
|
||||
|
||||
/**
|
||||
* WPSEO_Import_Status constructor.
|
||||
*
|
||||
* @param string $action The type of import action.
|
||||
* @param bool $status The status of the import.
|
||||
* @param string $msg Extra messages about the status.
|
||||
*/
|
||||
public function __construct( $action, $status, $msg = '' ) {
|
||||
$this->action = $action;
|
||||
$this->status = $status;
|
||||
$this->msg = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the import message.
|
||||
*
|
||||
* @return string Message about current status.
|
||||
*/
|
||||
public function get_msg() {
|
||||
if ( $this->msg !== '' ) {
|
||||
return $this->msg;
|
||||
}
|
||||
|
||||
if ( $this->status === false ) {
|
||||
/* translators: %s is replaced with the name of the plugin we're trying to find data from. */
|
||||
return __( '%s data not found.', 'wordpress-seo' );
|
||||
}
|
||||
|
||||
return $this->get_default_success_message();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the import action.
|
||||
*
|
||||
* @return string Import action type.
|
||||
*/
|
||||
public function get_action() {
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the import action, set status to false.
|
||||
*
|
||||
* @param string $action The type of action to set as import action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_action( $action ) {
|
||||
$this->action = $action;
|
||||
$this->status = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the importer status message.
|
||||
*
|
||||
* @param string $msg The message to set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_msg( $msg ) {
|
||||
$this->msg = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the importer status.
|
||||
*
|
||||
* @param bool $status The status to set.
|
||||
*
|
||||
* @return WPSEO_Import_Status The current object.
|
||||
*/
|
||||
public function set_status( $status ) {
|
||||
$this->status = (bool) $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a success message depending on the action.
|
||||
*
|
||||
* @return string Returns a success message for the current action.
|
||||
*/
|
||||
private function get_default_success_message() {
|
||||
switch ( $this->action ) {
|
||||
case 'import':
|
||||
/* translators: %s is replaced with the name of the plugin we're importing data from. */
|
||||
return __( '%s data successfully imported.', 'wordpress-seo' );
|
||||
case 'cleanup':
|
||||
/* translators: %s is replaced with the name of the plugin we're removing data from. */
|
||||
return __( '%s data successfully removed.', 'wordpress-seo' );
|
||||
case 'detect':
|
||||
default:
|
||||
/* translators: %s is replaced with the name of the plugin we've found data from. */
|
||||
return __( '%s data found.', 'wordpress-seo' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
/**
|
||||
* This file holds the abstract class for dealing with imports from other plugins.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Plugin_Importer.
|
||||
*
|
||||
* Class with functionality to import meta data from other plugins.
|
||||
*/
|
||||
abstract class WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* Holds the import status object.
|
||||
*
|
||||
* @var WPSEO_Import_Status
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name;
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key;
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Returns the string for the plugin we're importing from.
|
||||
*
|
||||
* @return string Plugin name.
|
||||
*/
|
||||
public function get_plugin_name() {
|
||||
return $this->plugin_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the settings and post meta data from another SEO plugin.
|
||||
*
|
||||
* @return WPSEO_Import_Status Import status object.
|
||||
*/
|
||||
public function run_import() {
|
||||
$this->status = new WPSEO_Import_Status( 'import', false );
|
||||
|
||||
if ( ! $this->detect() ) {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
$this->status->set_status( $this->import() );
|
||||
|
||||
// Flush the entire cache, as we no longer know what's valid and what's not.
|
||||
wp_cache_flush();
|
||||
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles post meta data to import.
|
||||
*
|
||||
* @return bool Import success status.
|
||||
*/
|
||||
protected function import() {
|
||||
return $this->meta_keys_clone( $this->clone_keys );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the plugin data from the database.
|
||||
*
|
||||
* @return WPSEO_Import_Status Import status object.
|
||||
*/
|
||||
public function run_cleanup() {
|
||||
$this->status = new WPSEO_Import_Status( 'cleanup', false );
|
||||
|
||||
if ( ! $this->detect() ) {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
return $this->status->set_status( $this->cleanup() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the plugin data from the database.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
protected function cleanup() {
|
||||
global $wpdb;
|
||||
if ( empty( $this->meta_key ) ) {
|
||||
return true;
|
||||
}
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s",
|
||||
$this->meta_key
|
||||
)
|
||||
);
|
||||
$result = $wpdb->__get( 'result' );
|
||||
if ( ! $result ) {
|
||||
$this->cleanup_error_msg();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status message for when a cleanup has gone bad.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function cleanup_error_msg() {
|
||||
/* translators: %s is replaced with the plugin's name. */
|
||||
$this->status->set_msg( sprintf( __( 'Cleanup of %s data failed.', 'wordpress-seo' ), $this->plugin_name ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether an import for this plugin is needed.
|
||||
*
|
||||
* @return WPSEO_Import_Status Import status object.
|
||||
*/
|
||||
public function run_detect() {
|
||||
$this->status = new WPSEO_Import_Status( 'detect', false );
|
||||
|
||||
if ( ! $this->detect() ) {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
return $this->status->set_status( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether there is post meta data to import.
|
||||
*
|
||||
* @return bool Boolean indicating whether there is something to import.
|
||||
*/
|
||||
protected function detect() {
|
||||
global $wpdb;
|
||||
|
||||
$meta_keys = wp_list_pluck( $this->clone_keys, 'old_key' );
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) AS `count`
|
||||
FROM {$wpdb->postmeta}
|
||||
WHERE meta_key IN ( " . implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) ) . ' )',
|
||||
$meta_keys
|
||||
)
|
||||
);
|
||||
|
||||
if ( $result === '0' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to clone meta keys and (optionally) change their values in bulk.
|
||||
*
|
||||
* @param string $old_key The existing meta key.
|
||||
* @param string $new_key The new meta key.
|
||||
* @param array $replace_values An array, keys old value, values new values.
|
||||
*
|
||||
* @return bool Clone status.
|
||||
*/
|
||||
protected function meta_key_clone( $old_key, $new_key, $replace_values = [] ) {
|
||||
global $wpdb;
|
||||
|
||||
// First we create a temp table with all the values for meta_key.
|
||||
$result = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- This is intentional + temporary.
|
||||
"CREATE TEMPORARY TABLE tmp_meta_table SELECT * FROM {$wpdb->postmeta} WHERE meta_key = %s",
|
||||
$old_key
|
||||
)
|
||||
);
|
||||
if ( $result === false ) {
|
||||
$this->set_missing_db_rights_status();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete all the values in our temp table for posts that already have data for $new_key.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM tmp_meta_table WHERE post_id IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s )",
|
||||
WPSEO_Meta::$meta_prefix . $new_key
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* We set meta_id to NULL so on re-insert into the postmeta table, MYSQL can set
|
||||
* new meta_id's and we don't get duplicates.
|
||||
*/
|
||||
$wpdb->query( 'UPDATE tmp_meta_table SET meta_id = NULL' );
|
||||
|
||||
// Now we rename the meta_key.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'UPDATE tmp_meta_table SET meta_key = %s',
|
||||
WPSEO_Meta::$meta_prefix . $new_key
|
||||
)
|
||||
);
|
||||
|
||||
$this->meta_key_clone_replace( $replace_values );
|
||||
|
||||
// With everything done, we insert all our newly cloned lines into the postmeta table.
|
||||
$wpdb->query( "INSERT INTO {$wpdb->postmeta} SELECT * FROM tmp_meta_table" );
|
||||
|
||||
// Now we drop our temporary table.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- This is intentional + a temporary table.
|
||||
$wpdb->query( 'DROP TEMPORARY TABLE IF EXISTS tmp_meta_table' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones multiple meta keys.
|
||||
*
|
||||
* @param array $clone_keys The keys to clone.
|
||||
*
|
||||
* @return bool Success status.
|
||||
*/
|
||||
protected function meta_keys_clone( $clone_keys ) {
|
||||
foreach ( $clone_keys as $clone_key ) {
|
||||
$result = $this->meta_key_clone( $clone_key['old_key'], $clone_key['new_key'], ( $clone_key['convert'] ?? [] ) );
|
||||
if ( ! $result ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the import status to false and returns a message about why it failed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_missing_db_rights_status() {
|
||||
$this->status->set_status( false );
|
||||
/* translators: %s is replaced with Yoast SEO. */
|
||||
$this->status->set_msg( sprintf( __( 'The %s importer functionality uses temporary database tables. It seems your WordPress install does not have the capability to do this, please consult your hosting provider.', 'wordpress-seo' ), 'Yoast SEO' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to search for a key in an array and maybe save it as a meta field.
|
||||
*
|
||||
* @param string $plugin_key The key in the $data array to check.
|
||||
* @param string $yoast_key The identifier we use in our meta settings.
|
||||
* @param array $data The array of data for this post to sift through.
|
||||
* @param int $post_id The post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function import_meta_helper( $plugin_key, $yoast_key, $data, $post_id ) {
|
||||
if ( ! empty( $data[ $plugin_key ] ) ) {
|
||||
$this->maybe_save_post_meta( $yoast_key, $data[ $plugin_key ], $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a post meta value if it doesn't already exist.
|
||||
*
|
||||
* @param string $new_key The key to save.
|
||||
* @param mixed $value The value to set the key to.
|
||||
* @param int $post_id The Post to save the meta for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function maybe_save_post_meta( $new_key, $value, $post_id ) {
|
||||
// Big. Fat. Sigh. Mostly used for _yst_is_cornerstone, but might be useful for other hidden meta's.
|
||||
$key = WPSEO_Meta::$meta_prefix . $new_key;
|
||||
$wpseo_meta = true;
|
||||
if ( substr( $new_key, 0, 1 ) === '_' ) {
|
||||
$key = $new_key;
|
||||
$wpseo_meta = false;
|
||||
}
|
||||
|
||||
$existing_value = get_post_meta( $post_id, $key, true );
|
||||
if ( empty( $existing_value ) ) {
|
||||
if ( $wpseo_meta ) {
|
||||
WPSEO_Meta::set_value( $new_key, $value, $post_id );
|
||||
return;
|
||||
}
|
||||
update_post_meta( $post_id, $new_key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces values in our temporary table according to our settings.
|
||||
*
|
||||
* @param array $replace_values Key value pair of values to replace with other values.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function meta_key_clone_replace( $replace_values ) {
|
||||
global $wpdb;
|
||||
|
||||
// Now we replace values if needed.
|
||||
if ( is_array( $replace_values ) && $replace_values !== [] ) {
|
||||
foreach ( $replace_values as $old_value => $new_value ) {
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'UPDATE tmp_meta_table SET meta_value = %s WHERE meta_value = %s',
|
||||
$new_value,
|
||||
$old_value
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from All in One SEO Pack, versions 4 and up.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Cleanup_Action;
|
||||
use Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Posts_Importing_Action;
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean All in One SEO Pack post metadata, versions 4 and up.
|
||||
*/
|
||||
class WPSEO_Import_AIOSEO_V4 extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'All In One SEO Pack';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_aioseo_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_aioseo_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_aioseo_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_aioseo_og_title',
|
||||
'new_key' => 'opengraph-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_aioseo_og_description',
|
||||
'new_key' => 'opengraph-description',
|
||||
],
|
||||
[
|
||||
'old_key' => '_aioseo_twitter_title',
|
||||
'new_key' => 'twitter-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_aioseo_twitter_description',
|
||||
'new_key' => 'twitter-description',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Mapping between the AiOSEO replace vars and the Yoast replace vars.
|
||||
*
|
||||
* @see https://yoast.com/help/list-available-snippet-variables-yoast-seo/
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $replace_vars = [
|
||||
// They key is the AiOSEO replace var, the value is the Yoast replace var (see class-wpseo-replace-vars).
|
||||
'#author_first_name' => '%%author_first_name%%',
|
||||
'#author_last_name' => '%%author_last_name%%',
|
||||
'#author_name' => '%%name%%',
|
||||
'#categories' => '%%category%%',
|
||||
'#current_date' => '%%currentdate%%',
|
||||
'#current_day' => '%%currentday%%',
|
||||
'#current_month' => '%%currentmonth%%',
|
||||
'#current_year' => '%%currentyear%%',
|
||||
'#permalink' => '%%permalink%%',
|
||||
'#post_content' => '%%post_content%%',
|
||||
'#post_date' => '%%date%%',
|
||||
'#post_day' => '%%post_day%%',
|
||||
'#post_month' => '%%post_month%%',
|
||||
'#post_title' => '%%title%%',
|
||||
'#post_year' => '%%post_year%%',
|
||||
'#post_excerpt_only' => '%%excerpt_only%%',
|
||||
'#post_excerpt' => '%%excerpt%%',
|
||||
'#separator_sa' => '%%sep%%',
|
||||
'#site_title' => '%%sitename%%',
|
||||
'#tagline' => '%%sitedesc%%',
|
||||
'#taxonomy_title' => '%%category_title%%',
|
||||
];
|
||||
|
||||
/**
|
||||
* Replaces the AiOSEO variables in our temporary table with Yoast variables (replace vars).
|
||||
*
|
||||
* @param array $replace_values Key value pair of values to replace with other values. This is only used in the base class but not here.
|
||||
* That is because this class doesn't have any `convert` keys in `$clone_keys`.
|
||||
* For that reason, we're overwriting the base class' `meta_key_clone_replace()` function without executing that base functionality.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function meta_key_clone_replace( $replace_values ) {
|
||||
global $wpdb;
|
||||
|
||||
// At this point we're already looping through all the $clone_keys (this happens in meta_keys_clone() in the abstract class).
|
||||
// Now, we'll also loop through the replace_vars array, which holds the mappings between the AiOSEO variables and the Yoast variables.
|
||||
// We'll replace all the AiOSEO variables in the temporary table with their Yoast equivalents.
|
||||
foreach ( $this->replace_vars as $aioseo_variable => $yoast_variable ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: We need this query and this is done at many other places as well, for example class-import-rankmath.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'UPDATE tmp_meta_table SET meta_value = REPLACE( meta_value, %s, %s )',
|
||||
$aioseo_variable,
|
||||
$yoast_variable
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// The AiOSEO custom fields take the form of `#custom_field-myfield`.
|
||||
// These should be mapped to %%cf_myfield%%.
|
||||
$meta_values_with_custom_fields = $this->get_meta_values_with_custom_field_or_taxonomy( $wpdb, 'custom_field' );
|
||||
$unique_custom_fields = $this->get_unique_custom_fields_or_taxonomies( $meta_values_with_custom_fields, 'custom_field' );
|
||||
$this->replace_custom_field_or_taxonomy_replace_vars( $unique_custom_fields, $wpdb, 'custom_field', 'cf' );
|
||||
|
||||
// Map `#tax_name-{tax-slug}` to `%%ct_{tax-slug}%%``.
|
||||
$meta_values_with_custom_taxonomies = $this->get_meta_values_with_custom_field_or_taxonomy( $wpdb, 'tax_name' );
|
||||
$unique_custom_taxonomies = $this->get_unique_custom_fields_or_taxonomies( $meta_values_with_custom_taxonomies, 'tax_name' );
|
||||
$this->replace_custom_field_or_taxonomy_replace_vars( $unique_custom_taxonomies, $wpdb, 'tax_name', 'ct' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out all unique custom fields/taxonomies/etc. used in an AiOSEO replace var.
|
||||
*
|
||||
* @param string[] $meta_values An array of all the meta values that
|
||||
* contain one or more AIOSEO custom field replace vars
|
||||
* (in the form `#custom_field-xyz`).
|
||||
* @param string $aioseo_prefix The AiOSEO prefix to use
|
||||
* (e.g. `custom-field` for custom fields or `tax_name` for custom taxonomies).
|
||||
*
|
||||
* @return string[] An array of all the unique custom fields/taxonomies/etc. used in the replace vars.
|
||||
* E.g. `xyz` in the above example.
|
||||
*/
|
||||
protected function get_unique_custom_fields_or_taxonomies( $meta_values, $aioseo_prefix ) {
|
||||
$unique_custom_fields_or_taxonomies = [];
|
||||
|
||||
foreach ( $meta_values as $meta_value ) {
|
||||
// Find all custom field replace vars, store them in `$matches`.
|
||||
preg_match_all(
|
||||
"/#$aioseo_prefix-([\w-]+)/",
|
||||
$meta_value,
|
||||
$matches
|
||||
);
|
||||
|
||||
/*
|
||||
* `$matches[1]` contain the captured matches of the
|
||||
* first capturing group (the `([\w-]+)` in the regex above).
|
||||
*/
|
||||
$custom_fields_or_taxonomies = $matches[1];
|
||||
|
||||
foreach ( $custom_fields_or_taxonomies as $custom_field_or_taxonomy ) {
|
||||
$unique_custom_fields_or_taxonomies[ trim( $custom_field_or_taxonomy ) ] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys( $unique_custom_fields_or_taxonomies );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces every AIOSEO custom field/taxonomy/etc. replace var with the Yoast version.
|
||||
*
|
||||
* E.g. `#custom_field-xyz` becomes `%%cf_xyz%%`.
|
||||
*
|
||||
* @param string[] $unique_custom_fields_or_taxonomies An array of unique custom fields to replace the replace vars of.
|
||||
* @param wpdb $wpdb The WordPress database object.
|
||||
* @param string $aioseo_prefix The AiOSEO prefix to use
|
||||
* (e.g. `custom-field` for custom fields or `tax_name` for custom taxonomies).
|
||||
* @param string $yoast_prefix The Yoast prefix to use (e.g. `cf` for custom fields).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function replace_custom_field_or_taxonomy_replace_vars( $unique_custom_fields_or_taxonomies, $wpdb, $aioseo_prefix, $yoast_prefix ) {
|
||||
foreach ( $unique_custom_fields_or_taxonomies as $unique_custom_field_or_taxonomy ) {
|
||||
$aioseo_variable = "#{$aioseo_prefix}-{$unique_custom_field_or_taxonomy}";
|
||||
$yoast_variable = "%%{$yoast_prefix}_{$unique_custom_field_or_taxonomy}%%";
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'UPDATE tmp_meta_table SET meta_value = REPLACE( meta_value, %s, %s )',
|
||||
$aioseo_variable,
|
||||
$yoast_variable
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
|
||||
/**
|
||||
* Retrieve all the meta values from the temporary meta table that contain
|
||||
* at least one AiOSEO custom field replace var.
|
||||
*
|
||||
* @param wpdb $wpdb The WordPress database object.
|
||||
* @param string $aioseo_prefix The AiOSEO prefix to use
|
||||
* (e.g. `custom-field` for custom fields or `tax_name` for custom taxonomies).
|
||||
*
|
||||
* @return string[] All meta values that contain at least one AioSEO custom field replace var.
|
||||
*/
|
||||
protected function get_meta_values_with_custom_field_or_taxonomy( $wpdb, $aioseo_prefix ) {
|
||||
return $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
'SELECT meta_value FROM tmp_meta_table WHERE meta_value LIKE %s',
|
||||
"%#$aioseo_prefix-%"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
|
||||
/**
|
||||
* Detects whether there is AIOSEO data to import by looking whether the AIOSEO data have been cleaned up.
|
||||
*
|
||||
* @return bool Boolean indicating whether there is something to import.
|
||||
*/
|
||||
protected function detect() {
|
||||
$aioseo_cleanup_action = YoastSEO()->classes->get( Aioseo_Cleanup_Action::class );
|
||||
return ( $aioseo_cleanup_action->get_total_unindexed() > 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import AIOSEO post data from their custom indexable table. Not currently used.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function import() {
|
||||
// This is overriden from the import.js and never run.
|
||||
$aioseo_posts_import_action = YoastSEO()->classes->get( Aioseo_Posts_Importing_Action::class );
|
||||
$aioseo_posts_import_action->index();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from All in One SEO Pack, versions 3 and under.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean All in One SEO Pack post metadata, versions 3 and under.
|
||||
*/
|
||||
class WPSEO_Import_AIOSEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'All In One SEO Pack';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_aioseop_%';
|
||||
|
||||
/**
|
||||
* OpenGraph keys to import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $import_keys = [
|
||||
'aioseop_opengraph_settings_title' => 'opengraph-title',
|
||||
'aioseop_opengraph_settings_desc' => 'opengraph-description',
|
||||
'aioseop_opengraph_settings_customimg' => 'opengraph-image',
|
||||
'aioseop_opengraph_settings_customimg_twitter' => 'twitter-image',
|
||||
];
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_aioseop_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_aioseop_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_aioseop_noindex',
|
||||
'new_key' => 'meta-robots-noindex',
|
||||
'convert' => [ 'on' => 1 ],
|
||||
],
|
||||
[
|
||||
'old_key' => '_aioseop_nofollow',
|
||||
'new_key' => 'meta-robots-nofollow',
|
||||
'convert' => [ 'on' => 1 ],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Import All In One SEO meta values.
|
||||
*
|
||||
* @return bool Import success status.
|
||||
*/
|
||||
protected function import() {
|
||||
$status = parent::import();
|
||||
if ( $status ) {
|
||||
$this->import_opengraph();
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the OpenGraph and Twitter settings for all posts.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function import_opengraph() {
|
||||
$query_posts = new WP_Query( 'post_type=any&meta_key=_aioseop_opengraph_settings&order=ASC&fields=ids&nopaging=true' );
|
||||
|
||||
if ( ! empty( $query_posts->posts ) ) {
|
||||
foreach ( array_values( $query_posts->posts ) as $post_id ) {
|
||||
$this->import_post_opengraph( $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the OpenGraph and Twitter settings for a single post.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_post_opengraph( $post_id ) {
|
||||
$meta = get_post_meta( $post_id, '_aioseop_opengraph_settings', true );
|
||||
$meta = maybe_unserialize( $meta );
|
||||
|
||||
foreach ( $this->import_keys as $old_key => $new_key ) {
|
||||
$this->maybe_save_post_meta( $new_key, $meta[ $old_key ], $post_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from Ultimate SEO.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean Ultimate SEO post metadata.
|
||||
*/
|
||||
class WPSEO_Import_Greg_SEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = "Greg's High Performance SEO";
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_ghpseo_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_ghpseo_alternative_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_ghpseo_secondary_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from HeadSpace.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Import_HeadSpace.
|
||||
*
|
||||
* Class with functionality to import & clean HeadSpace SEO post metadata.
|
||||
*/
|
||||
class WPSEO_Import_HeadSpace extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'HeadSpace SEO';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_headspace_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_headspace_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_headspace_page_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_headspace_noindex',
|
||||
'new_key' => 'meta-robots-noindex',
|
||||
'convert' => [ 'on' => 1 ],
|
||||
],
|
||||
[
|
||||
'old_key' => '_headspace_nofollow',
|
||||
'new_key' => 'meta-robots-nofollow',
|
||||
'convert' => [ 'on' => 1 ],
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from Jetpack's Advanced SEO settings.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Import_Jetpack_SEO.
|
||||
*
|
||||
* Class with functionality to import & clean Jetpack SEO post metadata.
|
||||
*/
|
||||
class WPSEO_Import_Jetpack_SEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'Jetpack';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = 'advanced_seo_description';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => 'advanced_seo_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from Platinum SEO Pack.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean Ultimate SEO post metadata.
|
||||
*/
|
||||
class WPSEO_Import_Platinum_SEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'Platinum SEO Pack';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = 'title';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => 'description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => 'title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Runs the import of post meta keys stored by Platinum SEO Pack.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function import() {
|
||||
$return = parent::import();
|
||||
if ( $return ) {
|
||||
$this->import_robots_meta();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up all the meta values Platinum SEO pack creates.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function cleanup() {
|
||||
$this->meta_key = 'title';
|
||||
parent::cleanup();
|
||||
|
||||
$this->meta_key = 'description';
|
||||
parent::cleanup();
|
||||
|
||||
$this->meta_key = 'metarobots';
|
||||
parent::cleanup();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all the robotsmeta fields to import and deals with them.
|
||||
*
|
||||
* There are four potential values that Platinum SEO stores:
|
||||
* - index,folllow
|
||||
* - index,nofollow
|
||||
* - noindex,follow
|
||||
* - noindex,nofollow
|
||||
*
|
||||
* We only have to deal with the latter 3, the first is our default.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function import_robots_meta() {
|
||||
$this->import_by_meta_robots( 'index,nofollow', [ 'nofollow' ] );
|
||||
$this->import_by_meta_robots( 'noindex,follow', [ 'noindex' ] );
|
||||
$this->import_by_meta_robots( 'noindex,nofollow', [ 'noindex', 'nofollow' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the values for all index, nofollow posts.
|
||||
*
|
||||
* @param string $value The meta robots value to find posts for.
|
||||
* @param array $metas The meta field(s) to save.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function import_by_meta_robots( $value, $metas ) {
|
||||
$posts = $this->find_posts_by_robots_meta( $value );
|
||||
if ( ! $posts ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $posts as $post_id ) {
|
||||
foreach ( $metas as $meta ) {
|
||||
$this->maybe_save_post_meta( 'meta-robots-' . $meta, 1, $post_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds posts by a given meta robots value.
|
||||
*
|
||||
* @param string $meta_value Robots meta value.
|
||||
*
|
||||
* @return array|bool Array of Post IDs on success, false on failure.
|
||||
*/
|
||||
protected function find_posts_by_robots_meta( $meta_value ) {
|
||||
$posts = get_posts(
|
||||
[
|
||||
'post_type' => 'any',
|
||||
'meta_key' => 'robotsmeta',
|
||||
'meta_value' => $meta_value,
|
||||
'order' => 'ASC',
|
||||
'fields' => 'ids',
|
||||
'nopaging' => true,
|
||||
]
|
||||
);
|
||||
if ( empty( $posts ) ) {
|
||||
return false;
|
||||
}
|
||||
return $posts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from Premium SEO Pack.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean Premium SEO Pack post metadata.
|
||||
*/
|
||||
class WPSEO_Import_Premium_SEO_Pack extends WPSEO_Import_Squirrly {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'Premium SEO Pack';
|
||||
|
||||
/**
|
||||
* WPSEO_Import_Premium_SEO_Pack constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
global $wpdb;
|
||||
$this->table_name = $wpdb->prefix . 'psp';
|
||||
$this->meta_key = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query to return an identifier for the posts to import.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function retrieve_posts_query() {
|
||||
return "SELECT URL AS identifier FROM {$this->table_name} WHERE blog_id = %d";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from RankMath.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import RankMath post metadata.
|
||||
*/
|
||||
class WPSEO_Import_RankMath extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'RankMath';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = 'rank_math_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => 'rank_math_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_canonical_url',
|
||||
'new_key' => 'canonical',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_primary_category',
|
||||
'new_key' => 'primary_category',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_facebook_title',
|
||||
'new_key' => 'opengraph-title',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_facebook_description',
|
||||
'new_key' => 'opengraph-description',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_facebook_image',
|
||||
'new_key' => 'opengraph-image',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_facebook_image_id',
|
||||
'new_key' => 'opengraph-image-id',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_twitter_title',
|
||||
'new_key' => 'twitter-title',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_twitter_description',
|
||||
'new_key' => 'twitter-description',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_twitter_image',
|
||||
'new_key' => 'twitter-image',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_twitter_image_id',
|
||||
'new_key' => 'twitter-image-id',
|
||||
],
|
||||
[
|
||||
'old_key' => 'rank_math_focus_keyword',
|
||||
'new_key' => 'focuskw',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Handles post meta data to import.
|
||||
*
|
||||
* @return bool Import success status.
|
||||
*/
|
||||
protected function import() {
|
||||
global $wpdb;
|
||||
// Replace % with %% as their variables are the same except for that.
|
||||
$wpdb->query( "UPDATE $wpdb->postmeta SET meta_value = REPLACE( meta_value, '%', '%%' ) WHERE meta_key IN ( 'rank_math_description', 'rank_math_title' )" );
|
||||
|
||||
$this->import_meta_robots();
|
||||
$return = $this->meta_keys_clone( $this->clone_keys );
|
||||
|
||||
// Return %% to % so our import is non-destructive.
|
||||
$wpdb->query( "UPDATE $wpdb->postmeta SET meta_value = REPLACE( meta_value, '%%', '%' ) WHERE meta_key IN ( 'rank_math_description', 'rank_math_title' )" );
|
||||
|
||||
if ( $return ) {
|
||||
$this->import_settings();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* RankMath stores robots meta quite differently, so we have to parse it out.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_meta_robots() {
|
||||
global $wpdb;
|
||||
$post_metas = $wpdb->get_results( "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = 'rank_math_robots'" );
|
||||
foreach ( $post_metas as $post_meta ) {
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- Reason: We can't control the form in which Rankmath sends the data.
|
||||
$robots_values = unserialize( $post_meta->meta_value );
|
||||
foreach ( [ 'noindex', 'nofollow' ] as $directive ) {
|
||||
$directive_key = array_search( $directive, $robots_values, true );
|
||||
if ( $directive_key !== false ) {
|
||||
update_post_meta( $post_meta->post_id, '_yoast_wpseo_meta-robots-' . $directive, 1 );
|
||||
unset( $robots_values[ $directive_key ] );
|
||||
}
|
||||
}
|
||||
if ( count( $robots_values ) > 0 ) {
|
||||
$value = implode( ',', $robots_values );
|
||||
update_post_meta( $post_meta->post_id, '_yoast_wpseo_meta-robots-adv', $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports some of the RankMath settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_settings() {
|
||||
$settings = [
|
||||
'title_separator' => 'separator',
|
||||
'homepage_title' => 'title-home-wpseo',
|
||||
'homepage_description' => 'metadesc-home-wpseo',
|
||||
'author_archive_title' => 'title-author-wpseo',
|
||||
'date_archive_title' => 'title-archive-wpseo',
|
||||
'search_title' => 'title-search-wpseo',
|
||||
'404_title' => 'title-404-wpseo',
|
||||
'pt_post_title' => 'title-post',
|
||||
'pt_page_title' => 'title-page',
|
||||
];
|
||||
$options = get_option( 'rank-math-options-titles' );
|
||||
|
||||
foreach ( $settings as $import_setting_key => $setting_key ) {
|
||||
if ( ! empty( $options[ $import_setting_key ] ) ) {
|
||||
$value = $options[ $import_setting_key ];
|
||||
// Make sure replace vars work.
|
||||
$value = str_replace( '%', '%%', $value );
|
||||
WPSEO_Options::set( $setting_key, $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the plugin data from the database.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
protected function cleanup() {
|
||||
$return = parent::cleanup();
|
||||
if ( $return ) {
|
||||
global $wpdb;
|
||||
$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'rank-math-%'" );
|
||||
$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '%rank_math%'" );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from SEO Framework.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean SEO Framework post metadata.
|
||||
*/
|
||||
class WPSEO_Import_SEO_Framework extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'The SEO Framework';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_genesis_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_genesis_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_genesis_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_genesis_noindex',
|
||||
'new_key' => 'meta-robots-noindex',
|
||||
],
|
||||
[
|
||||
'old_key' => '_genesis_nofollow',
|
||||
'new_key' => 'meta-robots-nofollow',
|
||||
],
|
||||
[
|
||||
'old_key' => '_genesis_canonical_uri',
|
||||
'new_key' => 'canonical',
|
||||
],
|
||||
[
|
||||
'old_key' => '_open_graph_title',
|
||||
'new_key' => 'opengraph-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_open_graph_description',
|
||||
'new_key' => 'opengraph-description',
|
||||
],
|
||||
[
|
||||
'old_key' => '_social_image_url',
|
||||
'new_key' => 'opengraph-image',
|
||||
],
|
||||
[
|
||||
'old_key' => '_twitter_title',
|
||||
'new_key' => 'twitter-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_twitter_description',
|
||||
'new_key' => 'twitter-description',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Removes all the metadata set by the SEO Framework plugin.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function cleanup() {
|
||||
$set1 = parent::cleanup();
|
||||
|
||||
$this->meta_key = '_social_image_%';
|
||||
$set2 = parent::cleanup();
|
||||
|
||||
$this->meta_key = '_twitter_%';
|
||||
$set3 = parent::cleanup();
|
||||
|
||||
$this->meta_key = '_open_graph_%';
|
||||
$set4 = parent::cleanup();
|
||||
|
||||
return ( $set1 || $set2 || $set3 || $set4 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from SEOPressor.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Import_SEOPressor.
|
||||
*
|
||||
* Class with functionality to import & clean SEOPressor post metadata.
|
||||
*/
|
||||
class WPSEO_Import_SEOPressor extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'SEOpressor';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_seop_settings';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_seop_settings',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Imports the post meta values to Yoast SEO.
|
||||
*
|
||||
* @return bool Import success status.
|
||||
*/
|
||||
protected function import() {
|
||||
// Query for all the posts that have an _seop_settings meta set.
|
||||
$query_posts = new WP_Query( 'post_type=any&meta_key=_seop_settings&order=ASC&fields=ids&nopaging=true' );
|
||||
foreach ( $query_posts->posts as $post_id ) {
|
||||
$this->import_post_focus_keywords( $post_id );
|
||||
$this->import_seopressor_post_settings( $post_id );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the post meta fields SEOpressor creates.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
protected function cleanup() {
|
||||
global $wpdb;
|
||||
|
||||
// If we get to replace the data, let's do some proper cleanup.
|
||||
return $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_seop_%'" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the data. SEOpressor stores most of the data in one post array, this loops over it.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_seopressor_post_settings( $post_id ) {
|
||||
$settings = get_post_meta( $post_id, '_seop_settings', true );
|
||||
|
||||
foreach (
|
||||
[
|
||||
'fb_description' => 'opengraph-description',
|
||||
'fb_title' => 'opengraph-title',
|
||||
'fb_type' => 'og_type',
|
||||
'fb_img' => 'opengraph-image',
|
||||
'meta_title' => 'title',
|
||||
'meta_description' => 'metadesc',
|
||||
'meta_canonical' => 'canonical',
|
||||
'tw_description' => 'twitter-description',
|
||||
'tw_title' => 'twitter-title',
|
||||
'tw_image' => 'twitter-image',
|
||||
] as $seopressor_key => $yoast_key ) {
|
||||
$this->import_meta_helper( $seopressor_key, $yoast_key, $settings, $post_id );
|
||||
}
|
||||
|
||||
if ( isset( $settings['meta_rules'] ) ) {
|
||||
$this->import_post_robots( $settings['meta_rules'], $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the focus keywords, and stores them for later use.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_post_focus_keywords( $post_id ) {
|
||||
// Import the focus keyword.
|
||||
$focuskw = trim( get_post_meta( $post_id, '_seop_kw_1', true ) );
|
||||
$this->maybe_save_post_meta( 'focuskw', $focuskw, $post_id );
|
||||
|
||||
// Import additional focus keywords for use in premium.
|
||||
$focuskw2 = trim( get_post_meta( $post_id, '_seop_kw_2', true ) );
|
||||
$focuskw3 = trim( get_post_meta( $post_id, '_seop_kw_3', true ) );
|
||||
|
||||
$focus_keywords = [];
|
||||
if ( ! empty( $focuskw2 ) ) {
|
||||
$focus_keywords[] = $focuskw2;
|
||||
}
|
||||
if ( ! empty( $focuskw3 ) ) {
|
||||
$focus_keywords[] = $focuskw3;
|
||||
}
|
||||
|
||||
if ( $focus_keywords !== [] ) {
|
||||
$this->maybe_save_post_meta( 'focuskeywords', WPSEO_Utils::format_json_encode( $focus_keywords ), $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the SEOpressor robot value and map this to Yoast SEO values.
|
||||
*
|
||||
* @param string $meta_rules The meta rules taken from the SEOpressor settings array.
|
||||
* @param int $post_id The post id of the current post.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_post_robots( $meta_rules, $post_id ) {
|
||||
$seopressor_robots = explode( '#|#|#', $meta_rules );
|
||||
$robot_value = $this->get_robot_value( $seopressor_robots );
|
||||
|
||||
// Saving the new meta values for Yoast SEO.
|
||||
$this->maybe_save_post_meta( 'meta-robots-noindex', $robot_value['index'], $post_id );
|
||||
$this->maybe_save_post_meta( 'meta-robots-nofollow', $robot_value['follow'], $post_id );
|
||||
$this->maybe_save_post_meta( 'meta-robots-adv', $robot_value['advanced'], $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the robot config by given SEOpressor robots value.
|
||||
*
|
||||
* @param array $seopressor_robots The value in SEOpressor that needs to be converted to the Yoast format.
|
||||
*
|
||||
* @return array The robots values in Yoast format.
|
||||
*/
|
||||
private function get_robot_value( $seopressor_robots ) {
|
||||
$return = [
|
||||
'index' => 2,
|
||||
'follow' => 0,
|
||||
'advanced' => '',
|
||||
];
|
||||
|
||||
if ( in_array( 'noindex', $seopressor_robots, true ) ) {
|
||||
$return['index'] = 1;
|
||||
}
|
||||
if ( in_array( 'nofollow', $seopressor_robots, true ) ) {
|
||||
$return['follow'] = 1;
|
||||
}
|
||||
foreach ( [ 'noarchive', 'nosnippet', 'noimageindex' ] as $needle ) {
|
||||
if ( in_array( $needle, $seopressor_robots, true ) ) {
|
||||
$return['advanced'] .= $needle . ',';
|
||||
}
|
||||
}
|
||||
$return['advanced'] = rtrim( $return['advanced'], ',' );
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from Smartcrawl SEO.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean Smartcrawl SEO post metadata.
|
||||
*/
|
||||
class WPSEO_Import_Smartcrawl_SEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'Smartcrawl SEO';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_wds_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_wds_metadesc',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wds_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wds_canonical',
|
||||
'new_key' => 'canonical',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wds_focus-keywords',
|
||||
'new_key' => 'focuskw',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wds_meta-robots-noindex',
|
||||
'new_key' => 'meta-robots-noindex',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wds_meta-robots-nofollow',
|
||||
'new_key' => 'meta-robots-nofollow',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Used for importing Twitter and Facebook meta's.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $social_keys = [];
|
||||
|
||||
/**
|
||||
* Handles post meta data to import.
|
||||
*
|
||||
* @return bool Import success status.
|
||||
*/
|
||||
protected function import() {
|
||||
$return = parent::import();
|
||||
if ( $return ) {
|
||||
$this->import_opengraph();
|
||||
$this->import_twitter();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the OpenGraph meta keys saved by Smartcrawl.
|
||||
*
|
||||
* @return bool Import status.
|
||||
*/
|
||||
protected function import_opengraph() {
|
||||
$this->social_keys = [
|
||||
'title' => 'opengraph-title',
|
||||
'description' => 'opengraph-description',
|
||||
'images' => 'opengraph-image',
|
||||
];
|
||||
return $this->post_find_import( '_wds_opengraph' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the Twitter meta keys saved by Smartcrawl.
|
||||
*
|
||||
* @return bool Import status.
|
||||
*/
|
||||
protected function import_twitter() {
|
||||
$this->social_keys = [
|
||||
'title' => 'twitter-title',
|
||||
'description' => 'twitter-description',
|
||||
];
|
||||
return $this->post_find_import( '_wds_twitter' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a post's serialized post meta values.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param string $key The meta key to import.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function import_serialized_post_meta( $post_id, $key ) {
|
||||
$data = get_post_meta( $post_id, $key, true );
|
||||
$data = maybe_unserialize( $data );
|
||||
foreach ( $this->social_keys as $key => $meta_key ) {
|
||||
if ( ! isset( $data[ $key ] ) ) {
|
||||
return;
|
||||
}
|
||||
$value = $data[ $key ];
|
||||
if ( is_array( $value ) ) {
|
||||
$value = $value[0];
|
||||
}
|
||||
$this->maybe_save_post_meta( $meta_key, $value, $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all the posts with a certain meta key and imports its values.
|
||||
*
|
||||
* @param string $key The meta key to search for.
|
||||
*
|
||||
* @return bool Import status.
|
||||
*/
|
||||
protected function post_find_import( $key ) {
|
||||
$query_posts = new WP_Query( 'post_type=any&meta_key=' . $key . '&order=ASC&fields=ids&nopaging=true' );
|
||||
|
||||
if ( empty( $query_posts->posts ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( array_values( $query_posts->posts ) as $post_id ) {
|
||||
$this->import_serialized_post_meta( $post_id, $key );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from Squirrly.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean Squirrly post metadata.
|
||||
*/
|
||||
class WPSEO_Import_Squirrly extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'Squirrly SEO';
|
||||
|
||||
/**
|
||||
* Holds the name of the table Squirrly uses to store data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table_name;
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_sq_post_keyword';
|
||||
|
||||
/**
|
||||
* Data to import from (and the target to field) the serialized array stored in the SEO field in the Squirrly table.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $seo_field_keys = [
|
||||
'noindex' => 'meta-robots-noindex',
|
||||
'nofollow' => 'meta-robots-nofollow',
|
||||
'title' => 'title',
|
||||
'description' => 'metadesc',
|
||||
'canonical' => 'canonical',
|
||||
'cornerstone' => '_yst_is_cornerstone',
|
||||
'tw_media' => 'twitter-image',
|
||||
'tw_title' => 'twitter-title',
|
||||
'tw_description' => 'twitter-description',
|
||||
'og_title' => 'opengraph-title',
|
||||
'og_description' => 'opengraph-description',
|
||||
'og_media' => 'opengraph-image',
|
||||
'focuskw' => 'focuskw',
|
||||
];
|
||||
|
||||
/**
|
||||
* WPSEO_Import_Squirrly constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
global $wpdb;
|
||||
$this->table_name = $wpdb->prefix . 'qss';
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the post meta values to Yoast SEO.
|
||||
*
|
||||
* @return bool Import success status.
|
||||
*/
|
||||
protected function import() {
|
||||
$results = $this->retrieve_posts();
|
||||
foreach ( $results as $post ) {
|
||||
$return = $this->import_post_values( $post->identifier );
|
||||
if ( ! $return ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the posts from the Squirrly Database.
|
||||
*
|
||||
* @return array Array of post IDs from the DB.
|
||||
*/
|
||||
protected function retrieve_posts() {
|
||||
global $wpdb;
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
$this->retrieve_posts_query(),
|
||||
get_current_blog_id()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query to return an identifier for the posts to import.
|
||||
*
|
||||
* @return string Query to get post ID's from the DB.
|
||||
*/
|
||||
protected function retrieve_posts_query() {
|
||||
return "SELECT post_id AS identifier FROM {$this->table_name} WHERE blog_id = %d";
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the DB table and the post meta field Squirrly creates.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
protected function cleanup() {
|
||||
global $wpdb;
|
||||
|
||||
// If we can clean, let's clean.
|
||||
$wpdb->query( "DROP TABLE {$this->table_name}" );
|
||||
|
||||
// This removes the post meta field for the focus keyword from the DB.
|
||||
parent::cleanup();
|
||||
|
||||
// If we can still see the table, something went wrong.
|
||||
if ( $this->detect() ) {
|
||||
$this->cleanup_error_msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether there is post meta data to import.
|
||||
*
|
||||
* @return bool Boolean indicating whether there is something to import.
|
||||
*/
|
||||
protected function detect() {
|
||||
global $wpdb;
|
||||
|
||||
$result = $wpdb->get_var( "SHOW TABLES LIKE '{$this->table_name}'" );
|
||||
if ( is_wp_error( $result ) || $result === null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the data of a post out of Squirrly's DB table.
|
||||
*
|
||||
* @param mixed $post_identifier Post identifier, can be ID or string.
|
||||
*
|
||||
* @return bool Import status.
|
||||
*/
|
||||
private function import_post_values( $post_identifier ) {
|
||||
$data = $this->retrieve_post_data( $post_identifier );
|
||||
if ( ! $data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! is_numeric( $post_identifier ) ) {
|
||||
$post_id = url_to_postid( $post_identifier );
|
||||
}
|
||||
|
||||
if ( is_numeric( $post_identifier ) ) {
|
||||
$post_id = (int) $post_identifier;
|
||||
$data['focuskw'] = $this->maybe_add_focus_kw( $post_identifier );
|
||||
}
|
||||
|
||||
foreach ( $this->seo_field_keys as $squirrly_key => $yoast_key ) {
|
||||
$this->import_meta_helper( $squirrly_key, $yoast_key, $data, $post_id );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Squirrly SEO data for a post from the DB.
|
||||
*
|
||||
* @param int $post_identifier Post ID.
|
||||
*
|
||||
* @return array|bool Array of data or false.
|
||||
*/
|
||||
private function retrieve_post_data( $post_identifier ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( is_numeric( $post_identifier ) ) {
|
||||
$post_identifier = (int) $post_identifier;
|
||||
$query_where = 'post_id = %d';
|
||||
}
|
||||
if ( ! is_numeric( $post_identifier ) ) {
|
||||
$query_where = 'URL = %s';
|
||||
}
|
||||
|
||||
$replacements = [
|
||||
get_current_blog_id(),
|
||||
$post_identifier,
|
||||
];
|
||||
|
||||
$data = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT seo FROM {$this->table_name} WHERE blog_id = %d AND " . $query_where,
|
||||
$replacements
|
||||
)
|
||||
);
|
||||
if ( ! $data || is_wp_error( $data ) ) {
|
||||
return false;
|
||||
}
|
||||
$data = maybe_unserialize( $data );
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Squirrly stores the focus keyword in post meta.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return string The focus keyword.
|
||||
*/
|
||||
private function maybe_add_focus_kw( $post_id ) {
|
||||
$focuskw = get_post_meta( $post_id, '_sq_post_keyword', true );
|
||||
if ( $focuskw ) {
|
||||
$focuskw = json_decode( $focuskw );
|
||||
return $focuskw->keyword;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from Ultimate SEO.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean Ultimate SEO post metadata.
|
||||
*/
|
||||
class WPSEO_Import_Ultimate_SEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'Ultimate SEO';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_su_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_su_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_su_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_su_og_title',
|
||||
'new_key' => 'opengraph-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_su_og_description',
|
||||
'new_key' => 'opengraph-description',
|
||||
],
|
||||
[
|
||||
'old_key' => '_su_og_image',
|
||||
'new_key' => 'opengraph-image',
|
||||
],
|
||||
[
|
||||
'old_key' => '_su_meta_robots_noindex',
|
||||
'new_key' => 'meta-robots-noindex',
|
||||
'convert' => [ 'on' => 1 ],
|
||||
],
|
||||
[
|
||||
'old_key' => '_su_meta_robots_nofollow',
|
||||
'new_key' => 'meta-robots-nofollow',
|
||||
'convert' => [ 'on' => 1 ],
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from WooThemes SEO.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Import_WooThemes_SEO
|
||||
*
|
||||
* Class with functionality to import & clean WooThemes SEO post metadata.
|
||||
*/
|
||||
class WPSEO_Import_WooThemes_SEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'WooThemes SEO';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = 'seo_title';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => 'seo_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => 'seo_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => 'seo_noindex',
|
||||
'new_key' => 'meta-robots-noindex',
|
||||
],
|
||||
[
|
||||
'old_key' => 'seo_follow',
|
||||
'new_key' => 'meta-robots-nofollow',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the meta fields we can delete after import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cleanup_metas = [
|
||||
'seo_follow',
|
||||
'seo_noindex',
|
||||
'seo_title',
|
||||
'seo_description',
|
||||
'seo_keywords',
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the options we can delete after import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cleanup_options = [
|
||||
'seo_woo_archive_layout',
|
||||
'seo_woo_single_layout',
|
||||
'seo_woo_page_layout',
|
||||
'seo_woo_wp_title',
|
||||
'seo_woo_meta_single_desc',
|
||||
'seo_woo_meta_single_key',
|
||||
'seo_woo_home_layout',
|
||||
];
|
||||
|
||||
/**
|
||||
* Cleans up the WooThemes SEO settings.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
protected function cleanup() {
|
||||
$result = $this->cleanup_meta();
|
||||
if ( $result ) {
|
||||
$this->cleanup_options();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Woo Options from the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanup_options() {
|
||||
foreach ( $this->cleanup_options as $option ) {
|
||||
delete_option( $option );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the post meta fields from the database.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
private function cleanup_meta() {
|
||||
foreach ( $this->cleanup_metas as $key ) {
|
||||
$result = $this->cleanup_meta_key( $key );
|
||||
if ( ! $result ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single meta field from the postmeta table in the database.
|
||||
*
|
||||
* @param string $key The meta_key to delete.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
private function cleanup_meta_key( $key ) {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s",
|
||||
$key
|
||||
)
|
||||
);
|
||||
return $wpdb->__get( 'result' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from WP Meta SEO.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class with functionality to import & clean WP Meta SEO post metadata.
|
||||
*/
|
||||
class WPSEO_Import_WP_Meta_SEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'WP Meta SEO';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_metaseo_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_metaseo_metadesc',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metatitle',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metaopengraph-title',
|
||||
'new_key' => 'opengraph-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metaopengraph-desc',
|
||||
'new_key' => 'opengraph-description',
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metaopengraph-image',
|
||||
'new_key' => 'opengraph-image',
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metatwitter-title',
|
||||
'new_key' => 'twitter-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metatwitter-desc',
|
||||
'new_key' => 'twitter-description',
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metatwitter-image',
|
||||
'new_key' => 'twitter-image',
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metaindex',
|
||||
'new_key' => 'meta-robots-noindex',
|
||||
'convert' => [
|
||||
'index' => 0,
|
||||
'noindex' => 1,
|
||||
],
|
||||
],
|
||||
[
|
||||
'old_key' => '_metaseo_metafollow',
|
||||
'new_key' => 'meta-robots-nofollow',
|
||||
'convert' => [
|
||||
'follow' => 0,
|
||||
'nofollow' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
/**
|
||||
* File with the class to handle data from wpSEO.de.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Import_WPSEO.
|
||||
*
|
||||
* Class with functionality to import & clean wpSEO.de post metadata.
|
||||
*/
|
||||
class WPSEO_Import_WPSEO extends WPSEO_Plugin_Importer {
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_name = 'wpSEO.de';
|
||||
|
||||
/**
|
||||
* Meta key, used in SQL LIKE clause for delete query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $meta_key = '_wpseo_edit_%';
|
||||
|
||||
/**
|
||||
* Array of meta keys to detect and import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $clone_keys = [
|
||||
[
|
||||
'old_key' => '_wpseo_edit_description',
|
||||
'new_key' => 'metadesc',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wpseo_edit_title',
|
||||
'new_key' => 'title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wpseo_edit_canonical',
|
||||
'new_key' => 'canonical',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wpseo_edit_og_title',
|
||||
'new_key' => 'opengraph-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wpseo_edit_og_description',
|
||||
'new_key' => 'opengraph-description',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wpseo_edit_og_image',
|
||||
'new_key' => 'opengraph-image',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wpseo_edit_twittercard_title',
|
||||
'new_key' => 'twitter-title',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wpseo_edit_twittercard_description',
|
||||
'new_key' => 'twitter-description',
|
||||
],
|
||||
[
|
||||
'old_key' => '_wpseo_edit_twittercard_image',
|
||||
'new_key' => 'twitter-image',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The values 1 - 6 are the configured values from wpSEO. This array will map the values of wpSEO to our values.
|
||||
*
|
||||
* There are some double array like 1-6 and 3-4. The reason is they only set the index value. The follow value is
|
||||
* the default we use in the cases there isn't a follow value present.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $robot_values = [
|
||||
// In wpSEO: index, follow.
|
||||
1 => [
|
||||
'index' => 2,
|
||||
'follow' => 0,
|
||||
],
|
||||
// In wpSEO: index, nofollow.
|
||||
2 => [
|
||||
'index' => 2,
|
||||
'follow' => 1,
|
||||
],
|
||||
// In wpSEO: noindex.
|
||||
3 => [
|
||||
'index' => 1,
|
||||
'follow' => 0,
|
||||
],
|
||||
// In wpSEO: noindex, follow.
|
||||
4 => [
|
||||
'index' => 1,
|
||||
'follow' => 0,
|
||||
],
|
||||
// In wpSEO: noindex, nofollow.
|
||||
5 => [
|
||||
'index' => 1,
|
||||
'follow' => 1,
|
||||
],
|
||||
// In wpSEO: index.
|
||||
6 => [
|
||||
'index' => 2,
|
||||
'follow' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Imports wpSEO settings.
|
||||
*
|
||||
* @return bool Import success status.
|
||||
*/
|
||||
protected function import() {
|
||||
$status = parent::import();
|
||||
if ( $status ) {
|
||||
$this->import_post_robots();
|
||||
$this->import_taxonomy_metas();
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes wpseo.de post meta's.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
protected function cleanup() {
|
||||
$this->cleanup_term_meta();
|
||||
$result = $this->cleanup_post_meta();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether there is post meta data to import.
|
||||
*
|
||||
* @return bool Boolean indicating whether there is something to import.
|
||||
*/
|
||||
protected function detect() {
|
||||
if ( parent::detect() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE 'wpseo_category_%'" );
|
||||
if ( $count !== '0' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the robot values from WPSEO plugin. These have to be converted to the Yoast format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_post_robots() {
|
||||
$query_posts = new WP_Query( 'post_type=any&meta_key=_wpseo_edit_robots&order=ASC&fields=ids&nopaging=true' );
|
||||
|
||||
if ( ! empty( $query_posts->posts ) ) {
|
||||
foreach ( array_values( $query_posts->posts ) as $post_id ) {
|
||||
$this->import_post_robot( $post_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the wpSEO robot value and map this to Yoast SEO values.
|
||||
*
|
||||
* @param int $post_id The post id of the current post.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_post_robot( $post_id ) {
|
||||
$wpseo_robots = get_post_meta( $post_id, '_wpseo_edit_robots', true );
|
||||
$robot_value = $this->get_robot_value( $wpseo_robots );
|
||||
|
||||
// Saving the new meta values for Yoast SEO.
|
||||
$this->maybe_save_post_meta( 'meta-robots-noindex', $robot_value['index'], $post_id );
|
||||
$this->maybe_save_post_meta( 'meta-robots-nofollow', $robot_value['follow'], $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the taxonomy metas from wpSEO.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_taxonomy_metas() {
|
||||
$terms = get_terms(
|
||||
[
|
||||
'taxonomy' => get_taxonomies(),
|
||||
'hide_empty' => false,
|
||||
]
|
||||
);
|
||||
$tax_meta = get_option( 'wpseo_taxonomy_meta' );
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
$this->import_taxonomy_description( $tax_meta, $term->taxonomy, $term->term_id );
|
||||
$this->import_taxonomy_robots( $tax_meta, $term->taxonomy, $term->term_id );
|
||||
}
|
||||
|
||||
update_option( 'wpseo_taxonomy_meta', $tax_meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the meta description to Yoast SEO.
|
||||
*
|
||||
* @param array $tax_meta The array with the current metadata.
|
||||
* @param string $taxonomy String with the name of the taxonomy.
|
||||
* @param string $term_id The ID of the current term.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_taxonomy_description( &$tax_meta, $taxonomy, $term_id ) {
|
||||
$description = get_option( 'wpseo_' . $taxonomy . '_' . $term_id, false );
|
||||
if ( $description !== false ) {
|
||||
// Import description.
|
||||
$tax_meta[ $taxonomy ][ $term_id ]['wpseo_desc'] = $description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the robot value to Yoast SEO.
|
||||
*
|
||||
* @param array $tax_meta The array with the current metadata.
|
||||
* @param string $taxonomy String with the name of the taxonomy.
|
||||
* @param string $term_id The ID of the current term.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function import_taxonomy_robots( &$tax_meta, $taxonomy, $term_id ) {
|
||||
$wpseo_robots = get_option( 'wpseo_' . $taxonomy . '_' . $term_id . '_robots', false );
|
||||
if ( $wpseo_robots === false ) {
|
||||
return;
|
||||
}
|
||||
// The value 1, 2 and 6 are the index values in wpSEO.
|
||||
$new_robot_value = 'noindex';
|
||||
|
||||
if ( in_array( (int) $wpseo_robots, [ 1, 2, 6 ], true ) ) {
|
||||
$new_robot_value = 'index';
|
||||
}
|
||||
|
||||
$tax_meta[ $taxonomy ][ $term_id ]['wpseo_noindex'] = $new_robot_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the wpSEO taxonomy meta data.
|
||||
*
|
||||
* @param string $taxonomy String with the name of the taxonomy.
|
||||
* @param string $term_id The ID of the current term.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function delete_taxonomy_metas( $taxonomy, $term_id ) {
|
||||
delete_option( 'wpseo_' . $taxonomy . '_' . $term_id );
|
||||
delete_option( 'wpseo_' . $taxonomy . '_' . $term_id . '_robots' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the robot config by given wpSEO robots value.
|
||||
*
|
||||
* @param string $wpseo_robots The value in wpSEO that needs to be converted to the Yoast format.
|
||||
*
|
||||
* @return string The correct robot value.
|
||||
*/
|
||||
private function get_robot_value( $wpseo_robots ) {
|
||||
if ( array_key_exists( $wpseo_robots, $this->robot_values ) ) {
|
||||
return $this->robot_values[ $wpseo_robots ];
|
||||
}
|
||||
|
||||
return $this->robot_values[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes wpSEO postmeta from the database.
|
||||
*
|
||||
* @return bool Cleanup status.
|
||||
*/
|
||||
private function cleanup_post_meta() {
|
||||
global $wpdb;
|
||||
|
||||
// If we get to replace the data, let's do some proper cleanup.
|
||||
return $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_wpseo_edit_%'" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the wpSEO term meta.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanup_term_meta() {
|
||||
$terms = get_terms(
|
||||
[
|
||||
'taxonomy' => get_taxonomies(),
|
||||
'hide_empty' => false,
|
||||
]
|
||||
);
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
$this->delete_taxonomy_metas( $term->taxonomy, $term->term_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Import\Plugins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Plugin_Importers.
|
||||
*
|
||||
* Object which contains all importers.
|
||||
*/
|
||||
class WPSEO_Plugin_Importers {
|
||||
|
||||
/**
|
||||
* List of supported importers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $importers = [
|
||||
'WPSEO_Import_AIOSEO',
|
||||
'WPSEO_Import_AIOSEO_V4',
|
||||
'WPSEO_Import_Greg_SEO',
|
||||
'WPSEO_Import_HeadSpace',
|
||||
'WPSEO_Import_Jetpack_SEO',
|
||||
'WPSEO_Import_WP_Meta_SEO',
|
||||
'WPSEO_Import_Platinum_SEO',
|
||||
'WPSEO_Import_Premium_SEO_Pack',
|
||||
'WPSEO_Import_RankMath',
|
||||
'WPSEO_Import_SEOPressor',
|
||||
'WPSEO_Import_SEO_Framework',
|
||||
'WPSEO_Import_Smartcrawl_SEO',
|
||||
'WPSEO_Import_Squirrly',
|
||||
'WPSEO_Import_Ultimate_SEO',
|
||||
'WPSEO_Import_WooThemes_SEO',
|
||||
'WPSEO_Import_WPSEO',
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns an array of importers available.
|
||||
*
|
||||
* @return array Available importers.
|
||||
*/
|
||||
public static function get() {
|
||||
return self::$importers;
|
||||
}
|
||||
}
|
||||
4
wp-content/plugins/wordpress-seo/admin/index.php
Normal file
4
wp-content/plugins/wordpress-seo/admin/index.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
/**
|
||||
* Nothing to see here.
|
||||
*/
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface that represents a collection.
|
||||
*/
|
||||
interface WPSEO_Collection {
|
||||
|
||||
/**
|
||||
* Returns the collection data.
|
||||
*
|
||||
* @return array The collection data.
|
||||
*/
|
||||
public function get();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the interface for an installable object.
|
||||
*/
|
||||
interface WPSEO_Installable {
|
||||
|
||||
/**
|
||||
* Runs the installation routine.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function install();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user