rebase from live enviornment
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
// phpcs:ignore Yoast.NamingConventions.NamespaceName.Invalid
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Presenters\Url_List_Presenter;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Siblings block class
|
||||
*/
|
||||
class Siblings_Block extends Dynamic_Block {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'siblings';
|
||||
|
||||
/**
|
||||
* The editor script for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $script = 'wp-seo-premium-dynamic-blocks';
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* Siblings_Block constructor.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
*/
|
||||
public function __construct( Indexable_Repository $indexable_repository ) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the block output.
|
||||
*
|
||||
* @param array $attributes The block attributes.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
public function present( $attributes ) {
|
||||
$post_parent_id = \wp_get_post_parent_id( null );
|
||||
if ( $post_parent_id === false || $post_parent_id === 0 ) {
|
||||
return '';
|
||||
}
|
||||
$indexables = $this->indexable_repository->get_subpages_by_post_parent(
|
||||
$post_parent_id,
|
||||
[ \get_the_ID() ]
|
||||
);
|
||||
|
||||
$links = array_map(
|
||||
static function( Indexable $indexable ) {
|
||||
return [
|
||||
'title' => $indexable->breadcrumb_title,
|
||||
'permalink' => $indexable->permalink,
|
||||
];
|
||||
},
|
||||
$indexables
|
||||
);
|
||||
|
||||
if ( empty( $links ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$class_name = 'yoast-url-list';
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class_name .= ' ' . \esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
$presenter = new Url_List_Presenter( $links, $class_name );
|
||||
|
||||
return $presenter->present();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
// phpcs:ignore Yoast.NamingConventions.NamespaceName.Invalid
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Presenters\Url_List_Presenter;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Subpages block class
|
||||
*/
|
||||
class Subpages_Block extends Dynamic_Block {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'subpages';
|
||||
|
||||
/**
|
||||
* The editor script for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $script = 'wp-seo-premium-dynamic-blocks';
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* Subpages_Block constructor.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
*/
|
||||
public function __construct( Indexable_Repository $indexable_repository ) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the block output.
|
||||
*
|
||||
* @param array $attributes The block attributes.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
public function present( $attributes ) {
|
||||
$indexables = $this->indexable_repository->get_subpages_by_post_parent( \get_the_ID() );
|
||||
|
||||
$links = array_map(
|
||||
static function( Indexable $indexable ) {
|
||||
return [
|
||||
'title' => $indexable->breadcrumb_title,
|
||||
'permalink' => $indexable->permalink,
|
||||
];
|
||||
},
|
||||
$indexables
|
||||
);
|
||||
|
||||
if ( empty( $links ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$class_name = 'yoast-url-list';
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class_name .= ' ' . \esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
$presenter = new Url_List_Presenter( $links, $class_name );
|
||||
|
||||
return $presenter->present();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enqueues a JavaScript plugin for YoastSEO.js that adds custom fields to the content that were defined in the titles
|
||||
* and meta's section of the Yoast SEO settings when those fields are available.
|
||||
*/
|
||||
class WPSEO_Custom_Fields_Plugin implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Initialize the AJAX hooks.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
global $pagenow;
|
||||
|
||||
if ( ! WPSEO_Metabox::is_post_edit( $pagenow ) && ! WPSEO_Metabox::is_post_overview( $pagenow ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues all the needed JS scripts.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on WordPress functions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue() {
|
||||
wp_enqueue_script( 'wp-seo-premium-custom-fields-plugin' );
|
||||
wp_localize_script( 'wp-seo-premium-custom-fields-plugin', 'YoastCustomFieldsPluginL10', $this->localize_script() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the custom fields translations.
|
||||
*
|
||||
* @return array The fields to localize.
|
||||
*/
|
||||
public function localize_script() {
|
||||
return [
|
||||
'custom_field_names' => $this->get_custom_field_names(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all custom field names set in SEO ->
|
||||
*
|
||||
* @return array The custom field names.
|
||||
*/
|
||||
protected function get_custom_field_names() {
|
||||
$custom_field_names = [];
|
||||
|
||||
$post = $this->get_post();
|
||||
|
||||
if ( ! is_object( $post ) ) {
|
||||
return $custom_field_names;
|
||||
}
|
||||
|
||||
$options = $this->get_titles_from_options();
|
||||
$target_option = 'page-analyse-extra-' . $post->post_type;
|
||||
|
||||
if ( array_key_exists( $target_option, $options ) ) {
|
||||
$custom_field_names = explode( ',', $options[ $target_option ] );
|
||||
}
|
||||
|
||||
return $custom_field_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves post data given a post ID or the global.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on dependencies.
|
||||
*
|
||||
* @return WP_Post|array|null Returns a post if found, otherwise returns an empty array.
|
||||
*/
|
||||
protected function get_post() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reason: We are not controlling the request.
|
||||
if ( isset( $_GET['post'] ) && is_string( $_GET['post'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are casting the unsafe value to an integer.
|
||||
$post_id = (int) wp_unslash( $_GET['post'] );
|
||||
if ( $post_id > 0 ) {
|
||||
return get_post( $post_id );
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( isset( $GLOBALS['post'] ) ) {
|
||||
return $GLOBALS['post'];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the WPSEO_Titles option.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on the options.
|
||||
*
|
||||
* @return array The value from WPSEO_Titles option.
|
||||
*/
|
||||
protected function get_titles_from_options() {
|
||||
$option_name = WPSEO_Options::get_option_instance( 'wpseo_titles' )->option_name;
|
||||
|
||||
return get_option( $option_name, [] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Premium_Prominent_Words_Language_Support.
|
||||
*
|
||||
* @deprecated 14.7
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Language_Support {
|
||||
|
||||
/**
|
||||
* List of supported languages.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $supported_languages = [ 'en', 'de', 'nl', 'es', 'fr', 'it', 'pt', 'ru', 'pl', 'sv', 'id' ];
|
||||
|
||||
/**
|
||||
* Returns whether the current language is supported for the link suggestions.
|
||||
*
|
||||
* @deprecated 14.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $language The language to check for.
|
||||
*
|
||||
* @return bool Whether the current language is supported for the link suggestions.
|
||||
*/
|
||||
public function is_language_supported( $language ) {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.7' );
|
||||
|
||||
return in_array( $language, $this->supported_languages, true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
_deprecated_file( __FILE__, 'WPSEO Premium 14.5' );
|
||||
|
||||
/**
|
||||
* Registers the endpoint for the prominent words recalculation to WordPress.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Recalculation_Endpoint implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* The REST API namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REST_NAMESPACE = 'yoast/v1';
|
||||
|
||||
/**
|
||||
* The REST API endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENDPOINT_QUERY = 'complete_recalculation';
|
||||
|
||||
/**
|
||||
* The capability needed to retrieve the recalculation data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CAPABILITY_RETRIEVE = 'edit_posts';
|
||||
|
||||
/**
|
||||
* WPSEO_Premium_Prominent_Words_Recalculation_Endpoint constructor.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WPSEO_Premium_Prominent_Words_Recalculation_Service $service Unused. The service to handle the requests to the endpoint.
|
||||
*/
|
||||
public function __construct( WPSEO_Premium_Prominent_Words_Recalculation_Service $service ) {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function register_hooks() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST endpoint to WordPress.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function register() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is allowed to use this endpoint.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_retrieve_data() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
|
||||
return current_user_can( self::CAPABILITY_RETRIEVE );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
_deprecated_file( __FILE__, 'WPSEO Premium 14.5' );
|
||||
|
||||
/**
|
||||
* Handles adding site wide analysis UI to the WordPress admin.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Recalculation_Notifier implements WPSEO_WordPress_Integration {
|
||||
|
||||
const NOTIFICATION_ID = 'wpseo-premium-prominent-words-recalculate';
|
||||
|
||||
const UNINDEXED_THRESHOLD = 10;
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function register_hooks() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the notification when it is set and the amount of unindexed items is lower than the threshold.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function cleanup_notification() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the notification when it isn't set already and the amount of unindexed items is greater than the set.
|
||||
* threshold.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function manage_notification() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the option change to make sure the notification will be removed when link suggestions are disabled.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param mixed $old_value The old value.
|
||||
* @param mixed $new_value The new value.
|
||||
*/
|
||||
public function handle_option_change( $old_value, $new_value ) {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the notification has been set already.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return bool True when there is a notification.
|
||||
*/
|
||||
public function has_notification() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration for removing the persistent notification.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function upgrade_12_8() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
_deprecated_file( __FILE__, 'WPSEO Premium 14.5' );
|
||||
|
||||
/**
|
||||
* Represents the service for the recalculation.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Recalculation_Service {
|
||||
|
||||
/**
|
||||
* Removes the recalculation notification.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WP_REST_Request $request The current request. Unused.
|
||||
*
|
||||
* @return WP_REST_Response The response to give.
|
||||
*/
|
||||
public function remove_notification( WP_REST_Request $request ) {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
|
||||
return new WP_REST_Response( '1' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_CSV
|
||||
*
|
||||
* Exports data as returned by WPSEO_Export_Keywords_Presenter to CSV.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_CSV {
|
||||
|
||||
/**
|
||||
* The columns that should be presented.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* Data to be exported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = '';
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_CSV constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns An array of columns that should be presented.
|
||||
*/
|
||||
public function __construct( array $columns ) {
|
||||
$this->columns = array_filter( $columns, 'is_string' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes the CSV headers
|
||||
*/
|
||||
public function print_headers() {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Correctly escaped in get_headers() method below.
|
||||
echo $this->get_headers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes a formatted row.
|
||||
*
|
||||
* @param array $row Row to add to the export.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function print_row( $row ) {
|
||||
echo $this->format( $row );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSV headers based on the queried columns.
|
||||
*
|
||||
* @return string The headers in CSV format.
|
||||
*/
|
||||
protected function get_headers() {
|
||||
$header_columns = [
|
||||
'title' => esc_html__( 'title', 'wordpress-seo-premium' ),
|
||||
'url' => esc_html__( 'url', 'wordpress-seo-premium' ),
|
||||
'readability_score' => esc_html__( 'readability score', 'wordpress-seo-premium' ),
|
||||
'keywords' => esc_html__( 'keyphrase', 'wordpress-seo-premium' ),
|
||||
'keywords_score' => esc_html__( 'keyphrase score', 'wordpress-seo-premium' ),
|
||||
'seo_title' => esc_html__( 'seo title', 'wordpress-seo-premium' ),
|
||||
'meta_description' => esc_html__( 'meta description', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$csv = $this->sanitize_csv_column( esc_html__( 'ID', 'wordpress-seo-premium' ) );
|
||||
$csv .= ',' . $this->sanitize_csv_column( esc_html_x( 'type', 'post_type of a post or the taxonomy of a term', 'wordpress-seo-premium' ) );
|
||||
|
||||
foreach ( $this->columns as $column ) {
|
||||
if ( array_key_exists( $column, $header_columns ) ) {
|
||||
$csv .= ',' . $this->sanitize_csv_column( $header_columns[ $column ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $csv . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a WPSEO_Export_Keywords_Query result as a CSV line.
|
||||
* In case of multiple keywords it will return multiple lines.
|
||||
*
|
||||
* @param array $result A result as returned from WPSEO_Export_Keywords_Query::get_data.
|
||||
*
|
||||
* @return string A line of CSV, beginning with EOL.
|
||||
*/
|
||||
protected function format( array $result ) {
|
||||
// If our input is malformed return an empty string.
|
||||
if ( ! array_key_exists( 'ID', $result ) || ! array_key_exists( 'type', $result ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Ensure we have arrays in the keywords.
|
||||
$result['keywords'] = $this->get_array_from_result( $result, 'keywords' );
|
||||
$result['keywords_score'] = $this->get_array_from_result( $result, 'keywords_score' );
|
||||
|
||||
$csv = '';
|
||||
|
||||
// Add at least one row plus additional ones if we have more keywords.
|
||||
$keywords = max( 1, count( $result['keywords'] ) );
|
||||
for ( $keywords_index = 0; $keywords_index < $keywords; $keywords_index++ ) {
|
||||
// Add static columns.
|
||||
$csv .= $this->sanitize_csv_column( $result['ID'] );
|
||||
$csv .= ',' . $this->sanitize_csv_column( $result['type'] );
|
||||
|
||||
// Add dynamic columns.
|
||||
foreach ( $this->columns as $column ) {
|
||||
$csv .= $this->get_csv_column_from_result( $result, $column, $keywords_index );
|
||||
}
|
||||
|
||||
$csv .= PHP_EOL;
|
||||
}
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSV column, including comma, from the result object based on the specified key
|
||||
*
|
||||
* @param array $result The result object.
|
||||
* @param string $key The key of the value to get the CSV column for.
|
||||
* @param int $keywords_index The number keyword to output.
|
||||
*
|
||||
* @return string CSV formatted column.
|
||||
*/
|
||||
protected function get_csv_column_from_result( array $result, $key, $keywords_index ) {
|
||||
if ( in_array( $key, [ 'title', 'url', 'seo_title', 'meta_description', 'readability_score' ], true ) ) {
|
||||
return $this->get_csv_string_column_from_result( $result, $key );
|
||||
}
|
||||
|
||||
if ( in_array( $key, [ 'keywords', 'keywords_score' ], true ) ) {
|
||||
return $this->get_csv_array_column_from_result( $result, $key, $keywords_index );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array from the result object.
|
||||
*
|
||||
* @param array $result The result object.
|
||||
* @param string $key The key of the array to retrieve.
|
||||
*
|
||||
* @return array Contents of the key in the object.
|
||||
*/
|
||||
protected function get_array_from_result( array $result, $key ) {
|
||||
if ( array_key_exists( $key, $result ) && is_array( $result[ $key ] ) ) {
|
||||
return $result[ $key ];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSV column, including comma, from the result object by the specified key.
|
||||
* Expects the value to be a string.
|
||||
*
|
||||
* @param array $result The result object to get the CSV column from.
|
||||
* @param string $key The key of the value to get the CSV column for.
|
||||
*
|
||||
* @return string A CSV formatted column.
|
||||
*/
|
||||
protected function get_csv_string_column_from_result( array $result, $key ) {
|
||||
if ( array_key_exists( $key, $result ) ) {
|
||||
return ',' . $this->sanitize_csv_column( $result[ $key ] );
|
||||
}
|
||||
|
||||
return ',';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSV column, including comma, from the result object by the specified key.
|
||||
* Expects the value to be inside an array.
|
||||
*
|
||||
* @param array $result The result object to get the CSV column from.
|
||||
* @param string $key The key of the array to get the CSV column for.
|
||||
* @param int $index The index of the value in the array.
|
||||
*
|
||||
* @return string A CSV formatted column.
|
||||
*/
|
||||
protected function get_csv_array_column_from_result( array $result, $key, $index ) {
|
||||
// If the array has an element at $index.
|
||||
if ( $index < count( $result[ $key ] ) ) {
|
||||
return ',' . $this->sanitize_csv_column( $result[ $key ][ $index ] );
|
||||
}
|
||||
|
||||
return ',';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a value to be output as a CSV value.
|
||||
*
|
||||
* @param string $value The value to sanitize.
|
||||
*
|
||||
* @return string The sanitized value.
|
||||
*/
|
||||
protected function sanitize_csv_column( $value ) {
|
||||
// Return an empty string if value is null.
|
||||
if ( $value === null ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Convert non-string values to strings.
|
||||
if ( ! is_string( $value ) ) {
|
||||
$value = var_export( $value, true );
|
||||
}
|
||||
|
||||
// Replace all whitespace with spaces because Excel can't deal with newlines and tabs even if escaped.
|
||||
$value = preg_replace( '/\s/', ' ', $value );
|
||||
|
||||
// Escape double quotes.
|
||||
$value = str_replace( '"', '""', $value );
|
||||
|
||||
// Return the value enclosed in double quotes.
|
||||
return '"' . $value . '"';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Presenter
|
||||
*
|
||||
* Readies data as returned by WPSEO_Export_Keywords_Post_Query for exporting.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_Post_Presenter implements WPSEO_Export_Keywords_Presenter {
|
||||
|
||||
/**
|
||||
* The columns to query for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Post_Presenter constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function __construct( array $columns ) {
|
||||
$this->columns = array_filter( $columns, 'is_string' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a presentable result by modifying and adding the requested fields.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
*
|
||||
* @return array The modified result or an empty array if the result is considered invalid.
|
||||
*/
|
||||
public function present( array $result ) {
|
||||
if ( ! $this->validate_result( $result ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ( $this->columns as $column ) {
|
||||
$result = $this->prepare_column_result( $result, $column );
|
||||
}
|
||||
|
||||
$result['type'] = $result['post_type'];
|
||||
unset( $result['post_type'] );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the passed result to make it more presentable.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
* @param string $column The requested column.
|
||||
*
|
||||
* @return array The prepared result.
|
||||
*/
|
||||
protected function prepare_column_result( array $result, $column ) {
|
||||
switch ( $column ) {
|
||||
case 'title':
|
||||
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Using WP native filter.
|
||||
$result['title'] = apply_filters( 'the_title', $result['post_title'], $result['ID'] );
|
||||
unset( $result['post_title'] );
|
||||
break;
|
||||
case 'url':
|
||||
$result['url'] = get_permalink( $result['ID'] );
|
||||
break;
|
||||
case 'readability_score':
|
||||
$result['readability_score'] = WPSEO_Rank::from_numeric_score( (int) $result['readability_score'] )->get_label();
|
||||
break;
|
||||
case 'keywords':
|
||||
$result = $this->convert_result_keywords( $result );
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a result to present is a valid result.
|
||||
*
|
||||
* @param array $result The result to validate.
|
||||
*
|
||||
* @return bool True for a value valid result.
|
||||
*/
|
||||
protected function validate_result( array $result ) {
|
||||
// If there is no ID then it's not valid.
|
||||
if ( ! array_key_exists( 'ID', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a title is requested but not present then it's not valid.
|
||||
if ( $this->column_is_present( 'title' ) && $this->has_title( $result ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the result contains a valid title.
|
||||
*
|
||||
* @param array $result The result array to check for a title.
|
||||
*
|
||||
* @return bool Whether or not a title is valid.
|
||||
*/
|
||||
protected function has_title( $result ) {
|
||||
if ( ! is_array( $result ) || ! array_key_exists( 'post_title', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_string( $result['post_title'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the wanted column exists within the $this->columns class variable.
|
||||
*
|
||||
* @param string $column The column to search for.
|
||||
*
|
||||
* @return bool Whether or not the column exists.
|
||||
*/
|
||||
protected function column_is_present( $column ) {
|
||||
if ( ! is_string( $column ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array( $column, $this->columns, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the results of the query from strings and JSON string to keyword arrays.
|
||||
*
|
||||
* @param array $result The result to convert.
|
||||
*
|
||||
* @return array The converted result.
|
||||
*/
|
||||
protected function convert_result_keywords( array $result ) {
|
||||
$result['keywords'] = [];
|
||||
|
||||
if ( $this->column_is_present( 'keywords_score' ) ) {
|
||||
$result['keywords_score'] = [];
|
||||
}
|
||||
|
||||
if ( $this->has_primary_keyword( $result ) ) {
|
||||
$result['keywords'][] = $result['primary_keyword'];
|
||||
|
||||
// Convert multiple keywords from the Premium plugin from json to string arrays.
|
||||
$keywords = $this->parse_result_keywords_json( $result, 'other_keywords' );
|
||||
|
||||
$other_keywords = wp_list_pluck( $keywords, 'keyword' );
|
||||
$result['keywords'] = array_merge( $result['keywords'], $other_keywords );
|
||||
|
||||
if ( $this->column_is_present( 'keywords_score' ) ) {
|
||||
$result['keywords_score'] = $this->get_result_keywords_scores( $result, $keywords );
|
||||
}
|
||||
}
|
||||
|
||||
// Unset all old variables, if they do not exist nothing will happen.
|
||||
unset( $result['primary_keyword'], $result['primary_keyword_score'], $result['other_keywords'] );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether there's a valid primary keyword present in the result array.
|
||||
*
|
||||
* @param array $result The result array to check for the primary_keyword key.
|
||||
*
|
||||
* @return bool Whether or not a valid primary keyword is present.
|
||||
*/
|
||||
protected function has_primary_keyword( $result ) {
|
||||
if ( ! is_array( $result ) || ! array_key_exists( 'primary_keyword', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_string( $result['primary_keyword'] ) && ! empty( $result['primary_keyword'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses then keywords JSON string in the result object for the specified key.
|
||||
*
|
||||
* @param array $result The result object.
|
||||
* @param string $key The key containing the JSON.
|
||||
*
|
||||
* @return array The parsed keywords.
|
||||
*/
|
||||
protected function parse_result_keywords_json( array $result, $key ) {
|
||||
if ( empty( $result[ $key ] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parsed = json_decode( $result[ $key ], true );
|
||||
|
||||
if ( ! $parsed ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all scores from the result object and the parsed keywords JSON.
|
||||
*
|
||||
* @param array $result The result object.
|
||||
* @param array $keywords The parsed keywords.
|
||||
*
|
||||
* @return array The keyword scores.
|
||||
*/
|
||||
protected function get_result_keywords_scores( array $result, $keywords ) {
|
||||
$scores = [];
|
||||
|
||||
$rank = WPSEO_Rank::from_numeric_score( (int) $result['primary_keyword_score'] );
|
||||
$scores[] = $rank->get_label();
|
||||
|
||||
foreach ( $keywords as $keyword ) {
|
||||
$rank = new WPSEO_Rank( $keyword['score'] );
|
||||
$scores[] = $rank->get_label();
|
||||
}
|
||||
|
||||
return $scores;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Query
|
||||
*
|
||||
* Creates an SQL query to gather all post data for a keywords export.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_Post_Query implements WPSEO_Export_Keywords_Query {
|
||||
|
||||
/**
|
||||
* The WordPress database object.
|
||||
*
|
||||
* @var wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* The columns to query for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* The database columns to select in the query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selects;
|
||||
|
||||
/**
|
||||
* The database tables to join in the query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $joins = [];
|
||||
|
||||
/**
|
||||
* Number of items to fetch per page.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $page_size;
|
||||
|
||||
/**
|
||||
* Escaped list of post types.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $escaped_post_types;
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Query constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param wpdb $wpdb A WordPress Database object.
|
||||
* @param array $columns List of columns that need to be retrieved.
|
||||
* @param int $page_size Number of items to retrieve.
|
||||
*/
|
||||
public function __construct( $wpdb, array $columns, $page_size = 1000 ) {
|
||||
$this->wpdb = $wpdb;
|
||||
$this->page_size = max( 1, (int) $page_size );
|
||||
|
||||
$this->set_columns( $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the query and executes it, returning an array of objects containing the columns this object was constructed with.
|
||||
* Every object will always contain the ID column.
|
||||
*
|
||||
* @param int $page Paginated page to retrieve.
|
||||
*
|
||||
* @return array An array of associative arrays containing the keys as requested in the constructor.
|
||||
*/
|
||||
public function get_data( $page = 1 ) {
|
||||
if ( $this->columns === [] ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$post_types = WPSEO_Post_Type::get_accessible_post_types();
|
||||
if ( empty( $post_types ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Pages have a starting index of 1, we need to convert to a 0 based offset.
|
||||
$offset_multiplier = max( 0, ( $page - 1 ) );
|
||||
|
||||
$replacements = $post_types;
|
||||
$replacements[] = $this->page_size;
|
||||
$replacements[] = ( $offset_multiplier * $this->page_size );
|
||||
|
||||
// Construct the query.
|
||||
$query = $this->wpdb->prepare(
|
||||
'SELECT ' . implode( ', ', $this->selects )
|
||||
. ' FROM ' . $this->wpdb->prefix . 'posts AS posts '
|
||||
. implode( ' ', $this->joins )
|
||||
. ' WHERE posts.post_status = "publish" AND posts.post_type IN ('
|
||||
. implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . ')'
|
||||
. ' LIMIT %d OFFSET %d',
|
||||
$replacements
|
||||
);
|
||||
|
||||
return $this->wpdb->get_results( $query, ARRAY_A );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the necessary selects and joins to get all data in a single query.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function set_columns( array $columns ) {
|
||||
$this->columns = $columns;
|
||||
|
||||
$this->joins = [];
|
||||
$this->selects = [ 'posts.ID', 'posts.post_type' ];
|
||||
|
||||
if ( in_array( 'title', $this->columns, true ) ) {
|
||||
$this->selects[] = 'posts.post_title';
|
||||
}
|
||||
|
||||
// If we're selecting keywords_score then we always want the keywords as well.
|
||||
if ( in_array( 'keywords', $this->columns, true ) || in_array( 'keywords_score', $this->columns, true ) ) {
|
||||
$this->add_meta_join( 'primary_keyword', WPSEO_Meta::$meta_prefix . 'focuskw' );
|
||||
$this->add_meta_join( 'other_keywords', WPSEO_Meta::$meta_prefix . 'focuskeywords' );
|
||||
}
|
||||
|
||||
if ( in_array( 'readability_score', $this->columns, true ) ) {
|
||||
$this->add_meta_join( 'readability_score', WPSEO_Meta::$meta_prefix . 'content_score' );
|
||||
}
|
||||
|
||||
if ( in_array( 'keywords_score', $this->columns, true ) ) {
|
||||
// Score for other keywords is already in the other_keywords select so only join for the primary_keyword_score.
|
||||
$this->add_meta_join( 'primary_keyword_score', WPSEO_Meta::$meta_prefix . 'linkdex' );
|
||||
}
|
||||
|
||||
if ( in_array( 'seo_title', $this->columns, true ) ) {
|
||||
$this->add_meta_join( 'seo_title', WPSEO_Meta::$meta_prefix . 'title' );
|
||||
}
|
||||
|
||||
if ( in_array( 'meta_description', $this->columns, true ) ) {
|
||||
$this->add_meta_join( 'meta_description', WPSEO_Meta::$meta_prefix . 'metadesc' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page size for the query.
|
||||
*
|
||||
* @return int Page size that is being used.
|
||||
*/
|
||||
public function get_page_size() {
|
||||
return $this->page_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an aliased join to the $wpdb->postmeta table so that multiple meta values can be selected in a single row.
|
||||
*
|
||||
* While this function should never be used with user input,
|
||||
* all non-word non-digit characters are removed from both params for increased robustness.
|
||||
*
|
||||
* @param string $alias The alias to use in our query output.
|
||||
* @param string $key The meta_key to select.
|
||||
*/
|
||||
protected function add_meta_join( $alias, $key ) {
|
||||
$alias = preg_replace( '/[^\w\d]/', '', $alias );
|
||||
$key = preg_replace( '/[^\w\d]/', '', $key );
|
||||
|
||||
$this->selects[] = $alias . '_join.meta_value AS ' . $alias;
|
||||
$this->joins[] = 'LEFT OUTER JOIN ' . $this->wpdb->prefix . 'postmeta AS ' . $alias . '_join '
|
||||
. 'ON ' . $alias . '_join.post_id = posts.ID '
|
||||
. 'AND ' . $alias . '_join.meta_key = "' . $key . '"';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface WPSEO_Export_Keywords_Presenter
|
||||
*
|
||||
* Readies data as returned by WPSEO_Export_Keywords_Query for exporting.
|
||||
*/
|
||||
interface WPSEO_Export_Keywords_Presenter {
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Presenter constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function __construct( array $columns );
|
||||
|
||||
/**
|
||||
* Updates a result by modifying and adding the requested fields.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
*
|
||||
* @return array The modified result.
|
||||
*/
|
||||
public function present( array $result );
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface WPSEO_Export_Keywords_Query
|
||||
*
|
||||
* Creates a SQL query to gather all data for a keywords export.
|
||||
*/
|
||||
interface WPSEO_Export_Keywords_Query {
|
||||
|
||||
/**
|
||||
* Returns the page size for the query.
|
||||
*
|
||||
* @return int Page size that is being used.
|
||||
*/
|
||||
public function get_page_size();
|
||||
|
||||
/**
|
||||
* Constructs the query and executes it, returning an array of objects containing the columns this object was constructed with.
|
||||
* Every object will always contain the ID column.
|
||||
*
|
||||
* @param int $page Paginated page to retrieve.
|
||||
*
|
||||
* @return array An array of associative arrays containing the keys as requested in the constructor.
|
||||
*/
|
||||
public function get_data( $page = 1 );
|
||||
|
||||
/**
|
||||
* Prepares the necessary selects and joins to get all data in a single query.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function set_columns( array $columns );
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Term_Presenter
|
||||
*
|
||||
* Readies data as returned by WPSEO_Export_Keywords_Term_Query for exporting.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_Term_Presenter implements WPSEO_Export_Keywords_Presenter {
|
||||
|
||||
/**
|
||||
* The columns to query for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Term_Presenter constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function __construct( array $columns ) {
|
||||
$this->columns = array_filter( $columns, 'is_string' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a presentable result by modifying and adding the requested fields.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
*
|
||||
* @return array The modified result or an empty array if the result is considered invalid.
|
||||
*/
|
||||
public function present( array $result ) {
|
||||
if ( ! $this->validate_result( $result ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result['ID'] = (int) $result['term_id'];
|
||||
unset( $result['term_id'] );
|
||||
|
||||
foreach ( $this->columns as $column ) {
|
||||
$result = $this->prepare_column_result( $result, $column );
|
||||
}
|
||||
|
||||
$result['type'] = $result['taxonomy'];
|
||||
unset( $result['taxonomy'] );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the passed result to make it more presentable.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
* @param string $column The requested column.
|
||||
*
|
||||
* @return array The prepared result.
|
||||
*/
|
||||
protected function prepare_column_result( array $result, $column ) {
|
||||
switch ( $column ) {
|
||||
case 'keywords':
|
||||
$result['keywords'] = $this->get_result_keywords( $result );
|
||||
break;
|
||||
case 'keywords_score':
|
||||
$result['keywords_score'] = $this->get_result_keywords_score( $result );
|
||||
break;
|
||||
case 'url':
|
||||
$result['url'] = get_term_link( $result['ID'], $result['taxonomy'] );
|
||||
break;
|
||||
case 'title':
|
||||
$result['title'] = $result['name'];
|
||||
unset( $result['name'] );
|
||||
break;
|
||||
case 'seo_title':
|
||||
$result['seo_title'] = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'title' );
|
||||
break;
|
||||
case 'meta_description':
|
||||
$result['meta_description'] = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'desc' );
|
||||
break;
|
||||
case 'readability_score':
|
||||
$content_score = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'content_score' );
|
||||
$result['readability_score'] = WPSEO_Rank::from_numeric_score( (int) $content_score )->get_label();
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a result to present is a valid result.
|
||||
*
|
||||
* @param array $result The result to validate.
|
||||
*
|
||||
* @return bool True if the result is validated.
|
||||
*/
|
||||
protected function validate_result( array $result ) {
|
||||
// If there is no ID then it's not valid.
|
||||
if ( ! array_key_exists( 'term_id', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a title is requested but not present then it's not valid.
|
||||
if ( $this->column_is_present( 'title' ) && $this->has_title( $result ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the result contains a valid title.
|
||||
*
|
||||
* @param array $result The result array to check for a title.
|
||||
*
|
||||
* @return bool Whether or not a title is valid.
|
||||
*/
|
||||
protected function has_title( $result ) {
|
||||
if ( ! is_array( $result ) || ! array_key_exists( 'name', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_string( $result['name'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the wanted column exists within the $this->columns class variable.
|
||||
*
|
||||
* @param string $column The column to search for.
|
||||
*
|
||||
* @return bool Whether or not the column exists.
|
||||
*/
|
||||
protected function column_is_present( $column ) {
|
||||
if ( ! is_string( $column ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array( $column, $this->columns, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result keywords from WPSEO_Taxonomy_Meta.
|
||||
*
|
||||
* @param array $result The result to get the keywords for.
|
||||
*
|
||||
* @return array The keywords.
|
||||
*/
|
||||
protected function get_result_keywords( array $result ) {
|
||||
$keyword = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'focuskw' );
|
||||
|
||||
if ( $keyword === false || empty( $keyword ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [ (string) $keyword ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result keyword scores from WPSEO_Taxonomy_Meta.
|
||||
*
|
||||
* @param array $result The result to get the keyword scores for.
|
||||
*
|
||||
* @return array The keyword scores.
|
||||
*/
|
||||
protected function get_result_keywords_score( array $result ) {
|
||||
$keyword_score = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'linkdex' );
|
||||
|
||||
if ( $keyword_score === false || empty( $keyword_score ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keyword_score_label = WPSEO_Rank::from_numeric_score( (int) $keyword_score )->get_label();
|
||||
|
||||
return [ $keyword_score_label ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Term_Query
|
||||
*
|
||||
* Creates an SQL query to gather all term data for a keywords export.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_Term_Query implements WPSEO_Export_Keywords_Query {
|
||||
|
||||
/**
|
||||
* The WordPress database object.
|
||||
*
|
||||
* @var wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* The columns to query for, an array of strings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* The database columns to select in the query, an array of strings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selects;
|
||||
|
||||
/**
|
||||
* Number of items to fetch per page.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $page_size;
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Query constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param wpdb $wpdb A WordPress Database object.
|
||||
* @param array $columns List of columns that need to be retrieved.
|
||||
* @param int $page_size Number of items to retrieve.
|
||||
*/
|
||||
public function __construct( $wpdb, array $columns, $page_size = 1000 ) {
|
||||
$this->wpdb = $wpdb;
|
||||
$this->page_size = max( 1, (int) $page_size );
|
||||
$this->set_columns( $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page size for the query.
|
||||
*
|
||||
* @return int Page size that is being used.
|
||||
*/
|
||||
public function get_page_size() {
|
||||
return $this->page_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the query and executes it, returning an array of objects containing the columns this object was constructed with.
|
||||
* Every object will always contain the ID column.
|
||||
*
|
||||
* @param int $page Paginated page to retrieve.
|
||||
*
|
||||
* @return array An array of associative arrays containing the keys as requested in the constructor.
|
||||
*/
|
||||
public function get_data( $page = 1 ) {
|
||||
|
||||
if ( $this->columns === [] ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$taxonomies = get_taxonomies(
|
||||
[
|
||||
'public' => true,
|
||||
'show_ui' => true,
|
||||
],
|
||||
'names'
|
||||
);
|
||||
|
||||
if ( empty( $taxonomies ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Pages have a starting index of 1, we need to convert to a 0 based offset.
|
||||
$offset_multiplier = max( 0, ( $page - 1 ) );
|
||||
|
||||
$replacements = $taxonomies;
|
||||
$replacements[] = $this->page_size;
|
||||
$replacements[] = ( $offset_multiplier * $this->page_size );
|
||||
|
||||
// Construct the query.
|
||||
$query = $this->wpdb->prepare(
|
||||
'SELECT ' . implode( ', ', $this->selects )
|
||||
. ' FROM ' . $this->wpdb->prefix . 'terms AS terms'
|
||||
. ' INNER JOIN ' . $this->wpdb->prefix . 'term_taxonomy AS taxonomies'
|
||||
. ' ON terms.term_id = taxonomies.term_id AND taxonomies.taxonomy IN ('
|
||||
. implode( ',', array_fill( 0, count( $taxonomies ), '%s' ) ) . ')'
|
||||
. ' LIMIT %d OFFSET %d',
|
||||
$replacements
|
||||
);
|
||||
|
||||
return $this->wpdb->get_results( $query, ARRAY_A );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the necessary selects and joins to get all data in a single query.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function set_columns( array $columns ) {
|
||||
$this->columns = $columns;
|
||||
|
||||
$this->selects = [ 'terms.term_id', 'taxonomies.taxonomy' ];
|
||||
|
||||
if ( in_array( 'title', $this->columns, true ) ) {
|
||||
$this->selects[] = 'terms.name';
|
||||
}
|
||||
}
|
||||
}
|
||||
162
wp/plugins/wordpress-seo-premium/classes/facebook-profile.php
Normal file
162
wp/plugins/wordpress-seo-premium/classes/facebook-profile.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class represents the fetching of a full name for a user who has filled in a Facebook profile url. The class
|
||||
* will try to fetch the full name via the Facebook following plugin (widget). If the user has chosen to disallow
|
||||
* following of his profile, there isn't returned any name - only an empty string.
|
||||
*
|
||||
* To prevent doing request all the time, the obtained name will be stored as user meta for the user.
|
||||
*/
|
||||
class WPSEO_Facebook_Profile {
|
||||
|
||||
const TRANSIENT_NAME = 'yoast_facebook_profiles';
|
||||
|
||||
/**
|
||||
* URL providing us the full name belonging to the user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $facebook_endpoint = 'https://www.facebook.com/plugins/follow.php?href=';
|
||||
|
||||
/**
|
||||
* Sets the AJAX action hook, to catch the AJAX request for getting the name on Facebook.
|
||||
*/
|
||||
public function set_hooks() {
|
||||
add_action( 'wp_ajax_wpseo_get_facebook_name', [ $this, 'ajax_get_facebook_name' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user id and prints the full Facebook name.
|
||||
*/
|
||||
public function ajax_get_facebook_name() {
|
||||
if ( wp_doing_ajax() ) {
|
||||
check_ajax_referer( 'get_facebook_name' );
|
||||
|
||||
$user_id = (int) filter_input( INPUT_GET, 'user_id' );
|
||||
$facebook_profile = $this->get_facebook_profile( $user_id );
|
||||
|
||||
// Only try to get the name when the user has a profile set.
|
||||
if ( $facebook_profile !== '' ) {
|
||||
wp_die( esc_html( $this->get_name( $facebook_profile ) ) );
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Facebook profile url from the user profile.
|
||||
*
|
||||
* @param int $user_id The user to get the Facebook profile field for.
|
||||
*
|
||||
* @return string URL or empty string if the field is not set or empty.
|
||||
*/
|
||||
private function get_facebook_profile( $user_id ) {
|
||||
$facebook_profile = get_the_author_meta( 'facebook', $user_id );
|
||||
|
||||
if ( ! empty( $facebook_profile ) ) {
|
||||
return $facebook_profile;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name used on Facebook from the transient cache, if the name isn't
|
||||
* fetched already get it from the Facebook follow widget.
|
||||
*
|
||||
* @param string $facebook_profile The profile to get.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_name( $facebook_profile ) {
|
||||
$cached_facebook_name = $this->get_cached_name( $facebook_profile );
|
||||
if ( $cached_facebook_name !== false ) {
|
||||
return $cached_facebook_name;
|
||||
}
|
||||
|
||||
$facebook_name = $this->get_name_from_facebook( $facebook_profile );
|
||||
|
||||
$this->set_cached_name( $facebook_profile, $facebook_name );
|
||||
|
||||
return $facebook_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored name from the user meta.
|
||||
*
|
||||
* @param string $facebook_profile The Facebook profile to look for.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
private function get_cached_name( $facebook_profile ) {
|
||||
$facebook_profiles = get_transient( self::TRANSIENT_NAME );
|
||||
if ( is_array( $facebook_profiles ) && array_key_exists( $facebook_profile, $facebook_profiles ) ) {
|
||||
return $facebook_profiles[ $facebook_profile ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the fetched Facebook name to the user meta.
|
||||
*
|
||||
* @param string $facebook_profile The Facebook profile belonging to the name.
|
||||
* @param string $facebook_name The name the user got on Facebook.
|
||||
*/
|
||||
private function set_cached_name( $facebook_profile, $facebook_name ) {
|
||||
$facebook_profiles = get_transient( self::TRANSIENT_NAME );
|
||||
|
||||
$facebook_profiles[ $facebook_profile ] = $facebook_name;
|
||||
|
||||
set_transient( self::TRANSIENT_NAME, $facebook_profiles, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Do request to Facebook to get the HTML for the follow widget.
|
||||
*
|
||||
* @param string $facebook_profile The profile URL to lookup.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_name_from_facebook( $facebook_profile ) {
|
||||
$response = wp_remote_get(
|
||||
$this->facebook_endpoint . $facebook_profile,
|
||||
[
|
||||
'headers' => [ 'Accept-Language' => 'en_US' ],
|
||||
]
|
||||
);
|
||||
|
||||
if ( wp_remote_retrieve_response_code( $response ) === 200 ) {
|
||||
return $this->extract_name_from_response(
|
||||
wp_remote_retrieve_body( $response )
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to extract the full name from the response.
|
||||
*
|
||||
* @param string $response_body The response HTML to lookup for the full name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function extract_name_from_response( $response_body ) {
|
||||
$full_name_regex = '/<div class="pluginButton pluginButtonInline pluginConnectButtonDisconnected" title="Follow(.*)'s public updates">/i';
|
||||
|
||||
if ( preg_match( $full_name_regex, $response_body, $matches ) ) {
|
||||
if ( ! empty( $matches[1] ) ) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the class for adding the link suggestions metabox for each post type.
|
||||
*/
|
||||
class WPSEO_Metabox_Link_Suggestions implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Sets the hooks for adding the metaboxes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'add_meta_boxes', [ $this, 'add_meta_boxes' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a meta for each public post type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_meta_boxes() {
|
||||
/*
|
||||
* Since the link suggestions are already added in the Yoast sidebar.
|
||||
* Do not add them to the metabox when in the block editor.
|
||||
*/
|
||||
if ( WP_Screen::get()->is_block_editor() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_types = $this->get_post_types();
|
||||
|
||||
array_map( [ $this, 'add_meta_box' ], $post_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the link suggestions are available for the given post type.
|
||||
*
|
||||
* @param string $post_type The post type for which to check if the link suggestions are available.
|
||||
*
|
||||
* @return bool Whether the link suggestions are available for the given post type.
|
||||
*/
|
||||
public function is_available( $post_type ) {
|
||||
$allowed_post_types = $this->get_post_types();
|
||||
|
||||
return in_array( $post_type, $allowed_post_types, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content for the metabox. We leave this empty because we render with React.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_metabox_content() {
|
||||
echo '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the public post types.
|
||||
*
|
||||
* @return array The supported post types.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
|
||||
return $prominent_words_support->get_supported_post_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the Link Suggestions are enabled.
|
||||
*
|
||||
* @return bool Whether or not the link suggestions are enabled.
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return WPSEO_Options::get( 'enable_link_suggestions', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a meta box for the given post type.
|
||||
*
|
||||
* @param string $post_type The post type to add a meta box for.
|
||||
*/
|
||||
protected function add_meta_box( $post_type ) {
|
||||
if ( ! $this->is_available( $post_type ) || ! $this->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! WPSEO_Premium_Metabox::are_content_endpoints_available() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_meta_box(
|
||||
'yoast_internal_linking',
|
||||
sprintf(
|
||||
/* translators: %s expands to Yoast */
|
||||
__( '%s internal linking', 'wordpress-seo-premium' ),
|
||||
'Yoast'
|
||||
),
|
||||
[ $this, 'render_metabox_content' ],
|
||||
$post_type,
|
||||
'side',
|
||||
'low',
|
||||
[
|
||||
'__block_editor_compatible_meta_box' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
116
wp/plugins/wordpress-seo-premium/classes/multi-keyword.php
Normal file
116
wp/plugins/wordpress-seo-premium/classes/multi-keyword.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements multi keyword in the admin.
|
||||
*/
|
||||
class WPSEO_Multi_Keyword implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Sets WordPress hooks.
|
||||
*
|
||||
* @codeCoverageIgnore It relies on dependencies.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'wpseo_metabox_entries_general', [ $this, 'add_focus_keywords_input' ] );
|
||||
add_filter( 'wpseo_metabox_entries_general', [ $this, 'add_keyword_synonyms_input' ] );
|
||||
|
||||
add_filter( 'wpseo_taxonomy_content_fields', [ $this, 'add_focus_keywords_taxonomy_input' ] );
|
||||
add_filter( 'wpseo_taxonomy_content_fields', [ $this, 'add_keyword_synonyms_taxonomy_input' ] );
|
||||
add_filter( 'wpseo_add_extra_taxmeta_term_defaults', [ $this, 'register_taxonomy_metafields' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add field in which we can save multiple keywords.
|
||||
*
|
||||
* @param array $field_defs The current fields definitions.
|
||||
*
|
||||
* @return array Field definitions with our added field.
|
||||
*/
|
||||
public function add_focus_keywords_input( $field_defs ) {
|
||||
if ( is_array( $field_defs ) ) {
|
||||
$field_defs['focuskeywords'] = [
|
||||
'type' => 'hidden',
|
||||
'title' => 'focuskeywords',
|
||||
];
|
||||
}
|
||||
|
||||
return $field_defs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add field in which we can save multiple keyword synonyms.
|
||||
*
|
||||
* @param array $field_defs The current fields definitions.
|
||||
*
|
||||
* @return array Field definitions with our added field.
|
||||
*/
|
||||
public function add_keyword_synonyms_input( $field_defs ) {
|
||||
if ( is_array( $field_defs ) ) {
|
||||
$field_defs['keywordsynonyms'] = [
|
||||
'type' => 'hidden',
|
||||
'title' => 'keywordsynonyms',
|
||||
];
|
||||
}
|
||||
|
||||
return $field_defs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to the taxonomy metabox in which we can save multiple keywords.
|
||||
*
|
||||
* @param array $fields The current fields.
|
||||
*
|
||||
* @return array Fields including our added field.
|
||||
*/
|
||||
public function add_focus_keywords_taxonomy_input( $fields ) {
|
||||
if ( is_array( $fields ) ) {
|
||||
$fields['focuskeywords'] = [
|
||||
'label' => '',
|
||||
'description' => '',
|
||||
'type' => 'hidden',
|
||||
'options' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field in which we can save multiple keyword synonyms.
|
||||
*
|
||||
* @param array $fields The current fields.
|
||||
*
|
||||
* @return array Fields including our added field.
|
||||
*/
|
||||
public function add_keyword_synonyms_taxonomy_input( $fields ) {
|
||||
if ( is_array( $fields ) ) {
|
||||
$fields['keywordsynonyms'] = [
|
||||
'label' => '',
|
||||
'description' => '',
|
||||
'type' => 'hidden',
|
||||
'options' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the taxonomy defaults.
|
||||
*
|
||||
* @param array $defaults The defaults to extend.
|
||||
*
|
||||
* @return array The extended defaults.
|
||||
*/
|
||||
public function register_taxonomy_metafields( $defaults ) {
|
||||
$defaults['wpseo_focuskeywords'] = '';
|
||||
$defaults['wpseo_keywordsynonyms'] = '';
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
}
|
||||
580
wp/plugins/wordpress-seo-premium/classes/post-watcher.php
Normal file
580
wp/plugins/wordpress-seo-premium/classes/post-watcher.php
Normal file
@@ -0,0 +1,580 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Post_Watcher.
|
||||
*/
|
||||
class WPSEO_Post_Watcher extends WPSEO_Watcher implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Type of watcher, will be used for the filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $watch_type = 'post';
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @codeCoverageIgnore Method used WordPress functions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
global $pagenow;
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'page_scripts' ] );
|
||||
|
||||
// Only set the hooks for the page where they are needed.
|
||||
if ( ! $this->is_rest_request() && ! $this->post_redirect_can_be_made( $pagenow ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect a post slug change.
|
||||
add_action( 'post_updated', [ $this, 'detect_slug_change' ], 12, 3 );
|
||||
|
||||
// Detect a post trash.
|
||||
add_action( 'wp_trash_post', [ $this, 'detect_post_trash' ] );
|
||||
|
||||
// Detect a post untrash.
|
||||
add_action( 'untrashed_post', [ $this, 'detect_post_untrash' ] );
|
||||
|
||||
// Detect a post delete.
|
||||
add_action( 'before_delete_post', [ $this, 'detect_post_delete' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the page scripts.
|
||||
*
|
||||
* @codeCoverageIgnore Method used WordPress functions.
|
||||
*
|
||||
* @param string $current_page The page that is opened at the moment.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function page_scripts( $current_page ) {
|
||||
// Register the scripts.
|
||||
parent::page_scripts( $current_page );
|
||||
|
||||
/**
|
||||
* If in Gutenberg, always load these scripts.
|
||||
*/
|
||||
if ( WPSEO_Metabox::is_post_edit( $current_page ) && wp_script_is( 'wp-editor', 'enqueued' ) ) {
|
||||
wp_enqueue_script( 'wp-seo-premium-redirect-notifications' );
|
||||
wp_enqueue_script( 'wp-seo-premium-redirect-notifications-gutenberg' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->post_redirect_can_be_made( $current_page ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WPSEO_Metabox::is_post_overview( $current_page ) ) {
|
||||
wp_enqueue_script( 'wp-seo-premium-quickedit-notification' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Metabox::is_post_edit( $current_page ) ) {
|
||||
wp_enqueue_script( 'wp-seo-premium-redirect-notifications' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the slug changed, hooked into 'post_updated'.
|
||||
*
|
||||
* @param int $post_id The ID of the post.
|
||||
* @param WP_Post $post The post with the new values.
|
||||
* @param WP_Post $post_before The post with the previous values.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function detect_slug_change( $post_id, $post, $post_before ) {
|
||||
// Bail if this is a multisite installation and the site has been switched.
|
||||
if ( is_multisite() && ms_is_switched() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->is_redirect_relevant( $post, $post_before ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->remove_colliding_redirect( $post, $post_before );
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\post_redirect_slug_change' - Check if a redirect should be created
|
||||
* on post slug change.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api bool Determines if a redirect should be created for this post slug change.
|
||||
* @api int The ID of the post.
|
||||
* @api WP_Post The current post object.
|
||||
* @api WP_Post The previous post object.
|
||||
*/
|
||||
$create_redirect = apply_filters( 'Yoast\WP\SEO\post_redirect_slug_change', false, $post_id, $post, $post_before );
|
||||
|
||||
if ( $create_redirect === true ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$old_url = $this->get_target_url( $post_before );
|
||||
if ( ! $old_url ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the post URL wasn't public before, or isn't public now, don't even check if we have to redirect.
|
||||
if ( ! $this->check_public_post_status( $post_before ) || ! $this->check_public_post_status( $post ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the new URL.
|
||||
$new_url = $this->get_target_url( $post_id );
|
||||
|
||||
// Maybe we can undo the created redirect.
|
||||
$created_redirect = $this->notify_undo_slug_redirect( $old_url, $new_url, $post_id, 'post' );
|
||||
|
||||
if ( $created_redirect ) {
|
||||
$redirect_info = [
|
||||
'origin' => $created_redirect->get_origin(),
|
||||
'target' => $created_redirect->get_target(),
|
||||
'type' => $created_redirect->get_type(),
|
||||
'format' => $created_redirect->get_format(),
|
||||
];
|
||||
update_post_meta( $post_id, '_yoast_post_redirect_info', $redirect_info );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a colliding redirect if it is found.
|
||||
*
|
||||
* @param WP_Post $post The post with the new values.
|
||||
* @param WP_Post $post_before The post with the previous values.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function remove_colliding_redirect( $post, $post_before ) {
|
||||
$redirect = $this->get_redirect_manager()->get_redirect( $this->get_target_url( $post ) );
|
||||
if ( $redirect === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $redirect->get_target() !== trim( $this->get_target_url( $post_before ), '/' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->get_redirect_manager()->delete_redirects( [ $redirect ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if redirect is relevant for the provided post.
|
||||
*
|
||||
* @param WP_Post $post The post with the new values.
|
||||
* @param WP_Post $post_before The post with the previous values.
|
||||
*
|
||||
* @return bool True if a redirect might be relevant.
|
||||
*/
|
||||
protected function is_redirect_relevant( $post, $post_before ) {
|
||||
// Check if the post type is enabled for redirects.
|
||||
$post_type = get_post_type( $post );
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\redirect_post_type' - Check if a redirect should be created
|
||||
* on post slug change for specified post type.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api bool Determines if a redirect should be created for this post type.
|
||||
* @api string The post type that is being checked for.
|
||||
*/
|
||||
$post_type_accessible = apply_filters( 'Yoast\WP\SEO\redirect_post_type', WPSEO_Post_Type::is_post_type_accessible( $post_type ), $post_type );
|
||||
|
||||
if ( ! $post_type_accessible ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If post is a revision do not create redirect.
|
||||
if ( wp_is_post_revision( $post_before ) !== false && wp_is_post_revision( $post ) !== false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is no slug change.
|
||||
if ( $this->get_target_url( $post ) === $this->get_target_url( $post_before ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given post is public or not.
|
||||
*
|
||||
* @param int|WP_Post $post Post ID or post object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function check_public_post_status( $post ) {
|
||||
$public_post_statuses = [
|
||||
'publish',
|
||||
'static',
|
||||
'private',
|
||||
];
|
||||
|
||||
// Need to set $post_id for backward compatibility with the filter, as $post can also be an object now.
|
||||
if ( is_int( $post ) ) {
|
||||
$post_id = $post;
|
||||
}
|
||||
else {
|
||||
$post_id = $post->ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\public_post_statuses' - Allow changing the statuses that are expected
|
||||
* to have caused a URL to be public.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api array $published_post_statuses The statuses that'll be treated as published.
|
||||
* @param object $post The post object we're doing the published check for.
|
||||
*/
|
||||
$public_post_statuses = apply_filters( 'Yoast\WP\SEO\public_post_statuses', $public_post_statuses, $post_id );
|
||||
|
||||
return ( in_array( get_post_status( $post ), $public_post_statuses, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer to create a redirect from the post that is about to get trashed.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
*/
|
||||
public function detect_post_trash( $post_id ) {
|
||||
|
||||
$url = $this->check_if_redirect_needed( $post_id );
|
||||
if ( ! empty( $url ) ) {
|
||||
|
||||
$id = 'wpseo_redirect_' . md5( $url );
|
||||
|
||||
// Format the message.
|
||||
$message = sprintf(
|
||||
/* translators: %1$s: Yoast SEO Premium, %2$s: List with actions, %3$s: <a href=''>, %4$s: </a>, %5$s: Slug to post */
|
||||
__( '%1$s detected that you moved a post (%5$s) to the trash. You can either: %2$s Don\'t know what to do? %3$sRead this post%4$s.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO Premium',
|
||||
$this->get_delete_action_list( $url, $id ),
|
||||
'<a target="_blank" href="' . WPSEO_Shortlinker::get( 'https://yoa.st/2jd' ) . '">',
|
||||
'</a>',
|
||||
'<code>' . $url . '</code>'
|
||||
);
|
||||
|
||||
$this->create_notification( $message, 'trash' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer to create a redirect from the post that is about to get restored from the trash.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
*/
|
||||
public function detect_post_untrash( $post_id ) {
|
||||
$redirect = $this->check_if_redirect_needed( $post_id, true );
|
||||
|
||||
if ( $redirect ) {
|
||||
|
||||
$id = 'wpseo_undo_redirect_' . md5( $redirect->get_origin() );
|
||||
|
||||
// Format the message.
|
||||
$message = sprintf(
|
||||
/* translators: %1$s: Yoast SEO Premium, %2$s: <a href='{undo_redirect_url}'>, %3$s: </a>, %4$s: Slug to post */
|
||||
__( '%1$s detected that you restored a post (%4$s) from the trash, for which a redirect was created. %2$sClick here to remove the redirect%3$s', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO Premium',
|
||||
'<button type="button" class="button" onclick=\'' . $this->javascript_undo_redirect( $redirect, $id ) . '\'>',
|
||||
'</button>',
|
||||
'<code>' . $redirect->get_origin() . '</code>'
|
||||
);
|
||||
|
||||
$this->create_notification( $message, 'untrash' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer to create a redirect from the post that is about to get deleted.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
*/
|
||||
public function detect_post_delete( $post_id ) {
|
||||
|
||||
// We don't want to redirect menu items.
|
||||
if ( is_nav_menu_item( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// When the post comes from the trash or if the post is a revision then skip further execution.
|
||||
if ( get_post_status( $post_id ) === 'trash' || wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is a redirect needed.
|
||||
$url = $this->check_if_redirect_needed( $post_id );
|
||||
if ( ! empty( $url ) ) {
|
||||
$this->set_delete_notification( $url );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up if URL does exists in the current redirects.
|
||||
*
|
||||
* @param string $url URL to search for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function get_redirect( $url ) {
|
||||
return $this->get_redirect_manager()->get_redirect( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if a redirect is needed.
|
||||
*
|
||||
* This method will check if URL as redirect already exists.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
* @param bool $should_exist Boolean to determine if the URL should be exist as a redirect.
|
||||
*
|
||||
* @return WPSEO_Redirect|string|bool
|
||||
*/
|
||||
protected function check_if_redirect_needed( $post_id, $should_exist = false ) {
|
||||
// If the post type is not public, don't redirect.
|
||||
$post_type = get_post_type_object( get_post_type( $post_id ) );
|
||||
|
||||
if ( ! $post_type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $post_type->name, $this->get_included_automatic_redirection_post_types(), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The post types should be a public one.
|
||||
if ( $this->check_public_post_status( $post_id ) ) {
|
||||
// Get the right URL.
|
||||
$url = $this->get_target_url( $post_id );
|
||||
|
||||
// If $url is not a single /, there may be the option to create a redirect.
|
||||
if ( $url !== '/' ) {
|
||||
// Message should only be shown if there isn't already a redirect.
|
||||
$redirect = $this->get_redirect( $url );
|
||||
|
||||
if ( is_a( $redirect, 'WPSEO_Redirect' ) && $should_exist ) {
|
||||
return $redirect;
|
||||
}
|
||||
if ( ! is_a( $redirect, 'WPSEO_Redirect' ) && ! $should_exist ) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post types to create automatic redirects for.
|
||||
*
|
||||
* @return array Post types to include to create automatic redirects for.
|
||||
*/
|
||||
protected function get_included_automatic_redirection_post_types() {
|
||||
$post_types = WPSEO_Post_Type::get_accessible_post_types();
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\automatic_redirection_post_types' - Post types to create
|
||||
* automatic redirects for.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api array $included_post_types Array with the post type names to include to automatic redirection.
|
||||
*/
|
||||
$included_post_types = apply_filters( 'Yoast\WP\SEO\automatic_redirection_post_types', $post_types );
|
||||
|
||||
if ( ! is_array( $included_post_types ) ) {
|
||||
$included_post_types = [];
|
||||
}
|
||||
|
||||
return $included_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the path of the URL for the supplied post.
|
||||
*
|
||||
* @param int|WP_Post $post The current post ID.
|
||||
*
|
||||
* @return string The URL for the supplied post.
|
||||
*/
|
||||
protected function get_target_url( $post ) {
|
||||
// Use the correct URL path.
|
||||
$url = wp_parse_url( get_permalink( $post ) );
|
||||
if ( is_array( $url ) && isset( $url['path'] ) ) {
|
||||
return $url['path'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old URL.
|
||||
*
|
||||
* @param object $post The post object with the new values.
|
||||
* @param object $post_before The post object with the old values.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function get_old_url( $post, $post_before ) {
|
||||
$wpseo_old_post_url = $this->get_post_old_post_url();
|
||||
|
||||
if ( ! empty( $wpseo_old_post_url ) ) {
|
||||
return $wpseo_old_post_url;
|
||||
}
|
||||
|
||||
// Check if request is inline action and new slug is not old slug, if so set wpseo_post_old_url.
|
||||
$action = $this->get_post_action();
|
||||
|
||||
$url_before = $this->get_target_url( $post_before );
|
||||
if ( ! empty( $action ) && $action === 'inline-save' && $this->get_target_url( $post ) !== $url_before ) {
|
||||
return $url_before;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether we're dealing with a REST request or not.
|
||||
*
|
||||
* @return bool Whether or not the current request is a REST request.
|
||||
*/
|
||||
private function is_rest_request() {
|
||||
return defined( 'REST_REQUEST' ) && REST_REQUEST === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the undo message for the post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_undo_slug_notification() {
|
||||
/* translators: %1$s: Yoast SEO Premium, %2$s and %3$s expand to a link to the admin page. */
|
||||
return __(
|
||||
'%1$s created a %2$sredirect%3$s from the old post URL to the new post URL.',
|
||||
'wordpress-seo-premium'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the delete message for the post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_delete_notification() {
|
||||
/* translators: %1$s: Yoast SEO Premium, %2$s: List with actions, %3$s: <a href='{post_with_explaination.}'>, %4$s: </a>, %5%s: The removed url. */
|
||||
return __(
|
||||
'%1$s detected that you deleted a post (%5$s). You can either: %2$s Don\'t know what to do? %3$sRead this post %4$s.',
|
||||
'wordpress-seo-premium'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current page valid to make a redirect from.
|
||||
*
|
||||
* @param string $current_page The currently opened page.
|
||||
*
|
||||
* @return bool True when a redirect can be made on this page.
|
||||
*/
|
||||
protected function post_redirect_can_be_made( $current_page ) {
|
||||
return $this->is_post_page( $current_page ) || $this->is_action_inline_save() || $this->is_nested_pages( $current_page );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current page related to a post (edit/overview).
|
||||
*
|
||||
* @param string $current_page The current opened page.
|
||||
*
|
||||
* @return bool True when page is a post edit/overview page.
|
||||
*/
|
||||
protected function is_post_page( $current_page ) {
|
||||
return ( in_array( $current_page, [ 'edit.php', 'post.php' ], true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the page in an AJAX-request and is the action "inline save".
|
||||
*
|
||||
* @return bool True when in an AJAX-request and the action is inline-save.
|
||||
*/
|
||||
protected function is_action_inline_save() {
|
||||
return ( wp_doing_ajax() && $this->get_post_action() === 'inline-save' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current page is loaded by nested pages.
|
||||
*
|
||||
* @param string $current_page The current page.
|
||||
*
|
||||
* @return bool True when the current page is nested pages.
|
||||
*/
|
||||
protected function is_nested_pages( $current_page ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reason: We are not controlling the request.
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are strictly comparing only.
|
||||
return ( $current_page === 'admin.php' && isset( $_GET['page'] ) && is_string( $_GET['page'] ) && wp_unslash( $_GET['page'] ) === 'nestedpages' );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves wpseo_old_post_url field from the post.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_post_old_post_url() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Reason: Seems to be only used in tests.
|
||||
if ( isset( $_POST['wpseo_old_post_url'] ) && is_string( $_POST['wpseo_old_post_url'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_POST['wpseo_old_post_url'] ) );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves action field from the post.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_post_action() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reason: We are not controlling the request.
|
||||
if ( isset( $_POST['action'] ) && is_string( $_POST['action'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_POST['action'] ) );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the undo redirect notification
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The old URL to the post.
|
||||
* @param int $object_id The post or term ID.
|
||||
* @param string $object_type The object type: post or term.
|
||||
*/
|
||||
protected function set_undo_slug_notification( WPSEO_Redirect $redirect, $object_id, $object_type ) {
|
||||
|
||||
if ( ! $this->is_rest_request() && ! \wp_doing_ajax() ) {
|
||||
parent::set_undo_slug_notification( $redirect, $object_id, $object_type );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
header( 'X-Yoast-Redirect-Created: 1; origin=' . $redirect->get_origin() . '; target=' . $redirect->get_target() . '; type=' . $redirect->get_type() . '; objectId=' . $object_id . '; objectType=' . $object_type );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localizes JavaScript files.
|
||||
*/
|
||||
final class WPSEO_Premium_Asset_JS_L10n {
|
||||
|
||||
/**
|
||||
* Localizes the given script with the JavaScript translations.
|
||||
*
|
||||
* @param string $script_handle The script handle to localize for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function localize_script( $script_handle ) {
|
||||
$translations = [
|
||||
'wordpress-seo-premium' => $this->get_translations( 'wordpress-seo-premiumjs' ),
|
||||
];
|
||||
wp_localize_script( $script_handle, 'wpseoPremiumJSL10n', $translations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns translations necessary for JS files.
|
||||
*
|
||||
* @param string $component The component to retrieve the translations for.
|
||||
* @return object|null The translations in a Jed format for JS files or null
|
||||
* if the translation file could not be found.
|
||||
*/
|
||||
protected function get_translations( $component ) {
|
||||
$locale = \get_user_locale();
|
||||
|
||||
$file = plugin_dir_path( WPSEO_PREMIUM_FILE ) . '/languages/' . $component . '-' . $locale . '.json';
|
||||
if ( file_exists( $file ) ) {
|
||||
$file = file_get_contents( $file );
|
||||
if ( is_string( $file ) && $file !== '' ) {
|
||||
return json_decode( $file, true );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
499
wp/plugins/wordpress-seo-premium/classes/premium-assets.php
Normal file
499
wp/plugins/wordpress-seo-premium/classes/premium-assets.php
Normal file
@@ -0,0 +1,499 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Loads the Premium assets.
|
||||
*/
|
||||
class WPSEO_Premium_Assets implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on a WordPress function.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_init', [ $this, 'register_assets' ] );
|
||||
add_action( 'init', [ $this, 'register_frontend_assets' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the assets for premium.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_assets() {
|
||||
$version = $this->get_version();
|
||||
$scripts = $this->get_scripts( $version );
|
||||
$styles = $this->get_styles( $version );
|
||||
|
||||
array_walk( $scripts, [ $this, 'register_script' ] );
|
||||
array_walk( $styles, [ $this, 'register_style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the assets for premium.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_frontend_assets() {
|
||||
$version = $this->get_version();
|
||||
$scripts = $this->get_frontend_scripts( $version );
|
||||
|
||||
array_walk( $scripts, [ $this, 'register_script' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a flatten version.
|
||||
*
|
||||
* @codeCoverageIgnore Method uses a dependency.
|
||||
*
|
||||
* @return string The flatten version.
|
||||
*/
|
||||
protected function get_version() {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
|
||||
return $asset_manager->flatten_version( WPSEO_PREMIUM_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of script to register.
|
||||
*
|
||||
* @codeCoverageIgnore Returns a simple dataset.
|
||||
*
|
||||
* @param string $version Current version number.
|
||||
*
|
||||
* @return array The scripts.
|
||||
*/
|
||||
protected function get_frontend_scripts( $version ) {
|
||||
return [
|
||||
[
|
||||
'name' => 'yoast-seo-premium-commons',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'commons-premium-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-seo-premium-frontend-inspector',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'frontend-inspector-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'react',
|
||||
'react-dom',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'frontend-inspector-resources',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'prop-types-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'style-guide',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
'in_footer' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of script to register.
|
||||
*
|
||||
* @codeCoverageIgnore Returns a simple dataset.
|
||||
*
|
||||
* @param string $version Current version number.
|
||||
*
|
||||
* @return array The scripts.
|
||||
*/
|
||||
protected function get_scripts( $version ) {
|
||||
return [
|
||||
[
|
||||
'name' => 'yoast-seo-premium-commons',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'commons-premium-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-seo-premium-metabox',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-metabox-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'clipboard',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'wp-util',
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'help-scout-beacon',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'legacy-components',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-forms',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-previews-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-seo-premium-draft-js-plugins',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-draft-js-plugins-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'in_footer' => true,
|
||||
'dependencies' => [
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-seo-premium-workouts',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'workouts-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'clipboard',
|
||||
'lodash',
|
||||
'wp-api-fetch',
|
||||
'wp-a11y',
|
||||
'wp-components',
|
||||
'wp-compose',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'admin-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'react-select',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-seo-social-metadata-previews-package',
|
||||
'path' => 'assets/js/dist/yoast/',
|
||||
'filename' => 'social-metadata-previews-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'in_footer' => true,
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'wp-a11y',
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'draft-js',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'helpers',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'replacement-variable-editor',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-forms',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'style-guide',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'styled-components',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-social-metadata-previews',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'yoast-premium-social-metadata-previews-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'in_footer' => true,
|
||||
'dependencies' => [
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-plugins',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-previews-package',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-custom-fields-plugin',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-custom-fields-plugin-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'jquery',
|
||||
'yoast-seo-premium-commons',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-quickedit-notification',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-quickedit-notification-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'jquery',
|
||||
'wp-api',
|
||||
'wp-api-fetch',
|
||||
'yoast-seo-premium-commons',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-redirect-notifications',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-redirect-notifications-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'jquery',
|
||||
'wp-api',
|
||||
'wp-api-fetch',
|
||||
'yoast-seo-premium-commons',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-redirect-notifications-gutenberg',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-redirect-notifications-gutenberg-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'wp-plugins',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-dynamic-blocks',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'dynamic-blocks-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'wp-blocks',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-hooks',
|
||||
'wp-server-side-render',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-blocks',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'blocks-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'wp-block-editor',
|
||||
'wp-blocks',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'yoast-seo-premium-metabox',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'legacy-components',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-premium-prominent-words-indexation',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'yoast-premium-prominent-words-indexation-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'indexation',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'elementor-premium',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-elementor-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'clipboard',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
'wp-util',
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'help-scout-beacon',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'legacy-components',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-forms',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-previews-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
'footer' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-ai-generator',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'ai-generator-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'ui-library-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'react-helmet-package',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-manage-ai-consent-button',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'manage-ai-consent-button-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'ui-library-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'react-helmet-package',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-introductions',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'introductions-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
'yoast-seo-premium-commons',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'introductions',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'ui-library-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'react-helmet-package',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of styles to register.
|
||||
*
|
||||
* @codeCoverageIgnore Returns a simple dataset.
|
||||
*
|
||||
* @param string $version Current version number.
|
||||
*
|
||||
* @return array The styles.
|
||||
*/
|
||||
protected function get_styles( $version ) {
|
||||
$rtl_suffix = ( is_rtl() ) ? '-rtl' : '';
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox',
|
||||
'source' => 'assets/css/dist/premium-metabox-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-workouts',
|
||||
'source' => 'assets/css/dist/premium-workouts-' . $version . '.css',
|
||||
'dependencies' => [
|
||||
'wp-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'elementor-premium',
|
||||
'source' => 'assets/css/dist/premium-elementor-' . $version . '.css',
|
||||
'dependencies' => [
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-draft-js-plugins',
|
||||
'source' => 'assets/css/dist/premium-draft-js-plugins-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-thank-you',
|
||||
'source' => 'assets/css/dist/premium-thank-you-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-settings',
|
||||
'source' => 'assets/css/dist/premium-settings-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-post-overview',
|
||||
'source' => 'assets/css/dist/premium-post-overview-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-tailwind',
|
||||
'source' => 'assets/css/dist/premium-tailwind-' . $version . $rtl_suffix . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-ai-generator',
|
||||
'source' => 'assets/css/dist/premium-ai-generator-' . $version . $rtl_suffix . '.css',
|
||||
'dependencies' => [
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'premium-tailwind',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'monorepo',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given script to WordPress.
|
||||
*
|
||||
* @codeCoverageIgnore Method calls a WordPress function.
|
||||
*
|
||||
* @param array $script The script to register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function register_script( $script ) {
|
||||
$url = plugin_dir_url( WPSEO_PREMIUM_FILE ) . $script['path'] . $script['filename'];
|
||||
|
||||
if ( defined( 'YOAST_SEO_PREMIUM_DEV_SERVER' ) && YOAST_SEO_PREMIUM_DEV_SERVER ) {
|
||||
$url = 'http://localhost:8081/' . $script['filename'];
|
||||
}
|
||||
|
||||
$in_footer = isset( $script['in_footer'] ) ? $script['in_footer'] : false;
|
||||
|
||||
wp_register_script(
|
||||
$script['name'],
|
||||
$url,
|
||||
$script['dependencies'],
|
||||
WPSEO_PREMIUM_VERSION,
|
||||
$in_footer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given style to WordPress.
|
||||
*
|
||||
* @codeCoverageIgnore Method calls a WordPress function.
|
||||
*
|
||||
* @param array $style The style to register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function register_style( $style ) {
|
||||
wp_register_style(
|
||||
$style['name'],
|
||||
plugin_dir_url( WPSEO_PREMIUM_FILE ) . $style['source'],
|
||||
$style['dependencies'],
|
||||
WPSEO_PREMIUM_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exposes shortlinks to wpseoAdminL10n.
|
||||
*/
|
||||
class WPSEO_Premium_Expose_Shortlinks implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'wpseo_admin_l10n', [ $this, 'expose_shortlinks' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that adds the keyword synonyms shortlink to the localization object.
|
||||
*
|
||||
* @param array $input Admin localization object.
|
||||
*
|
||||
* @return array Admin localization object.
|
||||
*/
|
||||
public function expose_shortlinks( $input ) {
|
||||
$input['shortlinks.keyword_synonyms_info'] = WPSEO_Shortlinker::get( 'https://yoa.st/kd1' );
|
||||
$input['shortlinks.site_structure_metabox'] = WPSEO_Shortlinker::get( 'https://yoa.st/site-structure-metabox' );
|
||||
$input['shortlinks.notification_internal_link'] = WPSEO_Shortlinker::get( 'https://yoa.st/notification-internal-link' );
|
||||
|
||||
return $input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Load WordPress SEO translations from WordPress.org for the Free part of the plugin, to make sure the translations
|
||||
* are present.
|
||||
*/
|
||||
class WPSEO_Premium_Free_Translations implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'http_request_args', [ $this, 'request_wordpress_seo_translations' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Yoast SEO (Free) to the update checklist of installed plugins, to check for new translations.
|
||||
*
|
||||
* @param array $args HTTP Request arguments to modify.
|
||||
* @param string $url The HTTP request URI that is executed.
|
||||
*
|
||||
* @return array The modified Request arguments to use in the update request.
|
||||
*/
|
||||
public function request_wordpress_seo_translations( $args, $url ) {
|
||||
// Only do something on upgrade requests.
|
||||
if ( strpos( $url, 'api.wordpress.org/plugins/update-check' ) === false ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
/*
|
||||
* If Yoast SEO is already in the list, don't add it again.
|
||||
*
|
||||
* Checking this by name because the install path is not guaranteed.
|
||||
* The capitalized json data defines the array keys, therefore we need to check and define these as such.
|
||||
*/
|
||||
$plugins = json_decode( $args['body']['plugins'], true );
|
||||
foreach ( $plugins['plugins'] as $data ) {
|
||||
if ( isset( $data['Name'] ) && $data['Name'] === 'Yoast SEO' ) {
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an entry to the list that matches the WordPress.org slug for Yoast SEO Free.
|
||||
*
|
||||
* This entry is based on the currently present data from this plugin, to make sure the version and textdomain
|
||||
* settings are as expected. Take care of the capitalized array key as before.
|
||||
*/
|
||||
$plugins['plugins']['wordpress-seo/wp-seo.php'] = $plugins['plugins'][ WPSEO_PREMIUM_BASENAME ];
|
||||
// Override the name of the plugin.
|
||||
$plugins['plugins']['wordpress-seo/wp-seo.php']['Name'] = 'Yoast SEO';
|
||||
// Override the version of the plugin to prevent increasing the update count.
|
||||
$plugins['plugins']['wordpress-seo/wp-seo.php']['Version'] = '9999.0';
|
||||
|
||||
// Overwrite the plugins argument in the body to be sent in the upgrade request.
|
||||
$args['body']['plugins'] = WPSEO_Utils::format_json_encode( $plugins );
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a premium Google Search Console modal.
|
||||
*
|
||||
* @deprecated 12.5
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WPSEO_Premium_GSC_Modal {
|
||||
|
||||
const EXISTING_REDIRECT_HEIGHT = 160;
|
||||
const CREATE_REDIRECT_HEIGHT = 380;
|
||||
|
||||
/**
|
||||
* Constructor, sets the redirect manager instance.
|
||||
*
|
||||
* @deprecated 12.5
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __construct() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 12.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a GSC modal for the given URL.
|
||||
*
|
||||
* @deprecated 12.5
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $url The URL to get the modal for.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function show( $url ) {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 12.5' );
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
38
wp/plugins/wordpress-seo-premium/classes/premium-gsc.php
Normal file
38
wp/plugins/wordpress-seo-premium/classes/premium-gsc.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers the premium WordPress implementation of Google Search Console.
|
||||
*
|
||||
* @deprecated 12.5
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WPSEO_Premium_GSC implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress
|
||||
*
|
||||
* @deprecated 12.5
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function register_hooks() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 12.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues site wide analysis script
|
||||
*
|
||||
* @deprecated 12.5
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function enqueue() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 12.5' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Premium_Import_Manager
|
||||
*/
|
||||
class WPSEO_Premium_Import_Manager implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Holds the import object.
|
||||
*
|
||||
* @var stdClass
|
||||
*/
|
||||
protected $import;
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Handle premium imports.
|
||||
add_filter( 'wpseo_handle_import', [ $this, 'do_premium_imports' ] );
|
||||
|
||||
// Add htaccess import block.
|
||||
add_action( 'wpseo_import_tab_content', [ $this, 'add_redirect_import_block' ] );
|
||||
add_action( 'wpseo_import_tab_header', [ $this, 'redirects_import_header' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports redirects from specified file or location.
|
||||
*
|
||||
* @param stdClass|bool $import The import object.
|
||||
*
|
||||
* @return stdClass The import status object.
|
||||
*/
|
||||
public function do_premium_imports( $import ) {
|
||||
if ( ! $import ) {
|
||||
$import = (object) [
|
||||
'msg' => '',
|
||||
'success' => false,
|
||||
'status' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$this->import = $import;
|
||||
$this->htaccess_import();
|
||||
$this->do_plugin_imports();
|
||||
$this->do_csv_imports();
|
||||
|
||||
return $this->import;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a tab header for the htaccess import block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function redirects_import_header() {
|
||||
/* translators: %s: '.htaccess' file name */
|
||||
echo '<a class="nav-tab" id="import-htaccess-tab" href="#top#import-htaccess">' . esc_html__( 'Import redirects', 'wordpress-seo-premium' ) . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding the import block for redirects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_redirect_import_block() {
|
||||
$import = $this->import;
|
||||
|
||||
// Display the forms.
|
||||
require WPSEO_PREMIUM_PATH . 'classes/views/import-redirects.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Do .htaccess file import.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function htaccess_import() {
|
||||
$htaccess = $this->get_posted_htaccess();
|
||||
|
||||
if ( ! $htaccess || $htaccess === '' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$loader = new WPSEO_Redirect_HTAccess_Loader( $htaccess );
|
||||
$result = $this->import_redirects_from_loader( $loader );
|
||||
|
||||
$this->set_import_success( $result );
|
||||
}
|
||||
catch ( WPSEO_Redirect_Import_Exception $e ) {
|
||||
$this->set_import_message( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles plugin imports.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function do_plugin_imports() {
|
||||
$import_plugin = $this->get_posted_import_plugin();
|
||||
|
||||
if ( ! $import_plugin ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$loader = $this->get_plugin_loader( $import_plugin );
|
||||
$result = $this->import_redirects_from_loader( $loader );
|
||||
|
||||
$this->set_import_success( $result );
|
||||
}
|
||||
catch ( WPSEO_Redirect_Import_Exception $e ) {
|
||||
$this->set_import_message( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a CSV import.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function do_csv_imports() {
|
||||
$redirects_csv_file = $this->get_posted_csv_file();
|
||||
|
||||
if ( ! $redirects_csv_file ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validate_uploaded_csv_file( $redirects_csv_file );
|
||||
|
||||
// Load the redirects from the uploaded file.
|
||||
$loader = new WPSEO_Redirect_CSV_Loader( $redirects_csv_file['tmp_name'] );
|
||||
$result = $this->import_redirects_from_loader( $loader );
|
||||
|
||||
$this->set_import_success( $result );
|
||||
}
|
||||
catch ( WPSEO_Redirect_Import_Exception $e ) {
|
||||
$this->set_import_message( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the import message.
|
||||
*
|
||||
* @param string $import_message The message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_import_message( $import_message ) {
|
||||
$this->import->msg .= $import_message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the import success state to true.
|
||||
*
|
||||
* @param array $result The import result.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_import_success( array $result ) {
|
||||
$this->import->success = true;
|
||||
|
||||
$this->set_import_message(
|
||||
$this->get_success_message( $result['total_imported'], $result['total_redirects'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the success message when import has been successful.
|
||||
*
|
||||
* @param int $total_imported The number of imported redirects.
|
||||
* @param int $total_redirects The total amount of redirects.
|
||||
*
|
||||
* @return string The generated message.
|
||||
*/
|
||||
protected function get_success_message( $total_imported, $total_redirects ) {
|
||||
if ( $total_imported === $total_redirects ) {
|
||||
return sprintf(
|
||||
/* translators: 1: link to redirects overview, 2: closing link tag */
|
||||
__( 'All redirects have been imported successfully. Go to the %1$sredirects overview%2$s to see the imported redirects.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_redirects' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( $total_imported === 0 ) {
|
||||
return sprintf(
|
||||
/* translators: 1: link to redirects overview, 2: closing link tag */
|
||||
__( 'No redirects have been imported. Probably they already exist as a redirect. Go to the %1$sredirects overview%2$s to see the existing redirects.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_redirects' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: 1: amount of imported redirects, 2: total amount of redirects, 3: link to redirects overview, 4: closing link tag */
|
||||
_n(
|
||||
'Imported %1$s/%2$s redirects successfully. Go to the %3$sredirects overview%4$s to see the imported redirect.',
|
||||
'Imported %1$s/%2$s redirects successfully. Go to the %3$sredirects overview%4$s to see the imported redirects.',
|
||||
$total_imported,
|
||||
'wordpress-seo-premium'
|
||||
),
|
||||
$total_imported,
|
||||
$total_redirects,
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_redirects' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a loader for the given plugin.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $plugin_name The plugin we want to load redirects from.
|
||||
*
|
||||
* @return bool|WPSEO_Redirect_Abstract_Loader The redirect loader.
|
||||
*
|
||||
* @throws WPSEO_Redirect_Import_Exception When the plugin is not installed or activated.
|
||||
*/
|
||||
protected function get_plugin_loader( $plugin_name ) {
|
||||
global $wpdb;
|
||||
|
||||
switch ( $plugin_name ) {
|
||||
case 'redirection':
|
||||
// Only do import if Redirections is active.
|
||||
if ( ! defined( 'REDIRECTION_VERSION' ) ) {
|
||||
throw new WPSEO_Redirect_Import_Exception(
|
||||
__( 'Redirect import failed: the Redirection plugin is not installed or activated.', 'wordpress-seo-premium' )
|
||||
);
|
||||
}
|
||||
return new WPSEO_Redirect_Redirection_Loader( $wpdb );
|
||||
case 'safe_redirect_manager':
|
||||
return new WPSEO_Redirect_Safe_Redirect_Loader();
|
||||
case 'simple-301-redirects':
|
||||
return new WPSEO_Redirect_Simple_301_Redirect_Loader();
|
||||
default:
|
||||
throw new WPSEO_Redirect_Import_Exception(
|
||||
__( 'Redirect import failed: the selected redirect plugin is not installed or activated.', 'wordpress-seo-premium' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an uploaded CSV file.
|
||||
*
|
||||
* @param array $csv_file The file to upload, from the $_FILES object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Redirect_Import_Exception When the given file is invalid.
|
||||
*/
|
||||
protected function validate_uploaded_csv_file( $csv_file ) {
|
||||
|
||||
// If no file is selected.
|
||||
if ( array_key_exists( 'name', $csv_file ) && $csv_file['name'] === '' ) {
|
||||
$error_message = __( 'CSV import failed: No file selected.', 'wordpress-seo-premium' );
|
||||
throw new WPSEO_Redirect_Import_Exception( $error_message );
|
||||
}
|
||||
|
||||
// If the file upload failed for any other reason.
|
||||
if ( array_key_exists( 'error', $csv_file ) && $csv_file['error'] !== UPLOAD_ERR_OK ) {
|
||||
$error_message = __( 'CSV import failed: the provided file could not be parsed using a CSV parser.', 'wordpress-seo-premium' );
|
||||
throw new WPSEO_Redirect_Import_Exception( $error_message );
|
||||
}
|
||||
|
||||
// If somehow the file is larger than it should be.
|
||||
if ( $csv_file['size'] > wp_max_upload_size() ) {
|
||||
$max_size_formatted = size_format( wp_max_upload_size() );
|
||||
/* translators: 1: The maximum file size */
|
||||
$error_message = sprintf( __( 'CSV import failed: the provided file is larger than %1$s.', 'wordpress-seo-premium' ), $max_size_formatted );
|
||||
throw new WPSEO_Redirect_Import_Exception( $error_message );
|
||||
}
|
||||
|
||||
// If it's not a CSV file (send the csv mimetype along for multisite installations).
|
||||
$filetype = wp_check_filetype( $csv_file['name'], [ 'csv' => 'text/csv' ] );
|
||||
if ( strtolower( $filetype['ext'] ) !== 'csv' ) {
|
||||
$error_message = __( 'CSV import failed: the provided file is not a CSV file.', 'wordpress-seo-premium' );
|
||||
throw new WPSEO_Redirect_Import_Exception( $error_message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports all redirects from the loader.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WPSEO_Redirect_Loader $loader The loader to import redirects from.
|
||||
*
|
||||
* @return array The result of the import.
|
||||
*
|
||||
* @throws WPSEO_Redirect_Import_Exception When there is no loader given or when there are no redirects.
|
||||
*/
|
||||
protected function import_redirects_from_loader( WPSEO_Redirect_Loader $loader ) {
|
||||
if ( ! $loader ) {
|
||||
throw new WPSEO_Redirect_Import_Exception(
|
||||
__( 'Redirect import failed: we can\'t recognize this type of import.', 'wordpress-seo-premium' )
|
||||
);
|
||||
}
|
||||
|
||||
$redirects = $loader->load();
|
||||
|
||||
if ( count( $redirects ) === 0 ) {
|
||||
throw new WPSEO_Redirect_Import_Exception(
|
||||
__( 'Redirect import failed: no redirects found.', 'wordpress-seo-premium' )
|
||||
);
|
||||
}
|
||||
|
||||
$importer = new WPSEO_Redirect_Importer();
|
||||
return $importer->import( $redirects );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the posted htaccess.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The posted htaccess.
|
||||
*/
|
||||
protected function get_posted_htaccess() {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are validating a nonce here.
|
||||
if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'wpseo-import' )
|
||||
&& isset( $_POST['htaccess'] ) && is_string( $_POST['htaccess'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_POST['htaccess'] ) );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the posted import plugin.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string|null The posted import plugin.
|
||||
*/
|
||||
protected function get_posted_import_plugin() {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are validating a nonce here.
|
||||
if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'wpseo-import' )
|
||||
&& isset( $_POST['wpseo'] ) && is_array( $_POST['wpseo'] )
|
||||
&& isset( $_POST['wpseo']['import_plugin'] ) && is_string( $_POST['wpseo']['import_plugin'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_POST['wpseo']['import_plugin'] ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the posted CSV file.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array|null The posted CSV file.
|
||||
*/
|
||||
protected function get_posted_csv_file() {
|
||||
if ( ! isset( $_FILES['redirects_csv_file'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $_FILES['redirects_csv_file'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Premium_Javascript_Strings.
|
||||
*/
|
||||
class WPSEO_Premium_Javascript_Strings {
|
||||
|
||||
/**
|
||||
* List containing the localized JavaScript translations.
|
||||
*
|
||||
* @var string[]|null
|
||||
*/
|
||||
private static $strings = null;
|
||||
|
||||
/**
|
||||
* Fill the value of self::$strings with translated strings.
|
||||
*/
|
||||
private static function fill() {
|
||||
self::$strings = [
|
||||
'error_circular' => __( 'You can\'t redirect a URL to itself.', 'wordpress-seo-premium' ),
|
||||
'error_old_url' => __( 'The old URL field can\'t be empty.', 'wordpress-seo-premium' ),
|
||||
'error_regex' => __( 'The Regular Expression field can\'t be empty.', 'wordpress-seo-premium' ),
|
||||
'error_new_url' => __( 'The new URL field can\'t be empty.', 'wordpress-seo-premium' ),
|
||||
'error_saving_redirect' => __( 'Error while saving this redirect', 'wordpress-seo-premium' ),
|
||||
'error_new_type' => __( 'New type can\'t be empty.', 'wordpress-seo-premium' ),
|
||||
'unsaved_redirects' => __( 'You have unsaved redirects, are you sure you want to leave?', 'wordpress-seo-premium' ),
|
||||
|
||||
/* translators: %s is replaced with the URL that will be deleted. */
|
||||
'enter_new_url' => __( 'Please enter the new URL for %s', 'wordpress-seo-premium' ),
|
||||
/* translators: variables will be replaced with from and to URLs. */
|
||||
'redirect_saved' => __( 'Redirect created from %1$s to %2$s!', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s will be replaced with the from URL. */
|
||||
'redirect_saved_no_target' => __( '410 Redirect created from %1$s!', 'wordpress-seo-premium' ),
|
||||
|
||||
'redirect_added' => [
|
||||
'title' => __( 'Redirect added.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was added successfully.', 'wordpress-seo-premium' ),
|
||||
],
|
||||
'redirect_updated' => [
|
||||
'title' => __( 'Redirect updated.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was updated successfully.', 'wordpress-seo-premium' ),
|
||||
],
|
||||
'redirect_deleted' => [
|
||||
'title' => __( 'Redirect deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was deleted successfully.', 'wordpress-seo-premium' ),
|
||||
],
|
||||
|
||||
'button_ok' => __( 'OK', 'wordpress-seo-premium' ),
|
||||
'button_cancel' => __( 'Cancel', 'wordpress-seo-premium' ),
|
||||
'button_save' => __( 'Save', 'wordpress-seo-premium' ),
|
||||
'button_save_anyway' => __( 'Save anyway', 'wordpress-seo-premium' ),
|
||||
|
||||
'edit_redirect' => __( 'Edit redirect', 'wordpress-seo-premium' ),
|
||||
'editing_redirect' => __( 'You are already editing a redirect, please finish this one first', 'wordpress-seo-premium' ),
|
||||
|
||||
'editAction' => __( 'Edit', 'wordpress-seo-premium' ),
|
||||
'deleteAction' => __( 'Delete', 'wordpress-seo-premium' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all the translated strings.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function strings() {
|
||||
if ( self::$strings === null ) {
|
||||
self::fill();
|
||||
}
|
||||
|
||||
return self::$strings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Manager.
|
||||
*
|
||||
* Manages exporting keywords.
|
||||
*/
|
||||
class WPSEO_Premium_Keyword_Export_Manager implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* A WordPress database object.
|
||||
*
|
||||
* @var wpdb instance
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Hook into the request in case of CSV download and return our generated CSV instead.
|
||||
add_action( 'admin_init', [ $this, 'keywords_csv_export' ] );
|
||||
|
||||
// Add htaccess import block.
|
||||
add_action( 'wpseo_import_tab_content', [ $this, 'add_keyword_export_tab_block' ] );
|
||||
add_action( 'wpseo_import_tab_header', [ $this, 'keywords_export_tab_header' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a tab header for the CSV export block.
|
||||
*/
|
||||
public function keywords_export_tab_header() {
|
||||
if ( current_user_can( 'export' ) ) {
|
||||
echo '<a class="nav-tab" id="keywords-export-tab" href="#top#keywords-export">'
|
||||
. esc_html__( 'Export keyphrases', 'wordpress-seo-premium' )
|
||||
. '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the export block for CSV. Makes it able to export redirects to CSV.
|
||||
*/
|
||||
public function add_keyword_export_tab_block() {
|
||||
// Display the forms.
|
||||
if ( current_user_can( 'export' ) ) {
|
||||
$yform = Yoast_Form::get_instance();
|
||||
require WPSEO_PREMIUM_PATH . 'classes/views/export-keywords.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into the request and returns a CSV file if we're on the right page with the right method and the right capabilities.
|
||||
*/
|
||||
public function keywords_csv_export() {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $this->is_valid_csv_export_request() || ! current_user_can( 'export' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have a valid nonce.
|
||||
check_admin_referer( 'wpseo-export' );
|
||||
|
||||
$this->wpdb = $wpdb;
|
||||
|
||||
// Clean any content that has been already outputted, for example by other plugins or faulty PHP files.
|
||||
if ( ob_get_contents() ) {
|
||||
ob_clean();
|
||||
}
|
||||
|
||||
// Make sure we don't time out during the collection of items.
|
||||
set_time_limit( 0 );
|
||||
|
||||
// Set CSV headers and content.
|
||||
$this->set_csv_headers();
|
||||
echo $this->get_csv_contents();
|
||||
|
||||
// And exit so we don't start appending HTML to our CSV file.
|
||||
// NOTE: this makes this entire class untestable as it will exit all tests but WordPress seems to have no elegant way of handling this.
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is a POST request for a CSV export of posts and keywords.
|
||||
*
|
||||
* @return bool True if this is a valid CSV export request.
|
||||
*/
|
||||
protected function is_valid_csv_export_request() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification -- Reason: Nonce is checked in export.
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are strictly comparing only or ignoring the value.
|
||||
return ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) && wp_unslash( $_GET['page'] ) === 'wpseo_tools' )
|
||||
&& ( isset( $_GET['tool'] ) && is_string( $_GET['tool'] ) && wp_unslash( $_GET['tool'] ) === 'import-export' )
|
||||
&& ( isset( $_POST['export-posts'] ) && ! empty( $_POST['export-posts'] ) );
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the headers to trigger a CSV download in the browser.
|
||||
*/
|
||||
protected function set_csv_headers() {
|
||||
header( 'Content-type: text/csv' );
|
||||
header( 'Content-Disposition: attachment; filename=' . gmdate( 'Y-m-d' ) . '-yoast-seo-keywords.csv' );
|
||||
header( 'Pragma: no-cache' );
|
||||
header( 'Expires: 0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the CSV to be exported.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function get_csv_contents() {
|
||||
$columns = [ 'keywords' ];
|
||||
|
||||
$post_wpseo = filter_input( INPUT_POST, 'wpseo', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
|
||||
|
||||
if ( is_array( $post_wpseo ) ) {
|
||||
$columns = array_merge( $columns, $this->get_export_columns( $post_wpseo ) );
|
||||
}
|
||||
|
||||
$builder = new WPSEO_Export_Keywords_CSV( $columns );
|
||||
$builder->print_headers();
|
||||
$this->prepare_export( $builder, $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the requested columns.
|
||||
*
|
||||
* @param array $post_object An associative array with the post data.
|
||||
*
|
||||
* @return array List of export columns.
|
||||
*/
|
||||
protected function get_export_columns( array $post_object ) {
|
||||
$exportable_columns = [
|
||||
'export-keywords-score' => 'keywords_score',
|
||||
'export-url' => 'url',
|
||||
'export-title' => 'title',
|
||||
'export-seo-title' => 'seo_title',
|
||||
'export-meta-description' => 'meta_description',
|
||||
'export-readability-score' => 'readability_score',
|
||||
];
|
||||
|
||||
// Need to call array_values to ensure that we get a numerical key back.
|
||||
return array_values( array_intersect_key( $exportable_columns, $post_object ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds post and term items to the CSV builder.
|
||||
*
|
||||
* @param WPSEO_Export_Keywords_CSV $builder The builder to use.
|
||||
* @param array $columns The columns that need to be exported.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepare_export( WPSEO_Export_Keywords_CSV $builder, array $columns ) {
|
||||
$this->feed_to_builder(
|
||||
$builder,
|
||||
new WPSEO_Export_Keywords_Post_Query( $this->wpdb, $columns, 1000 ),
|
||||
new WPSEO_Export_Keywords_Post_Presenter( $columns )
|
||||
);
|
||||
|
||||
$this->feed_to_builder(
|
||||
$builder,
|
||||
new WPSEO_Export_Keywords_Term_Query( $this->wpdb, $columns, 1000 ),
|
||||
new WPSEO_Export_Keywords_Term_Presenter( $columns )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the items and feeds them to the builder.
|
||||
*
|
||||
* @param WPSEO_Export_Keywords_CSV $builder Builder to feed the items to.
|
||||
* @param WPSEO_Export_Keywords_Query $export_query Query to use to get the items.
|
||||
* @param WPSEO_Export_Keywords_Presenter $presenter Presenter to present the items in the builder format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function feed_to_builder( WPSEO_Export_Keywords_CSV $builder, WPSEO_Export_Keywords_Query $export_query, WPSEO_Export_Keywords_Presenter $presenter ) {
|
||||
$page_size = $export_query->get_page_size();
|
||||
|
||||
$page = 1;
|
||||
do {
|
||||
$results = $export_query->get_data( $page );
|
||||
|
||||
if ( ! is_array( $results ) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$result_count = count( $results );
|
||||
|
||||
// Present the result.
|
||||
$presented = array_map( [ $presenter, 'present' ], $results );
|
||||
|
||||
// Feed presented item to the builder.
|
||||
array_walk( $presented, [ $builder, 'print_row' ] );
|
||||
|
||||
++$page;
|
||||
|
||||
// If we have the number of items per page, there will be more items ahead.
|
||||
} while ( $result_count === $page_size );
|
||||
}
|
||||
}
|
||||
393
wp/plugins/wordpress-seo-premium/classes/premium-metabox.php
Normal file
393
wp/plugins/wordpress-seo-premium/classes/premium-metabox.php
Normal file
@@ -0,0 +1,393 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium|Classes
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Premium\Integrations\Admin\Prominent_Words\Indexing_Integration;
|
||||
|
||||
/**
|
||||
* The metabox for premium.
|
||||
*/
|
||||
class WPSEO_Premium_Metabox implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Instance of the WPSEO_Metabox_Link_Suggestions class.
|
||||
*
|
||||
* @var WPSEO_Metabox_Link_Suggestions
|
||||
*/
|
||||
protected $link_suggestions;
|
||||
|
||||
/**
|
||||
* The prominent words helper.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper instance.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Creates the meta box class.
|
||||
*
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
* @param WPSEO_Metabox_Link_Suggestions|null $link_suggestions The link suggestions meta box.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Helper $prominent_words_helper,
|
||||
Current_Page_Helper $current_page_helper,
|
||||
WPSEO_Metabox_Link_Suggestions $link_suggestions = null
|
||||
) {
|
||||
if ( $link_suggestions === null ) {
|
||||
$link_suggestions = new WPSEO_Metabox_Link_Suggestions();
|
||||
}
|
||||
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->link_suggestions = $link_suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers relevant hooks to WordPress.
|
||||
*
|
||||
* @codeCoverageIgnore Method uses dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
add_action( 'admin_init', [ $this, 'initialize' ] );
|
||||
|
||||
$this->link_suggestions->register_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the content endpoints are available.
|
||||
*
|
||||
* @return bool Returns true if the content endpoints are available
|
||||
*/
|
||||
public static function are_content_endpoints_available() {
|
||||
if ( function_exists( 'rest_get_server' ) ) {
|
||||
$namespaces = rest_get_server()->get_namespaces();
|
||||
|
||||
return in_array( 'wp/v2', $namespaces, true );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the metabox by loading the register_hooks for the dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
if ( ! $this->load_metabox( $this->get_current_page() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $this->get_metabox_integrations() as $integration ) {
|
||||
$integration->register_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues assets when relevant.
|
||||
*
|
||||
* @codeCoverageIgnore Method uses dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
if ( ! $this->load_metabox( $this->get_current_page() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox' );
|
||||
wp_enqueue_style( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox' );
|
||||
|
||||
$premium_localization = new WPSEO_Premium_Asset_JS_L10n();
|
||||
$premium_localization->localize_script( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox' );
|
||||
|
||||
$this->send_data_to_assets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data to assets by using wp_localize_script.
|
||||
* Also localizes the Table of Contents heading title to the wp-seo-premium-blocks asset.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function send_data_to_assets() {
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$content_analysis = new WPSEO_Metabox_Analysis_Readability();
|
||||
$assets_manager = new WPSEO_Admin_Asset_Manager();
|
||||
|
||||
/**
|
||||
* Filters the parameter to disable Table of Content block.
|
||||
*
|
||||
* Note: Used to prevent auto-generation of HTML anchors for headings when TOC block is registered.
|
||||
*
|
||||
* @since 21.5
|
||||
*
|
||||
* @param bool $disable_table_of_content The value of the `autoload` parameter. Default: false.
|
||||
*
|
||||
* @return bool The filtered value of the `disable_table_of_content` parameter.
|
||||
*/
|
||||
$disable_table_of_content = apply_filters( 'Yoast\WP\SEO\disable_table_of_content_block', false );
|
||||
|
||||
$data = [
|
||||
'restApi' => $this->get_rest_api_config(),
|
||||
'seoAnalysisEnabled' => $analysis_seo->is_enabled(),
|
||||
'contentAnalysisEnabled' => $content_analysis->is_enabled(),
|
||||
'licensedURL' => WPSEO_Utils::get_home_url(),
|
||||
'settingsPageUrl' => admin_url( 'admin.php?page=wpseo_page_settings#/site-features#card-wpseo-enable_link_suggestions' ),
|
||||
'integrationsTabURL' => admin_url( 'admin.php?page=wpseo_integrations' ),
|
||||
'commonsScriptUrl' => \plugins_url(
|
||||
'assets/js/dist/commons-premium-' . $assets_manager->flatten_version( WPSEO_PREMIUM_VERSION ) . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
WPSEO_PREMIUM_FILE
|
||||
),
|
||||
'premiumAssessmentsScriptUrl' => \plugins_url(
|
||||
'assets/js/dist/register-premium-assessments-' . $assets_manager->flatten_version( WPSEO_PREMIUM_VERSION ) . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
WPSEO_PREMIUM_FILE
|
||||
),
|
||||
'pluginUrl' => \plugins_url( '', \WPSEO_PREMIUM_FILE ),
|
||||
];
|
||||
|
||||
if ( \defined( 'YOAST_SEO_TEXT_FORMALITY' ) && YOAST_SEO_TEXT_FORMALITY === true ) {
|
||||
$data['textFormalityScriptUrl'] = \plugins_url(
|
||||
'assets/js/dist/register-text-formality-' . $assets_manager->flatten_version( WPSEO_PREMIUM_VERSION ) . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
WPSEO_PREMIUM_FILE
|
||||
);
|
||||
}
|
||||
|
||||
if ( WPSEO_Metabox::is_post_edit( $this->get_current_page() ) ) {
|
||||
$data = array_merge( $data, $this->get_post_metabox_config() );
|
||||
}
|
||||
elseif ( WPSEO_Taxonomy::is_term_edit( $this->get_current_page() ) ) {
|
||||
$data = array_merge( $data, $this->get_term_metabox_config() );
|
||||
}
|
||||
|
||||
if ( current_user_can( 'edit_others_posts' ) ) {
|
||||
$data['workoutsUrl'] = admin_url( 'admin.php?page=wpseo_workouts' );
|
||||
}
|
||||
|
||||
// Use an extra level in the array to preserve booleans. WordPress sanitizes scalar values in the first level of the array.
|
||||
wp_localize_script( 'yoast-seo-premium-metabox', 'wpseoPremiumMetaboxData', [ 'data' => $data ] );
|
||||
|
||||
// Localize the title of the Table of Contents block: the translation needs to be based on the site language instead of the user language.
|
||||
wp_localize_script(
|
||||
'wp-seo-premium-blocks',
|
||||
'wpseoTOCData',
|
||||
[
|
||||
'data' => [
|
||||
'TOCTitle' => \__( 'Table of contents', 'wordpress-seo-premium' ),
|
||||
'disableTableOfContents' => $disable_table_of_content,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the metabox config for a post.
|
||||
*
|
||||
* @return array The config.
|
||||
*/
|
||||
protected function get_post_metabox_config() {
|
||||
$link_suggestions_enabled = WPSEO_Options::get( 'enable_link_suggestions', false );
|
||||
|
||||
$post = $this->get_post();
|
||||
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
$is_prominent_words_available = $prominent_words_support->is_post_type_supported( $post->post_type );
|
||||
|
||||
$site_locale = \get_locale();
|
||||
$language = WPSEO_Language_Utils::get_language( $site_locale );
|
||||
|
||||
|
||||
return [
|
||||
'currentObjectId' => $this->get_post_ID(),
|
||||
'currentObjectType' => 'post',
|
||||
'linkSuggestionsEnabled' => ( $link_suggestions_enabled ) ? 'enabled' : 'disabled',
|
||||
'linkSuggestionsAvailable' => $is_prominent_words_available,
|
||||
'linkSuggestionsUnindexed' => ! $this->is_prominent_words_indexing_completed() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
|
||||
'perIndexableLimit' => $this->per_indexable_limit( $language ),
|
||||
'isProminentWordsAvailable' => $is_prominent_words_available,
|
||||
'isTitleAssessmentAvailable' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the metabox config for a term.
|
||||
*
|
||||
* @return array The config.
|
||||
*/
|
||||
protected function get_term_metabox_config() {
|
||||
$term = null;
|
||||
if ( isset( $GLOBALS['tag_ID'], $GLOBALS['taxonomy'] ) ) {
|
||||
$term = get_term( $GLOBALS['tag_ID'], $GLOBALS['taxonomy'] );
|
||||
}
|
||||
|
||||
if ( $term === null || is_wp_error( $term ) ) {
|
||||
return [
|
||||
'insightsEnabled' => 'disabled',
|
||||
'linkSuggestionsEnabled' => 'disabled',
|
||||
'linkSuggestionsAvailable' => false,
|
||||
'linkSuggestionsUnindexed' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$link_suggestions_enabled = WPSEO_Options::get( 'enable_link_suggestions', false );
|
||||
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
$is_prominent_words_available = $prominent_words_support->is_taxonomy_supported( $term->taxonomy );
|
||||
|
||||
$site_locale = \get_locale();
|
||||
$language = WPSEO_Language_Utils::get_language( $site_locale );
|
||||
|
||||
return [
|
||||
'currentObjectId' => $term->term_id,
|
||||
'currentObjectType' => 'term',
|
||||
'linkSuggestionsEnabled' => ( $link_suggestions_enabled ) ? 'enabled' : 'disabled',
|
||||
'linkSuggestionsAvailable' => $is_prominent_words_available,
|
||||
'linkSuggestionsUnindexed' => ! $this->is_prominent_words_indexing_completed() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
|
||||
'perIndexableLimit' => $this->per_indexable_limit( $language ),
|
||||
'isProminentWordsAvailable' => $is_prominent_words_available,
|
||||
'isTitleAssessmentAvailable' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the REST API configuration.
|
||||
*
|
||||
* @return array The configuration.
|
||||
*/
|
||||
protected function get_rest_api_config() {
|
||||
return [
|
||||
'available' => WPSEO_Utils::is_api_available(),
|
||||
'contentEndpointsAvailable' => self::are_content_endpoints_available(),
|
||||
'root' => esc_url_raw( rest_url() ),
|
||||
'nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post for the current admin page.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return WP_Post The post for the current admin page.
|
||||
*/
|
||||
protected function get_post() {
|
||||
return get_post( $this->get_post_ID() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post ID from the globals.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return int The post ID.
|
||||
*/
|
||||
protected function get_post_ID() {
|
||||
if ( ! isset( $GLOBALS['post_ID'] ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $GLOBALS['post_ID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the metabox specific integrations.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return WPSEO_WordPress_Integration[] The metabox integrations.
|
||||
*/
|
||||
protected function get_metabox_integrations() {
|
||||
return [
|
||||
'social-previews' => new WPSEO_Social_Previews(),
|
||||
|
||||
// Add custom fields plugin to post and page edit pages.
|
||||
'premium-custom-fields' => new WPSEO_Custom_Fields_Plugin(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the metabox related scripts should be loaded.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $current_page The page we are on.
|
||||
*
|
||||
* @return bool True when it should be loaded.
|
||||
*/
|
||||
protected function load_metabox( $current_page ) {
|
||||
// When the current page is a term related one.
|
||||
if ( WPSEO_Taxonomy::is_term_edit( $current_page ) || WPSEO_Taxonomy::is_term_overview( $current_page ) ) {
|
||||
return WPSEO_Options::get( 'display-metabox-tax-' . $this->current_page_helper->get_current_taxonomy() );
|
||||
}
|
||||
|
||||
// When the current page isn't a post related one.
|
||||
if ( WPSEO_Metabox::is_post_edit( $current_page ) || WPSEO_Metabox::is_post_overview( $current_page ) ) {
|
||||
return WPSEO_Post_Type::has_metabox_enabled( $this->current_page_helper->get_current_post_type() );
|
||||
}
|
||||
|
||||
// Make sure ajax integrations are loaded.
|
||||
return wp_doing_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the pagenow variable.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The value of pagenow.
|
||||
*/
|
||||
private function get_current_page() {
|
||||
global $pagenow;
|
||||
|
||||
return $pagenow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not we need to index more posts for correct link suggestion functionality.
|
||||
*
|
||||
* @return bool Whether or not we need to index more posts.
|
||||
*/
|
||||
protected function is_prominent_words_indexing_completed() {
|
||||
$is_indexing_completed = $this->prominent_words_helper->is_indexing_completed();
|
||||
if ( $is_indexing_completed === null ) {
|
||||
$indexation_integration = YoastSEOPremium()->classes->get( Indexing_Integration::class );
|
||||
$is_indexing_completed = $indexation_integration->get_unindexed_count( 0 ) === 0;
|
||||
|
||||
$this->prominent_words_helper->set_indexing_completed( $is_indexing_completed );
|
||||
}
|
||||
|
||||
return $is_indexing_completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of prominent words to store for content written in the given language.
|
||||
*
|
||||
* @param string $language The current language.
|
||||
*
|
||||
* @return int The number of words to store.
|
||||
*/
|
||||
protected function per_indexable_limit( $language ) {
|
||||
if ( YoastSEO()->helpers->language->has_function_word_support( $language ) ) {
|
||||
return Indexing_Integration::PER_INDEXABLE_LIMIT;
|
||||
}
|
||||
|
||||
return Indexing_Integration::PER_INDEXABLE_LIMIT_NO_FUNCTION_WORD_SUPPORT;
|
||||
}
|
||||
}
|
||||
100
wp/plugins/wordpress-seo-premium/classes/premium-option.php
Normal file
100
wp/plugins/wordpress-seo-premium/classes/premium-option.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the premium option.
|
||||
*/
|
||||
class WPSEO_Premium_Option extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo_premium';
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* {@internal Shouldn't be requested directly, use $this->get_defaults();}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [
|
||||
// Form fields.
|
||||
'prominent_words_indexing_completed' => null,
|
||||
'workouts' => [ 'cornerstone' => [ 'finishedSteps' => [] ] ],
|
||||
'should_redirect_after_install' => false,
|
||||
'activation_redirect_timestamp' => 0,
|
||||
'dismiss_update_premium_notification' => '0',
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the option to the WPSEO Options framework.
|
||||
*/
|
||||
public static function register_option() {
|
||||
WPSEO_Options::register_option( static::get_instance() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return static Returns instance of itself.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( static::$instance instanceof static ) ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* All concrete classes must contain a validate_option() method which validates all
|
||||
* values within the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array The clean option value.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
foreach ( $clean as $key => $value ) {
|
||||
switch ( $key ) {
|
||||
case 'prominent_words_indexing_completed':
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== null ) {
|
||||
$clean[ $key ] = WPSEO_Utils::validate_bool( $dirty[ $key ] );
|
||||
}
|
||||
|
||||
break;
|
||||
case 'workouts':
|
||||
if ( isset( $dirty[ $key ] ) && is_array( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
case 'should_redirect_after_install':
|
||||
if ( isset( $dirty[ $key ] ) && is_bool( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
case 'activation_redirect_timestamp':
|
||||
if ( isset( $dirty[ $key ] ) && is_int( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
case 'dismiss_update_premium_notification':
|
||||
if ( isset( $dirty[ $key ] ) && is_string( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the functionality for the orphaned content support.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Content_Support {
|
||||
|
||||
/**
|
||||
* Returns an array with the supported post types.
|
||||
*
|
||||
* @return array The supported post types.
|
||||
*/
|
||||
public function get_supported_post_types() {
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\orphaned_post_types' - Allows changes for the accessible post types.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api array The accessible post types.
|
||||
*/
|
||||
$orphaned_post_types = apply_filters( 'Yoast\WP\SEO\orphaned_post_types', WPSEO_Post_Type::get_accessible_post_types() );
|
||||
|
||||
if ( ! is_array( $orphaned_post_types ) || empty( $orphaned_post_types ) ) {
|
||||
$orphaned_post_types = [];
|
||||
}
|
||||
|
||||
return $orphaned_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the post type is supported.
|
||||
*
|
||||
* @param string $post_type The post type to look up.
|
||||
*
|
||||
* @return bool True when post type is supported.
|
||||
*/
|
||||
public function is_post_type_supported( $post_type ) {
|
||||
return in_array( $post_type, $this->get_supported_post_types(), true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action;
|
||||
use Yoast\WP\SEO\Config\Migration_Status;
|
||||
|
||||
/**
|
||||
* Represents some util helpers for the orphaned posts.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Content_Utils {
|
||||
|
||||
/**
|
||||
* Checks if the orphaned content feature is enabled.
|
||||
*
|
||||
* @return bool True when the text link counter is enabled.
|
||||
*/
|
||||
public static function is_feature_enabled() {
|
||||
if ( ! YoastSEO()->classes->get( Migration_Status::class )->is_version( 'free', WPSEO_VERSION ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WPSEO_Options::get( 'enable_text_link_counter', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are unprocessed objects.
|
||||
*
|
||||
* @return bool True when there are unprocessed objects.
|
||||
*/
|
||||
public static function has_unprocessed_content() {
|
||||
static $has_unprocessed_posts;
|
||||
|
||||
if ( $has_unprocessed_posts === null ) {
|
||||
$post_link_action = YoastSEO()->classes->get( Post_Link_Indexing_Action::class );
|
||||
$has_unprocessed_posts = $post_link_action->get_total_unindexed();
|
||||
}
|
||||
|
||||
return $has_unprocessed_posts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Config\Migration_Status;
|
||||
|
||||
/**
|
||||
* Registers the filter for filtering posts by orphaned content.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Post_Filter extends WPSEO_Abstract_Post_Filter {
|
||||
|
||||
/**
|
||||
* Returns the query value this filter uses.
|
||||
*
|
||||
* @return string The query value this filter uses.
|
||||
*/
|
||||
public function get_query_val() {
|
||||
return 'orphaned';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks when the link feature is enabled.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( ! YoastSEO()->classes->get( Migration_Status::class )->is_version( 'free', WPSEO_VERSION ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WPSEO_Premium_Orphaned_Content_Utils::is_feature_enabled() ) {
|
||||
parent::register_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text explaining this filter.
|
||||
*
|
||||
* @return string|null The explanation or null if the current post stype is unknown.
|
||||
*/
|
||||
protected function get_explanation() {
|
||||
$post_type_object = get_post_type_object( $this->get_current_post_type() );
|
||||
|
||||
if ( $post_type_object === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$unprocessed = WPSEO_Premium_Orphaned_Content_Utils::has_unprocessed_content();
|
||||
$can_recalculate = WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' );
|
||||
|
||||
$learn_more = sprintf(
|
||||
/* translators: %1$s expands to the link to an article to read more about orphaned content, %2$s expands to </a> */
|
||||
__( '%1$sLearn more about orphaned content%2$s.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/1ja' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
|
||||
if ( $unprocessed && ! $can_recalculate ) {
|
||||
return sprintf(
|
||||
/* translators: %1$s: plural form of the current post type, %2$s: a Learn more about link */
|
||||
__( 'Ask your SEO Manager or Site Administrator to count links in all texts, so we can identify orphaned %1$s. %2$s', 'wordpress-seo-premium' ),
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
$learn_more
|
||||
);
|
||||
}
|
||||
|
||||
if ( $unprocessed ) {
|
||||
return sprintf(
|
||||
/* translators: %1$s expands to link to the recalculation option, %2$s: anchor closing, %3$s: plural form of the current post type, %4$s: a Learn more about link */
|
||||
__( '%1$sClick here%2$s to index your links, so we can identify orphaned %3$s. %4$s', 'wordpress-seo-premium' ),
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_tools&reIndexLinks=1' ) ) . '">',
|
||||
'</a>',
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
$learn_more
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: %1$s: plural form of the current post type, %2$s: a Learn more about link */
|
||||
__( '\'Orphaned content\' refers to %1$s that have no inbound links, consider adding links towards these %1$s. %2$s', 'wordpress-seo-premium' ),
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
$learn_more
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the query based on the seo_filter variable in $_GET.
|
||||
*
|
||||
* @param string $where Query variables.
|
||||
*
|
||||
* @return string The modified query.
|
||||
*/
|
||||
public function filter_posts( $where ) {
|
||||
if ( $this->is_filter_active() ) {
|
||||
$where .= $this->get_where_filter();
|
||||
$where .= $this->filter_published_posts();
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the where clause to use.
|
||||
*
|
||||
* @return string The where clause.
|
||||
*/
|
||||
protected function get_where_filter() {
|
||||
global $wpdb;
|
||||
|
||||
if ( WPSEO_Premium_Orphaned_Content_Utils::has_unprocessed_content() ) {
|
||||
// Hide all posts, because we cannot tell anything for certain.
|
||||
return 'AND 1 = 0';
|
||||
}
|
||||
|
||||
$subquery = WPSEO_Premium_Orphaned_Post_Query::get_orphaned_content_query();
|
||||
return ' AND ' . $wpdb->posts . '.ID IN ( ' . $subquery . ' ) ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a published posts filter so we don't show unpublished posts in the orphaned pages results.
|
||||
*
|
||||
* @return string A published posts filter.
|
||||
*/
|
||||
protected function filter_published_posts() {
|
||||
global $wpdb;
|
||||
|
||||
return " AND {$wpdb->posts}.post_status = 'publish' AND {$wpdb->posts}.post_password = ''";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for this filter.
|
||||
*
|
||||
* @return string The label for this filter.
|
||||
*/
|
||||
protected function get_label() {
|
||||
static $label;
|
||||
|
||||
if ( $label === null ) {
|
||||
$label = __( 'Orphaned content', 'wordpress-seo-premium' );
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total amount of articles that are orphaned content.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_post_total() {
|
||||
global $wpdb;
|
||||
|
||||
static $count;
|
||||
|
||||
if ( WPSEO_Premium_Orphaned_Content_Utils::has_unprocessed_content() ) {
|
||||
return '?';
|
||||
}
|
||||
|
||||
if ( $count === null ) {
|
||||
$subquery = WPSEO_Premium_Orphaned_Post_Query::get_orphaned_content_query();
|
||||
$count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(ID)
|
||||
FROM `{$wpdb->posts}`
|
||||
WHERE ID IN ( $subquery )
|
||||
AND post_status = 'publish'
|
||||
AND post_password = ''
|
||||
AND post_type = %s",
|
||||
$this->get_current_post_type()
|
||||
)
|
||||
);
|
||||
|
||||
$count = (int) $count;
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post types to which this filter should be added.
|
||||
*
|
||||
* @return array The post types to which this filter should be added.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
$orphaned_content_support = new WPSEO_Premium_Orphaned_Content_Support();
|
||||
|
||||
return $orphaned_content_support->get_supported_post_types();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the notifier when there is orphaned content present for one of the post types.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Post_Notifier implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Instance of the Yoast_Notification_Center class.
|
||||
*
|
||||
* @var Yoast_Notification_Center
|
||||
*/
|
||||
protected $notification_center;
|
||||
|
||||
/**
|
||||
* WPSEO_Premium_Orphaned_Content_Notifier constructor.
|
||||
*
|
||||
* @param array $post_types Unused. The supported post types.
|
||||
* @param Yoast_Notification_Center $notification_center The notification center object.
|
||||
*/
|
||||
public function __construct( array $post_types, Yoast_Notification_Center $notification_center ) {
|
||||
$this->notification_center = $notification_center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Joost de Valk, April 6th 2019.
|
||||
// Disabling this until we've found a better way to display this data that doesn't become annoying when you have a lot of post types.
|
||||
return;
|
||||
|
||||
if ( filter_input( INPUT_GET, 'page' ) === 'wpseo_dashboard' ) {
|
||||
add_action( 'admin_init', [ $this, 'notify' ] );
|
||||
}
|
||||
|
||||
if ( ! wp_next_scheduled( 'wpseo-premium-orphaned-content' ) ) {
|
||||
wp_schedule_event( time(), 'daily', 'wpseo-premium-orphaned-content' );
|
||||
}
|
||||
|
||||
add_action( 'wpseo-premium-orphaned-content', [ $this, 'notify' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the notifications for all post types.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notify() {
|
||||
// Joost de Valk, April 6th 2019.
|
||||
// Disabling this until we've found a better way to display this data that doesn't become annoying when you have a lot of post types.
|
||||
return;
|
||||
|
||||
$post_types = $this->get_post_types();
|
||||
$post_types = $this->format_post_types( $post_types );
|
||||
|
||||
// Walks over the posts types and handle the notification.
|
||||
array_walk( $post_types, [ $this, 'notify_for_post_type' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post types to which this filter should be added.
|
||||
*
|
||||
* @return array The post types to which this filter should be added.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
$orphaned_content_support = new WPSEO_Premium_Orphaned_Content_Support();
|
||||
|
||||
return $orphaned_content_support->get_supported_post_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the array with post types as an array with post type objects.
|
||||
*
|
||||
* It also filters out the null values, because these are unknown post types.
|
||||
*
|
||||
* @param array $post_types Array with post type names.
|
||||
*
|
||||
* @return WP_Post_Type[] The formatted posttypes.
|
||||
*/
|
||||
protected function format_post_types( array $post_types ) {
|
||||
// First convert the array to post type objects.
|
||||
$post_type_objects = array_map( 'get_post_type_object', $post_types );
|
||||
|
||||
// The unknown post types will have a value of null, filter these.
|
||||
return array_filter( $post_type_objects );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the notification for the given post type.
|
||||
*
|
||||
* @param WP_Post_Type $post_type The post type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function notify_for_post_type( WP_Post_Type $post_type ) {
|
||||
$notification_id = sprintf( 'wpseo-premium-orphaned-content-%1$s', $post_type->name );
|
||||
$message = $this->get_notification( $notification_id, $post_type );
|
||||
|
||||
$show_notification = WPSEO_Premium_Orphaned_Content_Utils::is_feature_enabled() && ! WPSEO_Premium_Orphaned_Content_Utils::has_unprocessed_content();
|
||||
if ( $show_notification && $this->requires_notification( $post_type ) ) {
|
||||
Yoast_Notification_Center::get()->add_notification( $message );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Yoast_Notification_Center::get()->remove_notification( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the notification is required for the passed post type.
|
||||
*
|
||||
* @param WP_Post_Type $post_type The post type.
|
||||
*
|
||||
* @return bool True if a notification is required.
|
||||
*/
|
||||
protected function requires_notification( WP_Post_Type $post_type ) {
|
||||
return $this->get_count_by_post_type( $post_type->name ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the notification for the passed post type.
|
||||
*
|
||||
* @param string $notification_id The id for the notification.
|
||||
* @param WP_Post_Type $post_type The post type to generate the message for.
|
||||
*
|
||||
* @return Yoast_Notification The notification.
|
||||
*/
|
||||
protected function get_notification( $notification_id, $post_type ) {
|
||||
$total_orphaned = $this->get_count_by_post_type( $post_type->name );
|
||||
$post_type_value = ( $total_orphaned === 1 ) ? $post_type->labels->singular_name : $post_type->labels->name;
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: %1$s: Link to the filter page, %2$d: amount of orphaned items, %3$s: plural/singular form of post type, %4$s closing tag. */
|
||||
_n(
|
||||
'We\'ve detected %1$s%2$d \'orphaned\' %3$s%4$s (no inbound links). Consider adding links towards this %3$s.',
|
||||
'We\'ve detected %1$s%2$d \'orphaned\' %3$s%4$s (no inbound links). Consider adding links towards these %3$s.',
|
||||
$total_orphaned,
|
||||
'wordpress-seo-premium'
|
||||
),
|
||||
'<a href="' . $this->get_filter_url( $post_type->name ) . '">',
|
||||
$total_orphaned,
|
||||
strtolower( $post_type_value ),
|
||||
'</a>'
|
||||
);
|
||||
|
||||
return new Yoast_Notification(
|
||||
$message,
|
||||
[
|
||||
'type' => Yoast_Notification::WARNING,
|
||||
'id' => $notification_id,
|
||||
'capabilities' => 'wpseo_manage_options',
|
||||
'priority' => 0.8,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of orphaned items for given post type name.
|
||||
*
|
||||
* @param string $post_type_name The name of the post type.
|
||||
*
|
||||
* @return int Total orphaned items.
|
||||
*/
|
||||
protected function get_count_by_post_type( $post_type_name ) {
|
||||
static $post_type_counts;
|
||||
|
||||
if ( ! is_array( $post_type_counts ) ) {
|
||||
$post_type_counts = WPSEO_Premium_Orphaned_Post_Query::get_counts( $this->get_post_types() );
|
||||
}
|
||||
|
||||
if ( array_key_exists( $post_type_name, $post_type_counts ) ) {
|
||||
return (int) $post_type_counts[ $post_type_name ];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to the page with the filtered items for the given post type.
|
||||
*
|
||||
* @param string $post_type_name The name of the post type.
|
||||
*
|
||||
* @return string The URL containing the required filter.
|
||||
*/
|
||||
protected function get_filter_url( $post_type_name ) {
|
||||
$query_args = [
|
||||
'post_type' => $post_type_name,
|
||||
'yoast_filter' => 'orphaned',
|
||||
];
|
||||
|
||||
return add_query_arg( $query_args, admin_url( 'edit.php' ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Represents the orphaned post query methods.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Post_Query {
|
||||
|
||||
/**
|
||||
* Returns the total number of orphaned items for the given post types.
|
||||
*
|
||||
* @param array $post_types The post types to get the counts for.
|
||||
*
|
||||
* @return int[] The counts for all post types.
|
||||
*/
|
||||
public static function get_counts( array $post_types ) {
|
||||
global $wpdb;
|
||||
|
||||
$post_type_counts = array_fill_keys( $post_types, 0 );
|
||||
$subquery = self::get_orphaned_content_query();
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT( ID ) as total_orphaned, post_type
|
||||
FROM {$wpdb->posts}
|
||||
WHERE
|
||||
ID IN ( " . $subquery . " )
|
||||
AND post_status = 'publish'
|
||||
AND post_type IN ( " . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . ' )
|
||||
GROUP BY post_type',
|
||||
$post_types
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $results as $result ) {
|
||||
$post_type_counts[ $result->post_type ] = (int) $result->total_orphaned;
|
||||
}
|
||||
|
||||
return $post_type_counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a query to retrieve the object ids from the records with an incoming link count of 0.
|
||||
*
|
||||
* @return string Query for get all objects with an incoming link count of 0 from the DB.
|
||||
*/
|
||||
public static function get_orphaned_content_query() {
|
||||
static $query;
|
||||
|
||||
if ( $query === null ) {
|
||||
$repository = YoastSEO()->classes->get( Indexable_Repository::class );
|
||||
$query = $repository->query()
|
||||
->select( 'object_id' )
|
||||
->where( 'object_type', 'post' )
|
||||
->where_any_is(
|
||||
[
|
||||
[ 'incoming_link_count' => 0 ],
|
||||
[ 'incoming_link_count' => null ],
|
||||
]
|
||||
);
|
||||
|
||||
$frontpage_id = self::get_frontpage_id();
|
||||
if ( $frontpage_id ) {
|
||||
$query = $query->where_not_equal( 'object_id', $frontpage_id );
|
||||
}
|
||||
|
||||
$query = sprintf( $query->get_sql(), '\'post\'', 0, $frontpage_id );
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the object ids from the records with an incoming link count of 0.
|
||||
*
|
||||
* @return array Array with the object ids.
|
||||
*/
|
||||
public static function get_orphaned_object_ids() {
|
||||
$repository = YoastSEO()->classes->get( Indexable_Repository::class );
|
||||
$results = $repository->query()
|
||||
->select( 'object_id' )
|
||||
->where( 'object_type', 'post' )
|
||||
->where( 'incoming_link_count', 0 )
|
||||
->find_array();
|
||||
|
||||
$object_ids = wp_list_pluck( $results, 'object_id' );
|
||||
$object_ids = self::remove_frontpage_id( $object_ids );
|
||||
|
||||
return $object_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the frontpage id from orphaned id's when the frontpage is a static page.
|
||||
*
|
||||
* @param array $object_ids The orphaned object ids.
|
||||
*
|
||||
* @return array The orphaned object ids, without frontpage id.
|
||||
*/
|
||||
protected static function remove_frontpage_id( $object_ids ) {
|
||||
// When the frontpage is a static page, remove it from the object ids.
|
||||
if ( get_option( 'show_on_front' ) !== 'page' ) {
|
||||
return $object_ids;
|
||||
}
|
||||
|
||||
$frontpage_id = get_option( 'page_on_front' );
|
||||
|
||||
// If the frontpage ID exists in the list, remove it.
|
||||
$object_id_key = array_search( $frontpage_id, $object_ids, true );
|
||||
if ( $object_id_key !== false ) {
|
||||
unset( $object_ids[ $object_id_key ] );
|
||||
}
|
||||
|
||||
return $object_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the frontpage id when set, otherwise null.
|
||||
*
|
||||
* @return int|null The frontpage id when set.
|
||||
*/
|
||||
protected static function get_frontpage_id() {
|
||||
if ( get_option( 'show_on_front' ) !== 'page' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$page_on_front = get_option( 'page_on_front', null );
|
||||
if ( empty( $page_on_front ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $page_on_front;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
// Mark this file as deprecated.
|
||||
_deprecated_file( __FILE__, 'WPSEO Premium 14.5' );
|
||||
|
||||
/**
|
||||
* Handles adding site wide analysis UI to the WordPress admin.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Recalculation implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Base height of the recalculation modal in pixels.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MODAL_DIALOG_HEIGHT_BASE = 220;
|
||||
|
||||
/**
|
||||
* Height of the recalculation progressbar in pixels.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const PROGRESS_BAR_HEIGHT = 32;
|
||||
|
||||
/**
|
||||
* WPSEO_Premium_Prominent_Words_Recalculation constructor.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WPSEO_Premium_Prominent_Words_Support $prominent_words_support Unused. The prominent words support class to determine supported posts types to index.
|
||||
*/
|
||||
public function __construct( WPSEO_Premium_Prominent_Words_Support $prominent_words_support ) {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item on the tools page list.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show_tools_overview_item() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the modal box to be displayed when needed.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modal_box() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues site wide analysis script.
|
||||
*
|
||||
* @deprecated 14.5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO Premium 14.5' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the functionality for the prominent words support.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Support {
|
||||
|
||||
/**
|
||||
* Returns an array with the supported post types.
|
||||
*
|
||||
* @return array The supported post types.
|
||||
*/
|
||||
public function get_supported_post_types() {
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\prominent_words_post_types' - Allows changes for the accessible post types.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api array The accessible post types.
|
||||
*/
|
||||
$prominent_words_post_types = apply_filters(
|
||||
'Yoast\WP\SEO\prominent_words_post_types',
|
||||
WPSEO_Post_Type::get_accessible_post_types()
|
||||
);
|
||||
|
||||
if ( ! is_array( $prominent_words_post_types ) || empty( $prominent_words_post_types ) ) {
|
||||
$prominent_words_post_types = [];
|
||||
}
|
||||
|
||||
$prominent_words_post_types = WPSEO_Post_Type::filter_attachment_post_type( $prominent_words_post_types );
|
||||
$prominent_words_post_types = array_filter( $prominent_words_post_types, [ 'WPSEO_Post_Type', 'has_metabox_enabled' ] );
|
||||
|
||||
/*
|
||||
* The above combination of functions results in array looking like this:
|
||||
* [
|
||||
* `post` => `post`
|
||||
* `page` => `page`
|
||||
* ]
|
||||
*
|
||||
* This can result in problems downstream when trying to array_merge this twice.
|
||||
* array_values prevents this issue by ensuring numeric keys.
|
||||
*/
|
||||
$prominent_words_post_types = array_values( $prominent_words_post_types );
|
||||
|
||||
return $prominent_words_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the post type is supported.
|
||||
*
|
||||
* @param string $post_type The post type to look up.
|
||||
*
|
||||
* @return bool True when post type is supported.
|
||||
*/
|
||||
public function is_post_type_supported( $post_type ) {
|
||||
return in_array( $post_type, $this->get_supported_post_types(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of taxonomies that are public, viewable and have the metabox enabled.
|
||||
*
|
||||
* @return array The supported taxonomies.
|
||||
*/
|
||||
public function get_supported_taxonomies() {
|
||||
$taxonomies = get_taxonomies( [ 'public' => true ] );
|
||||
$taxonomies = array_filter( $taxonomies, 'is_taxonomy_viewable' );
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\prominent_words_taxonomies' - Allows to filter from which taxonomies terms are eligible for generating prominent words.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 14.7.0
|
||||
*
|
||||
* @api array The accessible taxonomies.
|
||||
*/
|
||||
$prominent_words_taxonomies = apply_filters(
|
||||
'Yoast\WP\SEO\prominent_words_taxonomies',
|
||||
$taxonomies
|
||||
);
|
||||
|
||||
if ( ! is_array( $prominent_words_taxonomies ) || empty( $prominent_words_taxonomies ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$prominent_words_taxonomies = array_filter(
|
||||
$prominent_words_taxonomies,
|
||||
static function( $taxonomy ) {
|
||||
return (bool) WPSEO_Options::get( 'display-metabox-tax-' . $taxonomy, true );
|
||||
}
|
||||
);
|
||||
|
||||
return array_values( $prominent_words_taxonomies );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the taxonomy is supported.
|
||||
*
|
||||
* @param string $taxonomy The taxonomy to look up.
|
||||
*
|
||||
* @return bool True when taxonomy is supported.
|
||||
*/
|
||||
public function is_taxonomy_supported( $taxonomy ) {
|
||||
return in_array( $taxonomy, $this->get_supported_taxonomies(), true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the unindexed posts.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Unindexed_Post_Query {
|
||||
|
||||
/**
|
||||
* List containing unindexed posts totals per post type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $totals = [];
|
||||
|
||||
/**
|
||||
* Returns the total amount of posts.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*
|
||||
* @param int $limit The offset for the query.
|
||||
*
|
||||
* @return bool True when the limit has been exceeded.
|
||||
*/
|
||||
public function exceeds_limit( $limit ) {
|
||||
$unindexed_post_ids = $this->get_unindexed_post_ids( $this->get_post_types(), ( $limit + 1 ) );
|
||||
return count( $unindexed_post_ids ) > $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total unindexed posts for given post type.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*
|
||||
* @param string $post_type The posttype to fetch.
|
||||
*
|
||||
* @return int The total amount of unindexed posts.
|
||||
*/
|
||||
public function get_total( $post_type ) {
|
||||
if ( ! array_key_exists( $post_type, $this->totals ) ) {
|
||||
$totals = $this->get_totals( $this->get_post_types() );
|
||||
|
||||
foreach ( $totals as $total_post_type => $total ) {
|
||||
$this->totals[ $total_post_type ] = $total;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $post_type, $this->totals ) ) {
|
||||
$this->totals[ $post_type ] = 0;
|
||||
}
|
||||
|
||||
return $this->totals[ $post_type ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the totals for each posttype by counting them.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*
|
||||
* @param array $post_types The posttype to limit the resultset for.
|
||||
*
|
||||
* @return array Array with the totals for the requested posttypes.
|
||||
*/
|
||||
public function get_totals( $post_types ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( $post_types === [] ) {
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
$replacements = [
|
||||
WPSEO_Premium_Prominent_Words_Versioning::POST_META_NAME,
|
||||
WPSEO_Premium_Prominent_Words_Versioning::get_version_number(),
|
||||
];
|
||||
$replacements = array_merge( $replacements, $post_types );
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'
|
||||
SELECT COUNT( ID ) as total, post_type
|
||||
FROM ' . $wpdb->posts . '
|
||||
WHERE ID NOT IN( SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = %s AND meta_value = %s )
|
||||
AND post_status IN( "future", "draft", "pending", "private", "publish" )
|
||||
AND post_type IN( ' . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . ' )
|
||||
GROUP BY post_type
|
||||
',
|
||||
$replacements
|
||||
)
|
||||
);
|
||||
|
||||
$totals = [];
|
||||
|
||||
foreach ( $results as $result ) {
|
||||
$totals[ $this->determine_rest_endpoint_for_post_type( $result->post_type ) ] = (int) $result->total;
|
||||
}
|
||||
|
||||
return $totals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the REST endpoint for the given post type.
|
||||
*
|
||||
* @param string $post_type The post type to determine the endpoint for.
|
||||
*
|
||||
* @return string The endpoint. Returns empty string if post type doesn't exist.
|
||||
*/
|
||||
protected function determine_rest_endpoint_for_post_type( $post_type ) {
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
|
||||
if ( is_null( $post_type_object ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( isset( $post_type_object->rest_base ) && ! empty( $post_type_object->rest_base ) ) {
|
||||
return $post_type_object->rest_base;
|
||||
}
|
||||
|
||||
return $post_type_object->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array with supported posttypes.
|
||||
*
|
||||
* @return array The supported posttypes.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
|
||||
return array_filter( $prominent_words_support->get_supported_post_types(), [ 'WPSEO_Post_Type', 'is_rest_enabled' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Post IDs of un-indexed objects.
|
||||
*
|
||||
* @param array|string $post_types The post type(s) to fetch.
|
||||
* @param int $limit Limit the number of results.
|
||||
*
|
||||
* @return int[] Post IDs found which are un-indexed.
|
||||
*/
|
||||
public function get_unindexed_post_ids( $post_types, $limit ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( is_string( $post_types ) ) {
|
||||
$post_types = (array) $post_types;
|
||||
}
|
||||
|
||||
if ( $post_types === [] ) {
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
$replacements = [
|
||||
WPSEO_Premium_Prominent_Words_Versioning::POST_META_NAME,
|
||||
WPSEO_Premium_Prominent_Words_Versioning::get_version_number(),
|
||||
];
|
||||
$replacements = array_merge( $replacements, $post_types );
|
||||
$replacements[] = $limit;
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'
|
||||
SELECT ID
|
||||
FROM ' . $wpdb->posts . '
|
||||
WHERE ID NOT IN( SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = %s AND meta_value = %s )
|
||||
AND post_status IN( "future", "draft", "pending", "private", "publish" )
|
||||
AND post_type IN( ' . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . ' )
|
||||
LIMIT %d',
|
||||
$replacements
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
// Make sure we return a list of IDs.
|
||||
$results = wp_list_pluck( $results, 'ID' );
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array with supported post statuses.
|
||||
*
|
||||
* @return string[] The supported post statuses.
|
||||
*/
|
||||
public function get_supported_post_statuses() {
|
||||
return [ 'future', 'draft', 'pending', 'private', 'publish' ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Keeps track of the prominent words version.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Versioning {
|
||||
|
||||
// Needs to be manually updated in case of a major change.
|
||||
const VERSION_NUMBER = 2;
|
||||
|
||||
const POST_META_NAME = '_yst_prominent_words_version';
|
||||
|
||||
/**
|
||||
* Gets the version number.
|
||||
*
|
||||
* @return int The version number that was set in WPSEO_Premium_Prominent_Words_Versioning.
|
||||
*/
|
||||
public static function get_version_number() {
|
||||
return self::VERSION_NUMBER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the meta key for the prominent words version. It was a public meta field and it has to be private.
|
||||
*/
|
||||
public static function upgrade_4_7() {
|
||||
global $wpdb;
|
||||
|
||||
// The meta key has to be private, so prefix it.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'UPDATE ' . $wpdb->postmeta . ' SET meta_key = %s WHERE meta_key = "yst_prominent_words_version"',
|
||||
self::POST_META_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the meta key for the prominent words version for the unsupported languages that might have this value
|
||||
* set.
|
||||
*/
|
||||
public static function upgrade_4_8() {
|
||||
$supported_languages = [ 'en', 'de', 'nl', 'es', 'fr', 'it', 'pt', 'ru', 'pl', 'sv', 'id' ];
|
||||
|
||||
if ( in_array( WPSEO_Language_Utils::get_language( get_locale() ), $supported_languages, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// The remove all post metas.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'DELETE FROM ' . $wpdb->postmeta . ' WHERE meta_key = %s',
|
||||
self::POST_META_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers the endpoint for the redirects to WordPress.
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_EndPoint implements WPSEO_WordPress_Integration {
|
||||
|
||||
const REST_NAMESPACE = 'yoast/v1';
|
||||
const ENDPOINT_QUERY = 'redirects';
|
||||
const ENDPOINT_UNDO = 'redirects/delete';
|
||||
|
||||
const CAPABILITY_STORE = 'wpseo_manage_redirects';
|
||||
|
||||
/**
|
||||
* Instance of the WPSEO_Premium_Redirect_Service class.
|
||||
*
|
||||
* @var WPSEO_Premium_Redirect_Service
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* Sets the service to handle the request.
|
||||
*
|
||||
* @param WPSEO_Premium_Redirect_Service $service The service to handle the requests to the endpoint.
|
||||
*/
|
||||
public function __construct( WPSEO_Premium_Redirect_Service $service ) {
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'rest_api_init', [ $this, 'register' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST endpoint to WordPress.
|
||||
*/
|
||||
public function register() {
|
||||
$args = [
|
||||
'origin' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The origin to redirect',
|
||||
],
|
||||
'target' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'description' => 'The redirect target',
|
||||
],
|
||||
'type' => [
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'description' => 'The redirect type',
|
||||
],
|
||||
];
|
||||
|
||||
register_rest_route(
|
||||
self::REST_NAMESPACE,
|
||||
self::ENDPOINT_QUERY,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => $args,
|
||||
'callback' => [
|
||||
$this->service,
|
||||
'save',
|
||||
],
|
||||
'permission_callback' => [
|
||||
$this,
|
||||
'can_save_data',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::REST_NAMESPACE,
|
||||
self::ENDPOINT_UNDO,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => array_merge(
|
||||
$args,
|
||||
[
|
||||
'type' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'description' => 'The redirect format',
|
||||
],
|
||||
]
|
||||
),
|
||||
'callback' => [
|
||||
$this->service,
|
||||
'delete',
|
||||
],
|
||||
'permission_callback' => [
|
||||
$this,
|
||||
'can_save_data',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is allowed to use this endpoint.
|
||||
*
|
||||
* @return bool True user is allowed to use this endpoint.
|
||||
*/
|
||||
public function can_save_data() {
|
||||
return current_user_can( self::CAPABILITY_STORE );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Premium_Redirect_Export_Manager
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_Export_Manager implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Add export CSV block, the import and export settings are confusingly named only import.
|
||||
add_action( 'wpseo_import_tab_content', [ $this, 'add_redirect_export_block' ] );
|
||||
add_action( 'wpseo_import_tab_header', [ $this, 'redirects_export_header' ] );
|
||||
|
||||
// Hijack the request in case of CSV download and return our generated CSV instead.
|
||||
add_action( 'admin_init', [ $this, 'redirects_csv_export' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a tab header for the CSV export block.
|
||||
*/
|
||||
public function redirects_export_header() {
|
||||
if ( current_user_can( 'export' ) ) {
|
||||
echo '<a class="nav-tab" id="export-redirects-tab" href="#top#export-redirects">'
|
||||
. esc_html__( 'Export redirects', 'wordpress-seo-premium' )
|
||||
. '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding the export block for CSV. Makes it able to export redirects to CSV.
|
||||
*/
|
||||
public function add_redirect_export_block() {
|
||||
// Display the forms.
|
||||
if ( current_user_can( 'export' ) ) {
|
||||
require WPSEO_PREMIUM_PATH . 'classes/views/export-redirects.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hijacks the request and returns a CSV file if we're on the right page with the right method and the right capabilities.
|
||||
*/
|
||||
public function redirects_csv_export() {
|
||||
if ( $this->is_valid_csv_export_request() && current_user_can( 'export' ) ) {
|
||||
// Check if we have a valid nonce.
|
||||
check_admin_referer( 'wpseo-export' );
|
||||
|
||||
// Clean any content that has been already outputted, for example by other plugins or faulty PHP files.
|
||||
if ( ob_get_contents() ) {
|
||||
ob_clean();
|
||||
}
|
||||
|
||||
// Set CSV headers and content.
|
||||
$this->set_csv_headers();
|
||||
echo $this->get_csv_contents();
|
||||
|
||||
// And exit so we don't start appending HTML to our CSV file.
|
||||
// NOTE: this makes this entire class untestable as it will exit all tests but WordPress seems to have no elegant way of handling this.
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we on the wpseo_tools page in the import-export tool and have we received an export post request?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_valid_csv_export_request() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification -- Reason: Nonce is checked in export.
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are strictly comparing only or ignoring the value.
|
||||
return ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) && wp_unslash( $_GET['page'] ) === 'wpseo_tools' )
|
||||
&& ( isset( $_GET['tool'] ) && is_string( $_GET['tool'] ) && wp_unslash( $_GET['tool'] ) === 'import-export' )
|
||||
&& ( isset( $_POST['export'] ) && ! empty( $_POST['export'] ) );
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the headers to trigger an CSV download in the browser.
|
||||
*/
|
||||
protected function set_csv_headers() {
|
||||
header( 'Content-type: text/csv' );
|
||||
header( 'Content-Disposition: attachment; filename=wordpress-seo-redirects.csv' );
|
||||
header( 'Pragma: no-cache' );
|
||||
header( 'Expires: 0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates CSV from all redirects.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_csv_contents() {
|
||||
// Grab all our redirects.
|
||||
$redirect_manager = new WPSEO_Redirect_Manager();
|
||||
$redirects = $redirect_manager->get_all_redirects();
|
||||
|
||||
$csv_exporter = new WPSEO_Redirect_CSV_Exporter();
|
||||
return $csv_exporter->export( $redirects );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the premium redirect option
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_Option extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo_redirect';
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* {@internal Shouldn't be requested directly, use $this->get_defaults();}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [
|
||||
// Form fields.
|
||||
'disable_php_redirect' => 'off',
|
||||
'separate_file' => 'off',
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the option to the WPSEO Options framework.
|
||||
*/
|
||||
public static function register_option() {
|
||||
WPSEO_Options::register_option( static::get_instance() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return static Returns instance of itself.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( static::$instance instanceof static ) ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* All concrete classes must contain a validate_option() method which validates all
|
||||
* values within the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array The clean option with the saved value.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
|
||||
foreach ( $clean as $key => $value ) {
|
||||
switch ( $key ) {
|
||||
case 'disable_php_redirect':
|
||||
case 'separate_file':
|
||||
if ( isset( $dirty[ $key ] ) && in_array( $dirty[ $key ], [ 'on', 'off' ], true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* The service for the redirects to WordPress.
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_Service {
|
||||
|
||||
/**
|
||||
* Saves the redirect to the redirects.
|
||||
*
|
||||
* This save function is only used in the deprecated google-search-console integration.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response to send back.
|
||||
*/
|
||||
public function save( WP_REST_Request $request ) {
|
||||
$redirect = $this->map_request_to_redirect( $request );
|
||||
|
||||
if ( $this->get_redirect_manager()->create_redirect( $redirect ) ) {
|
||||
return new WP_REST_Response( 'true' );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( 'false' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the redirect from the redirects.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response to send back.
|
||||
*/
|
||||
public function delete( WP_REST_Request $request ) {
|
||||
$redirect = $this->map_request_to_redirect( $request );
|
||||
$redirects = [ $redirect ];
|
||||
|
||||
$redirect_format = $request->get_param( 'format' );
|
||||
if ( ! $redirect_format ) {
|
||||
$redirect_format = WPSEO_Redirect_Formats::PLAIN;
|
||||
}
|
||||
|
||||
if ( $this->get_redirect_manager( $redirect_format )->delete_redirects( $redirects ) ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was deleted successfully.', 'wordpress-seo-premium' ),
|
||||
'success' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect not deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'Something went wrong when deleting this redirect.', 'wordpress-seo-premium' ),
|
||||
'success' => false,
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an instance of the redirect manager.
|
||||
*
|
||||
* @param string $format The redirect format.
|
||||
*
|
||||
* @return WPSEO_Redirect_Manager The redirect maanger.
|
||||
*/
|
||||
protected function get_redirect_manager( $format = WPSEO_Redirect_Formats::PLAIN ) {
|
||||
return new WPSEO_Redirect_Manager( $format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the given request to an instance of the WPSEO_Redirect.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WPSEO_Redirect Redirect instance.
|
||||
*/
|
||||
protected function map_request_to_redirect( WP_REST_Request $request ) {
|
||||
$origin = $request->get_param( 'origin' );
|
||||
$target = $request->get_param( 'target' );
|
||||
$type = $request->get_param( 'type' );
|
||||
$format = $request->get_param( 'format' );
|
||||
|
||||
return new WPSEO_Redirect( $origin, $target, $type, $format );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* Capabilities registration class.
|
||||
*/
|
||||
class WPSEO_Premium_Register_Capabilities implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'wpseo_register_capabilities_premium', [ $this, 'register' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the capabilities.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$manager = WPSEO_Capability_Manager_Factory::get( 'premium' );
|
||||
|
||||
$manager->register( 'wpseo_manage_redirects', [ 'administrator', 'editor', 'wpseo_editor', 'wpseo_manager' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers the filter for filtering stale cornerstone content.
|
||||
*/
|
||||
class WPSEO_Premium_Stale_Cornerstone_Content_Filter extends WPSEO_Abstract_Post_Filter {
|
||||
|
||||
/**
|
||||
* Returns the query value this filter uses.
|
||||
*
|
||||
* @return string The query value this filter uses.
|
||||
*/
|
||||
public function get_query_val() {
|
||||
return 'stale-cornerstone-content';
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the query based on the seo_filter variable in $_GET.
|
||||
*
|
||||
* @param string $where The where statement.
|
||||
*
|
||||
* @return string The modified query.
|
||||
*/
|
||||
public function filter_posts( $where ) {
|
||||
if ( ! $this->is_filter_active() ) {
|
||||
return $where;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$where .= sprintf(
|
||||
' AND ' . $wpdb->posts . '.ID IN( SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = "%s" AND meta_value = "1" ) AND ' . $wpdb->posts . '.post_modified < "%s" ',
|
||||
WPSEO_Meta::$meta_prefix . 'is_cornerstone',
|
||||
$this->date_threshold()
|
||||
);
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for this filter.
|
||||
*
|
||||
* @return string The label for this filter.
|
||||
*/
|
||||
protected function get_label() {
|
||||
return __( 'Stale cornerstone content', 'wordpress-seo-premium' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text explaining this filter.
|
||||
*
|
||||
* @return string|null The explanation for this filter.
|
||||
*/
|
||||
protected function get_explanation() {
|
||||
$post_type_object = get_post_type_object( $this->get_current_post_type() );
|
||||
|
||||
if ( $post_type_object === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: %1$s expands to dynamic post type label, %2$s expands anchor to blog post about cornerstone content, %3$s expands to </a> */
|
||||
__( 'Stale cornerstone content refers to cornerstone content that hasn’t been updated in the last 6 months. Make sure to keep these %1$s up-to-date. %2$sLearn more about cornerstone content%3$s.', 'wordpress-seo-premium' ),
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/1i9' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total amount of stale cornerstone content.
|
||||
*
|
||||
* @return int The total amount of stale cornerstone content.
|
||||
*/
|
||||
protected function get_post_total() {
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
'
|
||||
SELECT COUNT( 1 )
|
||||
FROM ' . $wpdb->postmeta . '
|
||||
WHERE post_id IN( SELECT ID FROM ' . $wpdb->posts . ' WHERE post_type = %s && post_modified < %s ) &&
|
||||
meta_value = "1" AND meta_key = %s
|
||||
',
|
||||
$this->get_current_post_type(),
|
||||
$this->date_threshold(),
|
||||
WPSEO_Meta::$meta_prefix . 'is_cornerstone'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post types to which this filter should be added.
|
||||
*
|
||||
* @return array The post types to which this filter should be added.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using YoastSEO hook.
|
||||
$post_types = apply_filters( 'wpseo_cornerstone_post_types', parent::get_post_types() );
|
||||
if ( ! is_array( $post_types ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date 6 months ago.
|
||||
*
|
||||
* @return string The formatted date.
|
||||
*/
|
||||
protected function date_threshold() {
|
||||
return gmdate( 'Y-m-d', strtotime( '-6months' ) );
|
||||
}
|
||||
}
|
||||
60
wp/plugins/wordpress-seo-premium/classes/product-premium.php
Normal file
60
wp/plugins/wordpress-seo-premium/classes/product-premium.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
if ( class_exists( 'Yoast_Product' ) && ! class_exists( 'WPSEO_Product_Premium', false ) ) {
|
||||
|
||||
/**
|
||||
* Class WPSEO_Product_Premium
|
||||
*/
|
||||
class WPSEO_Product_Premium extends Yoast_Product {
|
||||
|
||||
/**
|
||||
* Plugin author name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PLUGIN_AUTHOR = 'Yoast';
|
||||
|
||||
/**
|
||||
* License endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EDD_STORE_URL = 'http://my.yoast.com';
|
||||
|
||||
/**
|
||||
* Product name to use for license checks.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EDD_PLUGIN_NAME = 'Yoast SEO Premium';
|
||||
|
||||
/**
|
||||
* Construct the Product Premium class
|
||||
*/
|
||||
public function __construct() {
|
||||
$file = plugin_basename( WPSEO_FILE );
|
||||
$slug = dirname( $file );
|
||||
|
||||
parent::__construct(
|
||||
trailingslashit( self::EDD_STORE_URL ) . 'edd-sl-api',
|
||||
self::EDD_PLUGIN_NAME,
|
||||
$slug,
|
||||
WPSEO_Premium::PLUGIN_VERSION_NAME,
|
||||
'https://yoast.com/wordpress/plugins/seo-premium/',
|
||||
'admin.php?page=wpseo_licenses#top#licenses',
|
||||
'wordpress-seo',
|
||||
self::PLUGIN_AUTHOR,
|
||||
$file
|
||||
);
|
||||
|
||||
if ( method_exists( $this, 'set_extension_url' ) ) {
|
||||
$this->set_extension_url( 'https://my.yoast.com/licenses/' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers the endpoint to delete the redirect for a Post to WordPress.
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_Undo_EndPoint implements WPSEO_WordPress_Integration {
|
||||
|
||||
const REST_NAMESPACE = 'yoast/v1';
|
||||
const ENDPOINT_UNDO = 'redirects/undo-for-object';
|
||||
|
||||
/**
|
||||
* Instance of the WPSEO_Redirect_Manager class.
|
||||
*
|
||||
* @var WPSEO_Redirect_Manager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* Sets the service to handle the request.
|
||||
*
|
||||
* @param WPSEO_Redirect_Manager $manager The manager for working with redirects.
|
||||
*/
|
||||
public function __construct( WPSEO_Redirect_Manager $manager ) {
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'rest_api_init', [ $this, 'register' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST endpoint to WordPress.
|
||||
*/
|
||||
public function register() {
|
||||
register_rest_route(
|
||||
self::REST_NAMESPACE,
|
||||
self::ENDPOINT_UNDO,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'obj_id' => [
|
||||
'required' => true,
|
||||
'type' => 'int',
|
||||
'description' => 'The id of the post or term',
|
||||
],
|
||||
'obj_type' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The object type: post or term',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'undo_redirect' ],
|
||||
'permission_callback' => [ $this, 'can_save_data' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the latest redirect to the post or term referenced in the request.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
*
|
||||
* @return WP_REST_Response The response.
|
||||
*/
|
||||
public function undo_redirect( WP_REST_Request $request ) {
|
||||
$object_id = $request->get_param( 'obj_id' );
|
||||
$object_type = $request->get_param( 'obj_type' );
|
||||
|
||||
$redirect_info = $this->retrieve_post_or_term_redirect_info( $object_type, $object_id );
|
||||
$redirect = $this->map_redirect_info_to_redirect( $redirect_info );
|
||||
|
||||
if ( ! $redirect->get_origin() ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect not deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'Something went wrong when deleting this redirect.', 'wordpress-seo-premium' ),
|
||||
'success' => false,
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->manager->delete_redirects( [ $redirect ] ) ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was deleted successfully.', 'wordpress-seo-premium' ),
|
||||
'success' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect not deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'Something went wrong when deleting this redirect.', 'wordpress-seo-premium' ),
|
||||
'success' => false,
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the given redirect info to an instance of the WPSEO_Redirect.
|
||||
*
|
||||
* @param array $redirect_info The redirect info array.
|
||||
*
|
||||
* @return WPSEO_Redirect Redirect instance.
|
||||
*/
|
||||
protected function map_redirect_info_to_redirect( $redirect_info ) {
|
||||
$origin = isset( $redirect_info['origin'] ) ? $redirect_info['origin'] : null;
|
||||
$target = isset( $redirect_info['target'] ) ? $redirect_info['target'] : null;
|
||||
$type = isset( $redirect_info['type'] ) ? $redirect_info['type'] : null;
|
||||
$format = isset( $redirect_info['format'] ) ? $redirect_info['format'] : null;
|
||||
|
||||
return new WPSEO_Redirect( $origin, $target, $type, $format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the redirect info from the meta for the specified object and id.
|
||||
*
|
||||
* @param string $object_type The type of object: post or term.
|
||||
* @param int $object_id The post or term ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function retrieve_post_or_term_redirect_info( $object_type, $object_id ) {
|
||||
if ( $object_type === 'post' ) {
|
||||
$redirect_info = get_post_meta( $object_id, '_yoast_post_redirect_info', true );
|
||||
delete_post_meta( $object_id, '_yoast_post_redirect_info' );
|
||||
return $redirect_info;
|
||||
}
|
||||
|
||||
if ( $object_type === 'term' ) {
|
||||
$redirect_info = get_term_meta( $object_id, '_yoast_term_redirect_info', true );
|
||||
delete_term_meta( $object_id, '_yoast_term_redirect_info' );
|
||||
return $redirect_info;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is allowed to use this endpoint.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
*
|
||||
* @return bool True user is allowed to use this endpoint.
|
||||
*/
|
||||
public function can_save_data( WP_REST_Request $request ) {
|
||||
$object_id = $request->get_param( 'obj_id' );
|
||||
$object_type = $request->get_param( 'obj_type' );
|
||||
|
||||
if ( $object_type === 'post' ) {
|
||||
return current_user_can( 'edit_post', $object_id );
|
||||
}
|
||||
|
||||
if ( $object_type === 'term' ) {
|
||||
return current_user_can( 'edit_term', $object_id );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirects
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an executable redirect.
|
||||
*/
|
||||
final class WPSEO_Executable_Redirect {
|
||||
|
||||
/**
|
||||
* Redirect origin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $origin;
|
||||
|
||||
/**
|
||||
* Redirect target.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $target;
|
||||
|
||||
/**
|
||||
* A HTTP code determining the redirect type.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* A string determining the redirect format (plain or regex).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $format;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect constructor.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $origin The origin of the redirect.
|
||||
* @param string $target The target of the redirect.
|
||||
* @param int $type The type of the redirect.
|
||||
* @param string $format The format of the redirect.
|
||||
*/
|
||||
public function __construct( $origin, $target, $type, $format ) {
|
||||
$this->origin = $origin;
|
||||
$this->target = $target;
|
||||
$this->type = $type;
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance based on the given data.
|
||||
*
|
||||
* @param array $data The redirect data.
|
||||
*
|
||||
* @return WPSEO_Executable_Redirect The created object.
|
||||
*/
|
||||
public static function from_array( $data ) {
|
||||
return new self( $data['origin'], $data['target'], $data['type'], $data['format'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the origin.
|
||||
*
|
||||
* @return string The origin.
|
||||
*/
|
||||
public function get_origin() {
|
||||
return $this->origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the target.
|
||||
*
|
||||
* @return string The target.
|
||||
*/
|
||||
public function get_target() {
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type.
|
||||
*
|
||||
* @return int The redirect type.
|
||||
*/
|
||||
public function get_type() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the redirect format.
|
||||
*
|
||||
* @return string The redirect format.
|
||||
*/
|
||||
public function get_format() {
|
||||
return $this->format;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* This exporter class will format the redirects for apache files.
|
||||
*/
|
||||
class WPSEO_Redirect_Apache_Exporter extends WPSEO_Redirect_File_Exporter {
|
||||
|
||||
/**
|
||||
* %1$s is the old URL
|
||||
* %2$s is the new URL
|
||||
* %3$s is the redirect type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url_format = 'Redirect %3$s "%1$s" "%2$s"';
|
||||
|
||||
/**
|
||||
* %1$s is the old URL
|
||||
* %2$s is the redirect type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url_non_target_format = 'Redirect %2$s "%1$s"';
|
||||
|
||||
/**
|
||||
* %1$s is the old URL
|
||||
* %2$s is the new URL
|
||||
* %3$s is the redirect type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex_format = 'RedirectMatch %3$s %1$s %2$s';
|
||||
|
||||
/**
|
||||
* %1$s is the old URL
|
||||
* %2$s is the redirect type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex_non_target_format = 'RedirectMatch %2$s %1$s';
|
||||
|
||||
/**
|
||||
* Overrides the parent method. This method will in case of URL redirects add slashes to the URL.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
|
||||
// 4xx redirects don't have a target.
|
||||
$redirect_type = intval( $redirect->get_type() );
|
||||
if ( $redirect_type >= 400 && $redirect_type < 500 ) {
|
||||
return $this->format_non_target( $redirect );
|
||||
}
|
||||
|
||||
$origin = $redirect->get_origin();
|
||||
|
||||
if ( $redirect->get_format() === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
$origin = $this->format_url( $redirect->get_origin() );
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
$this->get_format( $redirect->get_format() ),
|
||||
$origin,
|
||||
$this->format_url( $redirect->get_target() ),
|
||||
$redirect->get_type()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the URL before it is added to the redirects.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return string Formatted URL.
|
||||
*/
|
||||
protected function format_url( $url ) {
|
||||
return $this->add_url_slash( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the redirect output for non-target status codes (4xx)
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format_non_target( WPSEO_Redirect $redirect ) {
|
||||
|
||||
$target = $redirect->get_origin();
|
||||
if ( $redirect->get_format() === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
$target = $this->add_url_slash( $target );
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
$this->get_non_target_format( $redirect->get_format() ),
|
||||
$target,
|
||||
$redirect->get_type()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format the redirect needs to output
|
||||
*
|
||||
* @param string $redirect_format The format of the redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_non_target_format( $redirect_format ) {
|
||||
if ( $redirect_format === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
return $this->url_non_target_format;
|
||||
}
|
||||
|
||||
return $this->regex_non_target_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if first character is a slash, adds a slash if it ain't so
|
||||
*
|
||||
* @param string $url The URL add the slashes to.
|
||||
*
|
||||
* @return string mixed
|
||||
*/
|
||||
private function add_url_slash( $url ) {
|
||||
$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
|
||||
if ( substr( $url, 0, 1 ) !== '/' && empty( $scheme ) ) {
|
||||
$url = '/' . $url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* This exporter class will format the redirects for csv files.
|
||||
*
|
||||
* Does not implement WPSEO_Redirect_File_Exporter as the CSV is intended to be streamed, not saved.
|
||||
*/
|
||||
class WPSEO_Redirect_CSV_Exporter implements WPSEO_Redirect_Exporter {
|
||||
|
||||
/**
|
||||
* Exports an array of redirects to a CSV string.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to export.
|
||||
*
|
||||
* @return string CSV string of all exported redirects with headers.
|
||||
*/
|
||||
public function export( $redirects ) {
|
||||
$csv = $this->get_headers();
|
||||
|
||||
if ( ! empty( $redirects ) ) {
|
||||
foreach ( $redirects as $redirect ) {
|
||||
if ( $redirect instanceof WPSEO_Redirect ) {
|
||||
$csv .= PHP_EOL . $this->format( $redirect );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export, returns a line of CSV.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return string CSV line of the redirect for format.
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
$target = $redirect->get_target();
|
||||
if ( WPSEO_Redirect_Util::is_relative_url( $target ) ) {
|
||||
$target = '/' . $target;
|
||||
}
|
||||
if ( WPSEO_Redirect_Util::requires_trailing_slash( $target ) ) {
|
||||
$target = trailingslashit( $target );
|
||||
}
|
||||
|
||||
$origin = $redirect->get_origin();
|
||||
if ( $redirect->get_format() === WPSEO_Redirect_Formats::PLAIN && WPSEO_Redirect_Util::is_relative_url( $origin ) ) {
|
||||
$origin = '/' . $origin;
|
||||
}
|
||||
|
||||
$redirect_details = [
|
||||
$this->format_csv_column( $origin ),
|
||||
$this->format_csv_column( $target ),
|
||||
$this->format_csv_column( $redirect->get_type() ),
|
||||
$this->format_csv_column( $redirect->get_format() ),
|
||||
];
|
||||
|
||||
return implode( ',', $redirect_details );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers to add to the first line of the generated CSV.
|
||||
*
|
||||
* @return string CSV line of the headers.
|
||||
*/
|
||||
protected function get_headers() {
|
||||
$headers = [
|
||||
__( 'Origin', 'wordpress-seo-premium' ),
|
||||
__( 'Target', 'wordpress-seo-premium' ),
|
||||
__( 'Type', 'wordpress-seo-premium' ),
|
||||
__( 'Format', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
return implode( ',', $headers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Surrounds a value with double quotes and escapes existing double quotes.
|
||||
*
|
||||
* @param string $value The value to sanitize.
|
||||
*
|
||||
* @return string The sanitized value.
|
||||
*/
|
||||
protected function format_csv_column( $value ) {
|
||||
return '"' . str_replace( '"', '""', (string) $value ) . '"';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a redirect export
|
||||
*/
|
||||
interface WPSEO_Redirect_Exporter {
|
||||
|
||||
/**
|
||||
* Exports an array of redirects.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to export.
|
||||
*/
|
||||
public function export( $redirects );
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect );
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_File.
|
||||
*/
|
||||
abstract class WPSEO_Redirect_File_Exporter implements WPSEO_Redirect_Exporter {
|
||||
|
||||
/**
|
||||
* The URL format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url_format = '';
|
||||
|
||||
/**
|
||||
* The regex format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex_format = '';
|
||||
|
||||
/**
|
||||
* Exports an array of redirects.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to export.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function export( $redirects ) {
|
||||
|
||||
$file_content = '';
|
||||
if ( ! empty( $redirects ) ) {
|
||||
foreach ( $redirects as $redirect ) {
|
||||
$file_content .= $this->format( $redirect ) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
// Check if the file content isset.
|
||||
return $this->save( $file_content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
return sprintf(
|
||||
$this->get_format( $redirect->get_format() ),
|
||||
$redirect->get_origin(),
|
||||
$redirect->get_target(),
|
||||
$redirect->get_type()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the needed format for the redirect.
|
||||
*
|
||||
* @param string $redirect_format The format of the redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_format( $redirect_format ) {
|
||||
if ( $redirect_format === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
return $this->url_format;
|
||||
}
|
||||
|
||||
return $this->regex_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the redirect file.
|
||||
*
|
||||
* @param string $file_content The file content that will be saved.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function save( $file_content ) {
|
||||
// Save the actual file.
|
||||
if ( is_writable( WPSEO_Redirect_File_Util::get_file_path() ) ) {
|
||||
WPSEO_Redirect_File_Util::write_file( WPSEO_Redirect_File_Util::get_file_path(), $file_content );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Htaccess_Redirect_File
|
||||
*/
|
||||
class WPSEO_Redirect_Htaccess_Exporter extends WPSEO_Redirect_Apache_Exporter {
|
||||
|
||||
/**
|
||||
* Save the redirect file
|
||||
*
|
||||
* @param string $file_content The file content that will be saved.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function save( $file_content ) {
|
||||
$file_path = WPSEO_Redirect_Htaccess_Util::get_htaccess_file_path();
|
||||
|
||||
// Update the .htaccess file.
|
||||
if ( is_writable( $file_path ) ) {
|
||||
$htaccess = $this->get_htaccess_content( $file_path, $file_content );
|
||||
$return = (bool) WPSEO_Redirect_File_Util::write_file( $file_path, $htaccess );
|
||||
|
||||
// Make sure defines are created.
|
||||
WP_Filesystem();
|
||||
chmod( $file_path, FS_CHMOD_FILE );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the content from current .htaccess
|
||||
*
|
||||
* @param string $file_path The location of the htaccess file.
|
||||
* @param string $file_content THe content to save in the htaccess file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_htaccess_content( $file_path, $file_content ) {
|
||||
// Read current htaccess.
|
||||
$htaccess = '';
|
||||
if ( file_exists( $file_path ) ) {
|
||||
$htaccess = file_get_contents( $file_path );
|
||||
}
|
||||
|
||||
$htaccess = preg_replace( '`# BEGIN YOAST REDIRECTS.*# END YOAST REDIRECTS' . PHP_EOL . '`is', '', $htaccess );
|
||||
|
||||
// Only add redirect code when redirects are present.
|
||||
if ( ! empty( $file_content ) ) {
|
||||
$file_content = '# BEGIN YOAST REDIRECTS' . PHP_EOL . '<IfModule mod_rewrite.c>' . PHP_EOL . 'RewriteEngine On' . PHP_EOL . $file_content . '</IfModule>' . PHP_EOL . '# END YOAST REDIRECTS' . PHP_EOL;
|
||||
|
||||
// Prepend our redirects to htaccess file.
|
||||
$htaccess = $file_content . $htaccess;
|
||||
}
|
||||
|
||||
return $htaccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters in the URL that will cause problems in .htaccess.
|
||||
*
|
||||
* Overrides WPSEO_Redirect_Apache_Exporter::format_url.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return string The escaped URL.
|
||||
*/
|
||||
protected function format_url( $url ) {
|
||||
$url = parent::format_url( $url );
|
||||
|
||||
return $this->sanitize( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters that will cause problems in .htaccess.
|
||||
*
|
||||
* @param string $unsanitized The unsanitized value.
|
||||
*
|
||||
* @return string The sanitized value.
|
||||
*/
|
||||
private function sanitize( $unsanitized ) {
|
||||
return str_replace(
|
||||
[
|
||||
'\\',
|
||||
'"',
|
||||
],
|
||||
[
|
||||
'/',
|
||||
'\"',
|
||||
],
|
||||
$unsanitized
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exporter for Nginx, only declares the two formats
|
||||
*/
|
||||
class WPSEO_Redirect_Nginx_Exporter extends WPSEO_Redirect_File_Exporter {
|
||||
|
||||
/**
|
||||
* %1$s is the origin
|
||||
* %2$s is the target
|
||||
* %3$s is the redirect type
|
||||
* %4$s is the optional x-redirect-by filter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url_format = 'location /%1$s { %4$s return %3$s %2$s; }';
|
||||
|
||||
/**
|
||||
* %1$s is the origin
|
||||
* %2$s is the target
|
||||
* %3$s is the redirect type
|
||||
* %4$s is the optional x-redirect-by filter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex_format = 'location ~ %1$s { %4$s return %3$s %2$s; }';
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
return sprintf(
|
||||
$this->get_format( $redirect->get_format() ),
|
||||
$redirect->get_origin(),
|
||||
$redirect->get_target(),
|
||||
$redirect->get_type(),
|
||||
$this->add_x_redirect_header()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an X-Redirect-By header if allowed by the filter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function add_x_redirect_header() {
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\add_x_redirect' - can be used to remove the X-Redirect-By header
|
||||
* Yoast SEO Premium creates (defaults to true, which is adding it)
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api bool
|
||||
*/
|
||||
if ( apply_filters( 'Yoast\WP\SEO\add_x_redirect', true ) === true ) {
|
||||
return 'add_header X-Redirect-By "Yoast SEO Premium";';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirects\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Saving the redirects from a single file into two smaller options files.
|
||||
*/
|
||||
class WPSEO_Redirect_Option_Exporter implements WPSEO_Redirect_Exporter {
|
||||
|
||||
/**
|
||||
* This method will split the redirects in separate arrays and store them in an option.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to export.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function export( $redirects ) {
|
||||
$formatted_redirects = [
|
||||
WPSEO_Redirect_Formats::PLAIN => [],
|
||||
WPSEO_Redirect_Formats::REGEX => [],
|
||||
];
|
||||
|
||||
foreach ( $redirects as $redirect ) {
|
||||
$formatted_redirects[ $redirect->get_format() ][ $redirect->get_origin() ] = $this->format( $redirect );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the parameter to save the redirect options as autoloaded.
|
||||
*
|
||||
* Note that the `autoload` value in the database will change only if the option value changes (i.e. a redirect is added, edited or deleted).
|
||||
* Otherwise you will need to change the `autoload` value directly in the DB.
|
||||
*
|
||||
* @since 20.13
|
||||
*
|
||||
* @param bool $autoload The value of the `autoload` parameter. Default: true.
|
||||
* @param string $type The type of redirects, either `plain` or `regex`.
|
||||
* @param array $formatted_redirects The redirects to be written in the options, already formatted.
|
||||
*
|
||||
* @return bool The filtered value of the `autoload` parameter.
|
||||
*/
|
||||
$autoload_options_plain = apply_filters( 'Yoast\WP\SEO\redirects_options_autoload', true, 'plain', $formatted_redirects );
|
||||
$autoload_options_regex = apply_filters( 'Yoast\WP\SEO\redirects_options_autoload', true, 'regex', $formatted_redirects );
|
||||
|
||||
update_option( WPSEO_Redirect_Option::OPTION_PLAIN, $formatted_redirects[ WPSEO_Redirect_Formats::PLAIN ], $autoload_options_plain );
|
||||
update_option( WPSEO_Redirect_Option::OPTION_REGEX, $formatted_redirects[ WPSEO_Redirect_Formats::REGEX ], $autoload_options_regex );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
return [
|
||||
'url' => $redirect->get_target(),
|
||||
'type' => $redirect->get_type(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for loading redirects from an external source and validating them.
|
||||
*/
|
||||
abstract class WPSEO_Redirect_Abstract_Loader implements WPSEO_Redirect_Loader {
|
||||
|
||||
/**
|
||||
* Validates if the given value is a http status code.
|
||||
*
|
||||
* @param string|int $status_code The status code to validate.
|
||||
*
|
||||
* @return bool Whether or not the status code is valid.
|
||||
*/
|
||||
protected function validate_status_code( $status_code ) {
|
||||
if ( is_string( $status_code ) ) {
|
||||
if ( ! preg_match( '/\A\d+\Z/', $status_code, $matches ) ) {
|
||||
return false;
|
||||
}
|
||||
$status_code = (int) $status_code;
|
||||
}
|
||||
|
||||
$status_codes = new WPSEO_Redirect_Types();
|
||||
|
||||
return $status_codes->has( $status_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the given value is a redirect format.
|
||||
*
|
||||
* @param string $format The format to validate.
|
||||
*
|
||||
* @return bool Whether or not the format is valid.
|
||||
*/
|
||||
protected function validate_format( $format ) {
|
||||
$redirect_formats = new WPSEO_Redirect_Formats();
|
||||
|
||||
return $redirect_formats->has( $format );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from a CSV file and validating them.
|
||||
*/
|
||||
class WPSEO_Redirect_CSV_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* Path of the CSV file to load.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $csv_file;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_CSV_Loader constructor.
|
||||
*
|
||||
* @param string $csv_file Path of the CSV file to load.
|
||||
*/
|
||||
public function __construct( $csv_file ) {
|
||||
$this->csv_file = $csv_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all redirects from the CSV file.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The redirects loaded from the CSV file.
|
||||
*/
|
||||
public function load() {
|
||||
$handle = fopen( $this->csv_file, 'r' );
|
||||
|
||||
if ( ! $handle ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$redirects = [];
|
||||
while ( $item = fgetcsv( $handle, 10000 ) ) {
|
||||
if ( ! $this->validate_item( $item ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $item[0], $item[1], $item[2], $item[3] );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a parsed CSV row is has a valid redirect format.
|
||||
* It should have exactly 4 values.
|
||||
* The third value should be a http status code.
|
||||
* The last value should be a redirect format.
|
||||
*
|
||||
* @param array $item The parsed CSV row.
|
||||
*
|
||||
* @return bool Whether or not the parsed CSV row is valid.
|
||||
*/
|
||||
protected function validate_item( $item ) {
|
||||
if ( count( $item ) !== 4 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->validate_status_code( $item[2] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->validate_format( $item[3] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from .htaccess files.
|
||||
*/
|
||||
class WPSEO_Redirect_HTAccess_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* The contents of the htaccess file to import.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $htaccess;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_HTAccess_Loader constructor.
|
||||
*
|
||||
* @param string $htaccess The contents of the htaccess file to import.
|
||||
*/
|
||||
public function __construct( $htaccess ) {
|
||||
$this->htaccess = $htaccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads redirects as WPSEO_Redirects from the .htaccess file given to the constructor.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load() {
|
||||
$redirects = [];
|
||||
|
||||
// Loop through patterns.
|
||||
foreach ( self::regex_patterns() as $regex ) {
|
||||
$matches = $this->match_redirects( $regex['pattern'] );
|
||||
|
||||
if ( is_array( $matches ) ) {
|
||||
$redirects = array_merge( $redirects, $this->convert_redirects_from_matches( $matches, $regex['type'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the string (containing redirects) for the given regex.
|
||||
*
|
||||
* @param string $pattern The regular expression to match redirects.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
protected function match_redirects( $pattern ) {
|
||||
preg_match_all( $pattern, $this->htaccess, $matches, PREG_SET_ORDER );
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts matches to WPSEO_Redirect objects.
|
||||
*
|
||||
* @param array[] $matches The redirects to save.
|
||||
* @param string $format The format for the redirects.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The redirects.
|
||||
*/
|
||||
protected function convert_redirects_from_matches( $matches, $format ) {
|
||||
$redirects = [];
|
||||
|
||||
foreach ( $matches as $match ) {
|
||||
$type = trim( $match[1] );
|
||||
$source = trim( $match[2] );
|
||||
$target = $this->parse_target( $type, $match );
|
||||
|
||||
if ( $target === false || $source === '' || ! $this->validate_status_code( $type ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $source, $target, $type, $format );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the target from a match.
|
||||
*
|
||||
* @param string $type The status code of the redirect.
|
||||
* @param string[] $matched The match.
|
||||
*
|
||||
* @return bool|string The status code, false if no status code could be parsed.
|
||||
*/
|
||||
protected function parse_target( $type, $matched ) {
|
||||
// If it's a gone status code that doesn't need a target.
|
||||
if ( $type === '410' ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$target = trim( $matched[3] );
|
||||
|
||||
// There is no target, skip it.
|
||||
if ( $target === '' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns regex patterns to match redirects in .htaccess files.
|
||||
*
|
||||
* @return array[] The regex patterns to test against.
|
||||
*/
|
||||
protected static function regex_patterns() {
|
||||
return [
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::PLAIN,
|
||||
'pattern' => '`^Redirect ([0-9]{3}) ([^"\s]+) ([a-z0-9-_+/.:%&?=#\][]+)`im',
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::PLAIN,
|
||||
'pattern' => '`^Redirect ([0-9]{3}) "([^"]+)" ([a-z0-9-_+/.:%&?=#\][]+)`im',
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::PLAIN,
|
||||
'pattern' => '`^Redirect (410) ([^"\s]+)`im', // Matches a redirect without a target.
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::PLAIN,
|
||||
'pattern' => '`^Redirect (410) "([^"]+)"`im', // Matches a redirect without a target.
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::REGEX,
|
||||
'pattern' => '`^RedirectMatch ([0-9]{3}) ([^"\s]+) ([^\s]+)`im',
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::REGEX,
|
||||
'pattern' => '`^RedirectMatch ([0-9]{3}) "([^"]+)" ([^\s]+)`im',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a redirect loader for external sources.
|
||||
*/
|
||||
interface WPSEO_Redirect_Loader {
|
||||
|
||||
/**
|
||||
* Loads the redirects from an external source and validates them.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from the Redirection plugin.
|
||||
*/
|
||||
class WPSEO_Redirect_Redirection_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* A WordPress database object.
|
||||
*
|
||||
* @var wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_Redirection_Loader constructor.
|
||||
*
|
||||
* @param wpdb $wpdb A WordPress database object.
|
||||
*/
|
||||
public function __construct( $wpdb ) {
|
||||
$this->wpdb = $wpdb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads redirects as WPSEO_Redirects from the Redirection plugin.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load() {
|
||||
// Get redirects.
|
||||
// phpcs:disable WordPress.DB.PreparedSQL -- Prefix variable comes from wpdb, query is fine without preparing.
|
||||
$items = $this->wpdb->get_results(
|
||||
"SELECT `url`, `action_data`, `regex`, `action_code`
|
||||
FROM {$this->wpdb->prefix}redirection_items
|
||||
WHERE `status` = 'enabled' AND `action_type` = 'url'"
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
$redirects = [];
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$format = WPSEO_Redirect_Formats::PLAIN;
|
||||
if ( (int) $item->regex === 1 ) {
|
||||
$format = WPSEO_Redirect_Formats::REGEX;
|
||||
}
|
||||
|
||||
if ( ! $this->validate_status_code( $item->action_code ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $item->url, $item->action_data, $item->action_code, $format );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from the Safe Redirect Manager plugin.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/safe-redirect-manager/
|
||||
*/
|
||||
class WPSEO_Redirect_Safe_Redirect_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* Loads redirects as WPSEO_Redirects from the Safe Redirect Manager plugin.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load() {
|
||||
$items = get_transient( '_srm_redirects' );
|
||||
$redirects = [];
|
||||
|
||||
if ( ! is_array( $items ) ) {
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$item = $this->convert_wildcards( $item );
|
||||
|
||||
$format = WPSEO_Redirect_Formats::PLAIN;
|
||||
if ( (int) $item['enable_regex'] === 1 ) {
|
||||
$format = WPSEO_Redirect_Formats::REGEX;
|
||||
}
|
||||
|
||||
$status_code = $this->convert_status_code( $item['status_code'] );
|
||||
|
||||
if ( ! $this->validate_status_code( $status_code ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $item['redirect_from'], $item['redirect_to'], $status_code, $format );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts unsupported 404 and 403 status codes to a 410 status code.
|
||||
* Also converts unsupported 303 status codes to a 302 status code.
|
||||
*
|
||||
* @param int $status_code The original status code.
|
||||
*
|
||||
* @return int A status code Yoast supports.
|
||||
*/
|
||||
protected function convert_status_code( $status_code ) {
|
||||
switch ( $status_code ) {
|
||||
case 303:
|
||||
return 302;
|
||||
case 403:
|
||||
case 404:
|
||||
return 410;
|
||||
default:
|
||||
return (int) $status_code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts unsupported wildcard format to supported regex format.
|
||||
*
|
||||
* @param array $item A Safe Redirect Manager redirect.
|
||||
*
|
||||
* @return array A converted redirect.
|
||||
*/
|
||||
protected function convert_wildcards( $item ) {
|
||||
if ( substr( $item['redirect_from'], -1, 1 ) === '*' ) {
|
||||
$item['redirect_from'] = preg_replace( '/(\*)$/', '.*', $item['redirect_from'] );
|
||||
$item['enable_regex'] = 1;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from the Simple 301 Redirects plugin.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/simple-301-redirects/
|
||||
*/
|
||||
class WPSEO_Redirect_Simple_301_Redirect_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* Loads redirects as WPSEO_Redirects from the Simple 301 Redirects plugin.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load() {
|
||||
$items = get_option( '301_redirects' );
|
||||
$uses_wildcards = get_option( '301_redirects_wildcard' );
|
||||
$redirects = [];
|
||||
|
||||
if ( ! is_array( $items ) ) {
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
foreach ( $items as $origin => $target ) {
|
||||
$format = WPSEO_Redirect_Formats::PLAIN;
|
||||
|
||||
// If wildcard redirects had been used, and this is one, flip it.
|
||||
if ( $uses_wildcards && strpos( $origin, '*' ) !== false ) {
|
||||
$format = WPSEO_Redirect_Formats::REGEX;
|
||||
$origin = str_replace( '*', '(.*)', $origin );
|
||||
$target = str_replace( '*', '$1', $target );
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $origin, $target, 301, $format );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* The presenter for the form, this form will be used for adding and updating the redirects.
|
||||
*/
|
||||
class WPSEO_Redirect_Form_Presenter implements WPSEO_Redirect_Presenter {
|
||||
|
||||
/**
|
||||
* Variables to be passed to the form view.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $view_vars;
|
||||
|
||||
/**
|
||||
* Setting up the view_vars.
|
||||
*
|
||||
* @param array $view_vars The variables to pass into the view.
|
||||
*/
|
||||
public function __construct( array $view_vars ) {
|
||||
$this->view_vars = $view_vars;
|
||||
|
||||
$this->view_vars['redirect_types'] = $this->get_redirect_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the form.
|
||||
*
|
||||
* @param array $display Additional display variables.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display( array $display = [] ) {
|
||||
$display_vars = $this->view_vars;
|
||||
if ( ! empty( $display ) ) {
|
||||
$display_vars = array_merge_recursive( $display_vars, $display );
|
||||
}
|
||||
|
||||
require WPSEO_PREMIUM_PATH . 'classes/redirect/views/redirects-form.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting array with the available redirect types.
|
||||
*
|
||||
* @return array Array with the redirect types.
|
||||
*/
|
||||
private function get_redirect_types() {
|
||||
$types = new WPSEO_Redirect_Types();
|
||||
|
||||
return $types->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Page_Presenter
|
||||
*/
|
||||
class WPSEO_Redirect_Page_Presenter implements WPSEO_Redirect_Presenter {
|
||||
|
||||
/**
|
||||
* Displays the redirect page.
|
||||
*
|
||||
* @param array $display Contextual display data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display( array $display = [] ) {
|
||||
$current_tab = ! empty( $display['current_tab'] ) ? $display['current_tab'] : '';
|
||||
$tab_presenter = $this->get_tab_presenter( $current_tab );
|
||||
$redirect_tabs = $this->navigation_tabs( $current_tab );
|
||||
|
||||
include WPSEO_PREMIUM_PATH . 'classes/redirect/views/redirects.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a tab presenter.
|
||||
*
|
||||
* @param string $tab_to_display The tab that will be shown.
|
||||
*
|
||||
* @return WPSEO_Redirect_Tab_Presenter|null Tab presenter instance, or null if invalid tab given.
|
||||
*/
|
||||
private function get_tab_presenter( $tab_to_display ) {
|
||||
$tab_presenter = null;
|
||||
switch ( $tab_to_display ) {
|
||||
case WPSEO_Redirect_Formats::PLAIN:
|
||||
case WPSEO_Redirect_Formats::REGEX:
|
||||
$tab_presenter = new WPSEO_Redirect_Table_Presenter( $tab_to_display );
|
||||
break;
|
||||
case 'settings':
|
||||
if ( current_user_can( 'wpseo_manage_options' ) ) {
|
||||
$tab_presenter = new WPSEO_Redirect_Settings_Presenter( $tab_to_display );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $tab_presenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returning the anchors html for the tabs
|
||||
*
|
||||
* @param string $current_tab The tab that will be active.
|
||||
*
|
||||
* @return array {
|
||||
* Associative array of navigation tabs data.
|
||||
*
|
||||
* @type array $tabs Array of $tab_slug => $tab_label pairs.
|
||||
* @type string $current_tab The currently active tab slug.
|
||||
* @type string $page_url Base URL of the current page, to append the tab slug to.
|
||||
* }
|
||||
*/
|
||||
private function navigation_tabs( $current_tab ) {
|
||||
$tabs = $this->get_redirect_formats();
|
||||
|
||||
if ( current_user_can( 'wpseo_manage_options' ) ) {
|
||||
$tabs['settings'] = __( 'Settings', 'wordpress-seo-premium' );
|
||||
}
|
||||
|
||||
return [
|
||||
'tabs' => $tabs,
|
||||
'current_tab' => $current_tab,
|
||||
'page_url' => admin_url( 'admin.php?page=wpseo_redirects&tab=' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the available redirect formats.
|
||||
*
|
||||
* @return array Redirect formats as $slug => $label pairs.
|
||||
*/
|
||||
protected function get_redirect_formats() {
|
||||
$redirect_formats = new WPSEO_Redirect_Formats();
|
||||
|
||||
return $redirect_formats->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a presenter for a redirects UI component.
|
||||
*/
|
||||
interface WPSEO_Redirect_Presenter {
|
||||
|
||||
/**
|
||||
* Displaying the table URL or regex. Depends on the current active tab.
|
||||
*
|
||||
* @param array $display Contextual display data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display( array $display = [] );
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Presenter for the quick edit
|
||||
*/
|
||||
class WPSEO_Redirect_Quick_Edit_Presenter implements WPSEO_Redirect_Presenter {
|
||||
|
||||
/**
|
||||
* Displays the table
|
||||
*
|
||||
* @param array $display_data Data to display on the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display( array $display_data = [] ) {
|
||||
require WPSEO_PREMIUM_PATH . 'classes/redirect/views/redirects-quick-edit.php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Settings_Presenter
|
||||
*/
|
||||
class WPSEO_Redirect_Settings_Presenter extends WPSEO_Redirect_Tab_Presenter {
|
||||
|
||||
/**
|
||||
* Extending the view vars with pre settings key
|
||||
*
|
||||
* @param array $passed_vars Optional. View data manually passed. Default empty array.
|
||||
*
|
||||
* @return array Contextual variables to pass to the view.
|
||||
*/
|
||||
protected function get_view_vars( array $passed_vars = [] ) {
|
||||
return array_merge(
|
||||
$passed_vars,
|
||||
[
|
||||
'file_path' => WPSEO_Redirect_File_Util::get_file_path(),
|
||||
'redirect_file' => $this->writable_redirect_file(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it is possible to write to the files
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
private function writable_redirect_file() {
|
||||
if ( WPSEO_Options::get( 'disable_php_redirect' ) !== 'on' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do file checks.
|
||||
$file_exists = file_exists( WPSEO_Redirect_File_Util::get_file_path() );
|
||||
|
||||
if ( WPSEO_Utils::is_apache() ) {
|
||||
$separate_file = ( WPSEO_Options::get( 'separate_file' ) === 'on' );
|
||||
|
||||
if ( $separate_file && $file_exists ) {
|
||||
return 'apache_include_file';
|
||||
}
|
||||
|
||||
if ( ! $separate_file ) {
|
||||
// Everything is as expected.
|
||||
if ( is_writable( WPSEO_Redirect_Htaccess_Util::get_htaccess_file_path() ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return 'cannot_write_htaccess';
|
||||
}
|
||||
|
||||
if ( WPSEO_Utils::is_nginx() ) {
|
||||
if ( $file_exists ) {
|
||||
return 'nginx_include_file';
|
||||
}
|
||||
|
||||
return 'cannot_write_file';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Tab_Presenter.
|
||||
*/
|
||||
abstract class WPSEO_Redirect_Tab_Presenter implements WPSEO_Redirect_Presenter {
|
||||
|
||||
/**
|
||||
* The view to be rendered in the tab.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Sets the view.
|
||||
*
|
||||
* @param string $view The view to display.
|
||||
*/
|
||||
public function __construct( $view ) {
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displaying the table URL or regex. Depends on the current active tab.
|
||||
*
|
||||
* @param array $display Contextual display data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display( array $display = [] ) {
|
||||
$view_vars = $this->get_view_vars( $display );
|
||||
include WPSEO_PREMIUM_PATH . 'classes/redirect/views/redirects-tab-' . $this->view . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* The method to get the variables for the view. This method should return an array, because this will be extracted.
|
||||
*
|
||||
* @param array $passed_vars Optional. View data manually passed. Default empty array.
|
||||
*
|
||||
* @return array Contextual variables to pass to the view.
|
||||
*/
|
||||
abstract protected function get_view_vars( array $passed_vars = [] );
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Table_Presenter.
|
||||
*/
|
||||
class WPSEO_Redirect_Table_Presenter extends WPSEO_Redirect_Tab_Presenter {
|
||||
|
||||
/**
|
||||
* Gets the variables for the view.
|
||||
*
|
||||
* @param array $passed_vars Optional. View data manually passed. Default empty array.
|
||||
*
|
||||
* @return array Contextual variables to pass to the view.
|
||||
*/
|
||||
protected function get_view_vars( array $passed_vars = [] ) {
|
||||
$redirect_manager = new WPSEO_Redirect_Manager( $this->view );
|
||||
|
||||
return array_merge(
|
||||
$passed_vars,
|
||||
[
|
||||
'redirect_table' => new WPSEO_Redirect_Table(
|
||||
$this->view,
|
||||
$this->get_first_column_value(),
|
||||
$redirect_manager->get_redirects()
|
||||
),
|
||||
'origin_from_url' => $this->get_old_url(),
|
||||
'quick_edit_table' => new WPSEO_Redirect_Quick_Edit_Presenter(),
|
||||
'form_presenter' => new WPSEO_Redirect_Form_Presenter(
|
||||
[
|
||||
'origin_label_value' => $this->get_first_column_value(),
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old URL from the URL.
|
||||
*
|
||||
* @return string The old URL.
|
||||
*/
|
||||
private function get_old_url() {
|
||||
if ( isset( $_GET['old_url'] ) && is_string( $_GET['old_url'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We decode it before sanitization to keep encoded characters.
|
||||
$old_url = sanitize_text_field( rawurldecode( wp_unslash( $_GET['old_url'] ) ) );
|
||||
if ( ! empty( $old_url ) ) {
|
||||
check_admin_referer( 'wpseo_redirects-old-url', 'wpseo_premium_redirects_nonce' );
|
||||
|
||||
return esc_attr( $old_url );
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the first column based on the table type.
|
||||
*
|
||||
* @return string The value of the first column.
|
||||
*/
|
||||
private function get_first_column_value() {
|
||||
if ( $this->view === 'regex' ) {
|
||||
return __( 'Regular Expression', 'wordpress-seo-premium' );
|
||||
}
|
||||
|
||||
return __( 'Old URL', 'wordpress-seo-premium' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Ajax.
|
||||
*/
|
||||
class WPSEO_Redirect_Ajax {
|
||||
|
||||
/**
|
||||
* Instance of the WPSEO_Redirect_Manager instance.
|
||||
*
|
||||
* @var WPSEO_Redirect_Manager
|
||||
*/
|
||||
private $redirect_manager;
|
||||
|
||||
/**
|
||||
* Format of the redirect, might be plain or regex.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $redirect_format;
|
||||
|
||||
/**
|
||||
* Setting up the object by instantiate the redirect manager and setting the hooks.
|
||||
*
|
||||
* @param string $redirect_format The redirects format.
|
||||
*/
|
||||
public function __construct( $redirect_format ) {
|
||||
$this->redirect_manager = new WPSEO_Redirect_Manager( $redirect_format );
|
||||
$this->redirect_format = $redirect_format;
|
||||
|
||||
$this->set_hooks( $redirect_format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that handles the AJAX 'wpseo_add_redirect' action.
|
||||
*/
|
||||
public function ajax_add_redirect() {
|
||||
$this->valid_ajax_check();
|
||||
|
||||
// Save the redirect.
|
||||
$redirect = $this->get_redirect_from_post( 'redirect' );
|
||||
$this->validate( $redirect );
|
||||
|
||||
// The method always returns the added redirect.
|
||||
if ( $this->redirect_manager->create_redirect( $redirect ) ) {
|
||||
$response = [
|
||||
'origin' => $redirect->get_origin(),
|
||||
'target' => $redirect->get_target(),
|
||||
'type' => $redirect->get_type(),
|
||||
'info' => [
|
||||
'hasTrailingSlash' => WPSEO_Redirect_Util::requires_trailing_slash( $redirect->get_target() ),
|
||||
'isTargetRelative' => WPSEO_Redirect_Util::is_relative_url( $redirect->get_target() ),
|
||||
],
|
||||
];
|
||||
}
|
||||
else {
|
||||
// Set the value error.
|
||||
$error = [
|
||||
'type' => 'error',
|
||||
'message' => __( 'Unknown error. Failed to create redirect.', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$response = [ 'error' => $error ];
|
||||
}
|
||||
|
||||
// Response.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- WPCS bug/methods can't be whitelisted yet.
|
||||
wp_die( WPSEO_Utils::format_json_encode( $response ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that handles the AJAX 'wpseo_update_redirect' action.
|
||||
*/
|
||||
public function ajax_update_redirect() {
|
||||
|
||||
$this->valid_ajax_check();
|
||||
|
||||
$current_redirect = $this->get_redirect_from_post( 'old_redirect' );
|
||||
$new_redirect = $this->get_redirect_from_post( 'new_redirect' );
|
||||
$this->validate( $new_redirect, $current_redirect );
|
||||
|
||||
// The method always returns the added redirect.
|
||||
if ( $this->redirect_manager->update_redirect( $current_redirect, $new_redirect ) ) {
|
||||
$response = [
|
||||
'origin' => $new_redirect->get_origin(),
|
||||
'target' => $new_redirect->get_target(),
|
||||
'type' => $new_redirect->get_type(),
|
||||
];
|
||||
}
|
||||
else {
|
||||
// Set the value error.
|
||||
$error = [
|
||||
'type' => 'error',
|
||||
'message' => __( 'Unknown error. Failed to update redirect.', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$response = [ 'error' => $error ];
|
||||
}
|
||||
|
||||
// Response.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- WPCS bug/methods can't be whitelisted yet.
|
||||
wp_die( WPSEO_Utils::format_json_encode( $response ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the validation.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to save.
|
||||
* @param WPSEO_Redirect|null $current_redirect The current redirect.
|
||||
*/
|
||||
private function validate( WPSEO_Redirect $redirect, WPSEO_Redirect $current_redirect = null ) {
|
||||
$validator = new WPSEO_Redirect_Validator();
|
||||
|
||||
if ( $validator->validate( $redirect, $current_redirect ) === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ignore_warning = filter_input( INPUT_POST, 'ignore_warning' );
|
||||
|
||||
$error = $validator->get_error();
|
||||
|
||||
if ( $error->get_type() === 'error' || ( $error->get_type() === 'warning' && $ignore_warning === 'false' ) ) {
|
||||
wp_die(
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- WPCS bug/methods can't be whitelisted yet.
|
||||
WPSEO_Utils::format_json_encode( [ 'error' => $error->to_array() ] )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the AJAX hooks.
|
||||
*
|
||||
* @param string $hook_suffix The piece that will be stitched after the hooknames.
|
||||
*/
|
||||
private function set_hooks( $hook_suffix ) {
|
||||
// Add the new redirect.
|
||||
add_action( 'wp_ajax_wpseo_add_redirect_' . $hook_suffix, [ $this, 'ajax_add_redirect' ] );
|
||||
|
||||
// Update an existing redirect.
|
||||
add_action( 'wp_ajax_wpseo_update_redirect_' . $hook_suffix, [ $this, 'ajax_update_redirect' ] );
|
||||
|
||||
// Add URL response code check AJAX.
|
||||
if ( ! has_action( 'wp_ajax_wpseo_check_url' ) ) {
|
||||
add_action( 'wp_ajax_wpseo_check_url', [ $this, 'ajax_check_url' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the posted nonce is valid and if the user has the needed rights.
|
||||
*/
|
||||
private function valid_ajax_check() {
|
||||
// Check nonce.
|
||||
check_ajax_referer( 'wpseo-redirects-ajax-security', 'ajax_nonce' );
|
||||
|
||||
$this->permission_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current user is allowed to do what he's doing.
|
||||
*/
|
||||
private function permission_check() {
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
wp_die( '0' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redirect from the post values.
|
||||
*
|
||||
* @param string $post_value The key where the post values are located in the $_POST.
|
||||
*
|
||||
* @return WPSEO_Redirect
|
||||
*/
|
||||
private function get_redirect_from_post( $post_value ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification -- Reason: nonce is verified in ajax_update_redirect and ajax_add_redirect.
|
||||
if ( isset( $_POST[ $post_value ] ) && is_array( $_POST[ $post_value ] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification -- Reason: we want to stick to sanitize_url function, while the nonce has been already checked.
|
||||
$post_values = wp_unslash( $_POST[ $post_value ] );
|
||||
|
||||
return new WPSEO_Redirect(
|
||||
$this->sanitize_url( $post_values['origin'] ),
|
||||
$this->sanitize_url( $post_values['target'] ),
|
||||
urldecode( $post_values['type'] ),
|
||||
$this->redirect_format
|
||||
);
|
||||
}
|
||||
|
||||
return new WPSEO_Redirect( '', '', '', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the URL for displaying on the window.
|
||||
*
|
||||
* @param string $url The URL to sanitize.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sanitize_url( $url ) {
|
||||
return trim( htmlspecialchars_decode( rawurldecode( $url ) ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_File_Manager
|
||||
*/
|
||||
class WPSEO_Redirect_File_Util {
|
||||
|
||||
/**
|
||||
* Get the full path to the WPSEO redirect directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_dir() {
|
||||
$wp_upload_dir = wp_upload_dir();
|
||||
|
||||
return $wp_upload_dir['basedir'] . '/wpseo-redirects';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path to the redirect file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_file_path() {
|
||||
return self::get_dir() . '/.redirects';
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that creates the WPSEO redirect directory
|
||||
*/
|
||||
public static function create_upload_dir() {
|
||||
$basedir = self::get_dir();
|
||||
|
||||
// Create the Redirect file dir.
|
||||
if ( ! wp_mkdir_p( $basedir ) ) {
|
||||
Yoast_Notification_Center::get()->add_notification(
|
||||
new Yoast_Notification(
|
||||
/* translators: %s expands to the file path that we tried to write to */
|
||||
sprintf( __( "We're unable to create the directory %s", 'wordpress-seo-premium' ), $basedir ),
|
||||
[ 'type' => 'error' ]
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the .htaccess file.
|
||||
if ( ! file_exists( $basedir . '/.htaccess' ) ) {
|
||||
self::write_file( $basedir . '/.htaccess', "Options -Indexes\ndeny from all" );
|
||||
}
|
||||
|
||||
// Create an empty index.php file.
|
||||
if ( ! file_exists( $basedir . '/index.php' ) ) {
|
||||
self::write_file( $basedir . '/index.php', '<?php' . PHP_EOL . '// Silence is golden.' );
|
||||
}
|
||||
|
||||
// Create an empty redirect file.
|
||||
if ( ! file_exists( self::get_file_path() ) ) {
|
||||
self::write_file( self::get_file_path(), '' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper method for file_put_contents. Catches the result, if result is false add notification.
|
||||
*
|
||||
* @param string $file_path The path to write the content to.
|
||||
* @param string $file_content The content that will be saved.
|
||||
*
|
||||
* @return bool True on successful file write.
|
||||
*/
|
||||
public static function write_file( $file_path, $file_content ) {
|
||||
$has_written = false;
|
||||
if ( is_writable( dirname( $file_path ) ) ) {
|
||||
$has_written = file_put_contents( $file_path, $file_content );
|
||||
}
|
||||
|
||||
if ( $has_written === false ) {
|
||||
Yoast_Notification_Center::get()->add_notification(
|
||||
new Yoast_Notification(
|
||||
/* translators: %s expands to the file path that we tried to write to */
|
||||
sprintf( __( "We're unable to write data to the file %s", 'wordpress-seo-premium' ), $file_path ),
|
||||
[ 'type' => 'error' ]
|
||||
)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the object which will save the redirects file
|
||||
*
|
||||
* @param string $separate_file Saving the redirects in an separate apache file.
|
||||
*
|
||||
* @return WPSEO_Redirect_File_Exporter|null
|
||||
*/
|
||||
public static function get_file_exporter( $separate_file ) {
|
||||
// Create the correct file object.
|
||||
if ( WPSEO_Utils::is_apache() ) {
|
||||
if ( $separate_file === 'on' ) {
|
||||
return new WPSEO_Redirect_Apache_Exporter();
|
||||
}
|
||||
|
||||
return new WPSEO_Redirect_Htaccess_Exporter();
|
||||
}
|
||||
|
||||
if ( WPSEO_Utils::is_nginx() ) {
|
||||
return new WPSEO_Redirect_Nginx_Exporter();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class representing a list of redirect formats.
|
||||
*/
|
||||
class WPSEO_Redirect_Formats {
|
||||
|
||||
const PLAIN = 'plain';
|
||||
const REGEX = 'regex';
|
||||
|
||||
/**
|
||||
* Returns the redirect formats.
|
||||
*
|
||||
* @return string[] Array with the redirect formats.
|
||||
*/
|
||||
public function get() {
|
||||
return [
|
||||
self::PLAIN => __( 'Redirects', 'wordpress-seo-premium' ),
|
||||
self::REGEX => __( 'Regex Redirects', 'wordpress-seo-premium' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given value is a valid redirect format.
|
||||
*
|
||||
* @param string $value Value to check.
|
||||
*
|
||||
* @return bool True if a redirect format, false otherwise.
|
||||
*/
|
||||
public function has( $value ) {
|
||||
$formats = $this->get();
|
||||
|
||||
return isset( $formats[ $value ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for formatting redirects.
|
||||
*/
|
||||
class WPSEO_Redirect_Formatter {
|
||||
|
||||
/**
|
||||
* Formats a redirect into a executable redirect.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The original redirect.
|
||||
*
|
||||
* @return WPSEO_Executable_Redirect The executable redirect.
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
return new WPSEO_Executable_Redirect(
|
||||
$redirect->get_origin(),
|
||||
$redirect->get_target(),
|
||||
$redirect->get_type(),
|
||||
$redirect->get_format()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Htaccess
|
||||
*/
|
||||
class WPSEO_Redirect_Htaccess_Util {
|
||||
|
||||
/**
|
||||
* Clear the WPSEO added entries added in the .htaccess file
|
||||
*/
|
||||
public static function clear_htaccess_entries() {
|
||||
|
||||
$htaccess = '';
|
||||
if ( file_exists( self::get_htaccess_file_path() ) ) {
|
||||
$htaccess = file_get_contents( self::get_htaccess_file_path() );
|
||||
}
|
||||
|
||||
$cleaned = preg_replace( '`# BEGIN YOAST REDIRECTS.*# END YOAST REDIRECTS' . PHP_EOL . '`is', '', $htaccess );
|
||||
// If nothing changed, don't even try to save it.
|
||||
if ( $cleaned === $htaccess ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WPSEO_Redirect_File_Util::write_file( self::get_htaccess_file_path(), $cleaned );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path to the .htaccess file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_htaccess_file_path() {
|
||||
if ( ! function_exists( 'get_home_path' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
return get_home_path() . '.htaccess';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Import_Exception
|
||||
*/
|
||||
class WPSEO_Redirect_Import_Exception extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* This exporter class will import.
|
||||
*/
|
||||
class WPSEO_Redirect_Importer {
|
||||
|
||||
/**
|
||||
* Instance of the redirect option.
|
||||
*
|
||||
* @var WPSEO_Redirect_Option
|
||||
*/
|
||||
protected $redirect_option;
|
||||
|
||||
/**
|
||||
* Total amount of successfully imported redirects
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $total_imported = 0;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_Importer constructor.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WPSEO_Redirect_Option|null $redirect_option The redirect option.
|
||||
*/
|
||||
public function __construct( $redirect_option = null ) {
|
||||
if ( ! $redirect_option ) {
|
||||
$redirect_option = new WPSEO_Redirect_Option();
|
||||
}
|
||||
|
||||
$this->redirect_option = $redirect_option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the redirects and retrieves the import statistics.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to import.
|
||||
*
|
||||
* @return int[] The import statistics.
|
||||
*/
|
||||
public function import( array $redirects ) {
|
||||
array_walk( $redirects, [ $this, 'add_redirect' ] );
|
||||
|
||||
if ( $this->total_imported > 0 ) {
|
||||
$this->save_import();
|
||||
}
|
||||
|
||||
return [
|
||||
'total_redirects' => count( $redirects ),
|
||||
'total_imported' => $this->total_imported,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the redirects to the database and exports them to the necessary configuration file.
|
||||
*
|
||||
* @codeCoverageIgnore Because it contains dependencies
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function save_import() {
|
||||
$this->redirect_option->save();
|
||||
|
||||
// Export the redirects to .htaccess, Apache or NGinx configuration files depending on plugin settings.
|
||||
$redirect_manager = new WPSEO_Redirect_Manager();
|
||||
$redirect_manager->export_redirects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a redirect to the option.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to add.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_redirect( $redirect ) {
|
||||
if ( ! $this->redirect_option->add( $redirect ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
++$this->total_imported;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Manager.
|
||||
*/
|
||||
class WPSEO_Redirect_Manager {
|
||||
|
||||
/**
|
||||
* Model object to handle the redirects.
|
||||
*
|
||||
* @var WPSEO_Redirect_Option
|
||||
*/
|
||||
protected $redirect_option;
|
||||
|
||||
/**
|
||||
* The redirect format, this might be plain or regex.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirect_format;
|
||||
|
||||
/**
|
||||
* List of redirect exporters.
|
||||
*
|
||||
* @var WPSEO_Redirect_Exporter[]
|
||||
*/
|
||||
protected $exporters;
|
||||
|
||||
/**
|
||||
* Returns the default exporters.
|
||||
*
|
||||
* @return WPSEO_Redirect_Exporter[]
|
||||
*/
|
||||
public static function default_exporters() {
|
||||
$exporters = [ new WPSEO_Redirect_Option_Exporter() ];
|
||||
|
||||
if ( WPSEO_Options::get( 'disable_php_redirect' ) === 'on' ) {
|
||||
$file_exporter = WPSEO_Redirect_File_Util::get_file_exporter( WPSEO_Options::get( 'separate_file' ) );
|
||||
if ( isset( $file_exporter ) && $file_exporter instanceof WPSEO_Redirect_File_Exporter ) {
|
||||
$exporters[] = $file_exporter;
|
||||
}
|
||||
}
|
||||
|
||||
return $exporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the property with the redirects.
|
||||
*
|
||||
* @param string $redirect_format The format for the redirects.
|
||||
* @param WPSEO_Redirect_Exporter[]|null $exporters The exporters used to save redirects in files.
|
||||
* @param WPSEO_Redirect_Option|null $option Model object to handle the redirects.
|
||||
*/
|
||||
public function __construct( $redirect_format = WPSEO_Redirect_Formats::PLAIN, $exporters = null, WPSEO_Redirect_Option $option = null ) {
|
||||
if ( $option === null ) {
|
||||
$option = new WPSEO_Redirect_Option();
|
||||
}
|
||||
|
||||
$this->redirect_option = $option;
|
||||
$this->redirect_format = $redirect_format;
|
||||
$this->exporters = $exporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redirects.
|
||||
*
|
||||
* @return WPSEO_Redirect[]
|
||||
*/
|
||||
public function get_redirects() {
|
||||
// Filter the redirect for the current format.
|
||||
return array_filter( $this->redirect_option->get_all(), [ $this, 'filter_redirects_by_format' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all redirects.
|
||||
*
|
||||
* @return WPSEO_Redirect[]
|
||||
*/
|
||||
public function get_all_redirects() {
|
||||
return $this->redirect_option->get_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the redirects to the specified sources.
|
||||
*/
|
||||
public function export_redirects() {
|
||||
$redirects = $this->redirect_option->get_all();
|
||||
$exporters = $this->exporters;
|
||||
|
||||
if ( ! $exporters ) {
|
||||
$exporters = self::default_exporters();
|
||||
}
|
||||
|
||||
foreach ( $exporters as $exporter ) {
|
||||
$exporter->export( $redirects );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new redirect.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect object to add.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function create_redirect( WPSEO_Redirect $redirect ) {
|
||||
if ( $this->redirect_option->add( $redirect ) ) {
|
||||
$this->save_redirects();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the redirect.
|
||||
*
|
||||
* @param WPSEO_Redirect $current_redirect The old redirect, the value is a key in the redirects array.
|
||||
* @param WPSEO_Redirect $redirect New redirect object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update_redirect( WPSEO_Redirect $current_redirect, WPSEO_Redirect $redirect ) {
|
||||
if ( $this->redirect_option->update( $current_redirect, $redirect ) ) {
|
||||
$this->save_redirects();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the redirects.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $delete_redirects Array with the redirects to remove.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_redirects( $delete_redirects ) {
|
||||
$deleted = false;
|
||||
foreach ( $delete_redirects as $delete_redirect ) {
|
||||
if ( $this->redirect_option->delete( $delete_redirect ) ) {
|
||||
$deleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $deleted === true ) {
|
||||
$this->save_redirects();
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the redirect when it's found, otherwise it will return false.
|
||||
*
|
||||
* @param string $origin The origin to search for.
|
||||
*
|
||||
* @return bool|WPSEO_Redirect
|
||||
*/
|
||||
public function get_redirect( $origin ) {
|
||||
return $this->redirect_option->get( $origin );
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will save the redirect option and if necessary the redirect file.
|
||||
*/
|
||||
public function save_redirects() {
|
||||
// Update the database option.
|
||||
$this->redirect_option->save();
|
||||
|
||||
// Save the redirect file.
|
||||
$this->export_redirects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the redirects that don't match the needed format.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to filter.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function filter_redirects_by_format( WPSEO_Redirect $redirect ) {
|
||||
return $redirect->get_format() === $this->redirect_format;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class handling the redirect options.
|
||||
*/
|
||||
class WPSEO_Redirect_Option {
|
||||
|
||||
/**
|
||||
* The plain redirect option before 3.1.
|
||||
*/
|
||||
const OLD_OPTION_PLAIN = 'wpseo-premium-redirects';
|
||||
|
||||
/**
|
||||
* The regex redirect option before 3.1.
|
||||
*/
|
||||
const OLD_OPTION_REGEX = 'wpseo-premium-redirects-regex';
|
||||
|
||||
/**
|
||||
* The option which contains the redirects base.
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
const OPTION = 'wpseo-premium-redirects-base';
|
||||
|
||||
/**
|
||||
* The option which contains the plain redirects.
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
const OPTION_PLAIN = 'wpseo-premium-redirects-export-plain';
|
||||
|
||||
/**
|
||||
* The option which contains the regex redirects.
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
const OPTION_REGEX = 'wpseo-premium-redirects-export-regex';
|
||||
|
||||
/**
|
||||
* List of redirects.
|
||||
*
|
||||
* @var WPSEO_Redirect[]
|
||||
*/
|
||||
private $redirects = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param bool $retrieve_redirects Whether to retrieve the redirects on construction.
|
||||
*/
|
||||
public function __construct( $retrieve_redirects = true ) {
|
||||
if ( $retrieve_redirects ) {
|
||||
$this->redirects = $this->get_all();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the array with all the redirects.
|
||||
*
|
||||
* @return WPSEO_Redirect[]
|
||||
*/
|
||||
public function get_all() {
|
||||
$redirects = $this->get_from_option();
|
||||
|
||||
array_walk( $redirects, [ $this, 'map_option_to_object' ] );
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the old redirect doesn't exist already, if not it will be added.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect object to save.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function add( WPSEO_Redirect $redirect ) {
|
||||
if ( $this->search( $redirect->get_origin() ) === false ) {
|
||||
$this->run_redirects_modified_action( $redirect );
|
||||
|
||||
$this->redirects[] = $redirect;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the $current_redirect exists and remove it if so.
|
||||
*
|
||||
* @param WPSEO_Redirect $current_redirect The current redirect value.
|
||||
* @param WPSEO_Redirect $redirect The redirect object to save.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update( WPSEO_Redirect $current_redirect, WPSEO_Redirect $redirect ) {
|
||||
$found = $this->search( $current_redirect->get_origin() );
|
||||
if ( $found !== false ) {
|
||||
$this->run_redirects_modified_action( $redirect );
|
||||
|
||||
$this->redirects[ $found ] = $redirect;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given redirect from the array.
|
||||
*
|
||||
* @param WPSEO_Redirect $current_redirect The redirect that will be removed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( WPSEO_Redirect $current_redirect ) {
|
||||
$found = $this->search( $current_redirect->get_origin() );
|
||||
if ( $found !== false ) {
|
||||
$this->run_redirects_modified_action( $current_redirect );
|
||||
|
||||
unset( $this->redirects[ $found ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a redirect from the array.
|
||||
*
|
||||
* @param string $origin The redirects origin to search for.
|
||||
*
|
||||
* @return WPSEO_Redirect|bool
|
||||
*/
|
||||
public function get( $origin ) {
|
||||
$found = $this->search( $origin );
|
||||
if ( $found !== false ) {
|
||||
return $this->redirects[ $found ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the $origin already exists as a key in the array.
|
||||
*
|
||||
* @param string $origin The redirect to search for.
|
||||
*
|
||||
* @return int|bool
|
||||
*/
|
||||
public function search( $origin ) {
|
||||
foreach ( $this->redirects as $redirect_key => $redirect ) {
|
||||
if ( $redirect->origin_is( $origin ) ) {
|
||||
return $redirect_key;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving the redirects.
|
||||
*
|
||||
* @param bool $retry_upgrade Whether or not to retry the 3.1 upgrade. Used to prevent infinite recursion.
|
||||
*/
|
||||
public function save( $retry_upgrade = true ) {
|
||||
$redirects = $this->redirects;
|
||||
|
||||
// Retry the 3.1 upgrade routine to make sure we're always dealing with valid redirects.
|
||||
$upgrade_manager = new WPSEO_Upgrade_Manager();
|
||||
if ( $retry_upgrade && $upgrade_manager->should_retry_upgrade_31() ) {
|
||||
$upgrade_manager->retry_upgrade_31( true );
|
||||
$redirects = array_merge( $redirects, $this->get_all() );
|
||||
}
|
||||
|
||||
array_walk( $redirects, [ $this, 'map_object_to_option' ] );
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\save_redirects' - can be used to filter the redirects before saving.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api array $redirects
|
||||
*/
|
||||
$redirects = apply_filters( 'Yoast\WP\SEO\save_redirects', $redirects );
|
||||
|
||||
// Update the database option.
|
||||
update_option( self::OPTION, $redirects, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the redirects property.
|
||||
*
|
||||
* @param string $option_name The target option name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_from_option( $option_name = self::OPTION ) {
|
||||
$redirects = get_option( $option_name );
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\get_redirects' - can be used to filter the redirects on option retrieval.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api array $redirects
|
||||
*/
|
||||
$redirects = apply_filters( 'Yoast\WP\SEO\get_redirects', $redirects );
|
||||
|
||||
if ( ! is_array( $redirects ) ) {
|
||||
$redirects = [];
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the redirects modified hook with the altered redirect as input.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect that has been altered.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function run_redirects_modified_action( WPSEO_Redirect $redirect ) {
|
||||
/**
|
||||
* Filter: Yoast\WP\SEO\redirects_modified - Allow developers to run actions when the redirects are modified.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api string $origin The redirect origin.
|
||||
* @param string $target The redirect target.
|
||||
* @param int $type The redirect type (301, 404, 410, etc).
|
||||
*/
|
||||
do_action( 'Yoast\WP\SEO\redirects_modified', $redirect->get_origin(), $redirect->get_target(), $redirect->get_type() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the array values to a redirect object.
|
||||
*
|
||||
* @param array $redirect_values The data for the redirect option.
|
||||
*/
|
||||
private function map_option_to_object( array &$redirect_values ) {
|
||||
$redirect_values = new WPSEO_Redirect( $redirect_values['origin'], $redirect_values['url'], $redirect_values['type'], $redirect_values['format'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a redirect object to an array option.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to map.
|
||||
*/
|
||||
private function map_object_to_option( WPSEO_Redirect &$redirect ) {
|
||||
$redirect = [
|
||||
'origin' => $redirect->get_origin(),
|
||||
'url' => $redirect->get_target(),
|
||||
'type' => $redirect->get_type(),
|
||||
'format' => $redirect->get_format(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Page.
|
||||
*/
|
||||
class WPSEO_Redirect_Page {
|
||||
|
||||
/**
|
||||
* Constructing redirect module.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( is_admin() ) {
|
||||
$this->initialize_admin();
|
||||
}
|
||||
|
||||
// Only initialize the ajax for all tabs except settings.
|
||||
if ( wp_doing_ajax() ) {
|
||||
$this->initialize_ajax();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the presenter.
|
||||
*/
|
||||
public function display() {
|
||||
$display_args = [ 'current_tab' => $this->get_current_tab() ];
|
||||
|
||||
$redirect_presenter = new WPSEO_Redirect_Page_Presenter();
|
||||
$redirect_presenter->display( $display_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Catches possible posted filter values and redirects it to a GET-request.
|
||||
*
|
||||
* It catches:
|
||||
* A search post.
|
||||
* A redirect-type filter.
|
||||
*/
|
||||
public function list_table_search() {
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Variable is used in a strict comparison and sanitized by wp_safe_redirect anyway.
|
||||
$url = wp_unslash( $_SERVER['REQUEST_URI'] );
|
||||
$new_url = $this->extract_redirect_type_from_url( $url );
|
||||
$new_url = $this->extract_search_string_from_url( $new_url );
|
||||
|
||||
if ( $url !== $new_url ) {
|
||||
// Do the redirect.
|
||||
wp_safe_redirect( $new_url );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the redirect type from the passed URL.
|
||||
*
|
||||
* @param string $url The URL to try and extract the redirect type from.
|
||||
*
|
||||
* @return string The newly formatted URL. Returns original URL if filter is null.
|
||||
*/
|
||||
protected function extract_redirect_type_from_url( $url ) {
|
||||
if ( ( ! isset( $_POST['redirect-type'] ) ) || ( ! is_string( $_POST['redirect-type'] ) )
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in wp_verify_none.
|
||||
|| ! isset( $_POST['wpseo_redirects_ajax_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['wpseo_redirects_ajax_nonce'] ), 'wpseo-redirects-ajax-security' ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$filter = sanitize_text_field( wp_unslash( $_POST['redirect-type'] ) );
|
||||
|
||||
$new_url = remove_query_arg( 'redirect-type', $url );
|
||||
|
||||
if ( $filter !== '0' ) {
|
||||
$new_url = add_query_arg( 'redirect-type', rawurlencode( $filter ), $new_url );
|
||||
}
|
||||
|
||||
return $new_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the search string from the passed URL.
|
||||
*
|
||||
* @param string $url The URL to try and extract the search string from.
|
||||
*
|
||||
* @return string The newly formatted URL. Returns original URL if search string is null.
|
||||
*/
|
||||
protected function extract_search_string_from_url( $url ) {
|
||||
if ( ( ! isset( $_POST['s'] ) ) || ( ! is_string( $_POST['s'] ) )
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in wp_verify_none.
|
||||
|| ! isset( $_POST['wpseo_redirects_ajax_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['wpseo_redirects_ajax_nonce'] ), 'wpseo-redirects-ajax-security' ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$search_string = sanitize_text_field( wp_unslash( $_POST['s'] ) );
|
||||
|
||||
$new_url = remove_query_arg( 's', $url );
|
||||
|
||||
if ( $search_string !== '' ) {
|
||||
$new_url = add_query_arg( 's', rawurlencode( $search_string ), $new_url );
|
||||
}
|
||||
|
||||
return $new_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the admin redirects scripts.
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
$version = $asset_manager->flatten_version( WPSEO_PREMIUM_VERSION );
|
||||
|
||||
$dependencies = [
|
||||
'jquery',
|
||||
'jquery-ui-dialog',
|
||||
'wp-util',
|
||||
'underscore',
|
||||
'yoast-seo-premium-commons',
|
||||
'wp-api',
|
||||
'wp-api-fetch',
|
||||
];
|
||||
|
||||
wp_enqueue_script(
|
||||
'wp-seo-premium-admin-redirects',
|
||||
plugin_dir_url( WPSEO_PREMIUM_FILE )
|
||||
. 'assets/js/dist/wp-seo-premium-admin-redirects-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
$dependencies,
|
||||
WPSEO_PREMIUM_VERSION,
|
||||
true
|
||||
);
|
||||
wp_localize_script( 'wp-seo-premium-admin-redirects', 'wpseoPremiumStrings', WPSEO_Premium_Javascript_Strings::strings() );
|
||||
wp_localize_script( 'wp-seo-premium-admin-redirects', 'wpseoUserLocale', [ 'code' => substr( \get_user_locale(), 0, 2 ) ] );
|
||||
wp_localize_script( 'wp-seo-premium-admin-redirects', 'wpseoAdminRedirect', [ 'homeUrl' => home_url( '/' ) ] );
|
||||
wp_enqueue_style( 'wpseo-premium-redirects', plugin_dir_url( WPSEO_PREMIUM_FILE ) . 'assets/css/dist/premium-redirects-' . $version . '.css', [], WPSEO_PREMIUM_VERSION );
|
||||
|
||||
wp_enqueue_style( 'wp-jquery-ui-dialog' );
|
||||
|
||||
$screen_option_args = [
|
||||
'label' => __( 'Redirects per page', 'wordpress-seo-premium' ),
|
||||
'default' => 25,
|
||||
'option' => 'redirects_per_page',
|
||||
];
|
||||
add_screen_option( 'per_page', $screen_option_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch redirects_per_page.
|
||||
*
|
||||
* @param string|false $status The value to save instead of the option value.
|
||||
* Default false (to skip saving the current option).
|
||||
* @param string $option The option name where the value is set for.
|
||||
* @param string $value The new value for the screen option.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function set_screen_option( $status, $option, $value ) {
|
||||
if ( $option === 'redirects_per_page' ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that runs after the 'wpseo_redirect' option is updated.
|
||||
*
|
||||
* @param array $old_value Unused.
|
||||
* @param array $value The new saved values.
|
||||
*/
|
||||
public function save_redirect_files( $old_value, $value ) {
|
||||
|
||||
$is_php = ( empty( $value['disable_php_redirect'] ) || $value['disable_php_redirect'] !== 'on' );
|
||||
|
||||
$was_separate_file = ( ! empty( $old_value['separate_file'] ) && $old_value['separate_file'] === 'on' );
|
||||
$is_separate_file = ( ! empty( $value['separate_file'] ) && $value['separate_file'] === 'on' );
|
||||
|
||||
// Check if the 'disable_php_redirect' option set to true/on.
|
||||
if ( ! $is_php ) {
|
||||
// The 'disable_php_redirect' option is set to true(on) so we need to generate a file.
|
||||
// The Redirect Manager will figure out what file needs to be created.
|
||||
$redirect_manager = new WPSEO_Redirect_Manager();
|
||||
$redirect_manager->export_redirects();
|
||||
}
|
||||
|
||||
// Check if we need to remove the .htaccess redirect entries.
|
||||
if ( WPSEO_Utils::is_apache() ) {
|
||||
if ( $is_php || ( ! $was_separate_file && $is_separate_file ) ) {
|
||||
// Remove the apache redirect entries.
|
||||
WPSEO_Redirect_Htaccess_Util::clear_htaccess_entries();
|
||||
}
|
||||
|
||||
if ( $is_php || ( $was_separate_file && ! $is_separate_file ) ) {
|
||||
// Remove the apache separate file redirect entries.
|
||||
WPSEO_Redirect_File_Util::write_file( WPSEO_Redirect_File_Util::get_file_path(), '' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( WPSEO_Utils::is_nginx() && $is_php ) {
|
||||
// Remove the nginx redirect entries.
|
||||
$this->clear_nginx_redirects();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The server should always be apache. And the php redirects have to be enabled or in case of a separate
|
||||
* file it should be disabled.
|
||||
*
|
||||
* @param bool $disable_php_redirect Are the php redirects disabled.
|
||||
* @param bool $separate_file Value of the separate file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function remove_htaccess_entries( $disable_php_redirect, $separate_file ) {
|
||||
return ( WPSEO_Utils::is_apache() && ( ! $disable_php_redirect || ( $disable_php_redirect && $separate_file ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the redirects from the nginx config.
|
||||
*/
|
||||
private function clear_nginx_redirects() {
|
||||
$redirect_file = WPSEO_Redirect_File_Util::get_file_path();
|
||||
if ( is_writable( $redirect_file ) ) {
|
||||
WPSEO_Redirect_File_Util::write_file( $redirect_file, '' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize admin hooks.
|
||||
*/
|
||||
private function initialize_admin() {
|
||||
$this->fetch_bulk_action();
|
||||
|
||||
// Check if we need to save files after updating options.
|
||||
add_action( 'update_option_wpseo_redirect', [ $this, 'save_redirect_files' ], 10, 2 );
|
||||
|
||||
// Convert post into get on search and loading the page scripts.
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended -- We're not manipulating the value.
|
||||
if ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) && wp_unslash( $_GET['page'] ) === 'wpseo_redirects' ) {
|
||||
$upgrade_manager = new WPSEO_Upgrade_Manager();
|
||||
$upgrade_manager->retry_upgrade_31();
|
||||
|
||||
add_action( 'admin_init', [ $this, 'list_table_search' ] );
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
add_filter( 'set-screen-option', [ $this, 'set_screen_option' ], 11, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the AJAX redirect files.
|
||||
*/
|
||||
private function initialize_ajax() {
|
||||
// Normal Redirect AJAX.
|
||||
new WPSEO_Redirect_Ajax( WPSEO_Redirect_Formats::PLAIN );
|
||||
|
||||
// Regex Redirect AJAX.
|
||||
new WPSEO_Redirect_Ajax( WPSEO_Redirect_Formats::REGEX );
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the current active tab.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_current_tab() {
|
||||
static $current_tab;
|
||||
|
||||
if ( $current_tab === null ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We're not manipulating the value.
|
||||
if ( isset( $_GET['tab'] ) && is_string( $_GET['tab'] )
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended -- value sanitized in the if body, regex filters unwanted values.
|
||||
&& in_array( wp_unslash( $_GET['tab'] ), [ WPSEO_Redirect_Formats::PLAIN, WPSEO_Redirect_Formats::REGEX, 'settings' ], true ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- the regex takes care of filtering out unwanted values.
|
||||
$current_tab = sanitize_text_field( wp_unslash( $_GET['tab'] ) );
|
||||
}
|
||||
else {
|
||||
$current_tab = WPSEO_Redirect_Formats::PLAIN;
|
||||
}
|
||||
}
|
||||
|
||||
return $current_tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting redirect manager, based on the current active tab.
|
||||
*
|
||||
* @return WPSEO_Redirect_Manager
|
||||
*/
|
||||
private function get_redirect_manager() {
|
||||
static $redirect_manager;
|
||||
|
||||
if ( $redirect_manager === null ) {
|
||||
$redirects_format = WPSEO_Redirect_Formats::PLAIN;
|
||||
if ( $this->get_current_tab() === WPSEO_Redirect_Formats::REGEX ) {
|
||||
$redirects_format = WPSEO_Redirect_Formats::REGEX;
|
||||
}
|
||||
|
||||
$redirect_manager = new WPSEO_Redirect_Manager( $redirects_format );
|
||||
}
|
||||
|
||||
return $redirect_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the bulk action for removing redirects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function fetch_bulk_action() {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in wp_verify_none.
|
||||
if ( ! isset( $_POST['wpseo_redirects_ajax_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['wpseo_redirects_ajax_nonce'] ), 'wpseo-redirects-ajax-security' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We're just strictly comparing the value.
|
||||
if ( ( ! isset( $_POST['action'] ) || ! is_string( $_POST['action'] ) || ! wp_unslash( $_POST['action'] ) === 'delete' )
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We're just strictly comparing the value.
|
||||
&& ( ! isset( $_POST['action2'] ) || ! is_string( $_POST['action2'] ) || ! wp_unslash( $_POST['action2'] ) === 'delete' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST['wpseo_redirects_bulk_delete'] ) || ! is_array( $_POST['wpseo_redirects_bulk_delete'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Array elements are sanitized one by one in the foreach loop.
|
||||
$bulk_delete = wp_unslash( $_POST['wpseo_redirects_bulk_delete'] );
|
||||
$redirects = [];
|
||||
foreach ( $bulk_delete as $origin ) {
|
||||
$redirect = $this->get_redirect_manager()->get_redirect( $origin );
|
||||
if ( $redirect !== false ) {
|
||||
$redirects[] = $redirect;
|
||||
}
|
||||
}
|
||||
$this->get_redirect_manager()->delete_redirects( $redirects );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirects
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the filter for removing redirected entries from the sitemaps.
|
||||
*/
|
||||
class WPSEO_Redirect_Sitemap_Filter implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* URL to the homepage.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $home_url;
|
||||
|
||||
/**
|
||||
* Constructs the object.
|
||||
*
|
||||
* @param string $home_url The home url.
|
||||
*/
|
||||
public function __construct( $home_url ) {
|
||||
$this->home_url = $home_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'wpseo_sitemap_entry', [ $this, 'filter_sitemap_entry' ] );
|
||||
add_action( 'Yoast\WP\SEO\redirects_modified', [ $this, 'clear_sitemap_cache' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents a redirected URL from being added to the sitemap.
|
||||
*
|
||||
* @param array $url The url data.
|
||||
*
|
||||
* @return bool|array False when entry will be redirected.
|
||||
*/
|
||||
public function filter_sitemap_entry( $url ) {
|
||||
if ( empty( $url['loc'] ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$entry_location = str_replace( $this->home_url, '', $url['loc'] );
|
||||
|
||||
if ( $this->is_redirect( $entry_location ) !== false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the sitemap cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_sitemap_cache() {
|
||||
WPSEO_Sitemaps_Cache::clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given entry location already exists as a redirect.
|
||||
*
|
||||
* @param string $entry_location The entry location.
|
||||
*
|
||||
* @return bool Whether the entry location exists as a redirect.
|
||||
*/
|
||||
protected function is_redirect( $entry_location ) {
|
||||
static $redirects = null;
|
||||
|
||||
if ( $redirects === null ) {
|
||||
$redirects = new WPSEO_Redirect_Option();
|
||||
}
|
||||
|
||||
return $redirects->search( $entry_location ) !== false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Table.
|
||||
*/
|
||||
class WPSEO_Redirect_Table extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* List of all redirects.
|
||||
*
|
||||
* @var WPSEO_Redirect[]
|
||||
*/
|
||||
public $items;
|
||||
|
||||
/**
|
||||
* List containing redirect filter parameters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $filter = [
|
||||
'redirect_type' => null,
|
||||
'search_string' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* The name of the first column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $current_column;
|
||||
|
||||
/**
|
||||
* The primary column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $primary_column = 'type';
|
||||
|
||||
/**
|
||||
* Caches the WPSEO_Redirect_Types::get() result.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $redirect_types;
|
||||
|
||||
/**
|
||||
* Holds the orderby.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $orderby = 'old';
|
||||
|
||||
/**
|
||||
* Holds the order.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $order = 'asc';
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_Table constructor.
|
||||
*
|
||||
* @param array|string $type Type of the redirects that is opened.
|
||||
* @param string $current_column The value of the first column.
|
||||
* @param WPSEO_Redirect[] $redirects The redirects.
|
||||
*/
|
||||
public function __construct( $type, $current_column, $redirects ) {
|
||||
parent::__construct( [ 'plural' => $type ] );
|
||||
|
||||
$this->current_column = $current_column;
|
||||
|
||||
// Cache used in filter_items and extra_tablenav.
|
||||
$wpseo_redirect_types = new WPSEO_Redirect_Types();
|
||||
$this->redirect_types = $wpseo_redirect_types->get();
|
||||
|
||||
$this->set_items( $redirects );
|
||||
|
||||
add_filter( 'list_table_primary_column', [ $this, 'redirect_list_table_primary_column' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the extra table navigation.
|
||||
*
|
||||
* @param string $which Which tablenav is called.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function extra_tablenav( $which ) {
|
||||
if ( $which !== 'top' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$selected = $this->filter['redirect_type'];
|
||||
if ( $selected === null ) {
|
||||
$selected = 0;
|
||||
}
|
||||
?>
|
||||
<div class="alignleft actions">
|
||||
<label for="filter-by-redirect" class="screen-reader-text">
|
||||
<?php
|
||||
/* translators: Hidden accessibility text. */
|
||||
esc_html_e( 'Filter by redirect type', 'wordpress-seo-premium' );
|
||||
?>
|
||||
</label>
|
||||
<select name="redirect-type" id="filter-by-redirect">
|
||||
<option<?php selected( $selected, 0 ); ?> value="0"><?php esc_html_e( 'All redirect types', 'wordpress-seo-premium' ); ?></option>
|
||||
<?php
|
||||
foreach ( $this->redirect_types as $http_code => $redirect_type ) {
|
||||
printf(
|
||||
"<option %s value='%s'>%s</option>\n",
|
||||
selected( $selected, $http_code, false ),
|
||||
esc_attr( $http_code ),
|
||||
esc_html( $redirect_type )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php submit_button( __( 'Filter', 'wordpress-seo-premium' ), '', 'filter_action', false, [ 'id' => 'post-query-submit' ] ); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table columns.
|
||||
*
|
||||
* @return string[] The table columns.
|
||||
*/
|
||||
public function get_columns() {
|
||||
return [
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'type' => _x( 'Type', 'noun', 'wordpress-seo-premium' ),
|
||||
'old' => $this->current_column,
|
||||
'new' => __( 'New URL', 'wordpress-seo-premium' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the total columns for the table.
|
||||
*
|
||||
* @return int The total amount of columns.
|
||||
*/
|
||||
public function count_columns() {
|
||||
return count( $this->get_columns() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for setting the primary table column.
|
||||
*
|
||||
* @param string $column The current column.
|
||||
* @param string $screen The current opened window.
|
||||
*
|
||||
* @return string The primary table column.
|
||||
*/
|
||||
public function redirect_list_table_primary_column( $column, $screen ) {
|
||||
if ( $screen === 'seo_page_wpseo_redirects' ) {
|
||||
$column = $this->primary_column;
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the table variables, fetch the items from the database, search, sort and format the items.
|
||||
* Sets the items as the WPSEO_Redirect_Table items variable.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepare_items() {
|
||||
// Setup the columns.
|
||||
$this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ];
|
||||
|
||||
// Get variables needed for pagination.
|
||||
$per_page = $this->get_items_per_page( 'redirects_per_page', 25 );
|
||||
$total_items = count( $this->items );
|
||||
$pagination_args = [
|
||||
'total_items' => $total_items,
|
||||
'total_pages' => ceil( $total_items / $per_page ),
|
||||
'per_page' => $per_page,
|
||||
];
|
||||
|
||||
// Set pagination.
|
||||
$this->set_pagination_args( $pagination_args );
|
||||
|
||||
$current_page = $this->get_pagenum();
|
||||
|
||||
// Setting the starting point. If starting point is below 1, overwrite it with value 0, otherwise it will be sliced of at the back.
|
||||
$slice_start = ( $current_page - 1 );
|
||||
if ( $slice_start < 0 ) {
|
||||
$slice_start = 0;
|
||||
}
|
||||
|
||||
// Apply 'pagination'.
|
||||
$formatted_items = array_slice( $this->items, ( $slice_start * $per_page ), $per_page );
|
||||
|
||||
// Set items.
|
||||
$this->items = $formatted_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the columns that are sortable.
|
||||
*
|
||||
* @return array[] An array containing the sortable columns.
|
||||
*/
|
||||
public function get_sortable_columns() {
|
||||
return [
|
||||
'old' => [ 'old', false ],
|
||||
'new' => [ 'new', false ],
|
||||
'type' => [ 'type', false ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the items based on user input.
|
||||
*
|
||||
* @param array $a The current sort direction.
|
||||
* @param array $b The new sort direction.
|
||||
*
|
||||
* @return int The order that should be used.
|
||||
*/
|
||||
public function do_reorder( $a, $b ) {
|
||||
// Determine sort order.
|
||||
$result = strcmp( $a[ $this->orderby ], $b[ $this->orderby ] );
|
||||
|
||||
// Send final sort direction to usort.
|
||||
return ( $this->order === 'asc' ) ? $result : ( -$result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a column for a checkbox.
|
||||
*
|
||||
* @param array $item Array with the row data.
|
||||
*
|
||||
* @return string The column with a checkbox.
|
||||
*/
|
||||
public function column_cb( $item ) {
|
||||
return sprintf(
|
||||
'<label class="screen-reader-text" for="wpseo-redirects-bulk-cb-%2$s">%3$s</label> <input type="checkbox" name="wpseo_redirects_bulk_delete[]" id="wpseo-redirects-bulk-cb-%2$s" value="%1$s" />',
|
||||
esc_attr( $item['old'] ),
|
||||
$item['row_number'],
|
||||
/* translators: Hidden accessibility text. */
|
||||
esc_html( __( 'Select this redirect', 'wordpress-seo-premium' ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a default column.
|
||||
*
|
||||
* @param array $item Array with the row data.
|
||||
* @param string $column_name The name of the needed column.
|
||||
*
|
||||
* @return string The default column.
|
||||
*/
|
||||
public function column_default( $item, $column_name ) {
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$is_regex = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) && sanitize_text_field( wp_unslash( $_GET['tab'] ) ) === 'regex';
|
||||
$row_actions = $this->get_row_actions( $column_name );
|
||||
|
||||
switch ( $column_name ) {
|
||||
case 'new':
|
||||
$classes = [ 'val' ];
|
||||
$new_url = $item['new'];
|
||||
$new_full_url = home_url( $new_url );
|
||||
if ( ! $is_regex && WPSEO_Redirect_Util::requires_trailing_slash( $new_url ) ) {
|
||||
$classes[] = 'has-trailing-slash';
|
||||
}
|
||||
|
||||
if (
|
||||
$new_url === ''
|
||||
|| $new_url === '/'
|
||||
|| ! WPSEO_Redirect_Util::is_relative_url( $new_url )
|
||||
) {
|
||||
$classes[] = 'remove-slashes';
|
||||
}
|
||||
|
||||
if ( $new_url ) {
|
||||
return '<a class="' . esc_attr( implode( ' ', $classes ) ) . '" href="' . esc_url( $new_full_url ) . '" target="_blank">' . esc_html( $new_url ) . '</a>' . $row_actions;
|
||||
}
|
||||
return '<div class="val remove-slashes"></div>' . $row_actions;
|
||||
|
||||
case 'old':
|
||||
$classes = '';
|
||||
$old_full_url = home_url( $item['old'] );
|
||||
if ( $is_regex === true ) {
|
||||
return '<div class="val remove-slashes">' . esc_html( $item['old'] ) . '</div>' . $row_actions;
|
||||
}
|
||||
|
||||
return '<a class="val' . $classes . '" href="' . esc_url( $old_full_url ) . '" target="_blank">' . esc_html( $item['old'] ) . '</a>' . $row_actions;
|
||||
|
||||
case 'type':
|
||||
return '<div class="val type">' . esc_html( $item['type'] ) . '</div>' . $row_actions;
|
||||
|
||||
default:
|
||||
return $item[ $column_name ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available bulk actions.
|
||||
*
|
||||
* @return string[] Array containing the available bulk actions.
|
||||
*/
|
||||
public function get_bulk_actions() {
|
||||
return [
|
||||
'delete' => __( 'Delete', 'wordpress-seo-premium' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the items and orders them.
|
||||
*
|
||||
* @param array $items The data that will be showed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function set_items( $items ) {
|
||||
// Getting the items.
|
||||
$this->items = $this->filter_items( $items );
|
||||
|
||||
$this->format_items();
|
||||
|
||||
// Sort the results.
|
||||
if ( count( $this->items ) > 0 ) {
|
||||
$this->orderby = $this->get_orderby();
|
||||
$this->order = $this->get_order();
|
||||
usort( $this->items, [ $this, 'do_reorder' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the given items.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $items The items to filter.
|
||||
*
|
||||
* @return array The filtered items.
|
||||
*/
|
||||
private function filter_items( array $items ) {
|
||||
$search_string = '';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We come from our own redirect in a simple search form, let's not overcomplicate.
|
||||
if ( isset( $_GET['s'] ) && is_string( $_GET['s'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: Same as above.
|
||||
$search_string = trim( sanitize_text_field( wp_unslash( $_GET['s'] ) ), '/' );
|
||||
}
|
||||
if ( $search_string !== '' ) {
|
||||
$this->filter['search_string'] = $search_string;
|
||||
|
||||
$items = array_filter( $items, [ $this, 'filter_by_search_string' ] );
|
||||
}
|
||||
|
||||
$redirect_type = 0;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We come from our own redirect in a simple filter form, let's not overcomplicate.
|
||||
if ( isset( $_GET['redirect-type'] ) && is_string( $_GET['redirect-type'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Cast to an integer and strictly compared against known keys.
|
||||
$redirect_type = (int) wp_unslash( $_GET['redirect-type'] );
|
||||
$redirect_type = array_key_exists( $redirect_type, $this->redirect_types ) ? $redirect_type : 0;
|
||||
}
|
||||
if ( $redirect_type !== 0 ) {
|
||||
$this->filter['redirect_type'] = $redirect_type;
|
||||
|
||||
$items = array_filter( $items, [ $this, 'filter_by_type' ] );
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the items.
|
||||
*/
|
||||
private function format_items() {
|
||||
// Format the data.
|
||||
$formatted_items = [];
|
||||
|
||||
$counter = 1;
|
||||
|
||||
foreach ( $this->items as $redirect ) {
|
||||
$formatted_items[] = [
|
||||
'old' => $redirect->get_origin(),
|
||||
'new' => $redirect->get_target(),
|
||||
'type' => $redirect->get_type(),
|
||||
'row_number' => $counter,
|
||||
];
|
||||
|
||||
++$counter;
|
||||
}
|
||||
|
||||
$this->items = $formatted_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the redirect by entered search string.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to filter.
|
||||
*
|
||||
* @return bool True when the search strings match.
|
||||
*/
|
||||
private function filter_by_search_string( WPSEO_Redirect $redirect ) {
|
||||
return ( stripos( $redirect->get_origin(), $this->filter['search_string'] ) !== false || stripos( $redirect->get_target(), $this->filter['search_string'] ) !== false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the redirect by redirect type.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to filter.
|
||||
*
|
||||
* @return bool True when type matches redirect type.
|
||||
*/
|
||||
private function filter_by_type( WPSEO_Redirect $redirect ) {
|
||||
return $redirect->get_type() === $this->filter['redirect_type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* The old column actions.
|
||||
*
|
||||
* @param string $column The column name to verify.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_row_actions( $column ) {
|
||||
if ( $column === $this->primary_column ) {
|
||||
$actions = [
|
||||
'edit' => '<a href="#" role="button" class="redirect-edit">' . __( 'Edit', 'wordpress-seo-premium' ) . '</a>',
|
||||
'trash' => '<a href="#" role="button" class="redirect-delete">' . __( 'Delete', 'wordpress-seo-premium' ) . '</a>',
|
||||
];
|
||||
|
||||
return $this->row_actions( $actions );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and display row actions links for the list table.
|
||||
*
|
||||
* We override the parent class method to avoid doubled buttons to be printed out.
|
||||
*
|
||||
* @param object $item The item being acted upon.
|
||||
* @param string $column_name Current column name.
|
||||
* @param string $primary Primary column name.
|
||||
*
|
||||
* @return string Empty string.
|
||||
*/
|
||||
protected function handle_row_actions( $item, $column_name, $primary ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the orderby from the request.
|
||||
*
|
||||
* @return string The orderby value.
|
||||
*/
|
||||
private function get_orderby() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: WP list table is not using a nonce.
|
||||
if ( isset( $_GET['orderby'] ) && is_string( $_GET['orderby'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: same as above and we are strictly comparing the values.
|
||||
$orderby = wp_unslash( $_GET['orderby'] );
|
||||
if ( array_key_exists( $orderby, $this->get_sortable_columns() ) ) {
|
||||
return $orderby;
|
||||
}
|
||||
}
|
||||
|
||||
return 'old';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the order from the request.
|
||||
*
|
||||
* @return string The order value.
|
||||
*/
|
||||
private function get_order() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: WP list table is not using a nonce.
|
||||
if ( isset( $_GET['order'] ) && is_string( $_GET['order'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: same as above and we are strictly comparing the values.
|
||||
$order = wp_unslash( $_GET['order'] );
|
||||
if ( in_array( $order, [ 'asc', 'desc' ], true ) ) {
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
|
||||
return 'asc';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class representing a list of redirect types.
|
||||
*/
|
||||
class WPSEO_Redirect_Types {
|
||||
|
||||
const TEMPORARY = 307;
|
||||
const UNAVAILABLE = 451;
|
||||
const DELETED = 410;
|
||||
const FOUND = 302;
|
||||
const PERMANENT = 301;
|
||||
|
||||
/**
|
||||
* Returns the redirect types.
|
||||
*
|
||||
* @return string[] Array with the redirect types.
|
||||
*/
|
||||
public function get() {
|
||||
$redirect_types = [
|
||||
'301' => __( '301 Moved Permanently', 'wordpress-seo-premium' ),
|
||||
'302' => __( '302 Found', 'wordpress-seo-premium' ),
|
||||
'307' => __( '307 Temporary Redirect', 'wordpress-seo-premium' ),
|
||||
'410' => __( '410 Content Deleted', 'wordpress-seo-premium' ),
|
||||
'451' => __( '451 Unavailable For Legal Reasons', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\redirect_types' - can be used to filter the redirect types.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @api array $redirect_types
|
||||
*/
|
||||
return apply_filters( 'Yoast\WP\SEO\redirect_types', $redirect_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given value is a valid redirect type.
|
||||
*
|
||||
* @param string $value Value to check.
|
||||
*
|
||||
* @return bool True if a redirect type, false otherwise.
|
||||
*/
|
||||
public function has( $value ) {
|
||||
$types = $this->get();
|
||||
|
||||
return isset( $types[ $value ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Manager.
|
||||
*/
|
||||
class WPSEO_Redirect_Upgrade {
|
||||
|
||||
/**
|
||||
* Lookup table for previous redirect format constants to their current counterparts.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $redirect_option_names = [
|
||||
WPSEO_Redirect_Option::OLD_OPTION_PLAIN => WPSEO_Redirect_Formats::PLAIN,
|
||||
WPSEO_Redirect_Option::OLD_OPTION_REGEX => WPSEO_Redirect_Formats::REGEX,
|
||||
];
|
||||
|
||||
/**
|
||||
* Upgrade routine from Yoast SEO premium 1.2.0.
|
||||
*/
|
||||
public static function upgrade_1_2_0() {
|
||||
$redirect_option = self::get_redirect_option();
|
||||
$redirects = [];
|
||||
|
||||
foreach ( self::$redirect_option_names as $redirect_option_name => $redirect_format ) {
|
||||
$old_redirects = $redirect_option->get_from_option( $redirect_option_name );
|
||||
|
||||
foreach ( $old_redirects as $origin => $redirect ) {
|
||||
// Check if the redirect is not an array yet.
|
||||
if ( ! is_array( $redirect ) ) {
|
||||
$redirects[] = new WPSEO_Redirect( $origin, $redirect['url'], $redirect['type'], $redirect_format );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::import_redirects( $redirects );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if redirects should be imported from the free version.
|
||||
*
|
||||
* @since 2.3
|
||||
*/
|
||||
public static function import_redirects_2_3() {
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery -- Upgrade routine, so rarely used, therefore not an issue.
|
||||
$wp_query = new WP_Query( 'post_type=any&meta_key=_yoast_wpseo_redirect&order=ASC' );
|
||||
|
||||
if ( ! empty( $wp_query->posts ) ) {
|
||||
$redirects = [];
|
||||
|
||||
foreach ( $wp_query->posts as $post ) {
|
||||
|
||||
$old_url = '/' . $post->post_name . '/';
|
||||
$new_url = get_post_meta( $post->ID, '_yoast_wpseo_redirect', true );
|
||||
|
||||
// Create redirect.
|
||||
$redirects[] = new WPSEO_Redirect( $old_url, $new_url, 301, WPSEO_Redirect_Formats::PLAIN );
|
||||
|
||||
// Remove post meta value.
|
||||
delete_post_meta( $post->ID, '_yoast_wpseo_redirect' );
|
||||
}
|
||||
|
||||
self::import_redirects( $redirects );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade routine to merge plain and regex redirects in a single option.
|
||||
*/
|
||||
public static function upgrade_3_1() {
|
||||
$redirects = [];
|
||||
|
||||
foreach ( self::$redirect_option_names as $redirect_option_name => $redirect_format ) {
|
||||
$old_redirects = get_option( $redirect_option_name, [] );
|
||||
|
||||
foreach ( $old_redirects as $origin => $redirect ) {
|
||||
// Only when URL and type is set.
|
||||
if ( array_key_exists( 'url', $redirect ) && array_key_exists( 'type', $redirect ) ) {
|
||||
$redirects[] = new WPSEO_Redirect( $origin, $redirect['url'], $redirect['type'], $redirect_format );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Saving the redirects to the option.
|
||||
self::import_redirects( $redirects, [ new WPSEO_Redirect_Option_Exporter() ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the redirects to htaccess or nginx file if needed.
|
||||
*/
|
||||
public static function upgrade_13_0() {
|
||||
$redirect_manager = new WPSEO_Redirect_Manager();
|
||||
$redirect_manager->export_redirects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports an array of redirect objects.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects.
|
||||
* @param WPSEO_Redirect_Exporter[]|null $exporters The exporters.
|
||||
*/
|
||||
private static function import_redirects( $redirects, $exporters = null ) {
|
||||
if ( empty( $redirects ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$redirect_option = self::get_redirect_option();
|
||||
$redirect_manager = new WPSEO_Redirect_Manager( null, $exporters, $redirect_option );
|
||||
|
||||
foreach ( $redirects as $redirect ) {
|
||||
$redirect_option->add( $redirect );
|
||||
}
|
||||
|
||||
$redirect_option->save( false );
|
||||
$redirect_manager->export_redirects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and caches the redirect option.
|
||||
*
|
||||
* @return WPSEO_Redirect_Option
|
||||
*/
|
||||
private static function get_redirect_option() {
|
||||
static $redirect_option;
|
||||
|
||||
if ( empty( $redirect_option ) ) {
|
||||
$redirect_option = new WPSEO_Redirect_Option( false );
|
||||
}
|
||||
|
||||
return $redirect_option;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class representing a formatter for an URL.
|
||||
*/
|
||||
class WPSEO_Redirect_Url_Formatter {
|
||||
|
||||
/**
|
||||
* The URL to format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url = '';
|
||||
|
||||
/**
|
||||
* Sets the URL used for formatting.
|
||||
*
|
||||
* @param string $url The URL to format.
|
||||
*/
|
||||
public function __construct( $url ) {
|
||||
$this->url = $this->sanitize_url( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to strip the subdirectory from the redirect url.
|
||||
*
|
||||
* @param string $home_url The URL to use as the base.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format_without_subdirectory( $home_url ) {
|
||||
$subdirectory = $this->get_subdirectory( $home_url );
|
||||
|
||||
if ( ! empty( $subdirectory ) ) {
|
||||
$subdirectory = trailingslashit( $subdirectory );
|
||||
$path_position = strpos( $this->url, $subdirectory );
|
||||
if ( $path_position === 0 ) {
|
||||
return '/' . $this->sanitize_url( substr( $this->url, strlen( $subdirectory ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
return '/' . $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the slashes at the beginning of an url.
|
||||
*
|
||||
* @param string $url The URL to sanitize.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitize_url( $url ) {
|
||||
return ltrim( $url, '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subdirectory from the given URL.
|
||||
*
|
||||
* @param string $url The URL to get the subdirectory for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_subdirectory( $url ) {
|
||||
$path = wp_parse_url( $url, PHP_URL_PATH );
|
||||
if ( is_string( $path ) ) {
|
||||
return $this->sanitize_url( $path );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helpers for redirects.
|
||||
*/
|
||||
class WPSEO_Redirect_Util {
|
||||
|
||||
/**
|
||||
* Whether or not the permalink contains a trailing slash.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $has_permalink_trailing_slash = null;
|
||||
|
||||
/**
|
||||
* Returns whether or not a URL is a relative URL.
|
||||
*
|
||||
* @param string $url The URL to determine the relativity for.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_relative_url( $url ) {
|
||||
$url_scheme = wp_parse_url( $url, PHP_URL_SCHEME );
|
||||
|
||||
return ! $url_scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the permalink structure has a trailing slash.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_permalink_trailing_slash() {
|
||||
if ( self::$has_permalink_trailing_slash === null ) {
|
||||
$permalink_structure = get_option( 'permalink_structure' );
|
||||
|
||||
self::$has_permalink_trailing_slash = substr( $permalink_structure, -1 ) === '/';
|
||||
}
|
||||
|
||||
return self::$has_permalink_trailing_slash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the URL has query variables.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_query_parameters( $url ) {
|
||||
return strpos( $url, '?' ) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given URL has a fragment identifier.
|
||||
*
|
||||
* @param string $url The URL to parse.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_fragment_identifier( $url ) {
|
||||
// Deal with this case if the last character is a hash.
|
||||
if ( substr( $url, -1 ) === '#' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$fragment = wp_parse_url( $url, PHP_URL_FRAGMENT );
|
||||
|
||||
return ! empty( $fragment );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given URL has an extension.
|
||||
*
|
||||
* @param string $url The URL to parse.
|
||||
*
|
||||
* @return bool Whether or not the given URL has an extension.
|
||||
*/
|
||||
public static function has_extension( $url ) {
|
||||
$parsed = wp_parse_url( $url, PHP_URL_PATH );
|
||||
|
||||
return ( is_string( $parsed ) && strpos( $parsed, '.' ) !== false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a target URL requires a trailing slash.
|
||||
*
|
||||
* @param string $target_url The target URL to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function requires_trailing_slash( $target_url ) {
|
||||
return $target_url !== '/'
|
||||
&& self::has_permalink_trailing_slash()
|
||||
&& self::is_relative_url( $target_url )
|
||||
&& ! self::has_query_parameters( $target_url )
|
||||
&& ! self::has_fragment_identifier( $target_url )
|
||||
&& ! self::has_extension( $target_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the base url path from the given URL.
|
||||
*
|
||||
* @param string $base_url The base URL that will be stripped.
|
||||
* @param string $url URL to remove the path from.
|
||||
*
|
||||
* @return string The URL without the base url
|
||||
*/
|
||||
public static function strip_base_url_path_from_url( $base_url, $url ) {
|
||||
$base_url_path = wp_parse_url( $base_url, PHP_URL_PATH );
|
||||
if ( ! empty( $base_url_path ) ) {
|
||||
$base_url_path = ltrim( $base_url_path, '/' );
|
||||
}
|
||||
|
||||
if ( empty( $base_url_path ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$url = ltrim( $url, '/' );
|
||||
|
||||
// When the url doesn't begin with the base url path.
|
||||
if ( stripos( trailingslashit( $url ), trailingslashit( $base_url_path ) ) !== 0 ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return substr( $url, strlen( $base_url_path ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect
|
||||
*/
|
||||
|
||||
/**
|
||||
* The validation class.
|
||||
*/
|
||||
class WPSEO_Redirect_Validator {
|
||||
|
||||
/**
|
||||
* List containing all possible validation rules.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $validation_rules = [
|
||||
'relative-origin' => [
|
||||
'validation_class' => 'WPSEO_Redirect_Relative_Origin_Validation',
|
||||
'exclude_types' => [],
|
||||
'exclude_format' => [ WPSEO_Redirect_Formats::REGEX ],
|
||||
],
|
||||
'self-redirect' => [
|
||||
'validation_class' => 'WPSEO_Redirect_Self_Redirect_Validation',
|
||||
'exclude_types' => [],
|
||||
'exclude_format' => [ WPSEO_Redirect_Formats::REGEX ],
|
||||
],
|
||||
'uniqueness' => [
|
||||
'validation_class' => 'WPSEO_Redirect_Uniqueness_Validation',
|
||||
'exclude_types' => [],
|
||||
'exclude_format' => [],
|
||||
],
|
||||
'presence' => [
|
||||
'validation_class' => 'WPSEO_Redirect_Presence_Validation',
|
||||
'exclude_types' => [],
|
||||
'exclude_format' => [],
|
||||
],
|
||||
'subdirectory-presence' => [
|
||||
'validation_class' => 'WPSEO_Redirect_Subdirectory_Validation',
|
||||
'exclude_types' => [],
|
||||
'exclude_format' => [],
|
||||
],
|
||||
'accessible' => [
|
||||
'validation_class' => 'WPSEO_Redirect_Accessible_Validation',
|
||||
'exclude_types' => [ WPSEO_Redirect_Types::DELETED, WPSEO_Redirect_Types::UNAVAILABLE ],
|
||||
'exclude_format' => [ WPSEO_Redirect_Formats::REGEX ],
|
||||
],
|
||||
'endpoint' => [
|
||||
'validation_class' => 'WPSEO_Redirect_Endpoint_Validation',
|
||||
'exclude_types' => [ WPSEO_Redirect_Types::DELETED, WPSEO_Redirect_Types::UNAVAILABLE ],
|
||||
'exclude_format' => [ WPSEO_Redirect_Formats::REGEX ],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* A string holding a possible redirect validation error.
|
||||
*
|
||||
* @var bool|string The validation error.
|
||||
*/
|
||||
protected $validation_error = false;
|
||||
|
||||
/**
|
||||
* Validates the old and the new URL.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect that will be saved.
|
||||
* @param WPSEO_Redirect|null $current_redirect Redirect that will be used for comparison.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate( WPSEO_Redirect $redirect, WPSEO_Redirect $current_redirect = null ) {
|
||||
|
||||
$validators = $this->get_validations( $this->get_filtered_validation_rules( $this->validation_rules, $redirect ) );
|
||||
$redirects = $this->get_redirects( $redirect->get_format() );
|
||||
|
||||
$this->validation_error = '';
|
||||
foreach ( $validators as $validator ) {
|
||||
if ( ! $validator->run( $redirect, $current_redirect, $redirects ) ) {
|
||||
$this->validation_error = $validator->get_error();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the validation error.
|
||||
*
|
||||
* @return WPSEO_Validation_Result
|
||||
*/
|
||||
public function get_error() {
|
||||
return $this->validation_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a rule from the validations.
|
||||
*
|
||||
* @param array $validations Array with the validations.
|
||||
* @param string $rule_to_remove The rule that will be removed.
|
||||
*/
|
||||
protected function remove_rule( &$validations, $rule_to_remove ) {
|
||||
if ( array_key_exists( $rule_to_remove, $validations ) ) {
|
||||
unset( $validations[ $rule_to_remove ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the validation rules.
|
||||
*
|
||||
* @param array $validations Array with validation rules.
|
||||
* @param WPSEO_Redirect $redirect The redirect that will be saved.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_filtered_validation_rules( array $validations, WPSEO_Redirect $redirect ) {
|
||||
foreach ( $validations as $validation => $validation_rules ) {
|
||||
$exclude_format = in_array( $redirect->get_format(), $validation_rules['exclude_format'], true );
|
||||
$exclude_type = in_array( $redirect->get_type(), $validation_rules['exclude_types'], true );
|
||||
|
||||
if ( $exclude_format || $exclude_type ) {
|
||||
$this->remove_rule( $validations, $validation );
|
||||
}
|
||||
}
|
||||
|
||||
return $validations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the validations based on the set validation rules.
|
||||
*
|
||||
* @param array $validation_rules The rules for the validations that will be run.
|
||||
*
|
||||
* @return WPSEO_Redirect_Validation[]
|
||||
*/
|
||||
protected function get_validations( $validation_rules ) {
|
||||
$validations = [];
|
||||
foreach ( $validation_rules as $validation_rule ) {
|
||||
$validations[] = new $validation_rule['validation_class']();
|
||||
}
|
||||
|
||||
return $validations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the redirect property.
|
||||
*
|
||||
* @param string $format The format for the redirects.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_redirects( $format ) {
|
||||
$redirect_manager = new WPSEO_Redirect_Manager( $format );
|
||||
|
||||
// Format the redirects.
|
||||
$redirects = [];
|
||||
foreach ( $redirect_manager->get_all_redirects() as $redirect ) {
|
||||
$redirects[ $redirect->get_origin() ] = $redirect->get_target();
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
}
|
||||
350
wp/plugins/wordpress-seo-premium/classes/redirect/redirect.php
Normal file
350
wp/plugins/wordpress-seo-premium/classes/redirect/redirect.php
Normal file
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Home_Url_Helper;
|
||||
|
||||
/**
|
||||
* Represents a single redirect
|
||||
*/
|
||||
class WPSEO_Redirect implements ArrayAccess {
|
||||
|
||||
/**
|
||||
* Redirect origin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $origin;
|
||||
|
||||
/**
|
||||
* Redirect target.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $target = '';
|
||||
|
||||
/**
|
||||
* A HTTP code determining the redirect type.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* A string determining the redirect format (plain or regex).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $format;
|
||||
|
||||
/**
|
||||
* A string holding a possible redirect validation error.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $validation_error;
|
||||
|
||||
/**
|
||||
* The home URL helper.
|
||||
*
|
||||
* @var Home_Url_Helper
|
||||
*/
|
||||
protected static $home_url;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect constructor.
|
||||
*
|
||||
* @param string $origin The origin of the redirect.
|
||||
* @param string $target The target of the redirect.
|
||||
* @param int $type The type of the redirect.
|
||||
* @param string $format The format of the redirect.
|
||||
*/
|
||||
public function __construct( $origin, $target = '', $type = WPSEO_Redirect_Types::PERMANENT, $format = WPSEO_Redirect_Formats::PLAIN ) {
|
||||
if ( static::$home_url === null ) {
|
||||
static::$home_url = new Home_Url_Helper();
|
||||
}
|
||||
|
||||
$this->origin = ( $format === WPSEO_Redirect_Formats::PLAIN ) ? $this->sanitize_origin_url( $origin ) : $origin;
|
||||
$this->target = $this->sanitize_target_url( $target );
|
||||
$this->format = $format;
|
||||
$this->type = (int) $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the origin.
|
||||
*
|
||||
* @return string The set origin.
|
||||
*/
|
||||
public function get_origin() {
|
||||
return $this->origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target
|
||||
*
|
||||
* @return string The set target.
|
||||
*/
|
||||
public function get_target() {
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type
|
||||
*
|
||||
* @return int The set type.
|
||||
*/
|
||||
public function get_type() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the format
|
||||
*
|
||||
* @return string The set format.
|
||||
*/
|
||||
public function get_format() {
|
||||
return $this->format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a offset exists.
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
*
|
||||
* @param string $offset An offset to check for.
|
||||
*
|
||||
* @return bool True on success or false on failure.
|
||||
* The return value will be cast to boolean if non-boolean was returned.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetExists( $offset ) {
|
||||
return in_array( $offset, [ 'url', 'type' ], true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve.
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
*
|
||||
* @param string $offset The offset to retrieve.
|
||||
*
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetGet( $offset ) {
|
||||
switch ( $offset ) {
|
||||
case 'old':
|
||||
return $this->origin;
|
||||
|
||||
case 'url':
|
||||
return $this->target;
|
||||
|
||||
case 'type':
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set.
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
*
|
||||
* @param string $offset The offset to assign the value to.
|
||||
* @param string $value The value to set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetSet( $offset, $value ) {
|
||||
switch ( $offset ) {
|
||||
case 'url':
|
||||
$this->target = $value;
|
||||
break;
|
||||
case 'type':
|
||||
$this->type = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset.
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $offset The offset to unset.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetUnset( $offset ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares an URL with the origin of the redirect.
|
||||
*
|
||||
* @param string $url The URL to compare.
|
||||
*
|
||||
* @return bool True when url matches the origin.
|
||||
*/
|
||||
public function origin_is( $url ) {
|
||||
// Sanitize the slash in case of plain redirect.
|
||||
if ( $this->format === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
$url = $this->sanitize_slash( $url, $this->parse_url( $url ) );
|
||||
}
|
||||
|
||||
return (string) $this->origin === (string) $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the trailing slashes for relative URLs.
|
||||
*
|
||||
* @param string $url_to_sanitize The URL to sanitize.
|
||||
* @param array $url_pieces The url pieces.
|
||||
*
|
||||
* @return string The sanitized url.
|
||||
*/
|
||||
private function sanitize_slash( $url_to_sanitize, array $url_pieces = [] ) {
|
||||
$url = $url_to_sanitize;
|
||||
if ( $url !== '/' && ! isset( $url_pieces['scheme'] ) ) {
|
||||
return trim( $url_to_sanitize, '/' );
|
||||
}
|
||||
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the protocol from the URL.
|
||||
*
|
||||
* @param string $scheme The scheme to strip.
|
||||
* @param string $url The URL to remove the scheme from.
|
||||
*
|
||||
* @return string The url without the scheme.
|
||||
*/
|
||||
private function strip_scheme_from_url( $scheme, $url ) {
|
||||
return str_replace( $scheme . '://', '', $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the home URL from the redirect to ensure that relative URLs are created.
|
||||
*
|
||||
* @param string $url The URL to sanitize.
|
||||
*
|
||||
* @return string The sanitized url.
|
||||
*/
|
||||
private function sanitize_origin_url( $url ) {
|
||||
$home_url = static::$home_url->get();
|
||||
$home_url_pieces = static::$home_url->get_parsed();
|
||||
$url_pieces = $this->parse_url( $url );
|
||||
|
||||
if ( $this->match_home_url( $home_url_pieces, $url_pieces ) ) {
|
||||
$url = substr(
|
||||
$this->strip_scheme_from_url( $url_pieces['scheme'], $url ),
|
||||
strlen( $this->strip_scheme_from_url( $home_url_pieces['scheme'], $home_url ) )
|
||||
);
|
||||
|
||||
$url_pieces['scheme'] = null;
|
||||
}
|
||||
|
||||
return $this->sanitize_slash( $url, $url_pieces );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the target url.
|
||||
*
|
||||
* @param string $url The url to sanitize.
|
||||
*
|
||||
* @return string The sanitized url.
|
||||
*/
|
||||
private function sanitize_target_url( $url ) {
|
||||
$home_url_pieces = static::$home_url->get_parsed();
|
||||
$url_pieces = $this->parse_url( $url );
|
||||
|
||||
if ( $this->match_home_url( $home_url_pieces, $url_pieces ) ) {
|
||||
$url = substr(
|
||||
$this->strip_scheme_from_url( $url_pieces['scheme'], $url ),
|
||||
strlen( $home_url_pieces['host'] )
|
||||
);
|
||||
|
||||
$url_pieces['scheme'] = null;
|
||||
}
|
||||
|
||||
return $this->sanitize_slash( $url, $url_pieces );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the URL matches the home URL.
|
||||
*
|
||||
* @param array $home_url_pieces The pieces (wp_parse_url) from the home_url.
|
||||
* @param array $url_pieces The pieces (wp_parse_url) from the url to match.
|
||||
*
|
||||
* @return bool True when the URL matches the home URL.
|
||||
*/
|
||||
private function match_home_url( $home_url_pieces, $url_pieces ) {
|
||||
if ( ! isset( $url_pieces['scheme'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $url_pieces['host'] ) || ! $this->match_home_url_host( $home_url_pieces['host'], $url_pieces['host'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $home_url_pieces['path'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isset( $url_pieces['path'] ) && $this->match_home_url_path( $home_url_pieces['path'], $url_pieces['path'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the URL matches the home URL by comparing their host.
|
||||
*
|
||||
* @param string $home_url_host The home URL host.
|
||||
* @param string $url_host The URL host.
|
||||
*
|
||||
* @return bool True when both hosts are equal.
|
||||
*/
|
||||
private function match_home_url_host( $home_url_host, $url_host ) {
|
||||
return $url_host === $home_url_host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the URL matches the home URL by comparing their path.
|
||||
*
|
||||
* @param string $home_url_path The home URL path.
|
||||
* @param string $url_path The URL path.
|
||||
*
|
||||
* @return bool True when the home URL path is empty or when the URL path begins with the home URL path.
|
||||
*/
|
||||
private function match_home_url_path( $home_url_path, $url_path ) {
|
||||
$home_url_path = trim( $home_url_path, '/' );
|
||||
if ( empty( $home_url_path ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return strpos( trim( $url_path, '/' ), $home_url_path ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the URL into separate pieces.
|
||||
*
|
||||
* @param string $url The URL string.
|
||||
*
|
||||
* @return array Array of URL pieces.
|
||||
*/
|
||||
private function parse_url( $url ) {
|
||||
$parsed_url = wp_parse_url( $url );
|
||||
if ( is_array( $parsed_url ) ) {
|
||||
return $parsed_url;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for validating redirects
|
||||
*/
|
||||
abstract class WPSEO_Redirect_Abstract_Validation implements WPSEO_Redirect_Validation {
|
||||
|
||||
/**
|
||||
* The validation error.
|
||||
*
|
||||
* @var WPSEO_Validation_Result
|
||||
*/
|
||||
private $error = null;
|
||||
|
||||
/**
|
||||
* Returns the validation error.
|
||||
*
|
||||
* @return WPSEO_Validation_Result|null
|
||||
*/
|
||||
public function get_error() {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the validation error.
|
||||
*
|
||||
* @param WPSEO_Validation_Result $error Validation error or warning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_error( WPSEO_Validation_Result $error ) {
|
||||
$this->error = $error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates the accessibility of a redirect's target.
|
||||
*/
|
||||
class WPSEO_Redirect_Accessible_Validation extends WPSEO_Redirect_Abstract_Validation {
|
||||
|
||||
/**
|
||||
* Validates if the target is accessible and based on its response code it will set a warning (if applicable).
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to validate.
|
||||
* @param WPSEO_Redirect|null $old_redirect The old redirect to compare.
|
||||
* @param array|null $redirects Unused.
|
||||
*
|
||||
* @return bool Whether or not the target is valid.
|
||||
*/
|
||||
public function run( WPSEO_Redirect $redirect, WPSEO_Redirect $old_redirect = null, array $redirects = null ) {
|
||||
// Do the request.
|
||||
$target = $this->parse_target( $redirect->get_target() );
|
||||
$decoded_url = rawurldecode( $target );
|
||||
$response = $this->remote_head( $decoded_url, [ 'sslverify' => false ] );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = __( 'The URL you entered could not be resolved.', 'wordpress-seo-premium' );
|
||||
$this->set_error( new WPSEO_Validation_Warning( $error, 'target' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$response_code = $this->retrieve_response_code( $response );
|
||||
|
||||
// Check if the target is a temporary location.
|
||||
if ( $this->is_temporary( $response_code ) ) {
|
||||
/* translators: %1$s expands to the returned http code */
|
||||
$error = __( 'The URL you are redirecting to seems to return a %1$s status. You might want to check if the target can be reached manually before saving.', 'wordpress-seo-premium' );
|
||||
$error = sprintf( $error, $response_code );
|
||||
$this->set_error( new WPSEO_Validation_Warning( $error, 'target' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the response code is 301.
|
||||
if ( $response_code === 301 ) {
|
||||
$error = __( 'You\'re redirecting to a target that returns a 301 HTTP code (permanently moved). Make sure the target you specify is directly reachable.', 'wordpress-seo-premium' );
|
||||
$this->set_error( new WPSEO_Validation_Warning( $error, 'target' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $response_code !== 200 ) {
|
||||
/* translators: %1$s expands to the returned http code */
|
||||
$error = __( 'The URL you entered returned a HTTP code different than 200(OK). The received HTTP code is %1$s.', 'wordpress-seo-premium' );
|
||||
$error = sprintf( $error, $response_code );
|
||||
$this->set_error( new WPSEO_Validation_Warning( $error, 'target' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the response code from the response array.
|
||||
*
|
||||
* @param array $response The response.
|
||||
*
|
||||
* @return int The response code.
|
||||
*/
|
||||
protected function retrieve_response_code( $response ) {
|
||||
return wp_remote_retrieve_response_code( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a HEAD request to the passed remote URL.
|
||||
*
|
||||
* @param string $url The URL to send the request to.
|
||||
* @param array $options The options to send along with the request.
|
||||
*
|
||||
* @return array|WP_Error The response or WP_Error if something goes wrong.
|
||||
*/
|
||||
protected function remote_head( $url, $options = [] ) {
|
||||
return wp_remote_head( $url, $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given response code is a temporary one.
|
||||
*
|
||||
* @param int $response_code The response code to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_temporary( $response_code ) {
|
||||
return in_array( $response_code, [ 302, 307 ], true ) || in_array( substr( $response_code, 0, 2 ), [ '40', '50' ], true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the target is relative, if so just parse a full URL.
|
||||
*
|
||||
* @param string $target The target to parse.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parse_target( $target ) {
|
||||
$scheme = wp_parse_url( $target, PHP_URL_SCHEME );
|
||||
|
||||
// If we have an absolute url return it.
|
||||
if ( ! empty( $scheme ) ) {
|
||||
return $target;
|
||||
}
|
||||
|
||||
// Removes the installation directory if present.
|
||||
$target = WPSEO_Redirect_Util::strip_base_url_path_from_url( $this->get_home_url(), $target );
|
||||
|
||||
// If we have a relative url make it absolute.
|
||||
$absolute = get_home_url( null, $target );
|
||||
|
||||
// If the path does not end with an extension then add a trailing slash.
|
||||
if ( WPSEO_Redirect_Util::requires_trailing_slash( $target ) ) {
|
||||
return trailingslashit( $absolute );
|
||||
}
|
||||
|
||||
return $absolute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the home url.
|
||||
*
|
||||
* @return string The home url.
|
||||
*/
|
||||
protected function get_home_url() {
|
||||
return home_url();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates the endpoint of a redirect
|
||||
*/
|
||||
class WPSEO_Redirect_Endpoint_Validation extends WPSEO_Redirect_Abstract_Validation {
|
||||
|
||||
/**
|
||||
* List of redirects.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $redirects;
|
||||
|
||||
/**
|
||||
* This validation checks if the redirect being created, follows:
|
||||
* - a path that results in a redirection to it's own origin due to other redirects pointing to the current origin.
|
||||
* - a path that can be shorten by creating a direct redirect.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to validate.
|
||||
* @param WPSEO_Redirect|null $old_redirect The old redirect to compare.
|
||||
* @param array|null $redirects Array with redirect to validate against.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function run( WPSEO_Redirect $redirect, WPSEO_Redirect $old_redirect = null, array $redirects = null ) {
|
||||
$this->redirects = $redirects;
|
||||
|
||||
$origin = $redirect->get_origin();
|
||||
$target = $redirect->get_target();
|
||||
$endpoint = $this->search_end_point( $target, $origin );
|
||||
|
||||
// Check for a redirect loop.
|
||||
if ( is_string( $endpoint ) && in_array( $endpoint, [ $origin, $target ], true ) ) {
|
||||
$error = __( 'The redirect you are trying to save will create a redirect loop. This means there probably already exists a redirect that points to the origin of the redirect you are trying to save', 'wordpress-seo-premium' );
|
||||
$this->set_error( new WPSEO_Validation_Error( $error, [ 'origin', 'target' ] ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_string( $endpoint ) && $target !== $endpoint ) {
|
||||
/* translators: %1$s: will be the target, %2$s: will be the found endpoint. */
|
||||
$error = __( '%1$s will be redirected to %2$s. Maybe it\'s worth considering to create a direct redirect to %2$s.', 'wordpress-seo-premium' );
|
||||
$error = sprintf( $error, $target, $endpoint );
|
||||
$this->set_error( new WPSEO_Validation_Warning( $error, 'target' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will check if the $new_url is redirected also and follows the trace of this redirect
|
||||
*
|
||||
* @param string $new_url The new URL to search for.
|
||||
* @param string $old_url The current URL that is redirected.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
private function search_end_point( $new_url, $old_url ) {
|
||||
$new_target = $this->find_url( $new_url );
|
||||
if ( $new_target !== false ) {
|
||||
// Unset the redirects, because it was found already.
|
||||
unset( $this->redirects[ $new_url ] );
|
||||
|
||||
if ( $new_url !== $old_url ) {
|
||||
$traced_target = $this->search_end_point( $new_target, $old_url );
|
||||
if ( $traced_target !== false ) {
|
||||
return $traced_target;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_target;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the given $url and returns it target
|
||||
*
|
||||
* @param string $url The URL to search for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function find_url( $url ) {
|
||||
if ( ! empty( $this->redirects[ $url ] ) ) {
|
||||
return $this->redirects[ $url ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates that all redirect fields have been correctly filled.
|
||||
*/
|
||||
class WPSEO_Redirect_Presence_Validation extends WPSEO_Redirect_Abstract_Validation {
|
||||
|
||||
/**
|
||||
* Validates if the redirect has all the required fields.
|
||||
* - For a 410 and 451 type redirect the target isn't necessary.
|
||||
* - For all other redirect types the target is required.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to validate.
|
||||
* @param WPSEO_Redirect|null $old_redirect The old redirect to compare.
|
||||
* @param array|null $redirects Unused.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function run( WPSEO_Redirect $redirect, WPSEO_Redirect $old_redirect = null, array $redirects = null ) {
|
||||
// If redirect type is 410 or 451, the target doesn't have to be filled.
|
||||
if ( $this->allow_empty_target( $redirect->get_type() ) && $redirect->get_origin() !== '' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ( $redirect->get_origin() !== '' && $redirect->get_target() !== '' && $redirect->get_type() !== '' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$error = __( 'Not all the required fields are filled.', 'wordpress-seo-premium' );
|
||||
$this->set_error( new WPSEO_Validation_Error( $error ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows an empty target when the given redirect type matches one of the values in the array.
|
||||
*
|
||||
* @param string $redirect_type The type to match.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function allow_empty_target( $redirect_type ) {
|
||||
$allowed_redirect_types = [ WPSEO_Redirect_Types::DELETED, WPSEO_Redirect_Types::UNAVAILABLE ];
|
||||
|
||||
return in_array( (int) $redirect_type, $allowed_redirect_types, true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates if the origin is a relative URL.
|
||||
*/
|
||||
class WPSEO_Redirect_Relative_Origin_Validation extends WPSEO_Redirect_Abstract_Validation {
|
||||
|
||||
/**
|
||||
* Validate the redirect to check if the origin URL is relative.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to validate.
|
||||
* @param WPSEO_Redirect|null $old_redirect The old redirect to compare.
|
||||
* @param array|null $redirects Array with redirects to validate against.
|
||||
*
|
||||
* @return bool True if the redirect is valid, false otherwise.
|
||||
*/
|
||||
public function run( WPSEO_Redirect $redirect, WPSEO_Redirect $old_redirect = null, array $redirects = null ) {
|
||||
if ( WPSEO_Redirect_Util::is_relative_url( $redirect->get_origin() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$error = __( 'The old URL for your redirect is not relative. Only the new URL is allowed to be absolute. Make sure to provide a relative old URL.', 'wordpress-seo-premium' );
|
||||
$this->set_error( new WPSEO_Validation_Warning( $error, 'origin' ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validator for validating that the redirect doesn't point to itself.
|
||||
*/
|
||||
class WPSEO_Redirect_Self_Redirect_Validation extends WPSEO_Redirect_Abstract_Validation {
|
||||
|
||||
/**
|
||||
* Validate the redirect to check if it doesn't point to itself.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to validate.
|
||||
* @param WPSEO_Redirect|null $old_redirect The old redirect to compare.
|
||||
* @param array|null $redirects Array with redirect to validate against.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function run( WPSEO_Redirect $redirect, WPSEO_Redirect $old_redirect = null, array $redirects = null ) {
|
||||
|
||||
if ( $redirect->get_origin() === $redirect->get_target() ) {
|
||||
$error = __( 'You are attempting to redirect to the same URL as the origin. Please choose a different URL to redirect to.', 'wordpress-seo-premium' );
|
||||
$this->set_error( new WPSEO_Validation_Error( $error, 'origin' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates if the origin starts with the subdirectory where the WordPress installation is in.
|
||||
*/
|
||||
class WPSEO_Redirect_Subdirectory_Validation extends WPSEO_Redirect_Abstract_Validation {
|
||||
|
||||
/**
|
||||
* Validate the redirect to check if the origin already exists.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to validate.
|
||||
* @param WPSEO_Redirect|null $old_redirect The old redirect to compare.
|
||||
* @param array|null $redirects Array with redirects to validate against.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function run( WPSEO_Redirect $redirect, WPSEO_Redirect $old_redirect = null, array $redirects = null ) {
|
||||
|
||||
$subdirectory = $this->get_subdirectory();
|
||||
|
||||
// When there is no subdirectory, there is nothing to validate.
|
||||
if ( $subdirectory === '' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// When the origin starts with subdirectory, it is okay.
|
||||
if ( $this->origin_starts_with_subdirectory( $subdirectory, $redirect->get_origin() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* translators: %1$s expands to the subdirectory WordPress is installed. */
|
||||
$error = __( 'Your redirect is missing the subdirectory where WordPress is installed in. This will result in a redirect that won\'t work. Make sure the redirect starts with %1$s', 'wordpress-seo-premium' );
|
||||
$error = sprintf( $error, '<code>' . $subdirectory . '</code>' );
|
||||
$this->set_error( new WPSEO_Validation_Warning( $error, 'origin' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subdirectory if applicable.
|
||||
*
|
||||
* Calculates the difference between the home and site url. It strips of the site_url from the home_url and returns
|
||||
* the part that remains.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_subdirectory() {
|
||||
$home_url = untrailingslashit( home_url() );
|
||||
$site_url = untrailingslashit( site_url() );
|
||||
if ( $home_url === $site_url ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Strips the site_url from the home_url. substr is used because we want it from the start.
|
||||
$encoding = get_bloginfo( 'charset' );
|
||||
return mb_substr( $home_url, mb_strlen( $site_url, $encoding ), null, $encoding );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the origin starts with the given subdirectory. If so, the origin must start with the subdirectory.
|
||||
*
|
||||
* @param string $subdirectory The subdirectory that should be present.
|
||||
* @param string $origin The origin to check for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function origin_starts_with_subdirectory( $subdirectory, $origin ) {
|
||||
// Strip slashes at the beginning because the origin doesn't start with a slash.
|
||||
$subdirectory = ltrim( $subdirectory, '/' );
|
||||
|
||||
if ( strstr( $origin, $subdirectory ) ) {
|
||||
return substr( $origin, 0, strlen( $subdirectory ) ) === $subdirectory;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates the uniqueness of a redirect.
|
||||
*/
|
||||
class WPSEO_Redirect_Uniqueness_Validation extends WPSEO_Redirect_Abstract_Validation {
|
||||
|
||||
/**
|
||||
* Validates if the redirect already exists as a redirect.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to validate.
|
||||
* @param WPSEO_Redirect|null $old_redirect The old redirect to compare.
|
||||
* @param array|null $redirects Array with redirect to validate against.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function run( WPSEO_Redirect $redirect, WPSEO_Redirect $old_redirect = null, array $redirects = null ) {
|
||||
|
||||
// Remove uniqueness validation when old origin is the same as the current one.
|
||||
if ( is_a( $old_redirect, 'WPSEO_Redirect' ) && $redirect->get_origin() === $old_redirect->get_origin() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( array_key_exists( $redirect->get_origin(), $redirects ) ) {
|
||||
$this->set_error(
|
||||
new WPSEO_Validation_Error(
|
||||
__( 'The old URL already exists as a redirect.', 'wordpress-seo-premium' ),
|
||||
'origin'
|
||||
)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validate interface for the validation classes.
|
||||
*/
|
||||
interface WPSEO_Redirect_Validation {
|
||||
|
||||
/**
|
||||
* Validates the redirect.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to validate.
|
||||
* @param WPSEO_Redirect|null $old_redirect The old redirect to compare.
|
||||
* @param array|null $redirects Array with redirect to validate against.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function run( WPSEO_Redirect $redirect, WPSEO_Redirect $old_redirect = null, array $redirects = null );
|
||||
|
||||
/**
|
||||
* Returns the validation error.
|
||||
*
|
||||
* @return WPSEO_Validation_Result|null
|
||||
*/
|
||||
public function get_error();
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Views
|
||||
*
|
||||
* @uses array $display_vars {
|
||||
* @type string origin_label_value The label that reflects the origin of the redirect: Old URL or Regular Expression.
|
||||
* @type array redirect_types The redirect types to show in the drop-down menu.
|
||||
* @type string input_suffix The input suffix.
|
||||
* @type array values The pre-filled values for the input fields.
|
||||
* }
|
||||
*/
|
||||
|
||||
$yoast_seo_origin_label_value = $display_vars['origin_label_value'];
|
||||
$yoast_seo_redirect_types = $display_vars['redirect_types'];
|
||||
$yoast_seo_input_suffix = $display_vars['input_suffix'];
|
||||
$yoast_seo_values = $display_vars['values'];
|
||||
?>
|
||||
<div class="redirect_form_row" id="row-wpseo_redirects_type">
|
||||
<label class='textinput' for='<?php echo esc_attr( 'wpseo_redirects_type' . $yoast_seo_input_suffix ); ?>'>
|
||||
<span class="title"><?php echo esc_html_x( 'Type', 'noun', 'wordpress-seo-premium' ); ?></span>
|
||||
</label>
|
||||
<select name='wpseo_redirects_type' id='<?php echo esc_attr( 'wpseo_redirects_type' . $yoast_seo_input_suffix ); ?>' class='select'>
|
||||
<?php
|
||||
// Loop through the redirect types.
|
||||
if ( count( $yoast_seo_redirect_types ) > 0 ) {
|
||||
foreach ( $yoast_seo_redirect_types as $yoast_seo_redirect_type => $yoast_seo_redirect_desc ) {
|
||||
echo '<option value="' . esc_attr( $yoast_seo_redirect_type ) . '"'
|
||||
. sprintf( $yoast_seo_values['type'], $yoast_seo_redirect_type ) . '>'
|
||||
. esc_html( $yoast_seo_redirect_desc ) . '</option>' . "\n";
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<p class="label desc description wpseo-redirect-clear">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: opens a link to a related help center article. 2: closes the link. */
|
||||
esc_html__( 'The redirect type is the HTTP response code sent to the browser telling the browser what type of redirect is served. %1$sLearn more about redirect types%2$s.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/2jb' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
||||
<div class='redirect_form_row' id="row-wpseo_redirects_origin">
|
||||
<label class='textinput' for='<?php echo esc_attr( 'wpseo_redirects_origin' . $yoast_seo_input_suffix ); ?>'>
|
||||
<span class="title"><?php echo esc_html( $yoast_seo_origin_label_value ); ?></span>
|
||||
</label>
|
||||
<input type='text' class='textinput' name='wpseo_redirects_origin' id='<?php echo esc_attr( 'wpseo_redirects_origin' . $yoast_seo_input_suffix ); ?>' value='<?php echo esc_attr( $yoast_seo_values['origin'] ); ?>' />
|
||||
</div>
|
||||
<br class='clear'/>
|
||||
|
||||
<div class="redirect_form_row wpseo_redirect_target_holder" id="row-wpseo_redirects_target">
|
||||
<label class='textinput' for='<?php echo esc_attr( 'wpseo_redirects_target' . $yoast_seo_input_suffix ); ?>'>
|
||||
<span class="title"><?php esc_html_e( 'URL', 'wordpress-seo-premium' ); ?></span>
|
||||
</label>
|
||||
<input type='text' class='textinput' name='wpseo_redirects_target' id='<?php echo esc_attr( 'wpseo_redirects_target' . $yoast_seo_input_suffix ); ?>' value='<?php echo esc_attr( $yoast_seo_values['target'] ); ?>' />
|
||||
</div>
|
||||
<br class='clear'/>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Views
|
||||
*
|
||||
* @uses array $display_data {
|
||||
* @type int total_columns The number of columns.
|
||||
* @type WPSEO_Redirect_Form_Presenter form_presenter Instance of the WPSEO_Redirect_Form_Presenter class.
|
||||
* }
|
||||
*/
|
||||
|
||||
$yoast_seo_total_columns = $display_data['total_columns'];
|
||||
$yoast_seo_form_presenter = $display_data['form_presenter'];
|
||||
|
||||
?>
|
||||
<script type="text/plain" id="tmpl-redirects-inline-edit">
|
||||
<tr id="inline-edit" class="inline-edit-row hidden">
|
||||
<td colspan="<?php echo (int) $yoast_seo_total_columns; ?>" class="colspanchange">
|
||||
|
||||
<fieldset>
|
||||
<legend class="inline-edit-legend"><?php esc_html_e( 'Edit redirect', 'wordpress-seo-premium' ); ?></legend>
|
||||
<div class="inline-edit-col">
|
||||
<div class="wpseo_redirect_form">
|
||||
<?php
|
||||
$yoast_seo_form_presenter->display(
|
||||
[
|
||||
'input_suffix' => '{{data.suffix}}',
|
||||
'values' => [
|
||||
'origin' => '{{data.origin}}',
|
||||
'target' => '{{data.target}}',
|
||||
'type' => '<# if(data.type === %1$s) { #> selected="selected"<# } #>',
|
||||
],
|
||||
]
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<p class="inline-edit-save submit">
|
||||
<button type="button" class="button button-primary save"><?php esc_html_e( 'Update Redirect', 'wordpress-seo-premium' ); ?></button>
|
||||
<button type="button" class="button cancel"><?php esc_html_e( 'Cancel', 'wordpress-seo-premium' ); ?></button>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</script>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Views
|
||||
*
|
||||
* @uses array $view_vars {
|
||||
* @type string redirect_table The file path to show in the notices.
|
||||
* @type string nonce The nonce.
|
||||
* @type WPSEO_Redirect_Form_Presenter form_presenter Instance of the WPSEO_Redirect_Form_Presenter class.
|
||||
* @type string origin_from_url The redirect origin.
|
||||
* @type WPSEO_Redirect_Quick_Edit_Presenter quick_edit_table Instance of the WPSEO_Redirect_Quick_Edit_Presenter class.
|
||||
* }
|
||||
*/
|
||||
|
||||
$yoast_seo_redirect_table = $view_vars['redirect_table'];
|
||||
$yoast_seo_nonce = $view_vars['nonce'];
|
||||
$yoast_seo_form_presenter = $view_vars['form_presenter'];
|
||||
$yoast_seo_origin_from_url = $view_vars['origin_from_url'];
|
||||
$yoast_seo_quick_edit_table = $view_vars['quick_edit_table'];
|
||||
|
||||
?>
|
||||
|
||||
<div id="table-plain" class="tab-url redirect-table-tab">
|
||||
<?php echo '<h2>' . esc_html__( 'Plain redirects', 'wordpress-seo-premium' ) . '</h2>'; ?>
|
||||
<form class='wpseo-new-redirect-form' method='post'>
|
||||
<div class='wpseo_redirect_form'>
|
||||
<?php
|
||||
$yoast_seo_form_presenter->display(
|
||||
[
|
||||
'input_suffix' => '',
|
||||
'values' => [
|
||||
'origin' => $yoast_seo_origin_from_url,
|
||||
'target' => '',
|
||||
'type' => '',
|
||||
],
|
||||
]
|
||||
);
|
||||
?>
|
||||
|
||||
<button type="button" class="button button-primary"><?php esc_html_e( 'Add Redirect', 'wordpress-seo-premium' ); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class='desc'> </p>
|
||||
|
||||
<?php
|
||||
$yoast_seo_quick_edit_table->display(
|
||||
[
|
||||
'form_presenter' => $yoast_seo_form_presenter,
|
||||
'total_columns' => $yoast_seo_redirect_table->count_columns(),
|
||||
]
|
||||
);
|
||||
?>
|
||||
|
||||
<form id='plain' class='wpseo-redirects-table-form' method='post' action=''>
|
||||
<input type='hidden' class="wpseo_redirects_ajax_nonce" name='wpseo_redirects_ajax_nonce' value='<?php echo esc_attr( $yoast_seo_nonce ); ?>' />
|
||||
<?php
|
||||
// The list table.
|
||||
$yoast_seo_redirect_table->prepare_items();
|
||||
$yoast_seo_redirect_table->search_box( __( 'Search', 'wordpress-seo-premium' ), 'wpseo-redirect-search' );
|
||||
$yoast_seo_redirect_table->display();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Views
|
||||
*
|
||||
* @uses array $view_vars {
|
||||
* @type string redirect_table The file path to show in the notices.
|
||||
* @type string nonce The nonce.
|
||||
* @type WPSEO_Redirect_Form_Presenter form_presenter Instance of the WPSEO_Redirect_Form_Presenter class.
|
||||
* @type string origin_from_url The redirect origin.
|
||||
* @type WPSEO_Redirect_Quick_Edit_Presenter quick_edit_table Instance of the WPSEO_Redirect_Quick_Edit_Presenter class.
|
||||
* }
|
||||
*/
|
||||
|
||||
$yoast_seo_redirect_table = $view_vars['redirect_table'];
|
||||
$yoast_seo_nonce = $view_vars['nonce'];
|
||||
$yoast_seo_form_presenter = $view_vars['form_presenter'];
|
||||
$yoast_seo_origin_from_url = $view_vars['origin_from_url'];
|
||||
$yoast_seo_quick_edit_table = $view_vars['quick_edit_table'];
|
||||
|
||||
?>
|
||||
|
||||
<div id="table-regex" class="tab-url redirect-table-tab">
|
||||
<?php echo '<h2>' . esc_html__( 'Regular Expression redirects', 'wordpress-seo-premium' ) . '</h2>'; ?>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: opens a link to a related help center article. 2: closes the link. */
|
||||
esc_html__( 'Regular Expression (regex) Redirects are extremely powerful redirects. You should only use them if you know what you are doing. %1$sRead more about regex redirects on our help center%2$s.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/3lo' ) ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
||||
<form class='wpseo-new-redirect-form' method='post'>
|
||||
<div class='wpseo_redirect_form'>
|
||||
<?php
|
||||
$yoast_seo_form_presenter->display(
|
||||
[
|
||||
'input_suffix' => '',
|
||||
'values' => [
|
||||
'origin' => $yoast_seo_origin_from_url,
|
||||
'target' => '',
|
||||
'type' => '',
|
||||
],
|
||||
]
|
||||
);
|
||||
?>
|
||||
|
||||
<button type="button" class="button button-primary"><?php esc_html_e( 'Add Redirect', 'wordpress-seo-premium' ); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class='desc'> </p>
|
||||
|
||||
<?php
|
||||
$yoast_seo_quick_edit_table->display(
|
||||
[
|
||||
'form_presenter' => $yoast_seo_form_presenter,
|
||||
'total_columns' => $yoast_seo_redirect_table->count_columns(),
|
||||
]
|
||||
);
|
||||
?>
|
||||
|
||||
<form id='regex' class='wpseo-redirects-table-form' method='post'>
|
||||
<input type='hidden' class="wpseo_redirects_ajax_nonce" name='wpseo_redirects_ajax_nonce' value='<?php echo esc_attr( $yoast_seo_nonce ); ?>' />
|
||||
<?php
|
||||
// The list table.
|
||||
$yoast_seo_redirect_table->prepare_items();
|
||||
$yoast_seo_redirect_table->search_box( __( 'Search', 'wordpress-seo-premium' ), 'wpseo-redirect-search' );
|
||||
$yoast_seo_redirect_table->display();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Views
|
||||
*
|
||||
* @uses array $view_vars {
|
||||
* @type string file_path The file path to show in the notices.
|
||||
* @type string|bool redirect_file The redirect file name or false.
|
||||
* }
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
|
||||
|
||||
$yoast_seo_file_path = $view_vars['file_path'];
|
||||
$yoast_seo_redirect_file = $view_vars['redirect_file'];
|
||||
|
||||
if ( ! empty( $yoast_seo_redirect_file ) ) {
|
||||
switch ( $yoast_seo_redirect_file ) {
|
||||
case 'apache_include_file':
|
||||
?>
|
||||
<div class="notice notice-warning inline">
|
||||
<p>
|
||||
<?php esc_html_e( "As you're on Apache, you should add the following include to the website httpd config file:", 'wordpress-seo-premium' ); ?>
|
||||
<br><code>Include <?php echo esc_html( $yoast_seo_file_path ); ?></code>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case 'cannot_write_htaccess':
|
||||
?>
|
||||
<div class='notice notice-error inline'>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: '.htaccess' file name. */
|
||||
esc_html__( "We're unable to save the redirects to your %s file. Please make the file writable.", 'wordpress-seo-premium' ),
|
||||
'<code>.htaccess</code>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
break;
|
||||
case 'nginx_include_file':
|
||||
?>
|
||||
<div class="notice notice-warning inline">
|
||||
<p>
|
||||
<?php esc_html_e( "As you're on Nginx, you should add the following include to the Nginx config file:", 'wordpress-seo-premium' ); ?>
|
||||
<br><code>include <?php echo esc_html( $yoast_seo_file_path ); ?></code>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case 'cannot_write_file':
|
||||
?>
|
||||
<div class='notice notice-error inline'>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s expands to the folder location where the redirects fill will be saved. */
|
||||
esc_html__( "We're unable to save the redirect file to %s", 'wordpress-seo-premium' ),
|
||||
esc_html( $yoast_seo_file_path )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="table-settings" class="tab-url redirect-table-tab">
|
||||
<?php echo '<h2>' . esc_html__( 'Redirects settings', 'wordpress-seo-premium' ) . '</h2>'; ?>
|
||||
|
||||
<?php
|
||||
$yoast_seo_disable_toggles = ( ( WPSEO_Options::get( 'disable_php_redirect' ) === 'off' ) && \is_multisite() );
|
||||
|
||||
if ( $yoast_seo_disable_toggles ) {
|
||||
$yoast_seo_disable_htaccess_message = esc_html__( 'Since this site is a multisite, web server redirect methods have been disabled to prevent issues.', 'wordpress-seo-premium' )
|
||||
. ' <a href="https://yoa.st/4k9 ">'
|
||||
. esc_html__( 'Read more about why web server redirect methods have been disabled on a multisite.', 'wordpress-seo-premium' )
|
||||
. '</a>';
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in Alert_Presenter.
|
||||
echo new Alert_Presenter( $yoast_seo_disable_htaccess_message, 'info' );
|
||||
}
|
||||
?>
|
||||
|
||||
<form action="<?php echo esc_url( admin_url( 'options.php' ) ); ?>" method="post">
|
||||
<?php
|
||||
settings_fields( 'yoast_wpseo_redirect_options' );
|
||||
|
||||
$yoast_seo_form = Yoast_Form::get_instance();
|
||||
|
||||
$yoast_seo_form->set_option( 'wpseo_redirect' );
|
||||
|
||||
$yoast_seo_toggle_values = [
|
||||
'off' => 'PHP',
|
||||
'on' => ( WPSEO_Utils::is_apache() ) ? '.htaccess' : __( 'Web server', 'wordpress-seo-premium' ),
|
||||
];
|
||||
$yoast_seo_form->toggle_switch( 'disable_php_redirect', $yoast_seo_toggle_values, __( 'Redirect method', 'wordpress-seo-premium' ), '', [ 'disabled' => $yoast_seo_disable_toggles ] );
|
||||
|
||||
$yoast_seo_opening_p = ( $yoast_seo_disable_toggles ) ? '<p class="redirect_htaccess_disabled">' : '<p>';
|
||||
|
||||
if ( WPSEO_Utils::is_apache() ) {
|
||||
/* translators: 1: '.htaccess' file name */
|
||||
echo $yoast_seo_opening_p . sprintf( esc_html__( 'Write redirects to the %1$s file. Make sure the %1$s file is writable.', 'wordpress-seo-premium' ), '<code>.htaccess</code>' ) . '</p>';
|
||||
|
||||
$yoast_seo_form->light_switch( 'separate_file', __( 'Generate a separate redirect file', 'wordpress-seo-premium' ), [], true, '', false, [ 'disabled' => $yoast_seo_disable_toggles ] );
|
||||
|
||||
/* translators: %s: '.htaccess' file name */
|
||||
echo $yoast_seo_opening_p . sprintf( esc_html__( 'By default we write the redirects to your %s file, check this if you want the redirects written to a separate file. Only check this option if you know what you are doing!', 'wordpress-seo-premium' ), '<code>.htaccess</code>' ) . '</p>';
|
||||
}
|
||||
else {
|
||||
/* translators: %s: 'Yoast SEO Premium' */
|
||||
echo $yoast_seo_opening_p . sprintf( esc_html__( '%s can generate redirect files that can be included in your website web server configuration. If you choose this option the PHP redirects will be disabled. Only check this option if you know what you are doing!', 'wordpress-seo-premium' ), 'Yoast SEO Premium' ) . '</p>';
|
||||
}
|
||||
?>
|
||||
<p class="submit">
|
||||
<input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_attr_e( 'Save Changes', 'wordpress-seo-premium' ); ?>" />
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user