first commit
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WPSEO_Addon_Manager;
|
||||
use WPSEO_Shortlinker;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Plugin_Links_Integration class
|
||||
*/
|
||||
class Plugin_Links_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'plugin_action_links_' . \WPSEO_BASENAME, [ $this, 'remove_yoast_seo_action_link' ], 10 );
|
||||
\add_filter( 'network_admin_plugin_action_links_' . \WPSEO_BASENAME, [ $this, 'remove_yoast_seo_action_link' ], 10 );
|
||||
|
||||
\add_filter( 'plugin_action_links_' . \WPSEO_PREMIUM_BASENAME, [ $this, 'add_yoast_seo_premium_action_link' ], 10 );
|
||||
\add_filter( 'network_admin_plugin_action_links_' . \WPSEO_PREMIUM_BASENAME, [ $this, 'add_yoast_seo_premium_action_link' ], 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the upgrade link from Yoast SEO free.
|
||||
*
|
||||
* @param string[] $links The action links.
|
||||
*
|
||||
* @return string[] The action link with the upgrade link removed.
|
||||
*/
|
||||
public function remove_yoast_seo_action_link( $links ) {
|
||||
$link_to_remove = $this->get_upgrade_link();
|
||||
return \array_filter(
|
||||
$links,
|
||||
static function ( $link ) use ( $link_to_remove ) {
|
||||
return $link !== $link_to_remove;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the upgrade link to the premium actions.
|
||||
*
|
||||
* @param string[] $links The action links.
|
||||
*
|
||||
* @return string[] The action link with the upgrade link added.
|
||||
*/
|
||||
public function add_yoast_seo_premium_action_link( $links ) {
|
||||
$addon_manager = new WPSEO_Addon_Manager();
|
||||
|
||||
if ( ! $addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ) ) {
|
||||
\array_unshift( $links, $this->get_upgrade_link() );
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the upgrade link.
|
||||
*
|
||||
* @return string The upgrade link.
|
||||
*/
|
||||
protected function get_upgrade_link() {
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Reason: text is originally from Yoast SEO.
|
||||
return '<a style="font-weight: bold;" href="' . \esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/activate-my-yoast' ) ) . '" target="_blank">' . \__( 'Activate your subscription', 'wordpress-seo' ) . '</a>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin\Prominent_Words;
|
||||
|
||||
use WPSEO_Language_Utils;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
|
||||
use Yoast\WP\SEO\Helpers\Language_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action;
|
||||
use Yoast\WP\SEO\Routes\Prominent_Words_Route;
|
||||
|
||||
/**
|
||||
* Class Indexing_Integration.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Admin\Prominent_Words
|
||||
*/
|
||||
class Indexing_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Number of prominent words to index per indexable
|
||||
* when a language has function word support.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const PER_INDEXABLE_LIMIT = 20;
|
||||
|
||||
/**
|
||||
* Number of prominent words to index per indexable
|
||||
* when a language does not have function word support.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const PER_INDEXABLE_LIMIT_NO_FUNCTION_WORD_SUPPORT = 30;
|
||||
|
||||
/**
|
||||
* Holds the content action.
|
||||
*
|
||||
* @var Content_Action
|
||||
*/
|
||||
protected $content_indexation_action;
|
||||
|
||||
/**
|
||||
* The post indexing action.
|
||||
*
|
||||
* @var Indexable_Post_Indexation_Action
|
||||
*/
|
||||
protected $post_indexation_action;
|
||||
|
||||
/**
|
||||
* The term indexing action.
|
||||
*
|
||||
* @var Indexable_Term_Indexation_Action
|
||||
*/
|
||||
protected $term_indexation_action;
|
||||
|
||||
/**
|
||||
* The post type archive indexing action.
|
||||
*
|
||||
* @var Indexable_Post_Type_Archive_Indexation_Action
|
||||
*/
|
||||
protected $post_type_archive_indexation_action;
|
||||
|
||||
/**
|
||||
* Represents the general indexing action.
|
||||
*
|
||||
* @var Indexable_General_Indexation_Action
|
||||
*/
|
||||
protected $general_indexation_action;
|
||||
|
||||
/**
|
||||
* Represents the language helper.
|
||||
*
|
||||
* @var Language_Helper
|
||||
*/
|
||||
protected $language_helper;
|
||||
|
||||
/**
|
||||
* Represents the prominent words helper.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Holds the total number of unindexed objects.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $total_unindexed;
|
||||
|
||||
/**
|
||||
* WPSEO_Premium_Prominent_Words_Recalculation constructor.
|
||||
*
|
||||
* @param Content_Action $content_indexation_action The content indexing action.
|
||||
* @param Indexable_Post_Indexation_Action $post_indexation_action The post indexing action.
|
||||
* @param Indexable_Term_Indexation_Action $term_indexation_action The term indexing action.
|
||||
* @param Indexable_General_Indexation_Action $general_indexation_action The general indexing action.
|
||||
* @param Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation_action The post type archive indexing action.
|
||||
* @param Language_Helper $language_helper The language helper.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Content_Action $content_indexation_action,
|
||||
Indexable_Post_Indexation_Action $post_indexation_action,
|
||||
Indexable_Term_Indexation_Action $term_indexation_action,
|
||||
Indexable_General_Indexation_Action $general_indexation_action,
|
||||
Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation_action,
|
||||
Language_Helper $language_helper,
|
||||
Prominent_Words_Helper $prominent_words_helper
|
||||
) {
|
||||
$this->content_indexation_action = $content_indexation_action;
|
||||
$this->post_indexation_action = $post_indexation_action;
|
||||
$this->term_indexation_action = $term_indexation_action;
|
||||
$this->general_indexation_action = $general_indexation_action;
|
||||
$this->post_type_archive_indexation_action = $post_type_archive_indexation_action;
|
||||
$this->language_helper = $language_helper;
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
|
||||
|
||||
\add_filter( 'wpseo_indexing_data', [ $this, 'adapt_indexing_data' ] );
|
||||
\add_filter( 'wpseo_indexing_get_unindexed_count', [ $this, 'get_unindexed_count' ] );
|
||||
\add_filter( 'wpseo_indexing_endpoints', [ $this, 'add_endpoints' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Admin_Conditional::class,
|
||||
Migrations_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the endpoints to call.
|
||||
*
|
||||
* @param array $endpoints The endpoints to extend.
|
||||
*
|
||||
* @return array The endpoints.
|
||||
*/
|
||||
public function add_endpoints( $endpoints ) {
|
||||
$endpoints['get_content'] = Prominent_Words_Route::FULL_GET_CONTENT_ROUTE;
|
||||
$endpoints['complete_words'] = Prominent_Words_Route::FULL_COMPLETE_ROUTE;
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts the indexing data as sent to the JavaScript side of the
|
||||
* indexing process.
|
||||
*
|
||||
* Adds the appropriate prominent words endpoints and other settings.
|
||||
*
|
||||
* @param array $data The data to be adapted.
|
||||
*
|
||||
* @return array The adapted indexing data.
|
||||
*/
|
||||
public function adapt_indexing_data( $data ) {
|
||||
$site_locale = \get_locale();
|
||||
$language = WPSEO_Language_Utils::get_language( $site_locale );
|
||||
|
||||
$data['locale'] = $site_locale;
|
||||
$data['language'] = $language;
|
||||
|
||||
$data['morphologySupported'] = $this->language_helper->is_word_form_recognition_active( $language );
|
||||
|
||||
$per_indexable_limit = self::PER_INDEXABLE_LIMIT_NO_FUNCTION_WORD_SUPPORT;
|
||||
if ( $this->language_helper->has_function_word_support( $language ) ) {
|
||||
$per_indexable_limit = self::PER_INDEXABLE_LIMIT;
|
||||
}
|
||||
|
||||
$data['prominentWords'] = [
|
||||
'endpoint' => Prominent_Words_Route::FULL_SAVE_ROUTE,
|
||||
'perIndexableLimit' => $per_indexable_limit,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the required scripts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_tools' || ( $_GET['page'] === 'wpseo_tools' && isset( $_GET['tool'] ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_completed = ( (int) $this->get_unindexed_count( 0 ) === 0 );
|
||||
$this->prominent_words_helper->set_indexing_completed( $is_completed );
|
||||
|
||||
\wp_enqueue_script( 'yoast-premium-prominent-words-indexation' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of unindexed objects.
|
||||
*
|
||||
* @param int $unindexed_count The unindexed count.
|
||||
*
|
||||
* @return int The total number of indexables to recalculate.
|
||||
*/
|
||||
public function get_unindexed_count( $unindexed_count ) {
|
||||
// Get the number of indexables that haven't had their prominent words indexed yet.
|
||||
$unindexed_count += $this->content_indexation_action->get_total_unindexed();
|
||||
|
||||
// Take posts and terms into account that do not have indexables yet.
|
||||
$unindexed_count += $this->post_indexation_action->get_total_unindexed();
|
||||
$unindexed_count += $this->term_indexation_action->get_total_unindexed();
|
||||
$unindexed_count += $this->general_indexation_action->get_total_unindexed();
|
||||
$unindexed_count += $this->post_type_archive_indexation_action->get_total_unindexed();
|
||||
|
||||
return $unindexed_count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action;
|
||||
|
||||
/**
|
||||
* Adds a hidden field to the metabox for storing the calculated words and also
|
||||
* handles the value of it after posting.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Admin\Prominent_Words
|
||||
*/
|
||||
class Metabox_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Represents the prominent words save action.
|
||||
*
|
||||
* @var Save_Action
|
||||
*/
|
||||
protected $save_action;
|
||||
|
||||
/**
|
||||
* Prominent_Words_Metabox constructor.
|
||||
*
|
||||
* @param Save_Action $save_action The prominent words save action.
|
||||
*/
|
||||
public function __construct( Save_Action $save_action ) {
|
||||
$this->save_action = $save_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the register_hooks function of the Integration interface.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'wpseo_metabox_entries_general', [ $this, 'add_words_for_linking_hidden_field' ] );
|
||||
\add_filter( 'update_post_metadata', [ $this, 'save_prominent_words_for_post' ], 10, 4 );
|
||||
|
||||
\add_filter( 'wpseo_taxonomy_content_fields', [ $this, 'add_words_for_linking_hidden_field' ] );
|
||||
\add_filter( 'edit_term', [ $this, 'save_prominent_words_for_term' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a hidden field for the prominent words to the metabox.
|
||||
*
|
||||
* @param array $field_defs The definitions for the input fields.
|
||||
*
|
||||
* @return array The definitions for the input fields.
|
||||
*/
|
||||
public function add_words_for_linking_hidden_field( $field_defs ) {
|
||||
if ( \is_array( $field_defs ) ) {
|
||||
$field_defs['words_for_linking'] = [
|
||||
'type' => 'hidden',
|
||||
'title' => 'words_for_linking',
|
||||
'label' => '',
|
||||
'options' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $field_defs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the value of the _yoast_wpseo_words_for_linking hidden field to the prominent_words table, not postmeta.
|
||||
* Added to the 'update_post_metadata' filter.
|
||||
*
|
||||
* @param false|null $check Whether to allow updating metadata for the given type.
|
||||
* @param int $object_id The post id.
|
||||
* @param string $meta_key The key of the metadata.
|
||||
* @param mixed $meta_value The value of the metadata.
|
||||
*
|
||||
* @return false|null Non-null value if meta data should not be updated.
|
||||
* Null if the metadata should be updated as normal.
|
||||
*/
|
||||
public function save_prominent_words_for_post( $check, $object_id, $meta_key, $meta_value ) {
|
||||
if ( $meta_key !== '_yoast_wpseo_words_for_linking' ) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
// If the save was triggered with an empty meta value, don't update the prominent words.
|
||||
if ( empty( $meta_value ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. Decode from stringified JSON.
|
||||
$words_for_linking = \json_decode( $meta_value, true );
|
||||
// 2. Save prominent words using the existing functionality.
|
||||
$this->save_action->link( 'post', $object_id, $words_for_linking );
|
||||
|
||||
// 3. Return non-null value so we don't save prominent words to the `post_meta` table.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the prominent words for a term.
|
||||
*
|
||||
* @param int $term_id The term id to save the words for.
|
||||
*/
|
||||
public function save_prominent_words_for_term( $term_id ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- The nonce is already validated.
|
||||
if ( ! isset( $_POST['wpseo_words_for_linking'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$words_for_linking = [];
|
||||
if ( ! empty( $_POST['wpseo_words_for_linking'] ) ) {
|
||||
$prominent_words = \sanitize_text_field( \wp_unslash( $_POST['wpseo_words_for_linking'] ) );
|
||||
// phpcs:enable
|
||||
$words_for_linking = \json_decode( $prominent_words, true );
|
||||
}
|
||||
|
||||
$this->save_action->link( 'term', $term_id, $words_for_linking );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin\Social_Templates;
|
||||
|
||||
use WP_Taxonomy;
|
||||
use WPSEO_Admin_Editor_Specific_Replace_Vars;
|
||||
use WPSEO_Admin_Recommended_Replace_Vars;
|
||||
use WPSEO_Replacevar_Editor;
|
||||
use Yoast\WP\SEO\Premium\Config\Badge_Group_Names;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Conditionals\Social_Templates_Conditional;
|
||||
use Yoast_Form;
|
||||
|
||||
/**
|
||||
* Class Social_Templates_Integration.
|
||||
*
|
||||
* Adds the social fields to the meta tabs for post types, taxonomies and archives.
|
||||
*/
|
||||
class Social_Templates_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Service that can be used to recommend a set of variables for a WPSEO_Replacevar_Editor.
|
||||
*
|
||||
* @var WPSEO_Admin_Recommended_Replace_Vars
|
||||
*/
|
||||
private $recommended_replace_vars;
|
||||
|
||||
/**
|
||||
* Service that can be used to recommend an editor specific set of variables for a WPSEO_Replacevar_Editor.
|
||||
*
|
||||
* @var WPSEO_Admin_Editor_Specific_Replace_Vars
|
||||
*/
|
||||
private $editor_specific_replace_vars;
|
||||
|
||||
/**
|
||||
* Group to which the 'New' badges belong to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $group;
|
||||
|
||||
/**
|
||||
* Returns the conditionals based on which this loadable should be active.
|
||||
*
|
||||
* @return array The conditionals that must be met to load this.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [
|
||||
Social_Templates_Conditional::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Social_Templates_Integration constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
|
||||
$this->editor_specific_replace_vars = new WPSEO_Admin_Editor_Specific_Replace_Vars();
|
||||
$this->group = 'global-templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'Yoast\WP\SEO\admin_author_archives_meta', [ $this, 'social_author_archives' ] );
|
||||
\add_action( 'Yoast\WP\SEO\admin_date_archives_meta', [ $this, 'social_date_archives' ] );
|
||||
\add_action( 'Yoast\WP\SEO\admin_post_types_meta', [ $this, 'social_post_type' ], 8, 2 );
|
||||
\add_action( 'Yoast\WP\SEO\admin_post_types_archive', [ $this, 'social_post_types_archive' ], 10, 2 );
|
||||
\add_action( 'Yoast\WP\SEO\admin_taxonomies_meta', [ $this, 'social_taxonomies' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of social fields for the author archives in the Search Appearance section.
|
||||
*
|
||||
* @param Yoast_Form $yform The form builder.
|
||||
*/
|
||||
public function social_author_archives( $yform ) {
|
||||
$identifier = 'author-wpseo';
|
||||
$page_type_recommended = $this->recommended_replace_vars->determine_for_archive( 'author' );
|
||||
$page_type_specific = $this->editor_specific_replace_vars->determine_for_archive( 'author' );
|
||||
|
||||
$this->build_social_fields( $yform, $identifier, $page_type_recommended, $page_type_specific );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of social fields for the date archives in the Search Appearance section.
|
||||
*
|
||||
* @param Yoast_Form $yform The form builder.
|
||||
*/
|
||||
public function social_date_archives( $yform ) {
|
||||
$identifier = 'archive-wpseo';
|
||||
$page_type_recommended = $this->recommended_replace_vars->determine_for_archive( 'date' );
|
||||
$page_type_specific = $this->editor_specific_replace_vars->determine_for_archive( 'date' );
|
||||
|
||||
$this->build_social_fields( $yform, $identifier, $page_type_recommended, $page_type_specific );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of social fields for the post types in the Search Appearance section.
|
||||
*
|
||||
* @param Yoast_Form $yform The form builder.
|
||||
* @param string $post_type_name The name of the current post_type that gets the social fields added.
|
||||
*/
|
||||
public function social_post_type( $yform, $post_type_name ) {
|
||||
if ( $post_type_name === 'attachment' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$page_type_recommended = $this->recommended_replace_vars->determine_for_post_type( $post_type_name );
|
||||
$page_type_specific = $this->editor_specific_replace_vars->determine_for_post_type( $post_type_name );
|
||||
|
||||
$this->build_social_fields( $yform, $post_type_name, $page_type_recommended, $page_type_specific );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of social fields for the post types archives in the Search Appearance section.
|
||||
*
|
||||
* @param Yoast_Form $yform The form builder.
|
||||
* @param string $post_type_name The name of the current post_type that gets the social fields added.
|
||||
*/
|
||||
public function social_post_types_archive( $yform, $post_type_name ) {
|
||||
$identifier = 'ptarchive-' . $post_type_name;
|
||||
$page_type_recommended = $this->recommended_replace_vars->determine_for_archive( $post_type_name );
|
||||
$page_type_specific = $this->editor_specific_replace_vars->determine_for_archive( $post_type_name );
|
||||
|
||||
$this->build_social_fields( $yform, $identifier, $page_type_recommended, $page_type_specific );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of social fields for the taxonomies in the Search Appearance section.
|
||||
*
|
||||
* @param Yoast_Form $yform The form builder.
|
||||
* @param WP_Taxonomy $taxonomy The taxonomy that gets the social fields added.
|
||||
*/
|
||||
public function social_taxonomies( $yform, $taxonomy ) {
|
||||
$identifier = 'tax-' . $taxonomy->name;
|
||||
$page_type_recommended = $this->recommended_replace_vars->determine_for_term( $taxonomy->name );
|
||||
$page_type_specific = $this->editor_specific_replace_vars->determine_for_term( $taxonomy->name );
|
||||
|
||||
$this->build_social_fields( $yform, $identifier, $page_type_recommended, $page_type_specific );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of social fields for the Search Appearance section.
|
||||
*
|
||||
* @param Yoast_Form $yform The form builder.
|
||||
* @param string $identifier A page-wide unique identifier for data storage and unique DOM elements.
|
||||
* @param string $page_type_recommended Recommended type of page for a list of replaceable variables.
|
||||
* @param string $page_type_specific Editor specific type of page for a list of replaceable variables.
|
||||
*/
|
||||
protected function build_social_fields( Yoast_Form $yform, $identifier, $page_type_recommended, $page_type_specific ) {
|
||||
$image_url_field_id = 'social-image-url-' . $identifier;
|
||||
$image_id_field_id = 'social-image-id-' . $identifier;
|
||||
$badge_group_names = new Badge_Group_Names();
|
||||
|
||||
echo '<div class="yoast-settings-section">';
|
||||
|
||||
$yform->hidden( $image_url_field_id, $image_url_field_id );
|
||||
$yform->hidden( $image_id_field_id, $image_id_field_id );
|
||||
\printf(
|
||||
'<div
|
||||
id="%1$s"
|
||||
data-react-image-portal
|
||||
data-react-image-portal-target-image="%2$s"
|
||||
data-react-image-portal-target-image-id="%3$s"
|
||||
data-react-image-portal-has-new-badge="%4$s"
|
||||
></div>',
|
||||
\esc_attr( 'yoast-social-' . $identifier . '-image-select' ),
|
||||
\esc_attr( $image_url_field_id ),
|
||||
\esc_attr( $image_id_field_id ),
|
||||
\esc_attr( $badge_group_names->is_still_eligible_for_new_badge( $this->group ) )
|
||||
);
|
||||
|
||||
$editor = new WPSEO_Replacevar_Editor(
|
||||
$yform,
|
||||
[
|
||||
'title' => 'social-title-' . $identifier,
|
||||
'description' => 'social-description-' . $identifier,
|
||||
'page_type_recommended' => $page_type_recommended,
|
||||
'page_type_specific' => $page_type_specific,
|
||||
'paper_style' => false,
|
||||
'label_title' => \__( 'Social title', 'wordpress-seo-premium' ),
|
||||
'label_description' => \__( 'Social description', 'wordpress-seo-premium' ),
|
||||
'description_placeholder' => \__( 'Modify your social description by editing it right here.', 'wordpress-seo-premium' ),
|
||||
'has_new_badge' => $badge_group_names->is_still_eligible_for_new_badge( $this->group ),
|
||||
]
|
||||
);
|
||||
$editor->render();
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Integrations\Admin;
|
||||
|
||||
use WP_User;
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Class User_Profile_Integration.
|
||||
*
|
||||
* This class deals with the interface for and the storing of user Schema fields. The output of these fields is done
|
||||
* by this class's sibling frontend integration. Note that the output is done "as is", so all the sanitization happens in
|
||||
* this class.
|
||||
*/
|
||||
class User_Profile_Integration implements Integration_Interface {
|
||||
|
||||
const NONCE_FIELD_ACTION = 'show_user_profile';
|
||||
const NONCE_FIELD_NAME = 'wpseo_premium_user_profile_schema_nonce';
|
||||
|
||||
/**
|
||||
* Holds the schema fields we're adding to the user profile.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $fields;
|
||||
|
||||
/**
|
||||
* User_Profile_Integration constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->set_fields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'show_user_profile', [ $this, 'user_profile' ], 5 );
|
||||
\add_action( 'edit_user_profile', [ $this, 'user_profile' ], 5 );
|
||||
|
||||
\add_action( 'personal_options_update', [ $this, 'process_user_option_update' ] );
|
||||
\add_action( 'edit_user_profile_update', [ $this, 'process_user_option_update' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fields and their labels and descriptions.
|
||||
*/
|
||||
private function set_fields() {
|
||||
$this->fields = [
|
||||
'basicInfo' => [
|
||||
'label' => \__( 'Basic information', 'wordpress-seo-premium' ),
|
||||
'type' => 'group',
|
||||
],
|
||||
'honorificPrefix' => [
|
||||
'label' => \__( 'Honorific prefix', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$sDr%2$s, %1$sMs%2$s, %1$sMr%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'honorificSuffix' => [
|
||||
'label' => \__( 'Honorific suffix', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$sMD%2$s, %1$sPhD%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'birthDate' => [
|
||||
'label' => \__( 'Birth date', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'Use format: %1$sYYYY-MM-DD%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'date',
|
||||
],
|
||||
'gender' => [
|
||||
'label' => \__( 'Gender', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$sfemale%2$s, %1$smale%2$s, %1$snon-binary%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'extraInfo' => [
|
||||
'label' => \__( 'Extra information', 'wordpress-seo-premium' ),
|
||||
'type' => 'group',
|
||||
],
|
||||
'award' => [
|
||||
'label' => \__( 'Awards', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'Comma separated, e.g. %1$sMost likely to succeed - 1991, Smartest in class - 1990%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'array',
|
||||
],
|
||||
'knowsAbout' => [
|
||||
'label' => \__( 'Expertise in', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'Comma separated, e.g. %1$sPHP, JavaScript, 90\'s rock music%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'array',
|
||||
],
|
||||
'knowsLanguage' => [
|
||||
'label' => \__( 'Language(s) spoken', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'Comma separated, e.g. %1$sEnglish, French, Dutch%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'array',
|
||||
],
|
||||
'jobInfo' => [
|
||||
'label' => \__( 'Employer information', 'wordpress-seo-premium' ),
|
||||
'type' => 'group',
|
||||
],
|
||||
'jobTitle' => [
|
||||
'label' => \__( 'Job title', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$ssoftware engineer%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'worksFor' => [
|
||||
'label' => \__( 'Employer name', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s is replaced by `<code>`, %2$s by `</code>`. */
|
||||
'description' => \sprintf( \esc_html__( 'E.g. %1$sAcme inc%2$s', 'wordpress-seo-premium' ), '<code>', '</code>' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a form to add Schema fields to a user.
|
||||
*
|
||||
* @param WP_User $user The current page's user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function user_profile( $user ) {
|
||||
\wp_nonce_field( self::NONCE_FIELD_ACTION, self::NONCE_FIELD_NAME );
|
||||
|
||||
echo '<h2 id="yoast-seo-schema">', \esc_html__( 'Yoast SEO Schema enhancements', 'wordpress-seo-premium' ), '</h2>';
|
||||
echo '<p>', \esc_html__( 'The info you add below is added to the data Yoast SEO outputs in its schema.org output, for instance when you\'re the author of a page. Please only add the info you feel good sharing publicly.', 'wordpress-seo-premium' ), '</p>';
|
||||
|
||||
$user_schema = \get_user_meta( $user->ID, 'wpseo_user_schema', true );
|
||||
|
||||
echo '<div class="yoast yoast-settings">';
|
||||
foreach ( $this->fields as $key => $field ) {
|
||||
if ( $field['type'] === 'group' ) {
|
||||
echo '<h2>', \esc_html( $field['label'] ), '</h2>';
|
||||
continue;
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- False positive, $key is set in the code above, not by a user.
|
||||
echo '<label for="wpseo_user_schema_', $key, '">', \esc_html( $field['label'] ), '</label>';
|
||||
$val = '';
|
||||
if ( isset( $user_schema[ $key ] ) ) {
|
||||
$val = $user_schema[ $key ];
|
||||
}
|
||||
if ( $field['type'] === 'array' && \is_array( $val ) ) {
|
||||
$val = \implode( ', ', $val );
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- False positive, $key is set in the code above, not by a user.
|
||||
echo '<input class="yoast-settings__text regular-text" type="text" id="wpseo_user_schema_', $key, '" name="wpseo_user_schema[', $key, ']" value="', \esc_attr( $val ), '"/>';
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- False positive, $field['description'] is set in the code above, not by a user.
|
||||
echo '<p>', $field['description'], '</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user metas that (might) have been set on the user profile page.
|
||||
*
|
||||
* @param int $user_id User ID of the updated user.
|
||||
*/
|
||||
public function process_user_option_update( $user_id ) {
|
||||
$nonce_value = \filter_input( \INPUT_POST, self::NONCE_FIELD_NAME, \FILTER_SANITIZE_STRING );
|
||||
if ( empty( $nonce_value ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
\check_admin_referer( self::NONCE_FIELD_ACTION, self::NONCE_FIELD_NAME );
|
||||
|
||||
\update_user_meta( $user_id, 'wpseo_user_schema', $this->get_posted_user_fields() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the arguments for filter_var_array which makes sure we only get the fields that we've defined above.
|
||||
*
|
||||
* @return array Filter arguments.
|
||||
*/
|
||||
private function build_filter_args() {
|
||||
$args = [];
|
||||
foreach ( $this->fields as $key => $field ) {
|
||||
if ( $field['type'] === 'group' ) {
|
||||
continue;
|
||||
}
|
||||
$args[ $key ] = \FILTER_SANITIZE_STRING;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the posted user fields and sanitizes them.
|
||||
*
|
||||
* As we output these values straight from the database both on frontend and backend, this sanitization is quite important.
|
||||
*
|
||||
* @return array The posted user fields, restricted to allowed fields.
|
||||
*/
|
||||
private function get_posted_user_fields() {
|
||||
$args = [
|
||||
'wpseo_user_schema' => [
|
||||
'filter' => \FILTER_SANITIZE_STRING,
|
||||
'flags' => \FILTER_FORCE_ARRAY,
|
||||
],
|
||||
];
|
||||
$user_schema = \filter_input_array( \INPUT_POST, $args )['wpseo_user_schema'];
|
||||
$user_schema = \filter_var_array( $user_schema, $this->build_filter_args(), false );
|
||||
|
||||
foreach ( $this->fields as $key => $object ) {
|
||||
switch ( $object['type'] ) {
|
||||
case 'array':
|
||||
$user_schema[ $key ] = \explode( ',', $user_schema[ $key ] );
|
||||
// Trim each item in the comma separated array.
|
||||
foreach ( $user_schema[ $key ] as $index => $item ) {
|
||||
$user_schema[ $key ][ $index ] = \trim( $item );
|
||||
}
|
||||
// Remove empty items.
|
||||
$user_schema[ $key ] = \array_filter( $user_schema[ $key ] );
|
||||
|
||||
if ( $user_schema[ $key ] === [] || $user_schema[ $key ][0] === '' ) {
|
||||
unset( $user_schema[ $key ] );
|
||||
}
|
||||
break;
|
||||
case 'date':
|
||||
$date = \explode( '-', $user_schema[ $key ] );
|
||||
if ( \count( $date ) !== 3 || ! \checkdate( (int) $date[1], (int) $date[2], (int) $date[0] ) ) {
|
||||
unset( $user_schema[ $key ] );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if ( empty( $user_schema[ $key ] ) ) {
|
||||
unset( $user_schema[ $key ] );
|
||||
}
|
||||
// Nothing further to be done for strings.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $user_schema;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user