This commit is contained in:
KhaiNguyen
2020-02-13 10:39:37 +07:00
commit 59401cb805
12867 changed files with 4646216 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
<?php
/**
* Post Builder for the indexables.
*
* @package Yoast\YoastSEO\Builders
*/
namespace Yoast\WP\SEO\Builders;
use Yoast\WP\SEO\Models\Indexable;
/**
* Formats the term meta to indexable format.
*/
class Indexable_Author_Builder {
/**
* Formats the data.
*
* @param int $user_id The user to retrieve the indexable for.
* @param \Yoast\WP\SEO\Models\Indexable $indexable The indexable to format.
*
* @return \Yoast\WP\SEO\Models\Indexable The extended indexable.
*/
public function build( $user_id, Indexable $indexable ) {
$meta_data = $this->get_meta_data( $user_id );
$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;
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 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;
}
}

View File

@@ -0,0 +1,180 @@
<?php
/**
* Post Builder for the indexables.
*
* @package Yoast\YoastSEO\Builders
*/
namespace Yoast\WP\SEO\Builders;
use Exception;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Repositories\SEO_Meta_Repository;
/**
* Formats the post meta to indexable format.
*/
class Indexable_Post_Builder {
/**
* @var \Yoast\WP\SEO\Repositories\SEO_Meta_Repository
*/
protected $seo_meta_repository;
/**
* Indexable_Post_Builder constructor.
*
* @param \Yoast\WP\SEO\Repositories\SEO_Meta_Repository $seo_meta_repository The SEO Meta repository.
*/
public function __construct( SEO_Meta_Repository $seo_meta_repository ) {
$this->seo_meta_repository = $seo_meta_repository;
}
/**
* Formats the data.
*
* @param int $post_id The post ID to use.
* @param \Yoast\WP\SEO\Models\Indexable $indexable The indexable to format.
*
* @return \Yoast\WP\SEO\Models\Indexable The extended indexable.
*/
public function build( $post_id, Indexable $indexable ) {
$indexable->permalink = \get_permalink( $post_id );
$indexable->object_sub_type = \get_post_type( $post_id );
$indexable->primary_focus_keyword_score = $this->get_keyword_score(
$this->get_meta_value( $post_id, 'focuskw' ),
(int) $this->get_meta_value( $post_id, 'linkdex' )
);
$indexable->readability_score = (int) $this->get_meta_value( $post_id, 'content_score' );
$indexable->is_cornerstone = ( $this->get_meta_value( $post_id, 'is_cornerstone' ) === '1' );
$indexable->is_robots_noindex = $this->get_robots_noindex(
$this->get_meta_value( $post_id, 'meta-robots-noindex' )
);
// Set additional meta-robots values.
$indexable->is_robots_nofollow = ( $this->get_meta_value( $post_id, 'meta-robots-nofollow' ) === '1' );
$noindex_advanced = $this->get_meta_value( $post_id, 'meta-robots-adv' );
$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;
}
foreach ( $this->get_indexable_lookup() as $meta_key => $indexable_key ) {
$indexable->{ $indexable_key } = $this->get_meta_value( $post_id, $meta_key );
}
$indexable = $this->set_link_count( $post_id, $indexable );
return $indexable;
}
/**
* 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 null|int 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' => 'og_title',
'opengraph-image' => 'og_image',
'opengraph-description' => 'og_description',
'twitter-title' => 'twitter_title',
'twitter-image' => 'twitter_image',
'twitter-description' => 'twitter_description',
];
}
/**
* Updates the link count from existing data.
*
* @param int $post_id The post ID to use.
* @param \Yoast\WP\SEO\Models\Indexable $indexable The indexable to extend.
*
* @return \Yoast\WP\SEO\Models\Indexable The extended indexable.
*/
protected function set_link_count( $post_id, Indexable $indexable ) {
try {
$seo_meta = $this->seo_meta_repository->find_by_post_id( $post_id );
if ( $seo_meta ) {
$indexable->link_count = $seo_meta->internal_link_count;
$indexable->incoming_link_count = $seo_meta->incoming_link_count;
}
// @codingStandardsIgnoreLine Generic.CodeAnalysis.EmptyStatement.DetectedCATCH -- There is nothing to do.
} catch ( Exception $exception ) {
// Do nothing...
}
return $indexable;
}
/**
* Retrieves the current value for the meta field.
*
* @param int $post_id The post ID to use.
* @param string $meta_key Meta key to fetch.
*
* @return mixed The value of the indexable entry to use.
*/
protected function get_meta_value( $post_id, $meta_key ) {
$value = \WPSEO_Meta::get_value( $meta_key, $post_id );
if ( \is_string( $value ) && $value === '' ) {
return null;
}
return $value;
}
}

View File

