This commit is contained in:
2024-05-20 15:37:46 +03:00
commit 00b7dbd0b7
10404 changed files with 3285853 additions and 0 deletions

View File

@ -0,0 +1,233 @@
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
use Yoast\WP\SEO\Helpers\Score_Icon_Helper;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
/**
* This class adds columns to the taxonomy table.
*/
class WPSEO_Taxonomy_Columns {
/**
* The SEO analysis.
*
* @var WPSEO_Metabox_Analysis_SEO
*/
private $analysis_seo;
/**
* The readability analysis.
*
* @var WPSEO_Metabox_Analysis_Readability
*/
private $analysis_readability;
/**
* The current taxonomy.
*
* @var string
*/
private $taxonomy;
/**
* Holds the Indexable_Repository.
*
* @var Indexable_Repository
*/
protected $indexable_repository;
/**
* Holds the Score_Icon_Helper.
*
* @var Score_Icon_Helper
*/
protected $score_icon_helper;
/**
* WPSEO_Taxonomy_Columns constructor.
*/
public function __construct() {
$this->taxonomy = $this->get_taxonomy();
if ( ! empty( $this->taxonomy ) ) {
add_filter( 'manage_edit-' . $this->taxonomy . '_columns', [ $this, 'add_columns' ] );
add_filter( 'manage_' . $this->taxonomy . '_custom_column', [ $this, 'parse_column' ], 10, 3 );
}
$this->analysis_seo = new WPSEO_Metabox_Analysis_SEO();
$this->analysis_readability = new WPSEO_Metabox_Analysis_Readability();
$this->indexable_repository = YoastSEO()->classes->get( Indexable_Repository::class );
$this->score_icon_helper = YoastSEO()->helpers->score_icon;
}
/**
* Adds an SEO score column to the terms table, right after the description column.
*
* @param array $columns Current set columns.
*
* @return array
*/
public function add_columns( array $columns ) {
if ( $this->display_metabox( $this->taxonomy ) === false ) {
return $columns;
}
$new_columns = [];
foreach ( $columns as $column_name => $column_value ) {
$new_columns[ $column_name ] = $column_value;
if ( $column_name === 'description' && $this->analysis_seo->is_enabled() ) {
$new_columns['wpseo-score'] = '<span class="yoast-tooltip yoast-tooltip-n yoast-tooltip-alt" data-label="' . esc_attr__( 'SEO score', 'wordpress-seo' ) . '"><span class="yoast-column-seo-score yoast-column-header-has-tooltip"><span class="screen-reader-text">' . __( 'SEO score', 'wordpress-seo' ) . '</span></span></span>';
}
if ( $column_name === 'description' && $this->analysis_readability->is_enabled() ) {
$new_columns['wpseo-score-readability'] = '<span class="yoast-tooltip yoast-tooltip-n yoast-tooltip-alt" data-label="' . esc_attr__( 'Readability score', 'wordpress-seo' ) . '"><span class="yoast-column-readability yoast-column-header-has-tooltip"><span class="screen-reader-text">' . __( 'Readability score', 'wordpress-seo' ) . '</span></span></span>';
}
}
return $new_columns;
}
/**
* Parses the column.
*
* @param string $content The current content of the column.
* @param string $column_name The name of the column.
* @param int $term_id ID of requested taxonomy.
*
* @return string
*/
public function parse_column( $content, $column_name, $term_id ) {
switch ( $column_name ) {
case 'wpseo-score':
return $this->get_score_value( $term_id );
case 'wpseo-score-readability':
return $this->get_score_readability_value( $term_id );
}
return $content;
}
/**
* Retrieves the taxonomy from the $_GET or $_POST variable.
*
* @return string|null The current taxonomy or null when it is not set.
*/
public function get_current_taxonomy() {
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( ! empty( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'POST' ) {
if ( isset( $_POST['taxonomy'] ) && is_string( $_POST['taxonomy'] ) ) {
return sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) );
}
}
else {
if ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
}
}
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
return null;
}
/**
* Returns the posted/get taxonomy value if it is set.
*
* @return string|null
*/
private function get_taxonomy() {
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( wp_doing_ajax() ) {
if ( isset( $_POST['taxonomy'] ) && is_string( $_POST['taxonomy'] ) ) {
return sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) );
}
}
else {
if ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
}
}
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
return null;
}
/**
* Parses the value for the score column.
*
* @param int $term_id ID of requested term.
*
* @return string
*/
private function get_score_value( $term_id ) {
$indexable = $this->indexable_repository->find_by_id_and_type( (int) $term_id, 'term' );
return $this->score_icon_helper->for_seo( $indexable, '', __( 'Term is set to noindex.', 'wordpress-seo' ) );
}
/**
* Parses the value for the readability score column.
*
* @param int $term_id ID of the requested term.
*
* @return string The HTML for the readability score indicator.
*/
private function get_score_readability_value( $term_id ) {
$score = (int) WPSEO_Taxonomy_Meta::get_term_meta( $term_id, $this->taxonomy, 'content_score' );
return $this->score_icon_helper->for_readability( $score );
}
/**
* Check if the taxonomy is indexable.
*
* @param mixed $term The current term.
*
* @return bool Whether the term is indexable.
*/
private function is_indexable( $term ) {
// When the no_index value is not empty and not default, check if its value is index.
$no_index = WPSEO_Taxonomy_Meta::get_term_meta( $term->term_id, $this->taxonomy, 'noindex' );
// Check if the default for taxonomy is empty (this will be index).
if ( ! empty( $no_index ) && $no_index !== 'default' ) {
return ( $no_index === 'index' );
}
if ( is_object( $term ) ) {
$no_index_key = 'noindex-tax-' . $term->taxonomy;
// If the option is false, this means we want to index it.
return WPSEO_Options::get( $no_index_key, false ) === false;
}
return true;
}
/**
* Wraps the WPSEO_Metabox check to determine whether the metabox should be displayed either by
* choice of the admin or because the taxonomy is not public.
*
* @since 7.0
*
* @param string|null $taxonomy Optional. The taxonomy to test, defaults to the current taxonomy.
*
* @return bool Whether the meta box (and associated columns etc) should be hidden.
*/
private function display_metabox( $taxonomy = null ) {
$current_taxonomy = $this->get_current_taxonomy();
if ( ! isset( $taxonomy ) && ! empty( $current_taxonomy ) ) {
$taxonomy = $current_taxonomy;
}
return WPSEO_Utils::is_metabox_active( $taxonomy, 'taxonomy' );
}
}

