first commit

This commit is contained in:
Rachit Bhargava
2023-07-21 17:12:10 -04:00
parent d0fe47dde4
commit 5d0f0734d8
14003 changed files with 2829464 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
<?php
namespace Yoast\WP\SEO\Premium\Integrations;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Premium\Conditionals\Social_Templates_Conditional;
/**
* Class Abstract_OpenGraph_Integration.
*/
abstract class Abstract_OpenGraph_Integration implements Integration_Interface {
/**
* The name or prefix for the social title option.
*
* @var string
*/
const OPTION_TITLES_KEY_TITLE = '';
/**
* The name or prefix for the social description option.
*
* @var string
*/
const OPTION_TITLES_KEY_DESCRIPTION = '';
/**
* The name or prefix for the social image ID option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE_ID = '';
/**
* The name or prefix for the social image URL option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE = '';
/**
* The options helper.
*
* @var Options_Helper
*/
protected $options;
/**
* Integration constructor.
*
* @param Options_Helper $options The options helper.
*/
public function __construct( Options_Helper $options ) {
$this->options = $options;
}
/**
* Returns the conditionals based in which this loadable should be active.
*
* @return array
*/
public static function get_conditionals() {
return [ Social_Templates_Conditional::class ];
}
/**
* Retrieves the relevant social title from the options.
*
* @param string $title The default title.
*
* @return mixed|string The filtered value.
*/
public function filter_title( $title ) {
$social_title = $this->options->get( $this::OPTION_TITLES_KEY_TITLE );
if ( ! empty( $social_title ) ) {
$title = $social_title;
}
return $title;
}
/**
* Retrieves the relevant social description from the options.
*
* @param string $description The default description.
*
* @return mixed|string The filtered value.
*/
public function filter_description( $description ) {
$social_description = $this->options->get( $this::OPTION_TITLES_KEY_DESCRIPTION );
if ( ! empty( $social_description ) ) {
$description = $social_description;
}
return $description;
}
/**
* Retrieves the relevant social image ID from the options.
*
* @param int $id The default image ID.
*
* @return mixed|int The filtered value.
*/
public function filter_image_id( $id ) {
$social_id = $this->options->get( $this::OPTION_TITLES_KEY_IMAGE_ID );
if ( ! empty( $social_id ) ) {
$id = $social_id;
}
return $id;
}
/**
* Retrieves the relevant social image URL from the options.
*
* @param string $url The default image URL.
*
* @return mixed|int The filtered value.
*/
public function filter_image( $url ) {
$social_url = $this->options->get( $this::OPTION_TITLES_KEY_IMAGE );
if ( ! empty( $social_url ) ) {
$url = $social_url;
}
return $url;
}
/**
* Retrieves the relevant social title for the subtype from the options.
*
* @param string $title The default title.
* @param string $object_subtype The subtype of the current indexable.
*
* @return mixed|string The filtered value.
*/
public function filter_title_for_subtype( $title, $object_subtype ) {
$social_title = $this->options->get( $this::OPTION_TITLES_KEY_TITLE . $object_subtype );
if ( ! empty( $social_title ) ) {
$title = $social_title;
}
return $title;
}
/**
* Retrieves the relevant social description for the subtype from the options.
*
* @param string $description The default description.
* @param string $object_subtype The subtype of the current indexable.
*
* @return mixed|string The filtered value.
*/
public function filter_description_for_subtype( $description, $object_subtype ) {
$social_description = $this->options->get( $this::OPTION_TITLES_KEY_DESCRIPTION . $object_subtype );
if ( ! empty( $social_description ) ) {
$description = $social_description;
}
return $description;
}
/**
* Retrieves the relevant social image ID for the subtype from the options.
*
* @param int $id The default image ID.
* @param string $object_subtype The subtype of the current indexable.
*
* @return mixed|string The filtered value.
*/
public function filter_image_id_for_subtype( $id, $object_subtype ) {
$social_id = $this->options->get( $this::OPTION_TITLES_KEY_IMAGE_ID . $object_subtype );
if ( ! empty( $social_id ) ) {
$id = $social_id;
}
return $id;
}
/**
* Retrieves the relevant social image URL for the subtype from the options.
*
* @param string $url The default image URL.
* @param string $object_subtype The subtype of the current indexable.
*
* @return mixed|string The filtered value.
*/
public function filter_image_for_subtype( $url, $object_subtype ) {
$social_url = $this->options->get( $this::OPTION_TITLES_KEY_IMAGE . $object_subtype );
if ( ! empty( $social_url ) ) {
$url = $social_url;
}
return $url;
}
}

View File

@@ -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>';
}
}

View File

