File manager - Edit - /home/decorea/www/wp-includes/certificates/XML/admin.tar
Back
notifiers/interface-notification-handler.php 0000644 00000000665 15231501436 0015325 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Notifiers */ /** * Dictates the required methods for a Notification Handler implementation. */ interface WPSEO_Notification_Handler { /** * Handles the notification object. * * @param Yoast_Notification_Center $notification_center The notification center object. * * @return void */ public function handle( Yoast_Notification_Center $notification_center ); } notifiers/dismissible-notification.php 0000644 00000006147 15231501436 0014262 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Notifiers */ /** * Abstract class representing a dismissible notification. */ abstract class WPSEO_Dismissible_Notification implements WPSEO_Listener, WPSEO_Notification_Handler { /** * The identifier for the notification. * * @var string */ protected $notification_identifier = ''; /** * Retrieves instance of a notification. * * @return Yoast_Notification The notification. */ abstract protected function get_notification(); /** * Listens to an argument in the request URL and triggers an action. * * @return void */ public function listen() { if ( $this->get_listener_value() !== $this->notification_identifier ) { return; } $this->dismiss(); } /** * Adds the notification if applicable, otherwise removes it. * * @param Yoast_Notification_Center $notification_center The notification center object. * * @return void */ public function handle( Yoast_Notification_Center $notification_center ) { if ( $this->is_applicable() ) { $notification = $this->get_notification(); $notification_center->add_notification( $notification ); return; } $notification_center->remove_notification_by_id( 'wpseo-' . $this->notification_identifier ); } /** * Listens to an argument in the request URL and triggers an action. * * @return void */ protected function dismiss() { $this->set_dismissal_state(); $this->redirect_to_dashboard(); } /** * Checks if a notice is applicable. * * @return bool Whether a notice should be shown or not. */ protected function is_applicable() { return $this->is_notice_dismissed() === false; } /** * Checks whether the notification has been dismissed. * * @codeCoverageIgnore * * @return bool True when notification is dismissed. */ protected function is_notice_dismissed() { return get_user_meta( get_current_user_id(), 'wpseo-remove-' . $this->notification_identifier, true ) === '1'; } /** * Retrieves the value where listener is listening for. * * @codeCoverageIgnore * * @return string|null The listener value or null if not set. */ protected function get_listener_value() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: Normally we would need to check for a nonce here but this class is not used anymore. if ( isset( $_GET['yoast_dismiss'] ) && is_string( $_GET['yoast_dismiss'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: Normally we would need to check for a nonce here but this class is not used anymore. return sanitize_text_field( wp_unslash( $_GET['yoast_dismiss'] ) ); } return null; } /** * Dismisses the notification. * * @codeCoverageIgnore * * @return void */ protected function set_dismissal_state() { update_user_meta( get_current_user_id(), 'wpseo-remove-' . $this->notification_identifier, true ); } /** * Redirects the user back to the dashboard. * * @codeCoverageIgnore * * @return void */ protected function redirect_to_dashboard() { wp_safe_redirect( admin_url( 'admin.php?page=wpseo_dashboard' ) ); exit; } } class-meta-columns.php 0000644 00000066501 15231501436 0010774 0 ustar 00 <?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></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></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 ); } } metabox/class-metabox-collapsible.php 0000644 00000003021 15231501436 0013741 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates the HTML for a metabox tab. */ class WPSEO_Metabox_Collapsible implements WPSEO_Metabox_Tab { /** * The collapsible's unique identifier. * * @var string */ private $name; /** * The content to be displayed inside the collapsible. * * @var string */ private $content; /** * The label. * * @var string */ private $link_content; /** * Constructor. * * @param string $name The name of the tab, used as an identifier in the html. * @param string $content The tab content. * @param string $link_content The text content of the tab link. */ public function __construct( $name, $content, $link_content ) { $this->name = $name; $this->content = $content; $this->link_content = $link_content; } /** * Returns the html for the tab link. * * @return string */ public function link() { return $this->link_content; } /** * Returns the html for the tab content. * * @return string */ public function content() { $collapsible_paper = new WPSEO_Paper_Presenter( $this->link(), null, [ 'content' => $this->content, 'collapsible' => true, 'class' => 'metabox wpseo-form wpseo-collapsible-container', 'paper_id' => 'collapsible-' . $this->name, ] ); return $collapsible_paper->get_output(); } /** * Returns the collapsible's unique identifier. * * @return string */ public function get_name() { return $this->name; } } metabox/interface-metabox-analysis.php 0000644 00000001267 15231501436 0014140 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Metabox */ /** * Describes an interface for an analysis that can either be enabled or disabled. */ interface WPSEO_Metabox_Analysis { /** * Whether this analysis is enabled. * * @return bool Whether or not this analysis is enabled. */ public function is_enabled(); /** * Whether or not this analysis is enabled by the user. * * @return bool Whether or not this analysis is enabled by the user. */ public function is_user_enabled(); /** * Whether or not this analysis is enabled globally. * * @return bool Whether or not this analysis is enabled globally. */ public function is_globally_enabled(); } metabox/class-metabox-form-tab.php 0000644 00000005440 15231501436 0013166 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates the HTML for a metabox tab. */ class WPSEO_Metabox_Form_Tab implements WPSEO_Metabox_Tab { /** * The tab identifier. * * @var string */ private $name; /** * The tab content. * * @var string */ private $content; /** * The tab link content. * * @var string */ private $link_content; /** * Additional tab content class. * * @var string */ private $tab_class; /** * Additional tab link class. * * @var string */ private $link_class; /** * Title attribute on the link span. * * @var string */ private $link_title; /** * Arial label attribute on the link span. * * @var string */ private $link_aria_label; /** * Does it contain a single tab. * * @var bool */ private $single; /** * Constructor. * * @param string $name The name of the tab, used as an identifier in the html. * @param string $content The tab content. * @param string $link_content The text content of the tab link. * @param array $options Optional link attributes. */ public function __construct( $name, $content, $link_content, array $options = [] ) { $default_options = [ 'tab_class' => '', 'link_class' => '', 'link_title' => '', 'link_aria_label' => '', 'single' => false, ]; $options = array_merge( $default_options, $options ); $this->name = $name; $this->content = $content; $this->link_content = $link_content; $this->tab_class = $options['tab_class']; $this->link_class = $options['link_class']; $this->link_title = $options['link_title']; $this->link_aria_label = $options['link_aria_label']; $this->single = $options['single']; } /** * Returns the html for the tab link. * * @return string */ public function link() { $html = '<li class="%1$s%2$s"><a class="wpseo_tablink%3$s" href="#wpseo_%1$s"%4$s%5$s>%6$s</a></li>'; if ( $this->single ) { $html = '<li class="%1$s%2$s"><span class="wpseo_tablink%3$s"%4$s%5$s>%6$s</span></li>'; } return sprintf( $html, esc_attr( $this->name ), ( $this->tab_class !== '' ) ? ' ' . esc_attr( $this->tab_class ) : '', ( $this->link_class !== '' ) ? ' ' . esc_attr( $this->link_class ) : '', ( $this->link_title !== '' ) ? ' title="' . esc_attr( $this->link_title ) . '"' : '', ( $this->link_aria_label !== '' ) ? ' aria-label="' . esc_attr( $this->link_aria_label ) . '"' : '', $this->link_content ); } /** * Returns the html for the tab content. * * @return string */ public function content() { return sprintf( '<div id="%1$s" class="wpseotab %2$s">%3$s</div>', esc_attr( 'wpseo_' . $this->name ), esc_attr( $this->name ), $this->content ); } } metabox/class-abstract-sectioned-metabox-tab.php 0000644 00000004406 15231501436 0016002 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Base class for metabox that consist of multiple sections. */ abstract class WPSEO_Abstract_Metabox_Tab_With_Sections implements WPSEO_Metabox_Section { /** * Holds the name of the tab. * * @var string */ public $name; /** * Holds the HTML of the tab header. * * @var string */ protected $link_content; /** * Holds the name of the tab header. * * @var string */ protected $link_title; /** * Holds the classname of the tab header. * * @var string */ protected $link_class; /** * Holds the aria label of the tab header. * * @var string */ protected $link_aria_label; /** * Constructor. * * @param string $name The name of the section, used as an identifier in the html. * Can only contain URL safe characters. * @param string $link_content The text content of the section link. * @param array $options Optional link attributes. */ public function __construct( $name, $link_content, array $options = [] ) { $default_options = [ 'link_title' => '', 'link_class' => '', 'link_aria_label' => '', ]; $options = array_merge( $default_options, $options ); $this->name = $name; $this->link_content = $link_content; $this->link_title = $options['link_title']; $this->link_class = $options['link_class']; $this->link_aria_label = $options['link_aria_label']; } /** * Outputs the section link if any section has been added. * * @return void */ public function display_link() { if ( $this->has_sections() ) { printf( '<li role="presentation"><a role="tab" href="#wpseo-meta-section-%1$s" id="wpseo-meta-tab-%1$s" aria-controls="wpseo-meta-section-%1$s" class="wpseo-meta-section-link %2$s"%3$s%4$s>%5$s</a></li>', esc_attr( $this->name ), esc_attr( $this->link_class ), ( $this->link_title !== '' ) ? ' title="' . esc_attr( $this->link_title ) . '"' : '', ( $this->link_aria_label !== '' ) ? ' aria-label="' . esc_attr( $this->link_aria_label ) . '"' : '', $this->link_content ); } } /** * Checks whether the tab has any sections. * * @return bool Whether the tab has any sections */ abstract protected function has_sections(); } metabox/class-metabox-null-tab.php 0000644 00000000651 15231501436 0013174 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates the HTML for a metabox tab. */ class WPSEO_Metabox_Null_Tab implements WPSEO_Metabox_Tab { /** * Returns the html for the tab link. * * @return string|null */ public function link() { return null; } /** * Returns the html for the tab content. * * @return string|null */ public function content() { return null; } } metabox/class-metabox.php 0000644 00000115371 15231501436 0011466 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository; use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter; use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter; /** * This class generates the metabox on the edit post / page as well as contains all page analysis functionality. */ class WPSEO_Metabox extends WPSEO_Meta { /** * Whether the social tab is enabled. * * @var bool */ private $social_is_enabled; /** * Helper to determine whether the SEO analysis is enabled. * * @var WPSEO_Metabox_Analysis_SEO */ protected $seo_analysis; /** * Helper to determine whether the readability analysis is enabled. * * @var WPSEO_Metabox_Analysis_Readability */ protected $readability_analysis; /** * Helper to determine whether the inclusive language analysis is enabled. * * @var WPSEO_Metabox_Analysis_Inclusive_Language */ protected $inclusive_language_analysis; /** * The metabox editor object. * * @var WPSEO_Metabox_Editor */ protected $editor; /** * The Metabox post. * * @var WP_Post */ protected $post = null; /** * Whether the advanced metadata is enabled. * * @var bool */ protected $is_advanced_metadata_enabled; /** * Class constructor. */ public function __construct() { if ( $this->is_internet_explorer() ) { add_action( 'add_meta_boxes', [ $this, 'internet_explorer_metabox' ] ); return; } add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); add_action( 'wp_insert_post', [ $this, 'save_postdata' ] ); add_action( 'edit_attachment', [ $this, 'save_postdata' ] ); add_action( 'add_attachment', [ $this, 'save_postdata' ] ); $this->editor = new WPSEO_Metabox_Editor(); $this->editor->register_hooks(); $this->social_is_enabled = WPSEO_Options::get( 'opengraph', false, [ 'wpseo_social' ] ) || WPSEO_Options::get( 'twitter', false, [ 'wpseo_social' ] ); $this->is_advanced_metadata_enabled = WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) || WPSEO_Options::get( 'disableadvanced_meta', null, [ 'wpseo' ] ) === false; $this->seo_analysis = new WPSEO_Metabox_Analysis_SEO(); $this->readability_analysis = new WPSEO_Metabox_Analysis_Readability(); $this->inclusive_language_analysis = new WPSEO_Metabox_Analysis_Inclusive_Language(); } /** * Checks whether the request comes from an IE 11 browser. * * @return bool Whether the request comes from an IE 11 browser. */ public static function is_internet_explorer() { if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) { return false; } $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); if ( stripos( $user_agent, 'Trident/7.0' ) === false ) { return false; } return true; } /** * Adds an alternative metabox for internet explorer users. * * @return void */ public function internet_explorer_metabox() { $post_types = WPSEO_Post_Type::get_accessible_post_types(); $post_types = array_filter( $post_types, [ $this, 'display_metabox' ] ); if ( ! is_array( $post_types ) || $post_types === [] ) { return; } $product_title = $this->get_product_title(); foreach ( $post_types as $post_type ) { add_filter( "postbox_classes_{$post_type}_wpseo_meta", [ $this, 'wpseo_metabox_class' ] ); add_meta_box( 'wpseo_meta', $product_title, [ $this, 'render_internet_explorer_notice' ], $post_type, 'normal', apply_filters( 'wpseo_metabox_prio', 'high' ), [ '__block_editor_compatible_meta_box' => true ] ); } } /** * Renders the content for the internet explorer metabox. * * @return void */ public function render_internet_explorer_notice() { $content = sprintf( /* translators: 1: Link start tag to the Firefox website, 2: Link start tag to the Chrome website, 3: Link start tag to the Edge website, 4: Link closing tag. */ esc_html__( 'The browser you are currently using is unfortunately rather dated. Since we strive to give you the best experience possible, we no longer support this browser. Instead, please use %1$sFirefox%4$s, %2$sChrome%4$s or %3$sMicrosoft Edge%4$s.', 'wordpress-seo' ), '<a href="https://www.mozilla.org/firefox/new/">', '<a href="https://www.google.com/chrome/">', '<a href="https://www.microsoft.com/windows/microsoft-edge">', '</a>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above. echo new Alert_Presenter( $content ); } /** * Translates text strings for use in the meta box. * * IMPORTANT: if you want to add a new string (option) somewhere, make sure you add that array key to * the main meta box definition array in the class WPSEO_Meta() as well!!!! * * @deprecated 23.5 * @codeCoverageIgnore * * @return void */ public static function translate_meta_boxes() { _deprecated_function( __METHOD__, 'Yoast SEO 23.5' ); WPSEO_Meta::$meta_fields['general']['title']['title'] = __( 'SEO title', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['general']['metadesc']['title'] = __( 'Meta description', 'wordpress-seo' ); /* translators: %s expands to the post type name. */ WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['title'] = __( 'Allow search engines to show this %s in search results?', 'wordpress-seo' ); if ( (string) get_option( 'blog_public' ) === '0' ) { WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['description'] = '<span class="error-message">' . __( 'Warning: even though you can set the meta robots setting here, the entire site is set to noindex in the sitewide privacy settings, so these settings won\'t have an effect.', 'wordpress-seo' ) . '</span>'; } /* translators: %1$s expands to Yes or No, %2$s expands to the post type name.*/ WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['0'] = __( 'Default for %2$s, currently: %1$s', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['2'] = __( 'Yes', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['1'] = __( 'No', 'wordpress-seo' ); /* translators: %1$s expands to the post type name.*/ WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['title'] = __( 'Should search engines follow links on this %1$s?', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['options']['0'] = __( 'Yes', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['options']['1'] = __( 'No', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['title'] = __( 'Meta robots advanced', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['description'] = __( 'If you want to apply advanced <code>meta</code> robots settings for this page, please define them in the following field.', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['noimageindex'] = __( 'No Image Index', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['noarchive'] = __( 'No Archive', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['nosnippet'] = __( 'No Snippet', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['bctitle']['title'] = __( 'Breadcrumbs Title', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['bctitle']['description'] = __( 'Title to use for this page in breadcrumb paths', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['canonical']['title'] = __( 'Canonical URL', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['canonical']['description'] = sprintf( /* translators: 1: link open tag; 2: link close tag. */ __( 'The canonical URL that this page should point to. Leave empty to default to permalink. %1$sCross domain canonical%2$s supported too.', 'wordpress-seo' ), '<a href="https://googlewebmastercentral.blogspot.com/2009/12/handling-legitimate-cross-domain.html" target="_blank" rel="noopener">', WPSEO_Admin_Utils::get_new_tab_message() . '</a>' ); WPSEO_Meta::$meta_fields['advanced']['redirect']['title'] = __( '301 Redirect', 'wordpress-seo' ); WPSEO_Meta::$meta_fields['advanced']['redirect']['description'] = __( 'The URL that this page should redirect to.', 'wordpress-seo' ); do_action_deprecated( 'wpseo_tab_translate', [], 'Yoast SEO 23.5', '', 'WPSEO_Metabox::translate_meta_boxes is deprecated.' ); } /** * Determines whether the metabox should be shown for the passed identifier. * * By default the check is done for post types, but can also be used for taxonomies. * * @param string|null $identifier The identifier to check. * @param string $type The type of object to check. Defaults to post_type. * * @return bool Whether or not the metabox should be displayed. */ public function display_metabox( $identifier = null, $type = 'post_type' ) { return WPSEO_Utils::is_metabox_active( $identifier, $type ); } /** * Adds the Yoast SEO meta box to the edit boxes in the edit post, page, * attachment, and custom post types pages. * * @return void */ public function add_meta_box() { $post_types = WPSEO_Post_Type::get_accessible_post_types(); $post_types = array_filter( $post_types, [ $this, 'display_metabox' ] ); if ( ! is_array( $post_types ) || $post_types === [] ) { return; } $product_title = $this->get_product_title(); foreach ( $post_types as $post_type ) { add_filter( "postbox_classes_{$post_type}_wpseo_meta", [ $this, 'wpseo_metabox_class' ] ); add_meta_box( 'wpseo_meta', $product_title, [ $this, 'meta_box' ], $post_type, 'normal', apply_filters( 'wpseo_metabox_prio', 'high' ), [ '__block_editor_compatible_meta_box' => true ] ); } } /** * Adds CSS classes to the meta box. * * @param string[] $classes An array of postbox CSS classes. * * @return string[] List of classes that will be applied to the editbox container. */ public function wpseo_metabox_class( $classes ) { $classes[] = 'yoast wpseo-metabox'; return $classes; } /** * Passes variables to js for use with the post-scraper. * * @return array<string,string|array<string|int|bool>|bool|int> */ public function get_metabox_script_data() { $permalink = $this->get_permalink(); $post_formatter = new WPSEO_Metabox_Formatter( new WPSEO_Post_Metabox_Formatter( $this->get_metabox_post(), [], $permalink ) ); $values = $post_formatter->get_values(); /** This filter is documented in admin/filters/class-cornerstone-filter.php. */ $post_types = apply_filters( 'wpseo_cornerstone_post_types', WPSEO_Post_Type::get_accessible_post_types() ); if ( $values['cornerstoneActive'] && ! in_array( $this->get_metabox_post()->post_type, $post_types, true ) ) { $values['cornerstoneActive'] = false; } if ( $values['semrushIntegrationActive'] && $this->post->post_type === 'attachment' ) { $values['semrushIntegrationActive'] = 0; } if ( $values['wincherIntegrationActive'] && $this->post->post_type === 'attachment' ) { $values['wincherIntegrationActive'] = 0; } return $values; } /** * Determines whether or not the current post type has registered taxonomies. * * @return bool Whether the current post type has taxonomies. */ private function current_post_type_has_taxonomies() { $post_taxonomies = get_object_taxonomies( get_post_type() ); return ! empty( $post_taxonomies ); } /** * Determines the scope based on the post type. * This can be used by the replacevar plugin to determine if a replacement needs to be executed. * * @return string String describing the current scope. */ private function determine_scope() { if ( $this->get_metabox_post()->post_type === 'page' ) { return 'page'; } return 'post'; } /** * Outputs the meta box. * * @return void */ public function meta_box() { $this->render_hidden_fields(); $this->render_tabs(); } /** * Renders the metabox hidden fields. * * @return void */ protected function render_hidden_fields() { wp_nonce_field( 'yoast_free_metabox', 'yoast_free_metabox_nonce' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class. echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'general' ); if ( $this->is_advanced_metadata_enabled ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class. echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'advanced' ); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class. echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'schema', $this->get_metabox_post()->post_type ); if ( $this->social_is_enabled ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class. echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'social' ); } /** * Filter: 'wpseo_content_meta_section_content' - Allow filtering the metabox content before outputting. * * @param string $post_content The metabox content string. */ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output should be escaped in the filter. echo apply_filters( 'wpseo_content_meta_section_content', '' ); } /** * Renders the metabox tabs. * * @return void */ protected function render_tabs() { echo '<div class="wpseo-metabox-content">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $this->get_product_title() returns a hard-coded string. printf( '<div class="wpseo-metabox-menu"><ul role="tablist" class="yoast-aria-tabs" aria-label="%s">', $this->get_product_title() ); $tabs = $this->get_tabs(); foreach ( $tabs as $tab ) { if ( $tab->name === 'premium' ) { continue; } $tab->display_link(); } echo '</ul></div>'; foreach ( $tabs as $tab ) { $tab->display_content(); } echo '</div>'; } /** * Returns the relevant metabox tabs for the current view. * * @return WPSEO_Metabox_Section[] */ private function get_tabs() { $tabs = []; $label = __( 'SEO', 'wordpress-seo' ); if ( $this->seo_analysis->is_enabled() ) { $label = '<span class="wpseo-score-icon-container" id="wpseo-seo-score-icon"></span>' . $label; } $tabs[] = new WPSEO_Metabox_Section_React( 'content', $label ); if ( $this->readability_analysis->is_enabled() ) { $tabs[] = new WPSEO_Metabox_Section_Readability(); } if ( $this->inclusive_language_analysis->is_enabled() ) { $tabs[] = new WPSEO_Metabox_Section_Inclusive_Language(); } if ( $this->is_advanced_metadata_enabled ) { $tabs[] = new WPSEO_Metabox_Section_React( 'schema', '<span class="wpseo-schema-icon"></span>' . __( 'Schema', 'wordpress-seo' ), '' ); } if ( $this->social_is_enabled ) { $tabs[] = new WPSEO_Metabox_Section_React( 'social', '<span class="dashicons dashicons-share"></span>' . __( 'Social', 'wordpress-seo' ), '', [ 'html_after' => '<div id="wpseo-section-social"></div>', ] ); } $tabs = array_merge( $tabs, $this->get_additional_tabs() ); return $tabs; } /** * Returns the metabox tabs that have been added by other plugins. * * @return WPSEO_Metabox_Section_Additional[] */ protected function get_additional_tabs() { $tabs = []; /** * Private filter: 'yoast_free_additional_metabox_sections'. * * Meant for internal use only. Allows adding additional tabs to the Yoast SEO metabox. * * @since 11.9 * * @param array[] $tabs { * An array of arrays with tab specifications. * * @type array $tab { * A tab specification. * * @type string $name The name of the tab. Used in the HTML IDs, href and aria properties. * @type string $link_content The content of the tab link. * @type string $content The content of the tab. * @type array $options { * Optional. Extra options. * * @type string $link_class Optional. The class for the tab link. * @type string $link_aria_label Optional. The aria label of the tab link. * } * } * } */ $requested_tabs = apply_filters( 'yoast_free_additional_metabox_sections', [] ); foreach ( $requested_tabs as $tab ) { if ( is_array( $tab ) && array_key_exists( 'name', $tab ) && array_key_exists( 'link_content', $tab ) && array_key_exists( 'content', $tab ) ) { $options = array_key_exists( 'options', $tab ) ? $tab['options'] : []; $tabs[] = new WPSEO_Metabox_Section_Additional( $tab['name'], $tab['link_content'], $tab['content'], $options ); } } return $tabs; } /** * Adds a line in the meta box. * * @deprecated 23.5 * @codeCoverageIgnore * * @param string[] $meta_field_def Contains the vars based on which output is generated. * @param string $key Internal key (without prefix). * * @return string */ public function do_meta_box( $meta_field_def, $key = '' ) { _deprecated_function( __METHOD__, 'Yoast SEO 23.5' ); $content = ''; $esc_form_key = esc_attr( WPSEO_Meta::$form_prefix . $key ); $meta_value = WPSEO_Meta::get_value( $key, $this->get_metabox_post()->ID ); $class = ''; if ( isset( $meta_field_def['class'] ) && $meta_field_def['class'] !== '' ) { $class = ' ' . $meta_field_def['class']; } $placeholder = ''; if ( isset( $meta_field_def['placeholder'] ) && $meta_field_def['placeholder'] !== '' ) { $placeholder = $meta_field_def['placeholder']; } $aria_describedby = ''; $description = ''; if ( isset( $meta_field_def['description'] ) ) { $aria_describedby = ' aria-describedby="' . $esc_form_key . '-desc"'; $description = '<p id="' . $esc_form_key . '-desc" class="yoast-metabox__description">' . $meta_field_def['description'] . '</p>'; } // Add a hide_on_pages option that returns nothing when the field is rendered on a page. if ( isset( $meta_field_def['hide_on_pages'] ) && $meta_field_def['hide_on_pages'] && get_post_type() === 'page' ) { return ''; } switch ( $meta_field_def['type'] ) { case 'text': $ac = ''; if ( isset( $meta_field_def['autocomplete'] ) && $meta_field_def['autocomplete'] === false ) { $ac = 'autocomplete="off" '; } if ( $placeholder !== '' ) { $placeholder = ' placeholder="' . esc_attr( $placeholder ) . '"'; } $content .= '<input type="text"' . $placeholder . ' id="' . $esc_form_key . '" ' . $ac . 'name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '" class="large-text' . $class . '"' . $aria_describedby . '/>'; break; case 'url': if ( $placeholder !== '' ) { $placeholder = ' placeholder="' . esc_attr( $placeholder ) . '"'; } $content .= '<input type="url"' . $placeholder . ' id="' . $esc_form_key . '" name="' . $esc_form_key . '" value="' . esc_attr( urldecode( $meta_value ) ) . '" class="large-text' . $class . '"' . $aria_describedby . '/>'; break; case 'textarea': $rows = 3; if ( isset( $meta_field_def['rows'] ) && $meta_field_def['rows'] > 0 ) { $rows = $meta_field_def['rows']; } $content .= '<textarea class="large-text' . $class . '" rows="' . esc_attr( $rows ) . '" id="' . $esc_form_key . '" name="' . $esc_form_key . '"' . $aria_describedby . '>' . esc_textarea( $meta_value ) . '</textarea>'; break; case 'hidden': $default = ''; if ( isset( $meta_field_def['default'] ) ) { $default = sprintf( ' data-default="%s"', esc_attr( $meta_field_def['default'] ) ); } $content .= '<input type="hidden" id="' . $esc_form_key . '" name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '"' . $default . '/>' . "\n"; break; case 'select': if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) { $content .= '<select name="' . $esc_form_key . '" id="' . $esc_form_key . '" class="yoast' . $class . '">'; foreach ( $meta_field_def['options'] as $val => $option ) { $selected = selected( $meta_value, $val, false ); $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>'; } unset( $val, $option, $selected ); $content .= '</select>'; } break; case 'multiselect': if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) { // Set $meta_value as $selected_arr. $selected_arr = $meta_value; // If the multiselect field is 'meta-robots-adv' we should explode on ,. if ( $key === 'meta-robots-adv' ) { $selected_arr = explode( ',', $meta_value ); } if ( ! is_array( $selected_arr ) ) { $selected_arr = (array) $selected_arr; } $options_count = count( $meta_field_def['options'] ); $content .= '<select multiple="multiple" size="' . esc_attr( $options_count ) . '" name="' . $esc_form_key . '[]" id="' . $esc_form_key . '" class="yoast' . $class . '"' . $aria_describedby . '>'; foreach ( $meta_field_def['options'] as $val => $option ) { $selected = ''; if ( in_array( $val, $selected_arr, true ) ) { $selected = ' selected="selected"'; } $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>'; } $content .= '</select>'; unset( $val, $option, $selected, $selected_arr, $options_count ); } break; case 'checkbox': $checked = checked( $meta_value, 'on', false ); $expl = ( isset( $meta_field_def['expl'] ) ) ? esc_html( $meta_field_def['expl'] ) : ''; $content .= '<input type="checkbox" id="' . $esc_form_key . '" name="' . $esc_form_key . '" ' . $checked . ' value="on" class="yoast' . $class . '"' . $aria_describedby . '/> <label for="' . $esc_form_key . '">' . $expl . '</label>'; unset( $checked, $expl ); break; case 'radio': if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) { foreach ( $meta_field_def['options'] as $val => $option ) { $checked = checked( $meta_value, $val, false ); $content .= '<input type="radio" ' . $checked . ' id="' . $esc_form_key . '_' . esc_attr( $val ) . '" name="' . $esc_form_key . '" value="' . esc_attr( $val ) . '"/> <label for="' . $esc_form_key . '_' . esc_attr( $val ) . '">' . esc_html( $option ) . '</label> '; } unset( $val, $option, $checked ); } break; } $html = ''; if ( $content === '' ) { $content = apply_filters_deprecated( 'wpseo_do_meta_box_field_' . $key, [ $content, $meta_value, $esc_form_key, $meta_field_def, $key ], 'Yoast SEO 23.5', '', 'do_meta_box is deprecated' ); } if ( $content !== '' ) { $title = esc_html( $meta_field_def['title'] ); // By default, use the field title as a label element. $label = '<label for="' . $esc_form_key . '">' . $title . '</label>'; // Set the inline help and help panel, if any. $help_button = ''; $help_panel = ''; if ( isset( $meta_field_def['help'] ) && $meta_field_def['help'] !== '' ) { $help = new WPSEO_Admin_Help_Panel( $key, $meta_field_def['help-button'], $meta_field_def['help'] ); $help_button = $help->get_button_html(); $help_panel = $help->get_panel_html(); } // If it's a set of radio buttons, output proper fieldset and legend. if ( $meta_field_def['type'] === 'radio' ) { return '<fieldset><legend>' . $title . '</legend>' . $help_button . $help_panel . $content . $description . '</fieldset>'; } // If it's a single checkbox, ignore the title. if ( $meta_field_def['type'] === 'checkbox' ) { $label = ''; } // Other meta box content or form fields. if ( $meta_field_def['type'] === 'hidden' ) { $html = $content; } else { $html = $label . $description . $help_button . $help_panel . $content; } } return $html; } /** * Saves the WP SEO metadata for posts. * * {@internal $_POST parameters are validated via sanitize_post_meta().}} * * @param int $post_id Post ID. * * @return bool|void Boolean false if invalid save post request. */ public function save_postdata( $post_id ) { // Bail if this is a multisite installation and the site has been switched. if ( is_multisite() && ms_is_switched() ) { return false; } if ( $post_id === null ) { return false; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in wp_verify_none. if ( ! isset( $_POST['yoast_free_metabox_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['yoast_free_metabox_nonce'] ), 'yoast_free_metabox' ) ) { return false; } if ( wp_is_post_revision( $post_id ) ) { $post_id = wp_is_post_revision( $post_id ); } /** * Determine we're not accidentally updating a different post. * We can't use filter_input here as the ID isn't available at this point, other than in the $_POST data. */ if ( ! isset( $_POST['ID'] ) || $post_id !== (int) $_POST['ID'] ) { return false; } clean_post_cache( $post_id ); $post = get_post( $post_id ); if ( ! is_object( $post ) ) { // Non-existent post. return false; } do_action( 'wpseo_save_compare_data', $post ); $social_fields = []; if ( $this->social_is_enabled ) { $social_fields = WPSEO_Meta::get_meta_field_defs( 'social' ); } $meta_boxes = apply_filters( 'wpseo_save_metaboxes', [] ); $meta_boxes = array_merge( $meta_boxes, WPSEO_Meta::get_meta_field_defs( 'general', $post->post_type ), WPSEO_Meta::get_meta_field_defs( 'advanced' ), $social_fields, WPSEO_Meta::get_meta_field_defs( 'schema', $post->post_type ) ); foreach ( $meta_boxes as $key => $meta_box ) { // If analysis is disabled remove that analysis score value from the DB. if ( $this->is_meta_value_disabled( $key ) ) { WPSEO_Meta::delete( $key, $post_id ); continue; } $data = null; $field_name = WPSEO_Meta::$form_prefix . $key; if ( $meta_box['type'] === 'checkbox' ) { $data = isset( $_POST[ $field_name ] ) ? 'on' : 'off'; } else { if ( isset( $_POST[ $field_name ] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We're preparing to do just that. $data = wp_unslash( $_POST[ $field_name ] ); // For multi-select. if ( is_array( $data ) ) { $data = array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], $data ); } if ( is_string( $data ) ) { $data = ( $key !== 'canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data ); } } // Reset options when no entry is present with multiselect - only applies to `meta-robots-adv` currently. if ( ! isset( $_POST[ $field_name ] ) && ( $meta_box['type'] === 'multiselect' ) ) { $data = []; } } if ( $data !== null ) { WPSEO_Meta::set_value( $key, $data, $post_id ); } } do_action( 'wpseo_saved_postdata' ); } /** * Determines if the given meta value key is disabled. * * @param string $key The key of the meta value. * * @return bool Whether the given meta value key is disabled. */ public function is_meta_value_disabled( $key ) { if ( $key === 'linkdex' && ! $this->seo_analysis->is_enabled() ) { return true; } if ( $key === 'content_score' && ! $this->readability_analysis->is_enabled() ) { return true; } if ( $key === 'inclusive_language_score' && ! $this->inclusive_language_analysis->is_enabled() ) { return true; } return false; } /** * Enqueues all the needed JS and CSS. * * @todo [JRF => whomever] Create css/metabox-mp6.css file and add it to the below allowed colors array when done. * * @return void */ public function enqueue() { global $pagenow; $asset_manager = new WPSEO_Admin_Asset_Manager(); if ( self::is_post_overview( $pagenow ) ) { return; } /* Filter 'wpseo_always_register_metaboxes_on_admin' documented in wpseo-main.php */ if ( ( self::is_post_edit( $pagenow ) === false && apply_filters( 'wpseo_always_register_metaboxes_on_admin', false ) === false ) || $this->display_metabox() === false ) { return; } $post_id = get_queried_object_id(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( empty( $post_id ) && isset( $_GET['post'] ) && is_string( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $post_id = sanitize_text_field( wp_unslash( $_GET['post'] ) ); } if ( $post_id !== 0 ) { // Enqueue files needed for upload functionality. wp_enqueue_media( [ 'post' => $post_id ] ); } $asset_manager->enqueue_style( 'metabox-css' ); $asset_manager->enqueue_style( 'scoring' ); $asset_manager->enqueue_style( 'monorepo' ); $asset_manager->enqueue_style( 'ai-generator' ); $asset_manager->enqueue_style( 'ai-fix-assessments' ); $is_block_editor = WP_Screen::get()->is_block_editor(); $post_edit_handle = 'post-edit'; if ( ! $is_block_editor ) { $post_edit_handle = 'post-edit-classic'; } $asset_manager->enqueue_script( $post_edit_handle ); $asset_manager->enqueue_style( 'admin-css' ); /** * Removes the emoji script as it is incompatible with both React and any * contenteditable fields. */ remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); $asset_manager->localize_script( $post_edit_handle, 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() ); $plugins_script_data = [ 'replaceVars' => [ 'replace_vars' => $this->get_replace_vars(), 'hidden_replace_vars' => $this->get_hidden_replace_vars(), 'recommended_replace_vars' => $this->get_recommended_replace_vars(), 'scope' => $this->determine_scope(), 'has_taxonomies' => $this->current_post_type_has_taxonomies(), ], 'shortcodes' => [ 'wpseo_shortcode_tags' => $this->get_valid_shortcode_tags(), 'wpseo_filter_shortcodes_nonce' => wp_create_nonce( 'wpseo-filter-shortcodes' ), ], ]; $worker_script_data = [ 'url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ), 'dependencies' => YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ), 'keywords_assessment_url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ), 'log_level' => WPSEO_Utils::get_analysis_worker_log_level(), ]; $page_on_front = (int) get_option( 'page_on_front' ); $homepage_is_page = get_option( 'show_on_front' ) === 'page'; $is_front_page = $homepage_is_page && $page_on_front === (int) $post_id; $script_data = [ 'metabox' => $this->get_metabox_script_data(), 'isPost' => true, 'isBlockEditor' => $is_block_editor, 'postId' => $post_id, 'postStatus' => get_post_status( $post_id ), 'postType' => get_post_type( $post_id ), 'usedKeywordsNonce' => wp_create_nonce( 'wpseo-keyword-usage-and-post-types' ), 'analysis' => [ 'plugins' => $plugins_script_data, 'worker' => $worker_script_data, ], 'isFrontPage' => $is_front_page, ]; /** * The website information repository. * * @var $repo Website_Information_Repository */ $repo = YoastSEO()->classes->get( Website_Information_Repository::class ); $site_information = $repo->get_post_site_information(); $site_information->set_permalink( $this->get_permalink() ); $script_data = array_merge_recursive( $site_information->get_legacy_site_information(), $script_data ); if ( ! $is_block_editor && post_type_supports( get_post_type(), 'thumbnail' ) ) { $asset_manager->enqueue_style( 'featured-image' ); } $asset_manager->localize_script( $post_edit_handle, 'wpseoScriptData', $script_data ); $asset_manager->enqueue_user_language_script(); } /** * Returns post in metabox context. * * @return WP_Post|array<string|int|bool> */ protected function get_metabox_post() { if ( $this->post !== null ) { return $this->post; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['post'] ) && is_string( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, Sanitization happens in the validate_int function. $post_id = (int) WPSEO_Utils::validate_int( wp_unslash( $_GET['post'] ) ); $this->post = get_post( $post_id ); return $this->post; } if ( isset( $GLOBALS['post'] ) ) { $this->post = $GLOBALS['post']; return $this->post; } return []; } /** * Returns an array with shortcode tags for all registered shortcodes. * * @return string[] */ private function get_valid_shortcode_tags() { $shortcode_tags = []; foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) { $shortcode_tags[] = $tag; } return $shortcode_tags; } /** * Prepares the replace vars for localization. * * @return string[] Replace vars. */ private function get_replace_vars() { $cached_replacement_vars = []; $vars_to_cache = [ 'date', 'id', 'sitename', 'sitedesc', 'sep', 'page', 'currentdate', 'currentyear', 'currentmonth', 'currentday', 'post_year', 'post_month', 'post_day', 'name', 'author_first_name', 'author_last_name', 'permalink', 'post_content', 'category_title', 'tag', 'category', ]; foreach ( $vars_to_cache as $var ) { $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $this->get_metabox_post() ); } // Merge custom replace variables with the WordPress ones. return array_merge( $cached_replacement_vars, $this->get_custom_replace_vars( $this->get_metabox_post() ) ); } /** * Returns the list of replace vars that should be hidden inside the editor. * * @return string[] The hidden replace vars. */ protected function get_hidden_replace_vars() { return ( new WPSEO_Replace_Vars() )->get_hidden_replace_vars(); } /** * Prepares the recommended replace vars for localization. * * @return array<string[]> Recommended replacement variables. */ private function get_recommended_replace_vars() { $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars(); // What is recommended depends on the current context. $post_type = $recommended_replace_vars->determine_for_post( $this->get_metabox_post() ); return $recommended_replace_vars->get_recommended_replacevars_for( $post_type ); } /** * Gets the custom replace variables for custom taxonomies and fields. * * @param WP_Post $post The post to check for custom taxonomies and fields. * * @return array<string[]> Array containing all the replacement variables. */ private function get_custom_replace_vars( $post ) { return [ 'custom_fields' => $this->get_custom_fields_replace_vars( $post ), 'custom_taxonomies' => $this->get_custom_taxonomies_replace_vars( $post ), ]; } /** * Gets the custom replace variables for custom taxonomies. * * @param WP_Post $post The post to check for custom taxonomies. * * @return array<string[]> Array containing all the replacement variables. */ private function get_custom_taxonomies_replace_vars( $post ) { $taxonomies = get_object_taxonomies( $post, 'objects' ); $custom_replace_vars = []; foreach ( $taxonomies as $taxonomy_name => $taxonomy ) { if ( is_string( $taxonomy ) ) { // If attachment, see https://core.trac.wordpress.org/ticket/37368 . $taxonomy_name = $taxonomy; $taxonomy = get_taxonomy( $taxonomy_name ); } if ( $taxonomy->_builtin && $taxonomy->public ) { continue; } $custom_replace_vars[ $taxonomy_name ] = [ 'name' => $taxonomy->name, 'description' => $taxonomy->description, ]; } return $custom_replace_vars; } /** * Gets the custom replace variables for custom fields. * * @param WP_Post $post The post to check for custom fields. * * @return array<string[]> Array containing all the replacement variables. */ private function get_custom_fields_replace_vars( $post ) { $custom_replace_vars = []; // If no post object is passed, return the empty custom_replace_vars array. if ( ! is_object( $post ) ) { return $custom_replace_vars; } $custom_fields = get_post_custom( $post->ID ); // If $custom_fields is an empty string or generally not an array, return early. if ( ! is_array( $custom_fields ) ) { return $custom_replace_vars; } $meta = YoastSEO()->meta->for_post( $post->ID ); if ( ! $meta ) { return $custom_replace_vars; } // Simply concatenate all fields containing replace vars so we can handle them all with a single regex find. $replace_vars_fields = implode( ' ', [ $meta->presentation->title, $meta->presentation->meta_description, ] ); preg_match_all( '/%%cf_([A-Za-z0-9_]+)%%/', $replace_vars_fields, $matches ); $fields_to_include = $matches[1]; foreach ( $custom_fields as $custom_field_name => $custom_field ) { // Skip private custom fields. if ( substr( $custom_field_name, 0, 1 ) === '_' ) { continue; } // Skip custom fields that are not used, new ones will be fetched dynamically. if ( ! in_array( $custom_field_name, $fields_to_include, true ) ) { continue; } // Skip custom field values that are serialized. if ( is_serialized( $custom_field[0] ) ) { continue; } $custom_replace_vars[ $custom_field_name ] = $custom_field[0]; } return $custom_replace_vars; } /** * Checks if the page is the post overview page. * * @param string $page The page to check for the post overview page. * * @return bool Whether or not the given page is the post overview page. */ public static function is_post_overview( $page ) { return $page === 'edit.php'; } /** * Checks if the page is the post edit page. * * @param string $page The page to check for the post edit page. * * @return bool Whether or not the given page is the post edit page. */ public static function is_post_edit( $page ) { return $page === 'post.php' || $page === 'post-new.php'; } /** * Retrieves the product title. * * @return string The product title. */ protected function get_product_title() { return YoastSEO()->helpers->product->get_product_name(); } /** * Gets the permalink. * * @return string */ protected function get_permalink() { $permalink = ''; if ( is_object( $this->get_metabox_post() ) ) { $permalink = get_sample_permalink( $this->get_metabox_post()->ID ); $permalink = $permalink[0]; } return $permalink; } } metabox/interface-metabox-section.php 0000644 00000000552 15231501436 0013755 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates and displays the HTML for a metabox section. */ interface WPSEO_Metabox_Section { /** * Outputs the section link. * * @return void */ public function display_link(); /** * Outputs the section content. * * @return void */ public function display_content(); } metabox/class-metabox-section-readability.php 0000644 00000002253 15231501436 0015411 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates and displays the React root element for a metabox section. */ class WPSEO_Metabox_Section_Readability implements WPSEO_Metabox_Section { /** * Name of the section, used as an identifier in the HTML. * * @var string */ public $name = 'readability'; /** * Outputs the section link. * * @return void */ public function display_link() { printf( '<li role="presentation"><a role="tab" href="#wpseo-meta-section-%1$s" id="wpseo-meta-tab-%1$s" aria-controls="wpseo-meta-section-%1$s" class="wpseo-meta-section-link"> <div class="wpseo-score-icon-container" id="wpseo-readability-score-icon"></div><span>%2$s</span></a></li>', esc_attr( $this->name ), esc_html__( 'Readability', 'wordpress-seo' ) ); } /** * Outputs the section content. * * @return void */ public function display_content() { printf( '<div role="tabpanel" id="wpseo-meta-section-%1$s" aria-labelledby="wpseo-meta-tab-%1$s" tabindex="0" class="wpseo-meta-section">', esc_attr( $this->name ) ); echo '<div id="wpseo-metabox-readability-root" class="wpseo-metabox-root"></div>', '</div>'; } } metabox/interface-metabox-tab.php 0000644 00000000533 15231501436 0013056 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates the HTML for a metabox tab. */ interface WPSEO_Metabox_Tab { /** * Returns the html for the tab link. * * @return string */ public function link(); /** * Returns the html for the tab content. * * @return string */ public function content(); } metabox/class-metabox-analysis-seo.php 0000644 00000001621 15231501436 0014063 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Metabox */ /** * Represents the SEO analysis. */ class WPSEO_Metabox_Analysis_SEO implements WPSEO_Metabox_Analysis { /** * Whether this analysis is enabled. * * @return bool Whether or not this analysis is enabled. */ public function is_enabled() { return $this->is_globally_enabled() && $this->is_user_enabled(); } /** * Whether or not this analysis is enabled by the user. * * @return bool Whether or not this analysis is enabled by the user. */ public function is_user_enabled() { return ! get_the_author_meta( 'wpseo_keyword_analysis_disable', get_current_user_id() ); } /** * Whether or not this analysis is enabled globally. * * @return bool Whether or not this analysis is enabled globally. */ public function is_globally_enabled() { return WPSEO_Options::get( 'keyword_analysis_active', true ); } } metabox/class-metabox-analysis-inclusive-language.php 0000644 00000003670 15231501436 0017065 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Metabox */ /** * Represents the inclusive language analysis. */ class WPSEO_Metabox_Analysis_Inclusive_Language implements WPSEO_Metabox_Analysis { /** * Whether this analysis is enabled. * * @return bool Whether or not this analysis is enabled. */ public function is_enabled() { return $this->is_globally_enabled() && $this->is_user_enabled() && $this->is_current_version_supported() && YoastSEO()->helpers->language->has_inclusive_language_support( WPSEO_Language_Utils::get_language( get_locale() ) ); } /** * Whether or not this analysis is enabled by the user. * * @return bool Whether or not this analysis is enabled by the user. */ public function is_user_enabled() { return ! get_the_author_meta( 'wpseo_inclusive_language_analysis_disable', get_current_user_id() ); } /** * Whether or not this analysis is enabled globally. * * @return bool Whether or not this analysis is enabled globally. */ public function is_globally_enabled() { return WPSEO_Options::get( 'inclusive_language_analysis_active', false ); } /** * Whether the inclusive language analysis should be loaded in Free. * * It should always be loaded when Premium is not active. If Premium is active, it depends on the version. Some Premium * versions also have inclusive language code (when it was still a Premium only feature) which would result in rendering * the analysis twice. In those cases, the analysis should be only loaded from the Premium side. * * @return bool Whether or not the inclusive language analysis should be loaded. */ private function is_current_version_supported() { $is_premium = YoastSEO()->helpers->product->is_premium(); $premium_version = YoastSEO()->helpers->product->get_premium_version(); return ! $is_premium || version_compare( $premium_version, '19.6-RC0', '>=' ) || version_compare( $premium_version, '19.2', '==' ); } } metabox/class-metabox-section-react.php 0000644 00000006034 15231501436 0014217 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates and displays the React root element for a metabox section. */ class WPSEO_Metabox_Section_React implements WPSEO_Metabox_Section { /** * Name of the section, used as an identifier in the HTML. * * @var string */ public $name; /** * Content to use before the React root node. * * @var string */ public $content; /** * Content to use to display the button to open this content block. * * @var string */ private $link_content; /** * Class to add to the link. * * @var string */ private $link_class; /** * Aria label to use for the link. * * @var string */ private $link_aria_label; /** * Additional html content to be displayed within the section. * * @var string */ private $html_after; /** * Constructor. * * @param string $name The name of the section, used as an identifier in the html. * Can only contain URL safe characters. * @param string $link_content The text content of the section link. * @param string $content Optional. Content to use above the React root element. * @param array $options Optional link attributes. */ public function __construct( $name, $link_content, $content = '', array $options = [] ) { $this->name = $name; $this->content = $content; $default_options = [ 'link_class' => '', 'link_aria_label' => '', 'html_after' => '', ]; $options = wp_parse_args( $options, $default_options ); $this->link_content = $link_content; $this->link_class = $options['link_class']; $this->link_aria_label = $options['link_aria_label']; $this->html_after = $options['html_after']; } /** * Outputs the section link. * * @return void */ public function display_link() { printf( '<li role="presentation"><a role="tab" href="#wpseo-meta-section-%1$s" id="wpseo-meta-tab-%1$s" aria-controls="wpseo-meta-section-%1$s" class="wpseo-meta-section-link %2$s"%3$s>%4$s</a></li>', esc_attr( $this->name ), esc_attr( $this->link_class ), ( $this->link_aria_label !== '' ) ? ' aria-label="' . esc_attr( $this->link_aria_label ) . '"' : '', wp_kses_post( $this->link_content ) ); } /** * Outputs the section content. * * @return void */ public function display_content() { add_filter( 'wp_kses_allowed_html', [ 'WPSEO_Utils', 'extend_kses_post_with_forms' ] ); add_filter( 'wp_kses_allowed_html', [ 'WPSEO_Utils', 'extend_kses_post_with_a11y' ] ); printf( '<div role="tabpanel" id="wpseo-meta-section-%1$s" aria-labelledby="wpseo-meta-tab-%1$s" tabindex="0" class="wpseo-meta-section">', esc_attr( $this->name ) ); echo wp_kses_post( $this->content ); echo '<div id="wpseo-metabox-root" class="wpseo-metabox-root"></div>'; echo wp_kses_post( $this->html_after ); echo '</div>'; remove_filter( 'wp_kses_allowed_html', [ 'WPSEO_Utils', 'extend_kses_post_with_forms' ] ); remove_filter( 'wp_kses_allowed_html', [ 'WPSEO_Utils', 'extend_kses_post_with_a11y' ] ); } } metabox/class-metabox-analysis-readability.php 0000644 00000001641 15231501436 0015570 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Metabox */ /** * Represents the readability analysis. */ class WPSEO_Metabox_Analysis_Readability implements WPSEO_Metabox_Analysis { /** * Whether this analysis is enabled. * * @return bool Whether or not this analysis is enabled. */ public function is_enabled() { return $this->is_globally_enabled() && $this->is_user_enabled(); } /** * Whether or not this analysis is enabled by the user. * * @return bool Whether or not this analysis is enabled by the user. */ public function is_user_enabled() { return ! get_the_author_meta( 'wpseo_content_analysis_disable', get_current_user_id() ); } /** * Whether or not this analysis is enabled globally. * * @return bool Whether or not this analysis is enabled globally. */ public function is_globally_enabled() { return WPSEO_Options::get( 'content_analysis_active', true ); } } metabox/class-metabox-editor.php 0000644 00000004415 15231501436 0012746 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Metabox */ /** * Handles all things with the metabox in combination with the WordPress editor. */ class WPSEO_Metabox_Editor { /** * Registers hooks to WordPress. * * @codeCoverageIgnore * * @return void */ public function register_hooks() { // For the Classic editor. add_filter( 'mce_css', [ $this, 'add_css_inside_editor' ] ); // For the Block/Gutenberg editor. // See https://github.com/danielbachhuber/gutenberg-migration-guide/blob/master/filter-mce-css.md. add_action( 'enqueue_block_editor_assets', [ $this, 'add_editor_styles' ] ); add_filter( 'tiny_mce_before_init', [ $this, 'add_custom_element' ] ); } /** * Adds our inside the editor CSS file to the list of CSS files to be loaded inside the editor. * * @param string $css_files The CSS files that WordPress wants to load inside the editor. * @return string The CSS files WordPress wants to load and our CSS file. */ public function add_css_inside_editor( $css_files ) { $asset_manager = new WPSEO_Admin_Asset_Manager(); $styles = $asset_manager->special_styles(); $inside_editor = $styles['inside-editor']; $asset_location = new WPSEO_Admin_Asset_SEO_Location( WPSEO_FILE ); $url = $asset_location->get_url( $inside_editor, WPSEO_Admin_Asset::TYPE_CSS ); if ( $css_files === '' ) { $css_files = $url; } else { $css_files .= ',' . $url; } return $css_files; } /** * Enqueues the CSS to use in the TinyMCE editor. * * @return void */ public function add_editor_styles() { $asset_manager = new WPSEO_Admin_Asset_Manager(); $asset_manager->enqueue_style( 'inside-editor' ); } /** * Adds a custom element to the tinyMCE editor that we need for marking the content. * * @param array $tinymce_config The tinyMCE config as configured by WordPress. * * @return array The new tinyMCE config with our added custom elements. */ public function add_custom_element( $tinymce_config ) { if ( ! empty( $tinymce_config['custom_elements'] ) ) { $custom_elements = $tinymce_config['custom_elements']; $custom_elements .= ',~yoastmark'; } else { $custom_elements = '~yoastmark'; } $tinymce_config['custom_elements'] = $custom_elements; return $tinymce_config; } } metabox/class-metabox-collapsibles-section.php 0000644 00000003745 15231501436 0015603 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates and displays a metabox tab that consists of collapsible sections. */ class WPSEO_Metabox_Collapsibles_Sections extends WPSEO_Abstract_Metabox_Tab_With_Sections { /** * Holds the tab's collapsibles. * * @var WPSEO_Metabox_Collapsible[] */ private $collapsibles = []; /** * Constructor. * * @param string $name The name of the section, used as an identifier in the html. * Can only contain URL safe characters. * @param string $link_content The text content of the section link. * @param array $collapsibles The metabox collapsibles (`WPSEO_Metabox_Collapsible[]`) to be included in the section. * @param array $options Optional link attributes. */ public function __construct( $name, $link_content, array $collapsibles = [], array $options = [] ) { parent::__construct( $name, $link_content, $options ); $this->collapsibles = $collapsibles; } /** * Outputs the section content if any tab has been added. * * @return void */ public function display_content() { if ( $this->has_sections() ) { printf( '<div id="%1$s" class="wpseo-meta-section">', esc_attr( 'wpseo-meta-section-' . $this->name ) ); echo '<div class="wpseo_content_wrapper">'; add_filter( 'wp_kses_allowed_html', [ 'WPSEO_Utils', 'extend_kses_post_with_forms' ] ); add_filter( 'wp_kses_allowed_html', [ 'WPSEO_Utils', 'extend_kses_post_with_a11y' ] ); foreach ( $this->collapsibles as $collapsible ) { echo wp_kses_post( $collapsible->content() ); } remove_filter( 'wp_kses_allowed_html', [ 'WPSEO_Utils', 'extend_kses_post_with_forms' ] ); remove_filter( 'wp_kses_allowed_html', [ 'WPSEO_Utils', 'extend_kses_post_with_a11y' ] ); echo '</div></div>'; } } /** * Checks whether the tab has any sections. * * @return bool Whether the tab has any sections */ protected function has_sections() { return ! empty( $this->collapsibles ); } } metabox/class-metabox-section-additional.php 0000644 00000005107 15231501436 0015231 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Metabox */ /** * Generates and displays an additional metabox section. */ class WPSEO_Metabox_Section_Additional implements WPSEO_Metabox_Section { /** * Name of the section, used as an identifier in the HTML. * * @var string */ public $name; /** * Content of the tab's section. * * @var string */ public $content; /** * HTML to use in the tab header. * * @var string */ private $link_content; /** * Class to add to the link. * * @var string */ private $link_class; /** * Aria label to use for the link. * * @var string */ private $link_aria_label; /** * Represents the content class. * * @var string */ private $content_class; /** * Constructor. * * @param string $name The name of the section, used as an identifier in the html. * Can only contain URL safe characters. * @param string $link_content The text content of the section link. * @param string $content Optional. Content to use above the React root element. * @param array $options Optional link attributes. */ public function __construct( $name, $link_content, $content = '', array $options = [] ) { $this->name = $name; $this->content = $content; $default_options = [ 'link_class' => '', 'link_aria_label' => '', 'content_class' => 'wpseo-form', ]; $options = wp_parse_args( $options, $default_options ); $this->link_content = $link_content; $this->link_class = $options['link_class']; $this->link_aria_label = $options['link_aria_label']; $this->content_class = $options['content_class']; } /** * Outputs the section link. * * @return void */ public function display_link() { printf( '<li role="presentation"><a role="tab" href="#wpseo-meta-section-%1$s" id="wpseo-meta-tab-%1$s" aria-controls="wpseo-meta-section-%1$s" class="wpseo-meta-section-link %2$s"%3$s>%4$s</a></li>', esc_attr( $this->name ), esc_attr( $this->link_class ), ( $this->link_aria_label !== '' ) ? ' aria-label="' . esc_attr( $this->link_aria_label ) . '"' : '', $this->link_content ); } /** * Outputs the section content. * * @return void */ public function display_content() { $html = sprintf( '<div role="tabpanel" id="wpseo-meta-section-%1$s" aria-labelledby="wpseo-meta-tab-%1$s" tabindex="0" class="wpseo-meta-section %2$s">', esc_attr( $this->name ), esc_attr( $this->content_class ) ); $html .= $this->content; $html .= '</div>'; echo $html; } } metabox/class-metabox-section-inclusive-language.php 0000644 00000002316 15231501436 0016702 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin */ /** * Generates and displays the React root element for a metabox section. */ class WPSEO_Metabox_Section_Inclusive_Language implements WPSEO_Metabox_Section { /** * Name of the section, used as an identifier in the HTML. * * @var string */ public $name = 'inclusive-language'; /** * Outputs the section link. * * @return void */ public function display_link() { printf( '<li role="presentation"><a role="tab" href="#wpseo-meta-section-%1$s" id="wpseo-meta-tab-%1$s" aria-controls="wpseo-meta-section-%1$s" class="wpseo-meta-section-link"> <div class="wpseo-score-icon-container" id="wpseo-inclusive-language-score-icon"></div><span>%2$s</span></a></li>', esc_attr( $this->name ), esc_html__( 'Inclusive language', 'wordpress-seo' ) ); } /** * Outputs the section content. * * @return void */ public function display_content() { printf( '<div role="tabpanel" id="wpseo-meta-section-%1$s" aria-labelledby="wpseo-meta-tab-%1$s" tabindex="0" class="wpseo-meta-section">', esc_attr( $this->name ) ); echo '<div id="wpseo-metabox-inclusive-language-root" class="wpseo-metabox-root"></div>', '</div>'; } } tracking/class-tracking-default-data.php 0000644 00000002644 15231501436 0014323 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Tracking */ /** * Represents the default data. */ class WPSEO_Tracking_Default_Data implements WPSEO_Collection { /** * Returns the collection data. * * @return array The collection data. */ public function get() { return [ 'siteTitle' => get_option( 'blogname' ), '@timestamp' => (int) gmdate( 'Uv' ), 'wpVersion' => $this->get_wordpress_version(), 'homeURL' => home_url(), 'adminURL' => admin_url(), 'isMultisite' => is_multisite(), 'siteLanguage' => get_bloginfo( 'language' ), 'gmt_offset' => get_option( 'gmt_offset' ), 'timezoneString' => get_option( 'timezone_string' ), 'migrationStatus' => get_option( 'yoast_migrations_free' ), 'countPosts' => $this->get_post_count( 'post' ), 'countPages' => $this->get_post_count( 'page' ), ]; } /** * Returns the number of posts of a certain type. * * @param string $post_type The post type return the count for. * * @return int The count for this post type. */ protected function get_post_count( $post_type ) { $count = wp_count_posts( $post_type ); if ( isset( $count->publish ) ) { return $count->publish; } return 0; } /** * Returns the WordPress version. * * @return string The version. */ protected function get_wordpress_version() { global $wp_version; return $wp_version; } } tracking/class-tracking-settings-data.php 0000644 00000015226 15231501436 0014537 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Tracking */ /** * Collects anonymized settings data. */ class WPSEO_Tracking_Settings_Data implements WPSEO_Collection { /** * The options that need to be anonymized before they can be sent elsewhere. * * @var array All of the option_names which need to be * anonymized before they can be sent elsewhere. */ private $anonymous_settings = [ 'baiduverify', 'googleverify', 'msverify', 'yandexverify', 'myyoast-oauth', 'website_name', 'alternate_website_name', 'company_logo', 'company_name', 'company_alternate_name', 'person_name', 'person_logo', 'person_logo_id', 'company_logo_id', 'facebook_site', 'instagram_url', 'linkedin_url', 'myspace_url', 'og_default_image', 'og_default_image_id', 'og_frontpage_title', 'og_frontpage_desc', 'og_frontpage_image', 'og_frontpage_image_id', 'open_graph_frontpage_title', 'open_graph_frontpage_desc', 'open_graph_frontpage_image', 'open_graph_frontpage_image_id', 'other_social_urls', 'mastodon_url', 'pinterest_url', 'pinterestverify', 'twitter_site', 'youtube_url', 'wikipedia_url', 'semrush_tokens', 'wincher_tokens', 'wincher_website_id', 'least_readability_ignore_list', 'least_seo_score_ignore_list', 'most_linked_ignore_list', 'least_linked_ignore_list', 'indexables_page_reading_list', 'publishing_principles_id', 'ownership_funding_info_id', 'actionable_feedback_policy_id', 'corrections_policy_id', 'ethics_policy_id', 'diversity_policy_id', 'diversity_staffing_report_id', ]; /** * The options we want to track. * * @var array The option_names for the options we want to track. */ private $include_list = [ 'ms_defaults_set', 'version', 'disableadvanced_meta', 'ryte_indexability', 'baiduverify', 'googleverify', 'msverify', 'yandexverify', 'site_type', 'has_multiple_authors', 'environment_type', 'content_analysis_active', 'keyword_analysis_active', 'inclusive_language_analysis_active', 'enable_admin_bar_menu', 'enable_cornerstone_content', 'enable_xml_sitemap', 'enable_text_link_counter', 'show_onboarding_notice', 'first_activated_on', 'myyoast-oauth', 'dynamic_permalinks', 'website_name', 'alternate_website_name', 'company_logo', 'company_name', 'company_or_person', 'person_name', 'forcerewritetitle', 'separator', 'title-home-wpseo', 'title-author-wpseo', 'title-archive-wpseo', 'title-search-wpseo', 'title-404-wpseo', 'metadesc-home-wpseo', 'metadesc-author-wpseo', 'metadesc-archive-wpseo', 'rssbefore', 'rssafter', 'noindex-author-wpseo', 'noindex-author-noposts-wpseo', 'noindex-archive-wpseo', 'disable-author', 'disable-date', 'disable-post_format', 'disable-attachment', 'breadcrumbs-404crumb', 'breadcrumbs-display-blog-page', 'breadcrumbs-boldlast', 'breadcrumbs-archiveprefix', 'breadcrumbs-enable', 'breadcrumbs-home', 'breadcrumbs-prefix', 'breadcrumbs-searchprefix', 'breadcrumbs-sep', 'person_logo', 'person_logo_id', 'company_logo_id', 'company_or_person_user_id', 'stripcategorybase', 'noindex-post', 'display-metabox-pt-post', 'noindex-page', 'display-metabox-pt-page', 'noindex-attachment', 'display-metabox-pt-attachment', 'display-metabox-tax-category', 'noindex-tax-category', 'display-metabox-tax-post_tag', 'noindex-tax-post_tag', 'display-metabox-tax-post_format', 'noindex-tax-post_format', 'taxonomy-category-ptparent', 'taxonomy-post_tag-ptparent', 'taxonomy-post_format-ptparent', 'breadcrumbs-blog-remove', 'hideeditbox-post', 'hideeditbox-page', 'hideeditbox-attachment', 'hideeditbox-tax-category', 'hideeditbox-tax-post_tag', 'hideeditbox-tax-post_format', 'facebook_site', 'instagram_url', 'linkedin_url', 'myspace_url', 'og_default_image', 'og_default_image_id', 'og_frontpage_title', 'og_frontpage_desc', 'og_frontpage_image', 'og_frontpage_image_id', 'open_graph_frontpage_title', 'open_graph_frontpage_desc', 'open_graph_frontpage_image', 'open_graph_frontpage_image_id', 'opengraph', 'pinterest_url', 'pinterestverify', 'twitter', 'twitter_site', 'twitter_card_type', 'youtube_url', 'wikipedia_url', 'mastodon_url', 'indexables_indexing_completed', 'semrush_integration_active', 'semrush_tokens', 'semrush_country_code', 'enable_enhanced_slack_sharing', 'enable_metabox_insights', 'enable_link_suggestions', 'enable_index_now', 'enable_ai_generator', 'workouts', 'wincher_integration_active', 'wincher_tokens', 'wincher_website_id', 'wincher_automatically_add_keyphrases', 'first_time_install', 'other_social_urls', 'remove_feed_global', 'remove_feed_global_comments', 'remove_feed_post_comments', 'remove_feed_authors', 'remove_feed_categories', 'remove_feed_tags', 'remove_feed_custom_taxonomies', 'remove_feed_post_types', 'remove_feed_search', 'remove_atom_rdf_feeds', 'remove_shortlinks', 'remove_rest_api_links', 'remove_rsd_wlw_links', 'remove_oembed_links', 'remove_generator', 'remove_emoji_scripts', 'remove_powered_by_header', 'remove_pingback_header', 'clean_campaign_tracking_urls', 'clean_permalinks', 'clean_permalinks_extra_variables', 'search_cleanup', 'search_cleanup_emoji', 'search_cleanup_patterns', 'search_character_limit', 'redirect_search_pretty_urls', 'indexables_overview_state', 'deny_search_crawling', 'deny_wp_json_crawling', 'deny_adsbot_crawling', 'deny_ccbot_crawling', 'deny_google_extended_crawling', 'deny_gptbot_crawling', 'last_known_no_unindexed', ]; /** * Returns the collection data. * * @return array The collection data. */ public function get() { /** * Filter: 'wpseo_tracking_settings_include_list' - Allow filtering the settings included in tracking. * * @param string $include_list The list with included setting names. */ $this->include_list = apply_filters( 'wpseo_tracking_settings_include_list', $this->include_list ); $options = WPSEO_Options::get_all(); // Returns the settings of which the keys intersect with the values of the include list. $options = array_intersect_key( $options, array_flip( $this->include_list ) ); return [ 'settings' => $this->anonymize_settings( $options ), ]; } /** * Anonimizes the WPSEO_Options array by replacing all $anonymous_settings values to 'used'. * * @param array $settings The settings. * * @return array The anonymized settings. */ private function anonymize_settings( $settings ) { foreach ( $this->anonymous_settings as $setting ) { if ( ! empty( $settings[ $setting ] ) ) { $settings[ $setting ] = 'used'; } } return $settings; } } tracking/class-tracking.php 0000644 00000015555 15231501436 0011777 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Tracking */ use Yoast\WP\SEO\Analytics\Application\Missing_Indexables_Collector; use Yoast\WP\SEO\Analytics\Application\To_Be_Cleaned_Indexables_Collector; /** * This class handles the tracking routine. */ class WPSEO_Tracking implements WPSEO_WordPress_Integration { /** * The tracking option name. * * @var string */ protected $option_name = 'wpseo_tracking_last_request'; /** * The limit for the option. * * @var int */ protected $threshold = 0; /** * The endpoint to send the data to. * * @var string */ protected $endpoint = ''; /** * The current time. * * @var int */ private $current_time; /** * WPSEO_Tracking constructor. * * @param string $endpoint The endpoint to send the data to. * @param int $threshold The limit for the option. */ public function __construct( $endpoint, $threshold ) { if ( ! $this->tracking_enabled() ) { return; } $this->endpoint = $endpoint; $this->threshold = $threshold; $this->current_time = time(); } /** * Registers all hooks to WordPress. * * @return void */ public function register_hooks() { if ( ! $this->tracking_enabled() ) { return; } // Send tracking data on `admin_init`. add_action( 'admin_init', [ $this, 'send' ], 1 ); // Add an action hook that will be triggered at the specified time by `wp_schedule_single_event()`. add_action( 'wpseo_send_tracking_data_after_core_update', [ $this, 'send' ] ); // Call `wp_schedule_single_event()` after a WordPress core update. add_action( 'upgrader_process_complete', [ $this, 'schedule_tracking_data_sending' ], 10, 2 ); } /** * Schedules a new sending of the tracking data after a WordPress core update. * * @param bool|WP_Upgrader $upgrader Optional. WP_Upgrader instance or false. * Depending on context, it might be a Theme_Upgrader, * Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader. * instance. Default false. * @param array $data Array of update data. * * @return void */ public function schedule_tracking_data_sending( $upgrader = false, $data = [] ) { // Return if it's not a WordPress core update. if ( ! $upgrader || ! isset( $data['type'] ) || $data['type'] !== 'core' ) { return; } /* * To uniquely identify the scheduled cron event, `wp_next_scheduled()` * needs to receive the same arguments as those used when originally * scheduling the event otherwise it will always return false. */ if ( ! wp_next_scheduled( 'wpseo_send_tracking_data_after_core_update', [ true ] ) ) { /* * Schedule sending of data tracking 6 hours after a WordPress core * update. Pass a `true` parameter for the callback `$force` argument. */ wp_schedule_single_event( ( time() + ( HOUR_IN_SECONDS * 6 ) ), 'wpseo_send_tracking_data_after_core_update', [ true ] ); } } /** * Sends the tracking data. * * @param bool $force Whether to send the tracking data ignoring the two * weeks time threshold. Default false. * * @return void */ public function send( $force = false ) { if ( ! $this->should_send_tracking( $force ) ) { return; } // Set a 'content-type' header of 'application/json'. $tracking_request_args = [ 'headers' => [ 'content-type:' => 'application/json', ], ]; $collector = $this->get_collector(); $request = new WPSEO_Remote_Request( $this->endpoint, $tracking_request_args ); $request->set_body( $collector->get_as_json() ); $request->send(); update_option( $this->option_name, $this->current_time, 'yes' ); } /** * Determines whether to send the tracking data. * * Returns false if tracking is disabled or the current page is one of the * admin plugins pages. Returns true when there's no tracking data stored or * the data was sent more than two weeks ago. The two weeks interval is set * when instantiating the class. * * @param bool $ignore_time_treshhold Whether to send the tracking data ignoring * the two weeks time treshhold. Default false. * * @return bool True when tracking data should be sent. */ protected function should_send_tracking( $ignore_time_treshhold = false ) { global $pagenow; // Only send tracking on the main site of a multi-site instance. This returns true on non-multisite installs. if ( is_network_admin() || ! is_main_site() ) { return false; } // Because we don't want to possibly block plugin actions with our routines. if ( in_array( $pagenow, [ 'plugins.php', 'plugin-install.php', 'plugin-editor.php' ], true ) ) { return false; } $last_time = get_option( $this->option_name ); // When tracking data haven't been sent yet or when sending data is forced. if ( ! $last_time || $ignore_time_treshhold ) { return true; } return $this->exceeds_treshhold( $this->current_time - $last_time ); } /** * Checks if the given amount of seconds exceeds the set threshold. * * @param int $seconds The amount of seconds to check. * * @return bool True when seconds is bigger than threshold. */ protected function exceeds_treshhold( $seconds ) { return ( $seconds > $this->threshold ); } /** * Returns the collector for collecting the data. * * @return WPSEO_Collector The instance of the collector. */ public function get_collector() { $collector = new WPSEO_Collector(); $collector->add_collection( new WPSEO_Tracking_Default_Data() ); $collector->add_collection( new WPSEO_Tracking_Server_Data() ); $collector->add_collection( new WPSEO_Tracking_Theme_Data() ); $collector->add_collection( new WPSEO_Tracking_Plugin_Data() ); $collector->add_collection( new WPSEO_Tracking_Settings_Data() ); $collector->add_collection( new WPSEO_Tracking_Addon_Data() ); $collector->add_collection( YoastSEO()->classes->get( Missing_Indexables_Collector::class ) ); $collector->add_collection( YoastSEO()->classes->get( To_Be_Cleaned_Indexables_Collector::class ) ); return $collector; } /** * See if we should run tracking at all. * * @return bool True when we can track, false when we can't. */ private function tracking_enabled() { // Check if we're allowing tracking. $tracking = WPSEO_Options::get( 'tracking' ); if ( $tracking === false ) { return false; } // Save this state. if ( $tracking === null ) { /** * Filter: 'wpseo_enable_tracking' - Enables the data tracking of Yoast SEO Premium and add-ons. * * @param string|false $is_enabled The enabled state. Default is false. */ $tracking = apply_filters( 'wpseo_enable_tracking', false ); WPSEO_Options::set( 'tracking', $tracking ); } if ( $tracking === false ) { return false; } if ( ! YoastSEO()->helpers->environment->is_production_mode() ) { return false; } return true; } } tracking/class-tracking-plugin-data.php 0000644 00000004042 15231501436 0014167 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Tracking */ /** * Represents the plugin data. */ class WPSEO_Tracking_Plugin_Data implements WPSEO_Collection { /** * Plugins with auto updating enabled. * * @var array */ private $auto_update_plugin_list; /** * Returns the collection data. * * @return array The collection data. */ public function get() { return [ 'plugins' => $this->get_plugin_data(), ]; } /** * Returns all plugins. * * @return array The formatted plugins. */ protected function get_plugin_data() { if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugins = wp_get_active_and_valid_plugins(); $plugins = array_map( 'get_plugin_data', $plugins ); $this->set_auto_update_plugin_list(); $plugins = array_map( [ $this, 'format_plugin' ], $plugins ); $plugin_data = []; foreach ( $plugins as $plugin ) { $plugin_key = sanitize_title( $plugin['name'] ); $plugin_data[ $plugin_key ] = $plugin; } return $plugin_data; } /** * Sets all auto updating plugin data so it can be used in the tracking list. * * @return void */ public function set_auto_update_plugin_list() { $auto_update_plugins = []; $auto_update_plugin_files = get_option( 'auto_update_plugins' ); if ( $auto_update_plugin_files ) { foreach ( $auto_update_plugin_files as $auto_update_plugin ) { $data = get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $auto_update_plugin ); $auto_update_plugins[ $data['Name'] ] = $data; } } $this->auto_update_plugin_list = $auto_update_plugins; } /** * Formats the plugin array. * * @param array $plugin The plugin details. * * @return array The formatted array. */ protected function format_plugin( array $plugin ) { return [ 'name' => $plugin['Name'], 'version' => $plugin['Version'], 'auto_updating' => array_key_exists( $plugin['Name'], $this->auto_update_plugin_list ), ]; } } tracking/class-tracking-addon-data.php 0000644 00000010170 15231501436 0013755 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Tracking */ use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; /** * Represents the addon option data. */ class WPSEO_Tracking_Addon_Data implements WPSEO_Collection { /** * The local options we want to track. * * @var string[] The option_names for the options we want to track. */ private $local_include_list = [ 'use_multiple_locations', 'multiple_locations_same_organization', 'business_type', 'woocommerce_local_pickup_setting', ]; /** * The woo options we want to track. * * @var string[] The option_names for the options we want to track. */ private $woo_include_list = []; /** * The news options we want to track. * * @var string[] The option_names for the options we want to track. */ private $news_include_list = []; /** * The video options we want to track. * * @var string[] The option_names for the options we want to track. */ private $video_include_list = []; /** * Returns the collection data. * * @return array The collection data. */ public function get() { $addon_settings = []; $addon_manager = new WPSEO_Addon_Manager(); if ( $addon_manager->is_installed( WPSEO_Addon_Manager::LOCAL_SLUG ) ) { $addon_settings = $this->get_local_addon_settings( $addon_settings, 'wpseo_local', WPSEO_Addon_Manager::LOCAL_SLUG, $this->local_include_list ); } if ( $addon_manager->is_installed( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ) { $addon_settings = $this->get_addon_settings( $addon_settings, 'wpseo_woo', WPSEO_Addon_Manager::WOOCOMMERCE_SLUG, $this->woo_include_list ); } if ( $addon_manager->is_installed( WPSEO_Addon_Manager::NEWS_SLUG ) ) { $addon_settings = $this->get_addon_settings( $addon_settings, 'wpseo_news', WPSEO_Addon_Manager::NEWS_SLUG, $this->news_include_list ); } if ( $addon_manager->is_installed( WPSEO_Addon_Manager::VIDEO_SLUG ) ) { $addon_settings = $this->get_addon_settings( $addon_settings, 'wpseo_video', WPSEO_Addon_Manager::VIDEO_SLUG, $this->video_include_list ); } return $addon_settings; } /** * Gets the tracked options from the addon * * @param array $addon_settings The current list of addon settings. * @param string $source_name The option key of the addon. * @param string $slug The addon slug. * @param array $option_include_list All the options to be included in tracking. * * @return array */ public function get_addon_settings( array $addon_settings, $source_name, $slug, $option_include_list ) { $source_options = get_option( $source_name, [] ); if ( ! is_array( $source_options ) || empty( $source_options ) ) { return $addon_settings; } $addon_settings[ $slug ] = array_intersect_key( $source_options, array_flip( $option_include_list ) ); return $addon_settings; } /** * Filter business_type in local addon settings. * * Remove the business_type setting when 'multiple_locations_shared_business_info' setting is turned off. * * @param array $addon_settings The current list of addon settings. * @param string $source_name The option key of the addon. * @param string $slug The addon slug. * @param array $option_include_list All the options to be included in tracking. * * @return array */ public function get_local_addon_settings( array $addon_settings, $source_name, $slug, $option_include_list ) { $source_options = get_option( $source_name, [] ); if ( ! is_array( $source_options ) || empty( $source_options ) ) { return $addon_settings; } $addon_settings[ $slug ] = array_intersect_key( $source_options, array_flip( $option_include_list ) ); if ( array_key_exists( 'use_multiple_locations', $source_options ) && array_key_exists( 'business_type', $addon_settings[ $slug ] ) && $source_options['use_multiple_locations'] === 'on' && $source_options['multiple_locations_shared_business_info'] === 'off' ) { $addon_settings[ $slug ]['business_type'] = 'multiple_locations'; } if ( ! ( new WooCommerce_Conditional() )->is_met() ) { unset( $addon_settings[ $slug ]['woocommerce_local_pickup_setting'] ); } return $addon_settings; } } tracking/class-tracking-theme-data.php 0000644 00000002245 15231501436 0013776 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Tracking */ /** * Represents the theme data. */ class WPSEO_Tracking_Theme_Data implements WPSEO_Collection { /** * Returns the collection data. * * @return array The collection data. */ public function get() { $theme = wp_get_theme(); return [ 'theme' => [ 'name' => $theme->get( 'Name' ), 'url' => $theme->get( 'ThemeURI' ), 'version' => $theme->get( 'Version' ), 'author' => [ 'name' => $theme->get( 'Author' ), 'url' => $theme->get( 'AuthorURI' ), ], 'parentTheme' => $this->get_parent_theme( $theme ), 'blockTemplateSupport' => current_theme_supports( 'block-templates' ), 'isBlockTheme' => function_exists( 'wp_is_block_theme' ) && wp_is_block_theme(), ], ]; } /** * Returns the name of the parent theme. * * @param WP_Theme $theme The theme object. * * @return string|null The name of the parent theme or null. */ private function get_parent_theme( WP_Theme $theme ) { if ( is_child_theme() ) { return $theme->get( 'Template' ); } return null; } } tracking/class-tracking-server-data.php 0000644 00000003757 15231501436 0014213 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Tracking */ /** * Represents the server data. */ class WPSEO_Tracking_Server_Data implements WPSEO_Collection { /** * Returns the collection data. * * @return array The collection data. */ public function get() { return [ 'server' => $this->get_server_data(), ]; } /** * Returns the values with server details. * * @return array Array with the value. */ protected function get_server_data() { $server_data = []; // Validate if the server address is a valid IP-address. $ipaddress = isset( $_SERVER['SERVER_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['SERVER_ADDR'] ), FILTER_VALIDATE_IP ) : ''; if ( $ipaddress ) { $server_data['ip'] = $ipaddress; $server_data['Hostname'] = gethostbyaddr( $ipaddress ); } $server_data['os'] = function_exists( 'php_uname' ) ? php_uname() : PHP_OS; $server_data['PhpVersion'] = PHP_VERSION; $server_data['CurlVersion'] = $this->get_curl_info(); $server_data['PhpExtensions'] = $this->get_php_extensions(); return $server_data; } /** * Returns details about the curl version. * * @return array|null The curl info. Or null when curl isn't available.. */ protected function get_curl_info() { if ( ! function_exists( 'curl_version' ) ) { return null; } $curl = curl_version(); $ssl_support = true; if ( ! $curl['features'] && CURL_VERSION_SSL ) { $ssl_support = false; } return [ 'version' => $curl['version'], 'sslSupport' => $ssl_support, ]; } /** * Returns a list with php extensions. * * @return array Returns the state of the php extensions. */ protected function get_php_extensions() { return [ 'imagick' => extension_loaded( 'imagick' ), 'filter' => extension_loaded( 'filter' ), 'bcmath' => extension_loaded( 'bcmath' ), 'pcre' => extension_loaded( 'pcre' ), 'xml' => extension_loaded( 'xml' ), 'pdo_mysql' => extension_loaded( 'pdo_mysql' ), ]; } } class-asset.php 0000644 00000010465 15231501436 0007505 0 ustar 00 <?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; } } class-admin-asset-dev-server-location.php 0000644 00000003210 15231501436 0014447 0 ustar 00 <?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 ); } } interface-installable.php 0000644 00000000376 15231501436 0011513 0 ustar 00 <?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(); } class-product-upsell-notice.php 0000644 00000013376 15231501436 0012633 0 ustar 00 <?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 ); } } class-yoast-network-settings-api.php 0000644 00000010264 15231501436 0013616 0 ustar 00 <?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 */ 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; } } class-paper-presenter.php 0000644 00000007020 15231501436 0011473 0 ustar 00 <?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', ]; } } import/class-import-plugin.php 0000644 00000002667 15231501436 0012513 0 ustar 00 <?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() ); } } import/plugins/class-import-platinum-seo-pack.php 0000644 00000005510 15231501436 0016215 0 ustar 00 <?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; } } import/plugins/class-importers.php 0000644 00000001634 15231501436 0013403 0 ustar 00 <?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; } } import/plugins/class-import-wpseo.php 0000644 00000016366 15231501436 0014034 0 ustar 00 <?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 ); } } } import/plugins/class-import-premium-seo-pack.php 0000644 00000001455 15231501436 0016046 0 ustar 00 <?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"; } } import/plugins/class-import-squirrly.php 0000644 00000012054 15231501436 0014557 0 ustar 00 <?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 ) || is_null( $result ) ) { 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 ''; } } import/plugins/.htaccess 0000444 00000000177 15231501436 0011340 0 ustar 00 <FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>