View File

@ -0,0 +1,221 @@
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Class WPSEO_Taxonomy_Presenter.
*/
class WPSEO_Taxonomy_Fields_Presenter {
/**
* The taxonomy meta data for the current term.
*
* @var array
*/
private $tax_meta;
/**
* Constructs the WPSEO_Taxonomy_Fields_Presenter class.
*
* @param stdClass $term The current term.
*/
public function __construct( $term ) {
$this->tax_meta = WPSEO_Taxonomy_Meta::get_term_meta( (int) $term->term_id, $term->taxonomy );
}
/**
* Displaying the form fields.
*
* @param array $fields Array with the fields that will be displayed.
*
* @return string
*/
public function html( array $fields ) {
$content = '';
foreach ( $fields as $field_name => $field_configuration ) {
$content .= $this->form_row( 'wpseo_' . $field_name, $field_configuration );
}
return $content;
}
/**
* Create a row in the form table.
*
* @param string $field_name Variable the row controls.
* @param array $field_configuration Array with the field configuration.
*
* @return string
*/
private function form_row( $field_name, array $field_configuration ) {
$esc_field_name = esc_attr( $field_name );
$options = (array) $field_configuration['options'];
if ( ! empty( $field_configuration['description'] ) ) {
$options['description'] = $field_configuration['description'];
}
$label = $this->get_label( $field_configuration['label'], $esc_field_name );
$field = $this->get_field( $field_configuration['type'], $esc_field_name, $this->get_field_value( $field_name ), $options );
$help_content = isset( $field_configuration['options']['help'] ) ? $field_configuration['options']['help'] : '';
$help_button_text = isset( $field_configuration['options']['help-button'] ) ? $field_configuration['options']['help-button'] : '';
$help = new WPSEO_Admin_Help_Panel( $field_name, $help_button_text, $help_content );
return $this->parse_row( $label, $help, $field );
}
/**
* Generates the html for the given field config.
*
* @param string $field_type The fieldtype, e.g: text, checkbox, etc.
* @param string $field_name The name of the field.
* @param string $field_value The value of the field.
* @param array $options Array with additional options.
*
* @return string
*/
private function get_field( $field_type, $field_name, $field_value, array $options ) {
$class = $this->get_class( $options );
$field = '';
$description = '';
$aria_describedby = '';
if ( ! empty( $options['description'] ) ) {
$aria_describedby = ' aria-describedby="' . $field_name . '-desc"';
$description = '<p id="' . $field_name . '-desc" class="yoast-metabox__description">' . $options['description'] . '</p>';
}
switch ( $field_type ) {
case 'div':
$field .= '<div id="' . $field_name . '"></div>';
break;
case 'url':
$field .= '<input name="' . $field_name . '" id="' . $field_name . '" ' . $class . ' type="url" value="' . esc_attr( urldecode( $field_value ) ) . '" size="40"' . $aria_describedby . '/>';
break;
case 'text':
$field .= '<input name="' . $field_name . '" id="' . $field_name . '" ' . $class . ' type="text" value="' . esc_attr( $field_value ) . '" size="40"' . $aria_describedby . '/>';
break;
case 'checkbox':
$field .= '<input name="' . $field_name . '" id="' . $field_name . '" type="checkbox" ' . checked( $field_value ) . $aria_describedby . '/>';
break;
case 'textarea':
$rows = 3;
if ( ! empty( $options['rows'] ) ) {
$rows = $options['rows'];
}
$field .= '<textarea class="large-text" rows="' . esc_attr( $rows ) . '" id="' . $field_name . '" name="' . $field_name . '"' . $aria_describedby . '>' . esc_textarea( $field_value ) . '</textarea>';
break;
case 'upload':
$field .= '<input' .
' id="' . $field_name . '"' .
' type="text"' .
' size="36"' .
' name="' . $field_name . '"' .
' value="' . esc_attr( $field_value ) . '"' . $aria_describedby . '' .
' readonly="readonly"' .
' /> ';
$field .= '<input' .
' id="' . esc_attr( $field_name ) . '_button"' .
' class="wpseo_image_upload_button button"' .
' data-target="' . esc_attr( $field_name ) . '"' .
' data-target-id="hidden_' . esc_attr( $field_name ) . '-id"' .
' type="button"' .
' value="' . esc_attr__( 'Upload Image', 'wordpress-seo' ) . '"' .
' /> ';
$field .= '<input' .
' id="' . esc_attr( $field_name ) . '_button"' .
' class="wpseo_image_remove_button button"' .
' type="button"' .
' value="' . esc_attr__( 'Clear Image', 'wordpress-seo' ) . '"' .
' />';
break;
case 'select':
if ( is_array( $options ) && $options !== [] ) {
$field .= '<select name="' . $field_name . '" id="' . $field_name . '"' . $aria_describedby . '>';
$select_options = ( array_key_exists( 'options', $options ) ) ? $options['options'] : $options;
foreach ( $select_options as $option => $option_label ) {
$selected = selected( $option, $field_value, false );
$field .= '<option ' . $selected . ' value="' . esc_attr( $option ) . '">' . esc_html( $option_label ) . '</option>';
}
unset( $option, $option_label, $selected );
$field .= '</select>';
}
break;
case 'hidden':
$field .= '<input name="' . $field_name . '" id="hidden_' . $field_name . '" type="hidden" value="' . esc_attr( $field_value ) . '" />';
break;
}
return $field . $description;
}
/**
* Getting the value for given field_name.
*
* @param string $field_name The fieldname to get the value for.
*
* @return string
*/
private function get_field_value( $field_name ) {
if ( isset( $this->tax_meta[ $field_name ] ) && $this->tax_meta[ $field_name ] !== '' ) {
return $this->tax_meta[ $field_name ];
}
return '';
}
/**
* Getting the class attributes if $options contains a class key.
*
* @param array $options The array with field options.
*
* @return string
*/
private function get_class( array $options ) {
if ( ! empty( $options['class'] ) ) {
return ' class="' . esc_attr( $options['class'] ) . '"';
}
return '';
}
/**
* Getting the label HTML.
*
* @param string $label The label value.
* @param string $field_name The target field.
*
* @return string
*/
private function get_label( $label, $field_name ) {
if ( $label !== '' ) {
return '<label for="' . $field_name . '">' . esc_html( $label ) . '</label>';
}
return '';
}
/**
* Returns the HTML for the row which contains label, help and the field.
*
* @param string $label The html for the label if there was a label set.
* @param WPSEO_Admin_Help_Panel $help The help panel to render in this row.
* @param string $field The html for the field.
*
* @return string
*/
private function parse_row( $label, WPSEO_Admin_Help_Panel $help, $field ) {
if ( $label !== '' || $help !== '' ) {
return $label . $help->get_button_html() . $help->get_panel_html() . $field;
}
return $field;
}
}