@@ -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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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>';
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,74 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
namespace Yoast\WP\SEO\Integrations\Blocks;
use Yoast\WP\SEO\Conditionals\Schema_Blocks_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Schema_Templates\Block_Patterns\Block_Pattern;
use Yoast\WP\SEO\Schema_Templates\Block_Patterns\Block_Pattern_Categories;
/**
* Registers the block patterns needed for the Premium Schema blocks.
*/
class Block_Patterns implements Integration_Interface {
/**
* The block patterns to register.
*
* @var Block_Pattern[]
*/
protected $block_patterns = [];
/**
* Block_Patterns integration constructor.
*
* @param Block_Pattern ...$block_patterns The block patterns to register.
*/
public function __construct( Block_Pattern ...$block_patterns ) {
$this->block_patterns = $block_patterns;
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_action( 'admin_init', [ $this, 'register_block_pattern_category' ] );
\add_action( 'admin_init', [ $this, 'register_block_patterns' ] );
}
/**
* Returns the list of conditionals.
*
* @return array The conditionals.
*/
public static function get_conditionals() {
return [ Schema_Blocks_Conditional::class ];
}
/**
* Registers the block patterns with WordPress.
*
* @return void
*/
public function register_block_patterns() {
foreach ( $this->block_patterns as $block_pattern ) {
\register_block_pattern( $block_pattern->get_name(), $block_pattern->get_configuration() );
}
}
/**
* Registers the block pattern category with WordPress.
*
* @return void
*/
public function register_block_pattern_category() {
$category = [ 'label' => \__( 'Yoast Job Posting', 'wordpress-seo-premium' ) ];
\register_block_pattern_category( Block_Pattern_Categories::YOAST_JOB_POSTING, $category );
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace Yoast\WP\SEO\Integrations\Blocks;
/**
* Estimated_Reading_Time_Block class.
*/
class Estimated_Reading_Time_Block extends Dynamic_Block {
/**
* The name of the block.
*
* @var string
*/
protected $block_name = 'estimated-reading-time';
/**
* Holds the clock icon HTML.
*
* @var string
*/
protected $clock_icon = '<span class="yoast-reading-time__icon"><svg aria-hidden="true" focusable="false" data-icon="clock" width="20" height="20" fill="none" stroke="currentColor" style="display:inline-block;vertical-align:-0.1em" role="img" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg></span><span class="yoast-reading-time__spacer" style="display:inline-block;width:1em"></span>';
/**
* The editor script for the block.
*
* @var string
*/
protected $script = 'wp-seo-premium-dynamic-blocks';
/**
* Registers the block.
*
* @return void
*/
public function register_block() {
\register_block_type(
'yoast-seo/' . $this->block_name,
[
'editor_script' => $this->script,
'render_callback' => [ $this, 'present' ],
'attributes' => [
'className' => [
'default' => '',
'type' => 'string',
],
'estimatedReadingTime' => [
'type' => 'number',
'default' => 0,
],
'descriptiveText' => [
'type' => 'string',
'default' => \__( 'Estimated reading time:', 'wordpress-seo-premium' ) . ' ',
],
'showDescriptiveText' => [
'type' => 'boolean',
'default' => true,
],
'showIcon' => [
'type' => 'boolean',
'default' => true,
],
],
]
);
}
/**
* Presents the block output.
*
* @param array $attributes The block attributes.
* @param string $content The content.
*
* @return string The block output.
*/
public function present( $attributes, $content = '' ) {
if ( $attributes['showIcon'] ) {
// Replace 15.7 icon placeholder.
$content = \preg_replace(
'/ICON_PLACEHOLDER/',
$this->clock_icon,
$content,
1
);
// Replace the 15.8+ icon placeholder.
return \preg_replace(
'/<span class="yoast-reading-time__icon"><\/span>/',
$this->clock_icon,
$content,
1
);
}
return $content;
}
}

View File

@@ -0,0 +1,53 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
namespace Yoast\WP\SEO\Integrations\Blocks;
use Yoast\WP\SEO\Conditionals\Schema_Blocks_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Adds the block categories for the Jobs Posting block.
*/
class Job_Posting_Block implements Integration_Interface {
/**
* Registers the hooks.
*/
public function register_hooks() {
\add_filter( 'block_categories', [ $this, 'add_block_categories' ] );
}
/**
* Returns the list of conditionals.
*
* @return array The conditionals.
*/
public static function get_conditionals() {
return [
Schema_Blocks_Conditional::class,
];
}
/**
* Adds Yoast block categories.
*
* @param array $categories The categories to filter.
*
* @return array The filtered categories.
*/
public function add_block_categories( $categories ) {
$categories[] = [
'slug' => 'yoast-required-job-blocks',
'title' => \__( 'Required Job Posting Blocks', 'wordpress-seo-premium' ),
];
$categories[] = [
'slug' => 'yoast-recommended-job-blocks',
'title' => \__( 'Recommended Job Posting Blocks', 'wordpress-seo-premium' ),
];
return $categories;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Yoast\WP\SEO\Integrations\Blocks;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Related content block class.
*/
class Related_Links_Block implements Integration_Interface {
use No_Conditionals;
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\register_block_type( 'yoast-seo/related-links', [ 'editor_script' => 'wp-seo-premium-blocks' ] );
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Yoast\WP\SEO\Integrations\Blocks;
use WPSEO_Admin_Asset_Manager;
use Yoast\WP\SEO\Conditionals\Schema_Blocks_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Loads the Premium schema block templates into Gutenberg.
*/
class Schema_Blocks implements Integration_Interface {
/**
* Represents the asset manager.
*
* @var WPSEO_Admin_Asset_Manager
*/
protected $asset_manager;
/**
* Schema_Blocks constructor.
*
* @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager.
*/
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager ) {
$this->asset_manager = $asset_manager;
}
/**
* Returns the list of conditionals.
*
* @return array The conditionals.
*/
public static function get_conditionals() {
return [
Schema_Blocks_Conditional::class,
];
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_filter( 'wpseo_load_schema_templates', [ $this, 'add_premium_templates' ] );
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
}
/**
* Collects the Premium structured data blocks templates.
*
* @param array $templates The templates from Yoast SEO.
*
* @return array All the templates that should be loaded.
*/
public function add_premium_templates( $templates ) {
$premium_schema_templates_path = \WPSEO_PREMIUM_PATH . 'src/schema-templates/';
$premium_templates = \glob( $premium_schema_templates_path . '*.php' );
return \array_merge( $templates, $premium_templates );
}
/**
* Enqueues the schema blocks css file.
*/
public function enqueue_assets() {
\wp_enqueue_script( 'wp-seo-premium-schema-blocks' );
$this->asset_manager->enqueue_style( 'premium-schema-blocks' );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Yoast\WP\SEO\Premium\Integrations;
/**
* Class OpenGraph_Author_Archive.
*/
class OpenGraph_Author_Archive extends Abstract_OpenGraph_Integration {
/**
* The name of the social title option.
*
* @var string
*/
const OPTION_TITLES_KEY_TITLE = 'social-title-author-wpseo';
/**
* The name of the social description option.
*
* @var string
*/
const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-author-wpseo';
/**
* The name of the social image ID option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-author-wpseo';
/**
* The name of the social image URL option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE = 'social-image-url-author-wpseo';
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_filter( 'Yoast\WP\SEO\open_graph_title_user', [ $this, 'filter_title' ] );
\add_filter( 'Yoast\WP\SEO\open_graph_description_user', [ $this, 'filter_description' ] );
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_user', [ $this, 'filter_image_id' ] );
\add_filter( 'Yoast\WP\SEO\open_graph_image_user', [ $this, 'filter_image' ] );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Yoast\WP\SEO\Premium\Integrations;
/**
* Class OpenGraph_Date_Archive.
*/
class OpenGraph_Date_Archive extends Abstract_OpenGraph_Integration {
/**
* The name of the social title option.
*
* @var string
*/
const OPTION_TITLES_KEY_TITLE = 'social-title-archive-wpseo';
/**
* The name of the social description option.
*
* @var string
*/
const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-archive-wpseo';
/**
* The name of the social image ID option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-archive-wpseo';
/**
* The name of the social image URL option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE = 'social-image-url-archive-wpseo';
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_filter( 'Yoast\WP\SEO\open_graph_title_date-archive', [ $this, 'filter_title' ] );
\add_filter( 'Yoast\WP\SEO\open_graph_description_date-archive', [ $this, 'filter_description' ] );
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_date-archive', [ $this, 'filter_image_id' ] );
\add_filter( 'Yoast\WP\SEO\open_graph_image_date-archive', [ $this, 'filter_image' ] );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Yoast\WP\SEO\Premium\Integrations;
/**
* Class OpenGraph_Post_Type.
*/
class OpenGraph_Post_Type extends Abstract_OpenGraph_Integration {
/**
* The prefix for the social title option.
*
* @var string
*/
const OPTION_TITLES_KEY_TITLE = 'social-title-';
/**
* The prefix for the social description option.
*
* @var string
*/
const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-';
/**
* The prefix for the social image ID option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-';
/**
* The prefix for the social image URL option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE = 'social-image-url-';
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_filter( 'Yoast\WP\SEO\open_graph_title_post', [ $this, 'filter_title_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_description_post', [ $this, 'filter_description_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_post', [ $this, 'filter_image_id_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_image_post', [ $this, 'filter_image_for_subtype' ], 10, 2 );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Yoast\WP\SEO\Premium\Integrations;
/**
* Class OpenGraph_PostType_Archive.
*/
class OpenGraph_PostType_Archive extends Abstract_OpenGraph_Integration {
/**
* The prefix for the social title option.
*
* @var string
*/
const OPTION_TITLES_KEY_TITLE = 'social-title-ptarchive-';
/**
* The prefix for the social description option.
*
* @var string
*/
const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-ptarchive-';
/**
* The prefix for the social image ID option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-ptarchive-';
/**
* The prefix for the social image URL option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE = 'social-image-url-ptarchive-';
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_filter( 'Yoast\WP\SEO\open_graph_title_post-type-archive', [ $this, 'filter_title_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_description_post-type-archive', [ $this, 'filter_description_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_post-type-archive', [ $this, 'filter_image_id_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_image_post-type-archive', [ $this, 'filter_image_for_subtype' ], 10, 2 );
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Yoast\WP\SEO\Premium\Integrations;
/**
* Class OpenGraph_Term_Archive.
*/
class OpenGraph_Term_Archive extends Abstract_OpenGraph_Integration {
/**
* The prefix for the social title option.
*
* @var string
*/
const OPTION_TITLES_KEY_TITLE = 'social-title-tax-';
/**
* The prefix for the social description option.
*
* @var string
*/
const OPTION_TITLES_KEY_DESCRIPTION = 'social-description-tax-';
/**
* The prefix for the social image ID option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE_ID = 'social-image-id-tax-';
/**
* The prefix for the social image URL option.
*
* @var string
*/
const OPTION_TITLES_KEY_IMAGE = 'social-image-url-tax-';
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_filter( 'Yoast\WP\SEO\open_graph_title_term', [ $this, 'filter_title_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_description_term', [ $this, 'filter_description_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_image_id_term', [ $this, 'filter_image_id_for_subtype' ], 10, 2 );
\add_filter( 'Yoast\WP\SEO\open_graph_image_term', [ $this, 'filter_image_for_subtype' ], 10, 2 );
}
}

View File

@@ -0,0 +1,330 @@
<?php
namespace Yoast\WP\SEO\Integrations\Third_Party;
use WP_Post;
use WPSEO_Admin_Asset_Yoast_Components_L10n;
use WPSEO_Capability_Utils;
use WPSEO_Custom_Fields_Plugin;
use WPSEO_Language_Utils;
use WPSEO_Metabox;
use WPSEO_Metabox_Analysis_SEO;
use WPSEO_Options;
use WPSEO_Post_Type;
use WPSEO_Post_Watcher;
use WPSEO_Premium_Asset_JS_L10n;
use WPSEO_Premium_Assets;
use WPSEO_Premium_Prominent_Words_Support;
use WPSEO_Social_Previews;
use WPSEO_Utils;
use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional;
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
use Yoast\WP\SEO\Integrations\Admin\Prominent_Words\Indexing_Integration;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Elementor integration class for Yoast SEO Premium.
*/
class Elementor_Premium implements Integration_Interface {
/**
* Holds the script handle.
*
* @var string
*/
const SCRIPT_HANDLE = 'elementor-premium';
/**
* Represents the post.
*
* @var WP_Post|null
*/
protected $post;
/**
* Represents the post watcher.
*
* @var WPSEO_Post_Watcher
*/
protected $post_watcher;
/**
* The prominent words helper.
*
* @var Prominent_Words_Helper
*/
protected $prominent_words_helper;
/**
* Returns the conditionals based in which this loadable should be active.
*
* @return array
*/
public static function get_conditionals() {
return [ Elementor_Edit_Conditional::class ];
}
/**
* Constructs the class.
*
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
*/
public function __construct( Prominent_Words_Helper $prominent_words_helper ) {
$this->prominent_words_helper = $prominent_words_helper;
$this->post_watcher = new WPSEO_Post_Watcher();
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue' ] );
\add_action( 'post_updated', [ $this->post_watcher, 'detect_slug_change' ], 12, 3 );
}
/**
* Enqueues all the needed JS and CSS.
*
* @return void
*/
public function enqueue() {
// Check if we should load.
if ( ! $this->load_metabox() ) {
return;
}
// Re-register assets as Elementor unregister everything.
$asset_manager = new WPSEO_Premium_Assets();
$asset_manager->register_assets();
// Initialize Elementor (replaces premium-metabox).
$this->enqueue_assets();
/*
* Re-enqueue the integrations as `admin_enqueue_scripts` is undone.
* Note the register_hooks were not even called (because it doesn't work anyway).
*/
$social_previews = new WPSEO_Social_Previews();
$social_previews->enqueue_assets();
$custom_fields = new WPSEO_Custom_Fields_Plugin();
$custom_fields->enqueue();
}
// Below is mostly copied from `premium-metabox.php`.
/**
* Enqueues assets when relevant.
*
* @codeCoverageIgnore Method uses dependencies.
*
* @return void
*/
public function enqueue_assets() {
\wp_enqueue_script( static::SCRIPT_HANDLE );
\wp_enqueue_style( static::SCRIPT_HANDLE );
$localization = new WPSEO_Admin_Asset_Yoast_Components_L10n();
$localization->localize_script( static::SCRIPT_HANDLE );
$premium_localization = new WPSEO_Premium_Asset_JS_L10n();
$premium_localization->localize_script( static::SCRIPT_HANDLE );
$this->send_data_to_assets();
}
/**
* Send data to assets by using wp_localize_script.
*
* @return void
*/
public function send_data_to_assets() {
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
$data = [
'restApi' => $this->get_rest_api_config(),
'seoAnalysisEnabled' => $analysis_seo->is_enabled(),
'licensedURL' => WPSEO_Utils::get_home_url(),
'settingsPageUrl' => \admin_url( 'admin.php?page=wpseo_dashboard#top#features' ),
'integrationsTabURL' => \admin_url( 'admin.php?page=wpseo_dashboard#top#integrations' ),
];
$data = \array_merge( $data, $this->get_post_metabox_config() );
// Use an extra level in the array to preserve booleans. WordPress sanitizes scalar values in the first level of the array.
\wp_localize_script( static::SCRIPT_HANDLE, 'wpseoPremiumMetaboxData', [ 'data' => $data ] );
}
/**
* Retrieves the metabox config for a post.
*
* @return array The config.
*/
protected function get_post_metabox_config() {
$insights_enabled = WPSEO_Options::get( 'enable_metabox_insights', false );
$link_suggestions_enabled = WPSEO_Options::get( 'enable_link_suggestions', false );
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
if ( ! $prominent_words_support->is_post_type_supported( $this->get_metabox_post()->post_type ) ) {
$insights_enabled = false;
}
$site_locale = \get_locale();
$language = WPSEO_Language_Utils::get_language( $site_locale );
return [
'insightsEnabled' => ( $insights_enabled ) ? 'enabled' : 'disabled',
'currentObjectId' => $this->get_metabox_post()->ID,
'currentObjectType' => 'post',
'linkSuggestionsEnabled' => ( $link_suggestions_enabled ) ? 'enabled' : 'disabled',
'linkSuggestionsAvailable' => $prominent_words_support->is_post_type_supported( $this->get_metabox_post()->post_type ),
'linkSuggestionsUnindexed' => ! $this->is_prominent_words_indexing_completed() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
'perIndexableLimit' => $this->per_indexable_limit( $language ),
];
}
/**
* 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;
}
/**
* 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|null The post for the current admin page.
*/
protected function get_metabox_post() {
if ( $this->post !== null ) {
return $this->post;
}
$post = \filter_input( \INPUT_GET, 'post' );
if ( ! empty( $post ) ) {
$post_id = (int) WPSEO_Utils::validate_int( $post );
$this->post = \get_post( $post_id );
return $this->post;
}
if ( isset( $GLOBALS['post'] ) ) {
$this->post = $GLOBALS['post'];
return $this->post;
}
return null;
}
/**
* Checks whether or not the metabox related scripts should be loaded.
*
* @return bool True when it should be loaded.
*/
protected function load_metabox() {
// When the current page isn't a post related one.
if ( WPSEO_Metabox::is_post_edit( $this->get_current_page() ) ) {
return WPSEO_Post_Type::has_metabox_enabled( $this->get_current_post_type() );
}
// Make sure ajax integrations are loaded.
return \wp_doing_ajax();
}
/**
* Retrieves the current post type.
*
* @codeCoverageIgnore It depends on external request input.
*
* @return string The post type.
*/
protected function get_current_post_type() {
$post = \filter_input( \INPUT_GET, 'post', \FILTER_SANITIZE_STRING );
if ( $post ) {
return \get_post_type( \get_post( $post ) );
}
return \filter_input(
\INPUT_GET,
'post_type',
\FILTER_SANITIZE_STRING,
[
'options' => [
'default' => 'post',
],
]
);
}
/**
* Retrieves the value of the pagenow variable.
*
* @codeCoverageIgnore
*
* @return string The value of pagenow.
*/
protected 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;
}
}

View File

@@ -0,0 +1,188 @@
<?php
namespace Yoast\WP\SEO\Integrations\Third_Party;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Helpers\Date_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Allows to download translations from TranslationsPress.
*/
class TranslationsPress implements Integration_Interface {
use No_Conditionals;
/**
* The plugin slug to retrieve the translations for.
*
* @var string
*/
protected $slug = 'wordpress-seo-premium';
/**
* The key of the custom transient where to store the translations info.
*
* @var string
*/
protected $transient_key;
/**
* The URL for the TranslationsPress API service.
*
* @var string
*/
protected $api_url;
/**
* The Date helper object.
*
* @var Date_Helper
*/
protected $date_helper;
/**
* Adds a new project to load translations for.
*
* @param Date_Helper $date_helper The Date Helper object.
*/
public function __construct( Date_Helper $date_helper ) {
$this->transient_key = 'yoast_translations_' . $this->slug;
$this->api_url = 'https://packages.translationspress.com/yoast/' . $this->slug . '/packages.json';
$this->date_helper = $date_helper;
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_action( 'init', [ $this, 'register_clean_translations_cache' ], \PHP_INT_MAX );
\add_filter( 'translations_api', [ $this, 'translations_api' ], 10, 3 );
\add_filter( 'site_transient_update_plugins', [ $this, 'site_transient_update_plugins' ] );
}
/**
* Short-circuits translations API requests for private projects.
*
* @param bool|array $result The result object. Default false.
* @param string $requested_type The type of translations being requested.
* @param object $args Translation API arguments.
*
* @return bool|array The translations array. False by default.
*/
public function translations_api( $result, $requested_type, $args ) {
if ( $requested_type === 'plugins' && $args['slug'] === $this->slug ) {
return $this->get_translations();
}
return $result;
}
/**
* Filters the translations transients to include the private plugin or theme.
*
* @param bool|object $value The transient value.
*
* @return object The filtered transient value.
*/
public function site_transient_update_plugins( $value ) {
if ( ! $value ) {
$value = new \stdClass();
}
if ( ! isset( $value->translations ) ) {
$value->translations = [];
}
$translations = $this->get_translations();
if ( empty( $translations[ $this->slug ]['translations'] ) ) {
return $value;
}
$installed_translations = \wp_get_installed_translations( 'plugins' );
$available_languages = \get_available_languages();
foreach ( $translations[ $this->slug ]['translations'] as $translation ) {
if ( ! \in_array( $translation['language'], $available_languages, true ) ) {
continue;
}
if ( isset( $installed_translations[ $this->slug ][ $translation['language'] ] ) && $translation['updated'] ) {
$local = new \DateTime( $installed_translations[ $this->slug ][ $translation['language'] ]['PO-Revision-Date'] );
$remote = new \DateTime( $translation['updated'] );
if ( $local >= $remote ) {
continue;
}
}
$translation['type'] = 'plugin';
$translation['slug'] = $this->slug;
$translation['autoupdate'] = true;
$value->translations[] = $translation;
}
return $value;
}
/**
* Registers actions for clearing translation caches.
*
* @return void
*/
public function register_clean_translations_cache() {
\add_action( 'set_site_transient_update_plugins', [ $this, 'clean_translations_cache' ] );
\add_action( 'delete_site_transient_update_plugins', [ $this, 'clean_translations_cache' ] );
}
/**
* Clears existing translation cache.
*
* @return void
*/
public function clean_translations_cache() {
$translations = \get_site_transient( $this->transient_key );
if ( ! \is_array( $translations ) ) {
return;
}
$cache_lifespan = \DAY_IN_SECONDS;
$time_not_changed = isset( $translations['_last_checked'] ) && ( $this->date_helper->current_time() - $translations['_last_checked'] ) > $cache_lifespan;
if ( ! $time_not_changed ) {
return;
}
\delete_site_transient( $this->transient_key );
}
/**
* Gets the translations for a given project.
*
* @return array The translation data.
*/
public function get_translations() {
$translations = \get_site_transient( $this->transient_key );
if ( $translations !== false && \is_array( $translations ) ) {
return $translations;
}
$translations = [];
$result = \json_decode( \wp_remote_retrieve_body( \wp_remote_get( $this->api_url ) ), true );
// Nothing found.
if ( ! \is_array( $result ) ) {
$result = [];
}
$translations[ $this->slug ] = $result;
$translations['_last_checked'] = $this->date_helper->current_time();
\set_site_transient( $this->transient_key, $translations );
return $translations;
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Yoast\WP\SEO\Integrations\Third_Party;
use WP_Post;
use WPSEO_Admin_Utils;
use Yoast\WP\SEO\Conditionals\Zapier_Enabled_Conditional;
use Yoast\WP\SEO\Helpers\Zapier_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Class to manage the Zapier integration in the Classic editor.
*/
class Zapier_Classic_Editor implements Integration_Interface {
/**
* The Zapier helper.
*
* @var Zapier_Helper
*/
protected $zapier_helper;
/**
* Zapier constructor.
*
* @param Zapier_Helper $zapier_helper The Zapier helper.
*/
public function __construct( Zapier_Helper $zapier_helper ) {
$this->zapier_helper = $zapier_helper;
}
/**
* Returns the conditionals based in which this loadable should be active.
*
* @return array
*/
public static function get_conditionals() {
return [ Zapier_Enabled_Conditional::class ];
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_action( 'wpseo_publishbox_misc_actions', [ $this, 'add_publishbox_text' ] );
}
/**
* Adds the Zapier text to the Classic Editor publish box.
*
* @param WP_Post $post The current post object.
*
* @return void
*/
public function add_publishbox_text( WP_Post $post ) {
if ( ! $this->zapier_helper->is_post_type_supported( $post->post_type )
|| $this->zapier_helper->is_connected() ) {
return;
}
?>
<div class="misc-pub-section yoast yoast-zapier-text">
<svg class="yoast-zapier-text__icon" role="img" aria-hidden="true" focusable="false" width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M159.999 128.056a76.55 76.55 0 0 1-4.915 27.024 76.745 76.745 0 0 1-27.032 4.923h-.108c-9.508-.012-18.618-1.75-27.024-4.919A76.557 76.557 0 0 1 96 128.056v-.112a76.598 76.598 0 0 1 4.91-27.02A76.492 76.492 0 0 1 127.945 96h.108a76.475 76.475 0 0 1 27.032 4.923 76.51 76.51 0 0 1 4.915 27.02v.112zm94.223-21.389h-74.716l52.829-52.833a128.518 128.518 0 0 0-13.828-16.349v-.004a129 129 0 0 0-16.345-13.816l-52.833 52.833V1.782A128.606 128.606 0 0 0 128.064 0h-.132c-7.248.004-14.347.62-21.265 1.782v74.716L53.834 23.665A127.82 127.82 0 0 0 37.497 37.49l-.028.02A128.803 128.803 0 0 0 23.66 53.834l52.837 52.833H1.782S0 120.7 0 127.956v.088c0 7.256.615 14.367 1.782 21.289h74.716l-52.837 52.833a128.91 128.91 0 0 0 30.173 30.173l52.833-52.837v74.72a129.3 129.3 0 0 0 21.24 1.778h.181a129.15 129.15 0 0 0 21.24-1.778v-74.72l52.838 52.837a128.994 128.994 0 0 0 16.341-13.82l.012-.012a129.245 129.245 0 0 0 13.816-16.341l-52.837-52.833h74.724c1.163-6.91 1.77-14 1.778-21.24v-.186c-.008-7.24-.615-14.33-1.778-21.24z" fill="#FF4A00"/></svg>
<span>
<?php
\printf(
/* translators: 1: Link start tag, 2: Yoast SEO, 3: Zapier, 4: Link closing tag. */
\esc_html__( '%1$sConnect %2$s with %3$s%4$s to instantly share your published posts with 2000+ destinations such as Twitter, Facebook and more.', 'wordpress-seo-premium' ),
'<a href="' . \esc_url( \admin_url( 'admin.php?page=wpseo_dashboard#top#integrations' ) ) . '" target="_blank">',
'Yoast SEO',
'Zapier',
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The content is already escaped.
WPSEO_Admin_Utils::get_new_tab_message() . '</a>'
);
?>
</span>
</div>
<?php
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Yoast\WP\SEO\Integrations\Third_Party;
use WP_Error;
use Yoast\WP\SEO\Conditionals\Zapier_Enabled_Conditional;
use Yoast\WP\SEO\Helpers\Meta_Helper;
use Yoast\WP\SEO\Helpers\Zapier_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Models\Indexable;
/**
* Class to manage the triggering of the Zapier integration.
*/
class Zapier_Trigger implements Integration_Interface {
/**
* The meta helper.
*
* @var Meta_Helper
*/
protected $meta_helper;
/**
* The Zapier helper.
*
* @var Zapier_Helper
*/
protected $zapier_helper;
/**
* Zapier constructor.
*
* @param Meta_Helper $meta_helper The meta helper.
* @param Zapier_Helper $zapier_helper The Zapier helper.
*/
public function __construct( Meta_Helper $meta_helper, Zapier_Helper $zapier_helper ) {
$this->meta_helper = $meta_helper;
$this->zapier_helper = $zapier_helper;
}
/**
* Returns the conditionals based in which this loadable should be active.
*
* @return array
*/
public static function get_conditionals() {
return [ Zapier_Enabled_Conditional::class ];
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_action( 'wpseo_save_indexable', [ $this, 'maybe_call_zapier' ] );
}
/**
* Decides if Zapier should be triggered.
*
* Zapier should be triggered only if:
* - we have a connection established
* - the item is a post (in the Indexable sense, as opposed to taxonomies etc.)
* - the item status is 'publish'
* - we are not serving a REST request (to avoid triggering on the first request by the block editor)
* - if the item hasn't been sent before
* - if the post_date is recent (so we are not just updating a post published before enabling Zapier)
*
* @param Indexable $indexable The indexable.
*
* @return void
*/
public function maybe_call_zapier( Indexable $indexable ) {
if ( ! $this->zapier_helper->is_connected()
|| $indexable->object_type !== 'post'
|| $indexable->post_status !== 'publish'
|| \defined( 'REST_REQUEST' ) && \REST_REQUEST
|| $this->meta_helper->get_value( 'zapier_trigger_sent', $indexable->object_id ) === '1' ) {
return;
}
// All dates are GMT to prevent failing checks due to timezone differences.
$post = \get_post( $indexable->object_id );
$published_datetime_gmt = \strtotime( $post->post_date_gmt . ' +0000' );
$half_an_hour_ago_datetime_gmt = ( \time() - ( \MINUTE_IN_SECONDS * 30 ) );
if ( ! $this->zapier_helper->is_post_type_supported( $post->post_type )
|| $published_datetime_gmt < $half_an_hour_ago_datetime_gmt ) {
return;
}
$this->call_zapier( $indexable );
}
/**
* Sends a request to the Zapier trigger hook.
*
* @param Indexable $indexable The indexable.
*
* @return void
*/
public function call_zapier( Indexable $indexable ) {
$trigger_url = $this->zapier_helper->get_trigger_url();
$zapier_data = $this->zapier_helper->get_data_for_zapier( $indexable );
$response = \wp_remote_post(
$trigger_url,
[
'body' => $zapier_data,
]
);
if ( ! $response instanceof WP_Error ) {
// Need to cast the new value to a string as booleans aren't supported.
$this->meta_helper->set_value( 'zapier_trigger_sent', '1', $indexable->object_id );
}
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace Yoast\WP\SEO\Integrations\Third_Party;
use WPSEO_Admin_Asset_Manager;
use WPSEO_Admin_Utils;
use Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional;
use Yoast\WP\SEO\Helpers\Zapier_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
/**
* Zapier integration class for managing the toggle.
*/
class Zapier implements Integration_Interface {
/**
* The Zapier dashboard URL.
*
* @var string
*/
const ZAPIER_DASHBOARD_URL = 'https://zapier.com/app/zaps';
/**
* Represents the admin asset manager.
*
* @var WPSEO_Admin_Asset_Manager
*/
protected $asset_manager;
/**
* The Zapier helper.
*
* @var Zapier_Helper
*/
protected $zapier_helper;
/**
* Returns the conditionals based in which this loadable should be active.
*
* @return array
*/
public static function get_conditionals() {
return [ Yoast_Admin_And_Dashboard_Conditional::class ];
}
/**
* Zapier constructor.
*
* @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager.
* @param Zapier_Helper $zapier_helper The Zapier helper.
*/
public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Zapier_Helper $zapier_helper ) {
$this->asset_manager = $asset_manager;
$this->zapier_helper = $zapier_helper;
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
// Add the Zapier toggle to the Integrations tab in the admin.
\add_filter( 'wpseo_integration_toggles', [ $this, 'add_integration_toggle' ] );
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
}
/**
* Enqueues the required assets.
*
* @return void
*/
public function enqueue_assets() {
$this->asset_manager->enqueue_style( 'monorepo' );
\wp_enqueue_script( 'clipboard' );
}
/**
* Adds integration toggles to the Integrations to be loaded.
*
* @param array $integration_toggles The feature toggles to extend.
*
* @return array
*/
public function add_integration_toggle( array $integration_toggles ) {
$integration_toggles[] = (object) [
/* translators: %s: Zapier. */
'name' => \sprintf( \esc_html__( '%s integration', 'wordpress-seo-premium' ), 'Zapier' ),
'setting' => 'zapier_integration_active',
'label' => \sprintf(
/* translators: 1: Yoast SEO, 2: Zapier. */
\__( 'Connecting %1$s to %2$s means you can instantly share your published posts with 2000+ destinations such as Twitter, Facebook and more.', 'wordpress-seo-premium' ),
'Yoast SEO',
'Zapier'
),
/* translators: %s: Zapier. */
'read_more_label' => \sprintf( \__( 'Read more about %s.', 'wordpress-seo-premium' ), 'Zapier' ),
'read_more_url' => 'https://yoa.st/46o',
'after' => $this->toggle_after(),
'order' => 20, // The SEMrush integration on Free has order => 10.
];
return $integration_toggles;
}
/**
* Returns additional content to be displayed after the Zapier toggle.
*
* @return string The additional content.
*/
private function toggle_after() {
if ( $this->zapier_helper->is_connected() ) {
return $this->get_connected_content();
}
return $this->get_not_connected_content();
}
/**
* Returns additional content to be displayed when Zapier is connected.
*
* @return string The additional content.
*/
private function get_connected_content() {
$alert = new Alert_Presenter(
\sprintf(
/* translators: 1: Yoast SEO, 2: Zapier. */
\esc_html__( '%1$s is successfully connected to %2$s!', 'wordpress-seo-premium' ),
'Yoast SEO',
'Zapier'
),
'success'
);
$output = '<div id="zapier-connection">';
$output .= $alert->present();
$output .= '<p><a href="' . self::ZAPIER_DASHBOARD_URL . '" class="yoast-button yoast-button--primary" type="button" target="_blank">' . \sprintf(
/* translators: %s: Zapier. */
\esc_html__( 'Go to your %s Dashboard', 'wordpress-seo-premium' ),
'Zapier'
) . WPSEO_Admin_Utils::get_new_tab_message() . '</a></p>';
$output .= '<p>' . \sprintf(
/* translators: 1: Zapier, 2: The Zapier API Key. */
\esc_html__( '%1$s uses this API Key: %2$s', 'wordpress-seo-premium' ),
'Zapier',
'<strong>' . $this->zapier_helper->get_or_generate_zapier_api_key() . '</strong>'
) . '</p>';
$output .= '<p><button name="zapier_api_key_reset" value="1" type="submit" class="yoast-button yoast-button--secondary">' . \esc_html__( 'Reset API Key', 'wordpress-seo-premium' ) . '</button></p>';
$output .= '</div>';
return $output;
}
/**
* Returns additional content to be displayed when Zapier is not connected.
*
* @return string The additional content.
*/
private function get_not_connected_content() {
$content = \sprintf(
/* translators: 1: Yoast SEO, 2: Zapier, 3: Emphasis open tag, 4: Emphasis close tag. */
\esc_html__( '%1$s is not connected to %2$s. To set up a connection, make sure you click %3$sSave changes%4$s first, then copy the given API key below and use it to %3$screate%4$s and %3$sturn on%4$s a Zap within your %2$s account.', 'wordpress-seo-premium' ),
'Yoast SEO',
'Zapier',
'<em>',
'</em>'
);
$content .= '<br/><br/>';
$content .= ' ' . \sprintf(
/* translators: 1: Yoast SEO. */
\esc_html__( 'Please note that you can only create 1 Zap with a trigger event from %1$s. Within this Zap you can choose one or more actions.', 'wordpress-seo-premium' ),
'Yoast SEO'
);
$alert = new Alert_Presenter(
$content,
'info'
);
$output = '<div id="zapier-connection">';
$output .= $alert->present();
$output .= '<div class="yoast-field-group">';
$output .= '<div class="yoast-field-group__title yoast-field-group__title--light">';
$output .= '<label for="zapier-api-key">' . \sprintf(
/* translators: %s: Zapier. */
\esc_html__( '%s will ask for an API key. Use this one:', 'wordpress-seo-premium' ),
'Zapier'
) . '</label>';
$output .= '</div>';
$output .= '<div class="yoast-field-group__inline">';
$output .= '<input class="yoast-field-group__inputfield" readonly type="text" id="zapier-api-key" name="wpseo[zapier_api_key]" value="' . $this->zapier_helper->get_or_generate_zapier_api_key() . '">';
$output .= '<button type="button" class="yoast-button yoast-button--secondary" id="copy-zapier-api-key" data-clipboard-target="#zapier-api-key">' . \esc_html__( 'Copy to clipboard', 'wordpress-seo-premium' ) . '</button><br />';
$output .= '</div>';
$output .= '</div>';
$output .= '<p><a href="' . self::ZAPIER_DASHBOARD_URL . '" class="yoast-button yoast-button--primary" type="button" target="_blank">' . \sprintf(
/* translators: %s: Zapier. */
\esc_html__( 'Create a Zap in %s', 'wordpress-seo-premium' ),
'Zapier'
) . WPSEO_Admin_Utils::get_new_tab_message() . '</a></p>';
$output .= '</div>';
return $output;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Yoast\WP\SEO\Premium\Integrations;
use WPSEO_Upgrade_Manager;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Class Upgrade_Integration.
*/
class Upgrade_Integration implements Integration_Interface {
use No_Conditionals;
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_action( 'init', [ $this, 'run_upgrade' ], 11 );
}
/**
* Run the upgrade for Yoast SEO Premium.
*/
public function run_upgrade() {
$upgrade_manager = new WPSEO_Upgrade_Manager();
$upgrade_manager->run_upgrade( \WPSEO_PREMIUM_VERSION );
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Yoast\WP\SEO\Premium\Integrations;
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Class User_Profile_Integration.
*/
class User_Profile_Integration implements Integration_Interface {
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_filter( 'wpseo_schema_person_data', [ $this, 'filter_person_schema' ], 10, 2 );
}
/**
* Filters the Person schema to add our stored user schema fields.
*
* To see which fields we can store see this class's sibling admin integration.
*
* @param array $data The Person schema data.
* @param int $user_id The user ID.
*
* @return array The Person schema data.
*/
public function filter_person_schema( $data, $user_id ) {
$user_schema = \get_user_meta( $user_id, 'wpseo_user_schema', true );
if ( \is_array( $user_schema ) ) {
$data = \array_merge( $data, $user_schema );
}
return $data;
}
/**
* Returns the conditionals based on which this loadable should be active.
*
* @return array The conditionals.
*/
public static function get_conditionals() {
return [ Front_End_Conditional::class ];
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Yoast\WP\SEO\Integrations\Watchers;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Watcher for the wpseo option on Premium.
*
* Represents the option wpseo watcher for Premium.
*/
class Premium_Option_Wpseo_Watcher implements Integration_Interface {
use No_Conditionals;
/**
* The options helper.
*
* @var Options_Helper
*/
private $options;
/**
* Watcher constructor.
*
* @param Options_Helper $options The options helper.
*/
public function __construct( Options_Helper $options ) {
$this->options = $options;
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_action( 'update_option_wpseo', [ $this, 'check_zapier_option_disabled' ], 10, 2 );
}
/**
* Checks if the Zapier integration is disabled; if so, deletes the data.
*
* @param array $old_value The old value of the option.
* @param array $new_value The new value of the option.
*
* @return bool Whether the Zapier data has been deleted or not.
*/
public function check_zapier_option_disabled( $old_value, $new_value ) {
if ( \array_key_exists( 'zapier_integration_active', $new_value )
&& $old_value['zapier_integration_active'] === true
&& $new_value['zapier_integration_active'] === false ) {
$this->options->set( 'zapier_subscription', [] );
$this->options->set( 'zapier_api_key', '' );
return true;
}
return false;
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Yoast\WP\SEO\Integrations\Watchers;
use Yoast\WP\SEO\Conditionals\Zapier_Enabled_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
/**
* Watcher for resetting the Zapier API key.
*
* Represents the Zapier API key reset watcher for Premium.
*/
class Zapier_APIKey_Reset_Watcher implements Integration_Interface {
/**
* The options helper.
*
* @var Options_Helper
*/
private $options;
/**
* Watcher constructor.
*
* @param Options_Helper $options The options helper.
*/
public function __construct( Options_Helper $options ) {
$this->options = $options;
}
/**
* Returns the conditionals based in which this loadable should be active.
*
* @return array
*/
public static function get_conditionals() {
return [ Zapier_Enabled_Conditional::class ];
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
\add_action( 'admin_init', [ $this, 'zapier_api_key_reset' ], 10 );
}
/**
* Checks if the Zapier API key must be reset; if so, deletes the data.
*
* @return bool Whether the Zapier data has been deleted or not.
*/
public function zapier_api_key_reset() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- The nonce is already validated.
if ( isset( $_POST['zapier_api_key_reset'] ) && $_POST['zapier_api_key_reset'] === '1' ) {
$this->options->set( 'zapier_api_key', '' );
$this->options->set( 'zapier_subscription', [] );
return true;
}
return false;
}
}