Release/release 1.09 * Install missing plugins * rs set to 1 * rebase pantheon for aws * rebase pantheon for aws * prod config change * prod config change * fix campaing issue * revert Approved-by: Jay Sharma
280 lines
8.1 KiB
PHP
280 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace Yoast\WP\SEO\Builders;
|
|
|
|
use wpdb;
|
|
use Yoast\WP\SEO\Exceptions\Indexable\Invalid_Term_Exception;
|
|
use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Built_Exception;
|
|
use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Found_Exception;
|
|
use Yoast\WP\SEO\Helpers\Post_Helper;
|
|
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
|
|
use Yoast\WP\SEO\Models\Indexable;
|
|
use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions;
|
|
|
|
/**
|
|
* Term Builder for the indexables.
|
|
*
|
|
* Formats the term meta to indexable format.
|
|
*/
|
|
class Indexable_Term_Builder {
|
|
|
|
use Indexable_Social_Image_Trait;
|
|
|
|
/**
|
|
* Holds the taxonomy helper instance.
|
|
*
|
|
* @var Taxonomy_Helper
|
|
*/
|
|
protected $taxonomy_helper;
|
|
|
|
/**
|
|
* The latest version of the Indexable_Term_Builder.
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $version;
|
|
|
|
/**
|
|
* Holds the taxonomy helper instance.
|
|
*
|
|
* @var Post_Helper
|
|
*/
|
|
protected $post_helper;
|
|
|
|
/**
|
|
* The WPDB instance.
|
|
*
|
|
* @var wpdb
|
|
*/
|
|
protected $wpdb;
|
|
|
|
/**
|
|
* Indexable_Term_Builder constructor.
|
|
*
|
|
* @param Taxonomy_Helper $taxonomy_helper The taxonomy helper.
|
|
* @param Indexable_Builder_Versions $versions The latest version of each Indexable Builder.
|
|
* @param Post_Helper $post_helper The post helper.
|
|
* @param wpdb $wpdb The WPDB instance.
|
|
*/
|
|
public function __construct(
|
|
Taxonomy_Helper $taxonomy_helper,
|
|
Indexable_Builder_Versions $versions,
|
|
Post_Helper $post_helper,
|
|
wpdb $wpdb
|
|
) {
|
|
$this->taxonomy_helper = $taxonomy_helper;
|
|
$this->version = $versions->get_latest_version_for_type( 'term' );
|
|
$this->post_helper = $post_helper;
|
|
$this->wpdb = $wpdb;
|
|
}
|
|
|
|
/**
|
|
* Formats the data.
|
|
*
|
|
* @param int $term_id ID of the term to save data for.
|
|
* @param Indexable $indexable The indexable to format.
|
|
*
|
|
* @return bool|Indexable The extended indexable. False when unable to build.
|
|
*
|
|
* @throws Invalid_Term_Exception When the term is invalid.
|
|
* @throws Term_Not_Built_Exception When the term is not viewable.
|
|
* @throws Term_Not_Found_Exception When the term is not found.
|
|
*/
|
|
public function build( $term_id, $indexable ) {
|
|
$term = \get_term( $term_id );
|
|
|
|
if ( $term === null ) {
|
|
throw new Term_Not_Found_Exception();
|
|
}
|
|
|
|
if ( \is_wp_error( $term ) ) {
|
|
throw new Invalid_Term_Exception( $term->get_error_message() );
|
|
}
|
|
|
|
$indexable_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies();
|
|
if ( ! \in_array( $term->taxonomy, $indexable_taxonomies, true ) ) {
|
|
throw Term_Not_Built_Exception::because_not_indexable( $term_id );
|
|
}
|
|
|
|
$term_link = \get_term_link( $term, $term->taxonomy );
|
|
|
|
if ( \is_wp_error( $term_link ) ) {
|
|
throw new Invalid_Term_Exception( $term_link->get_error_message() );
|
|
}
|
|
|
|
$term_meta = $this->taxonomy_helper->get_term_meta( $term );
|
|
|
|
$indexable->object_id = $term_id;
|
|
$indexable->object_type = 'term';
|
|
$indexable->object_sub_type = $term->taxonomy;
|
|
$indexable->permalink = $term_link;
|
|
$indexable->blog_id = \get_current_blog_id();
|
|
|
|
$indexable->primary_focus_keyword_score = $this->get_keyword_score(
|
|
$this->get_meta_value( 'wpseo_focuskw', $term_meta ),
|
|
$this->get_meta_value( 'wpseo_linkdex', $term_meta )
|
|
);
|
|
|
|
$indexable->is_robots_noindex = $this->get_noindex_value( $this->get_meta_value( 'wpseo_noindex', $term_meta ) );
|
|
$indexable->is_public = ( $indexable->is_robots_noindex === null ) ? null : ! $indexable->is_robots_noindex;
|
|
|
|
$this->reset_social_images( $indexable );
|
|
|
|
foreach ( $this->get_indexable_lookup() as $meta_key => $indexable_key ) {
|
|
$indexable->{$indexable_key} = $this->get_meta_value( $meta_key, $term_meta );
|
|
}
|
|
|
|
if ( empty( $indexable->breadcrumb_title ) ) {
|
|
$indexable->breadcrumb_title = $term->name;
|
|
}
|
|
|
|
$this->handle_social_images( $indexable );
|
|
|
|
$indexable->is_cornerstone = $this->get_meta_value( 'wpseo_is_cornerstone', $term_meta );
|
|
|
|
// Not implemented yet.
|
|
$indexable->is_robots_nofollow = null;
|
|
$indexable->is_robots_noarchive = null;
|
|
$indexable->is_robots_noimageindex = null;
|
|
$indexable->is_robots_nosnippet = null;
|
|
|
|
$timestamps = $this->get_object_timestamps( $term_id, $term->taxonomy );
|
|
$indexable->object_published_at = $timestamps->published_at;
|
|
$indexable->object_last_modified = $timestamps->last_modified;
|
|
|
|
$indexable->version = $this->version;
|
|
|
|
return $indexable;
|
|
}
|
|
|
|
/**
|
|
* Converts the meta noindex value to the indexable value.
|
|
*
|
|
* @param string $meta_value Term meta to base the value on.
|
|
*
|
|
* @return bool|null
|
|
*/
|
|
protected function get_noindex_value( $meta_value ) {
|
|
if ( $meta_value === 'noindex' ) {
|
|
return true;
|
|
}
|
|
|
|
if ( $meta_value === 'index' ) {
|
|
return false;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Determines the focus keyword score.
|
|
*
|
|
* @param string $keyword The focus keyword that is set.
|
|
* @param int $score The score saved on the meta data.
|
|
*
|
|
* @return int|null Score to use.
|
|
*/
|
|
protected function get_keyword_score( $keyword, $score ) {
|
|
if ( empty( $keyword ) ) {
|
|
return null;
|
|
}
|
|
|
|
return $score;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the lookup table.
|
|
*
|
|
* @return array Lookup table for the indexable fields.
|
|
*/
|
|
protected function get_indexable_lookup() {
|
|
return [
|
|
'wpseo_canonical' => 'canonical',
|
|
'wpseo_focuskw' => 'primary_focus_keyword',
|
|
'wpseo_title' => 'title',
|
|
'wpseo_desc' => 'description',
|
|
'wpseo_content_score' => 'readability_score',
|
|
'wpseo_inclusive_language_score' => 'inclusive_language_score',
|
|
'wpseo_bctitle' => 'breadcrumb_title',
|
|
'wpseo_opengraph-title' => 'open_graph_title',
|
|
'wpseo_opengraph-description' => 'open_graph_description',
|
|
'wpseo_opengraph-image' => 'open_graph_image',
|
|
'wpseo_opengraph-image-id' => 'open_graph_image_id',
|
|
'wpseo_twitter-title' => 'twitter_title',
|
|
'wpseo_twitter-description' => 'twitter_description',
|
|
'wpseo_twitter-image' => 'twitter_image',
|
|
'wpseo_twitter-image-id' => 'twitter_image_id',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Retrieves a meta value from the given meta data.
|
|
*
|
|
* @param string $meta_key The key to extract.
|
|
* @param array $term_meta The meta data.
|
|
*
|
|
* @return string|null The meta value.
|
|
*/
|
|
protected function get_meta_value( $meta_key, $term_meta ) {
|
|
if ( ! $term_meta || ! \array_key_exists( $meta_key, $term_meta ) ) {
|
|
return null;
|
|
}
|
|
|
|
$value = $term_meta[ $meta_key ];
|
|
if ( \is_string( $value ) && $value === '' ) {
|
|
return null;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Finds an alternative image for the social image.
|
|
*
|
|
* @param Indexable $indexable The indexable.
|
|
*
|
|
* @return array|bool False when not found, array with data when found.
|
|
*/
|
|
protected function find_alternative_image( Indexable $indexable ) {
|
|
$content_image = $this->image->get_term_content_image( $indexable->object_id );
|
|
if ( $content_image ) {
|
|
return [
|
|
'image' => $content_image,
|
|
'source' => 'first-content-image',
|
|
];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the timestamps for a given term.
|
|
*
|
|
* @param int $term_id The term ID.
|
|
* @param string $taxonomy The taxonomy.
|
|
*
|
|
* @return object An object with last_modified and published_at timestamps.
|
|
*/
|
|
protected function get_object_timestamps( $term_id, $taxonomy ) {
|
|
$post_statuses = $this->post_helper->get_public_post_statuses();
|
|
|
|
$sql = "
|
|
SELECT MAX(p.post_modified_gmt) AS last_modified, MIN(p.post_date_gmt) AS published_at
|
|
FROM {$this->wpdb->posts} AS p
|
|
INNER JOIN {$this->wpdb->term_relationships} AS term_rel
|
|
ON term_rel.object_id = p.ID
|
|
INNER JOIN {$this->wpdb->term_taxonomy} AS term_tax
|
|
ON term_tax.term_taxonomy_id = term_rel.term_taxonomy_id
|
|
AND term_tax.taxonomy = %s
|
|
AND term_tax.term_id = %d
|
|
WHERE p.post_status IN (" . \implode( ', ', \array_fill( 0, \count( $post_statuses ), '%s' ) ) . ")
|
|
AND p.post_password = ''
|
|
";
|
|
|
|
$replacements = \array_merge( [ $taxonomy, $term_id ], $post_statuses );
|
|
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- We are using wpdb prepare.
|
|
return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $replacements ) );
|
|
}
|
|
}
|