View File

@ -0,0 +1,235 @@
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* Class WPSEO_Taxonomy_Tab.
*
* Contains the basics for each class extending this one.
*/
class WPSEO_Taxonomy_Fields {
/**
* Returns the taxonomy fields.
*
* @param string $field_group The field group.
*
* @return array
*/
public function get( $field_group ) {
$fields = [];
switch ( $field_group ) {
case 'content':
$fields = $this->get_content_fields();
break;
case 'settings':
$fields = $this->get_settings_fields();
break;
case 'social':
$fields = $this->get_social_fields();
break;
}
return $this->filter_hidden_fields( $fields );
}
/**
* Returns array with the fields for the general tab.
*
* @return array
*/
protected function get_content_fields() {
$fields = [
'title' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'desc' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'linkdex' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'content_score' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'inclusive_language_score' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'focuskw' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'is_cornerstone' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
];
/**
* Filter: 'wpseo_taxonomy_content_fields' - Adds the possibility to register additional content fields.
*
* @api array - The additional fields.
*/
$additional_fields = apply_filters( 'wpseo_taxonomy_content_fields', [] );
return array_merge( $fields, $additional_fields );
}
/**
* Returns array with the fields for the settings tab.
*
* @return array
*/
protected function get_settings_fields() {
return [
'noindex' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'bctitle' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => ( WPSEO_Options::get( 'breadcrumbs-enable' ) !== true ),
],
'canonical' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
];
}
/**
* Returning the fields for the social media tab.
*
* @return array
*/
protected function get_social_fields() {
$fields = [];
if ( WPSEO_Options::get( 'opengraph', false ) === true ) {
$fields = [
'opengraph-title' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'opengraph-description' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'opengraph-image' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'opengraph-image-id' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
];
}
if ( WPSEO_Options::get( 'twitter', false ) === true ) {
$fields = array_merge(
$fields,
[
'twitter-title' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'twitter-description' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'twitter-image' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
'twitter-image-id' => [
'label' => '',
'description' => '',
'type' => 'hidden',
'options' => '',
'hide' => false,
],
]
);
}
return $fields;
}
/**
* Filter the hidden fields.
*
* @param array $fields Array with the form fields that has will be filtered.
*
* @return array
*/
protected function filter_hidden_fields( array $fields ) {
foreach ( $fields as $field_name => $field_options ) {
if ( ! empty( $field_options['hide'] ) ) {
unset( $fields[ $field_name ] );
}
}
return $fields;
}
}

