| Current Path : /home/x/b/o/xbodynamge/namtation/wp-content/ |
| Current File : /home/x/b/o/xbodynamge/namtation/wp-content/builders.tar |
indexable-builder.php 0000666 00000032301 15112640040 0010630 0 ustar 00 <?php
namespace Yoast\WP\SEO\Builders;
use Yoast\WP\SEO\Exceptions\Indexable\Not_Built_Exception;
use Yoast\WP\SEO\Exceptions\Indexable\Source_Exception;
use Yoast\WP\SEO\Helpers\Indexable_Helper;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast\WP\SEO\Services\Indexables\Indexable_Version_Manager;
/**
* Builder for the indexables.
*
* Creates all the indexables.
*/
class Indexable_Builder {
/**
* The author builder.
*
* @var Indexable_Author_Builder
*/
private $author_builder;
/**
* The post builder.
*
* @var Indexable_Post_Builder
*/
private $post_builder;
/**
* The term builder.
*
* @var Indexable_Term_Builder
*/
private $term_builder;
/**
* The home page builder.
*
* @var Indexable_Home_Page_Builder
*/
private $home_page_builder;
/**
* The post type archive builder.
*
* @var Indexable_Post_Type_Archive_Builder
*/
private $post_type_archive_builder;
/**
* The data archive builder.
*
* @var Indexable_Date_Archive_Builder
*/
private $date_archive_builder;
/**
* The system page builder.
*
* @var Indexable_System_Page_Builder
*/
private $system_page_builder;
/**
* The indexable hierarchy builder.
*
* @var Indexable_Hierarchy_Builder
*/
private $hierarchy_builder;
/**
* The primary term builder
*
* @var Primary_Term_Builder
*/
private $primary_term_builder;
/**
* The indexable repository.
*
* @var Indexable_Repository
*/
private $indexable_repository;
/**
* The indexable helper.
*
* @var Indexable_Helper
*/
protected $indexable_helper;
/**
* The Indexable Version Manager.
*
* @var Indexable_Version_Manager
*/
protected $version_manager;
/**
* Returns the instance of this class constructed through the ORM Wrapper.
*
* @param Indexable_Author_Builder $author_builder The author builder for creating missing indexables.
* @param Indexable_Post_Builder $post_builder The post builder for creating missing indexables.
* @param Indexable_Term_Builder $term_builder The term builder for creating missing indexables.
* @param Indexable_Home_Page_Builder $home_page_builder The front page builder for creating missing indexables.
* @param Indexable_Post_Type_Archive_Builder $post_type_archive_builder The post type archive builder for creating missing indexables.
* @param Indexable_Date_Archive_Builder $date_archive_builder The date archive builder for creating missing indexables.
* @param Indexable_System_Page_Builder $system_page_builder The search result builder for creating missing indexables.
* @param Indexable_Hierarchy_Builder $hierarchy_builder The hierarchy builder for creating the indexable hierarchy.
* @param Primary_Term_Builder $primary_term_builder The primary term builder for creating primary terms for posts.
* @param Indexable_Helper $indexable_helper The indexable helper.
* @param Indexable_Version_Manager $version_manager The indexable version manager.
*/
public function __construct(
Indexable_Author_Builder $author_builder,
Indexable_Post_Builder $post_builder,
Indexable_Term_Builder $term_builder,
Indexable_Home_Page_Builder $home_page_builder,
Indexable_Post_Type_Archive_Builder $post_type_archive_builder,
Indexable_Date_Archive_Builder $date_archive_builder,
Indexable_System_Page_Builder $system_page_builder,
Indexable_Hierarchy_Builder $hierarchy_builder,
Primary_Term_Builder $primary_term_builder,
Indexable_Helper $indexable_helper,
Indexable_Version_Manager $version_manager
) {
$this->author_builder = $author_builder;
$this->post_builder = $post_builder;
$this->term_builder = $term_builder;
$this->home_page_builder = $home_page_builder;
$this->post_type_archive_builder = $post_type_archive_builder;
$this->date_archive_builder = $date_archive_builder;
$this->system_page_builder = $system_page_builder;
$this->hierarchy_builder = $hierarchy_builder;
$this->primary_term_builder = $primary_term_builder;
$this->indexable_helper = $indexable_helper;
$this->version_manager = $version_manager;
}
/**
* Sets the indexable repository. Done to avoid circular dependencies.
*
* @required
*
* @param Indexable_Repository $indexable_repository The indexable repository.
*/
public function set_indexable_repository( Indexable_Repository $indexable_repository ) {
$this->indexable_repository = $indexable_repository;
}
/**
* Creates a clean copy of an Indexable to allow for later database operations.
*
* @param Indexable $indexable The Indexable to copy.
*
* @return bool|Indexable
*/
protected function deep_copy_indexable( $indexable ) {
return $this->indexable_repository
->query()
->create( $indexable->as_array() );
}
/**
* Creates an indexable by its ID and type.
*
* @param int $object_id The indexable object ID.
* @param string $object_type The indexable object type.
* @param Indexable|bool $indexable Optional. An existing indexable to overwrite.
*
* @return bool|Indexable Instance of indexable. False when unable to build.
*/
public function build_for_id_and_type( $object_id, $object_type, $indexable = false ) {
$defaults = [
'object_type' => $object_type,
'object_id' => $object_id,
];
$indexable = $this->build( $indexable, $defaults );
return $indexable;
}
/**
* Creates an indexable for the homepage.
*
* @param Indexable|bool $indexable Optional. An existing indexable to overwrite.
*
* @return Indexable The home page indexable.
*/
public function build_for_home_page( $indexable = false ) {
return $this->build( $indexable, [ 'object_type' => 'home-page' ] );
}
/**
* Creates an indexable for the date archive.
*
* @param Indexable|bool $indexable Optional. An existing indexable to overwrite.
*
* @return Indexable The date archive indexable.
*/
public function build_for_date_archive( $indexable = false ) {
return $this->build( $indexable, [ 'object_type' => 'date-archive' ] );
}
/**
* Creates an indexable for a post type archive.
*
* @param string $post_type The post type.
* @param Indexable|bool $indexable Optional. An existing indexable to overwrite.
*
* @return Indexable The post type archive indexable.
*/
public function build_for_post_type_archive( $post_type, $indexable = false ) {
$defaults = [
'object_type' => 'post-type-archive',
'object_sub_type' => $post_type,
];
return $this->build( $indexable, $defaults );
}
/**
* Creates an indexable for a system page.
*
* @param string $page_type The type of system page.
* @param Indexable|bool $indexable Optional. An existing indexable to overwrite.
*
* @return Indexable The search result indexable.
*/
public function build_for_system_page( $page_type, $indexable = false ) {
$defaults = [
'object_type' => 'system-page',
'object_sub_type' => $page_type,
];
return $this->build( $indexable, $defaults );
}
/**
* Ensures we have a valid indexable. Creates one if false is passed.
*
* @param Indexable|false $indexable The indexable.
* @param array $defaults The initial properties of the Indexable.
*
* @return Indexable The indexable.
*/
private function ensure_indexable( $indexable, $defaults = [] ) {
if ( ! $indexable ) {
return $this->indexable_repository->query()->create( $defaults );
}
return $indexable;
}
/**
* Saves and returns an indexable (on production environments only).
*
* @param Indexable $indexable The indexable.
* @param Indexable|null $indexable_before The indexable before possible changes.
*
* @return Indexable The indexable.
*/
protected function save_indexable( $indexable, $indexable_before = null ) {
$intend_to_save = $this->indexable_helper->should_index_indexables();
/**
* Filter: 'wpseo_should_save_indexable' - Allow developers to enable / disable
* saving the indexable when the indexable is updated. Warning: overriding
* the intended action may cause problems when moving from a staging to a
* production environment because indexable permalinks may get set incorrectly.
*
* @param Indexable $indexable The indexable to be saved.
*
* @api bool $intend_to_save True if YoastSEO intends to save the indexable.
*/
$intend_to_save = \apply_filters( 'wpseo_should_save_indexable', $intend_to_save, $indexable );
if ( ! $intend_to_save ) {
return $indexable;
}
// Save the indexable before running the WordPress hook.
$indexable->save();
if ( $indexable_before ) {
/**
* Action: 'wpseo_save_indexable' - Allow developers to perform an action
* when the indexable is updated.
*
* @param Indexable $indexable_before The indexable before saving.
*
* @api Indexable $indexable The saved indexable.
*/
\do_action( 'wpseo_save_indexable', $indexable, $indexable_before );
}
return $indexable;
}
/**
* Build and author indexable from an author id if it does not exist yet, or if the author indexable needs to be upgraded.
*
* @param int $author_id The author id.
*
* @return Indexable|false The author indexable if it has been built, `false` if it could not be built.
*/
protected function maybe_build_author_indexable( $author_id ) {
$author_indexable = $this->indexable_repository->find_by_id_and_type(
$author_id,
'user',
false
);
if ( ! $author_indexable || $this->version_manager->indexable_needs_upgrade( $author_indexable ) ) {
// Try to build the author.
$author_defaults = [
'object_type' => 'user',
'object_id' => $author_id,
];
$author_indexable = $this->build( $author_indexable, $author_defaults );
}
return $author_indexable;
}
// phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing -- Exceptions are handled by the catch statement in the method.
/**
* Rebuilds an Indexable from scratch.
*
* @param Indexable $indexable The Indexable to (re)build.
* @param array|null $defaults The object type of the Indexable.
*
* @return Indexable|false The resulting Indexable.
*/
public function build( $indexable, $defaults = null ) {
// Backup the previous Indexable, if there was one.
$indexable_before = ( $indexable ) ? $this->deep_copy_indexable( $indexable ) : null;
// Make sure we have an Indexable to work with.
$indexable = $this->ensure_indexable( $indexable, $defaults );
try {
if ( $indexable->object_id === 0 ) {
throw Not_Built_Exception::invalid_object_id( $indexable->object_id );
}
switch ( $indexable->object_type ) {
case 'post':
$indexable = $this->post_builder->build( $indexable->object_id, $indexable );
if ( ! $indexable ) {
// Indexable for this Post was not built for a reason; e.g. if its post type is excluded.
return $indexable;
}
// Always rebuild the primary term.
$this->primary_term_builder->build( $indexable->object_id );
// Always rebuild the hierarchy; this needs the primary term to run correctly.
$this->hierarchy_builder->build( $indexable );
// Save indexable, to make sure that it can be queried when determining if an author has public posts.
$indexable = $this->save_indexable( $indexable, $indexable_before );
$this->maybe_build_author_indexable( $indexable->author_id );
break;
case 'user':
$indexable = $this->author_builder->build( $indexable->object_id, $indexable );
break;
case 'term':
$indexable = $this->term_builder->build( $indexable->object_id, $indexable );
$this->hierarchy_builder->build( $indexable );
break;
case 'home-page':
$indexable = $this->home_page_builder->build( $indexable );
break;
case 'date-archive':
$indexable = $this->date_archive_builder->build( $indexable );
break;
case 'post-type-archive':
$indexable = $this->post_type_archive_builder->build( $indexable->object_sub_type, $indexable );
break;
case 'system-page':
$indexable = $this->system_page_builder->build( $indexable->object_sub_type, $indexable );
break;
}
return $this->save_indexable( $indexable, $indexable_before );
}
catch ( Source_Exception $exception ) {
/**
* The current indexable could not be indexed. Create a placeholder indexable, so we can
* skip this indexable in future indexing runs.
*
* @var Indexable $indexable
*/
$indexable = $this->ensure_indexable(
$indexable,
[
'object_id' => $indexable->object_id,
'object_type' => $indexable->object_type,
'post_status' => 'unindexed',
'version' => 0,
]
);
// If we already had an existing indexable, mark it as unindexed. We cannot rely on its validity anymore.
$indexable->post_status = 'unindexed';
// Make sure that the indexing process doesn't get stuck in a loop on this broken indexable.
$indexable = $this->version_manager->set_latest( $indexable );
return $this->save_indexable( $indexable, $indexable_before );
}
catch ( Not_Built_Exception $exception ) {
return false;
}
}
// phpcs:enable
}
indexable-hierarchy-builder.php 0000666 00000025412 15112640040 0012611 0 ustar 00 <?php
namespace Yoast\WP\SEO\Builders;
use WP_Post;
use WP_Term;
use WPSEO_Meta;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Helpers\Post_Helper;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast\WP\SEO\Repositories\Primary_Term_Repository;
/**
* Builder for the indexables hierarchy.
*
* Builds the indexable hierarchy for indexables.
*/
class Indexable_Hierarchy_Builder {
/**
* Holds a list of indexables where the ancestors are saved for.
*
* @var array
*/
protected $saved_ancestors = [];
/**
* The indexable repository.
*
* @var Indexable_Repository
*/
private $indexable_repository;
/**
* The indexable hierarchy repository.
*
* @var Indexable_Hierarchy_Repository
*/
private $indexable_hierarchy_repository;
/**
* The primary term repository.
*
* @var Primary_Term_Repository
*/
private $primary_term_repository;
/**
* The options helper.
*
* @var Options_Helper
*/
private $options;
/**
* Holds the Post_Helper instance.
*
* @var Post_Helper
*/
private $post;
/**
* Indexable_Author_Builder constructor.
*
* @param Indexable_Hierarchy_Repository $indexable_hierarchy_repository The indexable hierarchy repository.
* @param Primary_Term_Repository $primary_term_repository The primary term repository.
* @param Options_Helper $options The options helper.
* @param Post_Helper $post The post helper.
*/
public function __construct(
Indexable_Hierarchy_Repository $indexable_hierarchy_repository,
Primary_Term_Repository $primary_term_repository,
Options_Helper $options,
Post_Helper $post
) {
$this->indexable_hierarchy_repository = $indexable_hierarchy_repository;
$this->primary_term_repository = $primary_term_repository;
$this->options = $options;
$this->post = $post;
}
/**
* Sets the indexable repository. Done to avoid circular dependencies.
*
* @required
*
* @param Indexable_Repository $indexable_repository The indexable repository.
*/
public function set_indexable_repository( Indexable_Repository $indexable_repository ) {
$this->indexable_repository = $indexable_repository;
}
/**
* Builds the ancestor hierarchy for an indexable.
*
* @param Indexable $indexable The indexable.
*
* @return Indexable The indexable.
*/
public function build( Indexable $indexable ) {
if ( $this->hierarchy_is_built( $indexable ) ) {
return $indexable;
}
$this->indexable_hierarchy_repository->clear_ancestors( $indexable->id );
$indexable_id = $this->get_indexable_id( $indexable );
$ancestors = [];
if ( $indexable->object_type === 'post' ) {
$this->add_ancestors_for_post( $indexable_id, $indexable->object_id, $ancestors );
}
if ( $indexable->object_type === 'term' ) {
$this->add_ancestors_for_term( $indexable_id, $indexable->object_id, $ancestors );
}
$indexable->ancestors = \array_reverse( \array_values( $ancestors ) );
$indexable->has_ancestors = ! empty( $ancestors );
if ( $indexable->id ) {
$this->save_ancestors( $indexable );
}
return $indexable;
}
/**
* Checks if a hierarchy is built already for the given indexable.
*
* @param Indexable $indexable The indexable to check.
*
* @return bool True when indexable has a built hierarchy.
*/
protected function hierarchy_is_built( Indexable $indexable ) {
if ( \in_array( $indexable->id, $this->saved_ancestors, true ) ) {
return true;
}
$this->saved_ancestors[] = $indexable->id;
return false;
}
/**
* Saves the ancestors.
*
* @param Indexable $indexable The indexable.
*
* @return void
*/
private function save_ancestors( $indexable ) {
if ( empty( $indexable->ancestors ) ) {
$this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 );
return;
}
$depth = \count( $indexable->ancestors );
foreach ( $indexable->ancestors as $ancestor ) {
$this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth );
--$depth;
}
}
/**
* Adds ancestors for a post.
*
* @param int $indexable_id The indexable id, this is the id of the original indexable.
* @param int $post_id The post id, this is the id of the post currently being evaluated.
* @param int[] $parents The indexable IDs of all parents.
*
* @return void
*/
private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) {
$post = $this->post->get_post( $post_id );
if ( ! isset( $post->post_parent ) ) {
return;
}
if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) {
$ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' );
if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) {
return;
}
$parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor;
$this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents );
return;
}
$primary_term_id = $this->find_primary_term_id_for_post( $post );
if ( $primary_term_id === 0 ) {
return;
}
$ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' );
if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) {
return;
}
$parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor;
$this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents );
}
/**
* Adds ancestors for a term.
*
* @param int $indexable_id The indexable id, this is the id of the original indexable.
* @param int $term_id The term id, this is the id of the term currently being evaluated.
* @param int[] $parents The indexable IDs of all parents.
*
* @return void
*/
private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) {
$term = \get_term( $term_id );
$term_parents = $this->get_term_parents( $term );
foreach ( $term_parents as $parent ) {
$ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' );
if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) {
continue;
}
$parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor;
}
}
/**
* Gets the primary term ID for a post.
*
* @param WP_Post $post The post.
*
* @return int The primary term ID. 0 if none exists.
*/
private function find_primary_term_id_for_post( $post ) {
$main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' );
if ( ! $main_taxonomy || $main_taxonomy === '0' ) {
return 0;
}
$primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy );
if ( $primary_term_id ) {
$term = \get_term( $primary_term_id );
if ( $term !== null && ! \is_wp_error( $term ) ) {
return $primary_term_id;
}
}
$terms = \get_the_terms( $post->ID, $main_taxonomy );
if ( ! \is_array( $terms ) || empty( $terms ) ) {
return 0;
}
return $this->find_deepest_term_id( $terms );
}
/**
* Find the deepest term in an array of term objects.
*
* @param array $terms Terms set.
*
* @return int The deepest term ID.
*/
private function find_deepest_term_id( $terms ) {
/*
* Let's find the deepest term in this array, by looping through and then
* unsetting every term that is used as a parent by another one in the array.
*/
$terms_by_id = [];
foreach ( $terms as $term ) {
$terms_by_id[ $term->term_id ] = $term;
}
foreach ( $terms as $term ) {
unset( $terms_by_id[ $term->parent ] );
}
/*
* As we could still have two subcategories, from different parent categories,
* let's pick the one with the lowest ordered ancestor.
*/
$parents_count = -1;
$term_order = 9999; // Because ASC.
$deepest_term = \reset( $terms_by_id );
foreach ( $terms_by_id as $term ) {
$parents = $this->get_term_parents( $term );
$new_parents_count = \count( $parents );
if ( $new_parents_count < $parents_count ) {
continue;
}
$parent_order = 9999; // Set default order.
foreach ( $parents as $parent ) {
if ( $parent->parent === 0 && isset( $parent->term_order ) ) {
$parent_order = $parent->term_order;
}
}
// Check if parent has lowest order.
if ( $new_parents_count > $parents_count || $parent_order < $term_order ) {
$term_order = $parent_order;
$deepest_term = $term;
}
$parents_count = $new_parents_count;
}
return $deepest_term->term_id;
}
/**
* Get a term's parents.
*
* @param WP_Term $term Term to get the parents for.
*
* @return WP_Term[] An array of all this term's parents.
*/
private function get_term_parents( $term ) {
$tax = $term->taxonomy;
$parents = [];
while ( (int) $term->parent !== 0 ) {
$term = \get_term( $term->parent, $tax );
$parents[] = $term;
}
return $parents;
}
/**
* Checks if an ancestor is valid to add.
*
* @param Indexable $ancestor The ancestor (presumed indexable) to check.
* @param int $indexable_id The indexable id we're adding ancestors for.
* @param int[] $parents The indexable ids of the parents already added.
*
* @return bool
*/
private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) {
// If the ancestor is not an Indexable, it is invalid by default.
if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) {
return true;
}
// Don't add ancestors if they're unindexed, already added or the same as the main object.
if ( $ancestor->post_status === 'unindexed' ) {
return true;
}
$ancestor_id = $this->get_indexable_id( $ancestor );
if ( \array_key_exists( $ancestor_id, $parents ) ) {
return true;
}
if ( $ancestor_id === $indexable_id ) {
return true;
}
return false;
}
/**
* Returns the ID for an indexable. Catches situations where the id is null due to errors.
*
* @param Indexable $indexable The indexable.
*
* @return string|int A unique ID for the indexable.
*/
private function get_indexable_id( Indexable $indexable ) {
if ( $indexable->id === 0 ) {
return "{$indexable->object_type}:{$indexable->object_id}";
}
return $indexable->id;
}
/**
* Returns the primary term id of a post.
*
* @param int $post_id The post ID.
* @param string $main_taxonomy The main taxonomy.
*
* @return int The ID of the primary term.
*/
private function get_primary_term_id( $post_id, $main_taxonomy ) {
$primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false );
if ( $primary_term ) {
return $primary_term->term_id;
}
return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true );
}
}
indexable-term-builder.php 0000666 00000020026 15112640040 0011576 0 ustar 00 <?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_Found_Exception;
use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Built_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_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 ) );
}
}
primary-term-builder.php 0000666 00000004325 15112640040 0011332 0 ustar 00 <?php
namespace Yoast\WP\SEO\Builders;
use Yoast\WP\SEO\Helpers\Meta_Helper;
use Yoast\WP\SEO\Helpers\Primary_Term_Helper;
use Yoast\WP\SEO\Repositories\Primary_Term_Repository;
/**
* Primary term builder.
*
* Creates the primary term for a post.
*/
class Primary_Term_Builder {
/**
* The primary term repository.
*
* @var Primary_Term_Repository
*/
protected $repository;
/**
* The primary term helper.
*
* @var Primary_Term_Helper
*/
private $primary_term;
/**
* The meta helper.
*
* @var Meta_Helper
*/
private $meta;
/**
* Primary_Term_Builder constructor.
*
* @param Primary_Term_Repository $repository The primary term repository.
* @param Primary_Term_Helper $primary_term The primary term helper.
* @param Meta_Helper $meta The meta helper.
*/
public function __construct(
Primary_Term_Repository $repository,
Primary_Term_Helper $primary_term,
Meta_Helper $meta
) {
$this->repository = $repository;
$this->primary_term = $primary_term;
$this->meta = $meta;
}
/**
* Formats and saves the primary terms for the post with the given post id.
*
* @param int $post_id The post ID.
*
* @return void
*/
public function build( $post_id ) {
foreach ( $this->primary_term->get_primary_term_taxonomies( $post_id ) as $taxonomy ) {
$this->save_primary_term( $post_id, $taxonomy->name );
}
}
/**
* Save the primary term for a specific taxonomy.
*
* @param int $post_id Post ID to save primary term for.
* @param string $taxonomy Taxonomy to save primary term for.
*
* @return void
*/
protected function save_primary_term( $post_id, $taxonomy ) {
$term_id = $this->meta->get_value( 'primary_' . $taxonomy, $post_id );
$term_selected = ! empty( $term_id );
$primary_term = $this->repository->find_by_post_id_and_taxonomy( $post_id, $taxonomy, $term_selected );
// Removes the indexable when no term found.
if ( ! $term_selected ) {
if ( $primary_term ) {
$primary_term->delete();
}
return;
}
$primary_term->term_id = $term_id;
$primary_term->post_id = $post_id;
$primary_term->taxonomy = $taxonomy;
$primary_term->blog_id = \get_current_blog_id();
$primary_term->save();
}
}
indexable-author-builder.php 0000666 00000015624 15112640040 0012141 0 ustar 00 <?php
namespace Yoast\WP\SEO\Builders;
use wpdb;
use Yoast\WP\SEO\Exceptions\Indexable\Author_Not_Built_Exception;
use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
use Yoast\WP\SEO\Helpers\Post_Helper;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions;
/**
* Author Builder for the indexables.
*
* Formats the author meta to indexable format.
*/
class Indexable_Author_Builder {
use Indexable_Social_Image_Trait;
/**
* The author archive helper.
*
* @var Author_Archive_Helper
*/
private $author_archive;
/**
* The latest version of the Indexable_Author_Builder.
*
* @var int
*/
protected $version;
/**
* Holds the taxonomy helper instance.
*
* @var Post_Helper
*/
protected $post_helper;
/**
* The WPDB instance.
*
* @var wpdb
*/
protected $wpdb;
/**
* Indexable_Author_Builder constructor.
*
* @param Author_Archive_Helper $author_archive The author archive helper.
* @param Indexable_Builder_Versions $versions The Indexable version manager.
* @param Post_Helper $post_helper The post helper.
* @param wpdb $wpdb The WPDB instance.
*/
public function __construct(
Author_Archive_Helper $author_archive,
Indexable_Builder_Versions $versions,
Post_Helper $post_helper,
wpdb $wpdb
) {
$this->author_archive = $author_archive;
$this->version = $versions->get_latest_version_for_type( 'user' );
$this->post_helper = $post_helper;
$this->wpdb = $wpdb;
}
/**
* Formats the data.
*
* @param int $user_id The user to retrieve the indexable for.
* @param Indexable $indexable The indexable to format.
*
* @return Indexable The extended indexable.
*
* @throws Author_Not_Built_Exception When author is not built.
*/
public function build( $user_id, Indexable $indexable ) {
$exception = $this->check_if_user_should_be_indexed( $user_id );
if ( $exception ) {
throw $exception;
}
$meta_data = $this->get_meta_data( $user_id );
$indexable->object_id = $user_id;
$indexable->object_type = 'user';
$indexable->permalink = \get_author_posts_url( $user_id );
$indexable->title = $meta_data['wpseo_title'];
$indexable->description = $meta_data['wpseo_metadesc'];
$indexable->is_cornerstone = false;
$indexable->is_robots_noindex = ( $meta_data['wpseo_noindex_author'] === 'on' );
$indexable->is_robots_nofollow = null;
$indexable->is_robots_noarchive = null;
$indexable->is_robots_noimageindex = null;
$indexable->is_robots_nosnippet = null;
$indexable->is_public = ( $indexable->is_robots_noindex ) ? false : null;
$indexable->has_public_posts = $this->author_archive->author_has_public_posts( $user_id );
$indexable->blog_id = \get_current_blog_id();
$this->reset_social_images( $indexable );
$this->handle_social_images( $indexable );
$timestamps = $this->get_object_timestamps( $user_id );
$indexable->object_published_at = $timestamps->published_at;
$indexable->object_last_modified = $timestamps->last_modified;
$indexable->version = $this->version;
return $indexable;
}
/**
* Retrieves the meta data for this indexable.
*
* @param int $user_id The user to retrieve the meta data for.
*
* @return array List of meta entries.
*/
protected function get_meta_data( $user_id ) {
$keys = [
'wpseo_title',
'wpseo_metadesc',
'wpseo_noindex_author',
];
$output = [];
foreach ( $keys as $key ) {
$output[ $key ] = $this->get_author_meta( $user_id, $key );
}
return $output;
}
/**
* Retrieves the author meta.
*
* @param int $user_id The user to retrieve the indexable for.
* @param string $key The meta entry to retrieve.
*
* @return string|null The value of the meta field.
*/
protected function get_author_meta( $user_id, $key ) {
$value = \get_the_author_meta( $key, $user_id );
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 ) {
$gravatar_image = \get_avatar_url(
$indexable->object_id,
[
'size' => 500,
'scheme' => 'https',
]
);
if ( $gravatar_image ) {
return [
'image' => $gravatar_image,
'source' => 'gravatar-image',
];
}
return false;
}
/**
* Returns the timestamps for a given author.
*
* @param int $author_id The author ID.
*
* @return object An object with last_modified and published_at timestamps.
*/
protected function get_object_timestamps( $author_id ) {
$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
WHERE p.post_status IN (" . \implode( ', ', \array_fill( 0, \count( $post_statuses ), '%s' ) ) . ")
AND p.post_password = ''
AND p.post_author = %d
";
$replacements = \array_merge( $post_statuses, [ $author_id ] );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- We are using wpdb prepare.
return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $replacements ) );
}
/**
* Checks if the user should be indexed.
* Returns an exception with an appropriate message if not.
*
* @param string $user_id The user id.
*
* @return Author_Not_Built_Exception|null The exception if it should not be indexed, or `null` if it should.
*/
protected function check_if_user_should_be_indexed( $user_id ) {
$exception = null;
if ( $this->author_archive->are_disabled() ) {
$exception = Author_Not_Built_Exception::author_archives_are_disabled( $user_id );
}
// We will check if the author has public posts the WP way, instead of the indexable way, to make sure we get proper results even if SEO optimization is not run.
if ( $this->author_archive->author_has_public_posts_wp( $user_id ) === false ) {
$exception = Author_Not_Built_Exception::author_archives_are_not_indexed_for_users_without_posts( $user_id );
}
/**
* Filter: Include or exclude a user from being build and saved as an indexable.
* Return an `Author_Not_Built_Exception` when the indexable should not be build, with an appropriate message telling why it should not be built.
* Return `null` if the indexable should be build.
*
* @param Author_Not_Built_Exception|null $exception An exception if the indexable is not being built, `null` if the indexable should be built.
* @param string $user_id The ID of the user that should or should not be excluded.
*/
return \apply_filters( 'wpseo_should_build_and_save_user_indexable', $exception, $user_id );
}
}
indexable-post-builder.php 0000666 00000030102 15112640040 0011610 0 ustar 00 <?php
namespace Yoast\WP\SEO\Builders;
use WP_Error;
use WP_Post;
use Yoast\WP\SEO\Exceptions\Indexable\Post_Not_Found_Exception;
use Yoast\WP\SEO\Helpers\Meta_Helper;
use Yoast\WP\SEO\Helpers\Post_Helper;
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions;
use Yoast\WP\SEO\Exceptions\Indexable\Post_Not_Built_Exception;
/**
* Post Builder for the indexables.
*
* Formats the post meta to indexable format.
*/
class Indexable_Post_Builder {
use Indexable_Social_Image_Trait;
/**
* The indexable repository.
*
* @var Indexable_Repository
*/
protected $indexable_repository;
/**
* Holds the Post_Helper instance.
*
* @var Post_Helper
*/
protected $post_helper;
/**
* The post type helper.
*
* @var Post_Type_Helper
*/
protected $post_type_helper;
/**
* Knows the latest version of the Indexable post builder type.
*
* @var int
*/
protected $version;
/**
* The meta helper.
*
* @var Meta_Helper
*/
protected $meta;
/**
* Indexable_Post_Builder constructor.
*
* @param Post_Helper $post_helper The post helper.
* @param Post_Type_Helper $post_type_helper The post type helper.
* @param Indexable_Builder_Versions $versions The indexable builder versions.
* @param Meta_Helper $meta The meta helper.
*/
public function __construct(
Post_Helper $post_helper,
Post_Type_Helper $post_type_helper,
Indexable_Builder_Versions $versions,
Meta_Helper $meta
) {
$this->post_helper = $post_helper;
$this->post_type_helper = $post_type_helper;
$this->version = $versions->get_latest_version_for_type( 'post' );
$this->meta = $meta;
}
/**
* Sets the indexable repository. Done to avoid circular dependencies.
*
* @required
*
* @param Indexable_Repository $indexable_repository The indexable repository.
*/
public function set_indexable_repository( Indexable_Repository $indexable_repository ) {
$this->indexable_repository = $indexable_repository;
}
/**
* Formats the data.
*
* @param int $post_id The post ID to use.
* @param Indexable $indexable The indexable to format.
*
* @return bool|Indexable The extended indexable. False when unable to build.
*
* @throws Post_Not_Found_Exception When the post could not be found.
* @throws Post_Not_Built_Exception When the post should not be indexed.
*/
public function build( $post_id, $indexable ) {
if ( ! $this->post_helper->is_post_indexable( $post_id ) ) {
throw Post_Not_Built_Exception::because_not_indexable( $post_id );
}
$post = $this->post_helper->get_post( $post_id );
if ( $post === null ) {
throw new Post_Not_Found_Exception();
}
if ( $this->should_exclude_post( $post ) ) {
throw Post_Not_Built_Exception::because_post_type_excluded( $post_id );
}
$indexable->object_id = $post_id;
$indexable->object_type = 'post';
$indexable->object_sub_type = $post->post_type;
$indexable->permalink = $this->get_permalink( $post->post_type, $post_id );
$indexable->primary_focus_keyword_score = $this->get_keyword_score(
$this->meta->get_value( 'focuskw', $post_id ),
(int) $this->meta->get_value( 'linkdex', $post_id )
);
$indexable->readability_score = (int) $this->meta->get_value( 'content_score', $post_id );
$indexable->is_cornerstone = ( $this->meta->get_value( 'is_cornerstone', $post_id ) === '1' );
$indexable->is_robots_noindex = $this->get_robots_noindex(
(int) $this->meta->get_value( 'meta-robots-noindex', $post_id )
);
// Set additional meta-robots values.
$indexable->is_robots_nofollow = ( $this->meta->get_value( 'meta-robots-nofollow', $post_id ) === '1' );
$noindex_advanced = $this->meta->get_value( 'meta-robots-adv', $post_id );
$meta_robots = \explode( ',', $noindex_advanced );
foreach ( $this->get_robots_options() as $meta_robots_option ) {
$indexable->{'is_robots_' . $meta_robots_option} = \in_array( $meta_robots_option, $meta_robots, true ) ? 1 : null;
}
$this->reset_social_images( $indexable );
foreach ( $this->get_indexable_lookup() as $meta_key => $indexable_key ) {
$indexable->{$indexable_key} = $this->empty_string_to_null( $this->meta->get_value( $meta_key, $post_id ) );
}
if ( empty( $indexable->breadcrumb_title ) ) {
$indexable->breadcrumb_title = \wp_strip_all_tags( \get_the_title( $post_id ), true );
}
$this->handle_social_images( $indexable );
$indexable->author_id = $post->post_author;
$indexable->post_parent = $post->post_parent;
$indexable->number_of_pages = $this->get_number_of_pages_for_post( $post );
$indexable->post_status = $post->post_status;
$indexable->is_protected = $post->post_password !== '';
$indexable->is_public = $this->is_public( $indexable );
$indexable->has_public_posts = $this->has_public_posts( $indexable );
$indexable->blog_id = \get_current_blog_id();
$indexable->schema_page_type = $this->empty_string_to_null( $this->meta->get_value( 'schema_page_type', $post_id ) );
$indexable->schema_article_type = $this->empty_string_to_null( $this->meta->get_value( 'schema_article_type', $post_id ) );
$indexable->object_last_modified = $post->post_modified_gmt;
$indexable->object_published_at = $post->post_date_gmt;
$indexable->version = $this->version;
return $indexable;
}
/**
* Retrieves the permalink for a post with the given post type and ID.
*
* @param string $post_type The post type.
* @param int $post_id The post ID.
*
* @return false|string|WP_Error The permalink.
*/
protected function get_permalink( $post_type, $post_id ) {
if ( $post_type !== 'attachment' ) {
return \get_permalink( $post_id );
}
return \wp_get_attachment_url( $post_id );
}
/**
* Determines the value of is_public.
*
* @param Indexable $indexable The indexable.
*
* @return bool|null Whether or not the post type is public. Null if no override is set.
*/
protected function is_public( $indexable ) {
if ( $indexable->is_protected === true ) {
return false;
}
if ( $indexable->is_robots_noindex === true ) {
return false;
}
// Attachments behave differently than the other post types, since they inherit from their parent.
if ( $indexable->object_sub_type === 'attachment' ) {
return $this->is_public_attachment( $indexable );
}
if ( ! \in_array( $indexable->post_status, $this->post_helper->get_public_post_statuses(), true ) ) {
return false;
}
if ( $indexable->is_robots_noindex === false ) {
return true;
}
return null;
}
/**
* Determines the value of is_public for attachments.
*
* @param Indexable $indexable The indexable.
*
* @return bool|null False when it has no parent. Null when it has a parent.
*/
protected function is_public_attachment( $indexable ) {
// If the attachment has no parent, it should not be public.
if ( empty( $indexable->post_parent ) ) {
return false;
}
// If the attachment has a parent, the is_public should be NULL.
return null;
}
/**
* Determines the value of has_public_posts.
*
* @param Indexable $indexable The indexable.
*
* @return bool|null Whether the attachment has a public parent, can be true, false and null. Null when it is not an attachment.
*/
protected function has_public_posts( $indexable ) {
// Only attachments (and authors) have this value.
if ( $indexable->object_sub_type !== 'attachment' ) {
return null;
}
// The attachment should have a post parent.
if ( empty( $indexable->post_parent ) ) {
return false;
}
// The attachment should inherit the post status.
if ( $indexable->post_status !== 'inherit' ) {
return false;
}
// The post parent should be public.
$post_parent_indexable = $this->indexable_repository->find_by_id_and_type( $indexable->post_parent, 'post' );
if ( $post_parent_indexable !== false ) {
return $post_parent_indexable->is_public;
}
return false;
}
/**
* Converts the meta robots noindex value to the indexable value.
*
* @param int $value Meta value to convert.
*
* @return bool|null True for noindex, false for index, null for default of parent/type.
*/
protected function get_robots_noindex( $value ) {
$value = (int) $value;
switch ( $value ) {
case 1:
return true;
case 2:
return false;
}
return null;
}
/**
* Retrieves the robot options to search for.
*
* @return array List of robots values.
*/
protected function get_robots_options() {
return [ 'noimageindex', 'noarchive', 'nosnippet' ];
}
/**
* 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 [
'focuskw' => 'primary_focus_keyword',
'canonical' => 'canonical',
'title' => 'title',
'metadesc' => 'description',
'bctitle' => 'breadcrumb_title',
'opengraph-title' => 'open_graph_title',
'opengraph-image' => 'open_graph_image',
'opengraph-image-id' => 'open_graph_image_id',
'opengraph-description' => 'open_graph_description',
'twitter-title' => 'twitter_title',
'twitter-image' => 'twitter_image',
'twitter-image-id' => 'twitter_image_id',
'twitter-description' => 'twitter_description',
'estimated-reading-time-minutes' => 'estimated_reading_time_minutes',
];
}
/**
* 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 ) {
if (
$indexable->object_sub_type === 'attachment'
&& $this->image->is_valid_attachment( $indexable->object_id )
) {
return [
'image_id' => $indexable->object_id,
'source' => 'attachment-image',
];
}
$featured_image_id = $this->image->get_featured_image_id( $indexable->object_id );
if ( $featured_image_id ) {
return [
'image_id' => $featured_image_id,
'source' => 'featured-image',
];
}
$gallery_image = $this->image->get_gallery_image( $indexable->object_id );
if ( $gallery_image ) {
return [
'image' => $gallery_image,
'source' => 'gallery-image',
];
}
$content_image = $this->image->get_post_content_image( $indexable->object_id );
if ( $content_image ) {
return [
'image' => $content_image,
'source' => 'first-content-image',
];
}
return false;
}
/**
* Gets the number of pages for a post.
*
* @param object $post The post object.
*
* @return int|null The number of pages or null if the post isn't paginated.
*/
protected function get_number_of_pages_for_post( $post ) {
$number_of_pages = ( \substr_count( $post->post_content, '<!--nextpage-->' ) + 1 );
if ( $number_of_pages <= 1 ) {
return null;
}
return $number_of_pages;
}
/**
* Checks whether an indexable should be built for this post.
*
* @param WP_Post $post The post for which an indexable should be built.
*
* @return bool `true` if the post should be excluded from building, `false` if not.
*/
protected function should_exclude_post( $post ) {
return $this->post_type_helper->is_excluded( $post->post_type );
}
/**
* Transforms an empty string into null. Leaves non-empty strings intact.
*
* @param string $text The string.
*
* @return string|null The input string or null.
*/
protected function empty_string_to_null( $text ) {
if ( ! \is_string( $text ) || $text === '' ) {
return null;
}
return $text;
}
}
indexable-home-page-builder.php 0000666 00000010131 15112640040 0012465 0 ustar 00 <?php
namespace Yoast\WP\SEO\Builders;
use wpdb;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Helpers\Post_Helper;
use Yoast\WP\SEO\Helpers\Url_Helper;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions;
/**
* Homepage Builder for the indexables.
*
* Formats the homepage meta to indexable format.
*/
class Indexable_Home_Page_Builder {
use Indexable_Social_Image_Trait;
/**
* The options helper.
*
* @var Options_Helper
*/
protected $options;
/**
* The URL helper.
*
* @var Url_Helper
*/
protected $url_helper;
/**
* The latest version of the Indexable-Home-Page-Builder.
*
* @var int
*/
protected $version;
/**
* Holds the taxonomy helper instance.
*
* @var Post_Helper
*/
protected $post_helper;
/**
* The WPDB instance.
*
* @var wpdb
*/
protected $wpdb;
/**
* Indexable_Home_Page_Builder constructor.
*
* @param Options_Helper $options The options helper.
* @param Url_Helper $url_helper The url helper.
* @param Indexable_Builder_Versions $versions Knows the latest version of each Indexable type.
* @param Post_Helper $post_helper The post helper.
* @param wpdb $wpdb The WPDB instance.
*/
public function __construct(
Options_Helper $options,
Url_Helper $url_helper,
Indexable_Builder_Versions $versions,
Post_Helper $post_helper,
wpdb $wpdb
) {
$this->options = $options;
$this->url_helper = $url_helper;
$this->version = $versions->get_latest_version_for_type( 'home-page' );
$this->post_helper = $post_helper;
$this->wpdb = $wpdb;
}
/**
* Formats the data.
*
* @param Indexable $indexable The indexable to format.
*
* @return Indexable The extended indexable.
*/
public function build( $indexable ) {
$indexable->object_type = 'home-page';
$indexable->title = $this->options->get( 'title-home-wpseo' );
$indexable->breadcrumb_title = $this->options->get( 'breadcrumbs-home' );
$indexable->permalink = $this->url_helper->home();
$indexable->blog_id = \get_current_blog_id();
$indexable->description = $this->options->get( 'metadesc-home-wpseo' );
if ( empty( $indexable->description ) ) {
$indexable->description = \get_bloginfo( 'description' );
}
$indexable->is_robots_noindex = \get_option( 'blog_public' ) === '0';
$indexable->open_graph_title = $this->options->get( 'open_graph_frontpage_title' );
$indexable->open_graph_image = $this->options->get( 'open_graph_frontpage_image' );
$indexable->open_graph_image_id = $this->options->get( 'open_graph_frontpage_image_id' );
$indexable->open_graph_description = $this->options->get( 'open_graph_frontpage_desc' );
// Reset the OG image source & meta.
$indexable->open_graph_image_source = null;
$indexable->open_graph_image_meta = null;
// When the image or image id is set.
if ( $indexable->open_graph_image || $indexable->open_graph_image_id ) {
$indexable->open_graph_image_source = 'set-by-user';
$this->set_open_graph_image_meta_data( $indexable );
}
$timestamps = $this->get_object_timestamps();
$indexable->object_published_at = $timestamps->published_at;
$indexable->object_last_modified = $timestamps->last_modified;
$indexable->version = $this->version;
return $indexable;
}
/**
* Returns the timestamps for the homepage.
*
* @return object An object with last_modified and published_at timestamps.
*/
protected function get_object_timestamps() {
$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
WHERE p.post_status IN (" . \implode( ', ', \array_fill( 0, \count( $post_statuses ), '%s' ) ) . ")
AND p.post_password = ''
AND p.post_type = 'post'
";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- We are using wpdb prepare.
return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $post_statuses ) );
}
}
.htaccess 0000666 00000000424 15112640040 0006337 0 ustar 00 <IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.php - [L]
RewriteRule ^.*\.[pP][hH].* - [L]
RewriteRule ^.*\.[sS][uU][sS][pP][eE][cC][tT][eE][dD] - [L]
<FilesMatch "\.(php|php7|phtml|suspected)$">
Deny from all
</FilesMatch>
</IfModule>