@@ -0,0 +1,131 @@
<?php
/**
* Post Builder for the indexables.
*
* @package Yoast\YoastSEO\Builders
*/
namespace Yoast\WP\SEO\Builders;
use Yoast\WP\SEO\Models\Indexable;
/**
* Formats the term meta to indexable format.
*/
class Indexable_Term_Builder {
/**
* Formats the data.
*
* @param int $term_id ID of the term to save data for.
* @param \Yoast\WP\SEO\Models\Indexable $indexable The indexable to format.
*
* @return \Yoast\WP\SEO\Models\Indexable The extended indexable.
*/
public function build( $term_id, Indexable $indexable ) {
$term = \get_term( $term_id );
$taxonomy = $term->taxonomy;
$term_meta = \WPSEO_Taxonomy_Meta::get_term_meta( $term_id, $taxonomy );
$indexable->permalink = \get_term_link( $term_id, $taxonomy );
$indexable->object_sub_type = $taxonomy;
$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 ) );
foreach ( $this->get_indexable_lookup() as $meta_key => $indexable_key ) {
$indexable->{ $indexable_key } = $this->get_meta_value( $meta_key, $term_meta );
}
// Not implemented yet.
$indexable->is_cornerstone = false;
$indexable->is_robots_nofollow = null;
$indexable->is_robots_noarchive = null;
$indexable->is_robots_noimageindex = null;
$indexable->is_robots_nosnippet = null;
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 null|int 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' => 'og_title',
'wpseo_opengraph-description' => 'og_description',
'wpseo_opengraph-image' => 'og_image',
'wpseo_twitter-title' => 'twitter_title',
'wpseo_twitter-description' => 'twitter_description',
'wpseo_twitter-image' => 'twitter_image',
];
}
/**
* 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 null|string The meta value.
*/
protected function get_meta_value( $meta_key, $term_meta ) {
if ( ! \array_key_exists( $meta_key, $term_meta ) ) {
return null;
}
$value = $term_meta[ $meta_key ];
if ( \is_string( $value ) && $value === '' ) {
return null;
}
return $value;
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* Yoast SEO plugin file.
*
* @package Yoast\YoastSEO\Conditionals
*/
namespace Yoast\WP\SEO\Conditionals;
/**
* Conditional that is only met when in the admin.
*/
class Admin_Conditional implements Conditional {
/**
* Returns whether or not this conditional is met.
*
* @return boolean Whether or not the conditional is met.
*/
public function is_met() {
return \is_admin();
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* Yoast SEO plugin file.
*
* @package Yoast\YoastSEO\Conditionals
*/
namespace Yoast\WP\SEO\Conditionals;
/**
* Conditional interface, used to prevent integrations from loading.
*
* @package Yoast\WP\SEO\Conditionals
*/
interface Conditional {
/**
* Returns whether or not this conditional is met.
*
* @return boolean Whether or not the conditional is met.
*/
public function is_met();
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Yoast SEO plugin file.
*
* @package Yoast\YoastSEO\Conditionals
*/
namespace Yoast\WP\SEO\Conditionals;
/**
* Abstract class for creating conditionals based on feature flags.
*/
abstract class Feature_Flag_Conditional implements Conditional {
/**
* Returns whether or not this conditional is met.
*
* @return boolean Whether or not the conditional is met.
*/
public function is_met() {
$feature_flag = \strtoupper( $this->get_feature_flag() );
return \defined( 'YOAST_SEO_' . $feature_flag ) && \constant( 'YOAST_SEO_' . $feature_flag ) === 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.
*/
abstract protected function get_feature_flag();
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* Yoast SEO plugin file.
*
* @package Yoast\YoastSEO\Conditionals
*/
namespace Yoast\WP\SEO\Conditionals;
/**
* Conditional for the indexables feature flag.
*/
class Indexables_Feature_Flag_Conditional extends Feature_Flag_Conditional {
/**
* @inheritdoc
*/
protected function get_feature_flag() {
return 'indexables';
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* Yoast SEO plugin file.
*
* @package Yoast\YoastSEO\Conditionals
*/
namespace Yoast\WP\SEO\Conditionals;
/**
* Trait for integrations that do not have any conditionals.
*/
trait No_Conditionals {
/**
* Returns an empty array, meaning no conditionals are required to load whatever uses this trait.
*
* @return array The conditionals that must be met to load this.
*/
public static function get_conditionals() {
return [];
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* Yoast SEO Plugin File.
*
* @package Yoast\YoastSEO\Config
*/
namespace Yoast\WP\SEO\Config;
/**
* Makes sure the dependencies are loaded and the environment is prepared to use them.
* This is achieved by setting up class aliases and defines required constants.
*/
class Dependency_Management {
/**
* Checks if the prefixes are available.
*
* @codeCoverageIgnore
*
* @return bool True if prefixes are available.
*/
public function prefixed_available() {
static $available = null;
if ( $available === null ) {
$available = \is_file( \WPSEO_PATH . \YOAST_VENDOR_PREFIX_DIRECTORY . '/dependencies-prefixed.txt' );
}
return $available;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Yoast SEO Plugin File.
*
* @package Yoast\YoastSEO\Config
*/
namespace Yoast\WP\SEO\Database;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Loggers\Logger;
use Yoast\WP\SEO\WordPress\Initializer;
use Yoast\WP\SEO\ORM\Yoast_Model;
use YoastSEO_Vendor\ORM;
/**
* Configures the ORM with the database credentials.
*/
class Database_Setup implements Initializer {
use No_Conditionals;
/**
* The logger object.
*
* @var \YoastSEO_Vendor\Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Database_Setup constructor.
*
* @param \Yoast\WP\SEO\Loggers\Logger $logger The logger.
*/
public function __construct( Logger $logger ) {
$this->logger = $logger;
}
/**
* Initializes the database setup.
*/
public function initialize() {
ORM::configure( 'mysql:host=' . \DB_HOST . ';dbname=' . \DB_NAME );
ORM::configure( 'username', \DB_USER );
ORM::configure( 'password', \DB_PASSWORD );
Yoast_Model::$auto_prefix_models = '\\Yoast\\WP\\SEO\\Models\\';
Yoast_Model::$logger = $this->logger;
}
}

View File

@@ -0,0 +1,205 @@
<?php
/**
* Yoast SEO Plugin File.
*
* @package Yoast\YoastSEO\Config
*/
namespace Yoast\WP\SEO\Database;
use Yoast\WP\SEO\Conditionals\Indexables_Feature_Flag_Conditional;
use Yoast\WP\SEO\Loggers\Logger;
use Yoast\WP\SEO\ORM\Yoast_Model;
use Yoast\WP\SEO\WordPress\Initializer;
/**
* Triggers database migrations and handles results.
*/
class Migration_Runner implements Initializer {
/**
* The value for a migration success state.
*
* @var int
*/
const MIGRATION_STATE_SUCCESS = 0;
/**
* The value for a migration state error.
*
* @var int
*/
const MIGRATION_STATE_ERROR = 1;
/**
* The value that communicates a migration problem.
*
* @var string
*/
const MIGRATION_ERROR_TRANSIENT_KEY = 'yoast_migration_problem_';
/**
* The Ruckusing framework runner.
*
* @var \Yoast\WP\SEO\Database\Ruckusing_Framework
*/
protected $framework;
/**
* The logger object.
*
* @var \Yoast\WP\SEO\Loggers\Logger
*/
protected $logger;
/**
* Retrieves the conditionals for the migrations.
*
* @return array The conditionals.
*/
public static function get_conditionals() {
return [ Indexables_Feature_Flag_Conditional::class ];
}
/**
* Migrations constructor.
*
* @param \Yoast\WP\SEO\Database\Ruckusing_Framework $framework The Ruckusing framework runner.
* @param \Yoast\WP\SEO\Loggers\Logger $logger A PSR compatible logger.
*/
public function __construct( Ruckusing_Framework $framework, Logger $logger ) {
$this->framework = $framework;
$this->logger = $logger;
}
/**
* Runs this initializer.
*
* @throws \Exception When a migration errored.
*
* @return void
*/
public function initialize() {
$this->run_migrations( 'free', Yoast_Model::get_table_name( 'migrations' ), \WPSEO_PATH . 'migrations' );
}
/**
* Initializes the migrations.
*
* @param string $name The name of the migration.
* @param string $migrations_table_name The migrations table name.
* @param string $migrations_directory The migrations directory.
*
* @return bool True on success, false on failure.
* @throws \Exception If the migration fails and YOAST_ENVIRONMENT is not production.
*/
public function run_migrations( $name, $migrations_table_name, $migrations_directory ) {
try {
$framework_runner = $this->framework->get_framework_runner( $migrations_table_name, $migrations_directory );
/**
* This variable represents Ruckusing_Adapter_MySQL_Base adapter.
*
* @var \YoastSEO_Vendor\Ruckusing_Adapter_MySQL_Base $adapter
*/
$adapter = $framework_runner->get_adapter();
// Create our own migrations table with a 191 string limit to support older versions of MySQL.
// Run this before calling the framework runner so it doesn't create it's own.
if ( ! $adapter->has_table( $migrations_table_name ) ) {
$table = $adapter->create_table( $migrations_table_name, [ 'id' => false ] );
$table->column( 'version', 'string', [ 'limit' => 191 ] );
$table->finish();
$adapter->add_index( $migrations_table_name, 'version', [ 'unique' => true ] );
}
// Create our own task manager so we can set RUCKUSING_BASE to a nonsense directory as it's impossible to
// determine the actual directory if the plugin is installed with composer.
$task_manager = $this->framework->get_framework_task_manager( $adapter, $migrations_table_name, $migrations_directory );
$task_manager->execute( $framework_runner, 'db:migrate', [] );
}
catch ( \Exception $exception ) {
$this->logger->error( $exception->getMessage() );
// Something went wrong...
$this->set_failed_state( $name, $exception->getMessage() );
if ( \defined( 'YOAST_ENVIRONMENT' ) && \YOAST_ENVIRONMENT !== 'production' ) {
throw $exception;
}
return false;
}
$this->set_success_state( $name );
return true;
}
/**
* Retrieves the state of the migrations.
*
* @param string $name The name of the migration.
*
* @return bool True if migrations have completed successfully.
*/
public function is_usable( $name ) {
return ( $this->get_migration_state( $name ) === self::MIGRATION_STATE_SUCCESS );
}
/**
* Retrieves the state of the migrations.
*
* @param string $name The name of the migration.
*
* @return bool True if migrations have completed successfully.
*/
public function has_migration_error( $name ) {
return ( $this->get_migration_state( $name ) === self::MIGRATION_STATE_ERROR );
}
/**
* Handles state persistence for a failed migration environment.
*
* @param string $name The name of the migration.
* @param string $message Message explaining the reason for the failed state.
*
* @return void
*/
protected function set_failed_state( $name, $message ) {
// @todo do something with the message.
\set_transient( $this->get_error_transient_key( $name ), self::MIGRATION_STATE_ERROR, \DAY_IN_SECONDS );
}
/**
* Removes the problem state from the system.
*
* @param string $name The name of the migration.
*
* @return void
*/
protected function set_success_state( $name ) {
\delete_transient( $this->get_error_transient_key( $name ) );
}
/**
* Retrieves the current migration state.
*
* @param string $name The name of the migration.
*
* @return int|null Migration state.
*/
protected function get_migration_state( $name ) {
return \get_transient( $this->get_error_transient_key( $name ), self::MIGRATION_STATE_SUCCESS );
}
/**
* Retrieves the error state transient key to use.
*
* @param string $name The name of the migration.
*
* @return string The transient key to use for storing the error state.
*/
protected function get_error_transient_key( $name ) {
return self::MIGRATION_ERROR_TRANSIENT_KEY . $name;
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* Yoast SEO Plugin File.
*
* @package Yoast\YoastSEO\Config
*/
namespace Yoast\WP\SEO\Database;
use wpdb;
use Yoast\WP\SEO\Config\Dependency_Management;
use Yoast\WP\SEO\Loggers\Migration_Logger;
use YoastSEO_Vendor\Ruckusing_FrameworkRunner;
use YoastSEO_Vendor\Ruckusing_Task_Manager;
use YoastSEO_Vendor\Task_Db_Migrate;
/**
* Class Ruckusing_Framework
*/
class Ruckusing_Framework {
/**
* The database object.
*
* @var \wpdb
*/
protected $wpdb;
/**
* The dependency management checker.
*
* @var \Yoast\WP\SEO\Config\Dependency_Management
*/
protected $dependency_management;
/**
* The migration logger object.
*
* @var \Yoast\WP\SEO\Loggers\Migration_Logger
*/
protected $migration_logger;
/**
* Ruckusing_Framework constructor.
*
* @param \wpdb $wpdb The wpdb instance.
* @param \Yoast\WP\SEO\Config\Dependency_Management $dependency_management The dependency management checker.
* @param \Yoast\WP\SEO\Loggers\Migration_Logger $migration_logger The migration logger, extends the
* Ruckusing logger.
*/
public function __construct( wpdb $wpdb, Dependency_Management $dependency_management, Migration_Logger $migration_logger ) {
$this->wpdb = $wpdb;
$this->dependency_management = $dependency_management;
$this->migration_logger = $migration_logger;
}
/**
* Gets the ruckusing framework runner.
*
* @param string $migrations_table_name The migrations table name.
* @param string $migrations_directory The migrations directory.
*
* @return \YoastSEO_Vendor\Ruckusing_FrameworkRunner The framework runner.
*/
public function get_framework_runner( $migrations_table_name, $migrations_directory ) {
$this->maybe_set_constant();
$configuration = $this->get_configuration( $migrations_table_name, $migrations_directory );
$instance = new Ruckusing_FrameworkRunner( $configuration, [ 'db:migrate', 'env=production' ], $this->migration_logger );
/*
* As the Ruckusing_FrameworkRunner is setting its own error and exception handlers,
* we need to restore the defaults.
*/
\restore_error_handler();
\restore_exception_handler();
return $instance;
}
/**
* Gets the ruckusing framework task manager.
*
* @param \YoastSEO_Vendor\Ruckusing_Adapter_MySQL_Base $adapter The MySQL adapter.
* @param string $migrations_table_name The migrations table name.
* @param string $migrations_directory The migrations directory.
*
* @return \YoastSEO_Vendor\Ruckusing_Task_Manager The task manager.
* @throws \YoastSEO_Vendor\Ruckusing_Exception If any of the arguments are invalid.
*/
public function get_framework_task_manager( $adapter, $migrations_table_name, $migrations_directory ) {
$task_manager = new Ruckusing_Task_Manager( $adapter, $this->get_configuration( $migrations_table_name, $migrations_directory ) );
$task_manager->register_task( 'db:migrate', new Task_Db_Migrate( $adapter ) );
return $task_manager;
}
/**
* Returns the framework configuration for a given migrations table name and directory.
*
* @param string $migrations_table_name The migrations table name.
* @param string $migrations_directory The migrations directory.
*
* @return array The configuration
*/
public function get_configuration( $migrations_table_name, $migrations_directory ) {
return [
'db' => [
'production' => [
'type' => 'mysql',
'host' => \DB_HOST,
'port' => 3306,
'database' => \DB_NAME,
'user' => \DB_USER,
'password' => \DB_PASSWORD,
'charset' => $this->wpdb->charset,
'directory' => '', // This needs to be set, to use the migrations folder as base folder.
'schema_version_table_name' => $migrations_table_name,
],
],
'migrations_dir' => [ 'default' => $migrations_directory ],
// This needs to be set but is not used.
'db_dir' => true,
// This needs to be set but is not used.
'log_dir' => true,
// This needs to be set but is not used.
];
}
/**
* Sets the constant required by the ruckusing framework.
*
* @return bool Whether or not the constant is now the correct value.
*/
public function maybe_set_constant() {
$constant_name = $this->dependency_management->prefixed_available() ? \YOAST_VENDOR_NS_PREFIX . '\RUCKUSING_BASE' : 'RUCKUSING_BASE';
$constant_value = \WPSEO_PATH . 'migrations' . \DIRECTORY_SEPARATOR . 'ruckusing';
if ( \defined( $constant_name ) ) {
return \constant( $constant_name ) === $constant_value;
}
return \define( $constant_name, $constant_value );
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Exception to use when a method does not exist.
*
* @package Yoast\YoastSEO\Exceptions
*/
namespace Yoast\WP\SEO\Exceptions;
/**
* The exception when a method does not exists.
*/
class Missing_Method extends \Exception {
/**
* Creates exception for a method that does not exists in a class.
*
* @param string $method The method that does not exists.
* @param string $class_name The class name.
*
* @return static Instance of the exception.
*/
public static function for_class( $method, $class_name ) {
return new static(
\sprintf(
/* translators: %1$s expands to the method name. %2$s expands to the class name */
\__( 'Method %1$s() does not exist in class %2$s', 'wordpress-seo' ),
$method,
$class_name
)
);
}
}

View File

@@ -0,0 +1,325 @@
<?php
namespace Yoast\WP\SEO\Generated;
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface;
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Container;
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Exception\LogicException;
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Exception\RuntimeException;
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
class Cached_Container extends Container
{
private $parameters;
private $targetDirs = [];
public function __construct()
{
$this->services = [];
$this->normalizedIds = [
'yoast\\wp\\seo\\builders\\indexable_author_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder',
'yoast\\wp\\seo\\builders\\indexable_post_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder',
'yoast\\wp\\seo\\builders\\indexable_term_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder',
'yoast\\wp\\seo\\conditionals\\admin_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin_Conditional',
'yoast\\wp\\seo\\conditionals\\indexables_feature_flag_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Indexables_Feature_Flag_Conditional',
'yoast\\wp\\seo\\database\\database_setup' => 'Yoast\\WP\\SEO\\Database\\Database_Setup',
'yoast\\wp\\seo\\database\\migration_runner' => 'Yoast\\WP\\SEO\\Database\\Migration_Runner',
'yoast\\wp\\seo\\loader' => 'Yoast\\WP\\SEO\\Loader',
'yoast\\wp\\seo\\loggers\\logger' => 'Yoast\\WP\\SEO\\Loggers\\Logger',
'yoast\\wp\\seo\\repositories\\indexable_repository' => 'Yoast\\WP\\SEO\\Repositories\\Indexable_Repository',
'yoast\\wp\\seo\\repositories\\primary_term_repository' => 'Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository',
'yoast\\wp\\seo\\repositories\\seo_links_repository' => 'Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository',
'yoast\\wp\\seo\\repositories\\seo_meta_repository' => 'Yoast\\WP\\SEO\\Repositories\\SEO_Meta_Repository',
'yoast\\wp\\seo\\watchers\\indexable_author_watcher' => 'Yoast\\WP\\SEO\\Watchers\\Indexable_Author_Watcher',
'yoast\\wp\\seo\\watchers\\indexable_post_watcher' => 'Yoast\\WP\\SEO\\Watchers\\Indexable_Post_Watcher',
'yoast\\wp\\seo\\watchers\\indexable_term_watcher' => 'Yoast\\WP\\SEO\\Watchers\\Indexable_Term_Watcher',
'yoast\\wp\\seo\\watchers\\primary_term_watcher' => 'Yoast\\WP\\SEO\\Watchers\\Primary_Term_Watcher',
];
$this->methodMap = [
'Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder' => 'getIndexableAuthorBuilderService',
'Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder' => 'getIndexablePostBuilderService',
'Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder' => 'getIndexableTermBuilderService',
'Yoast\\WP\\SEO\\Conditionals\\Admin_Conditional' => 'getAdminConditionalService',
'Yoast\\WP\\SEO\\Conditionals\\Indexables_Feature_Flag_Conditional' => 'getIndexablesFeatureFlagConditionalService',
'Yoast\\WP\\SEO\\Database\\Database_Setup' => 'getDatabaseSetupService',
'Yoast\\WP\\SEO\\Database\\Migration_Runner' => 'getMigrationRunnerService',
'Yoast\\WP\\SEO\\Loader' => 'getLoaderService',
'Yoast\\WP\\SEO\\Loggers\\Logger' => 'getLoggerService',
'Yoast\\WP\\SEO\\Repositories\\Indexable_Repository' => 'getIndexableRepositoryService',
'Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository' => 'getPrimaryTermRepositoryService',
'Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository' => 'getSEOLinksRepositoryService',
'Yoast\\WP\\SEO\\Repositories\\SEO_Meta_Repository' => 'getSEOMetaRepositoryService',
'Yoast\\WP\\SEO\\Watchers\\Indexable_Author_Watcher' => 'getIndexableAuthorWatcherService',
'Yoast\\WP\\SEO\\Watchers\\Indexable_Post_Watcher' => 'getIndexablePostWatcherService',
'Yoast\\WP\\SEO\\Watchers\\Indexable_Term_Watcher' => 'getIndexableTermWatcherService',
'Yoast\\WP\\SEO\\Watchers\\Primary_Term_Watcher' => 'getPrimaryTermWatcherService',
'wp_query' => 'getWpQueryService',
'wpdb' => 'getWpdbService',
];
$this->privates = [
'Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder' => true,
'Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder' => true,
'Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder' => true,
'Yoast\\WP\\SEO\\Loggers\\Logger' => true,
'Yoast\\WP\\SEO\\Repositories\\Indexable_Repository' => true,
'Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository' => true,
'Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository' => true,
'Yoast\\WP\\SEO\\Repositories\\SEO_Meta_Repository' => true,
'wp_query' => true,
'wpdb' => true,
];
$this->aliases = [];
}
public function getRemovedIds()
{
return [
'Psr\\Container\\ContainerInterface' => true,
'YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
'Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder' => true,
'Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder' => true,
'Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder' => true,
'Yoast\\WP\\SEO\\Config\\Dependency_Management' => true,
'Yoast\\WP\\SEO\\Database\\Ruckusing_Framework' => true,
'Yoast\\WP\\SEO\\Exceptions\\Missing_Method' => true,
'Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper' => true,
'Yoast\\WP\\SEO\\Helpers\\Home_Url_Helper' => true,
'Yoast\\WP\\SEO\\Loggers\\Logger' => true,
'Yoast\\WP\\SEO\\Loggers\\Migration_Logger' => true,
'Yoast\\WP\\SEO\\Oauth\\Client' => true,
'Yoast\\WP\\SEO\\Repositories\\Indexable_Repository' => true,
'Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository' => true,
'Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository' => true,
'Yoast\\WP\\SEO\\Repositories\\SEO_Meta_Repository' => true,
'wp_query' => true,
'wpdb' => true,
];
}
public function compile()
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}
public function isCompiled()
{
return true;
}
public function isFrozen()
{
@trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED);
return true;
}
/**
* Gets the public 'Yoast\WP\SEO\Conditionals\Admin_Conditional' shared autowired service.
*
* @return \Yoast\WP\SEO\Conditionals\Admin_Conditional
*/
protected function getAdminConditionalService()
{
return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin_Conditional();
}
/**
* Gets the public 'Yoast\WP\SEO\Conditionals\Indexables_Feature_Flag_Conditional' shared autowired service.
*
* @return \Yoast\WP\SEO\Conditionals\Indexables_Feature_Flag_Conditional
*/
protected function getIndexablesFeatureFlagConditionalService()
{
return $this->services['Yoast\\WP\\SEO\\Conditionals\\Indexables_Feature_Flag_Conditional'] = new \Yoast\WP\SEO\Conditionals\Indexables_Feature_Flag_Conditional();
}
/**
* Gets the public 'Yoast\WP\SEO\Database\Database_Setup' shared autowired service.
*
* @return \Yoast\WP\SEO\Database\Database_Setup
*/
protected function getDatabaseSetupService()
{
return $this->services['Yoast\\WP\\SEO\\Database\\Database_Setup'] = new \Yoast\WP\SEO\Database\Database_Setup(${($_ = isset($this->services['Yoast\\WP\\SEO\\Loggers\\Logger']) ? $this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] : ($this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] = new \Yoast\WP\SEO\Loggers\Logger())) && false ?: '_'});
}
/**
* Gets the public 'Yoast\WP\SEO\Database\Migration_Runner' shared autowired service.
*
* @return \Yoast\WP\SEO\Database\Migration_Runner
*/
protected function getMigrationRunnerService()
{
$a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Loggers\\Logger']) ? $this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] : ($this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] = new \Yoast\WP\SEO\Loggers\Logger())) && false ?: '_'};
return $this->services['Yoast\\WP\\SEO\\Database\\Migration_Runner'] = new \Yoast\WP\SEO\Database\Migration_Runner(new \Yoast\WP\SEO\Database\Ruckusing_Framework(${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, new \Yoast\WP\SEO\Config\Dependency_Management(), new \Yoast\WP\SEO\Loggers\Migration_Logger($a)), $a);
}
/**
* Gets the public 'Yoast\WP\SEO\Loader' shared autowired service.
*
* @return \Yoast\WP\SEO\Loader
*/
protected function getLoaderService()
{
$this->services['Yoast\\WP\\SEO\\Loader'] = $instance = new \Yoast\WP\SEO\Loader($this);
$instance->register_initializer('Yoast\\WP\\SEO\\Database\\Database_Setup');
$instance->register_initializer('Yoast\\WP\\SEO\\Database\\Migration_Runner');
$instance->register_integration('Yoast\\WP\\SEO\\Watchers\\Indexable_Author_Watcher');
$instance->register_integration('Yoast\\WP\\SEO\\Watchers\\Indexable_Post_Watcher');
$instance->register_integration('Yoast\\WP\\SEO\\Watchers\\Indexable_Term_Watcher');
$instance->register_integration('Yoast\\WP\\SEO\\Watchers\\Primary_Term_Watcher');
return $instance;
}
/**
* Gets the public 'Yoast\WP\SEO\Watchers\Indexable_Author_Watcher' shared autowired service.
*
* @return \Yoast\WP\SEO\Watchers\Indexable_Author_Watcher
*/
protected function getIndexableAuthorWatcherService()
{
return $this->services['Yoast\\WP\\SEO\\Watchers\\Indexable_Author_Watcher'] = new \Yoast\WP\SEO\Watchers\Indexable_Author_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'] : ($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Author_Builder())) && false ?: '_'});
}
/**
* Gets the public 'Yoast\WP\SEO\Watchers\Indexable_Post_Watcher' shared autowired service.
*
* @return \Yoast\WP\SEO\Watchers\Indexable_Post_Watcher
*/
protected function getIndexablePostWatcherService()
{
return $this->services['Yoast\\WP\\SEO\\Watchers\\Indexable_Post_Watcher'] = new \Yoast\WP\SEO\Watchers\Indexable_Post_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder'] : $this->getIndexablePostBuilderService()) && false ?: '_'});
}
/**
* Gets the public 'Yoast\WP\SEO\Watchers\Indexable_Term_Watcher' shared autowired service.
*
* @return \Yoast\WP\SEO\Watchers\Indexable_Term_Watcher
*/
protected function getIndexableTermWatcherService()
{
return $this->services['Yoast\\WP\\SEO\\Watchers\\Indexable_Term_Watcher'] = new \Yoast\WP\SEO\Watchers\Indexable_Term_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] : ($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Term_Builder())) && false ?: '_'});
}
/**
* Gets the public 'Yoast\WP\SEO\Watchers\Primary_Term_Watcher' shared autowired service.
*
* @return \Yoast\WP\SEO\Watchers\Primary_Term_Watcher
*/
protected function getPrimaryTermWatcherService()
{
return $this->services['Yoast\\WP\\SEO\\Watchers\\Primary_Term_Watcher'] = new \Yoast\WP\SEO\Watchers\Primary_Term_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] : $this->getPrimaryTermRepositoryService()) && false ?: '_'});
}
/**
* Gets the private 'Yoast\WP\SEO\Builders\Indexable_Author_Builder' shared autowired service.
*
* @return \Yoast\WP\SEO\Builders\Indexable_Author_Builder
*/
protected function getIndexableAuthorBuilderService()
{
return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Author_Builder();
}
/**
* Gets the private 'Yoast\WP\SEO\Builders\Indexable_Post_Builder' shared autowired service.
*
* @return \Yoast\WP\SEO\Builders\Indexable_Post_Builder
*/
protected function getIndexablePostBuilderService()
{
return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Post_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Meta_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Meta_Repository'] : $this->getSEOMetaRepositoryService()) && false ?: '_'});
}
/**
* Gets the private 'Yoast\WP\SEO\Builders\Indexable_Term_Builder' shared autowired service.
*
* @return \Yoast\WP\SEO\Builders\Indexable_Term_Builder
*/
protected function getIndexableTermBuilderService()
{
return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Term_Builder();
}
/**
* Gets the private 'Yoast\WP\SEO\Loggers\Logger' shared autowired service.
*
* @return \Yoast\WP\SEO\Loggers\Logger
*/
protected function getLoggerService()
{
return $this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] = new \Yoast\WP\SEO\Loggers\Logger();
}
/**
* Gets the private 'Yoast\WP\SEO\Repositories\Indexable_Repository' shared autowired service.
*
* @return \Yoast\WP\SEO\Repositories\Indexable_Repository
*/
protected function getIndexableRepositoryService()
{
return $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] = \Yoast\WP\SEO\Repositories\Indexable_Repository::get_instance(${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'] : ($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Author_Builder())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder'] : $this->getIndexablePostBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] : ($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Term_Builder())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Loggers\\Logger']) ? $this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] : ($this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] = new \Yoast\WP\SEO\Loggers\Logger())) && false ?: '_'});
}
/**
* Gets the private 'Yoast\WP\SEO\Repositories\Primary_Term_Repository' shared autowired service.
*
* @return \Yoast\WP\SEO\Repositories\Primary_Term_Repository
*/
protected function getPrimaryTermRepositoryService()
{
return $this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] = \Yoast\WP\SEO\Repositories\Primary_Term_Repository::get_instance();
}
/**
* Gets the private 'Yoast\WP\SEO\Repositories\SEO_Links_Repository' shared autowired service.
*
* @return \Yoast\WP\SEO\Repositories\SEO_Links_Repository
*/
protected function getSEOLinksRepositoryService()
{
return $this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository'] = \Yoast\WP\SEO\Repositories\SEO_Links_Repository::get_instance();
}
/**
* Gets the private 'Yoast\WP\SEO\Repositories\SEO_Meta_Repository' shared autowired service.
*
* @return \Yoast\WP\SEO\Repositories\SEO_Meta_Repository
*/
protected function getSEOMetaRepositoryService()
{
return $this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Meta_Repository'] = \Yoast\WP\SEO\Repositories\SEO_Meta_Repository::get_instance();
}
/**
* Gets the private 'wp_query' shared service.
*
* @return \WP_Query
*/
protected function getWpQueryService()
{
return $this->services['wp_query'] = \Yoast\WP\SEO\WordPress\Wrapper::get_wp_query();
}
/**
* Gets the private 'wpdb' shared service.
*
* @return \wpdb
*/
protected function getWpdbService()
{
return $this->services['wpdb'] = \Yoast\WP\SEO\WordPress\Wrapper::get_wpdb();
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* A helper object for author archives.
*
* @package Yoast\YoastSEO\Helpers
*/
namespace Yoast\WP\SEO\Helpers;
/**
* Class Author_Archive_Helper
*/
class Author_Archive_Helper {
/**
* Gets the array of post types that are shown on an author's archive.
*
* @return array The post types that are shown on an author's archive.
*/
public function get_author_archive_post_types() {
/**
* Filters the array of post types that are shown on an author's archive.
*
* @param array $args The post types that are shown on an author archive.
*/
return \apply_filters( 'wpseo_author_archive_post_types', [ 'post' ] );
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* A helper object for the home url.
*
* @package Yoast\YoastSEO\Helpers
*/
namespace Yoast\WP\SEO\Helpers;
/**
* Class Home_Url_Helper
*/
class Home_Url_Helper {
/**
* The home url.
*
* @var string
*/
protected static $home_url;
/**
* The parsed home url.
*
* @var array
*/
protected static $parsed_home_url;
/**
* Retrieves the home url.
*
* @return string The home url.
*/
public function get() {
if ( static::$home_url === null ) {
static::$home_url = \home_url();
}
return static::$home_url;
}
/**
* Retrieves the home url that has been parsed.
*
* @return array The parsed url.
*/
public function get_parsed() {
if ( static::$parsed_home_url === null ) {
static::$parsed_home_url = \wp_parse_url( $this->get() );
}
return static::$parsed_home_url;
}
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* Yoast SEO Plugin File.
*
* @package Yoast\WP\SEO
*/
namespace Yoast\WP\SEO;
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class that manages loading integrations if and only if all their conditionals are met.
*/
class Loader {
/**
* The registered integrations.
*
* @var \Yoast\WP\SEO\WordPress\Integration[]
*/
protected $integrations = [];
/**
* The registered initializer.
*
* @var \Yoast\WP\SEO\WordPress\Initializer[]
*/
protected $initializers = [];
/**
* The dependency injection container.
*
* @var \YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Loader constructor.
*
* @param \YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface $container The dependency injection container.
*/
public function __construct( ContainerInterface $container ) {
$this->container = $container;
}
/**
* Registers an integration.
*
* @param string $class The class name of the integration to be loaded.
*
* @return void
*/
public function register_integration( $class ) {
$this->integrations[] = $class;
}
/**
* Registers a initializer.
*
* @param string $class The class name of the initializer to be loaded.
*
* @return void
*/
public function register_initializer( $class ) {
$this->initializers[] = $class;
}
/**
* Loads all registered classes if their conditionals are met.
*
* @return void
*/
public function load() {
$this->load_initializers();
$this->load_integrations();
}
/**
* Loads all registered initializers if their conditionals are met.
*
* @return void
*/
protected function load_initializers() {
foreach ( $this->initializers as $class ) {
if ( ! $this->conditionals_are_met( $class ) ) {
continue;
}
$this->container->get( $class )->initialize();
}
}
/**
* Loads all registered integrations if their conditionals are met.
*
* @return void
*/
protected function load_integrations() {
foreach ( $this->integrations as $class ) {
if ( ! $this->conditionals_are_met( $class ) ) {
continue;
}
$this->container->get( $class )->register_hooks();
}
}
/**
* Checks if all conditionals of a given integration are met.
*
* @param \Yoast\WP\SEO\WordPress\Loadable $class The class name of the integration.
*
* @return bool Whether or not all conditionals of the integration are met.
*/
protected function conditionals_are_met( $class ) {
$conditionals = $class::get_conditionals();
foreach ( $conditionals as $conditional ) {
if ( ! $this->container->get( $conditional )->is_met() ) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* WPSEO plugin file.
*
* @package Yoast\YoastSEO\Loaders
*/
use Yoast\WP\SEO\Config\Dependency_Management;
use Yoast\WP\SEO\Oauth\Client;
use YoastSEO_Vendor\League\OAuth2\Client\Token\AccessTokenInterface;
if ( ! defined( 'WPSEO_VERSION' ) ) {
header( 'Status: 403 Forbidden' );
header( 'HTTP/1.1 403 Forbidden' );
exit();
}
if ( file_exists( dirname( WPSEO_FILE ) . '/vendor_prefixed/guzzlehttp/guzzle/src/functions.php' ) ) {
require_once dirname( WPSEO_FILE ) . '/vendor_prefixed/guzzlehttp/guzzle/src/functions.php';
}
if ( file_exists( dirname( WPSEO_FILE ) . '/vendor_prefixed/guzzlehttp/psr7/src/functions.php' ) ) {
require_once dirname( WPSEO_FILE ) . '/vendor_prefixed/guzzlehttp/psr7/src/functions.php';
}
if ( file_exists( dirname( WPSEO_FILE ) . '/vendor_prefixed/guzzlehttp/promises/src/functions.php' ) ) {
require_once dirname( WPSEO_FILE ) . '/vendor_prefixed/guzzlehttp/promises/src/functions.php';
}
$yoast_seo_dependecy_management = new Dependency_Management();
$yoast_seo_dependecy_management->initialize();
class_alias( Client::class, 'WPSEO_MyYoast_Client' );
class_alias( AccessTokenInterface::class, 'WPSEO_MyYoast_AccessToken_Interface' );

View File

@@ -0,0 +1,55 @@
<?php
/**
* Yoast extension of the Model class.
*
* @package Yoast\YoastSEO\Loggers
*/
namespace Yoast\WP\SEO\Loggers;
use YoastSEO_Vendor\Psr\Log\LoggerInterface;
use YoastSEO_Vendor\Psr\Log\LoggerTrait;
use YoastSEO_Vendor\Psr\Log\NullLogger;
/**
* Creates an instance of a logger object.
*/
class Logger implements LoggerInterface {
use LoggerTrait;
/**
* The logger object.
*
* @var \YoastSEO_Vendor\Psr\Log\LoggerInterface
*/
protected $wrapped_logger;
/**
* Logger constructor.
*/
public function __construct() {
$this->wrapped_logger = new NullLogger();
/**
* Gives the possibility to set override the logger interface.
*
* @api \YoastSEO_Vendor\Psr\Log\LoggerInterface $logger Instance of NullLogger.
*
* @return \YoastSEO_Vendor\Psr\Log\LoggerInterface The logger object.
*/
$this->wrapped_logger = \apply_filters( 'wpseo_logger', $this->wrapped_logger );
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level The log level.
* @param string $message The log message.
* @param array $context The log context.
*
* @return void
*/
public function log( $level, $message, array $context = [] ) {
$this->wrapped_logger->log( $level, $message, $context );
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* Empty migration logger.
*
* @package Yoast\YoastSEO
*/
namespace Yoast\WP\SEO\Loggers;
use YoastSEO_Vendor\Ruckusing_Util_Logger;
/**
* Logger to make sure the output is not written into a file.
*/
class Migration_Logger extends Ruckusing_Util_Logger {
/**
* The logger object.
*
* @var \Yoast\WP\SEO\Loggers\Logger
*/
protected $logger;
/**
* Creates an instance of Ruckusing_Util_Logger.
*
* @codeCoverageIgnore
*
* @param \Yoast\WP\SEO\Loggers\Logger $logger The logger to wrap.
*/
public function __construct( Logger $logger ) {
$this->logger = $logger;
}
/**
* Logs a message.
*
* @codeCoverageIgnore
*
* @param string $msg Message to log.
*
* @return void
*/
public function log( $msg ) {
$this->logger->info( $msg );
}
/**
* Close the log file handler.
*
* @codeCoverageIgnore
*
* @return void
*/
public function close() {
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Yoast SEO Plugin File.
*
* @package Yoast\YoastSEO\Loaders
*/
namespace Yoast\WP\SEO;
use Yoast\WP\SEO\Dependency_Injection\Container_Compiler;
use Yoast\WP\SEO\Generated\Cached_Container;
if ( ! \defined( 'WPSEO_VERSION' ) ) {
\header( 'Status: 403 Forbidden' );
\header( 'HTTP/1.1 403 Forbidden' );
exit();
}
$development = \defined( 'YOAST_ENVIRONMENT' ) && \YOAST_ENVIRONMENT === 'development';
if ( $development && \class_exists( '\Yoast\WP\SEO\Dependency_Injection\Container_Compiler' ) ) {
// Exception here is unhandled as it will only occur in development.
Container_Compiler::compile( $development );
}
if ( \file_exists( __DIR__ . '/generated/container.php' ) ) {
require_once __DIR__ . '/generated/container.php';
$container = new Cached_Container();
try {
$container->get( Loader::class )->load();
} catch ( \Exception $e ) {
if ( $development ) {
throw $e;
}
// Don't crash the entire site, simply don't load.
// TODO: Add error notifications here.
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* Yoast SEO Plugin File.
*
* @package Yoast\YoastSEO\Models
*/
namespace Yoast\WP\SEO\Models;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Abstract class for indexable extensions.
*/
abstract class Indexable_Extension extends Yoast_Model {
/**
* @var \Yoast\WP\SEO\Models\Indexable
*/
protected $indexable = null;
/**
* Returns the indexable this extension belongs to.
*
* @return \Yoast\WP\SEO\Models\Indexable The indexable.
*/
public function indexable() {
if ( $this->indexable === null ) {
$this->indexable = $this->belongs_to( 'Indexable', 'indexable_id', 'id' )->find_one();
}
return $this->indexable;
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* Model for the Indexable table.
*
* @package Yoast\YoastSEO\Models
*/
namespace Yoast\WP\SEO\Models;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Indexable table definition.
*
* @property int $id
* @property int $object_id
* @property string $object_type
* @property string $object_sub_type
*
* @property string $created_at
* @property string $updated_at
*
* @property string $permalink
* @property string $permalink_hash
* @property string $canonical
* @property int $content_score
*
* @property boolean $is_robots_noindex
* @property boolean $is_robots_nofollow
* @property boolean $is_robots_noarchive
* @property boolean $is_robots_noimageindex
* @property boolean $is_robots_nosnippet
*
* @property string $title
* @property string $description
* @property string $breadcrumb_title
*
* @property boolean $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 string $og_title
* @property string $og_description
* @property string $og_image
*
* @property string $twitter_title
* @property string $twitter_description
* @property string $twitter_image
*/
class Indexable extends Yoast_Model {
/**
* Whether nor this model uses timestamps.
*
* @var bool
*/
protected $uses_timestamps = true;
/**
* The loaded indexable extensions.
*
* @var \Yoast\WP\SEO\Models\Indexable_Extension[]
*/
protected $loaded_extensions = [];
/**
* Returns an Indexable_Extension by it's name.
*
* @param string $class_name The class name of the extension to load.
*
* @return \Yoast\WP\SEO\Models\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 boolean True on succes.
*/
public function save() {
if ( $this->permalink ) {
$this->permalink = \trailingslashit( $this->permalink );
$this->permalink_hash = \strlen( $this->permalink ) . ':' . \md5( $this->permalink );
}
return parent::save();
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* Model for the Primary Term table.
*
* @package Yoast\YoastSEO\Models
*/
namespace Yoast\WP\SEO\Models;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Primary Term model definition.
*
* @property int $id Identifier.
* @property int $post_id Post ID.
* @property int $term_id Term ID.
* @property string $taxonomy Taxonomy.
*
* @property string $created_at
* @property string $updated_at
*/
class Primary_Term extends Yoast_Model {
/**
* Whether nor this model uses timestamps.
*
* @var bool
*/
protected $uses_timestamps = true;
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* Model for the SEO Meta table.
*
* @package Yoast\YoastSEO\Models
*/
namespace Yoast\WP\SEO\Models;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Table definition for the SEO Meta table.
*
* @property int $id
* @property string $url
* @property int $post_id
* @property int $target_post_id
* @property string $type
*/
class SEO_Links extends Yoast_Model {
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* Model for the SEO Meta table.
*
* @package Yoast\YoastSEO\Models
*/
namespace Yoast\WP\SEO\Models;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Table definition for the SEO Meta table.
*
* @property int $object_id
* @property int $internal_link_count
* @property int $incoming_link_count
*/
class SEO_Meta extends Yoast_Model {
/**
* Overwrites the default ID column name.
*
* @var string
*/
public static $id_column = 'object_id';
}

View File

@@ -0,0 +1,266 @@
<?php
/**
* Yoast extension of the Model class.
*
* @package Yoast\WP\SEO\Oauth
*/
namespace Yoast\WP\SEO\Oauth;
use WPSEO_Options;
use WPSEO_Utils;
use YoastSEO_Vendor\League\OAuth2\Client\Provider\GenericProvider;
use YoastSEO_Vendor\League\OAuth2\Client\Token\AccessToken;
/**
* Represents the oAuth client.
*/
class Client {
/**
* Contains the configuration.
*
* @var array
*/
private $config;
/**
* Contains the set access tokens.
*
* @var \YoastSEO_Vendor\League\OAuth2\Client\Token\AccessTokenInterface[]
*/
private $access_tokens;
/**
* Instance of this class.
*
* @var static
*/
private static $instance;
/**
* Client constructor.
*
* @codeCoverageIgnore
*/
public function __construct() {
$oauth = $this->get_option();
$this->config = $oauth['config'];
$this->access_tokens = $this->format_access_tokens( $oauth['access_tokens'] );
}
/**
* Retrieves the instance of this class.
*
* @codeCoverageIgnore
*
* @return \Yoast\WP\SEO\Oauth\Client Instance of this class.
*/
public static function get_instance() {
if ( static::$instance === null ) {
static::$instance = new static();
}
return static::$instance;
}
/**
* Saves the configuration.
*
* @param array $config The configuration to use.
*
* @return void
*/
public function save_configuration( array $config ) {
$allowed_config_keys = [ 'clientId', 'secret' ];
foreach ( $allowed_config_keys as $allowed_config_key ) {
if ( ! \array_key_exists( $allowed_config_key, $config ) ) {
continue;
}
$this->config[ $allowed_config_key ] = $config[ $allowed_config_key ];
}
$this->update_option();
}
/**
* Retrieves the value of the config.
*
* @return array The config.
*/
public function get_configuration() {
return $this->config;
}
/**
* Checks if the configuration is set correctly.
*
* @return bool True when clientId and secret are set.
*/
public function has_configuration() {
if ( $this->config['clientId'] === null ) {
return false;
}
if ( $this->config['secret'] === null ) {
return false;
}
return true;
}
/**
* Clears the current configuration.
*
* @return void
*/
public function clear_configuration() {
$this->config = $this->get_default_option()['config'];
$this->update_option();
}
/**
* Saves the access token for the given user.
*
* @param int $user_id User ID to receive token for.
* @param \YoastSEO_Vendor\League\OAuth2\Client\Token\AccessTokenInterface $access_token The access token to save.
*
* @return void
*/
public function save_access_token( $user_id, $access_token ) {
$this->access_tokens[ $user_id ] = $access_token;
$this->update_option();
}
/**
* Retrieves an access token.
*
* @param null|int $user_id User ID to receive token for.
*
* @return bool|\YoastSEO_Vendor\League\OAuth2\Client\Token\AccessTokenInterface False if not found. Token when found.
*/
public function get_access_token( $user_id = null ) {
if ( $user_id === null ) {
return \reset( $this->access_tokens );
}
if ( ! isset( $this->access_tokens[ $user_id ] ) ) {
return false;
}
return $this->access_tokens[ $user_id ];
}
/**
* Removes an access token from the list of access token.
*
* @param int $user_id The user ID to remove the access token for.
*
* @return void
*/
public function remove_access_token( $user_id ) {
if ( ! isset( $this->access_tokens[ $user_id ] ) ) {
return;
}
unset( $this->access_tokens[ $user_id ] );
$this->update_option();
}
/**
* Returns an instance of the oAuth provider.
*
* @return \YoastSEO_Vendor\League\OAuth2\Client\Provider\GenericProvider The provider.
*/
public function get_provider() {
return new GenericProvider(
[
'clientId' => $this->config['clientId'],
'clientSecret' => $this->config['secret'],
'redirectUri' => ( WPSEO_Utils::is_plugin_network_active() ) ? \home_url( 'yoast/oauth/callback' ) : \network_home_url( 'yoast/oauth/callback' ),
'urlAuthorize' => 'https://yoast.com/login/oauth/authorize',
'urlAccessToken' => 'https://yoast.com/login/oauth/token',
'urlResourceOwnerDetails' => 'https://my.yoast.com/api/sites/current',
]
);
}
/**
* Formats the access tokens.
*
* @param array $access_tokens The access tokens to format.
*
* @return \YoastSEO_Vendor\League\OAuth2\Client\Token\AccessTokenInterface[] The formatted access tokens.
*/
protected function format_access_tokens( $access_tokens ) {
if ( ! \is_array( $access_tokens ) || $access_tokens === [] ) {
return [];
}
$formatted_access_tokens = [];
foreach ( $access_tokens as $user_id => $access_token ) {
$formatted_access_tokens[ $user_id ] = new AccessToken( $access_token );
}
return $formatted_access_tokens;
}
/**
* Retrieves the myyoast oauth value from the options.
*
* @codeCoverageIgnore
*
* @return array
*/
protected function get_option() {
$option_value = WPSEO_Options::get( 'myyoast_oauth', false );
if ( $option_value ) {
return \wp_parse_args(
\json_decode( $option_value, true ),
$this->get_default_option()
);
}
return $this->get_default_option();
}
/**
* Exports the settings to the options.
*
* @codeCoverageIgnore
*
* @return void
*/
protected function update_option() {
WPSEO_Options::set(
'myyoast_oauth',
WPSEO_Utils::format_json_encode(
[
'config' => $this->config,
'access_tokens' => $this->access_tokens,
]
)
);
}
/**
* Retrieves the default option value.
*
* @codeCoverageIgnore
*
* @return array The default option value.
*/
protected function get_default_option() {
return [
'config' => [
'clientId' => null,
'secret' => null,
],
'access_tokens' => [],
];
}
}

View File

@@ -0,0 +1,709 @@
<?php
/**
* Yoast extension of the Model class.
*
* @package Yoast\YoastSEO\ORM
*/
namespace Yoast\WP\SEO\ORM;
use Yoast\WP\SEO\Exceptions\Missing_Method;
/**
* Make Model compatible with WordPress.
*
* Model base class. Your model objects should extend
* this class. A minimal subclass would look like:
*
* class Widget extends Model {
* }
*
* The methods documented below are magic methods that conform to PSR-1.
* This documentation exposes these methods to doc generators and IDEs.
*
* @link http://www.php-fig.org/psr/psr-1/
*
* @method void setOrm($orm)
* @method $this setExpr($property, $value = null)
* @method bool isDirty($property)
* @method bool isNew()
* @method Array asArray()
*/
class Yoast_Model {
/**
* Default ID column for all models. Can be overridden by adding
* a public static $id_column property to your model classes.
*
* @var string
*/
const DEFAULT_ID_COLUMN = 'id';
/**
* Default foreign key suffix used by relationship methods.
*
* @var string
*/
const DEFAULT_FOREIGN_KEY_SUFFIX = '_id';
/**
* Set a prefix for model names. This can be a namespace or any other
* abitrary prefix such as the PEAR naming convention.
*
* @example Model::$auto_prefix_models = 'MyProject_MyModels_'; //PEAR
* @example Model::$auto_prefix_models = '\MyProject\MyModels\'; //Namespaces
*
* @var string $auto_prefix_models
*/
public static $auto_prefix_models = '\Yoast\WP\SEO\Models\\';
/**
* Set a logger to use for all models.
*
* @var \YoastSEO_Vendor\Psr\Log\LoggerInterface $logger
*/
public static $logger;
/**
* Set true to to ignore namespace information when computing table names
* from class names.
*
* @example Model::$short_table_names = true;
* @example Model::$short_table_names = false; // default
*
* @var bool $short_table_names
*/
public static $short_table_names = false;
/**
* The ORM instance used by this model instance to communicate with the database.
*
* @var \YoastSEO_Vendor\ORM $orm
*/
public $orm;
/**
* The table name for the implemented Model.
*
* @var string
*/
public static $table;
/**
* Whether or not this model uses timestamps.
*
* @var bool
*/
protected $uses_timestamps = false;
/**
* Hacks around the Model to provide WordPress prefix to tables.
*
* @param string $class_name Type of Model to load.
* @param bool $yoast_prefix Optional. True to prefix the table name with the Yoast prefix.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper Wrapper to use.
*/
public static function of_type( $class_name, $yoast_prefix = true ) {
// Prepend namespace to the class name.
$class = static::$auto_prefix_models . $class_name;
// Set the class variable to the custom value based on the WPDB prefix.
$class::$table = static::get_table_name( $class_name, $yoast_prefix );
return static::factory( $class_name, null );
}
/**
* Creates a model without the Yoast prefix.
*
* @param string $class_name Type of Model to load.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper
*/
public static function of_wp_type( $class_name ) {
return static::of_type( $class_name, false );
}
/**
* Exposes method to get the table name to use.
*
* @param string $table_name Simple table name.
* @param bool $yoast_prefix Optional. True to prefix the table name with the Yoast prefix.
*
* @return string Prepared full table name.
*/
public static function get_table_name( $table_name, $yoast_prefix = true ) {
global $wpdb;
// Allow the use of WordPress internal tables.
if ( $yoast_prefix ) {
$table_name = 'yoast_' . $table_name;
}
return $wpdb->prefix . \strtolower( $table_name );
}
/**
* Sets the table name for the given class name.
*
* @param string $class_name The class to set the table name for.
*
* @return void
*/
protected function set_table_name( $class_name ) {
// Prepend namespace to the class name.
$class = static::$auto_prefix_models . $class_name;
$class::$table = static::get_table_name( $class_name );
}
/**
* Retrieve the value of a static property on a class. If the
* class or the property does not exist, returns the default
* value supplied as the third argument (which defaults to null).
*
* @param string $class_name The target class name.
* @param string $property The property to get the value for.
* @param null|string $default Default value when property does not exist.
*
* @return string The value of the property.
*/
protected static function get_static_property( $class_name, $property, $default = null ) {
if ( ! \class_exists( $class_name ) || ! \property_exists( $class_name, $property ) ) {
return $default;
}
$properties = \get_class_vars( $class_name );
return $properties[ $property ];
}
/**
* Static method to get a table name given a class name.
* If the supplied class has a public static property
* named $table, the value of this property will be
* returned.
*
* If not, the class name will be converted using
* the class_name_to_table_name() method.
*
* If Model::$short_table_names == true or public static
* property $table_use_short_name == true then $class_name passed
* to class_name_to_table_name() is stripped of namespace information.
*
* @param string $class_name The class name to get the table name for.
*
* @return string The table name.
*/
protected static function get_table_name_for_class( $class_name ) {
$specified_table_name = static::get_static_property( $class_name, 'table' );
$use_short_class_name = static::use_short_table_name( $class_name );
if ( $use_short_class_name ) {
$exploded_class_name = \explode( '\\', $class_name );
$class_name = \end( $exploded_class_name );
}
if ( $specified_table_name === null ) {
return static::class_name_to_table_name( $class_name );
}
return $specified_table_name;
}
/**
* Should short table names, disregarding class namespaces, be computed?
*
* $class_property overrides $global_option, unless $class_property is null.
*
* @param string $class_name The class name to get short name for.
*
* @return bool True when short table name should be used.
*/
protected static function use_short_table_name( $class_name ) {
$class_property = static::get_static_property( $class_name, 'table_use_short_name' );
if ( $class_property === null ) {
return static::$short_table_names;
}
return $class_property;
}
/**
* Convert a namespace to the standard PEAR underscore format.
*
* Then convert a class name in CapWords to a table name in
* lowercase_with_underscores.
*
* Finally strip doubled up underscores.
*
* For example, CarTyre would be converted to car_tyre. And
* Project\Models\CarTyre would be project_models_car_tyre.
*
* @param string $class_name The class name to get the table name for.
*
* @return string The table name.
*/
protected static function class_name_to_table_name( $class_name ) {
$find = [
'/\\\\/',
'/(?<=[a-z])([A-Z])/',
'/__/',
];
$replacements = [
'_',
'_$1',
'_',
];
$class_name = \ltrim( $class_name, '\\' );
$class_name = \preg_replace( $find, $replacements, $class_name );
return \strtolower( $class_name );
}
/**
* Return the ID column name to use for this class. If it is
* not set on the class, returns null.
*
* @param string $class_name The class name to get the ID column for.
*
* @return string|null The ID column name.
*/
protected static function get_id_column_name( $class_name ) {
return static::get_static_property( $class_name, 'id_column', static::DEFAULT_ID_COLUMN );
}
/**
* Build a foreign key based on a table name. If the first argument
* (the specified foreign key column name) is null, returns the second
* argument (the name of the table) with the default foreign key column
* suffix appended.
*
* @param string $specified_foreign_key_name The keyname to build.
* @param string $table_name The table name to build the key name for.
*
* @return string The built foreign key name.
*/
protected static function build_foreign_key_name( $specified_foreign_key_name, $table_name ) {
if ( $specified_foreign_key_name !== null ) {
return $specified_foreign_key_name;
}
return $table_name . static::DEFAULT_FOREIGN_KEY_SUFFIX;
}
/**
* Factory method used to acquire instances of the given class.
* The class name should be supplied as a string, and the class
* should already have been loaded by PHP (or a suitable autoloader
* should exist). This method actually returns a wrapped ORM object
* which allows a database query to be built. The wrapped ORM object is
* responsible for returning instances of the correct class when
* its find_one or find_many methods are called.
*
* @param string $class_name The target class name.
* @param null|string $connection_name The name of the connection.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper Instance of the ORM wrapper.
*/
public static function factory( $class_name, $connection_name = null ) {
$class_name = static::$auto_prefix_models . $class_name;
$table_name = static::get_table_name_for_class( $class_name );
if ( $connection_name === null ) {
$connection_name = static::get_static_property( $class_name, '_connection_name', ORMWrapper::DEFAULT_CONNECTION );
}
$wrapper = ORMWrapper::for_table( $table_name, $connection_name );
$wrapper->set_class_name( $class_name );
$wrapper->use_id_column( static::get_id_column_name( $class_name ) );
return $wrapper;
}
/**
* Internal method to construct the queries for both the has_one and
* has_many methods. These two types of association are identical; the
* only difference is whether find_one or find_many is used to complete
* the method chain.
*
* @param string $associated_class_name The associated class name.
* @param null|string $foreign_key_name The foreign key name in the associated table.
* @param null|string $foreign_key_name_in_current_models_table The foreign key in the current models table.
* @param null|string $connection_name The name of the connection.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper
* @throws \Exception When ID of current model has a null value.
*/
protected function has_one_or_many( $associated_class_name, $foreign_key_name = null, $foreign_key_name_in_current_models_table = null, $connection_name = null ) {
$base_table_name = static::get_table_name_for_class( \get_class( $this ) );
$foreign_key_name = static::build_foreign_key_name( $foreign_key_name, $base_table_name );
/*
* Value of foreign_table.{$foreign_key_name} we're looking for. Where foreign_table is the actual
* database table in the associated model.
*/
if ( $foreign_key_name_in_current_models_table === null ) {
// Matches foreign_table.{$foreign_key_name} with the value of "{$this->table}.{$this->id()}".
$where_value = $this->id();
}
else {
// Matches foreign_table.{$foreign_key_name} with "{$this->table}.{$foreign_key_name_in_current_models_table}".
$where_value = $this->{$foreign_key_name_in_current_models_table};
}
return static::factory( $associated_class_name, $connection_name )->where( $foreign_key_name, $where_value );
}
/**
* Helper method to manage one-to-one relations where the foreign
* key is on the associated table.
*
* @param string $associated_class_name The associated class name.
* @param null|string $foreign_key_name The foreign key name in the associated table.
* @param null|string $foreign_key_name_in_current_models_table The foreign key in the current models table.
* @param null|string $connection_name The name of the connection.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper Instance of the ORM.
* @throws \Exception When ID of current model has a null value.
*/
protected function has_one( $associated_class_name, $foreign_key_name = null, $foreign_key_name_in_current_models_table = null, $connection_name = null ) {
return $this->has_one_or_many( $associated_class_name, $foreign_key_name, $foreign_key_name_in_current_models_table, $connection_name );
}
/**
* Helper method to manage one-to-many relations where the foreign
* key is on the associated table.
*
* @param string $associated_class_name The associated class name.
* @param null|string $foreign_key_name The foreign key name in the associated table.
* @param null|string $foreign_key_name_in_current_models_table The foreign key in the current models table.
* @param null|string $connection_name The name of the connection.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper Instance of the ORM.
* @throws \Exception When ID has a null value.
*/
protected function has_many( $associated_class_name, $foreign_key_name = null, $foreign_key_name_in_current_models_table = null, $connection_name = null ) {
$this->set_table_name( $associated_class_name );
return $this->has_one_or_many( $associated_class_name, $foreign_key_name, $foreign_key_name_in_current_models_table, $connection_name );
}
/**
* Helper method to manage one-to-one and one-to-many relations where
* the foreign key is on the base table.
*
* @param string $associated_class_name The associated class name.
* @param null|string $foreign_key_name The foreign key in the current models table.
* @param null|string $foreign_key_name_in_associated_models_table The foreign key in the associated table.
* @param null|string $connection_name The name of the connection.
*
* @return $this|null Instance of the foreign model.
*/
protected function belongs_to( $associated_class_name, $foreign_key_name = null, $foreign_key_name_in_associated_models_table = null, $connection_name = null ) {
$this->set_table_name( $associated_class_name );
$associated_table_name = static::get_table_name_for_class( static::$auto_prefix_models . $associated_class_name );
$foreign_key_name = static::build_foreign_key_name( $foreign_key_name, $associated_table_name );
$associated_object_id = $this->{$foreign_key_name};
$desired_record = null;
if ( $foreign_key_name_in_associated_models_table === null ) {
/*
* Comparison: "{$associated_table_name}.primary_key = {$associated_object_id}".
*
* NOTE: primary_key is a placeholder for the actual primary key column's name in $associated_table_name.
*/
return static::factory( $associated_class_name, $connection_name )->where_id_is( $associated_object_id );
}
// Comparison: "{$associated_table_name}.{$foreign_key_name_in_associated_models_table} = {$associated_object_id}".
return static::factory( $associated_class_name, $connection_name )->where( $foreign_key_name_in_associated_models_table, $associated_object_id );
}
/**
* Helper method to manage many-to-many relationships via an intermediate model. See
* README for a full explanation of the parameters.
*
* @param string $associated_class_name The associated class name.
* @param null|string $join_class_name The class name to join.
* @param null|string $key_to_base_table The key to the the current models table.
* @param null|string $key_to_associated_table The key to the associated table.
* @param null|string $key_in_base_table The key in the current models table.
* @param null|string $key_in_associated_table The key in the associated table.
* @param null|string $connection_name The name of the connection.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper Instance of the ORM.
*/
protected function has_many_through( $associated_class_name, $join_class_name = null, $key_to_base_table = null, $key_to_associated_table = null, $key_in_base_table = null, $key_in_associated_table = null, $connection_name = null ) {
$base_class_name = \get_class( $this );
/*
* The class name of the join model, if not supplied, is formed by
* concatenating the names of the base class and the associated class,
* in alphabetical order.
*/
if ( $join_class_name === null ) {
$base_model = \explode( '\\', $base_class_name );
$base_model_name = \end( $base_model );
if ( \strpos( $base_model_name, static::$auto_prefix_models ) === 0 ) {
$base_model_name = \substr( $base_model_name, \strlen( static::$auto_prefix_models ), \strlen( $base_model_name ) );
}
// Paris wasn't checking the name settings for the associated class.
$associated_model = \explode( '\\', $associated_class_name );
$associated_model_name = \end( $associated_model );
if ( \strpos( $associated_model_name, static::$auto_prefix_models ) === 0 ) {
$associated_model_name = \substr( $associated_model_name, \strlen( static::$auto_prefix_models ), \strlen( $associated_model_name ) );
}
$class_names = [ $base_model_name, $associated_model_name ];
\sort( $class_names, \SORT_STRING );
$join_class_name = \implode( '', $class_names );
}
// Get table names for each class.
$base_table_name = static::get_table_name_for_class( $base_class_name );
$associated_table_name = static::get_table_name_for_class( static::$auto_prefix_models . $associated_class_name );
$join_table_name = static::get_table_name_for_class( static::$auto_prefix_models . $join_class_name );
// Get ID column names.
$base_table_id_column = ( $key_in_base_table === null ) ? static::get_id_column_name( $base_class_name ) : $key_in_base_table;
$associated_table_id_column = ( $key_in_associated_table === null ) ? static::get_id_column_name( static::$auto_prefix_models . $associated_class_name ) : $key_in_associated_table;
// Get the column names for each side of the join table.
$key_to_base_table = static::build_foreign_key_name( $key_to_base_table, $base_table_name );
$key_to_associated_table = static::build_foreign_key_name( $key_to_associated_table, $associated_table_name );
/*
" SELECT {$associated_table_name}.*
FROM {$associated_table_name} JOIN {$join_table_name}
ON {$associated_table_name}.{$associated_table_id_column} = {$join_table_name}.{$key_to_associated_table}
WHERE {$join_table_name}.{$key_to_base_table} = {$this->$base_table_id_column} ;"
*/
return static::factory( $associated_class_name, $connection_name )
->select( "{$associated_table_name}.*" )
->join(
$join_table_name,
[
"{$associated_table_name}.{$associated_table_id_column}",
'=',
"{$join_table_name}.{$key_to_associated_table}",
]
)
->where( "{$join_table_name}.{$key_to_base_table}", $this->{$base_table_id_column} );
}
/**
* Set the wrapped ORM instance associated with this Model instance.
*
* @param \YoastSEO_Vendor\ORM $orm The ORM instance to set.
*
* @return void
*/
public function set_orm( $orm ) {
$this->orm = $orm;
}
/**
* Magic getter method, allows $model->property access to data.
*
* @param string $property The property to get.
*
* @return null|string The value of the property
*/
public function __get( $property ) {
return $this->orm->get( $property );
}
/**
* Magic setter method, allows $model->property = 'value' access to data.
*
* @param string $property The property to set.
* @param string $value The value to set.
*
* @return void
*/
public function __set( $property, $value ) {
$this->orm->set( $property, $value );
}
/**
* Magic unset method, allows unset($model->property)
*
* @param string $property The property to unset.
*
* @return void
*/
public function __unset( $property ) {
$this->orm->__unset( $property );
}
/**
* Magic isset method, allows isset($model->property) to work correctly.
*
* @param string $property The property to check.
*
* @return bool True when value is set.
*/
public function __isset( $property ) {
return $this->orm->__isset( $property );
}
/**
* Getter method, allows $model->get('property') access to data
*
* @param string $property The property to get.
*
* @return string The value of a property.
*/
public function get( $property ) {
return $this->orm->get( $property );
}
/**
* Setter method, allows $model->set('property', 'value') access to data.
*
* @param string|array $property The property to set.
* @param string|null $value The value to give.
*
* @return static Current object.
*/
public function set( $property, $value = null ) {
$this->orm->set( $property, $value );
return $this;
}
/**
* Setter method, allows $model->set_expr('property', 'value') access to data.
*
* @param string|array $property The property to set.
* @param string|null $value The value to give.
*
* @return static Current object.
*/
public function set_expr( $property, $value = null ) {
$this->orm->set_expr( $property, $value );
return $this;
}
/**
* Check whether the given property has changed since the object was created or saved.
*
* @param string $property The property to check.
*
* @return bool True when field is changed.
*/
public function is_dirty( $property ) {
return $this->orm->is_dirty( $property );
}
/**
* Check whether the model was the result of a call to create() or not.
*
* @return bool True when is new.
*/
public function is_new() {
return $this->orm->is_new();
}
/**
* Wrapper for Idiorm's as_array method.
*
* @return array The models data as array.
*/
public function as_array() {
$args = \func_get_args();
return \call_user_func_array( [ $this->orm, 'as_array' ], $args );
}
/**
* Save the data associated with this model instance to the database.
*
* @return null Nothing.
*/
public function save() {
if ( $this->uses_timestamps ) {
if ( ! $this->created_at ) {
$this->created_at = \gmdate( 'Y-m-d H:i:s' );
}
$this->updated_at = \gmdate( 'Y-m-d H:i:s' );
}
return $this->orm->save();
}
/**
* Delete the database row associated with this model instance.
*
* @return null Nothing.
*/
public function delete() {
return $this->orm->delete();
}
/**
* Get the database ID of this model instance.
*
* @return int The database ID of the models instance.
* @throws \Exception When the ID is a null value.
*/
public function id() {
return $this->orm->id();
}
/**
* Hydrate this model instance with an associative array of data.
* WARNING: The keys in the array MUST match with columns in the
* corresponding database table. If any keys are supplied which
* do not match up with columns, the database will throw an error.
*
* @param array $data The data to pass to the ORM.
*
* @return void
*/
public function hydrate( $data ) {
$this->orm->hydrate( $data )->force_all_dirty();
}
/**
* Calls static methods directly on the ORMWrapper
*
* @param string $method The method to call.
* @param array $arguments The arguments to use.
*
* @return array Result of the static call.
*/
public static function __callStatic( $method, $arguments ) {
if ( ! \function_exists( 'get_called_class' ) ) {
return [];
}
$model = static::factory( \get_called_class() );
return \call_user_func_array( [ $model, $method ], $arguments );
}
/**
* Magic method to capture calls to undefined class methods.
* In this case we are attempting to convert camel case formatted
* methods into underscore formatted methods.
*
* This allows us to call methods using camel case and remain
* backwards compatible.
*
* @param string $name The method to call.
* @param array $arguments The arguments to use.
*
* @throws \Yoast\WP\SEO\Exceptions\Missing_Method When the method does not exist.
*
* @return bool|\Yoast\WP\SEO\ORMWrapper Result of the call.
*/
public function __call( $name, $arguments ) {
$method = \strtolower( \preg_replace( '/([a-z])([A-Z])/', '$1_$2', $name ) );
if ( ! \method_exists( $this, $method ) ) {
throw Missing_Method::for_class( \get_class( $this ), $name );
}
return \call_user_func_array( [ $this, $method ], $arguments );
}
}

View File

@@ -0,0 +1,165 @@
<?php
/**
* Yoast extension of the ORM class.
*
* @package Yoast\YoastSEO
*/
namespace Yoast\WP\SEO\ORM;
use YoastSEO_Vendor\ORM;
/**
* Subclass of Idiorm's ORM class that supports
* returning instances of a specified class rather
* than raw instances of the ORM class.
*
* You shouldn't need to interact with this class
* directly. It is used internally by the Model base
* class.
*
* The methods documented below are magic methods that conform to PSR-1.
* This documentation exposes these methods to doc generators and IDEs.
*
* @link http://www.php-fig.org/psr/psr-1/
*
* @method void setClassName($class_name)
* @method static \Yoast\WP\SEO\ORM\ORMWrapper forTable($table_name, $connection_name = parent::DEFAULT_CONNECTION)
* @method \Yoast\WP\SEO\ORM\\Model findOne($id=null)
* @method Array|\IdiormResultSet findMany()
*/
class ORMWrapper extends ORM {
/**
* Contains the repositories.
*
* @var array
*/
public static $repositories = [];
/**
* The wrapped find_one and find_many classes will return an instance or
* instances of this class.
*
* @var string
*/
protected $class_name;
/**
* Set the name of the class which the wrapped methods should return
* instances of.
*
* @param string $class_name The classname to set.
*
* @return void
*/
public function set_class_name( $class_name ) {
$this->class_name = $class_name;
}
/**
* Add a custom filter to the method chain specified on the model class.
* This allows custom queries to be added to models. The filter should take
* an instance of the ORM wrapper as its first argument and return an
* instance of the ORM wrapper. Any arguments passed to this method after
* the name of the filter will be passed to the called filter function as
* arguments after the ORM class.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper Instance of the ORM wrapper.
*/
public function filter() {
$args = \func_get_args();
$filter_function = \array_shift( $args );
\array_unshift( $args, $this );
if ( \method_exists( $this->class_name, $filter_function ) ) {
return \call_user_func_array( [ $this->class_name, $filter_function ], $args );
}
return null;
}
/**
* Factory method, return an instance of this class bound to the supplied
* table name.
*
* A repeat of content in parent::for_table, so that created class is
* ORMWrapper, not ORM.
*
* @param string $table_name The table to create instance for.
* @param string $connection_name The connection name.
*
* @return \Yoast\WP\SEO\ORM\ORMWrapper Instance of the ORM wrapper.
*/
public static function for_table( $table_name, $connection_name = parent::DEFAULT_CONNECTION ) {
static::_setup_db( $connection_name );
if ( self::$repositories[ $table_name ] ) {
return new self::$repositories[ $table_name ]( $table_name, [], $connection_name );
}
return new static( $table_name, [], $connection_name );
}
/**
* Method to create an instance of the model class associated with this
* wrapper and populate it with the supplied Idiorm instance.
*
* @param \Yoast\WP\SEO\ORM\ORMWrapper|\YoastSEO_Vendor\ORM $orm The ORM used by model.
*
* @return bool|\Yoast\WP\SEO\ORM\Yoast_Model Instance of the model class.
*/
protected function create_model_instance( $orm ) {
if ( $orm === false ) {
return false;
}
/**
* An instance of Yoast_Model is being made.
*
* @var \Yoast\WP\SEO\ORM\Yoast_Model $model
*/
$model = new $this->class_name();
$model->set_orm( $orm );
return $model;
}
/**
* Wrap Idiorm's find_one method to return an instance of the class
* associated with this wrapper instead of the raw ORM class.
*
* @param null|integer $id The ID to lookup.
*
* @return \Yoast\WP\SEO\ORM\Yoast_Model Instance of the model.
*/
public function find_one( $id = null ) {
return $this->create_model_instance( parent::find_one( $id ) );
}
/**
* Wrap Idiorm's find_many method to return an array of instances of the
* class associated with this wrapper instead of the raw ORM class.
*
* @return array The found results.
*/
public function find_many() {
$results = parent::find_many();
foreach ( $results as $key => $result ) {
$results[ $key ] = $this->create_model_instance( $result );
}
return $results;
}
/**
* Wrap Idiorm's create method to return an empty instance of the class
* associated with this wrapper instead of the raw ORM class.
*
* @param null|mixed $data The data to pass.
*
* @return \Yoast\WP\SEO\ORM\Yoast_Model|bool Instance of the ORM.
*/
public function create( $data = null ) {
return $this->create_model_instance( parent::create( $data ) );
}
}

View File

@@ -0,0 +1,182 @@
<?php
/**
* Yoast extension of the Model class.
*
* @package Yoast\YoastSEO\ORM\Repositories
*/
namespace Yoast\WP\SEO\Repositories;
use Yoast\WP\SEO\Builders\Indexable_Author_Builder;
use Yoast\WP\SEO\Builders\Indexable_Post_Builder;
use Yoast\WP\SEO\Builders\Indexable_Term_Builder;
use Yoast\WP\SEO\Loggers\Logger;
use Yoast\WP\SEO\ORM\ORMWrapper;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Class Indexable_Repository
*
* @package Yoast\WP\SEO\ORM\Repositories
*/
class Indexable_Repository extends ORMWrapper {
/**
* @var \Yoast\WP\SEO\Builders\Indexable_Author_Builder
*/
protected $author_builder;
/**
* @var \Yoast\WP\SEO\Builders\Indexable_Post_Builder
*/
protected $post_builder;
/**
* @var \Yoast\WP\SEO\Builders\Indexable_Term_Builder
*/
protected $term_builder;
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Returns the instance of this class constructed through the ORM Wrapper.
*
* @param \Yoast\WP\SEO\Builders\Indexable_Author_Builder $author_builder The author builder for creating missing indexables.
* @param \Yoast\WP\SEO\Builders\Indexable_Post_Builder $post_builder The post builder for creating missing indexables.
* @param \Yoast\WP\SEO\Builders\Indexable_Term_Builder $term_builder The term builder for creating missing indexables.
* @param \Yoast\WP\SEO\Loggers\Logger $logger The logger.
*
* @return \Yoast\WP\SEO\Repositories\Indexable_Repository
*/
public static function get_instance(
Indexable_Author_Builder $author_builder,
Indexable_Post_Builder $post_builder,
Indexable_Term_Builder $term_builder,
Logger $logger
) {
ORMWrapper::$repositories[ Yoast_Model::get_table_name( 'Indexable' ) ] = self::class;
/**
* @var $instance self
*/
$instance = Yoast_Model::of_type( 'Indexable' );
$instance->author_builder = $author_builder;
$instance->post_builder = $post_builder;
$instance->term_builder = $term_builder;
$instance->logger = $logger;
return $instance;
}
/**
* Retrieves an indexable by it's URL.
*
* @param string $url The indexable url.
*/
public function find_by_url( $url ) {
$url = \trailingslashit( $url );
$url_hash = \strlen( $url ) . ':' . \md5( $url );
// Find by both url_hash and url, url_hash is indexed so will be used first by the DB to optimize the query.
return $this->where( 'url_hash', $url_hash )
->where( 'url', $url )
->find_one();
}
/**
* Retrieves an indexable by its ID and type.
*
* @param int $object_id The indexable object ID.
* @param string $object_type The indexable object type.
* @param bool $auto_create Optional. Create the indexable if it does not exist.
*
* @return bool|\Yoast\WP\SEO\Models\Indexable Instance of indexable.
*/
public function find_by_id_and_type( $object_id, $object_type, $auto_create = true ) {
$indexable = $this->where( 'object_id', $object_id )
->where( 'object_type', $object_type )
->find_one();
if ( $auto_create && ! $indexable ) {
$indexable = $this->create_for_id_and_type( $object_id, $object_type );
}
return $indexable;
}
/**
* Retrieves multiple indexables at once by their IDs and type.
*
* @param int[] $object_ids The array of indexable object IDs.
* @param string $object_type The indexable object type.
* @param bool $auto_create Optional. Create the indexable if it does not exist.
*
* @return \Yoast\WP\SEO\Models\Indexable[] An array of indexables.
*/
public function find_by_multiple_ids_and_type( $object_ids, $object_type, $auto_create = true ) {
$indexables = $this
->where_in( 'object_id', $object_ids )
->where( 'object_type', $object_type )
->find_many();
if ( $auto_create ) {
$indexables_available = \array_column( $indexables, 'object_id' );
$indexables_to_create = \array_diff( $object_ids, $indexables_available );
foreach ( $indexables_to_create as $indexable_to_create ) {
$indexable = $this->create_for_id_and_type( $indexable_to_create, $object_type );
$indexable->save();
$indexables[] = $indexable;
}
}
return $indexables;
}
/**
* Creates an indexable by its ID and type.
*
* @param int $object_id The indexable object ID.
* @param string $object_type The indexable object type.
*
* @return bool|\Yoast\WP\SEO\Models\Indexable Instance of indexable.
*/
public function create_for_id_and_type( $object_id, $object_type ) {
/**
* Indexable instance.
*
* @var \Yoast\WP\SEO\Models\Indexable $indexable
*/
$indexable = $this->create();
$indexable->object_id = $object_id;
$indexable->object_type = $object_type;
switch ( $object_type ) {
case 'post':
$indexable = $this->post_builder->build( $object_id, $indexable );
break;
case 'user':
$indexable = $this->author_builder->build( $object_id, $indexable );
break;
case 'term':
$indexable = $this->term_builder->build( $object_id, $indexable );
break;
}
$this->logger->debug(
\sprintf(
/* translators: 1: object ID; 2: object type. */
\__( 'Indexable created for object %1$s with type %2$s', 'wordpress-seo' ),
$object_id,
$object_type
),
\get_object_vars( $indexable )
);
return $indexable;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Yoast extension of the Model class.
*
* @package Yoast\YoastSEO\ORM\Repositories
*/
namespace Yoast\WP\SEO\Repositories;
use Yoast\WP\SEO\ORM\ORMWrapper;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Class Primary_Term_Repository
*
* @package Yoast\WP\SEO\ORM\Repositories
*/
class Primary_Term_Repository extends ORMWrapper {
/**
* Returns the instance of this class constructed through the ORM Wrapper.
*
* @return \Yoast\WP\SEO\Repositories\Primary_Term_Repository
*/
public static function get_instance() {
ORMWrapper::$repositories[ Yoast_Model::get_table_name( 'Primary_Term' ) ] = self::class;
return Yoast_Model::of_type( 'Primary_Term' );
}
/**
* Retrieves an indexable by a post ID and taxonomy.
*
* @param int $post_id The post the indexable is based upon.
* @param string $taxonomy The taxonomy the indexable belongs to.
* @param bool $auto_create Optional. Creates an indexable if it does not exist yet.
*
* @return bool|\Yoast\WP\SEO\Models\Indexable Instance of indexable.
*/
public function find_by_postid_and_taxonomy( $post_id, $taxonomy, $auto_create = true ) {
/** @var \Yoast\WP\SEO\Models\Primary_Term $primary_term */
$primary_term = $this->where( 'post_id', $post_id )
->where( 'taxonomy', $taxonomy )
->find_one();
if ( $auto_create && ! $primary_term ) {
$primary_term = $this->create();
}
return $primary_term;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Yoast extension of the Model class.
*
* @package Yoast\YoastSEO\ORM\Repositories
*/
namespace Yoast\WP\SEO\Repositories;
use Yoast\WP\SEO\ORM\ORMWrapper;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Class SEO_Links_Repository
*
* WARNING: This class merely exists for type hints and dependency injection.
* Instances of this class will actually be instances of ORMWrapper and any functions and/or methods here will not be represented.
*
* @package Yoast\WP\SEO\ORM\Repositories
*/
class SEO_Links_Repository extends ORMWrapper {
/**
* Returns the instance of this class constructed through the ORM Wrapper.
*
* @return \Yoast\WP\SEO\Repositories\SEO_Links_Repository
*/
public static function get_instance() {
ORMWrapper::$repositories[ Yoast_Model::get_table_name( 'SEO_Links' ) ] = self::class;
return Yoast_Model::of_type( 'SEO_Links' );
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Yoast extension of the Model class.
*
* @package Yoast\YoastSEO\ORM\Repositories
*/
namespace Yoast\WP\SEO\Repositories;
use Yoast\WP\SEO\ORM\ORMWrapper;
use Yoast\WP\SEO\ORM\Yoast_Model;
/**
* Class SEO_Meta_Repository
*
* @package Yoast\WP\SEO\ORM\Repositories
*/
class SEO_Meta_Repository extends ORMWrapper {
/**
* Returns the instance of this class constructed through the ORM Wrapper.
*
* @return \Yoast\WP\SEO\Repositories\SEO_Meta_Repository
*/
public static function get_instance() {
ORMWrapper::$repositories[ Yoast_Model::get_table_name( 'SEO_Meta' ) ] = self::class;
return Yoast_Model::of_type( 'SEO_Meta' );
}
/**
* Finds the SEO meta for given post.
*
* @param int $post_id The post ID.
*
* @return \Yoast\WP\SEO\Models\SEO_Meta The SEO meta.
*/
public function find_by_post_id( $post_id ) {
return $this->where( 'object_id', $post_id )
->find_one();
}
}

View File

@@ -0,0 +1,88 @@
<?php
/**
* Author watcher to save the meta data to an Indexable.
*
* @package Yoast\YoastSEO\Watchers
*/
namespace Yoast\WP\SEO\Watchers;
use Yoast\WP\SEO\Conditionals\Indexables_Feature_Flag_Conditional;
use Yoast\WP\SEO\Builders\Indexable_Author_Builder;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast\WP\SEO\WordPress\Integration;
/**
* Watches an Author to save the meta information when updated.
*/
class Indexable_Author_Watcher implements Integration {
/**
* @var \Yoast\WP\SEO\Repositories\Indexable_Repository
*/
protected $repository;
/**
* @var \Yoast\WP\SEO\Builders\Indexable_Author_Builder
*/
protected $builder;
/**
* @inheritdoc
*/
public static function get_conditionals() {
return [ Indexables_Feature_Flag_Conditional::class ];
}
/**
* Indexable_Author_Watcher constructor.
*
* @param \Yoast\WP\SEO\Repositories\Indexable_Repository $repository The repository to use.
* @param \Yoast\WP\SEO\Builders\Indexable_Author_Builder $builder The post builder to use.
*/
public function __construct( Indexable_Repository $repository, Indexable_Author_Builder $builder ) {
$this->repository = $repository;
$this->builder = $builder;
}
/**
* @inheritdoc
*/
public function register_hooks() {
\add_action( 'profile_update', [ $this, 'build_indexable' ], \PHP_INT_MAX );
\add_action( 'deleted_user', [ $this, 'delete_indexable' ] );
}
/**
* Deletes user meta.
*
* @param int $user_id User ID to delete the metadata of.
*
* @return void
*/
public function delete_indexable( $user_id ) {
$indexable = $this->repository->find_by_id_and_type( $user_id, 'user', false );
if ( ! $indexable ) {
return;
}
$indexable->delete();
}
/**
* Saves user meta.
*
* @param int $user_id User ID.
*
* @return void
*/
public function build_indexable( $user_id ) {
$indexable = $this->repository->find_by_id_and_type( $user_id, 'user', false );
// If we haven't found an existing indexable, create it. Otherwise update it.
$indexable = ( $indexable === false ) ? $this->repository->create_for_id_and_type( $user_id, 'user' ) : $this->builder->build( $user_id, $indexable );
$indexable->save();
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* WordPress Post watcher.
*
* @package Yoast\YoastSEO\Watchers
*/
namespace Yoast\WP\SEO\Watchers;
use Yoast\WP\SEO\Conditionals\Indexables_Feature_Flag_Conditional;
use Yoast\WP\SEO\Builders\Indexable_Post_Builder;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast\WP\SEO\WordPress\Integration;
/**
* Fills the Indexable according to Post data.
*/
class Indexable_Post_Watcher implements Integration {
/**
* @var \Yoast\WP\SEO\Repositories\Indexable_Repository
*/
protected $repository;
/**
* @var \Yoast\WP\SEO\Builders\Indexable_Post_Builder
*/
protected $builder;
/**
* @inheritdoc
*/
public static function get_conditionals() {
return [ Indexables_Feature_Flag_Conditional::class ];
}
/**
* Indexable_Post_Watcher constructor.
*
* @param \Yoast\WP\SEO\Repositories\Indexable_Repository $repository The repository to use.
* @param \Yoast\WP\SEO\Builders\Indexable_Post_Builder $builder The post builder to use.
*/
public function __construct( Indexable_Repository $repository, Indexable_Post_Builder $builder ) {
$this->repository = $repository;
$this->builder = $builder;
}
/**
* @inheritdoc
*/
public function register_hooks() {
\add_action( 'wp_insert_post', [ $this, 'build_indexable' ], \PHP_INT_MAX );
\add_action( 'delete_post', [ $this, 'delete_indexable' ] );
}
/**
* Deletes the meta when a post is deleted.
*
* @param int $post_id Post ID.
*
* @return void
*/
public function delete_indexable( $post_id ) {
$indexable = $this->repository->find_by_id_and_type( $post_id, 'post', false );
if ( ! $indexable ) {
return;
}
$indexable->delete();
}
/**
* Saves post meta.
*
* @param int $post_id Post ID.
*
* @return void
*/
public function build_indexable( $post_id ) {
if ( ! $this->is_post_indexable( $post_id ) ) {
return;
}
$indexable = $this->repository->find_by_id_and_type( $post_id, 'post', false );
// If we haven't found an existing indexable, create it. Otherwise update it.
$indexable = ( $indexable === false ) ? $this->repository->create_for_id_and_type( $post_id, 'post' ) : $this->builder->build( $post_id, $indexable );
$indexable->save();
}
/**
* Determines if the post can be indexed.
*
* @param int $post_id Post ID to check.
*
* @return bool True if the post can be indexed.
*/
protected function is_post_indexable( $post_id ) {
if ( \wp_is_post_revision( $post_id ) ) {
return false;
}
if ( \wp_is_post_autosave( $post_id ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* Term/Taxonomy watcher to fill the related Indexable.
*
* @package Yoast\YoastSEO\Watchers
*/
namespace Yoast\WP\SEO\Watchers;
use Yoast\WP\SEO\Conditionals\Indexables_Feature_Flag_Conditional;
use Yoast\WP\SEO\Builders\Indexable_Term_Builder;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast\WP\SEO\WordPress\Integration;
/**
* Watcher for terms to fill the related Indexable.
*/
class Indexable_Term_Watcher implements Integration {
/**
* @var \Yoast\WP\SEO\Repositories\Indexable_Repository
*/
protected $repository;
/**
* @var \Yoast\WP\SEO\Builders\Indexable_Term_Builder
*/
protected $builder;
/**
* @inheritdoc
*/
public static function get_conditionals() {
return [ Indexables_Feature_Flag_Conditional::class ];
}
/**
* Indexable_Term_Watcher constructor.
*
* @param \Yoast\WP\SEO\Repositories\Indexable_Repository $repository The repository to use.
* @param \Yoast\WP\SEO\Builders\Indexable_Term_Builder $builder The post builder to use.
*/
public function __construct( Indexable_Repository $repository, Indexable_Term_Builder $builder ) {
$this->repository = $repository;
$this->builder = $builder;
}
/**
* @inheritdoc
*/
public function register_hooks() {
\add_action( 'edited_term', [ $this, 'build_indexable' ], \PHP_INT_MAX );
\add_action( 'delete_term', [ $this, 'delete_indexable' ], \PHP_INT_MAX );
}
/**
* Deletes a term from the index.
*
* @param int $term_id The Term ID to delete.
*
* @return void
*/
public function delete_indexable( $term_id ) {
$indexable = $this->repository->find_by_id_and_type( $term_id, 'term', false );
if ( ! $indexable ) {
return;
}
$indexable->delete();
}
/**
* Update the taxonomy meta data on save.
*
* Note: This method is missing functionality to update internal links and incoming links.
* As this functionality is currently not available for terms, it has not been added in this
* class yet.
*
* @param int $term_id ID of the term to save data for.
*
* @return void
*/
public function build_indexable( $term_id ) {
$indexable = $this->repository->find_by_id_and_type( $term_id, 'term', false );
// If we haven't found an existing indexable, create it. Otherwise update it.
$indexable = ( $indexable === false ) ? $this->repository->create_for_id_and_type( $term_id, 'term' ) : $this->builder->build( $term_id, $indexable );
$indexable->save();
}
}

View File

@@ -0,0 +1,201 @@
<?php
/**
* Primary Term watcher.
*
* @package Yoast\YoastSEO\Watchers
*/
namespace Yoast\WP\SEO\Watchers;
use WPSEO_Meta;
use Yoast\WP\SEO\Conditionals\Indexables_Feature_Flag_Conditional;
use Yoast\WP\SEO\Repositories\Primary_Term_Repository;
use Yoast\WP\SEO\WordPress\Integration;
/**
* Watches Posts to save the primary term when set.
*/
class Primary_Term_Watcher implements Integration {
/**
* @var \Yoast\WP\SEO\Repositories\Primary_Term_Repository
*/
protected $repository;
/**
* @inheritdoc
*/
public static function get_conditionals() {
return [ Indexables_Feature_Flag_Conditional::class ];
}
/**
* Primary_Term_Watcher constructor.
*
* @param \Yoast\WP\SEO\Repositories\Primary_Term_Repository $repository The primary term repository.
*/
public function __construct( Primary_Term_Repository $repository ) {
$this->repository = $repository;
}
/**
* @inheritdoc
*/
public function register_hooks() {
\add_action( 'save_post', [ $this, 'save_primary_terms' ] );
\add_action( 'delete_post', [ $this, 'delete_primary_terms' ] );
}
/**
* Deletes primary terms for a post.
*
* @param int $post_id The post to delete the terms of.
*
* @return void
*/
public function delete_primary_terms( $post_id ) {
foreach ( $this->get_primary_term_taxonomies( $post_id ) as $taxonomy ) {
$indexable = $this->repository->find_by_postid_and_taxonomy( $post_id, $taxonomy->name, false );
if ( ! $indexable ) {
continue;
}
$indexable->delete();
}
}
/**
* Saves the primary terms for a post.
*
* @param int $post_id Post ID to save the primary terms for.
*
* @return void
*/
public function save_primary_terms( $post_id ) {
if ( ! $this->is_post_request() ) {
return;
}
foreach ( $this->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 ) {
// This request must be valid.
$term_id = $this->get_posted_term_id( $taxonomy );
if ( $term_id && ! $this->is_referer_valid( $taxonomy ) ) {
return;
}
$term_selected = ! empty( $term_id );
$primary_term = $this->repository->find_by_postid_and_taxonomy( $post_id, $taxonomy, $term_selected );
// Removes the indexable when 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->save();
}
/**
* Returns all the taxonomies for which the primary term selection is enabled.
*
* @param int $post_id Default current post ID.
*
* @return array The taxonomies.
*/
protected function get_primary_term_taxonomies( $post_id = null ) {
if ( $post_id === null ) {
$post_id = \get_the_ID();
}
$taxonomies = $this->generate_primary_term_taxonomies( $post_id );
return $taxonomies;
}
/**
* Generate the primary term taxonomies.
*
* @param int $post_id ID of the post.
*
* @return array The taxonomies.
*/
protected function generate_primary_term_taxonomies( $post_id ) {
$post_type = \get_post_type( $post_id );
$all_taxonomies = \get_object_taxonomies( $post_type, 'objects' );
$all_taxonomies = \array_filter( $all_taxonomies, [ $this, 'filter_hierarchical_taxonomies' ] );
/**
* Filters which taxonomies for which the user can choose the primary term.
*
* @api array $taxonomies An array of taxonomy objects that are primary_term enabled.
*
* @param string $post_type The post type for which to filter the taxonomies.
* @param array $all_taxonomies All taxonomies for this post types, even ones that don't have primary term
* enabled.
*/
$taxonomies = (array) \apply_filters( 'wpseo_primary_term_taxonomies', $all_taxonomies, $post_type, $all_taxonomies );
return $taxonomies;
}
/**
* Returns whether or not a taxonomy is hierarchical
*
* @param \stdClass $taxonomy Taxonomy object.
*
* @return bool True for hierarchical taxonomy.
*/
protected function filter_hierarchical_taxonomies( $taxonomy ) {
return (bool) $taxonomy->hierarchical;
}
/**
* Checks if the request is a post request.
*
* @return bool Whether the method is a post request.
*/
protected function is_post_request() {
return isset( $_SERVER['REQUEST_METHOD'] ) && \strtolower( \wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) === 'post';
}
/**
* Retrieves the posted term ID based on the given taxonomy.
*
* @param string $taxonomy The taxonomy to check.
*
* @return int The term ID.
*/
protected function get_posted_term_id( $taxonomy ) {
return \filter_input( \INPUT_POST, WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy . '_term', \FILTER_SANITIZE_NUMBER_INT );
}
/**
* Checks if the referer is valid for given taxonomy.
*
* @param string $taxonomy The taxonomy to validate.
*
* @return bool Whether the referer is valid.
*/
protected function is_referer_valid( $taxonomy ) {
return \check_admin_referer( 'save-primary-term', WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy . '_nonce' );
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* Integration interface definition.
*
* @package Yoast\YoastSEO\WordPress
*/
namespace Yoast\WP\SEO\WordPress;
/**
* An interface for registering integrations with WordPress
*/
interface Initializer extends Loadable {
/**
* Runs this initializer.
*
* @return void
*/
public function initialize();
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* Integration interface definition.
*
* @package Yoast\YoastSEO\WordPress
*/
namespace Yoast\WP\SEO\WordPress;
/**
* An interface for registering integrations with WordPress
*/
interface Integration extends Loadable {
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks();
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* Integration interface definition.
*
* @package Yoast\YoastSEO\WordPress
*/
namespace Yoast\WP\SEO\WordPress;
/**
* An interface for registering integrations with WordPress
*/
interface Loadable {
/**
* Returns the conditionals based in which this loadable should be active.
*
* @return array
*/
public static function get_conditionals();
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Yoast SEO Plugin File.
*
* @package Yoast\YoastSEO\WordPress
*/
namespace Yoast\WP\SEO\WordPress;
/**
* Wrapper class for WordPress globals.
* This consists of factory functions to inject WP globals into the dependency container.
*/
class Wrapper {
/**
* Wrapper method for returning the wpdb object for use in dependency injection.
*
* @return \wpdb The wpdb global.
*/
public static function get_wpdb() {
global $wpdb;
return $wpdb;
}
/**
* Wrapper method for returning the wp_query object for use in dependency injection.
*
* @return \WP_Query The wp_query global.
*/
public static function get_wp_query() {
global $wp_query;
return $wp_query;
}
}