first commit
This commit is contained in:
@@ -0,0 +1,535 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions;
|
||||
|
||||
use WP_Query;
|
||||
use WPSEO_Premium_Prominent_Words_Support;
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Prominent_Words_Repository;
|
||||
|
||||
/**
|
||||
* Handles the actual requests to the prominent words endpoints.
|
||||
*/
|
||||
class Link_Suggestions_Action {
|
||||
|
||||
/**
|
||||
* The repository to retrieve prominent words from.
|
||||
*
|
||||
* @var Prominent_Words_Repository
|
||||
*/
|
||||
protected $prominent_words_repository;
|
||||
|
||||
/**
|
||||
* The repository to retrieve indexables from.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* Contains helper functions for calculating with and comparing prominent words.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Represents the prominent words support class.
|
||||
*
|
||||
* @var WPSEO_Premium_Prominent_Words_Support
|
||||
*/
|
||||
protected $prominent_words_support;
|
||||
|
||||
/**
|
||||
* Link_Suggestions_Service constructor.
|
||||
*
|
||||
* @param Prominent_Words_Repository $prominent_words_repository The repository to retrieve prominent words from.
|
||||
* @param Indexable_Repository $indexable_repository The repository to retrieve indexables from.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper Class with helper methods for prominent words.
|
||||
* @param WPSEO_Premium_Prominent_Words_Support $prominent_words_support The prominent words support class.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Repository $prominent_words_repository,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Prominent_Words_Helper $prominent_words_helper,
|
||||
WPSEO_Premium_Prominent_Words_Support $prominent_words_support
|
||||
) {
|
||||
$this->prominent_words_repository = $prominent_words_repository;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
$this->prominent_words_support = $prominent_words_support;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests a list of links, based on the given array of prominent words.
|
||||
*
|
||||
* @param array $words_from_request The prominent words as an array mapping words to weights.
|
||||
* @param int $limit The maximum number of link suggestions to retrieve.
|
||||
* @param int $object_id The object id for the current indexable.
|
||||
* @param string $object_type The object type for the current indexable.
|
||||
*
|
||||
* @return array Links for the post that are suggested.
|
||||
*/
|
||||
public function get_suggestions( $words_from_request, $limit, $object_id, $object_type ) {
|
||||
$current_indexable_id = null;
|
||||
$current_indexable = $this->indexable_repository->find_by_id_and_type( $object_id, $object_type );
|
||||
if ( $current_indexable ) {
|
||||
$current_indexable_id = $current_indexable->id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets best suggestions (returns a sorted array [$indexable_id => score]).
|
||||
* The indexables are processed in batches of 100 indexables each.
|
||||
*/
|
||||
$suggestions_scores = $this->retrieve_suggested_indexable_ids( $words_from_request, $limit, 100, $current_indexable_id );
|
||||
|
||||
$indexable_ids = \array_keys( $suggestions_scores );
|
||||
|
||||
// Return the empty list if no suggestions have been found.
|
||||
if ( empty( $indexable_ids ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Retrieve indexables for suggestions.
|
||||
$suggestions_indexables = $this->indexable_repository->query()->where_id_in( $indexable_ids )->find_many();
|
||||
|
||||
/**
|
||||
* Filter 'wpseo_link_suggestions_indexables' - Allow filtering link suggestions indexable objects.
|
||||
*
|
||||
* @api array An array of suggestion indexables that can be filtered.
|
||||
*
|
||||
* @param int $object_id The object id for the current indexable.
|
||||
* @param string $object_type The object type for the current indexable.
|
||||
*/
|
||||
$suggestions_indexables = \apply_filters( 'wpseo_link_suggestions_indexables', $suggestions_indexables, $object_id, $object_type );
|
||||
|
||||
// Create suggestions objects.
|
||||
return $this->create_suggestions( $suggestions_indexables, $suggestions_scores );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the titles of the posts with the given IDs.
|
||||
*
|
||||
* @param array $post_ids The IDs of the posts to retrieve the titles of.
|
||||
*
|
||||
* @return array An array mapping post ID to title.
|
||||
*/
|
||||
protected function retrieve_posts( $post_ids ) {
|
||||
$query = new WP_Query(
|
||||
[
|
||||
'post_type' => $this->prominent_words_support->get_supported_post_types(),
|
||||
'post__in' => $post_ids,
|
||||
'posts_per_page' => \count( $post_ids ),
|
||||
]
|
||||
);
|
||||
$posts = $query->get_posts();
|
||||
|
||||
$post_data = [];
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
$post_data[ $post->ID ] = [
|
||||
'title' => $post->post_title,
|
||||
];
|
||||
}
|
||||
|
||||
return $post_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the names of the terms with the given IDs.
|
||||
*
|
||||
* @param Indexable[] $indexables The indexables to retrieve titles for.
|
||||
*
|
||||
* @return array An array mapping term ID to title.
|
||||
*/
|
||||
protected function retrieve_terms( $indexables ) {
|
||||
$data = [];
|
||||
foreach ( $indexables as $indexable ) {
|
||||
if ( $indexable->object_type !== 'term' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$term = \get_term_by( 'term_id', $indexable->object_id, $indexable->object_sub_type );
|
||||
|
||||
$data[ $indexable->object_id ] = [
|
||||
'title' => $term->name,
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the titles of the given array of indexables.
|
||||
*
|
||||
* @param Indexable[] $indexables An array of indexables for which to retrieve the titles.
|
||||
*
|
||||
* @return array A two-dimensional array mapping object type and object id to title.
|
||||
*/
|
||||
protected function retrieve_object_titles( $indexables ) {
|
||||
$object_ids = [];
|
||||
|
||||
foreach ( $indexables as $indexable ) {
|
||||
if ( \array_key_exists( $indexable->object_type, $object_ids ) ) {
|
||||
$object_ids[ $indexable->object_type ][] = $indexable->object_id;
|
||||
}
|
||||
else {
|
||||
$object_ids[ $indexable->object_type ] = [ $indexable->object_id ];
|
||||
}
|
||||
}
|
||||
|
||||
$objects = [
|
||||
'post' => [],
|
||||
'term' => [],
|
||||
];
|
||||
|
||||
// At the moment we only support internal linking for posts, so we only need the post titles.
|
||||
if ( \array_key_exists( 'post', $object_ids ) ) {
|
||||
$objects['post'] = $this->retrieve_posts( $object_ids['post'] );
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'term', $object_ids ) ) {
|
||||
$objects['term'] = $this->retrieve_terms( $indexables );
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes, for a given indexable, its raw matching score on the request words to match.
|
||||
* In general, higher scores mean better matches.
|
||||
*
|
||||
* @param array $request_data The words to match, as an array containing stems, weights and dfs.
|
||||
* @param array $candidate_data The words to match against, as an array of `Prominent_Words` objects.
|
||||
*
|
||||
* @return float A raw score of the indexable.
|
||||
*/
|
||||
protected function compute_raw_score( $request_data, $candidate_data ) {
|
||||
$raw_score = 0;
|
||||
|
||||
foreach ( $candidate_data as $stem => $candidate_word_data ) {
|
||||
if ( ! \array_key_exists( $stem, $request_data ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$word_from_request_weight = $request_data[ $stem ]['weight'];
|
||||
$word_from_request_df = $request_data[ $stem ]['df'];
|
||||
$candidate_weight = $candidate_word_data['weight'];
|
||||
$candidate_df = $candidate_word_data['df'];
|
||||
|
||||
$tf_idf_word_from_request = $this->prominent_words_helper->compute_tf_idf_score( $word_from_request_weight, $word_from_request_df );
|
||||
$tf_idf_word_from_database = $this->prominent_words_helper->compute_tf_idf_score( $candidate_weight, $candidate_df );
|
||||
|
||||
// Score on this word is the product of the tf-idf scores.
|
||||
$raw_score += ( $tf_idf_word_from_request * $tf_idf_word_from_database );
|
||||
}
|
||||
|
||||
return $raw_score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines weight data of the request words to their document frequencies. This is needed to calculate
|
||||
* vector length of the request data.
|
||||
*
|
||||
* @param array $request_words An array mapping words to weights.
|
||||
*
|
||||
* @return array An array mapping stems, weights and dfs.
|
||||
*/
|
||||
protected function compose_request_data( $request_words ) {
|
||||
$request_doc_frequencies = $this->prominent_words_repository->count_document_frequencies( \array_keys( $request_words ) );
|
||||
$combined_request_data = [];
|
||||
foreach ( $request_words as $stem => $weight ) {
|
||||
if ( ! isset( $request_doc_frequencies[ $stem ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$combined_request_data[ $stem ] = [
|
||||
'weight' => (int) $weight,
|
||||
'df' => $request_doc_frequencies[ $stem ],
|
||||
];
|
||||
}
|
||||
|
||||
return $combined_request_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the array of prominent words into an array of objects mapping indexable_id to an array
|
||||
* of prominent words associated with this indexable_id, with each prominent word's stem as a key.
|
||||
*
|
||||
* @param array $words The array of prominent words, with indexable_id as one of the keys.
|
||||
*
|
||||
* @return array An array mapping indexable IDs to their prominent words.
|
||||
*/
|
||||
protected function group_words_by_indexable_id( $words ) {
|
||||
$candidates_words_by_indexable_ids = [];
|
||||
foreach ( $words as $word ) {
|
||||
$indexable_id = $word->indexable_id;
|
||||
|
||||
$candidates_words_by_indexable_ids[ $indexable_id ][ $word->stem ] = [
|
||||
'weight' => (int) $word->weight,
|
||||
'df' => (int) $word->df,
|
||||
];
|
||||
}
|
||||
|
||||
return $candidates_words_by_indexable_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a matching score for one candidate indexable.
|
||||
*
|
||||
* @param array $request_data An array matching stems from request to their weights and dfs.
|
||||
* @param float $request_vector_length The vector length of the request words.
|
||||
* @param array $candidate_data An array matching stems from the candidate to their weights and dfs.
|
||||
*
|
||||
* @return float A matching score for an indexable.
|
||||
*/
|
||||
protected function calculate_score_for_indexable( $request_data, $request_vector_length, $candidate_data ) {
|
||||
$raw_score = $this->compute_raw_score( $request_data, $candidate_data );
|
||||
$candidate_vector_length = $this->prominent_words_helper->compute_vector_length( $candidate_data );
|
||||
return $this->normalize_score( $raw_score, $candidate_vector_length, $request_vector_length );
|
||||
}
|
||||
|
||||
/**
|
||||
* In the prominent words repository, find a $batch_size of all ProminentWord-IndexableID pairs where
|
||||
* prominent words match the set of stems we are interested in.
|
||||
* Request prominent words for indexables in the batch (including the iDF of all words) to calculate
|
||||
* their vector length later.
|
||||
*
|
||||
* @param array $stems The stems in the request.
|
||||
* @param int $batch_size How many indexables to request in one query.
|
||||
* @param int $page The start of the current batch (in pages).
|
||||
*
|
||||
* @return array An array of ProminentWords objects, containing their stem, weight, indexable id,
|
||||
* and document frequency.
|
||||
*/
|
||||
protected function get_candidate_words( $stems, $batch_size, $page ) {
|
||||
return $this->prominent_words_repository->find_by_list_of_ids(
|
||||
$this->prominent_words_repository->find_ids_by_stems( $stems, $batch_size, $page )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* For each candidate indexable, computes their matching score related to the request set of prominent words.
|
||||
* The candidate indexables are analyzed in batches.
|
||||
* After having computed scores for a batch the function saves the best candidates until now.
|
||||
*
|
||||
* @param array $request_words The words to match, as an array mapping words to weights.
|
||||
* @param int $limit The max number of suggestions that should be returned by the function.
|
||||
* @param int $batch_size The number of indexables that should be analyzed in every batch.
|
||||
* @param int|null $current_indexable_id The id for the current indexable.
|
||||
*
|
||||
* @return array An array mapping indexable IDs to scores. Higher scores mean better matches.
|
||||
*/
|
||||
protected function retrieve_suggested_indexable_ids( $request_words, $limit, $batch_size, $current_indexable_id ) {
|
||||
// Combine stems, weights and DFs from request.
|
||||
$request_data = $this->compose_request_data( $request_words );
|
||||
|
||||
// Calculate vector length of the request set (needed for score normalization later).
|
||||
$request_vector_length = $this->prominent_words_helper->compute_vector_length( $request_data );
|
||||
|
||||
$request_stems = \array_keys( $request_data );
|
||||
$scores = [];
|
||||
$page = 1;
|
||||
|
||||
do {
|
||||
// Retrieve the words of all indexables in this batch that share prominent word stems with request.
|
||||
$candidates_words = $this->get_candidate_words( $request_stems, $batch_size, $page );
|
||||
|
||||
// Transform the prominent words table so that it is indexed by indexable_ids.
|
||||
$candidates_words_by_indexable_ids = $this->group_words_by_indexable_id( $candidates_words );
|
||||
|
||||
$batch_scores_size = 0;
|
||||
|
||||
foreach ( $candidates_words_by_indexable_ids as $id => $candidate_data ) {
|
||||
$scores[ $id ] = $this->calculate_score_for_indexable( $request_data, $request_vector_length, $candidate_data );
|
||||
++$batch_scores_size;
|
||||
}
|
||||
|
||||
if ( $current_indexable_id && isset( $scores[ $current_indexable_id ] ) ) {
|
||||
unset( $scores[ $current_indexable_id ] );
|
||||
}
|
||||
|
||||
// Sort the list of scores and keep only the top $limit of the scores.
|
||||
$scores = $this->get_top_suggestions( $scores, $limit );
|
||||
|
||||
++$page;
|
||||
} while ( $batch_scores_size === $batch_size );
|
||||
|
||||
return $scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the raw score based on the length of the prominent word vectors.
|
||||
*
|
||||
* @param float $raw_score The raw (non-normalized) score.
|
||||
* @param float $vector_length_candidate The vector lengths of the candidate indexable.
|
||||
* @param float $vector_length_request The vector length of the words from the request.
|
||||
*
|
||||
* @return int|float The score, normalized on vector lengths.
|
||||
*/
|
||||
protected function normalize_score( $raw_score, $vector_length_candidate, $vector_length_request ) {
|
||||
$normalizing_factor = ( $vector_length_request * $vector_length_candidate );
|
||||
|
||||
if ( $normalizing_factor === 0.0 ) {
|
||||
// We can't divide by 0, so set the score to 0 instead.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ( $raw_score / $normalizing_factor );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the indexable ids based on the score and returns the top N indexable ids based on a specified limit.
|
||||
* (Returns all indexable ids if there are less indexable ids than specified by the limit.)
|
||||
*
|
||||
* @param array $scores The array matching indexable ids to their scores.
|
||||
* @param int $limit The maximum number of indexables that should be returned.
|
||||
*
|
||||
* @return array The top N indexable ids, sorted from highest to lowest score.
|
||||
*/
|
||||
protected function get_top_suggestions( $scores, $limit ) {
|
||||
// Sort the indexables by descending score.
|
||||
\uasort(
|
||||
$scores,
|
||||
static function( $score_1, $score_2 ) {
|
||||
if ( $score_1 === $score_2 ) {
|
||||
return 0;
|
||||
}
|
||||
return ( ( $score_1 < $score_2 ) ? 1 : -1 );
|
||||
}
|
||||
);
|
||||
|
||||
// Take the top $limit suggestions, while preserving their ids specified in the keys of the array elements.
|
||||
return \array_slice( $scores, 0, $limit, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the singular label of the given combination of object type and sub type.
|
||||
*
|
||||
* @param string $object_type An object type. For example 'post' or 'term'.
|
||||
* @param string $object_sub_type An object sub type. For example 'page' or 'category'.
|
||||
*
|
||||
* @return string The singular label of the given combination of object type and sub type,
|
||||
* or the empty string if the singular label does not exist.
|
||||
*/
|
||||
protected function get_sub_type_singular_label( $object_type, $object_sub_type ) {
|
||||
switch ( $object_type ) {
|
||||
case 'post':
|
||||
$post_type = \get_post_type_object( $object_sub_type );
|
||||
if ( $post_type ) {
|
||||
return $post_type->labels->singular_name;
|
||||
}
|
||||
break;
|
||||
case 'term':
|
||||
$taxonomy = \get_taxonomy( $object_sub_type );
|
||||
if ( $taxonomy ) {
|
||||
return $taxonomy->labels->singular_name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates link suggestion data based on the indexables that should be suggested and the scores for these
|
||||
* indexables.
|
||||
*
|
||||
* @param Indexable[] $indexables The indexables for which to create linking suggestions.
|
||||
* @param array $scores The scores for the linking suggestions.
|
||||
*
|
||||
* @return array The internal linking suggestions.
|
||||
*/
|
||||
protected function create_suggestions( $indexables, $scores ) {
|
||||
$objects = $this->retrieve_object_titles( $indexables );
|
||||
$link_suggestions = [];
|
||||
|
||||
foreach ( $indexables as $indexable ) {
|
||||
if ( ! \array_key_exists( $indexable->object_type, $objects ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Object tied to this indexable. E.g. post, page, term.
|
||||
if ( ! \array_key_exists( $indexable->object_id, $objects[ $indexable->object_type ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$link_suggestions[] = [
|
||||
'object_type' => $indexable->object_type,
|
||||
'id' => (int) ( $indexable->object_id ),
|
||||
'title' => $objects[ $indexable->object_type ][ $indexable->object_id ]['title'],
|
||||
'link' => $indexable->permalink,
|
||||
'isCornerstone' => (bool) $indexable->is_cornerstone,
|
||||
'labels' => $this->get_labels( $indexable ),
|
||||
'score' => \round( (float) ( $scores[ $indexable->id ] ), 2 ),
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Because the request to the indexables table messes up with the ordering of the suggestions,
|
||||
* we have to sort again.
|
||||
*/
|
||||
$this->sort_suggestions_by_field( $link_suggestions, 'score' );
|
||||
|
||||
$cornerstone_suggestions = $this->filter_suggestions( $link_suggestions, true );
|
||||
$non_cornerstone_suggestions = $this->filter_suggestions( $link_suggestions, false );
|
||||
|
||||
return \array_merge_recursive( [], $cornerstone_suggestions, $non_cornerstone_suggestions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the labels for the link suggestion.
|
||||
*
|
||||
* @param Indexable $indexable The indexable to determine the labels for.
|
||||
*
|
||||
* @return array The labels.
|
||||
*/
|
||||
protected function get_labels( Indexable $indexable ) {
|
||||
$labels = [];
|
||||
if ( $indexable->is_cornerstone ) {
|
||||
$labels[] = 'cornerstone';
|
||||
}
|
||||
|
||||
$labels[] = $this->get_sub_type_singular_label( $indexable->object_type, $indexable->object_sub_type );
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the given link suggestion by field.
|
||||
*
|
||||
* @param array $link_suggestions The link suggestions to sort.
|
||||
* @param string $field The field to sort suggestions by.
|
||||
*/
|
||||
protected function sort_suggestions_by_field( array &$link_suggestions, $field ) {
|
||||
\usort(
|
||||
$link_suggestions,
|
||||
static function( $suggestion_1, $suggestion_2 ) use ( $field ) {
|
||||
if ( $suggestion_1[ $field ] === $suggestion_2[ $field ] ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ( ( $suggestion_1[ $field ] < $suggestion_2[ $field ] ) ? 1 : -1 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the suggestions by cornerstone status.
|
||||
*
|
||||
* @param array $link_suggestions The suggestions to filter.
|
||||
* @param bool $cornerstone Whether or not to include the cornerstone suggestions.
|
||||
*
|
||||
* @return array The filtered suggestions.
|
||||
*/
|
||||
protected function filter_suggestions( $link_suggestions, $cornerstone ) {
|
||||
return \array_filter(
|
||||
$link_suggestions,
|
||||
static function( $suggestion ) use ( $cornerstone ) {
|
||||
return (bool) $suggestion['isCornerstone'] === $cornerstone;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
|
||||
/**
|
||||
* Action for completing the prominent words indexing.
|
||||
*/
|
||||
class Complete_Action {
|
||||
|
||||
/**
|
||||
* Represents the prominent words helper.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Complete_Action constructor.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indexing state to complete.
|
||||
*/
|
||||
public function complete() {
|
||||
$this->prominent_words_helper->complete_indexing();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use WPSEO_Premium_Prominent_Words_Support;
|
||||
use WPSEO_Premium_Prominent_Words_Versioning;
|
||||
use Yoast\WP\Lib\ORM;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface;
|
||||
use Yoast\WP\SEO\Context\Meta_Tags_Context;
|
||||
use Yoast\WP\SEO\Helpers\Meta_Helper;
|
||||
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Retrieves the indexable data and Yoast SEO metadata (meta-description, SEO title, keywords and synonyms)
|
||||
* from the database.
|
||||
*/
|
||||
class Content_Action implements Indexation_Action_Interface {
|
||||
|
||||
const TRANSIENT_CACHE_KEY = 'total_unindexed_prominent_words';
|
||||
|
||||
/**
|
||||
* An instance of the WPSEO_Premium_Prominent_Words_Support.
|
||||
*
|
||||
* @var WPSEO_Premium_Prominent_Words_Support An instance of the WPSEO_Premium_Prominent_Words_Support
|
||||
*/
|
||||
protected $prominent_words_support;
|
||||
|
||||
/**
|
||||
* Reference to the indexable repository to retrieve outdated indexables.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* The meta tags context memoizer.
|
||||
*
|
||||
* @var Meta_Tags_Context_Memoizer
|
||||
*/
|
||||
protected $memoizer;
|
||||
|
||||
/**
|
||||
* The meta value helper.
|
||||
*
|
||||
* @var Meta_Helper
|
||||
*/
|
||||
protected $meta;
|
||||
|
||||
/**
|
||||
* Holds the object sub types.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $object_sub_types;
|
||||
|
||||
/**
|
||||
* Content_Action constructor.
|
||||
*
|
||||
* @param WPSEO_Premium_Prominent_Words_Support $prominent_words_support An instance of
|
||||
* WPSEO_Premium_Prominent_Words_Support.
|
||||
* @param Indexable_Repository $indexable_repository An instance of Indexable_Repository.
|
||||
* @param Meta_Tags_Context_Memoizer $memoizer The meta tags context memoizer.
|
||||
* @param Meta_Helper $meta The meta value helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Premium_Prominent_Words_Support $prominent_words_support,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Meta_Tags_Context_Memoizer $memoizer,
|
||||
Meta_Helper $meta
|
||||
) {
|
||||
$this->prominent_words_support = $prominent_words_support;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->memoizer = $memoizer;
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of indexables to be indexed for internal linking suggestions in one batch.
|
||||
*
|
||||
* @return int The number of indexables to be indexed in one batch.
|
||||
*/
|
||||
public function get_limit() {
|
||||
/**
|
||||
* Filter 'wpseo_prominent_words_indexation_limit' - Allow filtering the amount of indexables indexed during each indexing pass.
|
||||
*
|
||||
* @api int The maximum number of indexables indexed.
|
||||
*/
|
||||
$limit = \apply_filters( 'wpseo_prominent_words_indexation_limit', 25 );
|
||||
|
||||
if ( ! \is_int( $limit ) || $limit < 1 ) {
|
||||
$limit = 25;
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of indexables without prominent words.
|
||||
*
|
||||
* @return int|false The total number of indexables without prominent words. False if the query fails.
|
||||
*/
|
||||
public function get_total_unindexed() {
|
||||
$object_sub_types = $this->get_object_sub_types();
|
||||
if ( empty( $object_sub_types ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This prevents an expensive query.
|
||||
$total_unindexed = \get_transient( static::TRANSIENT_CACHE_KEY );
|
||||
if ( $total_unindexed !== false ) {
|
||||
return (int) $total_unindexed;
|
||||
}
|
||||
|
||||
// Try a less expensive query first: check if the indexable table holds any indexables.
|
||||
// If not, no need to perform a query on the prominent words version and more.
|
||||
if ( ! $this->at_least_one_indexable() ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Run the expensive query to find out the exact number and store it for later use.
|
||||
$total_unindexed = $this->query()->count();
|
||||
\set_transient( static::TRANSIENT_CACHE_KEY, $total_unindexed, \DAY_IN_SECONDS );
|
||||
|
||||
return $total_unindexed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a batch of indexables, to be indexed for internal linking suggestions.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array The indexables data to use for generating prominent words.
|
||||
*/
|
||||
public function get() {
|
||||
\_deprecated_function( __METHOD__, '15.1', 'Content_Action::index' );
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a batch of indexables, to be indexed for internal linking suggestions.
|
||||
*
|
||||
* @return array The indexables data to use for generating prominent words.
|
||||
*/
|
||||
public function index() {
|
||||
$object_sub_types = $this->get_object_sub_types();
|
||||
if ( empty( $object_sub_types ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$indexables = $this
|
||||
->query()
|
||||
->limit( $this->get_limit() )
|
||||
->find_many();
|
||||
|
||||
\delete_transient( static::TRANSIENT_CACHE_KEY );
|
||||
|
||||
// If no indexables have been left unindexed, return the empty array.
|
||||
if ( ! $indexables ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->format_data( $indexables );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query that can find indexables with outdated prominent words.
|
||||
*
|
||||
* @return ORM Returns an ORM instance that can be used to execute the query.
|
||||
*/
|
||||
protected function query() {
|
||||
$updated_version = WPSEO_Premium_Prominent_Words_Versioning::get_version_number();
|
||||
|
||||
return $this->indexable_repository
|
||||
->query()
|
||||
->where_in( 'object_type', [ 'post', 'term' ] )
|
||||
->where_in( 'object_sub_type', $this->get_object_sub_types() )
|
||||
->where_raw( '(`prominent_words_version` IS NULL OR `prominent_words_version` != ' . $updated_version . ')' )
|
||||
->where_raw( '((`post_status` IS NULL AND `object_type` = \'term\') OR (`post_status` = \'publish\' AND `object_type` = \'post\'))' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query that checks whether the indexable table holds at least one record.
|
||||
*
|
||||
* @return bool true if at the database contains at least one indexable.
|
||||
*/
|
||||
protected function at_least_one_indexable() {
|
||||
return $this->indexable_repository
|
||||
->query()
|
||||
->select( 'id' )
|
||||
->find_one() !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of subtypes to get indexables for.
|
||||
*
|
||||
* @return array The array with object subtypes.
|
||||
*/
|
||||
protected function get_object_sub_types() {
|
||||
if ( $this->object_sub_types === null ) {
|
||||
$this->object_sub_types = \array_merge(
|
||||
$this->prominent_words_support->get_supported_post_types(),
|
||||
$this->prominent_words_support->get_supported_taxonomies()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->object_sub_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the data of the given array of indexables, so it can be used to generate prominent words.
|
||||
*
|
||||
* @param Indexable[] $indexables The indexables to gather data for.
|
||||
*
|
||||
* @return array The data.
|
||||
*/
|
||||
protected function format_data( $indexables ) {
|
||||
$data = [];
|
||||
foreach ( $indexables as $indexable ) {
|
||||
// Use the meta context, so we are sure that the data is the same as is output on the frontend.
|
||||
$context = $this->get_context( $indexable );
|
||||
|
||||
if ( ! $context ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[] = [
|
||||
'object_id' => $indexable->object_id,
|
||||
'object_type' => $indexable->object_type,
|
||||
'content' => $this->get_content( $context ),
|
||||
'meta' => [
|
||||
'primary_focus_keyword' => $context->indexable->primary_focus_keyword,
|
||||
'title' => $context->title,
|
||||
'description' => $context->description,
|
||||
'keyphrase_synonyms' => $this->retrieve_keyphrase_synonyms( $context->indexable ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the context for the current indexable.
|
||||
*
|
||||
* @param Indexable $indexable The indexable to get context for.
|
||||
*
|
||||
* @return Meta_Tags_Context|null The context object.
|
||||
*/
|
||||
protected function get_context( $indexable ) {
|
||||
if ( $indexable->object_type === 'post' ) {
|
||||
return $this->memoizer->get( $indexable, 'Post_Type' );
|
||||
}
|
||||
|
||||
if ( $indexable->object_type === 'term' ) {
|
||||
return $this->memoizer->get( $indexable, 'Term_Archive' );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the keyphrase synonyms for the indexable.
|
||||
*
|
||||
* @param Indexable $indexable The indexable to retrieve synonyms for.
|
||||
*
|
||||
* @return string[] The keyphrase synonyms.
|
||||
*/
|
||||
protected function retrieve_keyphrase_synonyms( $indexable ) {
|
||||
if ( $indexable->object_type === 'post' ) {
|
||||
return \json_decode( $this->meta->get_value( 'keywordsynonyms', $indexable->object_id ) );
|
||||
}
|
||||
|
||||
if ( $indexable->object_type === 'term' ) {
|
||||
return \json_decode( $this->meta->get_term_value( $indexable->object_id, $indexable->object_sub_type, 'wpseo_keywordsynonyms' ) );
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the content to use.
|
||||
*
|
||||
* @param Meta_Tags_Context $context The meta tags context object.
|
||||
*
|
||||
* @return string The content associated with the given context.
|
||||
*/
|
||||
protected function get_content( Meta_Tags_Context $context ) {
|
||||
if ( $context->indexable->object_type === 'post' ) {
|
||||
global $post;
|
||||
|
||||
/*
|
||||
* Set the global $post to be the post in this iteration.
|
||||
* This is required for post-specific shortcodes that reference the global post.
|
||||
*/
|
||||
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly.
|
||||
$post = $context->post;
|
||||
|
||||
// Set up WordPress data for this post, outside of "the_loop".
|
||||
\setup_postdata( $post );
|
||||
|
||||
// Wraps in output buffering to prevent shortcodes that echo stuff instead of return from breaking things.
|
||||
\ob_start();
|
||||
$content = \do_shortcode( $post->post_content );
|
||||
\ob_end_clean();
|
||||
|
||||
\wp_reset_postdata();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
if ( $context->indexable->object_type === 'term' ) {
|
||||
$term = \get_term( $context->indexable->object_id, $context->indexable->object_sub_type );
|
||||
if ( $term === null || \is_wp_error( $term ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Wraps in output buffering to prevent shortcodes that echo stuff instead of return from breaking things.
|
||||
\ob_start();
|
||||
$description = \do_shortcode( $term->description );
|
||||
\ob_end_clean();
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions\Prominent_Words;
|
||||
|
||||
use Exception;
|
||||
use WPSEO_Premium_Prominent_Words_Versioning;
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Models\Prominent_Words;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Prominent_Words_Repository;
|
||||
|
||||
/**
|
||||
* Action for linking a list of prominent words to an indexable.
|
||||
*
|
||||
* @see \Yoast\WP\SEO\Routes\Prominent_Words_Route;
|
||||
*/
|
||||
class Save_Action {
|
||||
|
||||
/**
|
||||
* The repository to retrieve and save prominent words with.
|
||||
*
|
||||
* @var Prominent_Words_Repository
|
||||
*/
|
||||
protected $prominent_words_repository;
|
||||
|
||||
/**
|
||||
* The repository to retrieve and save indexables with.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* Contains helper function for prominent words.
|
||||
* For e.g. computing vector lengths and tf-idf scores.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Prominent_Words_Link_Service constructor.
|
||||
*
|
||||
* @param Prominent_Words_Repository $prominent_words_repository The repository to create, read, update and delete
|
||||
* prominent words from.
|
||||
* @param Indexable_Repository $indexable_repository The repository to read, update and delete
|
||||
* indexables from.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Repository $prominent_words_repository,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Prominent_Words_Helper $prominent_words_helper
|
||||
) {
|
||||
$this->prominent_words_repository = $prominent_words_repository;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a list of prominent words to an indexable.
|
||||
*
|
||||
* Deletes the prominent words that have been stored previously, but are not in the new list of prominent words.
|
||||
*
|
||||
* @param array $data The data to process.
|
||||
*/
|
||||
public function save( $data ) {
|
||||
if ( $data ) {
|
||||
foreach ( $data as $row ) {
|
||||
$prominent_words = ( isset( $row['prominent_words'] ) ? $row['prominent_words'] : [] );
|
||||
|
||||
$this->link( $row['object_type'], $row['object_id'], $prominent_words );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a list of prominent words to an indexable.
|
||||
*
|
||||
* @param string $object_type The object type of the indexable (e.g. `post` or `term`).
|
||||
* @param int $object_id The object id of the indexable.
|
||||
* @param array $words The words to link, as a `'stem' => weight` map.
|
||||
*/
|
||||
public function link( $object_type, $object_id, $words ) {
|
||||
$indexable = $this->indexable_repository->find_by_id_and_type( $object_id, $object_type );
|
||||
|
||||
$indexable->prominent_words_version = WPSEO_Premium_Prominent_Words_Versioning::get_version_number();
|
||||
|
||||
/*
|
||||
* It is correct to save here, because find_by_id_and_type will auto create an indexable object
|
||||
* with the correct data. So we are not saving an incomplete indexable.
|
||||
*/
|
||||
$indexable->save();
|
||||
|
||||
$old_words = $this->prominent_words_repository->find_by_indexable_id( $indexable->id );
|
||||
|
||||
foreach ( $old_words as $old_word ) {
|
||||
// Remove when old word isn't found.
|
||||
if ( ! \array_key_exists( $old_word->stem, $words ) ) {
|
||||
$old_word->delete();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->update_weight_if_changed( $old_word, $words[ $old_word->stem ] );
|
||||
unset( $words[ $old_word->stem ] );
|
||||
}
|
||||
|
||||
// Create all new words that are not yet in the database.
|
||||
$this->create_words( $indexable->id, $words );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the weight of the given prominent word.
|
||||
* (Does not update when the weights are the same).
|
||||
*
|
||||
* @param Prominent_Words $word The prominent word of which to update the weight.
|
||||
* @param float $new_weight The new weight.
|
||||
*/
|
||||
protected function update_weight_if_changed( $word, $new_weight ) {
|
||||
if ( $word->weight !== $new_weight ) {
|
||||
$word->weight = $new_weight;
|
||||
$word->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the given words in the database and links them to the indexable with the given id.
|
||||
*
|
||||
* @param int $indexable_id The ID of the indexable.
|
||||
* @param array $words The words to create, as a `'stem'` => weight` map.
|
||||
*/
|
||||
protected function create_words( $indexable_id, $words ) {
|
||||
foreach ( $words as $stem => $weight ) {
|
||||
$new_word = $this->prominent_words_repository->query()->create(
|
||||
[
|
||||
'indexable_id' => $indexable_id,
|
||||
'stem' => $stem,
|
||||
'weight' => $weight,
|
||||
]
|
||||
);
|
||||
|
||||
try {
|
||||
$new_word->save();
|
||||
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- There is nothing to do.
|
||||
} catch ( Exception $exception ) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Actions;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Zapier_Helper;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Handles the actual requests to the Zapier endpoints.
|
||||
*/
|
||||
class Zapier_Action {
|
||||
|
||||
/**
|
||||
* Instance of the Options_Helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The Zapier helper.
|
||||
*
|
||||
* @var Zapier_Helper
|
||||
*/
|
||||
protected $zapier_helper;
|
||||
|
||||
/**
|
||||
* The Indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* Zapier_Action constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The Options Helper.
|
||||
* @param Zapier_Helper $zapier_helper The Zapier helper.
|
||||
* @param Indexable_Repository $indexable_repository The Indexable repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
Zapier_Helper $zapier_helper,
|
||||
Indexable_Repository $indexable_repository
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->zapier_helper = $zapier_helper;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes Zapier and stores the passed URL for later usage.
|
||||
*
|
||||
* @param string $url The URL to subscribe.
|
||||
* @param string $api_key The API key from Zapier to check against the one stored in the options.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function subscribe( $url, $api_key ) {
|
||||
if ( ! $this->zapier_helper->is_valid_api_key( $api_key ) ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key does not match.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
if ( $this->zapier_helper->is_connected() ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'Subscribing failed. A subscription already exists.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
$subscription_data = $this->zapier_helper->subscribe_url( $url );
|
||||
|
||||
if ( ! $subscription_data ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'Subscribing failed.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'data' => $subscription_data,
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes Zapier based on the passed ID.
|
||||
*
|
||||
* @param string $id The ID to unsubscribe.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function unsubscribe( $id ) {
|
||||
if ( ! $this->zapier_helper->is_subscribed_id( $id ) ) {
|
||||
return (object) [
|
||||
'message' => \sprintf( 'Unsubscribing failed. Subscription with ID `%s` does not exist.', $id ),
|
||||
'status' => 404,
|
||||
];
|
||||
}
|
||||
|
||||
if ( ! $this->zapier_helper->unsubscribe_id( $id ) ) {
|
||||
return (object) [
|
||||
'message' => 'Unsubscribing failed. Unable to delete subscription.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'message' => \sprintf( 'Successfully unsubscribed subscription with ID `%s`.', $id ),
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the API key submitted by Zapier.
|
||||
*
|
||||
* @param string $api_key The API key from Zapier to check against the one
|
||||
* stored in the options.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function check_api_key( $api_key ) {
|
||||
if ( ! $this->zapier_helper->is_valid_api_key( $api_key ) ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key does not match.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key is valid.',
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an array of the last published post URLs.
|
||||
*
|
||||
* @param string $api_key The API key from Zapier to check against the one
|
||||
* stored in the options.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function perform_list( $api_key ) {
|
||||
if ( ! $this->zapier_helper->is_valid_api_key( $api_key ) ) {
|
||||
return (object) [
|
||||
'data' => [],
|
||||
'message' => 'The API key does not match.',
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
$latest_post = \get_posts(
|
||||
[
|
||||
'numberposts' => 1,
|
||||
]
|
||||
);
|
||||
$zapier_data = [];
|
||||
foreach ( $latest_post as $item ) {
|
||||
$indexable = $this->indexable_repository->find_by_id_and_type( $item->ID, 'post' );
|
||||
$zapier_data[] = (object) $this->zapier_helper->get_data_for_zapier( $indexable );
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'data' => $zapier_data,
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class responsible for performing the premium as an addon installer.
|
||||
*/
|
||||
class Addon_Installer {
|
||||
|
||||
/**
|
||||
* The option key for tracking the status of the installer.
|
||||
*/
|
||||
const OPTION_KEY = 'yoast_premium_as_an_addon_installer';
|
||||
|
||||
/**
|
||||
* The minimum Yoast SEO version required.
|
||||
*/
|
||||
const MINIMUM_YOAST_SEO_VERSION = '16.4';
|
||||
|
||||
/**
|
||||
* The base directory for the installer.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base_dir;
|
||||
|
||||
/**
|
||||
* The detected Yoast SEO version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $yoast_seo_version = '0';
|
||||
|
||||
/**
|
||||
* The detected Yoast SEO plugin file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $yoast_seo_file = 'wordpress-seo/wp-seo.php';
|
||||
|
||||
/**
|
||||
* The detected Yoast SEO directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $yoast_seo_dir = \WP_PLUGIN_DIR . '/wordpress-seo';
|
||||
|
||||
/**
|
||||
* Addon installer constructor.
|
||||
*
|
||||
* @param string $base_dir The base directory.
|
||||
*/
|
||||
public function __construct( $base_dir ) {
|
||||
$this->base_dir = $base_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the installer if it hasn't been done yet.
|
||||
* Otherwise attempts to load Yoast SEO from the vendor directory.
|
||||
*
|
||||
* A notice will be shown in the admin if the installer failed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function install_or_load_yoast_seo_from_vendor_directory() {
|
||||
\add_action( 'admin_notices', [ $this, 'show_install_yoast_seo_notification' ] );
|
||||
\add_action( 'network_admin_notices', [ $this, 'show_install_yoast_seo_notification' ] );
|
||||
\add_action( 'plugins_loaded', [ $this, 'validate_installation_status' ] );
|
||||
if ( ! $this->get_status() ) {
|
||||
try {
|
||||
$this->install();
|
||||
} catch ( Exception $e ) {
|
||||
$this->load_yoast_seo_from_vendor_directory();
|
||||
}
|
||||
}
|
||||
elseif ( $this->get_status() === 'started' ) {
|
||||
require_once \ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$this->detect_yoast_seo();
|
||||
if ( \is_plugin_active( $this->yoast_seo_file ) ) {
|
||||
// Yoast SEO is active so mark installation as successful.
|
||||
\update_option( self::OPTION_KEY, 'completed', true );
|
||||
return;
|
||||
}
|
||||
$this->load_yoast_seo_from_vendor_directory();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a notification to install Yoast SEO.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show_install_yoast_seo_notification() {
|
||||
if ( ! $this->should_show_notification() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once \ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$this->detect_yoast_seo();
|
||||
|
||||
$action = $this->get_notification_action();
|
||||
|
||||
if ( ! $action ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo (
|
||||
'<div class="error">'
|
||||
. '<p>'
|
||||
. \sprintf(
|
||||
/* translators: %1$s: Yoast SEO, %2$s: The minimum Yoast SEO version required, %3$s: Yoast SEO Premium. */
|
||||
\esc_html__( '%1$s %2$s must be installed and activated in order to use %3$s.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO',
|
||||
\esc_html( self::MINIMUM_YOAST_SEO_VERSION ),
|
||||
'Yoast SEO Premium'
|
||||
)
|
||||
. '</p>'
|
||||
. '<p>'
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped above.
|
||||
. $action
|
||||
. '</p>'
|
||||
. '</div>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the notification action to display.
|
||||
*
|
||||
* @return false|string The notification action or false if no action should be taken.
|
||||
*/
|
||||
protected function get_notification_action() {
|
||||
$minimum_version_met = \version_compare( $this->yoast_seo_version, self::MINIMUM_YOAST_SEO_VERSION . '-RC0', '>=' );
|
||||
$network_active = \is_plugin_active_for_network( \WPSEO_PREMIUM_BASENAME );
|
||||
$yoast_seo_active = ( $network_active ) ? \is_plugin_active_for_network( $this->yoast_seo_file ) : \is_plugin_active( $this->yoast_seo_file );
|
||||
|
||||
if ( $minimum_version_met && $yoast_seo_active ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $minimum_version_met ) {
|
||||
$permission = 'activate_plugins';
|
||||
}
|
||||
elseif ( $this->yoast_seo_version !== '0' ) {
|
||||
$permission = 'update_plugins';
|
||||
}
|
||||
else {
|
||||
$permission = 'install_plugins';
|
||||
}
|
||||
|
||||
if ( \current_user_can( $permission ) ) {
|
||||
switch ( $permission ) {
|
||||
case 'activate_plugins':
|
||||
if ( $network_active ) {
|
||||
$base_url = \network_admin_url( 'plugins.php?action=activate&plugin=' . $this->yoast_seo_file );
|
||||
/* translators: %1$s: Yoast SEO, %2$s: Link start tag, %3$s: Link end tag. */
|
||||
$button_content = \__( '%2$sNetwork Activate %1$s now%3$s', 'wordpress-seo-premium' );
|
||||
}
|
||||
else {
|
||||
$base_url = \self_admin_url( 'plugins.php?action=activate&plugin=' . $this->yoast_seo_file );
|
||||
/* translators: %1$s: Yoast SEO, %2$s: Link start tag, %3$s: Link end tag. */
|
||||
$button_content = \__( '%2$sActivate %1$s now%3$s', 'wordpress-seo-premium' );
|
||||
}
|
||||
$url = \wp_nonce_url( $base_url, 'activate-plugin_' . $this->yoast_seo_file );
|
||||
break;
|
||||
case 'update_plugins':
|
||||
$url = \wp_nonce_url( \self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . $this->yoast_seo_file ), 'upgrade-plugin_' . $this->yoast_seo_file );
|
||||
/* translators: %1$s: Yoast SEO, %2$s: Link start tag, %3$s: Link end tag. */
|
||||
$button_content = \__( '%2$sUpgrade %1$s now%3$s', 'wordpress-seo-premium' );
|
||||
break;
|
||||
case 'install_plugins':
|
||||
$url = \wp_nonce_url( \self_admin_url( 'update.php?action=install-plugin&plugin=wordpress-seo' ), 'install-plugin_wordpress-seo' );
|
||||
/* translators: %1$s: Yoast SEO, %2$s: Link start tag, %3$s: Link end tag. */
|
||||
$button_content = \__( '%2$sInstall %1$s now%3$s', 'wordpress-seo-premium' );
|
||||
break;
|
||||
}
|
||||
return \sprintf(
|
||||
\esc_html( $button_content ),
|
||||
'Yoast SEO',
|
||||
'<a class="button" href="' . \esc_url( $url ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( \is_multisite() ) {
|
||||
/* translators: %1$s: Yoast SEO, %2$s: The minimum Yoast SEO version required. */
|
||||
$message = \__( 'Please contact a network administrator to install %1$s %2$s.', 'wordpress-seo-premium' );
|
||||
}
|
||||
else {
|
||||
/* translators: %1$s: Yoast SEO, %2$s: The minimum Yoast SEO version required. */
|
||||
$message = \__( 'Please contact an administrator to install %1$s %2$s.', 'wordpress-seo-premium' );
|
||||
}
|
||||
return \sprintf(
|
||||
\esc_html( $message ),
|
||||
'Yoast SEO',
|
||||
\esc_html( self::MINIMUM_YOAST_SEO_VERSION )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the installation status if Yoast SEO is not installed or outdated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function validate_installation_status() {
|
||||
if ( ! \defined( 'WPSEO_VERSION' ) || \version_compare( \WPSEO_VERSION, self::MINIMUM_YOAST_SEO_VERSION . '-RC0', '<' ) ) {
|
||||
\delete_option( self::OPTION_KEY );
|
||||
if ( ! \defined( 'WPSEO_VERSION' ) ) {
|
||||
$this->load_yoast_seo_from_vendor_directory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status of the installer.
|
||||
*
|
||||
* This uses a separate option from our options framework as it needs to be available
|
||||
* before Yoast SEO has been loaded.
|
||||
*
|
||||
* @return false|string false if the installer hasn't been started.
|
||||
* "started" if it has but hasn't completed.
|
||||
* "completed" if it has been completed.
|
||||
*/
|
||||
protected function get_status() {
|
||||
return \get_option( self::OPTION_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs to premium as an addon.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception If the installer failed.
|
||||
*/
|
||||
protected function install() {
|
||||
if ( $this->get_status() ) {
|
||||
return;
|
||||
}
|
||||
// Mark the installer as having been started but not completed.
|
||||
\update_option( self::OPTION_KEY, 'started', true );
|
||||
|
||||
require_once \ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$this->detect_yoast_seo();
|
||||
// Either the plugin is not installed or is installed and too old.
|
||||
if ( \version_compare( $this->yoast_seo_version, self::MINIMUM_YOAST_SEO_VERSION . '-RC0', '<' ) ) {
|
||||
$this->ensure_vendor_directory_exists();
|
||||
$this->clean_target_directory();
|
||||
$this->move_vendor_directory();
|
||||
// We just moved a new file to the plugins directory so clear the plugins cache.
|
||||
\wp_cache_delete( 'plugins', 'plugins' );
|
||||
// If for some weird reason the plugin file was previously renamed make sure we use the actual filename.
|
||||
$this->yoast_seo_file = \dirname( $this->yoast_seo_file ) . '/wp-seo.php';
|
||||
}
|
||||
|
||||
$this->ensure_yoast_seo_is_activated();
|
||||
$this->transfer_auto_update_settings();
|
||||
// Mark the installer as having been completed.
|
||||
\update_option( self::OPTION_KEY, 'completed', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Yoast SEO from the vendor directory.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function load_yoast_seo_from_vendor_directory() {
|
||||
if ( \file_exists( $this->base_dir . '/vendor/yoast/wordpress-seo/wp-seo.php' ) ) {
|
||||
require_once $this->base_dir . '/vendor/yoast/wordpress-seo/wp-seo.php';
|
||||
\register_activation_hook( \WPSEO_PREMIUM_FILE, 'wpseo_activate' );
|
||||
\register_deactivation_hook( \WPSEO_PREMIUM_FILE, 'wpseo_deactivate' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the Yoast SEO plugin file and version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function detect_yoast_seo() {
|
||||
// Make sure Yoast SEO isn't already installed in another directory.
|
||||
foreach ( \get_plugins() as $file => $plugin ) {
|
||||
// Use text domain to identify the plugin as it's the closest thing to a slug.
|
||||
if (
|
||||
isset( $plugin['TextDomain'] ) && $plugin['TextDomain'] === 'wordpress-seo'
|
||||
&& isset( $plugin['Name'] ) && $plugin['Name'] === 'Yoast SEO'
|
||||
) {
|
||||
$this->yoast_seo_file = $file;
|
||||
$this->yoast_seo_version = isset( $plugin['Version'] ) ? $plugin['Version'] : '0';
|
||||
$this->yoast_seo_dir = \WP_PLUGIN_DIR . '/' . \dirname( $file );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the vendor directory exists.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception If the required vendor directory does not exist.
|
||||
*/
|
||||
protected function ensure_vendor_directory_exists() {
|
||||
// If Yoast SEO no longer exists in the vendor directory then abort.
|
||||
if ( ! \file_exists( $this->base_dir . '/vendor/yoast/wordpress-seo/wp-seo.php' ) ) {
|
||||
throw new Exception( 'Missing Yoast SEO in Yoast SEO Premium vendor.' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the target directory.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception If the target directory could not be cleaned.
|
||||
*/
|
||||
protected function clean_target_directory() {
|
||||
if ( \file_exists( \WP_PLUGIN_DIR . '/' . $this->yoast_seo_file ) ) {
|
||||
if ( \file_exists( $this->yoast_seo_dir . '/.git' ) ) {
|
||||
throw new Exception( 'Existing Yoast SEO installation has a .git directory, refusing to automatically install.' );
|
||||
}
|
||||
if ( ! $this->remove_directory( $this->yoast_seo_dir ) ) {
|
||||
throw new Exception( 'Could not remove old Yoast SEO installation.' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a directory and all it's contents.
|
||||
*
|
||||
* @param string $directory The full path to the directory.
|
||||
*
|
||||
* @return bool Whether or not the remove was succesfull.
|
||||
*/
|
||||
protected function remove_directory( $directory ) {
|
||||
$items = \scandir( $directory );
|
||||
foreach ( $items as $item ) {
|
||||
if ( $item === '.' || $item === '..' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $directory . '/' . $item;
|
||||
if ( \is_dir( $path ) ) {
|
||||
if ( ! $this->remove_directory( $path ) ) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ( ! \unlink( $path ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return \rmdir( $directory );
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the vendor directory to the target directory.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception If the move failed.
|
||||
*/
|
||||
protected function move_vendor_directory() {
|
||||
if ( ! \rename( $this->base_dir . '/vendor/yoast/wordpress-seo', $this->yoast_seo_dir ) ) {
|
||||
throw new Exception( 'Could not automatically installed Yoast SEO' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates Yoast SEO.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception If Yoast SEO could not be activated.
|
||||
*/
|
||||
protected function ensure_yoast_seo_is_activated() {
|
||||
if ( ! \is_plugin_active( $this->yoast_seo_file ) ) {
|
||||
$network_active = \is_plugin_active_for_network( \WPSEO_PREMIUM_BASENAME );
|
||||
// If we're not active at all it means we're being activated.
|
||||
if ( ! $network_active && ! \is_plugin_active( \WPSEO_PREMIUM_BASENAME ) ) {
|
||||
// So set network active to whether or not we're in the network admin.
|
||||
$network_active = \is_network_admin();
|
||||
}
|
||||
// Activate Yoast SEO. If Yoast SEO Premium is network active then make sure Yoast SEO is as well.
|
||||
$activation = \activate_plugin( $this->yoast_seo_file, '', $network_active );
|
||||
if ( \is_wp_error( $activation ) ) {
|
||||
throw new Exception( 'Could not activate Yoast SEO: ' . $activation->get_error_message() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers the auto update settings for Yoast SEO Premium to Yoast SEO.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function transfer_auto_update_settings() {
|
||||
$auto_updates = (array) \get_site_option( 'auto_update_plugins', [] );
|
||||
|
||||
if ( \in_array( \WPSEO_PREMIUM_BASENAME, $auto_updates, true ) ) {
|
||||
$auto_updates[] = $this->yoast_seo_file;
|
||||
$auto_updates = \array_unique( $auto_updates );
|
||||
\update_site_option( 'auto_update_plugins', $auto_updates );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wether or not the notification to install Yoast SEO should be shown.
|
||||
*
|
||||
* This is copied from the Yoast_Admin_And_Dashboard_Conditional which we can't use as Yoast SEO may not be installed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_show_notification() {
|
||||
global $pagenow;
|
||||
|
||||
// Do not output on plugin / theme upgrade pages or when WordPress is upgrading.
|
||||
if ( ( \defined( 'IFRAME_REQUEST' ) && \IFRAME_REQUEST ) || \wp_installing() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* IFRAME_REQUEST is not defined on these pages,
|
||||
* though these action pages do show when upgrading themes or plugins.
|
||||
*/
|
||||
$actions = [ 'do-theme-upgrade', 'do-plugin-upgrade', 'do-core-upgrade', 'do-core-reinstall' ];
|
||||
if ( isset( $_GET['action'] ) && \in_array( $_GET['action'], $actions, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Only a strpos is done in the input.
|
||||
if ( $pagenow === 'admin.php' && isset( $_GET['page'] ) && \strpos( $_GET['page'], 'wpseo' ) === 0 ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$target_pages = [
|
||||
'index.php',
|
||||
'plugins.php',
|
||||
'update-core.php',
|
||||
'options-permalink.php',
|
||||
];
|
||||
|
||||
return \in_array( $pagenow, $target_pages, true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Conditionals;
|
||||
|
||||
use WPSEO_Options;
|
||||
use Yoast\WP\SEO\Conditionals\Feature_Flag_Conditional;
|
||||
|
||||
/**
|
||||
* Checks if the YOAST_SEO_SOCIAL_TEMPLATES constant is set.
|
||||
*/
|
||||
class Social_Templates_Conditional extends Feature_Flag_Conditional {
|
||||
|
||||
/**
|
||||
* Returns whether or not this conditional is met.
|
||||
*
|
||||
* @return bool Whether or not the conditional is met.
|
||||
*/
|
||||
public function is_met() {
|
||||
return parent::is_met() && WPSEO_Options::get( 'opengraph', true ) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the feature flag.
|
||||
* 'YOAST_SEO_' is automatically prepended to it and it will be uppercased.
|
||||
*
|
||||
* @return string the name of the feature flag.
|
||||
*/
|
||||
protected function get_feature_flag() {
|
||||
return 'SOCIAL_TEMPLATES';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Conditionals;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Zapier_Helper;
|
||||
|
||||
/**
|
||||
* Conditional that is only met when the Zapier integration is enabled.
|
||||
*/
|
||||
class Zapier_Enabled_Conditional implements Conditional {
|
||||
|
||||
/**
|
||||
* The Zapier helper.
|
||||
*
|
||||
* @var Zapier_Helper
|
||||
*/
|
||||
private $zapier;
|
||||
|
||||
/**
|
||||
* Zapier_Enabled_Conditional constructor.
|
||||
*
|
||||
* @param Zapier_Helper $zapier The Zapier helper.
|
||||
*/
|
||||
public function __construct( Zapier_Helper $zapier ) {
|
||||
$this->zapier = $zapier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this conditional is met.
|
||||
*
|
||||
* @return bool Whether or not the conditional is met.
|
||||
*/
|
||||
public function is_met() {
|
||||
return $this->zapier->is_enabled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Config;
|
||||
|
||||
use Yoast\WP\SEO\Config\Badge_Group_Names as New_Badge_Group_Names;
|
||||
|
||||
/**
|
||||
* Class Badge_Group_Names.
|
||||
*
|
||||
* This class defines groups for "new" badges, with the version in which those groups are no longer considered
|
||||
* to be "new".
|
||||
*/
|
||||
class Badge_Group_Names extends New_Badge_Group_Names {
|
||||
const GROUP_GLOBAL_TEMPLATES = 'global-templates';
|
||||
|
||||
/**
|
||||
* Constant describing when certain groups of new badges will no longer be shown.
|
||||
*/
|
||||
const GROUP_NAMES = [
|
||||
self::GROUP_GLOBAL_TEMPLATES => '16.5-beta0',
|
||||
];
|
||||
|
||||
/**
|
||||
* Badge_Group_Names constructor.
|
||||
*
|
||||
* @param string $version Optional: the current version number.
|
||||
*/
|
||||
public function __construct( $version = null ) {
|
||||
parent::__construct( $version );
|
||||
|
||||
if ( ! $version ) {
|
||||
$version = \WPSEO_PREMIUM_VERSION;
|
||||
}
|
||||
$this->version = $version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Config\Migrations;
|
||||
|
||||
use Yoast\WP\Lib\Migrations\Migration;
|
||||
use Yoast\WP\Lib\Model;
|
||||
|
||||
/**
|
||||
* Class WpYoastPremiumImprovedInternalLinking
|
||||
*
|
||||
* @package Yoast\WP\SEO\Config\Migrations
|
||||
*/
|
||||
class WpYoastPremiumImprovedInternalLinking extends Migration {
|
||||
|
||||
/**
|
||||
* The plugin this migration belongs to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $plugin = 'premium';
|
||||
|
||||
/**
|
||||
* Migration up.
|
||||
*/
|
||||
public function up() {
|
||||
$table_name = $this->get_table_name();
|
||||
$adapter = $this->get_adapter();
|
||||
|
||||
if ( ! $adapter->has_table( $table_name ) ) {
|
||||
$table = $this->create_table( $table_name );
|
||||
|
||||
$table->column(
|
||||
'stem',
|
||||
'string',
|
||||
[
|
||||
'null' => true,
|
||||
'limit' => 191,
|
||||
]
|
||||
);
|
||||
$table->column(
|
||||
'indexable_id',
|
||||
'integer',
|
||||
[
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'limit' => 11,
|
||||
]
|
||||
);
|
||||
$table->column( 'weight', 'float' );
|
||||
|
||||
$table->finish();
|
||||
}
|
||||
|
||||
if ( ! $adapter->has_index( $table_name, 'stem', [ 'name' => 'stem' ] ) ) {
|
||||
$this->add_index(
|
||||
$table_name,
|
||||
[
|
||||
'stem',
|
||||
],
|
||||
[
|
||||
'name' => 'stem',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $adapter->has_index( $table_name, 'indexable_id', [ 'name' => 'indexable_id' ] ) ) {
|
||||
$this->add_index(
|
||||
$table_name,
|
||||
[
|
||||
'indexable_id',
|
||||
],
|
||||
[
|
||||
'name' => 'indexable_id',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration down.
|
||||
*/
|
||||
public function down() {
|
||||
$table_name = $this->get_table_name();
|
||||
if ( $this->get_adapter()->has_table( $table_name ) ) {
|
||||
$this->drop_table( $table_name );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the table name to use.
|
||||
*
|
||||
* @return string The table name to use.
|
||||
*/
|
||||
protected function get_table_name() {
|
||||
return Model::get_table_name( 'Prominent_Words' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Database;
|
||||
|
||||
use Exception;
|
||||
use Yoast\WP\SEO\Initializers\Migration_Runner;
|
||||
|
||||
/**
|
||||
* Triggers premium database migrations and handles results.
|
||||
*/
|
||||
class Migration_Runner_Premium extends Migration_Runner {
|
||||
|
||||
/**
|
||||
* Runs this initializer.
|
||||
*
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->run_premium_migrations();
|
||||
|
||||
// The below action is used when queries fail, this may happen in a multisite environment when switch_to_blog is used.
|
||||
\add_action( '_yoast_run_migrations', [ $this, 'run_premium_migrations' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the Premium migrations.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception When a migration errored.
|
||||
*/
|
||||
public function run_premium_migrations() {
|
||||
$this->run_migrations( 'premium', \WPSEO_PREMIUM_VERSION );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* Graceful deprecation of various classes which were renamed.
|
||||
*
|
||||
* {@internal As this file is just (temporarily) put in place to warn extending
|
||||
* plugins about the class name changes, it is exempt from select CS standards.}
|
||||
*
|
||||
* @package Yoast\WP\SEO\Premium
|
||||
*
|
||||
* @since 16.1
|
||||
* @deprecated 16.1
|
||||
*
|
||||
* @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
|
||||
* @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound
|
||||
* @phpcs:disable Yoast.Commenting.CodeCoverageIgnoreDeprecated
|
||||
* @phpcs:disable Yoast.Commenting.FileComment.Unnecessary
|
||||
* @phpcs:disable Yoast.Files.FileName.InvalidClassFileName
|
||||
*/
|
||||
|
||||
namespace Yoast\WP\SEO\Actions\Prominent_Words;
|
||||
|
||||
use WPSEO_Premium_Prominent_Words_Support;
|
||||
use Yoast\WP\SEO\Helpers\Meta_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Complete_Action as New_Complete_Action;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action as New_Content_Action;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action as New_Save_Action;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Prominent_Words_Repository;
|
||||
|
||||
\_deprecated_file( \basename( __FILE__ ), 'Yoast SEO Premium 16.1' );
|
||||
|
||||
/**
|
||||
* Class Complete_Action.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Prominent_Words\Complete_Action} instead.
|
||||
*/
|
||||
class Complete_Action extends New_Complete_Action {
|
||||
|
||||
/**
|
||||
* Complete_Action constructor.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Prominent_Words\Complete_Action} instead.
|
||||
*
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
*/
|
||||
public function __construct( Prominent_Words_Helper $prominent_words_helper ) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 16.1', '\Yoast\WP\SEO\Premium\Actions\Prominent_Words\Complete_Action' );
|
||||
parent::__construct( $prominent_words_helper );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Content_Action.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action} instead.
|
||||
*/
|
||||
class Content_Action extends New_Content_Action {
|
||||
|
||||
/**
|
||||
* Content_Action constructor.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action} instead.
|
||||
*
|
||||
* @param WPSEO_Premium_Prominent_Words_Support $prominent_words_support An instance of
|
||||
* WPSEO_Premium_Prominent_Words_Support.
|
||||
* @param Indexable_Repository $indexable_repository An instance of Indexable_Repository.
|
||||
* @param Meta_Tags_Context_Memoizer $memoizer The meta tags context memoizer.
|
||||
* @param Meta_Helper $meta The meta value helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WPSEO_Premium_Prominent_Words_Support $prominent_words_support,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Meta_Tags_Context_Memoizer $memoizer,
|
||||
Meta_Helper $meta
|
||||
) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 16.1', '\Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action' );
|
||||
parent::__construct( $prominent_words_support, $indexable_repository, $memoizer, $meta );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Save_Action.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action} instead.
|
||||
*/
|
||||
class Save_Action extends New_Save_Action {
|
||||
|
||||
/**
|
||||
* Prominent_Words_Link_Service constructor.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action} instead.
|
||||
*
|
||||
* @param Prominent_Words_Repository $prominent_words_repository The repository to create, read, update and delete
|
||||
* prominent words from.
|
||||
* @param Indexable_Repository $indexable_repository The repository to read, update and delete
|
||||
* indexables from.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Repository $prominent_words_repository,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Prominent_Words_Helper $prominent_words_helper
|
||||
) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 16.1', '\Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action' );
|
||||
parent::__construct( $prominent_words_repository, $indexable_repository, $prominent_words_helper );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Graceful deprecation of various classes which were renamed.
|
||||
*
|
||||
* {@internal As this file is just (temporarily) put in place to warn extending
|
||||
* plugins about the class name changes, it is exempt from select CS standards.}
|
||||
*
|
||||
* @package Yoast\WP\SEO\Premium
|
||||
*
|
||||
* @since 16.1
|
||||
* @deprecated 16.1
|
||||
*
|
||||
* @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
|
||||
* @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound
|
||||
* @phpcs:disable Yoast.Commenting.CodeCoverageIgnoreDeprecated
|
||||
* @phpcs:disable Yoast.Commenting.FileComment.Unnecessary
|
||||
* @phpcs:disable Yoast.Files.FileName.InvalidClassFileName
|
||||
*/
|
||||
|
||||
namespace Yoast\WP\SEO\Actions;
|
||||
|
||||
use WPSEO_Premium_Prominent_Words_Support;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Zapier_Helper;
|
||||
use Yoast\WP\SEO\Premium\Actions\Link_Suggestions_Action as New_Link_Suggestions_Action;
|
||||
use Yoast\WP\SEO\Premium\Actions\Zapier_Action as New_Zapier_Action;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use Yoast\WP\SEO\Repositories\Prominent_Words_Repository;
|
||||
|
||||
\_deprecated_file( \basename( __FILE__ ), 'Yoast SEO Premium 16.1' );
|
||||
|
||||
/**
|
||||
* Class Link_Suggestions_Action.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Link_Suggestions_Action} instead.
|
||||
*/
|
||||
class Link_Suggestions_Action extends New_Link_Suggestions_Action {
|
||||
|
||||
/**
|
||||
* Link_Suggestions_Service constructor.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Link_Suggestions_Action} instead.
|
||||
*
|
||||
* @param Prominent_Words_Repository $prominent_words_repository The repository to retrieve prominent words from.
|
||||
* @param Indexable_Repository $indexable_repository The repository to retrieve indexables from.
|
||||
* @param Prominent_Words_Helper $prominent_words_helper Class with helper methods for prominent words.
|
||||
* @param WPSEO_Premium_Prominent_Words_Support $prominent_words_support The prominent words support class.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Repository $prominent_words_repository,
|
||||
Indexable_Repository $indexable_repository,
|
||||
Prominent_Words_Helper $prominent_words_helper,
|
||||
WPSEO_Premium_Prominent_Words_Support $prominent_words_support
|
||||
) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 16.1', '\Yoast\WP\SEO\Premium\Actions\Link_Suggestions_Action' );
|
||||
|
||||
parent::__construct(
|
||||
$prominent_words_repository,
|
||||
$indexable_repository,
|
||||
$prominent_words_helper,
|
||||
$prominent_words_support
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Zapier_Action.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Zapier_Action} instead.
|
||||
*/
|
||||
class Zapier_Action extends New_Zapier_Action {
|
||||
|
||||
/**
|
||||
* Zapier_Action constructor.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\Actions\Zapier_Action} instead.
|
||||
*
|
||||
* @param Options_Helper $options_helper The Options Helper.
|
||||
* @param Zapier_Helper $zapier_helper The Zapier helper.
|
||||
* @param Indexable_Repository $indexable_repository The Indexable repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
Zapier_Helper $zapier_helper,
|
||||
Indexable_Repository $indexable_repository
|
||||
) {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 16.1', '\Yoast\WP\SEO\Premium\Actions\Zapier_Action' );
|
||||
|
||||
parent::__construct( $options_helper, $zapier_helper, $indexable_repository );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Graceful deprecation of various classes which were renamed.
|
||||
*
|
||||
* {@internal As this file is just (temporarily) put in place to warn extending
|
||||
* plugins about the class name changes, it is exempt from select CS standards.}
|
||||
*
|
||||
* @package Yoast\WP\SEO\Premium
|
||||
*
|
||||
* @since 16.3
|
||||
* @deprecated 16.3
|
||||
*
|
||||
* @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
|
||||
* @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound
|
||||
* @phpcs:disable Yoast.Commenting.CodeCoverageIgnoreDeprecated
|
||||
* @phpcs:disable Yoast.Commenting.FileComment.Unnecessary
|
||||
* @phpcs:disable Yoast.Files.FileName.InvalidClassFileName
|
||||
*/
|
||||
|
||||
namespace Yoast\WP\SEO\Database;
|
||||
|
||||
use Yoast\WP\SEO\Premium\Database\Migration_Runner_Premium as New_Migration_Runner_Premium;
|
||||
|
||||
\_deprecated_file( \basename( __FILE__ ), 'Yoast SEO Premium 16.3' );
|
||||
|
||||
/**
|
||||
* Class Migration_Runner_Premium.
|
||||
*
|
||||
* @deprecated 16.3 Use {@see \Yoast\WP\SEO\Premium\Database\Migration_Runner_Premium} instead.
|
||||
*/
|
||||
class Migration_Runner_Premium extends New_Migration_Runner_Premium {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @deprecated 16.3 Use {@see \Yoast\WP\SEO\Premium\Database\Migration_Runner_Premium} instead.
|
||||
*/
|
||||
public function __construct() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 16.3', '\Yoast\WP\SEO\Premium\Database\Migration_Runner_Premium' );
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin;
|
||||
|
||||
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\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action;
|
||||
use Yoast_Notification_Center;
|
||||
|
||||
/**
|
||||
* Integration for determining and showing the notification
|
||||
* to ask users to calculate prominent words for their site.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @package Yoast\WP\SEO\Integrations\Admin
|
||||
*/
|
||||
class Prominent_Words_Notification implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The ID of the notification.
|
||||
*/
|
||||
const NOTIFICATION_ID = 'wpseo-premium-prominent-words-recalculate';
|
||||
|
||||
/**
|
||||
* How many indexables without prominent words should exist before this notification is shown to the user.
|
||||
*/
|
||||
const UNINDEXED_THRESHOLD = 25;
|
||||
|
||||
/**
|
||||
* Prominent_Words_Notification_Integration constructor.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Yoast_Notification_Center $notification_center The notification center.
|
||||
* @param Content_Action $content_action The content action.
|
||||
* @param Indexable_Post_Indexation_Action $post_indexation The post indexing action.
|
||||
* @param Indexable_Term_Indexation_Action $term_indexation The term indexing action.
|
||||
* @param Indexable_General_Indexation_Action $general_indexation The general indexing action.
|
||||
* @param Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation The post type indexing action.
|
||||
* @param Capability_Helper $capability The capability helper.
|
||||
* @param Options_Helper $options The options helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Yoast_Notification_Center $notification_center,
|
||||
Content_Action $content_action,
|
||||
Indexable_Post_Indexation_Action $post_indexation,
|
||||
Indexable_Term_Indexation_Action $term_indexation,
|
||||
Indexable_General_Indexation_Action $general_indexation,
|
||||
Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation,
|
||||
Capability_Helper $capability,
|
||||
Options_Helper $options
|
||||
) {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration by registering the right hooks and filters.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which this loadable should be active.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the option change to make sure the notification will be removed when link suggestions are disabled.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param mixed $old_value The old value.
|
||||
* @param mixed $new_value The new value.
|
||||
*/
|
||||
public function handle_option_change( $old_value, $new_value ) {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages, for each user, if the notification should be shown or removed.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function manage_notification() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the notification for all applicable users.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function cleanup_notification() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin\Prominent_Words;
|
||||
|
||||
/**
|
||||
* Class Indexation_Integration. Indexing integration for prominent words.
|
||||
*
|
||||
* @deprecated 15.2
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Indexation_Integration extends Indexing_Integration {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Admin\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Admin\Prominent_Words_Notification;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Integration for the prominent words notification event used in the cron job.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Notification_Event_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Notification_Event_Integration constructor.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Prominent_Words_Notification $prominent_words_notification The prominent words notification integration.
|
||||
*/
|
||||
public function __construct( Prominent_Words_Notification $prominent_words_notification ) {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration by registering the right hooks and filters.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Presenters\Admin\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Presenters\Abstract_Presenter;
|
||||
|
||||
/**
|
||||
* Represents the list item presenter for the prominent words indexing.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @package Yoast\WP\SEO\Presentations\Admin
|
||||
*/
|
||||
class Indexation_List_Item_Presenter extends Abstract_Presenter {
|
||||
|
||||
/**
|
||||
* Prominent_Words_Indexation_List_Item_Presenter constructor.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $total_unindexed The number of objects that need to be indexed.
|
||||
*/
|
||||
public function __construct( $total_unindexed ) {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output as string.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The output.
|
||||
*/
|
||||
public function present() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Presenters\Admin\Prominent_Words;
|
||||
|
||||
use Yoast\WP\SEO\Presenters\Abstract_Presenter;
|
||||
|
||||
/**
|
||||
* Class Indexation_Modal_Presenter.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Indexation_Modal_Presenter extends Abstract_Presenter {
|
||||
|
||||
/**
|
||||
* Indexation_Modal constructor.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $total_unindexed The number of objects that need to be indexed.
|
||||
*/
|
||||
public function __construct( $total_unindexed ) {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the modal.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The modal HTML.
|
||||
*/
|
||||
public function present() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Presenters;
|
||||
|
||||
/**
|
||||
* Class Prominent_Words_Notification_Presenter
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @package Yoast\WP\SEO\Presenters
|
||||
*/
|
||||
class Prominent_Words_Notification extends Abstract_Presenter {
|
||||
|
||||
/**
|
||||
* Presents the notification.
|
||||
*
|
||||
* @deprecated 15.1
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The notification.
|
||||
*/
|
||||
public function present() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO 15.1' );
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Initializers\Redirect_Handler;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_Handler class
|
||||
*
|
||||
* @deprecated 16.0
|
||||
*/
|
||||
class WPSEO_Redirect_Handler extends Redirect_Handler {
|
||||
|
||||
/**
|
||||
* Constructor to throw deprecation notice.
|
||||
*
|
||||
* @deprecated 16.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __construct() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO Premium 16.0', \esc_html( Redirect_Handler::class ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the redirect handler.
|
||||
*
|
||||
* @deprecated 16.0
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load() {
|
||||
\_deprecated_function( __METHOD__, 'WPSEO Premium 16.0', \esc_html( Redirect_Handler::class . '::initialize' ) );
|
||||
$this->initialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Graceful deprecation of various classes which were renamed.
|
||||
*
|
||||
* {@internal As this file is just (temporarily) put in place to warn extending
|
||||
* plugins about the class name changes, it is exempt from select CS standards.}
|
||||
*
|
||||
* @package Yoast\WP\SEO\Premium
|
||||
*
|
||||
* @since 16.1
|
||||
* @deprecated 16.1
|
||||
*
|
||||
* @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
|
||||
* @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound
|
||||
* @phpcs:disable Yoast.Commenting.CodeCoverageIgnoreDeprecated
|
||||
* @phpcs:disable Yoast.Commenting.FileComment.Unnecessary
|
||||
* @phpcs:disable Yoast.Files.FileName.InvalidClassFileName
|
||||
*/
|
||||
|
||||
namespace Yoast\WP\SEO\WordPress;
|
||||
|
||||
use Yoast\WP\SEO\Premium\WordPress\Wrapper;
|
||||
|
||||
\_deprecated_file( \basename( __FILE__ ), 'Yoast SEO Premium 16.1' );
|
||||
|
||||
/**
|
||||
* Class Premium_Wrapper.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\WordPress\Wrapper} instead.
|
||||
*/
|
||||
class Premium_Wrapper extends Wrapper {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @deprecated 16.1 Use {@see \Yoast\WP\SEO\Premium\WordPress\Wrapper} instead.
|
||||
*/
|
||||
public function __construct() {
|
||||
\_deprecated_function( __METHOD__, 'Yoast SEO Premium 16.1', '\Yoast\WP\SEO\Premium\WordPress\Wrapper' );
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Premium
|
||||
*/
|
||||
|
||||
if ( ! defined( 'WPSEO_PREMIUM_VERSION' ) ) {
|
||||
header( 'Status: 403 Forbidden' );
|
||||
header( 'HTTP/1.1 403 Forbidden' );
|
||||
exit();
|
||||
}
|
||||
|
||||
use Yoast\WP\SEO\Premium\Main;
|
||||
|
||||
/**
|
||||
* Retrieves the main instance.
|
||||
*
|
||||
* @phpcs:disable WordPress.NamingConventions -- Should probably be renamed, but leave for now.
|
||||
*
|
||||
* @return Main The main instance.
|
||||
*/
|
||||
function YoastSEOPremium() {
|
||||
// phpcs:enable
|
||||
|
||||
static $main;
|
||||
if ( did_action( 'wpseo_loaded' ) ) {
|
||||
if ( $main === null ) {
|
||||
// Ensure free is loaded as loading premium will fail without it.
|
||||
YoastSEO();
|
||||
$main = new Main();
|
||||
$main->load();
|
||||
}
|
||||
}
|
||||
else {
|
||||
add_action( 'wpseo_loaded', 'YoastSEOPremium' );
|
||||
}
|
||||
|
||||
return $main;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Helpers;
|
||||
|
||||
/**
|
||||
* Class Prominent_Words_Helper.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Helpers
|
||||
*/
|
||||
class Prominent_Words_Helper {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Prominent_Words_Helper constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the tf-idf (term frequency - inverse document frequency) score of a prominent word in a document.
|
||||
* The document frequency should be 1 or higher, if it is not, it is assumed to be 1.
|
||||
*
|
||||
* @param int $term_frequency How many times the word occurs in the document.
|
||||
* @param int $doc_frequency In how many documents this word occurs.
|
||||
*
|
||||
* @return float The tf-idf score of a prominent word.
|
||||
*/
|
||||
public function compute_tf_idf_score( $term_frequency, $doc_frequency ) {
|
||||
// Set doc frequency to a minimum of 1, to avoid division by 0.
|
||||
$doc_frequency = \max( 1, $doc_frequency );
|
||||
|
||||
return ( $term_frequency * ( 1 / $doc_frequency ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the vector length for the given prominent words, applying Pythagoras's Theorem on the weights.
|
||||
*
|
||||
* @param array $prominent_words The prominent words, as an array mapping stems to `weight` and `df` (document frequency).
|
||||
*
|
||||
* @return float Vector length for the prominent words.
|
||||
*/
|
||||
public function compute_vector_length( $prominent_words ) {
|
||||
$sum_of_squares = 0;
|
||||
|
||||
foreach ( $prominent_words as $stem => $word ) {
|
||||
$doc_frequency = 1;
|
||||
if ( \array_key_exists( 'df', $word ) ) {
|
||||
$doc_frequency = $word['df'];
|
||||
}
|
||||
|
||||
$tf_idf = $this->compute_tf_idf_score( $word['weight'], $doc_frequency );
|
||||
$sum_of_squares += ( $tf_idf ** 2 );
|
||||
}
|
||||
|
||||
return \sqrt( $sum_of_squares );
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the prominent words indexing.
|
||||
*/
|
||||
public function complete_indexing() {
|
||||
$this->set_indexing_completed( true );
|
||||
\set_transient( 'total_unindexed_prominent_words', '0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prominent_words_indexing_completed option.
|
||||
*
|
||||
* @param bool $indexing_completed Whether or not the prominent words indexing has completed.
|
||||
*/
|
||||
public function set_indexing_completed( $indexing_completed ) {
|
||||
$this->options_helper->set( 'prominent_words_indexing_completed', $indexing_completed );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean that indicates whether the prominent words indexing has completed.
|
||||
*
|
||||
* @return bool Whether the prominent words indexing has completed.
|
||||
*/
|
||||
public function is_indexing_completed() {
|
||||
return $this->options_helper->get( 'prominent_words_indexing_completed' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Helpers;
|
||||
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Surfaces\Meta_Surface;
|
||||
|
||||
/**
|
||||
* Class Zapier_Helper
|
||||
*
|
||||
* @package Yoast\WP\SEO\Helpers
|
||||
*/
|
||||
class Zapier_Helper {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The meta surface.
|
||||
*
|
||||
* @var Meta_Surface
|
||||
*/
|
||||
protected $meta_surface;
|
||||
|
||||
/**
|
||||
* Zapier_Helper constructor.
|
||||
*
|
||||
* @codeCoverageIgnore It only sets dependencies.
|
||||
*
|
||||
* @param Options_Helper $options The options helper.
|
||||
* @param Meta_Surface $meta_surface The Meta surface.
|
||||
*/
|
||||
public function __construct( Options_Helper $options, Meta_Surface $meta_surface ) {
|
||||
$this->options = $options;
|
||||
$this->meta_surface = $meta_surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a subscription exists in the database.
|
||||
*
|
||||
* @return bool Whether a subscription exists in the database.
|
||||
*/
|
||||
public function is_connected() {
|
||||
$subscription = $this->options->get( 'zapier_subscription' );
|
||||
|
||||
if ( \is_array( $subscription )
|
||||
&& ! empty( $subscription['id'] )
|
||||
&& \filter_var( $subscription['url'], \FILTER_VALIDATE_URL )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Zapier integration is currently enabled.
|
||||
*
|
||||
* @return bool Whether the integration is enabled.
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return (bool) $this->options->get( 'zapier_integration_active', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stored Zapier API Key.
|
||||
*
|
||||
* @return string The Zapier API Key.
|
||||
*/
|
||||
public function get_or_generate_zapier_api_key() {
|
||||
$zapier_api_key = $this->options->get( 'zapier_api_key' );
|
||||
|
||||
if ( empty( $zapier_api_key ) ) {
|
||||
$zapier_api_key = \wp_generate_password( 32, false );
|
||||
$this->options->set( 'zapier_api_key', $zapier_api_key );
|
||||
}
|
||||
|
||||
return $zapier_api_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string matches the API key in the DB, if present.
|
||||
*
|
||||
* @param string $api_key The API key to test.
|
||||
*
|
||||
* @return bool Whether the API key is valid or not.
|
||||
*/
|
||||
public function is_valid_api_key( $api_key ) {
|
||||
return ( ! empty( $api_key ) && $this->options->get( 'zapier_api_key' ) === $api_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Zapier hook URL of the trigger if present, null otherwise.
|
||||
*
|
||||
* @return string|null The hook URL, null if not set.
|
||||
*/
|
||||
public function get_trigger_url() {
|
||||
if ( $this->is_connected() ) {
|
||||
$subscription = $this->options->get( 'zapier_subscription', [] );
|
||||
|
||||
return $subscription['url'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the submitted id is present in the subscriptions.
|
||||
*
|
||||
* @param string $id The id to be tested.
|
||||
*
|
||||
* @return bool Whether the id is present in the subscriptions.
|
||||
*/
|
||||
public function is_subscribed_id( $id ) {
|
||||
if ( $this->is_connected() ) {
|
||||
$subscription = $this->options->get( 'zapier_subscription', [] );
|
||||
|
||||
return $subscription['id'] === $id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes the submitted id.
|
||||
*
|
||||
* @param string $id The id to be unsubscribed.
|
||||
*
|
||||
* @return bool Whether the unsubscription was successful.
|
||||
*/
|
||||
public function unsubscribe_id( $id ) {
|
||||
if ( $this->is_connected() && $this->is_subscribed_id( $id ) ) {
|
||||
return $this->options->set( 'zapier_subscription', [] );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new subscription with the submitted URL.
|
||||
*
|
||||
* @param string $url The URL to be subscribed.
|
||||
*
|
||||
* @return array|bool The subscription data (id and URL) if successful, false otherwise.
|
||||
*/
|
||||
public function subscribe_url( $url ) {
|
||||
if ( ! $this->is_connected() ) {
|
||||
$subscription_data = [
|
||||
'id' => \wp_generate_password( 32, false ),
|
||||
'url' => \esc_url_raw( $url, [ 'http', 'https' ] ),
|
||||
];
|
||||
|
||||
if ( $this->options->set( 'zapier_subscription', $subscription_data ) ) {
|
||||
return $subscription_data;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the data for Zapier.
|
||||
*
|
||||
* @param Indexable $indexable The indexable from which the data must be extracted.
|
||||
*
|
||||
* @return array[] The array of data ready to be sent to Zapier.
|
||||
*/
|
||||
public function get_data_for_zapier( Indexable $indexable ) {
|
||||
$post = \get_post( $indexable->object_id );
|
||||
if ( ! $post ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$meta = $this->meta_surface->for_indexable( $indexable );
|
||||
|
||||
$open_graph_image = '';
|
||||
if ( \count( $meta->open_graph_images ) > 0 ) {
|
||||
$open_graph_image_array = \reset( $meta->open_graph_images );
|
||||
$open_graph_image = $open_graph_image_array['url'];
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => $indexable->permalink,
|
||||
'post_type' => $post->post_type,
|
||||
'post_title' => \html_entity_decode( $post->post_title ),
|
||||
'author' => \get_the_author_meta( 'display_name', $post->post_author ),
|
||||
'tags' => \html_entity_decode( \implode( ', ', \wp_get_post_tags( $post->ID, [ 'fields' => 'names' ] ) ) ),
|
||||
'categories' => \html_entity_decode( \implode( ', ', \wp_get_post_categories( $post->ID, [ 'fields' => 'names' ] ) ) ),
|
||||
'primary_category' => \html_entity_decode( \yoast_get_primary_term( 'category', $post ) ),
|
||||
'meta_description' => \html_entity_decode( $meta->description ),
|
||||
'open_graph_title' => \html_entity_decode( $meta->open_graph_title ),
|
||||
'open_graph_description' => \html_entity_decode( $meta->open_graph_description ),
|
||||
'open_graph_image' => $open_graph_image,
|
||||
'twitter_title' => \html_entity_decode( $meta->twitter_title ),
|
||||
'twitter_description' => \html_entity_decode( $meta->twitter_description ),
|
||||
'twitter_image' => $meta->twitter_image,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the post type is supported by the Zapier integration.
|
||||
*
|
||||
* The Zapier integration should be visible and working only for post types
|
||||
* that support the Yoast Metabox. We filter out attachments regardless of
|
||||
* the Yoast SEO settings, anyway.
|
||||
*
|
||||
* @param string $post_type The post type to be checked.
|
||||
*
|
||||
* @return bool Whether the post type is supported by the Zapier integration.
|
||||
*/
|
||||
public function is_post_type_supported( $post_type ) {
|
||||
return $post_type !== 'attachment' && WPSEO_Utils::is_metabox_active( $post_type, 'post_type' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium\Initializers;
|
||||
|
||||
use WPSEO_Capability_Manager_Factory;
|
||||
use WPSEO_Premium;
|
||||
use WPSEO_Premium_Register_Capabilities;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Initializers\Initializer_Interface;
|
||||
|
||||
/**
|
||||
* Class Plugin.
|
||||
*/
|
||||
class Plugin implements Initializer_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* Plugin constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the redirect handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
\add_action( 'plugins_loaded', [ $this, 'load' ], 15 );
|
||||
|
||||
$wpseo_premium_capabilities = new WPSEO_Premium_Register_Capabilities();
|
||||
$wpseo_premium_capabilities->register_hooks();
|
||||
|
||||
\register_deactivation_hook( \WPSEO_PREMIUM_FILE, [ $this, 'wpseo_premium_deactivate' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* The premium setup
|
||||
*/
|
||||
public function load() {
|
||||
new WPSEO_Premium();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up Premium on deactivation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wpseo_premium_deactivate() {
|
||||
\do_action( 'wpseo_register_capabilities_premium' );
|
||||
WPSEO_Capability_Manager_Factory::get( 'premium' )->remove();
|
||||
|
||||
$this->options_helper->set( 'tracking', false );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,694 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Initializers;
|
||||
|
||||
use WP_Query;
|
||||
use WPSEO_Premium_Redirect_Option;
|
||||
use WPSEO_Redirect_Util;
|
||||
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
|
||||
|
||||
/**
|
||||
* Class Redirect_Handler.
|
||||
*/
|
||||
class Redirect_Handler implements Initializer_Interface {
|
||||
|
||||
/**
|
||||
* Array where the redirects will stored.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $redirects;
|
||||
|
||||
/**
|
||||
* The matches parts of the URL in case of a matched regex redirect.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $url_matches = [];
|
||||
|
||||
/**
|
||||
* Is the current page being redirected.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_redirected = false;
|
||||
|
||||
/**
|
||||
* The options where the URL redirects are stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $normal_option_name = 'wpseo-premium-redirects-export-plain';
|
||||
|
||||
/**
|
||||
* The option name where the regex redirects are stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $regex_option_name = 'wpseo-premium-redirects-export-regex';
|
||||
|
||||
/**
|
||||
* The URL that is called at the moment.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $request_url = '';
|
||||
|
||||
/**
|
||||
* Sets the error template to include.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $template_file_path;
|
||||
|
||||
/**
|
||||
* 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 ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the redirect handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
// Only handle the redirect when the option for php redirects is enabled.
|
||||
if ( ! $this->load_php_redirects() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the requested URL.
|
||||
$this->set_request_url();
|
||||
|
||||
// Check the normal redirects.
|
||||
$this->handle_normal_redirects( $this->request_url );
|
||||
|
||||
// Check the regex redirects.
|
||||
if ( $this->is_redirected() === false ) {
|
||||
$this->handle_regex_redirects();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the 410 status code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function do_410() {
|
||||
$is_include_hook_set = $this->set_template_include_hook( '410' );
|
||||
|
||||
if ( ! $is_include_hook_set ) {
|
||||
$this->set_404();
|
||||
}
|
||||
|
||||
$this->status_header( 410 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the 451 status code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function do_451() {
|
||||
$is_include_hook_set = $this->set_template_include_hook( '451' );
|
||||
|
||||
if ( ! $is_include_hook_set ) {
|
||||
$this->set_404();
|
||||
}
|
||||
|
||||
$this->status_header( 451, 'Unavailable For Legal Reasons' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the template that should be included.
|
||||
*
|
||||
* @param string $template The template that will included before executing hook.
|
||||
*
|
||||
* @return string Returns the template that should be included.
|
||||
*/
|
||||
public function set_template_include( $template ) {
|
||||
if ( ! empty( $this->template_file_path ) ) {
|
||||
return $this->template_file_path;
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the $regex vars with URL matches.
|
||||
*
|
||||
* @param string[] $matches Array with the matches from the matching redirect.
|
||||
*
|
||||
* @return string The replaced URL.
|
||||
*/
|
||||
public function format_regex_redirect_url( $matches ) {
|
||||
$arr_key = \substr( $matches[0], 1 );
|
||||
|
||||
if ( isset( $this->url_matches[ $arr_key ] ) ) {
|
||||
return $this->url_matches[ $arr_key ];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the wp_query to 404 when this is an object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_404() {
|
||||
$wp_query = $this->get_wp_query();
|
||||
$wp_query->is_404 = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current URL matches a normal redirect.
|
||||
*
|
||||
* @param string $request_url The request url to look for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handle_normal_redirects( $request_url ) {
|
||||
// Setting the redirects.
|
||||
$redirects = $this->get_redirects( $this->normal_option_name );
|
||||
$this->redirects = $this->normalize_redirects( $redirects );
|
||||
|
||||
$request_url = $this->normalize_url( $request_url );
|
||||
|
||||
// Get the URL and doing the redirect.
|
||||
$redirect_url = $this->find_url( $request_url );
|
||||
|
||||
if ( empty( $redirect_url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->normalize_url( $redirect_url['url'] ) === $request_url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->is_redirected = true;
|
||||
$this->do_redirect( $redirect_url['url'], $redirect_url['type'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the url by trimming the slashes. If the given URL is a slash only,
|
||||
* it will do nothing. By normalizing the URL there is a basis for matching multiple
|
||||
* variants (Like: url, /url, /url/, url/).
|
||||
*
|
||||
* @param string $url The URL to normalize.
|
||||
*
|
||||
* @return string The modified url.
|
||||
*/
|
||||
protected function normalize_url( $url ) {
|
||||
if ( $url === '/' ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return \trim( $url, '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current URL matches a regex.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handle_regex_redirects() {
|
||||
// Setting the redirects.
|
||||
$this->redirects = $this->get_redirects( $this->regex_option_name );
|
||||
|
||||
foreach ( $this->redirects as $regex => $redirect ) {
|
||||
// Check if the URL matches the $regex.
|
||||
$this->match_regex_redirect( $regex, $redirect );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if request URL matches one of the regex redirects.
|
||||
*
|
||||
* @param string $regex The reqular expression to match.
|
||||
* @param array $redirect The URL that might be matched with the regex.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function match_regex_redirect( $regex, array $redirect ) {
|
||||
/*
|
||||
* Escape the ` because we use ` to delimit the regex to prevent faulty redirects.
|
||||
*
|
||||
* Explicitly chosen not to use `preg_quote` because we need to be able to parse
|
||||
* user provided regular expression syntax.
|
||||
*/
|
||||
$regex = \str_replace( '`', '\\`', $regex );
|
||||
|
||||
// Suppress warning: a faulty redirect will give a warning and not an exception. So we can't catch it.
|
||||
// See issue: https://github.com/Yoast/wordpress-seo-premium/issues/662.
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
if ( @\preg_match( "`{$regex}`", $this->request_url, $this->url_matches ) === 1 ) {
|
||||
|
||||
// Replace the $regex vars with URL matches.
|
||||
$redirect_url = \preg_replace_callback(
|
||||
'/\$[0-9]+/',
|
||||
[ $this, 'format_regex_redirect_url' ],
|
||||
$redirect['url']
|
||||
);
|
||||
|
||||
$this->do_redirect( $redirect_url, $redirect['type'] );
|
||||
}
|
||||
|
||||
// Reset url_matches.
|
||||
$this->url_matches = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the redirects from the options.
|
||||
*
|
||||
* @param string $option The option name that wil be fetched.
|
||||
*
|
||||
* @return array Returns the redirects for the given option.
|
||||
*/
|
||||
protected function get_redirects( $option ) {
|
||||
$redirects = $this->get_redirects_from_options();
|
||||
|
||||
if ( ! empty( $redirects[ $option ] ) ) {
|
||||
return $redirects[ $option ];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the redirect.
|
||||
*
|
||||
* @param string $redirect_url The target URL.
|
||||
* @param string $redirect_type The type of the redirect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function do_redirect( $redirect_url, $redirect_type ) {
|
||||
$redirect_url = $this->parse_target_url( $redirect_url );
|
||||
|
||||
// Prevents redirecting to itself.
|
||||
if ( $this->home_url( $this->request_url ) === $redirect_url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$redirect_types_without_target = [ 410, 451 ];
|
||||
if ( \in_array( $redirect_type, $redirect_types_without_target, true ) ) {
|
||||
$this->handle_redirect_without_target( $redirect_type );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->redirect( $redirect_url, $redirect_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a redirect has been executed.
|
||||
*
|
||||
* @return bool Whether a redirect has been executed.
|
||||
*/
|
||||
protected function is_redirected() {
|
||||
return $this->is_redirected === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we should load the PHP redirects.
|
||||
*
|
||||
* If Apache or NginX configuration is selected, don't load PHP redirects.
|
||||
*
|
||||
* @return bool True if PHP redirects should be loaded and used.
|
||||
*/
|
||||
protected function load_php_redirects() {
|
||||
|
||||
if ( \defined( 'WPSEO_DISABLE_PHP_REDIRECTS' ) && \WPSEO_DISABLE_PHP_REDIRECTS === true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( \defined( 'WP_CLI' ) && \WP_CLI === true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$options = \get_option( 'wpseo_redirect', false );
|
||||
if ( $options === false ) {
|
||||
// If the option is not set, save it, to prevent a query for a non-existing option on every page load.
|
||||
\add_action( 'wp_head', [ $this, 'save_default_redirect_options' ] );
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the PHP redirects are disabled intentionally, return false.
|
||||
if ( ! empty( $options['disable_php_redirect'] ) && $options['disable_php_redirect'] === 'on' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// PHP redirects are the enabled method of redirecting.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the default redirects options to the DB.
|
||||
*/
|
||||
public function save_default_redirect_options() {
|
||||
$redirect_option = WPSEO_Premium_Redirect_Option::get_instance();
|
||||
\update_option( 'wpseo_redirect', $redirect_option->get_defaults(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request URI, with fallback for super global.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_request_uri() {
|
||||
$options = [ 'options' => [ 'default' => '' ] ];
|
||||
$request_uri = \filter_input( \INPUT_SERVER, 'REQUEST_URI', \FILTER_SANITIZE_URL, $options );
|
||||
|
||||
// Because there isn't an usable value, try the fallback.
|
||||
if ( empty( $request_uri ) && isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- this value is compared. I don't want to change the behavior.
|
||||
$request_uri = \filter_var( $_SERVER['REQUEST_URI'], \FILTER_SANITIZE_URL, $options );
|
||||
}
|
||||
|
||||
$request_uri = $this->strip_subdirectory( $request_uri );
|
||||
|
||||
return \rawurldecode( $request_uri );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the redirects by raw url decoding the origin.
|
||||
*
|
||||
* @param array $redirects The redirects to normalize.
|
||||
*
|
||||
* @return array The normalized redirects.
|
||||
*/
|
||||
protected function normalize_redirects( $redirects ) {
|
||||
$normalized_redirects = [];
|
||||
|
||||
foreach ( $redirects as $origin => $redirect ) {
|
||||
$normalized_redirects[ \rawurldecode( $origin ) ] = $redirect;
|
||||
}
|
||||
|
||||
return $normalized_redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request URL and sanitize the slashes for it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_request_url() {
|
||||
$this->request_url = $this->get_request_uri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the URL in the redirects.
|
||||
*
|
||||
* @param string $url The needed URL.
|
||||
*
|
||||
* @return bool|string The found url or false if not found.
|
||||
*/
|
||||
protected function find_url( $url ) {
|
||||
$redirect_url = $this->search( $url );
|
||||
if ( ! empty( $redirect_url ) ) {
|
||||
return $redirect_url;
|
||||
}
|
||||
|
||||
return $this->find_url_fallback( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the given URL in the redirects array.
|
||||
*
|
||||
* @param string $url The URL to search for.
|
||||
*
|
||||
* @return string|bool The found url or false if not found.
|
||||
*/
|
||||
protected function search( $url ) {
|
||||
if ( ! empty( $this->redirects[ $url ] ) ) {
|
||||
return $this->redirects[ $url ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for alternatives with slashes if requested URL isn't found.
|
||||
*
|
||||
* This will add a slash if there isn't a slash or it will remove a trailing slash when there isn't one.
|
||||
*
|
||||
* @todo Discuss: Maybe we should add slashes to all the values we handle instead of using a fallback.
|
||||
*
|
||||
* @param string $url The URL that have to be matched.
|
||||
*
|
||||
* @return bool|string The found url or false if not found.
|
||||
*/
|
||||
protected function find_url_fallback( $url ) {
|
||||
$no_trailing_slash = \rtrim( $url, '/' );
|
||||
|
||||
$checks = [
|
||||
'no_trailing_slash' => $no_trailing_slash,
|
||||
'trailing_slash' => $no_trailing_slash . '/',
|
||||
];
|
||||
|
||||
foreach ( $checks as $check ) {
|
||||
$redirect_url = $this->search( $check );
|
||||
if ( ! empty( $redirect_url ) ) {
|
||||
return $redirect_url;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the target URL.
|
||||
*
|
||||
* @param string $target_url The URL to parse. When there isn't found a scheme, just parse it based on the home URL.
|
||||
*
|
||||
* @return string The parsed url.
|
||||
*/
|
||||
protected function parse_target_url( $target_url ) {
|
||||
if ( $this->has_url_scheme( $target_url ) ) {
|
||||
return $target_url;
|
||||
}
|
||||
|
||||
$target_url = $this->trailingslashit( $target_url );
|
||||
$target_url = $this->format_for_multisite( $target_url );
|
||||
|
||||
return $this->home_url( $target_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given url has a scheme.
|
||||
*
|
||||
* @param string $url The url to check.
|
||||
*
|
||||
* @return bool True when url has scheme.
|
||||
*/
|
||||
protected function has_url_scheme( $url ) {
|
||||
$scheme = \wp_parse_url( $url, \PHP_URL_SCHEME );
|
||||
|
||||
return ! empty( $scheme );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the target URL ends with a slash and adds one if necessary.
|
||||
*
|
||||
* @param string $target_url The url to format.
|
||||
*
|
||||
* @return string The url with trailing slash.
|
||||
*/
|
||||
protected function trailingslashit( $target_url ) {
|
||||
// Adds slash to target URL when permalink structure ends with a slash.
|
||||
if ( $this->requires_trailing_slash( $target_url ) ) {
|
||||
return \trailingslashit( $target_url );
|
||||
}
|
||||
|
||||
return $target_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the target url for the multisite if needed.
|
||||
*
|
||||
* @param string $target_url The url to format.
|
||||
*
|
||||
* @return string The formatted url.
|
||||
*/
|
||||
protected function format_for_multisite( $target_url ) {
|
||||
if ( ! \is_multisite() ) {
|
||||
return $target_url;
|
||||
}
|
||||
|
||||
$blog_details = \get_blog_details();
|
||||
if ( $blog_details && ! empty( $blog_details->path ) ) {
|
||||
$blog_path = \ltrim( $blog_details->path, '/' );
|
||||
if ( ! empty( $blog_path ) && \strpos( $target_url, $blog_path ) === 0 ) {
|
||||
$target_url = \substr( $target_url, \strlen( $blog_path ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $target_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the redirect URL by given URL.
|
||||
*
|
||||
* @param string $redirect_url The URL that has to be redirected.
|
||||
*
|
||||
* @return string The redirect url.
|
||||
*/
|
||||
protected function home_url( $redirect_url ) {
|
||||
$redirect_url = $this->strip_subdirectory( $redirect_url );
|
||||
|
||||
return \home_url( $redirect_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the subdirectory from the given url.
|
||||
*
|
||||
* @param string $url The url to strip the subdirectory from.
|
||||
*
|
||||
* @return string The url with the stripped subdirectory.
|
||||
*/
|
||||
protected function strip_subdirectory( $url ) {
|
||||
return WPSEO_Redirect_Util::strip_base_url_path_from_url( $this->get_home_url(), $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL PATH from the home url.
|
||||
*
|
||||
* @return string|null The url path or null if there isn't one.
|
||||
*/
|
||||
protected function get_home_url() {
|
||||
return \home_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the redirects from the option table in the database.
|
||||
*
|
||||
* @return array The stored redirects.
|
||||
*/
|
||||
protected function get_redirects_from_options() {
|
||||
global $wpdb;
|
||||
static $redirects;
|
||||
|
||||
if ( $redirects !== null ) {
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
// The code below is needed because we used to not autoload our redirect options. This fixes that.
|
||||
$all_options = \wp_cache_get( 'alloptions', 'options' );
|
||||
foreach ( [ $this->normal_option_name, $this->regex_option_name ] as $option ) {
|
||||
$redirects[ $option ] = isset( $all_options[ $option ] ) ? \maybe_unserialize( $all_options[ $option ] ) : false;
|
||||
if ( $redirects[ $option ] === false ) {
|
||||
$redirects[ $option ] = \get_option( $option, false );
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Normal methods only work if the option value has changed.
|
||||
$wpdb->update( $wpdb->options, [ 'autoload' => 'yes' ], [ 'option_name' => $option ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hook for setting the template include. This is the file that we want to show.
|
||||
*
|
||||
* @param string $template_to_set The template to look for.
|
||||
*
|
||||
* @return bool True when template should be included.
|
||||
*/
|
||||
protected function set_template_include_hook( $template_to_set ) {
|
||||
$this->template_file_path = $this->get_query_template( $template_to_set );
|
||||
if ( ! empty( $this->template_file_path ) ) {
|
||||
\add_filter( 'template_include', [ $this, 'set_template_include' ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the WordPress status_header function.
|
||||
*
|
||||
* @param int $code HTTP status code.
|
||||
* @param string $description Optional. A custom description for the HTTP status.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function status_header( $code, $description = '' ) {
|
||||
\status_header( $code, $description );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance of WP_Query.
|
||||
*
|
||||
* @return WP_Query Instance of WP_Query.
|
||||
*/
|
||||
protected function get_wp_query() {
|
||||
global $wp_query;
|
||||
|
||||
if ( \is_object( $wp_query ) ) {
|
||||
return $wp_query;
|
||||
}
|
||||
|
||||
return new WP_Query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the redirects without a target by setting the needed hooks.
|
||||
*
|
||||
* @param string $redirect_type The type of the redirect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handle_redirect_without_target( $redirect_type ) {
|
||||
if ( $redirect_type === 410 ) {
|
||||
\add_action( 'wp', [ $this, 'do_410' ] );
|
||||
}
|
||||
|
||||
if ( $redirect_type === 451 ) {
|
||||
\add_action( 'wp', [ $this, 'do_451' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper method for doing the actual redirect.
|
||||
*
|
||||
* @param string $location The path to redirect to.
|
||||
* @param int $status Status code to use.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function redirect( $location, $status = 302 ) {
|
||||
if ( ! \function_exists( 'wp_redirect' ) ) {
|
||||
require_once \ABSPATH . 'wp-includes/pluggable.php';
|
||||
}
|
||||
|
||||
\wp_redirect( $location, $status, 'Yoast SEO Premium' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a target URL requires a trailing slash.
|
||||
*
|
||||
* @param string $target_url The target URL to check.
|
||||
*
|
||||
* @return bool True when trailing slash is required.
|
||||
*/
|
||||
protected function requires_trailing_slash( $target_url ) {
|
||||
return WPSEO_Redirect_Util::requires_trailing_slash( $target_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query template.
|
||||
*
|
||||
* @param string $filename Filename without extension.
|
||||
*
|
||||
* @return string Full path to template file.
|
||||
*/
|
||||
protected function get_query_template( $filename ) {
|
||||
return \get_query_template( $filename );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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' ] );
|
||||
}
|
||||
}
|
||||
@@ -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' );
|
||||
}
|
||||
}
|
||||
@@ -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' ] );
|
||||
}
|
||||
}
|
||||
@@ -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' ] );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
330
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/elementor-premium.php
vendored
Normal file
330
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/elementor-premium.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
188
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/translationspress.php
vendored
Normal file
188
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/translationspress.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
121
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/zapier-trigger.php
vendored
Normal file
121
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/zapier-trigger.php
vendored
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
208
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/zapier.php
vendored
Normal file
208
wp/wp-content/plugins/wordpress-seo-premium/src/integrations/third-party/zapier.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 ];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
62
wp/wp-content/plugins/wordpress-seo-premium/src/main.php
Normal file
62
wp/wp-content/plugins/wordpress-seo-premium/src/main.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Premium;
|
||||
|
||||
use Yoast\WP\Lib\Abstract_Main;
|
||||
use Yoast\WP\SEO\Dependency_Injection\Container_Compiler;
|
||||
use Yoast\WP\SEO\Premium\Generated\Cached_Container;
|
||||
use Yoast\WP\SEO\Surfaces\Classes_Surface;
|
||||
use Yoast\WP\SEO\Surfaces\Helpers_Surface;
|
||||
|
||||
/**
|
||||
* Main plugin class for premium.
|
||||
*/
|
||||
class Main extends Abstract_Main {
|
||||
|
||||
// @phpcs:disable Generic.Commenting.DocComment.MissingShort -- Short description is in the inherited comments.
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function get_name() {
|
||||
return 'yoast-seo-premium';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function get_container() {
|
||||
if (
|
||||
$this->is_development()
|
||||
&& \class_exists( '\Yoast\WP\SEO\Dependency_Injection\Container_Compiler' )
|
||||
&& \file_exists( __DIR__ . '/../config/dependency-injection/services.php' )
|
||||
) {
|
||||
// Exception here is unhandled as it will only occur in development.
|
||||
Container_Compiler::compile(
|
||||
$this->is_development(),
|
||||
__DIR__ . '/generated/container.php',
|
||||
__DIR__ . '/../config/dependency-injection/services.php',
|
||||
__DIR__ . '/../vendor/composer/autoload_classmap.php',
|
||||
'Yoast\WP\SEO\Premium\Generated'
|
||||
);
|
||||
}
|
||||
|
||||
if ( \file_exists( __DIR__ . '/generated/container.php' ) ) {
|
||||
require_once __DIR__ . '/generated/container.php';
|
||||
|
||||
return new Cached_Container();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function get_surfaces() {
|
||||
return [
|
||||
'classes' => Classes_Surface::class,
|
||||
'helpers' => Helpers_Surface::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Models;
|
||||
|
||||
use Yoast\WP\Lib\Model;
|
||||
|
||||
/**
|
||||
* Indexable Hierarchy model definition.
|
||||
*
|
||||
* @property int $indexable_id The ID of the indexable.
|
||||
* @property int $ancestor_id The ID of the indexable's ancestor.
|
||||
* @property int $depth The depth of the ancestry. 1 being a parent, 2 being a grandparent etc.
|
||||
* @property int $blog_id Blog ID.
|
||||
*/
|
||||
class Indexable_Hierarchy extends Model {
|
||||
|
||||
/**
|
||||
* Which columns contain int values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $int_columns = [
|
||||
'indexable_id',
|
||||
'ancestor_id',
|
||||
'depth',
|
||||
'blog_id',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Models;
|
||||
|
||||
use Yoast\WP\Lib\Model;
|
||||
|
||||
/**
|
||||
* Indexable table definition.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $object_id
|
||||
* @property string $object_type
|
||||
* @property string $object_sub_type
|
||||
*
|
||||
* @property int $author_id
|
||||
* @property int $post_parent
|
||||
*
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
*
|
||||
* @property string $permalink
|
||||
* @property string $permalink_hash
|
||||
* @property string $canonical
|
||||
*
|
||||
* @property bool $is_robots_noindex
|
||||
* @property bool $is_robots_nofollow
|
||||
* @property bool $is_robots_noarchive
|
||||
* @property bool $is_robots_noimageindex
|
||||
* @property bool $is_robots_nosnippet
|
||||
*
|
||||
* @property string $title
|
||||
* @property string $description
|
||||
* @property string $breadcrumb_title
|
||||
*
|
||||
* @property bool $is_cornerstone
|
||||
*
|
||||
* @property string $primary_focus_keyword
|
||||
* @property int $primary_focus_keyword_score
|
||||
*
|
||||
* @property int $readability_score
|
||||
*
|
||||
* @property int $link_count
|
||||
* @property int $incoming_link_count
|
||||
* @property int $number_of_pages
|
||||
*
|
||||
* @property string $open_graph_title
|
||||
* @property string $open_graph_description
|
||||
* @property string $open_graph_image
|
||||
* @property string $open_graph_image_id
|
||||
* @property string $open_graph_image_source
|
||||
* @property string $open_graph_image_meta
|
||||
*
|
||||
* @property string $twitter_title
|
||||
* @property string $twitter_description
|
||||
* @property string $twitter_image
|
||||
* @property string $twitter_image_id
|
||||
* @property string $twitter_image_source
|
||||
* @property string $twitter_card
|
||||
*
|
||||
* @property int $prominent_words_version
|
||||
*
|
||||
* @property bool $is_public
|
||||
* @property bool $is_protected
|
||||
* @property string $post_status
|
||||
* @property bool $has_public_posts
|
||||
*
|
||||
* @property int $blog_id
|
||||
*
|
||||
* @property string $language
|
||||
* @property string $region
|
||||
*
|
||||
* @property string $schema_page_type
|
||||
* @property string $schema_article_type
|
||||
*
|
||||
* @property bool $has_ancestors
|
||||
*
|
||||
* @property int $estimated_reading_time_minutes
|
||||
*/
|
||||
class Indexable extends Model {
|
||||
|
||||
/**
|
||||
* Holds the ancestors.
|
||||
*
|
||||
* @var Indexable[]
|
||||
*/
|
||||
public $ancestors = [];
|
||||
|
||||
/**
|
||||
* Whether nor this model uses timestamps.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $uses_timestamps = true;
|
||||
|
||||
/**
|
||||
* Which columns contain boolean values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $boolean_columns = [
|
||||
'is_robots_noindex',
|
||||
'is_robots_nofollow',
|
||||
'is_robots_noarchive',
|
||||
'is_robots_noimageindex',
|
||||
'is_robots_nosnippet',
|
||||
'is_cornerstone',
|
||||
'is_public',
|
||||
'is_protected',
|
||||
'has_public_posts',
|
||||
];
|
||||
|
||||
/**
|
||||
* Which columns contain int values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $int_columns = [
|
||||
'id',
|
||||
'object_id',
|
||||
'author_id',
|
||||
'post_parent',
|
||||
'primary_focus_keyword_score',
|
||||
'readability_score',
|
||||
'link_count',
|
||||
'incoming_link_count',
|
||||
'number_of_pages',
|
||||
'prominent_words_version',
|
||||
'blog_id',
|
||||
'estimated_reading_time_minutes',
|
||||
];
|
||||
|
||||
/**
|
||||
* The loaded indexable extensions.
|
||||
*
|
||||
* @var Indexable_Extension[]
|
||||
*/
|
||||
protected $loaded_extensions = [];
|
||||
|
||||
/**
|
||||
* Returns an Indexable_Extension by its name.
|
||||
*
|
||||
* @param string $class_name The class name of the extension to load.
|
||||
*
|
||||
* @return Indexable_Extension|bool The extension.
|
||||
*/
|
||||
public function get_extension( $class_name ) {
|
||||
if ( ! $this->loaded_extensions[ $class_name ] ) {
|
||||
$this->loaded_extensions[ $class_name ] = $this->has_one( $class_name, 'indexable_id', 'id' )->find_one();
|
||||
}
|
||||
|
||||
return $this->loaded_extensions[ $class_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances the save method.
|
||||
*
|
||||
* @return bool True on success.
|
||||
*/
|
||||
public function save() {
|
||||
if ( $this->permalink ) {
|
||||
$this->sanitize_permalink();
|
||||
$this->permalink_hash = \strlen( $this->permalink ) . ':' . \md5( $this->permalink );
|
||||
}
|
||||
if ( \strlen( $this->primary_focus_keyword ) > 191 ) {
|
||||
$this->primary_focus_keyword = \substr( $this->primary_focus_keyword, 0, 191 );
|
||||
}
|
||||
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the permalink.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function sanitize_permalink() {
|
||||
$permalink_structure = \get_option( 'permalink_structure' );
|
||||
$permalink_parts = \wp_parse_url( $this->permalink );
|
||||
|
||||
if ( ! isset( $permalink_parts['path'] ) ) {
|
||||
$permalink_parts['path'] = '/';
|
||||
}
|
||||
if ( \substr( $permalink_structure, -1, 1 ) === '/' && \strpos( \substr( $permalink_parts['path'], -5 ), '.' ) === false ) {
|
||||
$permalink_parts['path'] = \trailingslashit( $permalink_parts['path'] );
|
||||
}
|
||||
|
||||
$permalink = '';
|
||||
if ( isset( $permalink_parts['scheme'] ) ) {
|
||||
$permalink .= $permalink_parts['scheme'] . '://';
|
||||
}
|
||||
if ( isset( $permalink_parts['host'] ) ) {
|
||||
$permalink .= $permalink_parts['host'];
|
||||
}
|
||||
if ( isset( $permalink_parts['port'] ) ) {
|
||||
$permalink .= ':' . $permalink_parts['port'];
|
||||
}
|
||||
if ( isset( $permalink_parts['path'] ) ) {
|
||||
$permalink .= $permalink_parts['path'];
|
||||
}
|
||||
if ( isset( $permalink_parts['query'] ) ) {
|
||||
$permalink .= '?' . $permalink_parts['query'];
|
||||
}
|
||||
// We never set the fragment as the fragment is intended to be client-only.
|
||||
$this->permalink = $permalink;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Models;
|
||||
|
||||
use Yoast\WP\Lib\Model;
|
||||
|
||||
/**
|
||||
* Table definition for the Prominent Words table.
|
||||
*
|
||||
* @property int $id The unique ID of this prominent word.
|
||||
* @property string $stem The stem of the prominent word.
|
||||
* @property int $indexable_id The ID of the indexable in which the prominent word is located.
|
||||
* @property float $weight Currently just the nr. of occurrences (of stemmed prominent word in indexable) But could be any weight value (higher means that it carries more weight in the final calculation).
|
||||
*/
|
||||
class Prominent_Words extends Model {}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Models;
|
||||
|
||||
use Yoast\WP\Lib\Model;
|
||||
|
||||
/**
|
||||
* Table definition for the SEO Links table.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $url
|
||||
* @property int $post_id
|
||||
* @property int $target_post_id
|
||||
* @property string $type
|
||||
* @property int $indexable_id
|
||||
* @property int $target_indexable_id
|
||||
* @property int $height
|
||||
* @property int $width
|
||||
* @property int $size
|
||||
* @property string $language
|
||||
* @property string $region
|
||||
*/
|
||||
class SEO_Links extends Model {
|
||||
|
||||
/**
|
||||
* Indicates that the link is external.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TYPE_INTERNAL = 'internal';
|
||||
|
||||
/**
|
||||
* Indicates that the link is internal.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TYPE_EXTERNAL = 'external';
|
||||
|
||||
/**
|
||||
* Indicates the link is an internal image.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TYPE_INTERNAL_IMAGE = 'image-in';
|
||||
|
||||
/**
|
||||
* Indicates the link is an external image.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TYPE_EXTERNAL_IMAGE = 'image-ex';
|
||||
|
||||
/**
|
||||
* Holds the parsed URL. May not be set.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $parsed_url;
|
||||
|
||||
/**
|
||||
* Which columns contain int values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $int_columns = [
|
||||
'id',
|
||||
'post_id',
|
||||
'target_post_id',
|
||||
'indexable_id',
|
||||
'target_indexable_id',
|
||||
'height',
|
||||
'width',
|
||||
'size',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Repositories;
|
||||
|
||||
use Yoast\WP\Lib\Model;
|
||||
use Yoast\WP\Lib\ORM;
|
||||
|
||||
/**
|
||||
* Class Prominent_Words_Repository
|
||||
*
|
||||
* @package Yoast\WP\SEO\ORM\Repositories
|
||||
*/
|
||||
class Prominent_Words_Repository {
|
||||
|
||||
/**
|
||||
* Starts a query for this repository.
|
||||
*
|
||||
* @return ORM
|
||||
*/
|
||||
public function query() {
|
||||
return Model::of_type( 'Prominent_Words' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the prominent words for a given post (indexable).
|
||||
*
|
||||
* @param int $indexable_id The indexable ID.
|
||||
*
|
||||
* @return array The list of prominent words items found by the idexable id.
|
||||
*/
|
||||
public function find_by_indexable_id( $indexable_id ) {
|
||||
return $this->query()->where( 'indexable_id', $indexable_id )->find_many();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the prominent words based on a list of indexable ids.
|
||||
* The method also computes the document frequency of each word and adds it as a separate property on the objects.
|
||||
*
|
||||
* @param array<int> $ids The ids of indexables to get prominent words for.
|
||||
*
|
||||
* @return array The list of prominent words items found by indexable ids.
|
||||
*/
|
||||
public function find_by_list_of_ids( $ids ) {
|
||||
if ( empty( $ids ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the name of the prominent words table (can be different for example in case of a multisite setup).
|
||||
$table = Model::get_table_name( 'prominent_words' );
|
||||
|
||||
$query = '( SELECT COUNT(*) FROM ' . $table . ' WHERE ' . $table . '.stem = prominent_words_table.stem )';
|
||||
|
||||
return $this->query()
|
||||
->table_alias( 'prominent_words_table' )
|
||||
->select( '*' )
|
||||
->select_expr( $query, 'df' )
|
||||
->where_in( 'indexable_id', $ids )
|
||||
->find_many();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all indexable ids which have prominent words with stems from the list.
|
||||
*
|
||||
* @param array $stems The stems of prominent words to search for.
|
||||
* @param int $limit The number of indexable ids to return in 1 call.
|
||||
* @param int $page From which page (batch) to begin.
|
||||
*
|
||||
* @return array The list of indexable ids.
|
||||
*/
|
||||
public function find_ids_by_stems( $stems, $limit, $page ) {
|
||||
if ( empty( $stems ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$offset = ( ( $page - 1 ) * $limit );
|
||||
$indexable_ids_in_prominent_words = $this->query()
|
||||
->distinct()
|
||||
->select( 'indexable_id' )
|
||||
->where_in( 'stem', $stems )
|
||||
->limit( $limit )
|
||||
->offset( $offset )
|
||||
->find_many();
|
||||
|
||||
return \array_map(
|
||||
static function( $obj ) {
|
||||
return $obj->indexable_id;
|
||||
},
|
||||
$indexable_ids_in_prominent_words
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of documents in which each of the given stems occurs.
|
||||
*
|
||||
* @param array<string> $stems The stems of the words for which to find the document frequencies.
|
||||
*
|
||||
* @return array The list of stems and their respective document frequencies. Each entry has a 'stem' and a 'document_frequency' parameter.
|
||||
*/
|
||||
public function count_document_frequencies( $stems ) {
|
||||
if ( empty( $stems ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/*
|
||||
* Count in how many documents each stem occurs by querying the database.
|
||||
* Returns "Prominent_Words" with two properties: 'stem' and 'document_frequency'.
|
||||
*/
|
||||
$raw_doc_frequencies = $this->query()
|
||||
->select( 'stem' )
|
||||
->select_expr( 'COUNT( stem )', 'document_frequency' )
|
||||
->where_in( 'stem', $stems )
|
||||
->group_by( 'stem' )
|
||||
->find_many();
|
||||
|
||||
// We want to change the raw document frequencies into a map mapping stems to document frequency.
|
||||
$stems = \array_map(
|
||||
static function ( $item ) {
|
||||
return $item->stem;
|
||||
},
|
||||
$raw_doc_frequencies
|
||||
);
|
||||
|
||||
$doc_frequencies = \array_fill_keys( $stems, 0 );
|
||||
foreach ( $raw_doc_frequencies as $raw_doc_frequency ) {
|
||||
$doc_frequencies[ $raw_doc_frequency->stem ] = (int) $raw_doc_frequency->document_frequency;
|
||||
}
|
||||
|
||||
return $doc_frequencies;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Routes;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Premium\Actions\Link_Suggestions_Action;
|
||||
|
||||
/**
|
||||
* Registers the route for the link suggestions retrieval.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Routes
|
||||
*/
|
||||
class Link_Suggestions_Route implements Route_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Represents the endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENDPOINT_QUERY = 'link_suggestions';
|
||||
|
||||
/**
|
||||
* Instance of the Link_Suggestions_Action.
|
||||
*
|
||||
* @var Link_Suggestions_Action
|
||||
*/
|
||||
protected $link_suggestions_action;
|
||||
|
||||
/**
|
||||
* Link_Suggestions_Route constructor.
|
||||
*
|
||||
* @param Link_Suggestions_Action $link_suggestions_action The action to handle the requests to the endpoint.
|
||||
*/
|
||||
public function __construct( Link_Suggestions_Action $link_suggestions_action ) {
|
||||
$this->link_suggestions_action = $link_suggestions_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
$route_args = [
|
||||
'methods' => 'GET',
|
||||
'args' => [
|
||||
'prominent_words' => [
|
||||
'required' => true,
|
||||
'type' => 'object',
|
||||
'description' => 'Stems of prominent words and their term frequencies we want link suggestions based on',
|
||||
],
|
||||
'object_id' => [
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'description' => 'The object id of the current indexable.',
|
||||
],
|
||||
'object_type' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The object type of the current indexable.',
|
||||
],
|
||||
'limit' => [
|
||||
'required' => false,
|
||||
'default' => 5,
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum number of link suggestions to retrieve',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'run_get_suggestions_action' ],
|
||||
'permission_callback' => [ $this, 'can_retrieve_data' ],
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::ENDPOINT_QUERY, $route_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the get suggestions action..
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response for the query of link suggestions.
|
||||
*/
|
||||
public function run_get_suggestions_action( WP_REST_Request $request ) {
|
||||
$prominent_words = $request->get_param( 'prominent_words' );
|
||||
$limit = $request->get_param( 'limit' );
|
||||
$object_id = $request->get_param( 'object_id' );
|
||||
$object_type = $request->get_param( 'object_type' );
|
||||
|
||||
return new WP_REST_Response(
|
||||
$this->link_suggestions_action->get_suggestions(
|
||||
$prominent_words,
|
||||
$limit,
|
||||
$object_id,
|
||||
$object_type
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is allowed to use this endpoint.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_retrieve_data() {
|
||||
return \current_user_can( 'edit_posts' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Routes;
|
||||
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Config\Indexing_Reasons;
|
||||
use Yoast\WP\SEO\Helpers\Indexing_Helper;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Complete_Action;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Content_Action;
|
||||
use Yoast\WP\SEO\Premium\Actions\Prominent_Words\Save_Action;
|
||||
|
||||
/**
|
||||
* Class Prominent_Words_Route
|
||||
*
|
||||
* @package Yoast\WP\SEO\Routes
|
||||
*/
|
||||
class Prominent_Words_Route extends Abstract_Indexation_Route {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* Feature namespace for the REST endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FEATURE_NAMESPACE = 'prominent_words';
|
||||
|
||||
/**
|
||||
* The get content route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const GET_CONTENT_ROUTE = self::FEATURE_NAMESPACE . '/get_content';
|
||||
|
||||
/**
|
||||
* The full content route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FULL_GET_CONTENT_ROUTE = Main::API_V1_NAMESPACE . '/' . self::GET_CONTENT_ROUTE;
|
||||
|
||||
/**
|
||||
* The route for saving the prominent words.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SAVE_ROUTE = self::FEATURE_NAMESPACE . '/save';
|
||||
|
||||
/**
|
||||
* The full namespaced route for saving the prominent words.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FULL_SAVE_ROUTE = Main::API_V1_NAMESPACE . '/' . self::SAVE_ROUTE;
|
||||
|
||||
/**
|
||||
* The posts data route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COMPLETE_ROUTE = self::FEATURE_NAMESPACE . '/complete';
|
||||
|
||||
/**
|
||||
* The full post data route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FULL_COMPLETE_ROUTE = Main::API_V1_NAMESPACE . '/' . self::COMPLETE_ROUTE;
|
||||
|
||||
/**
|
||||
* Represents that action that retrieves the content to index.
|
||||
*
|
||||
* @var Content_Action
|
||||
*/
|
||||
protected $content_action;
|
||||
|
||||
/**
|
||||
* The action to complete the prominent words indexing.
|
||||
*
|
||||
* @var Complete_Action
|
||||
*/
|
||||
protected $complete_action;
|
||||
|
||||
/**
|
||||
* The action for saving prominent words to an indexable.
|
||||
*
|
||||
* @var Save_Action
|
||||
*/
|
||||
protected $save_action;
|
||||
|
||||
/**
|
||||
* The indexing helper.
|
||||
*
|
||||
* @var Indexing_Helper
|
||||
*/
|
||||
protected $indexing_helper;
|
||||
|
||||
/**
|
||||
* Prominent_Words_Route constructor.
|
||||
*
|
||||
* @param Content_Action $content_action The content action.
|
||||
* @param Save_Action $save_action The save action.
|
||||
* @param Complete_Action $complete_action The complete action.
|
||||
* @param Indexing_Helper $indexing_helper The indexing helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Content_Action $content_action,
|
||||
Save_Action $save_action,
|
||||
Complete_Action $complete_action,
|
||||
Indexing_Helper $indexing_helper
|
||||
) {
|
||||
$this->content_action = $content_action;
|
||||
$this->save_action = $save_action;
|
||||
$this->complete_action = $complete_action;
|
||||
$this->indexing_helper = $indexing_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
\register_rest_route(
|
||||
Main::API_V1_NAMESPACE,
|
||||
self::GET_CONTENT_ROUTE,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'run_content_action' ],
|
||||
'permission_callback' => [ $this, 'can_retrieve_data' ],
|
||||
]
|
||||
);
|
||||
|
||||
\register_rest_route(
|
||||
Main::API_V1_NAMESPACE,
|
||||
self::COMPLETE_ROUTE,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'run_complete_action' ],
|
||||
'permission_callback' => [ $this, 'can_retrieve_data' ],
|
||||
]
|
||||
);
|
||||
|
||||
$route_args = [
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'data' => [
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'object_id' => [
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
],
|
||||
'prominent_words' => [
|
||||
'type' => 'object',
|
||||
'required' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'run_save_action' ],
|
||||
'permission_callback' => [ $this, 'can_retrieve_data' ],
|
||||
];
|
||||
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::SAVE_ROUTE, $route_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the content that needs to be analyzed for prominent words.
|
||||
*
|
||||
* @return WP_REST_Response Response with the content that needs to be analyzed for prominent words.
|
||||
*/
|
||||
public function run_content_action() {
|
||||
return $this->run_indexation_action( $this->content_action, self::FULL_GET_CONTENT_ROUTE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the indexing of prominent words as completed.
|
||||
*
|
||||
* @return WP_REST_Response Response with empty data.
|
||||
*/
|
||||
public function run_complete_action() {
|
||||
$this->complete_action->complete();
|
||||
|
||||
return $this->respond_with( [], false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the prominent words for the indexables.
|
||||
*
|
||||
* The request should have the parameters:
|
||||
* - **data**: The data array containing:
|
||||
* - **object_id**: The ID of the object (post-id, term-id, etc.).
|
||||
* - **prominent_words**: The map of `'stem' => weight` key-value pairs,
|
||||
* e.g. the stems of the prominent words and their weights.
|
||||
* Leave this out when the indexable has no prominent words.
|
||||
*
|
||||
* @param WP_REST_Request $request The request to handle.
|
||||
*
|
||||
* @return WP_REST_Response The response to give.
|
||||
*/
|
||||
public function run_save_action( WP_REST_Request $request ) {
|
||||
$this->save_action->save( $request->get_param( 'data' ) );
|
||||
|
||||
return new WP_REST_Response(
|
||||
[ 'message' => 'The words have been successfully saved for the given indexables.' ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is allowed to use this endpoint.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_retrieve_data() {
|
||||
return \current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an indexing action and returns the response.
|
||||
*
|
||||
* @param Indexation_Action_Interface $indexation_action The indexing action.
|
||||
* @param string $url The url of the indexing route.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error The response, or an error when running the indexing action failed.
|
||||
*/
|
||||
protected function run_indexation_action( Indexation_Action_Interface $indexation_action, $url ) {
|
||||
try {
|
||||
return parent::run_indexation_action( $indexation_action, $url );
|
||||
} catch ( Exception $exception ) {
|
||||
$this->indexing_helper->set_reason( Indexing_Reasons::REASON_INDEXING_FAILED );
|
||||
|
||||
return new WP_Error( 'wpseo_error_indexing', $exception->getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Routes;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Conditionals\Zapier_Enabled_Conditional;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Premium\Actions\Zapier_Action;
|
||||
|
||||
/**
|
||||
* Registers the route for the Zapier integration.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Routes
|
||||
*/
|
||||
class Zapier_Route implements Route_Interface {
|
||||
|
||||
/**
|
||||
* The Zapier route prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ROUTE_PREFIX = 'zapier';
|
||||
|
||||
/**
|
||||
* The subscribe route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SUBSCRIBE_ROUTE = self::ROUTE_PREFIX . '/subscribe';
|
||||
|
||||
/**
|
||||
* The unsubscribe route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const UNSUBSCRIBE_ROUTE = self::ROUTE_PREFIX . '/unsubscribe';
|
||||
|
||||
/**
|
||||
* The check route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CHECK_API_KEY_ROUTE = self::ROUTE_PREFIX . '/check';
|
||||
|
||||
/**
|
||||
* The perform list route constant.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PERFORM_LIST = self::ROUTE_PREFIX . '/list';
|
||||
|
||||
/**
|
||||
* Instance of the Zapier_Action.
|
||||
*
|
||||
* @var Zapier_Action
|
||||
*/
|
||||
protected $zapier_action;
|
||||
|
||||
/**
|
||||
* Zapier_Route constructor.
|
||||
*
|
||||
* @param Zapier_Action $zapier_action The action to handle the requests to the endpoint.
|
||||
*/
|
||||
public function __construct( Zapier_Action $zapier_action ) {
|
||||
$this->zapier_action = $zapier_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
$subscribe_route_args = [
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'url' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The callback URL to use.',
|
||||
],
|
||||
'api_key' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The API key to validate.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'subscribe' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::SUBSCRIBE_ROUTE, $subscribe_route_args );
|
||||
|
||||
$unsubscribe_route_args = [
|
||||
'methods' => 'DELETE',
|
||||
'args' => [
|
||||
'id' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The ID of the subscription to unsubscribe.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'unsubscribe' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::UNSUBSCRIBE_ROUTE, $unsubscribe_route_args );
|
||||
|
||||
$check_api_key_route_args = [
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'api_key' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The API key to validate.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'check_api_key' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::CHECK_API_KEY_ROUTE, $check_api_key_route_args );
|
||||
|
||||
$perform_list_route_args = [
|
||||
'methods' => 'GET',
|
||||
'args' => [
|
||||
'api_key' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The API key to validate.',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'perform_list' ],
|
||||
'permission_callback' => '__return_true',
|
||||
];
|
||||
\register_rest_route( Main::API_V1_NAMESPACE, self::PERFORM_LIST, $perform_list_route_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the subscribe action.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the subscribe action.
|
||||
*/
|
||||
public function subscribe( WP_REST_Request $request ) {
|
||||
$subscription = $this->zapier_action->subscribe( $request['url'], $request['api_key'] );
|
||||
$response = $subscription->data;
|
||||
|
||||
if ( empty( $response ) && \property_exists( $subscription, 'message' ) ) {
|
||||
$response = $subscription->message;
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response, $subscription->status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the unsubscribe action.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the unsubscribe action.
|
||||
*/
|
||||
public function unsubscribe( WP_REST_Request $request ) {
|
||||
$subscription = $this->zapier_action->unsubscribe( $request['id'] );
|
||||
|
||||
return new WP_REST_Response( $subscription->message, $subscription->status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the check_api_key action.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the check_api_key action.
|
||||
*/
|
||||
public function check_api_key( WP_REST_Request $request ) {
|
||||
$check = $this->zapier_action->check_api_key( $request['api_key'] );
|
||||
|
||||
return new WP_REST_Response( $check->message, $check->status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the check_api_key action.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response of the check_api_key action.
|
||||
*/
|
||||
public function perform_list( WP_REST_Request $request ) {
|
||||
$response = $this->zapier_action->perform_list( $request['api_key'] );
|
||||
|
||||
return new WP_REST_Response( $response->data, $response->status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conditionals based in which these routes should be active.
|
||||
*
|
||||
* @return array The list of conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Zapier_Enabled_Conditional::class ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Assets;
|
||||
|
||||
/**
|
||||
* Class Icons.
|
||||
*
|
||||
* Provides SVG icons as strings.
|
||||
*/
|
||||
class Icons {
|
||||
|
||||
/**
|
||||
* Represents the start of the SVG tag.
|
||||
*/
|
||||
const SVG_START_TAG = "<svg xmlns='http://www.w3.org/2000/svg' fill='none' style='fill:none' viewBox='0 0 24 24' stroke='currentColor' height='%SIZE%' width='%SIZE%' >";
|
||||
|
||||
/**
|
||||
* The default height and width of an icon.
|
||||
*/
|
||||
const SIZE_DEFAULT = 24;
|
||||
|
||||
/**
|
||||
* The height and width of an icon in a variation picker.
|
||||
*/
|
||||
const SIZE_VARIATION = 36;
|
||||
|
||||
/**
|
||||
* The Heroicons academic cap svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_academic_cap( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path fill='#fff' d='M12 14l9-5-9-5-9 5 9 5z' />"
|
||||
. "<path fill='#fff' d='M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z' />"
|
||||
. "<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons annotation svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_annotation( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons ban svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_ban( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons briefcase svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_briefcase( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons calendar svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_calendar( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons clipboard check svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_clipboard_check( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons clipboard copy svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_clipboard_copy( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons clipboard list svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_clipboard_list( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons clock svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_clock( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<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' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons currency dollar svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_currency_dollar( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons cursor_click svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_cursor_click( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons document text svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_document_text( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons globe svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_globe( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons identification svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_identification( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons location marker svg icon
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_location_marker( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z' />"
|
||||
. "<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15 11a3 3 0 11-6 0 3 3 0 016 0z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons office building svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_office_building( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path d='M19 21V5C19 3.89543 18.1046 3 17 3H7C5.89543 3 5 3.89543 5 5V21M19 21L21 21M19 21H14M5 21L3 21M5 21H10M9 6.99998H10M9 11H10M14 6.99998H15M14 11H15M10 21V16C10 15.4477 10.4477 15 11 15H13C13.5523 15 14 15.4477 14 16V21M10 21H14' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons photograph svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_photograph( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons switch horizontal svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
* @return string
|
||||
*/
|
||||
public static function heroicons_switch_horizontal( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Heroicons grid svg icon.
|
||||
*
|
||||
* @param int $size The Height and Width of the SVG icon.
|
||||
*
|
||||
* @return string The generated icon.
|
||||
*/
|
||||
public static function heroicons_grid( $size = self::SIZE_DEFAULT ) {
|
||||
return self::svg(
|
||||
"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z' />",
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SVG based on given path.
|
||||
*
|
||||
* @param string $path The path to generate svg for.
|
||||
* @param int $svg_size The Height and Width of the SVG icon.
|
||||
*
|
||||
* @return string The generated icon svg.
|
||||
*/
|
||||
protected static function svg( $path, $svg_size = self::SIZE_DEFAULT ) {
|
||||
$start = \str_replace( '%SIZE%', $svg_size, self::SVG_START_TAG );
|
||||
return $start . $path . '</svg>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* Holds the names of the block pattern categories.
|
||||
*/
|
||||
class Block_Pattern_Categories {
|
||||
const YOAST_JOB_POSTING = 'yoast_job_posting';
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* Holds the names of the block pattern keywords.
|
||||
*/
|
||||
class Block_Pattern_Keywords {
|
||||
const YOAST_JOB_POSTING = [ 'yoast', 'job', 'posting', 'vacancy' ];
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A Gutenberg block pattern.
|
||||
*/
|
||||
abstract class Block_Pattern {
|
||||
|
||||
/**
|
||||
* Returns the block pattern configuration.
|
||||
*
|
||||
* @return string[] The configuration.
|
||||
*/
|
||||
public function get_configuration() {
|
||||
return [
|
||||
'title' => $this->get_title(),
|
||||
'content' => $this->get_content(),
|
||||
'categories' => (array) $this->get_categories(),
|
||||
'keywords' => (array) $this->get_keywords(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this block pattern.
|
||||
*
|
||||
* @return string The name of this block pattern.
|
||||
*/
|
||||
abstract public function get_name();
|
||||
|
||||
/**
|
||||
* Gets the title of this block pattern.
|
||||
*
|
||||
* @return string The title of this block pattern.
|
||||
*/
|
||||
abstract public function get_title();
|
||||
|
||||
/**
|
||||
* Gets the contents of this block pattern.
|
||||
*
|
||||
* @return string The contents of this block pattern.
|
||||
*/
|
||||
abstract public function get_content();
|
||||
|
||||
/**
|
||||
* Gets the categories of this block pattern.
|
||||
*
|
||||
* @return string[] The categories of this block pattern.
|
||||
*/
|
||||
abstract public function get_categories();
|
||||
|
||||
/**
|
||||
* Gets the keywords of this block pattern.
|
||||
*
|
||||
* @return string[] The keywords of this block pattern.
|
||||
*/
|
||||
abstract public function get_keywords();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
// phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A minimal job posting, containing required blocks only.
|
||||
*/
|
||||
abstract class Job_Posting_Base_Pattern extends Block_Pattern {
|
||||
|
||||
/**
|
||||
* Includes this Job Posting block pattern in the Yoast Job Posting block pattern category.
|
||||
*
|
||||
* @return array The categories under which this block pattern should be shown.
|
||||
*/
|
||||
public function get_categories() {
|
||||
return [ Block_Pattern_Categories::YOAST_JOB_POSTING ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keywords of this block pattern.
|
||||
*
|
||||
* @return array The keywords that help users discover the pattern while searching.
|
||||
*/
|
||||
public function get_keywords() {
|
||||
return [ Block_Pattern_Keywords::YOAST_JOB_POSTING ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A default job posting, containing all the required and recommended blocks.
|
||||
*/
|
||||
class Job_Posting_Default extends Job_Posting_Base_Pattern {
|
||||
|
||||
/**
|
||||
* Gets the name of this block pattern.
|
||||
*
|
||||
* @return string The name of this block pattern.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'yoast/job-posting/default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of this block pattern.
|
||||
*
|
||||
* @return string The title of this block pattern.
|
||||
*/
|
||||
public function get_title() {
|
||||
return 'Job Posting (default)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of this block pattern.
|
||||
*
|
||||
* @return string The contents of this block pattern.
|
||||
*/
|
||||
public function get_content() {
|
||||
return '<!-- wp:columns {"align":"wide"} -->
|
||||
<div class="wp-block-columns alignwide"><!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:yoast/job-employment-type {"employmentType":"FULL_TIME"} -->
|
||||
<div class="yoast-inner-container"><h3 data-id="title">Employment</h3><span data-id="employmentType" data-value="FULL_TIME">Full time</span></div>
|
||||
<!-- /wp:yoast/job-employment-type --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:yoast/job-salary -->
|
||||
<div class="yoast-inner-container"><!-- wp:yoast/job-salary-range {"currency":"USD","minValue":"1000","maxValue":"2000","unit":"MONTH"} -->
|
||||
<div class="yoast-inner-container"><h3 data-id="title">Salary range</h3><div class="yoast-schema-flex"><span data-id="currency" data-value="USD">USD</span> 1000 - 2000 / <span data-id="unit" data-value="MONTH">month</span></div></div>
|
||||
<!-- /wp:yoast/job-salary-range --></div>
|
||||
<!-- /wp:yoast/job-salary --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:yoast/job-location -->
|
||||
<div class="yoast-inner-container"><!-- wp:yoast/remote-location -->
|
||||
<div class="yoast-inner-container"><h3 data-id="Location">Location</h3><p data-id="remote-location">This job is 100% remote.</p></div>
|
||||
<!-- /wp:yoast/remote-location --></div>
|
||||
<!-- /wp:yoast/job-location --></div>
|
||||
<!-- /wp:column --></div>
|
||||
<!-- /wp:columns -->
|
||||
|
||||
<!-- wp:separator {"align":"wide"} -->
|
||||
<hr class="wp-block-separator alignwide"/>
|
||||
<!-- /wp:separator -->
|
||||
|
||||
<!-- wp:paragraph {"style":{"typography":{"fontSize":24}}} -->
|
||||
<p style="font-size:24px">We’re looking for a software architect or senior developer with a passion for software architecture to join our rapidly expanding business. Come help us make our development team even better!</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2 id="h-about-the-job">About the job</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:yoast/job-description -->
|
||||
<div class="yoast-inner-container"><p data-id="description">Technical leadership will be key in your role as a software architect. Our software architects solve big picture problems. You will be challenged by complex issues that require your smartly designed programs and systems to be tackled. So, software development is part of the job but will not be your primary task.</p></div>
|
||||
<!-- /wp:yoast/job-description -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2 id="h-about-you">About you</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>Preferably you have some experience with (some of) the technology we use, like WordPress, PHP and (modern) JavaScript. You are familiar with design patterns and coding principles like SOLID and TDD and are able to explain and think in different programming paradigms (like functional vs OOP).</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:yoast/job-requirements {"title_level":2} -->
|
||||
<div class="yoast-inner-container"><h2 data-id="title">Requirements</h2><ul data-id="requirements"><li>Multiple years of software development experience.</li><li>The ability to clearly explain things to your colleagues.</li><li>The ability to learn fast.</li><li>Quickly familiarize yourself with new codebases and frameworks.</li></ul></div>
|
||||
<!-- /wp:yoast/job-requirements -->
|
||||
|
||||
<!-- wp:yoast/job-benefits {"title_level":2} -->
|
||||
<div class="yoast-inner-container"><h2 data-id="title">Benefits</h2><ul data-id="benefits"><li>A challenging job in a fast-growing, dynamic, ambitious and international atmosphere. Working at a company that impacts the web.</li><li>We have a great pension plan, which is fully paid by Yoast.</li><li>Exercise and stay fit! We have our own gym and a personal trainer!</li><li>The opportunity to learn a lot in a short time, at one of the leading SEO companies.</li></ul></div>
|
||||
<!-- /wp:yoast/job-benefits -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>Are you interested?<strong> Then respond before May 19, 2021.</strong> The application procedure consists of three interviews. Do you have questions? We\'ll be happy to answer them. Please send an email to jobs@yoast.com.</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:columns -->
|
||||
<div class="wp-block-columns"><!-- wp:column {"verticalAlignment":"center","width":"33.33%"} -->
|
||||
<div class="wp-block-column is-vertically-aligned-center" style="flex-basis:33.33%"><!-- wp:yoast/job-apply-button {"placeholder":"Apply button (add a link)"} -->
|
||||
<div class="yoast-inner-container"><div><a class="wp-block-button__link">Apply now</a></div></div>
|
||||
<!-- /wp:yoast/job-apply-button --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column {"verticalAlignment":"center","width":"66.66%"} -->
|
||||
<div class="wp-block-column is-vertically-aligned-center" style="flex-basis:66.66%"><!-- wp:yoast/job-expiration {"expirationDate":"2021-05-19"} -->
|
||||
<div class="yoast-inner-container"><strong data-id="title">Apply before</strong><div><time datetime="2021-05-19">May 19, 2021</time></div></div>
|
||||
<!-- /wp:yoast/job-expiration --></div>
|
||||
<!-- /wp:column --></div>
|
||||
<!-- /wp:columns -->
|
||||
|
||||
<!-- wp:separator {"align":"wide"} -->
|
||||
<hr class="wp-block-separator alignwide"/>
|
||||
<!-- /wp:separator -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p></p>
|
||||
<!-- /wp:paragraph -->';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A minimal job posting, containing required blocks only.
|
||||
*/
|
||||
class Job_Posting_Minimal extends Job_Posting_Base_Pattern {
|
||||
|
||||
/**
|
||||
* Gets the name of this block pattern.
|
||||
*
|
||||
* @return string The name of this block pattern.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'yoast/job-posting/minimal';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of this block pattern.
|
||||
*
|
||||
* @return string The title of this block pattern.
|
||||
*/
|
||||
public function get_title() {
|
||||
return 'Job Posting (minimal)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of this block pattern.
|
||||
*
|
||||
* @return string The contents of this block pattern.
|
||||
*/
|
||||
public function get_content() {
|
||||
return '<!-- wp:yoast/job-description {"yoast-schema":null} -->
|
||||
<div class="yoast-inner-container"><p data-id="description"></p></div>
|
||||
<!-- /wp:yoast/job-description -->
|
||||
|
||||
<!-- wp:yoast/job-location -->
|
||||
<div class="yoast-inner-container"></div>
|
||||
<!-- /wp:yoast/job-location -->';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.Invalid
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
// phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Schema_Templates\Block_Patterns;
|
||||
|
||||
/**
|
||||
* A job posting as seen on Yoast.com.
|
||||
*/
|
||||
class Job_Posting_Yoast_Com extends Job_Posting_Base_Pattern {
|
||||
|
||||
/**
|
||||
* Gets the name of this block pattern.
|
||||
*
|
||||
* @return string The name of this block pattern.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'yoast/job-posting/yoast';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of this block pattern.
|
||||
*
|
||||
* @return string The title of this block pattern.
|
||||
*/
|
||||
public function get_title() {
|
||||
return 'Job Posting (yoast.com)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of this block pattern.
|
||||
*
|
||||
* @return string The contents of this block pattern.
|
||||
*/
|
||||
public function get_content() {
|
||||
return '<!-- wp:columns {"align":"wide"} -->
|
||||
<div class="wp-block-columns alignwide"><!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:yoast/job-employment-type {"title_level":2,"employmentType":"FULL_TIME"} -->
|
||||
<div class="yoast-inner-container"><h2 data-id="title">Employment</h2><span data-id="employmentType" data-value="FULL_TIME">Full time</span></div>
|
||||
<!-- /wp:yoast/job-employment-type --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column -->
|
||||
<div class="wp-block-column"><!-- wp:yoast/job-salary -->
|
||||
<div class="yoast-inner-container"><!-- wp:yoast/job-base-salary {"currency":"USD","value":"4000","unit":"MONTH"} -->
|
||||
<div class="yoast-inner-container"><h3 data-id="title">Base salary</h3><div class="yoast-schema-flex"><span data-id="currency" data-value="USD">USD</span> 4000 / <span data-id="unit" data-value="MONTH">month</span></div></div>
|
||||
<!-- /wp:yoast/job-base-salary --></div>
|
||||
<!-- /wp:yoast/job-salary --></div>
|
||||
<!-- /wp:column --></div>
|
||||
<!-- /wp:columns -->
|
||||
|
||||
<!-- wp:separator {"align":"wide"} -->
|
||||
<hr class="wp-block-separator alignwide"/>
|
||||
<!-- /wp:separator -->
|
||||
|
||||
<!-- wp:columns {"align":"wide"} -->
|
||||
<div class="wp-block-columns alignwide"><!-- wp:column {"width":"66.66%"} -->
|
||||
<div class="wp-block-column" style="flex-basis:66.66%"><!-- wp:paragraph {"style":{"typography":{"fontSize":24}}} -->
|
||||
<p style="font-size:24px">Yoast is growing! And we’re searching for an ambitious UX designer! Do you want to work on software that is used by over 12 million people worldwide? Do you believe that good design can make the complex field of SEO more accessible to an even larger audience? If you do, then we’re looking for you!</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2 id="h-about-the-job">About the job</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:yoast/job-description -->
|
||||
<div class="yoast-inner-container"><p data-id="description">As UX designer, you’ll be part of a multidisciplinary squad, consisting of developers, a tester, and a product owner. Together you’ll work on pushing Yoast SEO forward. Your squad will be partly based in the Netherlands, and partly in other places within a similar time zone.<br><br>Besides working within a squad on a daily basis, you’re also part of our UX tribe, consisting of other UX designers and - researchers. As part of this tribe you help improve our design system and our design processes. You contribute to develop a shared vision.</p></div>
|
||||
<!-- /wp:yoast/job-description -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2 id="h-about-you">About you</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>Do you love diving into details? And do you care deeply about creating visually appealing experiences? But are you also convinced that creativity is found in truly understanding a problem and in finding the right solution?</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>We encourage out-of-the-box thinking (we love new ideas!), but we also value a pragmatic attitude. You respect the scope of the project, how your choices impact others and what possible trade-offs are. You understand that your designs are not a work of art, but a means to building a product. A product that you build together with your teammates.</p>
|
||||
<!-- /wp:paragraph -->
|
||||
|
||||
<!-- wp:yoast/job-requirements {"title_level":2} -->
|
||||
<div class="yoast-inner-container"><h2 data-id="title">Requirements</h2><ul data-id="requirements"><li>You enjoy working as part of a close-knit team. Together with your squad members, you’re responsible for fleshing out product ideas, bringing them to life and shipping them to our users.</li><li>You are passionate about information architecture and are comfortable working with design systems and - patterns.</li><li>You are able to create highly detailed designs and prototypes, but also fast proof of concepts.</li><li>You are comfortable with modern design tools (Sketch is the tool we use).</li><li>If you have experience with HTML, CSS and JS, we consider that a bonus.</li><li>If you have experience with WordPress and the field of SEO, we also consider that a bonus.</li></ul></div>
|
||||
<!-- /wp:yoast/job-requirements -->
|
||||
|
||||
<!-- wp:yoast/job-benefits {"title_level":2} -->
|
||||
<div class="yoast-inner-container"><h2 data-id="title">Benefits</h2><ul data-id="benefits"><li>A challenging job in a fast-growing, dynamic, ambitious and international atmosphere.</li><li>Working at a company that impacts the web.</li><li>Exercise and stay fit! We have our own gym and a personal trainer!</li><li>We have a really fun (slightly crazy) company culture with lots and lots of team building activities. The know-your-colleagues-quiz, lots of celebrations and LEGO-building days. Because of Covid a lot of these things are online now!</li><li>The opportunity to learn a lot in a short time, at one of the leading SEO companies.</li></ul></div>
|
||||
<!-- /wp:yoast/job-benefits -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
<p>Are you interested?<strong> Then respond before May 19, 2021.</strong> The application procedure consists of three interviews. Do you have questions? We\'ll be happy to answer them. Please send an email to jobs@yoast.com.</p>
|
||||
<!-- /wp:paragraph --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column {"width":"33.33%"} -->
|
||||
<div class="wp-block-column" style="flex-basis:33.33%"><!-- wp:yoast/job-location -->
|
||||
<div class="yoast-inner-container"><!-- wp:yoast/office-location -->
|
||||
<div class="yoast-inner-container"><h3 data-id="Location">Location</h3><!-- wp:yoast/job-location-address -->
|
||||
<div class="yoast-job-block__location__address yoast-inner-container"><span data-id="address">Dierenstraat 2021</span></div>
|
||||
<!-- /wp:yoast/job-location-address -->
|
||||
|
||||
<!-- wp:yoast/job-location-postal-code -->
|
||||
<div class="yoast-job-block__location__postal-code yoast-inner-container"><span data-id="postal-code">1234 AB</span></div>
|
||||
<!-- /wp:yoast/job-location-postal-code -->
|
||||
|
||||
<!-- wp:yoast/job-location-city -->
|
||||
<div class="yoast-job-block__location__city yoast-inner-container"><span data-id="city">Wijchen</span></div>
|
||||
<!-- /wp:yoast/job-location-city -->
|
||||
|
||||
<!-- wp:yoast/job-location-country -->
|
||||
<div class="yoast-job-block__location__country yoast-inner-container"><span data-id="country">Netherlands</span></div>
|
||||
<!-- /wp:yoast/job-location-country --></div>
|
||||
<!-- /wp:yoast/office-location --></div>
|
||||
<!-- /wp:yoast/job-location -->
|
||||
|
||||
<!-- wp:yoast/job-apply-button {"placeholder":"Apply button (add a link)"} -->
|
||||
<div class="yoast-inner-container"><div><a class="wp-block-button__link">Apply now</a></div></div>
|
||||
<!-- /wp:yoast/job-apply-button -->
|
||||
|
||||
<!-- wp:yoast/job-expiration {"expirationDate":"2021-05-21"} -->
|
||||
<div class="yoast-inner-container"><strong data-id="title">Closes on</strong><div><time datetime="2021-05-21">May 21, 2021</time></div></div>
|
||||
<!-- /wp:yoast/job-expiration --></div>
|
||||
<!-- /wp:column --></div>
|
||||
<!-- /wp:columns -->';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Job apply button block template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-apply-button" title="<?php esc_attr_e( 'Apply button', 'wordpress-seo-premium' ); ?>" category="yoast-recommended-job-blocks" description="<?php esc_attr_e( 'A button through which visitors can apply for the job. (Make sure to add a link.)', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_cursor_click(); ?>" supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__apply-button {{class-name}}">
|
||||
{{link-button name="apply_button" placeholder="<?php esc_attr_e( 'Apply button (add a link)', 'wordpress-seo-premium' ); ?>" }}
|
||||
</div>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Job base salary block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
$wpseo_time_unit_options = [
|
||||
[
|
||||
'value' => 'HOUR',
|
||||
'label' => __( 'hour', 'wordpress-seo-premium' ),
|
||||
],
|
||||
[
|
||||
'value' => 'DAY',
|
||||
'label' => __( 'day', 'wordpress-seo-premium' ),
|
||||
],
|
||||
[
|
||||
'value' => 'WEEK',
|
||||
'label' => __( 'week', 'wordpress-seo-premium' ),
|
||||
],
|
||||
[
|
||||
'value' => 'MONTH',
|
||||
'label' => __( 'month', 'wordpress-seo-premium' ),
|
||||
],
|
||||
[
|
||||
'value' => 'YEAR',
|
||||
'label' => __( 'year', 'wordpress-seo-premium' ),
|
||||
],
|
||||
];
|
||||
|
||||
// phpcs:disable Yoast.Yoast.AlternativeFunctions.json_encode_wp_json_encode -- We do not want any pretty printing, since it would break the schema blocks.
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-base-salary" title="<?php esc_attr_e( 'Base salary', 'wordpress-seo-premium' ); ?>" category="common" description="<?php esc_attr_e( 'The base salary of the job.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_currency_dollar(); ?>" parent=[ "yoast/job-salary" ] supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__salary {{class-name}}">
|
||||
<div>
|
||||
{{rich-text name="title" tag="strong" keepPlaceholderOnFocus=true default="<?php esc_attr_e( 'Base salary', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
<div class="yoast-schema-flex">
|
||||
{{currency-select name="currency" hideLabelFromVision=true label="<?php esc_attr_e( 'Currency', 'wordpress-seo-premium' ); ?>" className="yoast-schema-select"}}
|
||||
{{text-input name="value" type="number" hideLabelFromVision=true placeholder="<?php esc_attr_e( 'Enter amount', 'wordpress-seo-premium' ); ?>" label="<?php esc_attr_e( 'Value', 'wordpress-seo-premium' ); ?>" }}
|
||||
/ {{select name="unit" label="<?php esc_attr_e( 'Unit', 'wordpress-seo-premium' ); ?>" hideLabelFromVision=true className="yoast-schema-select" options=<?php echo \wp_json_encode( $wpseo_time_unit_options ); ?> defaultValue="MONTH"}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-base-salary" only-nested=true}}
|
||||
{
|
||||
"@type": "MonetaryAmount",
|
||||
"currency": {{attribute name="currency"}},
|
||||
"value": {
|
||||
"@type": "QuantitativeValue",
|
||||
"value": {{attribute name="value"}},
|
||||
"unitText": {{attribute name="unit"}}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Job benefits block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-benefits" category="yoast-recommended-job-blocks" description="<?php esc_attr_e( 'The description of benefits associated with the job.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_clipboard_check(); ?>" supports={"multiple": false} title="<?php esc_attr_e( 'Benefits', 'wordpress-seo-premium' ); ?>" }}
|
||||
<div class="yoast-job-block__benefits {{class-name}}">
|
||||
{{heading name="title" defaultHeadingLevel=3 default="<?php esc_attr_e( 'Benefits', 'wordpress-seo-premium' ); ?>" }}
|
||||
{{rich-text name="benefits" tag="ul" multiline="li" keepPlaceholderOnFocus=true placeholder="<?php esc_attr_e( 'Enter benefit', 'wordpress-seo-premium' ); ?>" }}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-benefits" only-nested=true }}
|
||||
{{list name="benefits" tag="li" allowed-tags=[ "h1","h2","h3","h4","h5","h6","br","a","p","b","strong","i","em" ] }}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Job description block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-description" title="<?php esc_attr_e( 'Job description', 'wordpress-seo-premium' ); ?>" category="yoast-required-job-blocks" description="<?php esc_attr_e( 'The description of the job.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_identification(); ?>" supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__description {{class-name}}">
|
||||
{{rich-text name="description" required=true tag="p" keepPlaceholderOnFocus=true placeholder="<?php esc_attr_e( 'Enter a job description...', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-description" only-nested=true}}
|
||||
{
|
||||
"name": {{html name="description"}}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Job employment block template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
$wpseo_employment_type_options = [
|
||||
[
|
||||
'value' => 'FULL_TIME',
|
||||
'label' => __( 'Full time', 'wordpress-seo-premium' ),
|
||||
],
|
||||
[
|
||||
'value' => 'PART_TIME',
|
||||
'label' => __( 'Part time', 'wordpress-seo-premium' ),
|
||||
],
|
||||
[
|
||||
'value' => 'CONTRACTOR',
|
||||
'label' => __( 'Contractor', 'wordpress-seo-premium' ),
|
||||
],
|
||||
[
|
||||
'value' => 'TEMPORARY',
|
||||
'label' => __( 'Temporary', 'wordpress-seo-premium' ),
|
||||
],
|
||||
];
|
||||
|
||||
// phpcs:disable Yoast.Yoast.AlternativeFunctions.json_encode_wp_json_encode -- We do not want any pretty printing, since it would break the schema blocks.
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-employment-type" title="<?php esc_attr_e( 'Employment', 'wordpress-seo-premium' ); ?>" category="yoast-recommended-job-blocks" description="<?php esc_attr_e( 'The type of employment (e.g. full-time, part-time, contract, temporary, seasonal, internship).', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_document_text(); ?>" supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__employment {{class-name}}">
|
||||
<div>
|
||||
{{rich-text name="title" tag="strong" keepPlaceholderOnFocus=true default="<?php esc_attr_e( 'Employment', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
<div>
|
||||
{{select tag="span" name="employmentType" label="<?php esc_attr_e( 'Employment', 'wordpress-seo-premium' ); ?>" options=<?php echo \wp_json_encode( $wpseo_employment_type_options ); ?> hideLabelFromVision=true }}
|
||||
{{sidebar-checkbox name="isInternship" label="<?php esc_attr_e( 'This is an internship', 'wordpress-seo-premium' ); ?>" output=", <?php esc_attr_e( 'this is an internship', 'wordpress-seo-premium' ); ?>" }}
|
||||
{{sidebar-checkbox name="isVolunteer" label="<?php esc_attr_e( 'This is a volunteer role', 'wordpress-seo-premium' ); ?>" output=", <?php esc_attr_e( 'this is a volunteer role', 'wordpress-seo-premium' ); ?>" }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-employment-type" only-nested=true}}
|
||||
{{job-employment-type }}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Job expiration block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-expiration" title="<?php esc_attr_e( 'Job expiration date', 'wordpress-seo-premium' ); ?>" category="yoast-recommended-job-blocks" description="<?php esc_attr_e( 'The date after which the job posting is not valid anymore.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_ban(); ?>" supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__closing-date {{class-name}}">
|
||||
{{rich-text name="title" tag="span" default="<?php esc_attr_e( 'Closes on', 'wordpress-seo-premium' ); ?>"}}
|
||||
{{date name="expirationDate" }}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-expiration" only-nested=true}}
|
||||
{{attribute name="expirationDate"}}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Job location address block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-location-address" title="<?php esc_attr_e( 'Address', 'wordpress-seo-premium' ); ?>" description="<?php esc_attr_e( 'The street address. For example, 111 South Grand Avenue.', 'wordpress-seo-premium' ); ?>" category="yoast-required-job-blocks" description="<?php esc_attr_e( 'The address where the office is located.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_grid(); ?>" parent=[ "yoast/office-location" ] supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__location__address {{class-name}}">
|
||||
{{rich-text required=true name="address" tag="span" keepPlaceholderOnFocus=true placeholder="<?php esc_attr_e( 'Enter street address', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-location-address" only-nested=true}}
|
||||
{{html name="address"}}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Job location city block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-location-city" title="<?php esc_attr_e( 'City', 'wordpress-seo-premium' ); ?>" description="<?php esc_attr_e( 'The city in which the street address is, and which is in the region. For example, Los Angeles.', 'wordpress-seo-premium' ); ?>" category="yoast-required-job-blocks" icon="<?php echo Icons::heroicons_grid(); ?>" parent=[ "yoast/office-location" ] supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__location__city {{class-name}}">
|
||||
{{rich-text required=true name="city" tag="span" keepPlaceholderOnFocus=true placeholder="<?php esc_attr_e( 'Enter city', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-location-city" only-nested=true}}
|
||||
{{html name="city"}}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Job location Country block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contain safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-location-country" title="<?php esc_attr_e( 'Country', 'wordpress-seo-premium' ); ?>" description="<?php esc_attr_e( 'The country. For example, USA.', 'wordpress-seo-premium' ); ?>" category="yoast-required-job-blocks" icon="<?php echo Icons::heroicons_grid(); ?>" description="<?php esc_attr_e( 'The country. For example, USA.', 'wordpress-seo-premium' ); ?>" parent=[ "yoast/office-location" ] supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__location__country {{class-name}}">
|
||||
{{rich-text required=true name="country" tag="span" keepPlaceholderOnFocus=true placeholder="<?php esc_attr_e( 'Enter country', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-location-country" only-nested=true}}
|
||||
{{html name="country"}}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Job location postal code block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-location-postal-code" title="<?php esc_attr_e( 'Postal code', 'wordpress-seo-premium' ); ?>" description="<?php esc_attr_e( 'The postal code. For example, 90012.', 'wordpress-seo-premium' ); ?>" category="yoast-required-job-blocks" icon="<?php echo Icons::heroicons_grid(); ?>" parent=[ "yoast/office-location" ] supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__location__postal-code {{class-name}}">
|
||||
{{rich-text required=true name="postal-code" tag="span" keepPlaceholderOnFocus=true placeholder="<?php esc_attr_e( 'Enter postal code', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-location-postal-code" only-nested=true}}
|
||||
{{html name="postal-code"}}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Job location State/Province/Region block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-location-region" title="<?php esc_attr_e( 'State/Province/Region', 'wordpress-seo-premium' ); ?>" description="<?php esc_attr_e( 'The state, province or region in which the city is, and which is in the country. For example, California.', 'wordpress-seo-premium' ); ?>" category="yoast-required-job-blocks" icon="<?php echo Icons::heroicons_grid(); ?>" description="<?php esc_attr_e( 'The state, province or region in which the city is, and which is in the country. For example, California.', 'wordpress-seo-premium' ); ?>" parent=[ "yoast/office-location" ] supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__location__region {{class-name}}">
|
||||
{{rich-text required=true name="region" tag="span" keepPlaceholderOnFocus=true placeholder="<?php esc_attr_e( 'Enter state/province/region', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-location-region" only-nested=true}}
|
||||
{{html name="region"}}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* Job location block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-location" title="<?php esc_attr_e( 'Location', 'wordpress-seo-premium' ); ?>" category="yoast-required-job-blocks" description="<?php esc_attr_e( 'The (typically single) geographic location associated with the job.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_location_marker(); ?>" supports={"multiple": false} }}
|
||||
<div class={{class-name}}>
|
||||
{{variation name="office-location" title="<?php esc_attr_e( 'Office location', 'wordpress-seo-premium' ); ?>" description="<?php esc_attr_e( 'Address where the office is located', 'wordpress-seo-premium' ); ?>" scope=[ "block" ] icon="<?php echo Icons::heroicons_office_building( Icons::SIZE_VARIATION ); ?>" innerBlocks=[ { "name": "yoast/office-location", "attributes": { "address-title": "<?php esc_attr_e( 'Address', 'wordpress-seo-premium' ); ?>", "postal-code-title": "<?php esc_attr_e( 'Postal code', 'wordpress-seo-premium' ); ?>", "city-title": "<?php esc_attr_e( 'City', 'wordpress-seo-premium' ); ?>", "region-title": "<?php esc_attr_e( 'Region', 'wordpress-seo-premium' ); ?>", "country-title": "<?php esc_attr_e( 'Country', 'wordpress-seo-premium' ); ?>" } }] }}
|
||||
{{variation name="remote-location" title="<?php esc_attr_e( 'Remote job', 'wordpress-seo-premium' ); ?>" description="<?php esc_attr_e( 'This job is 100% remote', 'wordpress-seo-premium' ); ?>" scope=[ "block" ] icon="<?php echo Icons::heroicons_globe( Icons::SIZE_VARIATION ); ?>" innerBlocks=[ { "name": "yoast/remote-location" } ]}}
|
||||
{{inner-blocks appender=false}}
|
||||
{{variation-picker required=true}}
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Job office location block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
$yoast_seo_block_template = [
|
||||
[ 'yoast/job-location-address' ],
|
||||
[ 'yoast/job-location-postal-code' ],
|
||||
[ 'yoast/job-location-city' ],
|
||||
[ 'yoast/job-location-region' ],
|
||||
[ 'yoast/job-location-country' ],
|
||||
];
|
||||
|
||||
$yoast_seo_required_blocks = [
|
||||
[ 'name' => 'yoast/job-location-address' ],
|
||||
[ 'name' => 'yoast/job-location-postal-code' ],
|
||||
[ 'name' => 'yoast/job-location-city' ],
|
||||
[ 'name' => 'yoast/job-location-region' ],
|
||||
[ 'name' => 'yoast/job-location-country' ],
|
||||
];
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/office-location" title="<?php esc_attr_e( 'Office location', 'wordpress-seo-premium' ); ?>" category="common" description="<?php esc_attr_e( 'The address where the office is located.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_office_building(); ?>" parent=[ "yoast/job-location" ] supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__location {{class-name}}">
|
||||
{{heading name="<?php esc_attr_e( 'Location', 'wordpress-seo-premium' ); ?>" value="<?php esc_attr_e( 'Location', 'wordpress-seo-premium' ); ?>" defaultHeadingLevel=3 }}
|
||||
{{inner-blocks template=<?php echo WPSEO_Utils::format_json_encode( $yoast_seo_block_template ); ?> appender="button" appenderLabel="<?php esc_attr_e( 'Add additional information to your location details...', 'wordpress-seo-premium' ); ?>" }}
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/office-location" only-nested=true }}
|
||||
{
|
||||
"@type": "Place",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"streetAddress": {{inner-blocks allowed-blocks=[ "yoast/job-location-address" ] only-first=true }},
|
||||
"addressLocality": {{inner-blocks allowed-blocks=[ "yoast/job-location-city" ] only-first=true }},
|
||||
"addressRegion": {{inner-blocks allowed-blocks=[ "yoast/job-location-region" ] only-first=true }},
|
||||
"postalCode": {{inner-blocks allowed-blocks=[ "yoast/job-location-postal-code" ] only-first=true }},
|
||||
"addressCountry": {{inner-blocks allowed-blocks=[ "yoast/job-location-country" ] only-first=true }}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-posting" }}
|
||||
{
|
||||
"@type": "JobPosting",
|
||||
"title": "{{attribute name="job-title" }}",
|
||||
"description": {{inner-blocks-html blocks={ "yoast/job-description": "description" } null-when-empty=true allowed-tags=[ "h1","h2","h3","h4","h5","h6","br","a","p","b","strong","i","em", "ul", "ol", "li" ] }},
|
||||
"datePosted": "%%post_date%%",
|
||||
"validThrough": {{inner-blocks allowed-blocks=[ "yoast/job-expiration" ] only-first=true }},
|
||||
"employmentType": {{inner-blocks allowed-blocks=[ "yoast/job-employment-type" ] only-first=true }},
|
||||
"hiringOrganization": {
|
||||
"@id": "%%organization_id%%"
|
||||
},
|
||||
"mainEntityOfPage": {
|
||||
"@id": "%%main_schema_id%%"
|
||||
},
|
||||
"jobLocation": {{inner-blocks allowed-blocks=[ "yoast/office-location" ] only-first=true }},
|
||||
"jobLocationType": {{inner-blocks allowed-blocks=[ "yoast/remote-location" ] only-first=true }},
|
||||
"experienceRequirements": {{inner-blocks allowed-blocks=[ "yoast/job-requirements" ] only-first=true }},
|
||||
"jobBenefits": {{inner-blocks allowed-blocks=[ "yoast/job-benefits" ] only-first=true }},
|
||||
"baseSalary": {{inner-blocks allowed-blocks=[ "yoast/job-base-salary", "yoast/job-salary-range" ] only-first=true }}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Job remote location block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/remote-location" title="<?php esc_attr_e( 'Remote job', 'wordpress-seo-premium' ); ?>" category="common" description="<?php esc_attr_e( 'This job is 100% remote.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_globe(); ?>" parent=[ "yoast/job-location" ] supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__location {{class-name}}">
|
||||
{{heading name="<?php esc_attr_e( 'Location', 'wordpress-seo-premium' ); ?>" defaultHeadingLevel=3 value="<?php esc_attr_e( 'Location', 'wordpress-seo-premium' ); ?>"}}
|
||||
{{rich-text required=true tag="p" name="remote-location" default="<?php esc_attr_e( 'This job is 100% remote.', 'wordpress-seo-premium' ); ?>"}}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/remote-location" only-nested=true }}
|
||||
"TELECOMMUTE"
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Job requirements block schema template.
|
||||
*
|
||||
* @package Yoast\WP\SEO\Schema_Templates
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Schema_Templates\Assets\Icons;
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput -- Reason: The Icons contains safe svg.
|
||||
?>
|
||||
{{block name="yoast/job-requirements" title="<?php esc_attr_e( 'Requirements', 'wordpress-seo-premium' ); ?>" category="yoast-recommended-job-blocks" description="<?php esc_attr_e( 'The description of skills and experience needed for the position.', 'wordpress-seo-premium' ); ?>" icon="<?php echo Icons::heroicons_clipboard_list(); ?>" supports={"multiple": false} }}
|
||||
<div class="yoast-job-block__requirements {{class-name}}">
|
||||
{{heading name="title" defaultHeadingLevel=3 default="<?php esc_attr_e( 'Requirements', 'wordpress-seo-premium' ); ?>" }}
|
||||
{{rich-text name="requirements" tag="ul" multiline="li" keepPlaceholderOnFocus=true placeholder="<?php esc_attr_e( 'Enter requirement', 'wordpress-seo-premium' ); ?>" }}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php // phpcs:ignore Internal.NoCodeFound ?>
|
||||
{{schema name="yoast/job-requirements" only-nested=true }}
|
||||
{{list name="requirements" tag="li" allowed-tags=[ "h1","h2","h3","h4","h5","h6","br","a","p","b","strong","i","em" ] }}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user