View File

@ -0,0 +1,227 @@
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
/**
* This class generates the metabox on the edit term page.
*/
class WPSEO_Taxonomy_Metabox {
/**
* The term currently being edited.
*
* @var WP_Term
*/
private $term;
/**
* The term's taxonomy.
*
* @var string
*/
private $taxonomy;
/**
* Whether or not the social tab is enabled for this metabox.
*
* @var bool
*/
private $is_social_enabled;
/**
* Helper to determine whether or not the SEO analysis is enabled.
*
* @var WPSEO_Metabox_Analysis_SEO
*/
protected $seo_analysis;
/**
* Helper to determine whether or not the readability analysis is enabled.
*
* @var WPSEO_Metabox_Analysis_Readability
*/
protected $readability_analysis;
/**
* Helper to determine whether or not the inclusive language analysis is enabled.
*
* @var WPSEO_Metabox_Analysis_Inclusive_Language
*/
protected $inclusive_language_analysis;
/**
* The constructor.
*
* @param string $taxonomy The taxonomy.
* @param stdClass $term The term.
*/
public function __construct( $taxonomy, $term ) {
$this->term = $term;
$this->taxonomy = $taxonomy;
$this->is_social_enabled = WPSEO_Options::get( 'opengraph', false ) || WPSEO_Options::get( 'twitter', false );
$this->seo_analysis = new WPSEO_Metabox_Analysis_SEO();
$this->readability_analysis = new WPSEO_Metabox_Analysis_Readability();
$this->inclusive_language_analysis = new WPSEO_Metabox_Analysis_Inclusive_Language();
}
/**
* Shows the Yoast SEO metabox for the term.
*/
public function display() {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $this->get_product_title() returns a hard-coded string.
printf( '<div id="wpseo_meta" class="postbox yoast wpseo-taxonomy-metabox-postbox"><h2><span>%1$s</span></h2>', $this->get_product_title() );
echo '<div class="inside">';
echo '<div id="taxonomy_overall"></div>';
$this->render_hidden_fields();
$this->render_tabs();
echo '</div>';
echo '</div>';
}
/**
* Renders the metabox hidden fields.
*
* @return void
*/
protected function render_hidden_fields() {
$fields_presenter = new WPSEO_Taxonomy_Fields_Presenter( $this->term );
$field_definitions = new WPSEO_Taxonomy_Fields();
echo $fields_presenter->html( $field_definitions->get( 'content' ) );
if ( WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) || WPSEO_Options::get( 'disableadvanced_meta' ) === false ) {
echo $fields_presenter->html( $field_definitions->get( 'settings' ) );
}
if ( $this->is_social_enabled ) {
echo $fields_presenter->html( $field_definitions->get( 'social' ) );
}
}
/**
* Renders the metabox tabs.
*
* @return void
*/
protected function render_tabs() {
echo '<div class="wpseo-metabox-content">';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $this->get_product_title() returns a hard-coded string.
printf( '<div class="wpseo-metabox-menu"><ul role="tablist" class="yoast-aria-tabs" aria-label="%s">', $this->get_product_title() );
$tabs = $this->get_tabs();
foreach ( $tabs as $tab ) {
$tab->display_link();
}
echo '</ul></div>';
foreach ( $tabs as $tab ) {
$tab->display_content();
}
echo '</div>';
}
/**
* Returns the relevant metabox sections for the current view.
*
* @return WPSEO_Metabox_Section[]
*/
private function get_tabs() {
$tabs = [];
$label = __( 'SEO', 'wordpress-seo' );
if ( $this->seo_analysis->is_enabled() ) {
$label = '<span class="wpseo-score-icon-container" id="wpseo-seo-score-icon"></span>' . $label;
}
$tabs[] = new WPSEO_Metabox_Section_React( 'content', $label );
if ( $this->readability_analysis->is_enabled() ) {
$tabs[] = new WPSEO_Metabox_Section_Readability();
}
if ( $this->inclusive_language_analysis->is_enabled() ) {
$tabs[] = new WPSEO_Metabox_Section_Inclusive_Language();
}
if ( $this->is_social_enabled ) {
$tabs[] = new WPSEO_Metabox_Section_React(
'social',
'<span class="dashicons dashicons-share"></span>' . __( 'Social', 'wordpress-seo' ),
'',
[
'html_after' => '<div id="wpseo-section-social"></div>',
]
);
}
$tabs = array_merge( $tabs, $this->get_additional_tabs() );
return $tabs;
}
/**
* Returns the metabox tabs that have been added by other plugins.
*
* @return WPSEO_Metabox_Section_Additional[]
*/
protected function get_additional_tabs() {
$tabs = [];
/**
* Private filter: 'yoast_free_additional_taxonomy_metabox_sections'.
*
* Meant for internal use only. Allows adding additional tabs to the Yoast SEO metabox for taxonomies.
*
* @param array[] $tabs {
* An array of arrays with tab specifications.
*
* @type array $tab {
* A tab specification.
*
* @type string $name The name of the tab. Used in the HTML IDs, href and aria properties.
* @type string $link_content The content of the tab link.
* @type string $content The content of the tab.
* @type array $options {
* Optional. Extra options.
*
* @type string $link_class Optional. The class for the tab link.
* @type string $link_aria_label Optional. The aria label of the tab link.
* }
* }
* }
*/
$requested_tabs = apply_filters( 'yoast_free_additional_taxonomy_metabox_sections', [] );
foreach ( $requested_tabs as $tab ) {
if ( is_array( $tab ) && array_key_exists( 'name', $tab ) && array_key_exists( 'link_content', $tab ) && array_key_exists( 'content', $tab ) ) {
$options = array_key_exists( 'options', $tab ) ? $tab['options'] : [];
$tabs[] = new WPSEO_Metabox_Section_Additional(
$tab['name'],
$tab['link_content'],
$tab['content'],
$options
);
}
}
return $tabs;
}
/**
* Retrieves the product title.
*
* @return string The product title.
*/
protected function get_product_title() {
return YoastSEO()->helpers->product->get_product_name();
}
}

View File

@ -0,0 +1,449 @@
<?php
/**
* WPSEO plugin file.
*
* @package WPSEO\Admin
*/
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
/**
* Class that handles the edit boxes on taxonomy edit pages.
*/
class WPSEO_Taxonomy {
/**
* The current active taxonomy.
*
* @var string
*/
private $taxonomy = '';
/**
* Holds the metabox SEO analysis instance.
*
* @var WPSEO_Metabox_Analysis_SEO
*/
private $analysis_seo;
/**
* Holds the metabox readability analysis instance.
*
* @var WPSEO_Metabox_Analysis_Readability
*/
private $analysis_readability;
/**
* Holds the metabox inclusive language analysis instance.
*
* @var WPSEO_Metabox_Analysis_Inclusive_Language
*/
private $analysis_inclusive_language;
/**
* Class constructor.
*/
public function __construct() {
$this->taxonomy = $this::get_taxonomy();
add_action( 'edit_term', [ $this, 'update_term' ], 99, 3 );
add_action( 'init', [ $this, 'custom_category_descriptions_allow_html' ] );
add_action( 'admin_init', [ $this, 'admin_init' ] );
if ( self::is_term_overview( $GLOBALS['pagenow'] ) ) {
new WPSEO_Taxonomy_Columns();
}
$this->analysis_seo = new WPSEO_Metabox_Analysis_SEO();
$this->analysis_readability = new WPSEO_Metabox_Analysis_Readability();
$this->analysis_inclusive_language = new WPSEO_Metabox_Analysis_Inclusive_Language();
}
/**
* Add hooks late enough for taxonomy object to be available for checks.
*/
public function admin_init() {
$taxonomy = get_taxonomy( $this->taxonomy );
if ( empty( $taxonomy ) || empty( $taxonomy->public ) || ! $this->show_metabox() ) {
return;
}
// Adds custom category description editor. Needs a hook that runs before the description field.
add_action( "{$this->taxonomy}_term_edit_form_top", [ $this, 'custom_category_description_editor' ] );
add_action( sanitize_text_field( $this->taxonomy ) . '_edit_form', [ $this, 'term_metabox' ], 90, 1 );
add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
}
/**
* Show the SEO inputs for term.
*
* @param stdClass|WP_Term $term Term to show the edit boxes for.
*/
public function term_metabox( $term ) {
if ( WPSEO_Metabox::is_internet_explorer() ) {
$this->show_internet_explorer_notice();
return;
}
$metabox = new WPSEO_Taxonomy_Metabox( $this->taxonomy, $term );
$metabox->display();
}
/**
* Renders the content for the internet explorer metabox.
*
* @return void
*/
private function show_internet_explorer_notice() {
$product_title = YoastSEO()->helpers->product->get_product_name();
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $product_title is hardcoded.
printf( '<div id="wpseo_meta" class="postbox yoast wpseo-taxonomy-metabox-postbox"><h2><span>%1$s</span></h2>', $product_title );
echo '<div class="inside">';
$content = sprintf(
/* translators: 1: Link start tag to the Firefox website, 2: Link start tag to the Chrome website, 3: Link start tag to the Edge website, 4: Link closing tag. */
esc_html__( 'The browser you are currently using is unfortunately rather dated. Since we strive to give you the best experience possible, we no longer support this browser. Instead, please use %1$sFirefox%4$s, %2$sChrome%4$s or %3$sMicrosoft Edge%4$s.', 'wordpress-seo' ),
'<a href="https://www.mozilla.org/firefox/new/">',
'<a href="https://www.google.com/chrome/">',
'<a href="https://www.microsoft.com/windows/microsoft-edge">',
'</a>'
);
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
echo new Alert_Presenter( $content );
echo '</div></div>';
}
/**
* Queue assets for taxonomy screens.
*
* @since 1.5.0
*/
public function admin_enqueue_scripts() {
$pagenow = $GLOBALS['pagenow'];
if ( ! ( self::is_term_edit( $pagenow ) || self::is_term_overview( $pagenow ) ) ) {
return;
}
$asset_manager = new WPSEO_Admin_Asset_Manager();
$asset_manager->enqueue_style( 'scoring' );
$asset_manager->enqueue_style( 'monorepo' );
$tag_id = $this::get_tag_id();
if (
self::is_term_edit( $pagenow )
&& ! is_null( $tag_id )
) {
wp_enqueue_media(); // Enqueue files needed for upload functionality.
$asset_manager->enqueue_style( 'metabox-css' );
$asset_manager->enqueue_style( 'scoring' );
$asset_manager->enqueue_script( 'term-edit' );
/**
* Remove the emoji script as it is incompatible with both React and any
* contenteditable fields.
*/
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
$asset_manager->localize_script( 'term-edit', 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
$script_data = [
'analysis' => [
'plugins' => [
'replaceVars' => [
'no_parent_text' => __( '(no parent)', 'wordpress-seo' ),
'replace_vars' => $this->get_replace_vars(),
'recommended_replace_vars' => $this->get_recommended_replace_vars(),
'scope' => $this->determine_scope(),
],
],
'worker' => [
'url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ),
'dependencies' => YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ),
'keywords_assessment_url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
'log_level' => WPSEO_Utils::get_analysis_worker_log_level(),
],
],
'media' => [
// @todo replace this translation with JavaScript translations.
'choose_image' => __( 'Use Image', 'wordpress-seo' ),
],
'metabox' => $this->localize_term_scraper_script( $tag_id ),
'userLanguageCode' => WPSEO_Language_Utils::get_language( \get_user_locale() ),
'isTerm' => true,
'postId' => $tag_id,
'usedKeywordsNonce' => \wp_create_nonce( 'wpseo-keyword-usage' ),
'linkParams' => WPSEO_Shortlinker::get_query_params(),
];
$asset_manager->localize_script( 'term-edit', 'wpseoScriptData', $script_data );
$asset_manager->enqueue_user_language_script();
}
if ( self::is_term_overview( $pagenow ) ) {
$asset_manager->enqueue_script( 'edit-page' );
}
}
/**
* Update the taxonomy meta data on save.
*
* @param int $term_id ID of the term to save data for.
* @param int $tt_id The taxonomy_term_id for the term.
* @param string $taxonomy The taxonomy the term belongs to.
*/
public function update_term( $term_id, $tt_id, $taxonomy ) {
// Bail if this is a multisite installation and the site has been switched.
if ( is_multisite() && ms_is_switched() ) {
return;
}
/* Create post array with only our values. */
$new_meta_data = [];
foreach ( WPSEO_Taxonomy_Meta::$defaults_per_term as $key => $default ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce is already checked by WordPress before executing this action.
if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce is already checked by WordPress before executing this action.
$new_meta_data[ $key ] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) );
}
// If analysis is disabled remove that analysis score value from the DB.
if ( $this->is_meta_value_disabled( $key ) ) {
$new_meta_data[ $key ] = '';
}
}
unset( $key, $default );
// Saving the values.
WPSEO_Taxonomy_Meta::set_values( $term_id, $taxonomy, $new_meta_data );
}
/**
* Determines if the given meta value key is disabled.
*
* @param string $key The key of the meta value.
* @return bool Whether the given meta value key is disabled.
*/
public function is_meta_value_disabled( $key ) {
if ( $key === 'wpseo_linkdex' && ! $this->analysis_seo->is_enabled() ) {
return true;
}
if ( $key === 'wpseo_content_score' && ! $this->analysis_readability->is_enabled() ) {
return true;
}
if ( $key === 'wpseo_inclusive_language_score' && ! $this->analysis_inclusive_language->is_enabled() ) {
return true;
}
return false;
}
/**
* Allows post-kses-filtered HTML in term descriptions.
*/
public function custom_category_descriptions_allow_html() {
remove_filter( 'term_description', 'wp_kses_data' );
remove_filter( 'pre_term_description', 'wp_filter_kses' );
add_filter( 'term_description', 'wp_kses_post' );
add_filter( 'pre_term_description', 'wp_filter_post_kses' );
}
/**
* Output the WordPress editor.
*/
public function custom_category_description_editor() {
wp_editor( '', 'description' );
}
/**
* Pass variables to js for use with the term-scraper.
*
* @param int $term_id The ID of the term to localize the script for.
*
* @return array
*/
public function localize_term_scraper_script( $term_id ) {
$term = get_term_by( 'id', $term_id, $this::get_taxonomy() );
$taxonomy = get_taxonomy( $term->taxonomy );
$term_formatter = new WPSEO_Metabox_Formatter(
new WPSEO_Term_Metabox_Formatter( $taxonomy, $term )
);
return $term_formatter->get_values();
}
/**
* Pass some variables to js for replacing variables.
*
* @return array
*/
public function localize_replace_vars_script() {
return [
'no_parent_text' => __( '(no parent)', 'wordpress-seo' ),
'replace_vars' => $this->get_replace_vars(),
'recommended_replace_vars' => $this->get_recommended_replace_vars(),
'scope' => $this->determine_scope(),
];
}
/**
* Determines the scope based on the current taxonomy.
* This can be used by the replacevar plugin to determine if a replacement needs to be executed.
*
* @return string String decribing the current scope.
*/
private function determine_scope() {
$taxonomy = $this::get_taxonomy();
if ( $taxonomy === 'category' ) {
return 'category';
}
if ( $taxonomy === 'post_tag' ) {
return 'tag';
}
return 'term';
}
/**
* Determines if a given page is the term overview page.
*
* @param string $page The string to check for the term overview page.
*
* @return bool
*/
public static function is_term_overview( $page ) {
return $page === 'edit-tags.php';
}
/**
* Determines if a given page is the term edit page.
*
* @param string $page The string to check for the term edit page.
*
* @return bool
*/
public static function is_term_edit( $page ) {
return $page === 'term.php';
}
/**
* Function to get the labels for the current taxonomy.
*
* @return object|null Labels for the current taxonomy or null if the taxonomy is not set.
*/
public static function get_labels() {
$term = self::get_taxonomy();
if ( $term !== '' ) {
$taxonomy = get_taxonomy( $term );
return $taxonomy->labels;
}
return null;
}
/**
* Retrieves a template.
* Check if metabox for current taxonomy should be displayed.
*
* @return bool
*/
private function show_metabox() {
$option_key = 'display-metabox-tax-' . $this->taxonomy;
return WPSEO_Options::get( $option_key );
}
/**
* Getting the taxonomy from the URL.
*
* @return string
*/
private static function get_taxonomy() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
}
return '';
}
/**
* Get the current tag ID from the GET parameters.
*
* @return int|null the tag ID if it exists, null otherwise.
*/
private static function get_tag_id() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( isset( $_GET['tag_ID'] ) && is_string( $_GET['tag_ID'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are casting to an integer.
$tag_id = (int) wp_unslash( $_GET['tag_ID'] );
if ( $tag_id > 0 ) {
return $tag_id;
}
}
return null;
}
/**
* Prepares the replace vars for localization.
*
* @return array The replacement variables.
*/
private function get_replace_vars() {
$term_id = $this::get_tag_id();
$term = get_term_by( 'id', $term_id, $this::get_taxonomy() );
$cached_replacement_vars = [];
$vars_to_cache = [
'date',
'id',
'sitename',
'sitedesc',
'sep',
'page',
'term_title',
'term_description',
'term_hierarchy',
'category_description',
'tag_description',
'searchphrase',
'currentyear',
];
foreach ( $vars_to_cache as $var ) {
$cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $term );
}
return $cached_replacement_vars;
}
/**
* Prepares the recommended replace vars for localization.
*
* @return array The recommended replacement variables.
*/
private function get_recommended_replace_vars() {
$recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
$taxonomy = $this::get_taxonomy();
if ( $taxonomy === '' ) {
return [];
}
// What is recommended depends on the current context.
$page_type = $recommended_replace_vars->determine_for_term( $taxonomy );
return $recommended_replace_vars->get_recommended_replacevars_for( $page_type );
}
}