Phase 5: Content and SEO - Yoast SEO, Schema.org markup, Open Graph, favicon support, XML sitemap
This commit is contained in:
wp-content/plugins/wordpress-seo/src/dashboard/application/configuration/dashboard-configuration.php
Executable
+157
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Configuration;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Application\Content_Types\Content_Types_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Endpoints\Endpoints_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Tracking\Setup_Steps_Tracking;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Browser_Cache\Browser_Cache_Configuration;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Nonces\Nonce_Repository;
|
||||
use Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository;
|
||||
use Yoast\WP\SEO\Editors\Framework\Keyphrase_Analysis;
|
||||
use Yoast\WP\SEO\Editors\Framework\Readability_Analysis;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\User_Helper;
|
||||
|
||||
/**
|
||||
* Responsible for the dashboard configuration.
|
||||
*/
|
||||
class Dashboard_Configuration {
|
||||
|
||||
/**
|
||||
* The content types repository.
|
||||
*
|
||||
* @var Content_Types_Repository
|
||||
*/
|
||||
private $content_types_repository;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* The user helper.
|
||||
*
|
||||
* @var User_Helper
|
||||
*/
|
||||
private $user_helper;
|
||||
|
||||
/**
|
||||
* The repository.
|
||||
*
|
||||
* @var Enabled_Analysis_Features_Repository
|
||||
*/
|
||||
private $enabled_analysis_features_repository;
|
||||
|
||||
/**
|
||||
* The endpoints repository.
|
||||
*
|
||||
* @var Endpoints_Repository
|
||||
*/
|
||||
private $endpoints_repository;
|
||||
|
||||
/**
|
||||
* The nonce repository.
|
||||
*
|
||||
* @var Nonce_Repository
|
||||
*/
|
||||
private $nonce_repository;
|
||||
|
||||
/**
|
||||
* The Site Kit integration data.
|
||||
*
|
||||
* @var Site_Kit
|
||||
*/
|
||||
private $site_kit_integration_data;
|
||||
|
||||
/**
|
||||
* The setup steps tracking data.
|
||||
*
|
||||
* @var Setup_Steps_Tracking
|
||||
*/
|
||||
private $setup_steps_tracking;
|
||||
|
||||
/**
|
||||
* The browser cache configuration.
|
||||
*
|
||||
* @var Browser_Cache_Configuration
|
||||
*/
|
||||
private $browser_cache_configuration;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Content_Types_Repository $content_types_repository The content types repository.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper
|
||||
* repository.
|
||||
* @param User_Helper $user_helper The user helper.
|
||||
* @param Enabled_Analysis_Features_Repository $enabled_analysis_features_repository The analysis feature.
|
||||
* repository.
|
||||
* @param Endpoints_Repository $endpoints_repository The endpoints repository.
|
||||
* @param Nonce_Repository $nonce_repository The nonce repository.
|
||||
* @param Site_Kit $site_kit_integration_data The Site Kit integration data.
|
||||
* @param Setup_Steps_Tracking $setup_steps_tracking The setup steps tracking data.
|
||||
* @param Browser_Cache_Configuration $browser_cache_configuration The browser cache configuration.
|
||||
*/
|
||||
public function __construct(
|
||||
Content_Types_Repository $content_types_repository,
|
||||
Indexable_Helper $indexable_helper,
|
||||
User_Helper $user_helper,
|
||||
Enabled_Analysis_Features_Repository $enabled_analysis_features_repository,
|
||||
Endpoints_Repository $endpoints_repository,
|
||||
Nonce_Repository $nonce_repository,
|
||||
Site_Kit $site_kit_integration_data,
|
||||
Setup_Steps_Tracking $setup_steps_tracking,
|
||||
Browser_Cache_Configuration $browser_cache_configuration
|
||||
) {
|
||||
$this->content_types_repository = $content_types_repository;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->user_helper = $user_helper;
|
||||
$this->enabled_analysis_features_repository = $enabled_analysis_features_repository;
|
||||
$this->endpoints_repository = $endpoints_repository;
|
||||
$this->nonce_repository = $nonce_repository;
|
||||
$this->site_kit_integration_data = $site_kit_integration_data;
|
||||
$this->setup_steps_tracking = $setup_steps_tracking;
|
||||
$this->browser_cache_configuration = $browser_cache_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration
|
||||
*
|
||||
* @return array<string, array<string>|array<string, string|array<string, array<string, int>>>>
|
||||
*/
|
||||
public function get_configuration(): array {
|
||||
$configuration = [
|
||||
'contentTypes' => $this->content_types_repository->get_content_types(),
|
||||
'indexablesEnabled' => $this->indexable_helper->should_index_indexables(),
|
||||
'displayName' => $this->user_helper->get_current_user_display_name(),
|
||||
'enabledAnalysisFeatures' => $this->enabled_analysis_features_repository->get_features_by_keys(
|
||||
[
|
||||
Readability_Analysis::NAME,
|
||||
Keyphrase_Analysis::NAME,
|
||||
]
|
||||
)->to_array(),
|
||||
'endpoints' => $this->endpoints_repository->get_all_endpoints()->to_array(),
|
||||
'nonce' => $this->nonce_repository->get_rest_nonce(),
|
||||
'setupStepsTracking' => $this->setup_steps_tracking->to_array(),
|
||||
];
|
||||
|
||||
$site_kit_integration_data = $this->site_kit_integration_data->to_array();
|
||||
if ( ! empty( $site_kit_integration_data ) ) {
|
||||
$configuration ['siteKitConfiguration'] = $site_kit_integration_data;
|
||||
}
|
||||
|
||||
$browser_cache_configuration = $this->browser_cache_configuration->get_configuration();
|
||||
if ( ! empty( $browser_cache_configuration ) ) {
|
||||
$configuration ['browserCache'] = $browser_cache_configuration;
|
||||
}
|
||||
|
||||
return $configuration;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Content_Types;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Application\Taxonomies\Taxonomies_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector;
|
||||
|
||||
/**
|
||||
* The repository to get content types.
|
||||
*/
|
||||
class Content_Types_Repository {
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Content_Types_Collector
|
||||
*/
|
||||
protected $content_types_collector;
|
||||
|
||||
/**
|
||||
* The taxonomies repository.
|
||||
*
|
||||
* @var Taxonomies_Repository
|
||||
*/
|
||||
private $taxonomies_repository;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Content_Types_Collector $content_types_collector The post type helper.
|
||||
* @param Taxonomies_Repository $taxonomies_repository The taxonomies repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Content_Types_Collector $content_types_collector,
|
||||
Taxonomies_Repository $taxonomies_repository
|
||||
) {
|
||||
$this->content_types_collector = $content_types_collector;
|
||||
$this->taxonomies_repository = $taxonomies_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content types array.
|
||||
*
|
||||
* @return array<array<string, array<string, array<string, array<string, string|null>>>>> The content types array.
|
||||
*/
|
||||
public function get_content_types(): array {
|
||||
$content_types_list = $this->content_types_collector->get_content_types();
|
||||
|
||||
foreach ( $content_types_list->get() as $content_type ) {
|
||||
$content_type_taxonomy = $this->taxonomies_repository->get_content_type_taxonomy( $content_type->get_name() );
|
||||
$content_type->set_taxonomy( $content_type_taxonomy );
|
||||
}
|
||||
|
||||
return $content_types_list->to_array();
|
||||
}
|
||||
}
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Endpoints;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_List;
|
||||
|
||||
/**
|
||||
* Repository for endpoints.
|
||||
*/
|
||||
class Endpoints_Repository {
|
||||
|
||||
/**
|
||||
* Holds the endpoints.
|
||||
*
|
||||
* @var array<Endpoint_Interface>
|
||||
*/
|
||||
private $endpoints;
|
||||
|
||||
/**
|
||||
* Constructs the repository.
|
||||
*
|
||||
* @param Endpoint_Interface ...$endpoints The endpoints to add to the repository.
|
||||
*/
|
||||
public function __construct( Endpoint_Interface ...$endpoints ) {
|
||||
$this->endpoints = $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list with all endpoints.
|
||||
*
|
||||
* @return Endpoint_List The list with all endpoints.
|
||||
*/
|
||||
public function get_all_endpoints(): Endpoint_List {
|
||||
$list = new Endpoint_List();
|
||||
foreach ( $this->endpoints as $endpoint ) {
|
||||
$list->add_endpoint( $endpoint );
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Filter_Pairs;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Filter_Pairs\Filter_Pairs_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies\Taxonomies_Collector;
|
||||
|
||||
/**
|
||||
* The repository to get hardcoded filter pairs.
|
||||
*/
|
||||
class Filter_Pairs_Repository {
|
||||
|
||||
/**
|
||||
* The taxonomies collector.
|
||||
*
|
||||
* @var Taxonomies_Collector
|
||||
*/
|
||||
private $taxonomies_collector;
|
||||
|
||||
/**
|
||||
* All filter pairs.
|
||||
*
|
||||
* @var Filter_Pairs_Interface[]
|
||||
*/
|
||||
private $filter_pairs;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Taxonomies_Collector $taxonomies_collector The taxonomies collector.
|
||||
* @param Filter_Pairs_Interface ...$filter_pairs All filter pairs.
|
||||
*/
|
||||
public function __construct(
|
||||
Taxonomies_Collector $taxonomies_collector,
|
||||
Filter_Pairs_Interface ...$filter_pairs
|
||||
) {
|
||||
$this->taxonomies_collector = $taxonomies_collector;
|
||||
$this->filter_pairs = $filter_pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a taxonomy based on a content type, by looking into hardcoded filter pairs.
|
||||
*
|
||||
* @param string $content_type The content type.
|
||||
*
|
||||
* @return Taxonomy|null The taxonomy filter.
|
||||
*/
|
||||
public function get_taxonomy( string $content_type ): ?Taxonomy {
|
||||
foreach ( $this->filter_pairs as $filter_pair ) {
|
||||
if ( $filter_pair->get_filtered_content_type() === $content_type ) {
|
||||
return $this->taxonomies_collector->get_taxonomy( $filter_pair->get_filtering_taxonomy(), $content_type );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Score_Groups\SEO_Score_Groups;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface;
|
||||
|
||||
/**
|
||||
* The repository to get SEO score groups.
|
||||
*/
|
||||
class SEO_Score_Groups_Repository {
|
||||
|
||||
/**
|
||||
* All SEO score groups.
|
||||
*
|
||||
* @var SEO_Score_Groups_Interface[]
|
||||
*/
|
||||
private $seo_score_groups;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param SEO_Score_Groups_Interface ...$seo_score_groups All SEO score groups.
|
||||
*/
|
||||
public function __construct( SEO_Score_Groups_Interface ...$seo_score_groups ) {
|
||||
$this->seo_score_groups = $seo_score_groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SEO score group that a SEO score belongs to.
|
||||
*
|
||||
* @param int $seo_score The SEO score to be assigned into a group.
|
||||
*
|
||||
* @return SEO_Score_Groups_Interface The SEO score group that the SEO score belongs to.
|
||||
*/
|
||||
public function get_seo_score_group( ?int $seo_score ): SEO_Score_Groups_Interface {
|
||||
if ( $seo_score === null || $seo_score === 0 ) {
|
||||
return new No_SEO_Score_Group();
|
||||
}
|
||||
|
||||
foreach ( $this->seo_score_groups as $seo_score_group ) {
|
||||
if ( $seo_score_group->get_max_score() === null ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $seo_score >= $seo_score_group->get_min_score() && $seo_score <= $seo_score_group->get_max_score() ) {
|
||||
return $seo_score_group;
|
||||
}
|
||||
}
|
||||
|
||||
return new No_SEO_Score_Group();
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Score_Results;
|
||||
|
||||
use Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Result;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface;
|
||||
|
||||
/**
|
||||
* The abstract score results repository.
|
||||
*/
|
||||
abstract class Abstract_Score_Results_Repository {
|
||||
|
||||
/**
|
||||
* The score results collector.
|
||||
*
|
||||
* @var Score_Results_Collector_Interface
|
||||
*/
|
||||
protected $score_results_collector;
|
||||
|
||||
/**
|
||||
* The current scores repository.
|
||||
*
|
||||
* @var Current_Scores_Repository
|
||||
*/
|
||||
protected $current_scores_repository;
|
||||
|
||||
/**
|
||||
* All score groups.
|
||||
*
|
||||
* @var Score_Groups_Interface[]
|
||||
*/
|
||||
protected $score_groups;
|
||||
|
||||
/**
|
||||
* Sets the repositories.
|
||||
*
|
||||
* @required
|
||||
*
|
||||
* @param Current_Scores_Repository $current_scores_repository The current scores repository.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_repositories( Current_Scores_Repository $current_scores_repository ) {
|
||||
$this->current_scores_repository = $current_scores_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the score results for a content type.
|
||||
*
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param Taxonomy|null $taxonomy The taxonomy of the term we're filtering for.
|
||||
* @param int|null $term_id The ID of the term we're filtering for.
|
||||
* @param bool|null $is_troubleshooting Whether we're in troubleshooting mode.
|
||||
*
|
||||
* @return array<array<string, string|int|array<string, string>>> The scores.
|
||||
*
|
||||
* @throws Exception When getting score results from the infrastructure fails.
|
||||
*/
|
||||
public function get_score_results( Content_Type $content_type, ?Taxonomy $taxonomy, ?int $term_id, ?bool $is_troubleshooting ): array {
|
||||
$score_results = $this->score_results_collector->get_score_results( $this->score_groups, $content_type, $term_id, $is_troubleshooting );
|
||||
|
||||
if ( $is_troubleshooting === true ) {
|
||||
$score_results['score_ids'] = clone $score_results['scores'];
|
||||
|
||||
foreach ( $score_results['scores'] as &$score ) {
|
||||
$score = ( $score !== null ) ? \count( \explode( ',', $score ) ) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
$current_scores_list = $this->current_scores_repository->get_current_scores( $this->score_groups, $score_results, $content_type, $taxonomy, $term_id );
|
||||
$score_result_object = new Score_Result( $current_scores_list, $score_results['query_time'], $score_results['cache_used'] );
|
||||
|
||||
return $score_result_object->to_array();
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Score_Results;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Current_Score;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Current_Scores_List;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Groups\Score_Group_Link_Collector;
|
||||
|
||||
/**
|
||||
* The current scores repository.
|
||||
*/
|
||||
class Current_Scores_Repository {
|
||||
|
||||
/**
|
||||
* The score group link collector.
|
||||
*
|
||||
* @var Score_Group_Link_Collector
|
||||
*/
|
||||
protected $score_group_link_collector;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Score_Group_Link_Collector $score_group_link_collector The score group link collector.
|
||||
*/
|
||||
public function __construct( Score_Group_Link_Collector $score_group_link_collector ) {
|
||||
$this->score_group_link_collector = $score_group_link_collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current results.
|
||||
*
|
||||
* @param Score_Groups_Interface[] $score_groups The score groups.
|
||||
* @param array<string, string> $score_results The score results.
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param Taxonomy|null $taxonomy The taxonomy of the term we're filtering for.
|
||||
* @param int|null $term_id The ID of the term we're filtering for.
|
||||
*
|
||||
* @return array<array<string, string|int|array<string, string>>> The current results.
|
||||
*/
|
||||
public function get_current_scores( array $score_groups, array $score_results, Content_Type $content_type, ?Taxonomy $taxonomy, ?int $term_id ): Current_Scores_List {
|
||||
$current_scores_list = new Current_Scores_List();
|
||||
|
||||
foreach ( $score_groups as $score_group ) {
|
||||
$score_name = $score_group->get_name();
|
||||
$current_score_links = $this->get_current_score_links( $score_group, $content_type, $taxonomy, $term_id );
|
||||
$score_amount = (int) $score_results['scores']->$score_name;
|
||||
$score_ids = ( isset( $score_results['score_ids'] ) ) ? $score_results['score_ids']->$score_name : null;
|
||||
|
||||
$current_score = new Current_Score( $score_name, $score_amount, $score_ids, $current_score_links );
|
||||
$current_scores_list->add( $current_score, $score_group->get_position() );
|
||||
}
|
||||
|
||||
return $current_scores_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the links for the current scores of a score group.
|
||||
*
|
||||
* @param Score_Groups_Interface $score_group The scoure group.
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param Taxonomy|null $taxonomy The taxonomy of the term we're filtering for.
|
||||
* @param int|null $term_id The ID of the term we're filtering for.
|
||||
*
|
||||
* @return array<string, string> The current score links.
|
||||
*/
|
||||
protected function get_current_score_links( Score_Groups_Interface $score_group, Content_Type $content_type, ?Taxonomy $taxonomy, ?int $term_id ): array {
|
||||
return [
|
||||
'view' => $this->score_group_link_collector->get_view_link( $score_group, $content_type, $taxonomy, $term_id ),
|
||||
];
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Score_Results\Readability_Score_Results;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Application\Score_Results\Abstract_Score_Results_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Readability_Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Readability_Score_Results\Cached_Readability_Score_Results_Collector;
|
||||
|
||||
/**
|
||||
* The repository to get readability score results.
|
||||
*/
|
||||
class Readability_Score_Results_Repository extends Abstract_Score_Results_Repository {
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Cached_Readability_Score_Results_Collector $readability_score_results_collector The cached readability score results collector.
|
||||
* @param Readability_Score_Groups_Interface ...$readability_score_groups All readability score groups.
|
||||
*/
|
||||
public function __construct(
|
||||
Cached_Readability_Score_Results_Collector $readability_score_results_collector,
|
||||
Readability_Score_Groups_Interface ...$readability_score_groups
|
||||
) {
|
||||
$this->score_results_collector = $readability_score_results_collector;
|
||||
$this->score_groups = $readability_score_groups;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Score_Results\SEO_Score_Results;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Application\Score_Results\Abstract_Score_Results_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\SEO_Score_Results\Cached_SEO_Score_Results_Collector;
|
||||
|
||||
/**
|
||||
* The repository to get SEO score results.
|
||||
*/
|
||||
class SEO_Score_Results_Repository extends Abstract_Score_Results_Repository {
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Cached_SEO_Score_Results_Collector $seo_score_results_collector The cached SEO score results collector.
|
||||
* @param SEO_Score_Groups_Interface ...$seo_score_groups All SEO score groups.
|
||||
*/
|
||||
public function __construct(
|
||||
Cached_SEO_Score_Results_Collector $seo_score_results_collector,
|
||||
SEO_Score_Groups_Interface ...$seo_score_groups
|
||||
) {
|
||||
$this->score_results_collector = $seo_score_results_collector;
|
||||
$this->score_groups = $seo_score_groups;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Search_Rankings;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter;
|
||||
|
||||
/**
|
||||
* The data provider for comparing search ranking related data.
|
||||
*/
|
||||
class Search_Ranking_Compare_Repository implements Dashboard_Repository_Interface {
|
||||
|
||||
/**
|
||||
* The adapter.
|
||||
*
|
||||
* @var Site_Kit_Search_Console_Adapter
|
||||
*/
|
||||
private $site_kit_search_console_adapter;
|
||||
|
||||
/**
|
||||
* The site kit configuration object.
|
||||
*
|
||||
* @var Site_Kit
|
||||
*/
|
||||
private $site_kit_configuration;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter.
|
||||
* @param Site_Kit $site_kit_configuration The site kit configuration object.
|
||||
*/
|
||||
public function __construct(
|
||||
Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter,
|
||||
Site_Kit $site_kit_configuration
|
||||
) {
|
||||
$this->site_kit_search_console_adapter = $site_kit_search_console_adapter;
|
||||
$this->site_kit_configuration = $site_kit_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comparing search ranking data.
|
||||
*
|
||||
* @param Parameters $parameters The parameter to use for getting the comparing search ranking data.
|
||||
*
|
||||
* @return Data_Container
|
||||
*
|
||||
* @throws Data_Source_Not_Available_Exception When getting the comparing search ranking data fails.
|
||||
*/
|
||||
public function get_data( Parameters $parameters ): Data_Container {
|
||||
if ( ! $this->site_kit_configuration->is_onboarded() ) {
|
||||
throw new Data_Source_Not_Available_Exception( 'Comparison search ranking repository' );
|
||||
}
|
||||
|
||||
return $this->site_kit_search_console_adapter->get_comparison_data( $parameters );
|
||||
}
|
||||
}
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Search_Rankings;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Indexables\Top_Page_Indexable_Collector;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter;
|
||||
|
||||
/**
|
||||
* The data provider for top page data.
|
||||
*/
|
||||
class Top_Page_Repository implements Dashboard_Repository_Interface {
|
||||
|
||||
/**
|
||||
* The adapter.
|
||||
*
|
||||
* @var Site_Kit_Search_Console_Adapter
|
||||
*/
|
||||
private $site_kit_search_console_adapter;
|
||||
|
||||
/**
|
||||
* The top page indexable collector.
|
||||
*
|
||||
* @var Top_Page_Indexable_Collector
|
||||
*/
|
||||
private $top_page_indexable_collector;
|
||||
|
||||
/**
|
||||
* The site kit configuration object.
|
||||
*
|
||||
* @var Site_Kit
|
||||
*/
|
||||
private $site_kit_configuration;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter.
|
||||
* @param Top_Page_Indexable_Collector $top_page_indexable_collector The top page indexable collector.
|
||||
* @param Site_Kit $site_kit_configuration The site kit configuration object.
|
||||
*/
|
||||
public function __construct(
|
||||
Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter,
|
||||
Top_Page_Indexable_Collector $top_page_indexable_collector,
|
||||
Site_Kit $site_kit_configuration
|
||||
) {
|
||||
$this->site_kit_search_console_adapter = $site_kit_search_console_adapter;
|
||||
$this->top_page_indexable_collector = $top_page_indexable_collector;
|
||||
$this->site_kit_configuration = $site_kit_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the top pages' data.
|
||||
*
|
||||
* @param Parameters $parameters The parameter to use for getting the top pages.
|
||||
*
|
||||
* @return Data_Container
|
||||
*
|
||||
* @throws Data_Source_Not_Available_Exception When this repository is used without the needed prerequisites ready.
|
||||
*/
|
||||
public function get_data( Parameters $parameters ): Data_Container {
|
||||
if ( ! $this->site_kit_configuration->is_onboarded() ) {
|
||||
throw new Data_Source_Not_Available_Exception( 'Top page repository' );
|
||||
}
|
||||
$top_pages_search_ranking_data = $this->site_kit_search_console_adapter->get_data( $parameters );
|
||||
$top_pages_full_data = $this->top_page_indexable_collector->get_data( $top_pages_search_ranking_data );
|
||||
|
||||
return $top_pages_full_data;
|
||||
}
|
||||
}
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Search_Rankings;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter;
|
||||
|
||||
/**
|
||||
* The data provider for top query data.
|
||||
*/
|
||||
class Top_Query_Repository implements Dashboard_Repository_Interface {
|
||||
|
||||
/**
|
||||
* The adapter.
|
||||
*
|
||||
* @var Site_Kit_Search_Console_Adapter
|
||||
*/
|
||||
private $site_kit_search_console_adapter;
|
||||
|
||||
/**
|
||||
* The site kit configuration object.
|
||||
*
|
||||
* @var Site_Kit
|
||||
*/
|
||||
private $site_kit_configuration;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter.
|
||||
* @param Site_Kit $site_kit_configuration The site kit configuration object.
|
||||
*/
|
||||
public function __construct(
|
||||
Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter,
|
||||
Site_Kit $site_kit_configuration
|
||||
) {
|
||||
$this->site_kit_search_console_adapter = $site_kit_search_console_adapter;
|
||||
$this->site_kit_configuration = $site_kit_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the top queries' data.
|
||||
*
|
||||
* @param Parameters $parameters The parameter to use for getting the top queries.
|
||||
*
|
||||
* @return Data_Container
|
||||
*
|
||||
* @throws Data_Source_Not_Available_Exception When this repository is used without the needed prerequisites ready.
|
||||
*/
|
||||
public function get_data( Parameters $parameters ): Data_Container {
|
||||
if ( ! $this->site_kit_configuration->is_onboarded() ) {
|
||||
throw new Data_Source_Not_Available_Exception( 'Top queries repository' );
|
||||
}
|
||||
|
||||
return $this->site_kit_search_console_adapter->get_data( $parameters );
|
||||
}
|
||||
}
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Taxonomies;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Application\Filter_Pairs\Filter_Pairs_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies\Taxonomies_Collector;
|
||||
|
||||
/**
|
||||
* The repository to get taxonomies.
|
||||
*/
|
||||
class Taxonomies_Repository {
|
||||
|
||||
/**
|
||||
* The taxonomies collector.
|
||||
*
|
||||
* @var Taxonomies_Collector
|
||||
*/
|
||||
private $taxonomies_collector;
|
||||
|
||||
/**
|
||||
* The filter pairs repository.
|
||||
*
|
||||
* @var Filter_Pairs_Repository
|
||||
*/
|
||||
private $filter_pairs_repository;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Taxonomies_Collector $taxonomies_collector The taxonomies collector.
|
||||
* @param Filter_Pairs_Repository $filter_pairs_repository The filter pairs repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Taxonomies_Collector $taxonomies_collector,
|
||||
Filter_Pairs_Repository $filter_pairs_repository
|
||||
) {
|
||||
$this->taxonomies_collector = $taxonomies_collector;
|
||||
$this->filter_pairs_repository = $filter_pairs_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object of the filtering taxonomy of a content type.
|
||||
*
|
||||
* @param string $content_type The content type that the taxonomy filters.
|
||||
*
|
||||
* @return Taxonomy|null The filtering taxonomy of the content type.
|
||||
*/
|
||||
public function get_content_type_taxonomy( string $content_type ) {
|
||||
// First we check if there's a filter that overrides the filtering taxonomy for this content type.
|
||||
$taxonomy = $this->taxonomies_collector->get_custom_filtering_taxonomy( $content_type );
|
||||
if ( $taxonomy ) {
|
||||
return $taxonomy;
|
||||
}
|
||||
|
||||
// Then we check if there is a filter explicitly made for this content type.
|
||||
$taxonomy = $this->filter_pairs_repository->get_taxonomy( $content_type );
|
||||
if ( $taxonomy ) {
|
||||
return $taxonomy;
|
||||
}
|
||||
|
||||
// If everything else returned empty, we can always try the fallback taxonomy.
|
||||
return $this->taxonomies_collector->get_fallback_taxonomy( $content_type );
|
||||
}
|
||||
}
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Tracking;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Tracking\Setup_Steps_Tracking_Repository_Interface;
|
||||
|
||||
/**
|
||||
* Tracks the setup steps.
|
||||
*/
|
||||
class Setup_Steps_Tracking {
|
||||
|
||||
/**
|
||||
* The setup steps tracking repository.
|
||||
*
|
||||
* @var Setup_Steps_Tracking_Repository_Interface
|
||||
*/
|
||||
private $setup_steps_tracking_repository;
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Setup_Steps_Tracking_Repository_Interface $setup_steps_tracking_repository The setup steps tracking repository.
|
||||
*/
|
||||
public function __construct( Setup_Steps_Tracking_Repository_Interface $setup_steps_tracking_repository ) {
|
||||
$this->setup_steps_tracking_repository = $setup_steps_tracking_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Site Kit setup widget has been loaded.
|
||||
*
|
||||
* @return string "yes" on "no".
|
||||
*/
|
||||
public function get_setup_widget_loaded(): string {
|
||||
return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'setup_widget_loaded' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stage of the first interaction.
|
||||
*
|
||||
* @return string The stage name.
|
||||
*/
|
||||
public function get_first_interaction_stage(): string {
|
||||
return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'first_interaction_stage' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stage of the last interaction.
|
||||
*
|
||||
* @return string The stage name.
|
||||
*/
|
||||
public function get_last_interaction_stage(): string {
|
||||
return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'last_interaction_stage' );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the setup widget has been temporarily dismissed.
|
||||
*
|
||||
* @return string "yes" on "no".
|
||||
*/
|
||||
public function get_setup_widget_temporarily_dismissed(): string {
|
||||
return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'setup_widget_temporarily_dismissed' );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the setup widget has been permanently dismissed.
|
||||
*
|
||||
* @return string "yes" on "no".
|
||||
*/
|
||||
public function get_setup_widget_permanently_dismissed(): string {
|
||||
return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'setup_widget_permanently_dismissed' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this object represented by a key value array.
|
||||
*
|
||||
* @return array<string> The tracking data
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return [
|
||||
'setupWidgetLoaded' => $this->get_setup_widget_loaded(),
|
||||
'firstInteractionStage' => $this->get_first_interaction_stage(),
|
||||
'lastInteractionStage' => $this->get_last_interaction_stage(),
|
||||
'setupWidgetTemporarilyDismissed' => $this->get_setup_widget_temporarily_dismissed(),
|
||||
'setupWidgetPermanentlyDismissed' => $this->get_setup_widget_permanently_dismissed(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Traffic;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4\Site_Kit_Analytics_4_Adapter;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
|
||||
/**
|
||||
* The data provider for comparison organic sessions data.
|
||||
*/
|
||||
class Organic_Sessions_Compare_Repository implements Dashboard_Repository_Interface {
|
||||
|
||||
/**
|
||||
* The adapter.
|
||||
*
|
||||
* @var Site_Kit_Analytics_4_Adapter
|
||||
*/
|
||||
private $site_kit_analytics_4_adapter;
|
||||
|
||||
/**
|
||||
* The site kit configuration object.
|
||||
*
|
||||
* @var Site_Kit
|
||||
*/
|
||||
private $site_kit_configuration;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Site_Kit_Analytics_4_Adapter $site_kit_analytics_4_adapter The adapter.
|
||||
* @param Site_Kit $site_kit_configuration The site kit configuration object.
|
||||
*/
|
||||
public function __construct(
|
||||
Site_Kit_Analytics_4_Adapter $site_kit_analytics_4_adapter,
|
||||
Site_Kit $site_kit_configuration
|
||||
) {
|
||||
$this->site_kit_analytics_4_adapter = $site_kit_analytics_4_adapter;
|
||||
$this->site_kit_configuration = $site_kit_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets comparison organic sessions' data.
|
||||
*
|
||||
* @param Parameters $parameters The parameter to use for getting the comparison organic sessions' data.
|
||||
*
|
||||
* @return Data_Container
|
||||
*
|
||||
* @throws Data_Source_Not_Available_Exception When getting the comparison organic sessions' data fails.
|
||||
*/
|
||||
public function get_data( Parameters $parameters ): Data_Container {
|
||||
if ( ! $this->site_kit_configuration->is_onboarded() || ! $this->site_kit_configuration->is_ga_connected() ) {
|
||||
throw new Data_Source_Not_Available_Exception( 'Comparison organic sessions repository' );
|
||||
}
|
||||
|
||||
return $this->site_kit_analytics_4_adapter->get_comparison_data( $parameters );
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Application\Traffic;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4\Site_Kit_Analytics_4_Adapter;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
|
||||
/**
|
||||
* The data provider for daily organic sessions data.
|
||||
*/
|
||||
class Organic_Sessions_Daily_Repository implements Dashboard_Repository_Interface {
|
||||
|
||||
/**
|
||||
* The adapter.
|
||||
*
|
||||
* @var Site_Kit_Analytics_4_Adapter
|
||||
*/
|
||||
private $site_kit_analytics_4_adapter;
|
||||
|
||||
/**
|
||||
* The site kit configuration object.
|
||||
*
|
||||
* @var Site_Kit
|
||||
*/
|
||||
private $site_kit_configuration;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Site_Kit_Analytics_4_Adapter $site_kit_analytics_4_adapter The adapter.
|
||||
* @param Site_Kit $site_kit_configuration The site kit configuration object.
|
||||
*/
|
||||
public function __construct(
|
||||
Site_Kit_Analytics_4_Adapter $site_kit_analytics_4_adapter,
|
||||
Site_Kit $site_kit_configuration
|
||||
) {
|
||||
$this->site_kit_analytics_4_adapter = $site_kit_analytics_4_adapter;
|
||||
$this->site_kit_configuration = $site_kit_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets daily organic sessions' data.
|
||||
*
|
||||
* @param Parameters $parameters The parameter to use for getting the daily organic sessions' data.
|
||||
*
|
||||
* @return Data_Container
|
||||
*
|
||||
* @throws Data_Source_Not_Available_Exception When this repository is used without the needed prerequisites ready.
|
||||
*/
|
||||
public function get_data( Parameters $parameters ): Data_Container {
|
||||
if ( ! $this->site_kit_configuration->is_onboarded() || ! $this->site_kit_configuration->is_ga_connected() ) {
|
||||
throw new Data_Source_Not_Available_Exception( 'Daily organic sessions repository' );
|
||||
}
|
||||
|
||||
return $this->site_kit_analytics_4_adapter->get_daily_data( $parameters );
|
||||
}
|
||||
}
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Analytics_4;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for when an Analytics 4 request fails.
|
||||
*/
|
||||
class Failed_Request_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Constructor of the exception.
|
||||
*
|
||||
* @param string $error_message The error message of the request.
|
||||
* @param int $error_status_code The error status code of the request.
|
||||
*/
|
||||
public function __construct( $error_message, $error_status_code ) {
|
||||
parent::__construct( 'The Analytics 4 request failed: ' . $error_message, $error_status_code );
|
||||
}
|
||||
}
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Analytics_4;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for when an Analytics 4 request is invalid.
|
||||
*/
|
||||
class Invalid_Request_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Constructor of the exception.
|
||||
*
|
||||
* @param string $error_message The error message of the request.
|
||||
*/
|
||||
public function __construct( $error_message ) {
|
||||
parent::__construct( 'The Analytics 4 request is invalid: ' . $error_message, 400 );
|
||||
}
|
||||
}
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Analytics_4;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for when an Analytics 4 request returns with an unexpected response.
|
||||
*/
|
||||
class Unexpected_Response_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Constructor of the exception.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct( 'The response from Google Site Kit did not have an expected format.', 400 );
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Content_Types;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
|
||||
|
||||
/**
|
||||
* This class describes a Content Type.
|
||||
*/
|
||||
class Content_Type {
|
||||
|
||||
/**
|
||||
* The name of the content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* The label of the content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $label;
|
||||
|
||||
/**
|
||||
* The taxonomy that filters the content type.
|
||||
*
|
||||
* @var Taxonomy
|
||||
*/
|
||||
private $taxonomy;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param string $name The name of the content type.
|
||||
* @param string $label The label of the content type.
|
||||
* @param Taxonomy|null $taxonomy The taxonomy that filters the content type.
|
||||
*/
|
||||
public function __construct( string $name, string $label, ?Taxonomy $taxonomy = null ) {
|
||||
$this->name = $name;
|
||||
$this->label = $label;
|
||||
$this->taxonomy = $taxonomy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets name of the content type.
|
||||
*
|
||||
* @return string The name of the content type.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets label of the content type.
|
||||
*
|
||||
* @return string The label of the content type.
|
||||
*/
|
||||
public function get_label(): string {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the taxonomy that filters the content type.
|
||||
*
|
||||
* @return Taxonomy|null The taxonomy that filters the content type.
|
||||
*/
|
||||
public function get_taxonomy(): ?Taxonomy {
|
||||
return $this->taxonomy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the taxonomy that filters the content type.
|
||||
*
|
||||
* @param Taxonomy|null $taxonomy The taxonomy that filters the content type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_taxonomy( ?Taxonomy $taxonomy ): void {
|
||||
$this->taxonomy = $taxonomy;
|
||||
}
|
||||
}
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Content_Types;
|
||||
|
||||
/**
|
||||
* This class describes a list of content types.
|
||||
*/
|
||||
class Content_Types_List {
|
||||
|
||||
/**
|
||||
* The content types.
|
||||
*
|
||||
* @var array<Content_Type>
|
||||
*/
|
||||
private $content_types = [];
|
||||
|
||||
/**
|
||||
* Adds a content type to the list.
|
||||
*
|
||||
* @param Content_Type $content_type The content type to add.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add( Content_Type $content_type ): void {
|
||||
$this->content_types[ $content_type->get_name() ] = $content_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content types in the list.
|
||||
*
|
||||
* @return array<Content_Type> The content types in the list.
|
||||
*/
|
||||
public function get(): array {
|
||||
return $this->content_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the content type list to the expected key value representation.
|
||||
*
|
||||
* @return array<array<string, array<string, array<string, array<string, string|null>>>>> The content type list presented as the expected key value representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
$array = [];
|
||||
foreach ( $this->content_types as $content_type ) {
|
||||
$array[] = [
|
||||
'name' => $content_type->get_name(),
|
||||
'label' => $content_type->get_label(),
|
||||
'taxonomy' => ( $content_type->get_taxonomy() ) ? $content_type->get_taxonomy()->to_array() : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Data_Provider;
|
||||
|
||||
/**
|
||||
* Interface describing the way to get data for a specific data provider.
|
||||
*/
|
||||
interface Dashboard_Repository_Interface {
|
||||
|
||||
/**
|
||||
* Method to get dashboard related data from a provider.
|
||||
*
|
||||
* @param Parameters $parameters The parameter to get the dashboard data for.
|
||||
*
|
||||
* @return Data_Container
|
||||
*/
|
||||
public function get_data( Parameters $parameters ): Data_Container;
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Data_Provider;
|
||||
|
||||
/**
|
||||
* The data container.
|
||||
*/
|
||||
class Data_Container {
|
||||
|
||||
/**
|
||||
* All the data points.
|
||||
*
|
||||
* @var array<Data_Interface>
|
||||
*/
|
||||
private $data_container;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->data_container = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to add data.
|
||||
*
|
||||
* @param Data_Interface $data The data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_data( Data_Interface $data ) {
|
||||
$this->data_container[] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get all the data points.
|
||||
*
|
||||
* @return Data_Interface[] All the data points.
|
||||
*/
|
||||
public function get_data(): array {
|
||||
return $this->data_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the data points into an array.
|
||||
*
|
||||
* @return array<string, string> The array of the data points.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
$result = [];
|
||||
foreach ( $this->data_container as $data ) {
|
||||
$result[] = $data->to_array();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Data_Provider;
|
||||
|
||||
/**
|
||||
* The interface to describe the data domain.
|
||||
*/
|
||||
interface Data_Interface {
|
||||
|
||||
/**
|
||||
* A to array method.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function to_array(): array;
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Data_Provider;
|
||||
|
||||
/**
|
||||
* Object representation of the request parameters.
|
||||
*/
|
||||
abstract class Parameters {
|
||||
|
||||
/**
|
||||
* The start date.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $start_date;
|
||||
|
||||
/**
|
||||
* The end date.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $end_date;
|
||||
|
||||
/**
|
||||
* The amount of results.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $limit = 0;
|
||||
|
||||
/**
|
||||
* The compare start date.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $compare_start_date;
|
||||
|
||||
/**
|
||||
* The compare end date.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $compare_end_date;
|
||||
|
||||
/**
|
||||
* Getter for the start date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_start_date(): string {
|
||||
return $this->start_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the end date.
|
||||
* The date format should be Y-M-D.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_end_date(): string {
|
||||
return $this->end_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the result limit.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_limit(): int {
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the compare start date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_compare_start_date(): ?string {
|
||||
return $this->compare_start_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the compare end date.
|
||||
* The date format should be Y-M-D.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_compare_end_date(): ?string {
|
||||
return $this->compare_end_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* The start date setter.
|
||||
*
|
||||
* @param string $start_date The start date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_start_date( string $start_date ): void {
|
||||
$this->start_date = $start_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* The end date setter.
|
||||
*
|
||||
* @param string $end_date The end date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_end_date( string $end_date ): void {
|
||||
$this->end_date = $end_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* The result limit.
|
||||
*
|
||||
* @param int $limit The result limit.
|
||||
* @return void
|
||||
*/
|
||||
public function set_limit( int $limit ): void {
|
||||
$this->limit = $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The compare start date setter.
|
||||
*
|
||||
* @param string $compare_start_date The compare start date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_compare_start_date( string $compare_start_date ): void {
|
||||
$this->compare_start_date = $compare_start_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* The compare end date setter.
|
||||
*
|
||||
* @param string $compare_end_date The compare end date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_compare_end_date( string $compare_end_date ): void {
|
||||
$this->compare_end_date = $compare_end_date;
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Endpoint;
|
||||
|
||||
interface Endpoint_Interface {
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string;
|
||||
|
||||
/**
|
||||
* Gets the namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace(): string;
|
||||
|
||||
/**
|
||||
* Gets the route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_route(): string;
|
||||
|
||||
/**
|
||||
* Gets the URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url(): string;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Endpoint;
|
||||
|
||||
/**
|
||||
* List of endpoints.
|
||||
*/
|
||||
class Endpoint_List {
|
||||
|
||||
/**
|
||||
* Holds the endpoints.
|
||||
*
|
||||
* @var array<Endpoint_Interface>
|
||||
*/
|
||||
private $endpoints = [];
|
||||
|
||||
/**
|
||||
* Adds an endpoint to the list.
|
||||
*
|
||||
* @param Endpoint_Interface $endpoint An endpoint.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_endpoint( Endpoint_Interface $endpoint ): void {
|
||||
$this->endpoints[] = $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the list to an array.
|
||||
*
|
||||
* @return array<string, string> The array of endpoints.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
$result = [];
|
||||
foreach ( $this->endpoints as $endpoint ) {
|
||||
$result[ $endpoint->get_name() ] = $endpoint->get_url();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Filter_Pairs;
|
||||
|
||||
/**
|
||||
* This interface describes a Filter Pair implementation.
|
||||
*/
|
||||
interface Filter_Pairs_Interface {
|
||||
|
||||
/**
|
||||
* Gets the filtering taxonomy.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_filtering_taxonomy(): string;
|
||||
|
||||
/**
|
||||
* Gets the filtered content type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_filtered_content_type(): string;
|
||||
}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Filter_Pairs;
|
||||
|
||||
/**
|
||||
* This class describes the product category filter pair.
|
||||
*/
|
||||
class Product_Category_Filter_Pair implements Filter_Pairs_Interface {
|
||||
|
||||
/**
|
||||
* Gets the filtering taxonomy.
|
||||
*
|
||||
* @return string The filtering taxonomy.
|
||||
*/
|
||||
public function get_filtering_taxonomy(): string {
|
||||
return 'product_cat';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filtered content type.
|
||||
*
|
||||
* @return string The filtered content type.
|
||||
*/
|
||||
public function get_filtered_content_type(): string {
|
||||
return 'product';
|
||||
}
|
||||
}
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups;
|
||||
|
||||
/**
|
||||
* Abstract class for a score group.
|
||||
*/
|
||||
abstract class Abstract_Score_Group implements Score_Groups_Interface {
|
||||
|
||||
/**
|
||||
* The name of the score group.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* The key of the score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $filter_key;
|
||||
|
||||
/**
|
||||
* The value of the score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $filter_value;
|
||||
|
||||
/**
|
||||
* The min score of the score group.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $min_score;
|
||||
|
||||
/**
|
||||
* The max score of the score group.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $max_score;
|
||||
|
||||
/**
|
||||
* The position of the score group.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $position;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Abstract_Score_Group;
|
||||
|
||||
/**
|
||||
* Abstract class for a readability score group.
|
||||
*/
|
||||
abstract class Abstract_Readability_Score_Group extends Abstract_Score_Group implements Readability_Score_Groups_Interface {
|
||||
|
||||
/**
|
||||
* Gets the key of the readability score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the readability score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_key(): string {
|
||||
return 'readability_filter';
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups;
|
||||
|
||||
/**
|
||||
* This class describes a bad readability score group.
|
||||
*/
|
||||
class Bad_Readability_Score_Group extends Abstract_Readability_Score_Group {
|
||||
|
||||
/**
|
||||
* Gets the name of the readability score group.
|
||||
*
|
||||
* @return string The name of the readability score group.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'bad';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the readability score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the readability score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_value(): string {
|
||||
return 'bad';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the readability score group.
|
||||
*
|
||||
* @return int The position of the readability score group.
|
||||
*/
|
||||
public function get_position(): int {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the readability score group.
|
||||
*
|
||||
* @return int|null The minimum score of the readability score group.
|
||||
*/
|
||||
public function get_min_score(): ?int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the readability score group.
|
||||
*
|
||||
* @return int|null The maximum score of the readability score group.
|
||||
*/
|
||||
public function get_max_score(): ?int {
|
||||
return 40;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups;
|
||||
|
||||
/**
|
||||
* This class describes a good readability score group.
|
||||
*/
|
||||
class Good_Readability_Score_Group extends Abstract_Readability_Score_Group {
|
||||
|
||||
/**
|
||||
* Gets the name of the readability score group.
|
||||
*
|
||||
* @return string The name of the readability score group.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'good';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the readability score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the readability score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_value(): string {
|
||||
return 'good';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the readability score group.
|
||||
*
|
||||
* @return int The position of the readability score group.
|
||||
*/
|
||||
public function get_position(): int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the readability score group.
|
||||
*
|
||||
* @return int|null The minimum score of the readability score group.
|
||||
*/
|
||||
public function get_min_score(): ?int {
|
||||
return 71;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the readability score group.
|
||||
*
|
||||
* @return int|null The maximum score of the readability score group.
|
||||
*/
|
||||
public function get_max_score(): ?int {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups;
|
||||
|
||||
/**
|
||||
* This class describes a missing readability score group.
|
||||
*/
|
||||
class No_Readability_Score_Group extends Abstract_Readability_Score_Group {
|
||||
|
||||
/**
|
||||
* Gets the name of the readability score group.
|
||||
*
|
||||
* @return string The name of the readability score group.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'notAnalyzed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the readability score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the readability score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_value(): string {
|
||||
return 'na';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the readability score group.
|
||||
*
|
||||
* @return int The position of the readability score group.
|
||||
*/
|
||||
public function get_position(): int {
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the readability score group.
|
||||
*
|
||||
* @return int|null The minimum score of the readability score group.
|
||||
*/
|
||||
public function get_min_score(): ?int {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the readability score group.
|
||||
*
|
||||
* @return int|null The maximum score of the readability score group.
|
||||
*/
|
||||
public function get_max_score(): ?int {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups;
|
||||
|
||||
/**
|
||||
* This class describes an OK readability score group.
|
||||
*/
|
||||
class Ok_Readability_Score_Group extends Abstract_Readability_Score_Group {
|
||||
|
||||
/**
|
||||
* Gets the name of the readability score group.
|
||||
*
|
||||
* @return string The the name of the readability score group.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the readability score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the readability score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_value(): string {
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the readability score group.
|
||||
*
|
||||
* @return int The position of the readability score group.
|
||||
*/
|
||||
public function get_position(): int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the readability score group.
|
||||
*
|
||||
* @return int|null The minimum score of the readability score group.
|
||||
*/
|
||||
public function get_min_score(): ?int {
|
||||
return 41;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the readability score group.
|
||||
*
|
||||
* @return int|null The maximum score of the readability score group.
|
||||
*/
|
||||
public function get_max_score(): ?int {
|
||||
return 70;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface;
|
||||
|
||||
/**
|
||||
* This interface describes a readability score group implementation.
|
||||
*/
|
||||
interface Readability_Score_Groups_Interface extends Score_Groups_Interface {
|
||||
}
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups;
|
||||
|
||||
/**
|
||||
* This interface describes a score group implementation.
|
||||
*/
|
||||
interface Score_Groups_Interface {
|
||||
|
||||
/**
|
||||
* Gets the name of the score group.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string;
|
||||
|
||||
/**
|
||||
* Gets the key of the score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_filter_key(): string;
|
||||
|
||||
/**
|
||||
* Gets the value of the score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_filter_value(): string;
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the score group.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function get_min_score(): ?int;
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the score group.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function get_max_score(): ?int;
|
||||
|
||||
/**
|
||||
* Gets the position of the score group.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_position(): int;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Abstract_Score_Group;
|
||||
|
||||
/**
|
||||
* Abstract class for an SEO score group.
|
||||
*/
|
||||
abstract class Abstract_SEO_Score_Group extends Abstract_Score_Group implements SEO_Score_Groups_Interface {
|
||||
|
||||
/**
|
||||
* Gets the key of the SEO score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the SEO score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_key(): string {
|
||||
return 'seo_filter';
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups;
|
||||
|
||||
/**
|
||||
* This class describes a bad SEO score group.
|
||||
*/
|
||||
class Bad_SEO_Score_Group extends Abstract_SEO_Score_Group {
|
||||
|
||||
/**
|
||||
* Gets the name of the SEO score group.
|
||||
*
|
||||
* @return string The name of the SEO score group.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'bad';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the SEO score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the SEO score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_value(): string {
|
||||
return 'bad';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the SEO score group.
|
||||
*
|
||||
* @return int The position of the SEO score group.
|
||||
*/
|
||||
public function get_position(): int {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the SEO score group.
|
||||
*
|
||||
* @return int|null The minimum score of the SEO score group.
|
||||
*/
|
||||
public function get_min_score(): ?int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the SEO score group.
|
||||
*
|
||||
* @return int|null The maximum score of the SEO score group.
|
||||
*/
|
||||
public function get_max_score(): ?int {
|
||||
return 40;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups;
|
||||
|
||||
/**
|
||||
* This class describes a good SEO score group.
|
||||
*/
|
||||
class Good_SEO_Score_Group extends Abstract_SEO_Score_Group {
|
||||
|
||||
/**
|
||||
* Gets the name of the SEO score group.
|
||||
*
|
||||
* @return string The name of the SEO score group.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'good';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the SEO score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the SEO score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_value(): string {
|
||||
return 'good';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the SEO score group.
|
||||
*
|
||||
* @return int The position of the SEO score group.
|
||||
*/
|
||||
public function get_position(): int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the SEO score group.
|
||||
*
|
||||
* @return int|null The minimum score of the SEO score group.
|
||||
*/
|
||||
public function get_min_score(): ?int {
|
||||
return 71;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the SEO score group.
|
||||
*
|
||||
* @return int|null The maximum score of the SEO score group.
|
||||
*/
|
||||
public function get_max_score(): ?int {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups;
|
||||
|
||||
/**
|
||||
* This class describes a missing SEO score group.
|
||||
*/
|
||||
class No_SEO_Score_Group extends Abstract_SEO_Score_Group {
|
||||
|
||||
/**
|
||||
* Gets the name of the SEO score group.
|
||||
*
|
||||
* @return string The name of the SEO score group.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'notAnalyzed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the SEO score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the SEO score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_value(): string {
|
||||
return 'na';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the SEO score group.
|
||||
*
|
||||
* @return int The position of the SEO score group.
|
||||
*/
|
||||
public function get_position(): int {
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the SEO score group.
|
||||
*
|
||||
* @return int|null The minimum score of the SEO score group.
|
||||
*/
|
||||
public function get_min_score(): ?int {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the SEO score group.
|
||||
*
|
||||
* @return int|null The maximum score of the SEO score group.
|
||||
*/
|
||||
public function get_max_score(): ?int {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups;
|
||||
|
||||
/**
|
||||
* This class describes an OK SEO score group.
|
||||
*/
|
||||
class Ok_SEO_Score_Group extends Abstract_SEO_Score_Group {
|
||||
|
||||
/**
|
||||
* Gets the name of the SEO score group.
|
||||
*
|
||||
* @return string The the name of the SEO score group.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the SEO score group that is used when filtering on the posts page.
|
||||
*
|
||||
* @return string The name of the SEO score group that is used when filtering on the posts page.
|
||||
*/
|
||||
public function get_filter_value(): string {
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the SEO score group.
|
||||
*
|
||||
* @return int The position of the SEO score group.
|
||||
*/
|
||||
public function get_position(): int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum score of the SEO score group.
|
||||
*
|
||||
* @return int|null The minimum score of the SEO score group.
|
||||
*/
|
||||
public function get_min_score(): ?int {
|
||||
return 41;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum score of the SEO score group.
|
||||
*
|
||||
* @return int|null The maximum score of the SEO score group.
|
||||
*/
|
||||
public function get_max_score(): ?int {
|
||||
return 70;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface;
|
||||
|
||||
/**
|
||||
* This interface describes an SEO score group implementation.
|
||||
*/
|
||||
interface SEO_Score_Groups_Interface extends Score_Groups_Interface {}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Results;
|
||||
|
||||
/**
|
||||
* This class describes a current score.
|
||||
*/
|
||||
class Current_Score {
|
||||
|
||||
/**
|
||||
* The name of the current score.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* The amount of the current score.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $amount;
|
||||
|
||||
/**
|
||||
* The ids of the current score.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $ids;
|
||||
|
||||
/**
|
||||
* The links of the current score.
|
||||
*
|
||||
* @var array<string, string>|null
|
||||
*/
|
||||
private $links;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param string $name The name of the current score.
|
||||
* @param int $amount The amount of the current score.
|
||||
* @param string|null $ids The ids of the current score.
|
||||
* @param array<string, string>|null $links The links of the current score.
|
||||
*/
|
||||
public function __construct( string $name, int $amount, ?string $ids = null, ?array $links = null ) {
|
||||
$this->name = $name;
|
||||
$this->amount = $amount;
|
||||
$this->ids = $ids;
|
||||
$this->links = $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets name of the current score.
|
||||
*
|
||||
* @return string The name of the current score.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of the current score.
|
||||
*
|
||||
* @return int The amount of the current score.
|
||||
*/
|
||||
public function get_amount(): int {
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ids of the current score.
|
||||
*
|
||||
* @return string|null The ids of the current score.
|
||||
*/
|
||||
public function get_ids(): ?string {
|
||||
return $this->ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the links of the current score in the expected key value representation.
|
||||
*
|
||||
* @return array<string, string> The links of the current score in the expected key value representation.
|
||||
*/
|
||||
public function get_links_to_array(): ?array {
|
||||
$links = [];
|
||||
|
||||
if ( $this->links === null ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
foreach ( $this->links as $key => $link ) {
|
||||
if ( $link === null ) {
|
||||
continue;
|
||||
}
|
||||
$links[ $key ] = $link;
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Results;
|
||||
|
||||
/**
|
||||
* This class describes a list of current scores.
|
||||
*/
|
||||
class Current_Scores_List {
|
||||
|
||||
/**
|
||||
* The current scores.
|
||||
*
|
||||
* @var Current_Score[]
|
||||
*/
|
||||
private $current_scores = [];
|
||||
|
||||
/**
|
||||
* Adds a current score to the list.
|
||||
*
|
||||
* @param Current_Score $current_score The current score to add.
|
||||
* @param int $position The position to add the current score.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add( Current_Score $current_score, int $position ): void {
|
||||
$this->current_scores[ $position ] = $current_score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the current score list to the expected key value representation.
|
||||
*
|
||||
* @return array<array<string, string|int|array<string, string>>> The score list presented as the expected key value representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
$array = [];
|
||||
|
||||
\ksort( $this->current_scores );
|
||||
|
||||
foreach ( $this->current_scores as $key => $current_score ) {
|
||||
$array[] = [
|
||||
'name' => $current_score->get_name(),
|
||||
'amount' => $current_score->get_amount(),
|
||||
'links' => $current_score->get_links_to_array(),
|
||||
];
|
||||
|
||||
if ( $current_score->get_ids() !== null ) {
|
||||
$array[ $key ]['ids'] = $current_score->get_ids();
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Results;
|
||||
|
||||
/**
|
||||
* This class describes a score result.
|
||||
*/
|
||||
class Score_Result {
|
||||
|
||||
/**
|
||||
* The list of the current scores of the score result.
|
||||
*
|
||||
* @var Current_Scores_List
|
||||
*/
|
||||
private $current_scores_list;
|
||||
|
||||
/**
|
||||
* The time the query took to get the score results.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
private $query_time;
|
||||
|
||||
/**
|
||||
* Whether cache was used to get the score results.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $is_cached_used;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Current_Scores_List $current_scores_list The list of the current scores of the score result.
|
||||
* @param float $query_time The time the query took to get the score results.
|
||||
* @param bool $is_cached_used Whether cache was used to get the score results.
|
||||
*/
|
||||
public function __construct( Current_Scores_List $current_scores_list, float $query_time, bool $is_cached_used ) {
|
||||
$this->current_scores_list = $current_scores_list;
|
||||
$this->query_time = $query_time;
|
||||
$this->is_cached_used = $is_cached_used;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this object represented by a key value array.
|
||||
*
|
||||
* @return array<string, array<array<string, string|int|array<string, string>>>|float|bool> Returns the name and if the feature is enabled.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return [
|
||||
'scores' => $this->current_scores_list->to_array(),
|
||||
'queryTime' => $this->query_time,
|
||||
'cacheUsed' => $this->is_cached_used,
|
||||
];
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Score_Results;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for when score results are not found.
|
||||
*/
|
||||
class Score_Results_Not_Found_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Constructor of the exception.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct( 'Score results not found', 500 );
|
||||
}
|
||||
}
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Search_Console;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for when a search console request fails.
|
||||
*/
|
||||
class Failed_Request_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Constructor of the exception.
|
||||
*
|
||||
* @param string $error_message The error message of the request.
|
||||
* @param int $error_status_code The error status code of the request.
|
||||
*/
|
||||
public function __construct( $error_message, $error_status_code ) {
|
||||
parent::__construct( 'The Search Console request failed: ' . $error_message, $error_status_code );
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Search_Console;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for when a Search Console request returns with an unexpected response.
|
||||
*/
|
||||
class Unexpected_Response_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Constructor of the exception.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct( 'The response from Google Site Kit did not have an expected format.', 400 );
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Search_Rankings;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface;
|
||||
|
||||
/**
|
||||
* Domain object that represents a Comparison Search Ranking record.
|
||||
*/
|
||||
class Comparison_Search_Ranking_Data implements Data_Interface {
|
||||
|
||||
/**
|
||||
* The current search ranking data.
|
||||
*
|
||||
* @var Search_Ranking_Data[]
|
||||
*/
|
||||
private $current_search_ranking_data = [];
|
||||
|
||||
/**
|
||||
* The previous search ranking data.
|
||||
*
|
||||
* @var Search_Ranking_Data[]
|
||||
*/
|
||||
private $previous_search_ranking_data = [];
|
||||
|
||||
/**
|
||||
* Sets the current search ranking data.
|
||||
*
|
||||
* @param Search_Ranking_Data $current_search_ranking_data The current search ranking data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_current_traffic_data( Search_Ranking_Data $current_search_ranking_data ): void {
|
||||
\array_push( $this->current_search_ranking_data, $current_search_ranking_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the previous search ranking data.
|
||||
*
|
||||
* @param Search_Ranking_Data $previous_search_ranking_data The previous search ranking data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_previous_traffic_data( Search_Ranking_Data $previous_search_ranking_data ): void {
|
||||
\array_push( $this->previous_search_ranking_data, $previous_search_ranking_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* The array representation of this domain object.
|
||||
*
|
||||
* @return array<array<string, int>>
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return [
|
||||
'current' => $this->parse_data( $this->current_search_ranking_data ),
|
||||
'previous' => $this->parse_data( $this->previous_search_ranking_data ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses search ranking data into the expected format.
|
||||
*
|
||||
* @param Search_Ranking_Data[] $search_ranking_data The search ranking data to be parsed.
|
||||
*
|
||||
* @return array<string, int> The parsed data
|
||||
*/
|
||||
private function parse_data( array $search_ranking_data ): array {
|
||||
$parsed_data = [
|
||||
'total_clicks' => 0,
|
||||
'total_impressions' => 0,
|
||||
];
|
||||
$weighted_postion = 0;
|
||||
|
||||
foreach ( $search_ranking_data as $search_ranking ) {
|
||||
$parsed_data['total_clicks'] += $search_ranking->get_clicks();
|
||||
$parsed_data['total_impressions'] += $search_ranking->get_impressions();
|
||||
$weighted_postion += ( $search_ranking->get_position() * $search_ranking->get_impressions() );
|
||||
}
|
||||
|
||||
if ( $parsed_data['total_impressions'] !== 0 ) {
|
||||
$parsed_data['average_ctr'] = ( $parsed_data['total_clicks'] / $parsed_data['total_impressions'] );
|
||||
$parsed_data['average_position'] = ( $weighted_postion / $parsed_data['total_impressions'] );
|
||||
}
|
||||
|
||||
return $parsed_data;
|
||||
}
|
||||
}
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Search_Rankings;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface;
|
||||
|
||||
/**
|
||||
* Domain object that represents a single Search Ranking Data record.
|
||||
*/
|
||||
class Search_Ranking_Data implements Data_Interface {
|
||||
|
||||
/**
|
||||
* The amount of clicks a `subject` gets.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $clicks;
|
||||
|
||||
/**
|
||||
* The click-through rate a `subject` gets.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
private $ctr;
|
||||
|
||||
/**
|
||||
* The amount of impressions a `subject` gets.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $impressions;
|
||||
|
||||
/**
|
||||
* The average position for the given `subject`.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
private $position;
|
||||
|
||||
/**
|
||||
* In the context of this domain object subject can represent a `URI` or a `search term`
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param int $clicks The clicks.
|
||||
* @param float $ctr The ctr.
|
||||
* @param int $impressions The impressions.
|
||||
* @param float $position The position.
|
||||
* @param string $subject The subject of the data.
|
||||
*/
|
||||
public function __construct( int $clicks, float $ctr, int $impressions, float $position, string $subject ) {
|
||||
$this->clicks = $clicks;
|
||||
$this->ctr = $ctr;
|
||||
$this->impressions = $impressions;
|
||||
$this->position = $position;
|
||||
$this->subject = $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* The array representation of this domain object.
|
||||
*
|
||||
* @return array<string|float|int|string[]>
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return [
|
||||
'clicks' => $this->clicks,
|
||||
'ctr' => $this->ctr,
|
||||
'impressions' => $this->impressions,
|
||||
'position' => $this->position,
|
||||
'subject' => $this->subject,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clicks.
|
||||
*
|
||||
* @return string The clicks.
|
||||
*/
|
||||
public function get_clicks(): string {
|
||||
return $this->clicks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the click-through rate.
|
||||
*
|
||||
* @return string The click-through rate.
|
||||
*/
|
||||
public function get_ctr(): string {
|
||||
return $this->ctr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the impressions.
|
||||
*
|
||||
* @return string The impressions.
|
||||
*/
|
||||
public function get_impressions(): string {
|
||||
return $this->impressions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position.
|
||||
*
|
||||
* @return string The position.
|
||||
*/
|
||||
public function get_position(): string {
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subject.
|
||||
*
|
||||
* @return string The subject.
|
||||
*/
|
||||
public function get_subject(): string {
|
||||
return $this->subject;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Search_Rankings;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface;
|
||||
|
||||
/**
|
||||
* Domain object that represents a single Top Page Data record.
|
||||
*/
|
||||
class Top_Page_Data implements Data_Interface {
|
||||
|
||||
/**
|
||||
* The search ranking data for the top page.
|
||||
*
|
||||
* @var Search_Ranking_Data
|
||||
*/
|
||||
private $search_ranking_data;
|
||||
|
||||
/**
|
||||
* The SEO score group the top page belongs to.
|
||||
*
|
||||
* @var SEO_Score_Groups_Interface
|
||||
*/
|
||||
private $seo_score_group;
|
||||
|
||||
/**
|
||||
* The edit link of the top page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $edit_link;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Search_Ranking_Data $search_ranking_data The search ranking data for the top page.
|
||||
* @param SEO_Score_Groups_Interface $seo_score_group The SEO score group the top page belongs to.
|
||||
* @param string $edit_link The edit link of the top page.
|
||||
*/
|
||||
public function __construct(
|
||||
Search_Ranking_Data $search_ranking_data,
|
||||
SEO_Score_Groups_Interface $seo_score_group,
|
||||
?string $edit_link = null
|
||||
) {
|
||||
$this->search_ranking_data = $search_ranking_data;
|
||||
$this->seo_score_group = $seo_score_group;
|
||||
$this->edit_link = $edit_link;
|
||||
}
|
||||
|
||||
/**
|
||||
* The array representation of this domain object.
|
||||
*
|
||||
* @return array<string|float|int|string[]>
|
||||
*/
|
||||
public function to_array(): array {
|
||||
$top_page_data = $this->search_ranking_data->to_array();
|
||||
$top_page_data['seoScore'] = $this->seo_score_group->get_name();
|
||||
$top_page_data['links'] = [];
|
||||
|
||||
if ( $this->edit_link !== null ) {
|
||||
$top_page_data['links']['edit'] = $this->edit_link;
|
||||
}
|
||||
|
||||
return $top_page_data;
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Taxonomies;
|
||||
|
||||
/**
|
||||
* This class describes a Taxonomy.
|
||||
*/
|
||||
class Taxonomy {
|
||||
|
||||
/**
|
||||
* The name of the taxonomy.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* The label of the taxonomy.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $label;
|
||||
|
||||
/**
|
||||
* The REST URL of the taxonomy.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $rest_url;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param string $name The name of the taxonomy.
|
||||
* @param string $label The label of the taxonomy.
|
||||
* @param string $rest_url The REST URL of the taxonomy.
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
string $label,
|
||||
string $rest_url
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->label = $label;
|
||||
$this->rest_url = $rest_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the taxonomy.
|
||||
*
|
||||
* @return string The name of the taxonomy.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the taxonomy to the expected key value representation.
|
||||
*
|
||||
* @return array<string, array<string, string>> The taxonomy presented as the expected key value representation.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'label' => $this->label,
|
||||
'links' => [
|
||||
'search' => $this->rest_url,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for when the integration is not yet onboarded.
|
||||
*/
|
||||
class Data_Source_Not_Available_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Constructor of the exception.
|
||||
*
|
||||
* @param string $data_source_name The name of the data source that is not found.
|
||||
*/
|
||||
public function __construct( $data_source_name ) {
|
||||
parent::__construct( "$data_source_name is not available yet. Not all prerequisites have been met.", 400 );
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Time_Based_SEO_Metrics;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for when the repository for the given widget are not found.
|
||||
*/
|
||||
class Repository_Not_Found_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Constructor of the exception.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct( 'Repository not found', 404 );
|
||||
}
|
||||
}
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Traffic;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface;
|
||||
|
||||
/**
|
||||
* Domain object that represents a single Comparison Traffic record.
|
||||
*/
|
||||
class Comparison_Traffic_Data implements Data_Interface {
|
||||
|
||||
public const CURRENT_PERIOD_KEY = 'current';
|
||||
public const PREVIOUS_PERIOD_KEY = 'previous';
|
||||
|
||||
/**
|
||||
* The current traffic data.
|
||||
*
|
||||
* @var Traffic_Data
|
||||
*/
|
||||
private $current_traffic_data;
|
||||
|
||||
/**
|
||||
* The previous traffic data.
|
||||
*
|
||||
* @var Traffic_Data
|
||||
*/
|
||||
private $previous_traffic_data;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Traffic_Data $current_traffic_data The current traffic data.
|
||||
* @param Traffic_Data $previous_traffic_data The previous traffic data.
|
||||
*/
|
||||
public function __construct( ?Traffic_Data $current_traffic_data = null, ?Traffic_Data $previous_traffic_data = null ) {
|
||||
$this->current_traffic_data = $current_traffic_data;
|
||||
$this->previous_traffic_data = $previous_traffic_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current traffic data.
|
||||
*
|
||||
* @param Traffic_Data $current_traffic_data The current traffic data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_current_traffic_data( Traffic_Data $current_traffic_data ): void {
|
||||
$this->current_traffic_data = $current_traffic_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the previous traffic data.
|
||||
*
|
||||
* @param Traffic_Data $previous_traffic_data The previous traffic data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_previous_traffic_data( Traffic_Data $previous_traffic_data ): void {
|
||||
$this->previous_traffic_data = $previous_traffic_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The array representation of this domain object.
|
||||
*
|
||||
* @return array<string|float|int|string[]>
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return [
|
||||
'current' => $this->current_traffic_data->to_array(),
|
||||
'previous' => $this->previous_traffic_data->to_array(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Traffic;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface;
|
||||
|
||||
/**
|
||||
* Domain object that represents a single Daily Traffic record.
|
||||
*/
|
||||
class Daily_Traffic_Data implements Data_Interface {
|
||||
|
||||
/**
|
||||
* The date of the traffic data, in YYYYMMDD format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $date;
|
||||
|
||||
/**
|
||||
* The traffic data for the date.
|
||||
*
|
||||
* @var Traffic_Data
|
||||
*/
|
||||
private $traffic_data;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param string $date The date of the traffic data, in YYYYMMDD format.
|
||||
* @param Traffic_Data $traffic_data The traffic data for the date.
|
||||
*/
|
||||
public function __construct( string $date, Traffic_Data $traffic_data ) {
|
||||
$this->date = $date;
|
||||
$this->traffic_data = $traffic_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The array representation of this domain object.
|
||||
*
|
||||
* @return array<string, string|int>
|
||||
*/
|
||||
public function to_array(): array {
|
||||
$result = [];
|
||||
$result['date'] = $this->date;
|
||||
|
||||
return \array_merge( $result, $this->traffic_data->to_array() );
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Domain\Traffic;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface;
|
||||
|
||||
/**
|
||||
* Domain object that represents a single Traffic record.
|
||||
*/
|
||||
class Traffic_Data implements Data_Interface {
|
||||
|
||||
/**
|
||||
* The sessions, if any.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $sessions;
|
||||
|
||||
/**
|
||||
* The total users, if any.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $total_users;
|
||||
|
||||
/**
|
||||
* The array representation of this domain object.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function to_array(): array {
|
||||
$result = [];
|
||||
|
||||
if ( $this->sessions !== null ) {
|
||||
$result['sessions'] = $this->sessions;
|
||||
}
|
||||
|
||||
if ( $this->total_users !== null ) {
|
||||
$result['total_users'] = $this->total_users;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sessions.
|
||||
*
|
||||
* @param int $sessions The sessions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_sessions( int $sessions ): void {
|
||||
$this->sessions = $sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total users.
|
||||
*
|
||||
* @param int $total_users The total users.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_total_users( int $total_users ): void {
|
||||
$this->total_users = $total_users;
|
||||
}
|
||||
}
|
||||
wp-content/plugins/wordpress-seo/src/dashboard/infrastructure/analytics-4/analytics-4-parameters.php
Executable
+138
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters;
|
||||
|
||||
/**
|
||||
* Domain object to add Analytics 4 specific data to the parameters.
|
||||
*/
|
||||
class Analytics_4_Parameters extends Parameters {
|
||||
|
||||
/**
|
||||
* The dimensions to query.
|
||||
*
|
||||
* @var array<array<string, string>> $dimensions
|
||||
*/
|
||||
private $dimensions = [];
|
||||
|
||||
/**
|
||||
* The dimensions filters.
|
||||
*
|
||||
* @var array<string, array<string>> $dimension_filters
|
||||
*/
|
||||
private $dimension_filters = [];
|
||||
|
||||
/**
|
||||
* The metrics.
|
||||
*
|
||||
* @var array<array<string, string>> $metrics
|
||||
*/
|
||||
private $metrics = [];
|
||||
|
||||
/**
|
||||
* The order by.
|
||||
*
|
||||
* @var array<array<string, array<string, string>>> $order_by
|
||||
*/
|
||||
private $order_by = [];
|
||||
|
||||
/**
|
||||
* Sets the dimensions.
|
||||
*
|
||||
* @link https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/Dimension
|
||||
*
|
||||
* @param array<string> $dimensions The dimensions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_dimensions( array $dimensions ): void {
|
||||
foreach ( $dimensions as $dimension ) {
|
||||
$this->dimensions[] = [ 'name' => $dimension ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the dimensions.
|
||||
*
|
||||
* @return array<array<string, string>>
|
||||
*/
|
||||
public function get_dimensions(): array {
|
||||
return $this->dimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dimension filters.
|
||||
*
|
||||
* @param array<string, array<string>> $dimension_filters The dimension filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_dimension_filters( array $dimension_filters ): void {
|
||||
$this->dimension_filters = $dimension_filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the dimension filters.
|
||||
*
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public function get_dimension_filters(): array {
|
||||
return $this->dimension_filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the metrics.
|
||||
*
|
||||
* @link https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/Metric
|
||||
*
|
||||
* @param array<string> $metrics The metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_metrics( array $metrics ): void {
|
||||
foreach ( $metrics as $metric ) {
|
||||
$this->metrics[] = [ 'name' => $metric ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the metrics.
|
||||
*
|
||||
* @return array<array<string, string>>
|
||||
*/
|
||||
public function get_metrics(): array {
|
||||
return $this->metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the order by.
|
||||
*
|
||||
* @link https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/OrderBy
|
||||
*
|
||||
* @param string $key The key to order by.
|
||||
* @param string $name The name to order by.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_order_by( string $key, string $name ): void {
|
||||
$order_by = [
|
||||
[
|
||||
$key => [
|
||||
$key . 'Name' => $name,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->order_by = $order_by;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the order by.
|
||||
*
|
||||
* @return array<array<string, array<string, string>>>
|
||||
*/
|
||||
public function get_order_by(): array {
|
||||
return $this->order_by;
|
||||
}
|
||||
}
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4;
|
||||
|
||||
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Row;
|
||||
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportResponse;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Analytics_4\Failed_Request_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Analytics_4\Invalid_Request_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Analytics_4\Unexpected_Response_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Traffic\Comparison_Traffic_Data;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Traffic\Daily_Traffic_Data;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Traffic\Traffic_Data;
|
||||
|
||||
/**
|
||||
* The site API adapter to make calls to the Analytics 4 API, via the Site_Kit plugin.
|
||||
*/
|
||||
class Site_Kit_Analytics_4_Adapter {
|
||||
|
||||
/**
|
||||
* Holds the api call class.
|
||||
*
|
||||
* @var Site_Kit_Analytics_4_Api_Call $site_kit_analytics_4_api_call
|
||||
*/
|
||||
private $site_kit_search_console_api_call;
|
||||
|
||||
/**
|
||||
* The register method that sets the instance in the adapter.
|
||||
*
|
||||
* @param Site_Kit_Analytics_4_Api_Call $site_kit_analytics_4_api_call The api call class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( Site_Kit_Analytics_4_Api_Call $site_kit_analytics_4_api_call ) {
|
||||
$this->site_kit_search_console_api_call = $site_kit_analytics_4_api_call;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrapper method to do a comparison Site Kit API request for Analytics.
|
||||
*
|
||||
* @param Analytics_4_Parameters $parameters The parameters.
|
||||
*
|
||||
* @return Data_Container The Site Kit API response.
|
||||
*
|
||||
* @throws Failed_Request_Exception When the request responds with an error from Site Kit.
|
||||
* @throws Unexpected_Response_Exception When the request responds with an unexpected format.
|
||||
* @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters.
|
||||
*/
|
||||
public function get_comparison_data( Analytics_4_Parameters $parameters ): Data_Container {
|
||||
$api_parameters = $this->build_parameters( $parameters );
|
||||
|
||||
$response = $this->site_kit_search_console_api_call->do_request( $api_parameters );
|
||||
|
||||
$this->validate_response( $response );
|
||||
|
||||
return $this->parse_comparison_response( $response->get_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrapper method to do a daily Site Kit API request for Analytics.
|
||||
*
|
||||
* @param Analytics_4_Parameters $parameters The parameters.
|
||||
*
|
||||
* @return Data_Container The Site Kit API response.
|
||||
*
|
||||
* @throws Failed_Request_Exception When the request responds with an error from Site Kit.
|
||||
* @throws Unexpected_Response_Exception When the request responds with an unexpected format.
|
||||
* @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters.
|
||||
*/
|
||||
public function get_daily_data( Analytics_4_Parameters $parameters ): Data_Container {
|
||||
$api_parameters = $this->build_parameters( $parameters );
|
||||
|
||||
$response = $this->site_kit_search_console_api_call->do_request( $api_parameters );
|
||||
|
||||
$this->validate_response( $response );
|
||||
|
||||
return $this->parse_daily_response( $response->get_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the parameters to be used in the Site Kit API request.
|
||||
*
|
||||
* @param Analytics_4_Parameters $parameters The parameters.
|
||||
*
|
||||
* @return array<string, array<string, string>> The Site Kit API parameters.
|
||||
*/
|
||||
private function build_parameters( Analytics_4_Parameters $parameters ): array {
|
||||
$api_parameters = [
|
||||
'slug' => 'analytics-4',
|
||||
'datapoint' => 'report',
|
||||
'startDate' => $parameters->get_start_date(),
|
||||
'endDate' => $parameters->get_end_date(),
|
||||
];
|
||||
|
||||
if ( ! empty( $parameters->get_dimension_filters() ) ) {
|
||||
$api_parameters['dimensionFilters'] = $parameters->get_dimension_filters();
|
||||
}
|
||||
|
||||
if ( ! empty( $parameters->get_dimensions() ) ) {
|
||||
$api_parameters['dimensions'] = $parameters->get_dimensions();
|
||||
}
|
||||
|
||||
if ( ! empty( $parameters->get_metrics() ) ) {
|
||||
$api_parameters['metrics'] = $parameters->get_metrics();
|
||||
}
|
||||
|
||||
if ( ! empty( $parameters->get_order_by() ) ) {
|
||||
$api_parameters['orderby'] = $parameters->get_order_by();
|
||||
}
|
||||
|
||||
if ( ! empty( $parameters->get_compare_start_date() && ! empty( $parameters->get_compare_end_date() ) ) ) {
|
||||
$api_parameters['compareStartDate'] = $parameters->get_compare_start_date();
|
||||
$api_parameters['compareEndDate'] = $parameters->get_compare_end_date();
|
||||
}
|
||||
|
||||
return $api_parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a response for a Site Kit API request that requests daily data for Analytics 4.
|
||||
*
|
||||
* @param RunReportResponse $response The response to parse.
|
||||
*
|
||||
* @return Data_Container The parsed response.
|
||||
*
|
||||
* @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters.
|
||||
*/
|
||||
private function parse_daily_response( RunReportResponse $response ): Data_Container {
|
||||
if ( ! $this->is_daily_request( $response ) ) {
|
||||
throw new Invalid_Request_Exception( 'Unexpected parameters for the request' );
|
||||
}
|
||||
|
||||
$data_container = new Data_Container();
|
||||
|
||||
foreach ( $response->getRows() as $daily_traffic ) {
|
||||
$traffic_data = new Traffic_Data();
|
||||
|
||||
foreach ( $response->getMetricHeaders() as $key => $metric ) {
|
||||
|
||||
// As per https://developers.google.com/analytics/devguides/reporting/data/v1/basics#read_the_response,
|
||||
// the order of the columns is consistent in the request, header, and rows.
|
||||
// So we can use the key of the header to get the correct metric value from the row.
|
||||
$metric_value = $daily_traffic->getMetricValues()[ $key ]->getValue();
|
||||
|
||||
if ( $metric->getName() === 'sessions' ) {
|
||||
$traffic_data->set_sessions( (int) $metric_value );
|
||||
}
|
||||
elseif ( $metric->getName() === 'totalUsers' ) {
|
||||
$traffic_data->set_total_users( (int) $metric_value );
|
||||
}
|
||||
}
|
||||
|
||||
// Since we're here, we know that the first dimension is date, so we know that dimensionValues[0]->value is a date.
|
||||
$data_container->add_data( new Daily_Traffic_Data( $daily_traffic->getDimensionValues()[0]->getValue(), $traffic_data ) );
|
||||
}
|
||||
|
||||
return $data_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a response for a Site Kit API request for Analytics 4 that compares data ranges.
|
||||
*
|
||||
* @param RunReportResponse $response The response to parse.
|
||||
*
|
||||
* @return Data_Container The parsed response.
|
||||
*
|
||||
* @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters.
|
||||
*/
|
||||
private function parse_comparison_response( RunReportResponse $response ): Data_Container {
|
||||
if ( ! $this->is_comparison_request( $response ) ) {
|
||||
throw new Invalid_Request_Exception( 'Unexpected parameters for the request' );
|
||||
}
|
||||
|
||||
$data_container = new Data_Container();
|
||||
$comparison_traffic_data = new Comparison_Traffic_Data();
|
||||
|
||||
// First row is the current date range's data, second row is the previous date range's data.
|
||||
foreach ( $response->getRows() as $date_range_row ) {
|
||||
$traffic_data = new Traffic_Data();
|
||||
|
||||
// Loop through all the metrics of the date range.
|
||||
foreach ( $response->getMetricHeaders() as $key => $metric ) {
|
||||
|
||||
// As per https://developers.google.com/analytics/devguides/reporting/data/v1/basics#read_the_response,
|
||||
// the order of the columns is consistent in the request, header, and rows.
|
||||
// So we can use the key of the header to get the correct metric value from the row.
|
||||
$metric_value = $date_range_row->getMetricValues()[ $key ]->getValue();
|
||||
|
||||
if ( $metric->getName() === 'sessions' ) {
|
||||
$traffic_data->set_sessions( (int) $metric_value );
|
||||
}
|
||||
elseif ( $metric->getName() === 'totalUsers' ) {
|
||||
$traffic_data->set_total_users( (int) $metric_value );
|
||||
}
|
||||
}
|
||||
|
||||
$period = $this->get_period( $date_range_row );
|
||||
|
||||
if ( $period === Comparison_Traffic_Data::CURRENT_PERIOD_KEY ) {
|
||||
$comparison_traffic_data->set_current_traffic_data( $traffic_data );
|
||||
}
|
||||
elseif ( $period === Comparison_Traffic_Data::PREVIOUS_PERIOD_KEY ) {
|
||||
$comparison_traffic_data->set_previous_traffic_data( $traffic_data );
|
||||
}
|
||||
}
|
||||
|
||||
$data_container->add_data( $comparison_traffic_data );
|
||||
|
||||
return $data_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the response row and returns whether it's about the current period or the previous period.
|
||||
*
|
||||
* @see https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/DateRange
|
||||
*
|
||||
* @param Row $date_range_row The response row.
|
||||
*
|
||||
* @return string The key associated with the current or the previous period.
|
||||
*
|
||||
* @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters.
|
||||
*/
|
||||
private function get_period( Row $date_range_row ): string {
|
||||
foreach ( $date_range_row->getDimensionValues() as $dimension_value ) {
|
||||
if ( $dimension_value->getValue() === 'date_range_0' ) {
|
||||
return Comparison_Traffic_Data::CURRENT_PERIOD_KEY;
|
||||
}
|
||||
elseif ( $dimension_value->getValue() === 'date_range_1' ) {
|
||||
return Comparison_Traffic_Data::PREVIOUS_PERIOD_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Invalid_Request_Exception( 'Unexpected date range names' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the response of the request to detect if it's a comparison request.
|
||||
*
|
||||
* @param RunReportResponse $response The response.
|
||||
*
|
||||
* @return bool Whether it's a comparison request.
|
||||
*/
|
||||
private function is_comparison_request( RunReportResponse $response ): bool {
|
||||
return \count( $response->getDimensionHeaders() ) === 1 && $response->getDimensionHeaders()[0]->getName() === 'dateRange';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the response of the request to detect if it's a daily request.
|
||||
*
|
||||
* @param RunReportResponse $response The response.
|
||||
*
|
||||
* @return bool Whether it's a daily request.
|
||||
*/
|
||||
private function is_daily_request( RunReportResponse $response ): bool {
|
||||
return \count( $response->getDimensionHeaders() ) === 1 && $response->getDimensionHeaders()[0]->getName() === 'date';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the response coming from Google Analytics.
|
||||
*
|
||||
* @param WP_REST_Response $response The response we want to validate.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Failed_Request_Exception When the request responds with an error from Site Kit.
|
||||
* @throws Unexpected_Response_Exception When the request responds with an unexpected format.
|
||||
*/
|
||||
private function validate_response( WP_REST_Response $response ): void {
|
||||
if ( $response->is_error() ) {
|
||||
$error_data = $response->as_error()->get_error_data();
|
||||
$error_status_code = ( $error_data['status'] ?? 500 );
|
||||
throw new Failed_Request_Exception( \wp_kses_post( $response->as_error()->get_error_message() ), (int) $error_status_code );
|
||||
}
|
||||
|
||||
if ( ! \is_a( $response->get_data(), RunReportResponse::class ) ) {
|
||||
throw new Unexpected_Response_Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4;
|
||||
|
||||
use Google\Site_Kit\Core\REST_API\REST_Routes;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Class that hold the code to do the REST call to the Site Kit api.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Site_Kit_Analytics_4_Api_Call {
|
||||
|
||||
/**
|
||||
* The Analytics 4 API route path.
|
||||
*/
|
||||
private const ANALYTICS_DATA_REPORT_ROUTE = '/modules/analytics-4/data/report';
|
||||
|
||||
/**
|
||||
* Runs the internal REST api call.
|
||||
*
|
||||
* @param array<string, array<string, string>> $api_parameters The api parameters.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function do_request( array $api_parameters ): WP_REST_Response {
|
||||
$request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . self::ANALYTICS_DATA_REPORT_ROUTE );
|
||||
$request->set_query_params( $api_parameters );
|
||||
return \rest_do_request( $request );
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Browser_Cache;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
/**
|
||||
* Responsible for the browser cache configuration.
|
||||
*/
|
||||
class Browser_Cache_Configuration {
|
||||
|
||||
/**
|
||||
* The Site Kit conditional.
|
||||
*
|
||||
* @var Google_Site_Kit_Feature_Conditional
|
||||
*/
|
||||
private $google_site_kit_feature_conditional;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Google_Site_Kit_Feature_Conditional $google_site_kit_feature_conditional The Site Kit conditional.
|
||||
*/
|
||||
public function __construct( Google_Site_Kit_Feature_Conditional $google_site_kit_feature_conditional ) {
|
||||
$this->google_site_kit_feature_conditional = $google_site_kit_feature_conditional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Time To Live for each widget's cache.
|
||||
*
|
||||
* @return array<string, array<string, int>> The cache TTL for each widget.
|
||||
*/
|
||||
private function get_widgets_cache_ttl() {
|
||||
return [
|
||||
'topPages' => [
|
||||
'ttl' => ( 1 * \MINUTE_IN_SECONDS ),
|
||||
],
|
||||
'topQueries' => [
|
||||
'ttl' => ( 1 * \HOUR_IN_SECONDS ),
|
||||
],
|
||||
'searchRankingCompare' => [
|
||||
'ttl' => ( 1 * \HOUR_IN_SECONDS ),
|
||||
],
|
||||
'organicSessions' => [
|
||||
'ttl' => ( 1 * \HOUR_IN_SECONDS ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the prefix for the client side cache key.
|
||||
*
|
||||
* Cache key is scoped to user session and blog_id to isolate the
|
||||
* cache between users and sites (in multisite).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_storage_prefix() {
|
||||
$current_user = \wp_get_current_user();
|
||||
$auth_cookie = \wp_parse_auth_cookie();
|
||||
$blog_id = \get_current_blog_id();
|
||||
$session_token = isset( $auth_cookie['token'] ) ? $auth_cookie['token'] : '';
|
||||
|
||||
return \wp_hash( $current_user->user_login . '|' . $session_token . '|' . $blog_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the browser cache configuration.
|
||||
*
|
||||
* @return array<string, string|array<string, array<string, int>>>
|
||||
*/
|
||||
public function get_configuration(): array {
|
||||
if ( ! $this->google_site_kit_feature_conditional->is_met() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'storagePrefix' => $this->get_storage_prefix(),
|
||||
'yoastVersion' => \WPSEO_VERSION,
|
||||
'widgetsCacheTtl' => $this->get_widgets_cache_ttl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Configuration;
|
||||
|
||||
/**
|
||||
* Interface for the Permanently Dismissed Site Kit configuration Repository.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
interface Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface {
|
||||
|
||||
/**
|
||||
* Sets the Site Kit configuration dismissal status.
|
||||
*
|
||||
* @param bool $is_dismissed The dismissal status.
|
||||
*
|
||||
* @return bool False when the update failed, true when the update succeeded.
|
||||
*/
|
||||
public function set_site_kit_configuration_dismissal( bool $is_dismissed ): bool;
|
||||
|
||||
/**
|
||||
* Checks if the Site Kit configuration is dismissed permanently.
|
||||
* *
|
||||
*
|
||||
* @return bool True when the configuration is dismissed, false when it is not.
|
||||
*/
|
||||
public function is_site_kit_configuration_dismissed(): bool;
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Configuration;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
|
||||
/**
|
||||
* Stores and retrieves whether the Site Kit configuration is permanently dismissed.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Permanently_Dismissed_Site_Kit_Configuration_Repository implements Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface {
|
||||
|
||||
/**
|
||||
* Holds the Options_Helper instance.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Site Kit dismissal status.
|
||||
*
|
||||
* @param bool $is_dismissed The dismissal status.
|
||||
*
|
||||
* @return bool False when the update failed, true when the update succeeded.
|
||||
*/
|
||||
public function set_site_kit_configuration_dismissal( bool $is_dismissed ): bool {
|
||||
return $this->options_helper->set( 'site_kit_configuration_permanently_dismissed', $is_dismissed );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Site Kit configuration is dismissed permanently.
|
||||
* *
|
||||
*
|
||||
* @return bool True when the configuration is dismissed, false when it is not.
|
||||
*/
|
||||
public function is_site_kit_configuration_dismissed(): bool {
|
||||
return $this->options_helper->get( 'site_kit_configuration_permanently_dismissed', false );
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Configuration;
|
||||
|
||||
/**
|
||||
* Interface for theSite Kit Consent Repository.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
interface Site_Kit_Consent_Repository_Interface {
|
||||
|
||||
/**
|
||||
* Sets the Site Kit consent.
|
||||
*
|
||||
* @param bool $consent The consent value.
|
||||
*
|
||||
* @return bool False when the update failed, true when the update succeeded.
|
||||
*/
|
||||
public function set_site_kit_consent( bool $consent ): bool;
|
||||
|
||||
/**
|
||||
* Returns the Site Kit consent status.
|
||||
* *
|
||||
*
|
||||
* @return bool True when the consent has been granted, false when it is not.
|
||||
*/
|
||||
public function is_consent_granted(): bool;
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Configuration;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
|
||||
/**
|
||||
* Stores and retrieves the Site Kit consent status.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Site_Kit_Consent_Repository implements Site_Kit_Consent_Repository_Interface {
|
||||
|
||||
/**
|
||||
* Holds the Options_Helper instance.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Site Kit consent value.
|
||||
*
|
||||
* @param bool $consent The consent status.
|
||||
*
|
||||
* @return bool False when the update failed, true when the update succeeded.
|
||||
*/
|
||||
public function set_site_kit_consent( bool $consent ): bool {
|
||||
return $this->options_helper->set( 'site_kit_connected', $consent );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if consent has ben given for Site Kit.
|
||||
* *
|
||||
*
|
||||
* @return bool True when consent has been given, false when it is not.
|
||||
*/
|
||||
public function is_consent_granted(): bool {
|
||||
return $this->options_helper->get( 'site_kit_connected', false );
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Connection;
|
||||
|
||||
use Google\Site_Kit\Core\REST_API\REST_Routes;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* Class that hold the code to do the REST call to the Site Kit api.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Site_Kit_Is_Connected_Call {
|
||||
|
||||
/**
|
||||
* Runs the internal REST api call.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_setup_completed(): bool {
|
||||
if ( ! \class_exists( REST_Routes::class ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/site/data/connection' );
|
||||
|
||||
$response = \rest_do_request( $request );
|
||||
|
||||
if ( $response->is_error() ) {
|
||||
return false;
|
||||
}
|
||||
return $response->get_data()['setupCompleted'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the internal REST api call.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_ga_connected(): bool {
|
||||
if ( ! \class_exists( REST_Routes::class ) ) {
|
||||
return false;
|
||||
}
|
||||
$request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/modules/data/list' );
|
||||
$response = \rest_do_request( $request );
|
||||
|
||||
if ( $response->is_error() ) {
|
||||
return false;
|
||||
}
|
||||
$connected = false;
|
||||
foreach ( $response->get_data() as $module ) {
|
||||
if ( $module['slug'] === 'analytics-4' ) {
|
||||
$connected = $module['connected'];
|
||||
}
|
||||
}
|
||||
|
||||
return $connected;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Types_List;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
|
||||
/**
|
||||
* Class that collects post types and relevant information.
|
||||
*/
|
||||
class Content_Types_Collector {
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
private $post_type_helper;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
*/
|
||||
public function __construct( Post_Type_Helper $post_type_helper ) {
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content types in a list.
|
||||
*
|
||||
* @return Content_Types_List The content types in a list.
|
||||
*/
|
||||
public function get_content_types(): Content_Types_List {
|
||||
$content_types_list = new Content_Types_List();
|
||||
$post_types = $this->post_type_helper->get_indexable_post_type_objects();
|
||||
|
||||
foreach ( $post_types as $post_type_object ) {
|
||||
if ( $post_type_object->show_ui === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content_type = new Content_Type( $post_type_object->name, $post_type_object->label );
|
||||
$content_types_list->add( $content_type );
|
||||
}
|
||||
|
||||
return $content_types_list;
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints;
|
||||
|
||||
use Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Scores\Abstract_Scores_Route;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Scores\Readability_Scores_Route;
|
||||
|
||||
/**
|
||||
* Represents the readability scores endpoint.
|
||||
*/
|
||||
class Readability_Scores_Endpoint implements Endpoint_Interface {
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'readabilityScores';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace(): string {
|
||||
return Abstract_Scores_Route::ROUTE_NAMESPACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If the route prefix is not overwritten this throws.
|
||||
*/
|
||||
public function get_route(): string {
|
||||
return Readability_Scores_Route::get_route_prefix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url(): string {
|
||||
return \rest_url( $this->get_namespace() . $this->get_route() );
|
||||
}
|
||||
}
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints;
|
||||
|
||||
use Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Scores\Abstract_Scores_Route;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Scores\SEO_Scores_Route;
|
||||
|
||||
/**
|
||||
* Represents the SEO scores endpoint.
|
||||
*/
|
||||
class SEO_Scores_Endpoint implements Endpoint_Interface {
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'seoScores';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace(): string {
|
||||
return Abstract_Scores_Route::ROUTE_NAMESPACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If the route prefix is not overwritten this throws.
|
||||
*/
|
||||
public function get_route(): string {
|
||||
return SEO_Scores_Route::get_route_prefix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url(): string {
|
||||
return \rest_url( $this->get_namespace() . $this->get_route() );
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints;
|
||||
|
||||
use Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Tracking\Setup_Steps_Tracking_Route;
|
||||
|
||||
/**
|
||||
* Represents the setup steps tracking endpoint.
|
||||
*/
|
||||
class Setup_Steps_Tracking_Endpoint implements Endpoint_Interface {
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'setupStepsTracking';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace(): string {
|
||||
return Setup_Steps_Tracking_Route::ROUTE_NAMESPACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route.
|
||||
*
|
||||
* @throws Exception If the route prefix is not overwritten this throws.
|
||||
* @return string
|
||||
*/
|
||||
public function get_route(): string {
|
||||
return Setup_Steps_Tracking_Route::ROUTE_PREFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url(): string {
|
||||
return \rest_url( $this->get_namespace() . $this->get_route() );
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints;
|
||||
|
||||
use Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Configuration_Dismissal_Route;
|
||||
|
||||
/**
|
||||
* Represents the readability scores endpoint.
|
||||
*/
|
||||
class Site_Kit_Configuration_Dismissal_Endpoint implements Endpoint_Interface {
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'siteKitConfigurationDismissal';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace(): string {
|
||||
return Site_Kit_Configuration_Dismissal_Route::ROUTE_NAMESPACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If the route prefix is not overwritten this throws.
|
||||
*/
|
||||
public function get_route(): string {
|
||||
return Site_Kit_Configuration_Dismissal_Route::ROUTE_PREFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url(): string {
|
||||
return \rest_url( $this->get_namespace() . $this->get_route() );
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints;
|
||||
|
||||
use Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Consent_Management_Route;
|
||||
|
||||
/**
|
||||
* Represents the Site Kit consent management endpoint.
|
||||
*/
|
||||
class Site_Kit_Consent_Management_Endpoint implements Endpoint_Interface {
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'siteKitConsentManagement';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace(): string {
|
||||
return Site_Kit_Consent_Management_Route::ROUTE_NAMESPACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If the route prefix is not overwritten this throws.
|
||||
*/
|
||||
public function get_route(): string {
|
||||
return Site_Kit_Consent_Management_Route::ROUTE_PREFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url(): string {
|
||||
return \rest_url( $this->get_namespace() . $this->get_route() );
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Time_Based_SEO_Metrics\Time_Based_SEO_Metrics_Route;
|
||||
|
||||
/**
|
||||
* Represents the time based SEO metrics endpoint.
|
||||
*/
|
||||
class Time_Based_SEO_Metrics_Endpoint implements Endpoint_Interface {
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'timeBasedSeoMetrics';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace(): string {
|
||||
return Time_Based_SEO_Metrics_Route::ROUTE_NAMESPACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_route(): string {
|
||||
return Time_Based_SEO_Metrics_Route::ROUTE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url(): string {
|
||||
return \rest_url( $this->get_namespace() . $this->get_route() );
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Indexables;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Application\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Top_Page_Data;
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* The indexable collector that gets SEO scores from the indexables of top pages.
|
||||
*/
|
||||
class Top_Page_Indexable_Collector {
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* The SEO score groups repository.
|
||||
*
|
||||
* @var SEO_Score_Groups_Repository
|
||||
*/
|
||||
private $seo_score_groups_repository;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
* @param SEO_Score_Groups_Repository $seo_score_groups_repository The SEO score groups repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Indexable_Repository $indexable_repository,
|
||||
SEO_Score_Groups_Repository $seo_score_groups_repository
|
||||
) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
$this->seo_score_groups_repository = $seo_score_groups_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets full data for top pages.
|
||||
*
|
||||
* @param Data_Container $top_pages The top pages.
|
||||
*
|
||||
* @return Data_Container Data about SEO scores of top pages.
|
||||
*/
|
||||
public function get_data( Data_Container $top_pages ): Data_Container {
|
||||
$top_page_data_container = new Data_Container();
|
||||
|
||||
foreach ( $top_pages->get_data() as $top_page ) {
|
||||
$url = $top_page->get_subject();
|
||||
|
||||
$indexable = $this->get_top_page_indexable( $url );
|
||||
|
||||
if ( $indexable instanceof Indexable ) {
|
||||
$seo_score_group = $this->seo_score_groups_repository->get_seo_score_group( $indexable->primary_focus_keyword_score );
|
||||
$edit_link = $this->get_top_page_edit_link( $indexable );
|
||||
|
||||
$top_page_data_container->add_data( new Top_Page_Data( $top_page, $seo_score_group, $edit_link ) );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$seo_score_group = new No_SEO_Score_Group();
|
||||
$top_page_data_container->add_data( new Top_Page_Data( $top_page, $seo_score_group ) );
|
||||
}
|
||||
|
||||
return $top_page_data_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets indexable for a top page URL.
|
||||
*
|
||||
* @param string $url The URL of the top page.
|
||||
*
|
||||
* @return bool|Indexable The indexable of the top page URL or false if there is none.
|
||||
*/
|
||||
protected function get_top_page_indexable( string $url ) {
|
||||
// First check if the URL is the static homepage.
|
||||
if ( \trailingslashit( $url ) === \trailingslashit( \get_home_url() ) && \get_option( 'show_on_front' ) === 'page' ) {
|
||||
return $this->indexable_repository->find_by_id_and_type( \get_option( 'page_on_front' ), 'post', false );
|
||||
}
|
||||
|
||||
return $this->indexable_repository->find_by_permalink( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets edit links from a top page's indexable.
|
||||
*
|
||||
* @param Indexable $indexable The top page's indexable.
|
||||
*
|
||||
* @return string|null The edit link for the top page.
|
||||
*/
|
||||
protected function get_top_page_edit_link( Indexable $indexable ): ?string {
|
||||
if ( $indexable->object_type === 'post' && \current_user_can( 'edit_post', $indexable->object_id ) ) {
|
||||
return \get_edit_post_link( $indexable->object_id, '&' );
|
||||
}
|
||||
|
||||
if ( $indexable->object_type === 'term' && \current_user_can( 'edit_term', $indexable->object_id ) ) {
|
||||
return \get_edit_term_link( $indexable->object_id );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Integrations;
|
||||
|
||||
use Google\Site_Kit\Core\REST_API\REST_Routes;
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface as Configuration_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Connection\Site_Kit_Is_Connected_Call;
|
||||
use Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Url_Interceptor;
|
||||
|
||||
/**
|
||||
* Describes if the Site kit integration is enabled and configured.
|
||||
*/
|
||||
class Site_Kit {
|
||||
|
||||
private const SITE_KIT_FILE = 'google-site-kit/google-site-kit.php';
|
||||
|
||||
/**
|
||||
* The Site Kit feature conditional.
|
||||
*
|
||||
* @var Google_Site_Kit_Feature_Conditional
|
||||
*/
|
||||
protected $site_kit_feature_conditional;
|
||||
|
||||
/**
|
||||
* The Site Kit conditional.
|
||||
*
|
||||
* @var Site_Kit_Conditional
|
||||
*/
|
||||
private $site_kit_conditional;
|
||||
|
||||
/**
|
||||
* The Site Kit consent repository.
|
||||
*
|
||||
* @var Site_Kit_Consent_Repository_Interface
|
||||
*/
|
||||
private $site_kit_consent_repository;
|
||||
|
||||
/**
|
||||
* The Site Kit consent repository.
|
||||
*
|
||||
* @var Configuration_Repository
|
||||
*/
|
||||
private $permanently_dismissed_site_kit_configuration_repository;
|
||||
|
||||
/**
|
||||
* The call wrapper.
|
||||
*
|
||||
* @var Site_Kit_Is_Connected_Call $site_kit_is_connected_call
|
||||
*/
|
||||
private $site_kit_is_connected_call;
|
||||
|
||||
/**
|
||||
* The search console module data.
|
||||
*
|
||||
* @var array<string, bool> $search_console_module
|
||||
*/
|
||||
private $search_console_module = [
|
||||
'can_view' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* The analytics module data.
|
||||
*
|
||||
* @var array<string, bool> $ga_module
|
||||
*/
|
||||
private $ga_module = [
|
||||
'can_view' => null,
|
||||
'connected' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Site_Kit_Consent_Repository_Interface $site_kit_consent_repository The Site Kit consent repository.
|
||||
* @param Configuration_Repository $configuration_repository The Site Kit permanently dismissed
|
||||
* configuration repository.
|
||||
* @param Site_Kit_Is_Connected_Call $site_kit_is_connected_call The api call to check if the site is
|
||||
* connected.
|
||||
* @param Google_Site_Kit_Feature_Conditional $site_kit_feature_conditional The Site Kit feature conditional.
|
||||
* @param Site_Kit_Conditional $site_kit_conditional The Site Kit conditional.
|
||||
*/
|
||||
public function __construct(
|
||||
Site_Kit_Consent_Repository_Interface $site_kit_consent_repository,
|
||||
Configuration_Repository $configuration_repository,
|
||||
Site_Kit_Is_Connected_Call $site_kit_is_connected_call,
|
||||
Google_Site_Kit_Feature_Conditional $site_kit_feature_conditional,
|
||||
Site_Kit_Conditional $site_kit_conditional
|
||||
) {
|
||||
$this->site_kit_consent_repository = $site_kit_consent_repository;
|
||||
$this->permanently_dismissed_site_kit_configuration_repository = $configuration_repository;
|
||||
$this->site_kit_is_connected_call = $site_kit_is_connected_call;
|
||||
$this->site_kit_feature_conditional = $site_kit_feature_conditional;
|
||||
$this->site_kit_conditional = $site_kit_conditional;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Site Kit plugin is active.
|
||||
*
|
||||
* @return bool If the integration is activated.
|
||||
*/
|
||||
public function is_enabled(): bool {
|
||||
return $this->site_kit_conditional->is_met();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Google site kit setup has been completed.
|
||||
*
|
||||
* @return bool If the Google site kit setup has been completed.
|
||||
*/
|
||||
private function is_setup_completed(): bool {
|
||||
return $this->site_kit_is_connected_call->is_setup_completed();
|
||||
}
|
||||
|
||||
/**
|
||||
* If consent has been granted.
|
||||
*
|
||||
* @return bool If consent has been granted.
|
||||
*/
|
||||
private function is_connected(): bool {
|
||||
return $this->site_kit_consent_repository->is_consent_granted();
|
||||
}
|
||||
|
||||
/**
|
||||
* If Google Analytics is connected.
|
||||
*
|
||||
* @return bool If Google Analytics is connected.
|
||||
*/
|
||||
public function is_ga_connected(): bool {
|
||||
if ( $this->ga_module['connected'] !== null ) {
|
||||
return $this->ga_module['connected'];
|
||||
}
|
||||
|
||||
return $this->site_kit_is_connected_call->is_ga_connected();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Site Kit plugin is installed. This is needed since we cannot check with `is_plugin_active` in rest
|
||||
* requests. `Plugin.php` is only loaded on admin pages.
|
||||
*
|
||||
* @return bool If the Site Kit plugin is installed.
|
||||
*/
|
||||
private function is_site_kit_installed(): bool {
|
||||
return \class_exists( 'Google\Site_Kit\Plugin' );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the entire onboarding has been completed.
|
||||
*
|
||||
* @return bool If the entire onboarding has been completed.
|
||||
*/
|
||||
public function is_onboarded(): bool {
|
||||
// @TODO: Consider replacing the `is_setup_completed()` check with a `can_read_data( $module )` check (and possibly rename the method to something more genric eg. is_ready() ).
|
||||
return ( $this->is_site_kit_installed() && $this->is_setup_completed() && $this->is_connected() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current user can view dashboard data for a module
|
||||
*
|
||||
* @param array<array|null> $module The module.
|
||||
*
|
||||
* @return bool If the user can read the data.
|
||||
*/
|
||||
private function can_read_data( array $module ): bool {
|
||||
return ( ! \is_null( $module['can_view'] ) ? $module['can_view'] : false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this object represented by a key value array.
|
||||
*
|
||||
* @return array<string, bool> Returns the name and if the feature is enabled.
|
||||
*/
|
||||
public function to_array(): array {
|
||||
if ( ! $this->site_kit_feature_conditional->is_met() ) {
|
||||
return [];
|
||||
}
|
||||
if ( $this->is_enabled() ) {
|
||||
$this->parse_site_kit_data();
|
||||
}
|
||||
return [
|
||||
'installUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_install_url() ),
|
||||
'activateUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_activate_url() ),
|
||||
'setupUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_setup_url() ),
|
||||
'updateUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_update_url() ),
|
||||
'dashboardUrl' => \self_admin_url( 'admin.php?page=googlesitekit-dashboard' ),
|
||||
'isAnalyticsConnected' => $this->is_ga_connected(),
|
||||
'isFeatureEnabled' => true,
|
||||
'isSetupWidgetDismissed' => $this->permanently_dismissed_site_kit_configuration_repository->is_site_kit_configuration_dismissed(),
|
||||
'capabilities' => [
|
||||
'installPlugins' => \current_user_can( 'install_plugins' ),
|
||||
'viewSearchConsoleData' => $this->can_read_data( $this->search_console_module ),
|
||||
'viewAnalyticsData' => $this->can_read_data( $this->ga_module ),
|
||||
],
|
||||
'connectionStepsStatuses' => [
|
||||
'isInstalled' => \file_exists( \WP_PLUGIN_DIR . '/' . self::SITE_KIT_FILE ),
|
||||
'isActive' => $this->is_enabled(),
|
||||
'isSetupCompleted' => $this->can_read_data( $this->search_console_module ) || $this->can_read_data( $this->ga_module ),
|
||||
'isConsentGranted' => $this->is_connected(),
|
||||
],
|
||||
'isVersionSupported' => \defined( 'GOOGLESITEKIT_VERSION' ) ? \version_compare( \GOOGLESITEKIT_VERSION, '1.148.0', '>=' ) : false,
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
'isRedirectedFromSiteKit' => isset( $_GET['redirected_from_site_kit'] ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this object represented by a key value array. This is not used yet.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array<string, bool> Returns the name and if the feature is enabled.
|
||||
*/
|
||||
public function to_legacy_array(): array {
|
||||
return $this->to_array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the Site Kit configuration data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parse_site_kit_data(): void {
|
||||
$paths = $this->get_preload_paths();
|
||||
$preloaded = $this->get_preloaded_data( $paths );
|
||||
if ( empty( $preloaded ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$modules_data = ! empty( $preloaded[ $paths['modules'] ]['body'] ) ? $preloaded[ $paths['modules'] ]['body'] : [];
|
||||
$modules_permissions = ! empty( $preloaded[ $paths['permissions'] ]['body'] ) ? $preloaded[ $paths['permissions'] ]['body'] : [];
|
||||
|
||||
$can_view_dashboard = ( $modules_permissions['googlesitekit_view_authenticated_dashboard'] ?? false );
|
||||
|
||||
foreach ( $modules_data as $module ) {
|
||||
$slug = $module['slug'];
|
||||
// We have to also check if the module is recoverable, because if we rely on the module being shared, we have to make also sure the module owner is still connected.
|
||||
$is_recoverable = ( $module['recoverable'] ?? null );
|
||||
|
||||
if ( $slug === 'analytics-4' ) {
|
||||
$can_read_shared_module_data = ( $modules_permissions['googlesitekit_read_shared_module_data::["analytics-4"]'] ?? false );
|
||||
|
||||
$this->ga_module['can_view'] = $can_view_dashboard || ( $can_read_shared_module_data && ! $is_recoverable );
|
||||
$this->ga_module['connected'] = ( $module['connected'] ?? false );
|
||||
}
|
||||
|
||||
if ( $slug === 'search-console' ) {
|
||||
$can_read_shared_module_data = ( $modules_permissions['googlesitekit_read_shared_module_data::["search-console"]'] ?? false );
|
||||
|
||||
$this->search_console_module['can_view'] = $can_view_dashboard || ( $can_read_shared_module_data && ! $is_recoverable );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the parsed preload paths for preloading some Site Kit API data.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_preload_paths(): array {
|
||||
|
||||
$rest_root = ( \class_exists( REST_Routes::class ) ) ? REST_Routes::REST_ROOT : '';
|
||||
|
||||
return [
|
||||
'permissions' => '/' . $rest_root . '/core/user/data/permissions',
|
||||
'modules' => '/' . $rest_root . '/core/modules/data/list',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given paths through the `rest_preload_api_request` method.
|
||||
*
|
||||
* @param string[] $paths The paths to add to `rest_preload_api_request`.
|
||||
*
|
||||
* @return array<array|null> The array with all the now filled in preloaded data.
|
||||
*/
|
||||
public function get_preloaded_data( array $paths ): array {
|
||||
$preload_paths = \apply_filters( 'googlesitekit_apifetch_preload_paths', [] );
|
||||
$actual_paths = \array_intersect( $paths, $preload_paths );
|
||||
|
||||
return \array_reduce(
|
||||
\array_unique( $actual_paths ),
|
||||
'rest_preload_api_request',
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid activation URL for the Site Kit plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_activate_url(): string {
|
||||
return \html_entity_decode(
|
||||
\wp_nonce_url(
|
||||
\self_admin_url( 'plugins.php?action=activate&plugin=' . self::SITE_KIT_FILE ),
|
||||
'activate-plugin_' . self::SITE_KIT_FILE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid install URL for the Site Kit plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_install_url(): string {
|
||||
return \html_entity_decode(
|
||||
\wp_nonce_url(
|
||||
\self_admin_url( 'update.php?action=install-plugin&plugin=google-site-kit' ),
|
||||
'install-plugin_google-site-kit'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid update URL for the Site Kit plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_update_url(): string {
|
||||
return \html_entity_decode(
|
||||
\wp_nonce_url(
|
||||
\self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . self::SITE_KIT_FILE ),
|
||||
'upgrade-plugin_' . self::SITE_KIT_FILE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid setup URL for the Site Kit plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_setup_url(): string {
|
||||
return \self_admin_url( 'admin.php?page=googlesitekit-splash' );
|
||||
}
|
||||
}
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Nonces;
|
||||
|
||||
/**
|
||||
* Repository for WP nonces.
|
||||
*/
|
||||
class Nonce_Repository {
|
||||
|
||||
/**
|
||||
* Creates the nonce for a WP REST request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_rest_nonce(): string {
|
||||
return \wp_create_nonce( 'wp_rest' );
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Groups;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
|
||||
|
||||
/**
|
||||
* Getting links for score groups.
|
||||
*/
|
||||
class Score_Group_Link_Collector {
|
||||
|
||||
/**
|
||||
* Builds the view link of the score group.
|
||||
*
|
||||
* @param Score_Groups_Interface $score_group The score group.
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param Taxonomy|null $taxonomy The taxonomy of the term we might be filtering.
|
||||
* @param int|null $term_id The ID of the term we might be filtering.
|
||||
*
|
||||
* @return string|null The view link of the score.
|
||||
*/
|
||||
public function get_view_link( Score_Groups_Interface $score_group, Content_Type $content_type, ?Taxonomy $taxonomy, ?int $term_id ): ?string {
|
||||
$posts_page = \admin_url( 'edit.php' );
|
||||
$args = [
|
||||
'post_status' => 'publish',
|
||||
'post_type' => $content_type->get_name(),
|
||||
$score_group->get_filter_key() => $score_group->get_filter_value(),
|
||||
];
|
||||
|
||||
if ( $taxonomy === null || $term_id === null ) {
|
||||
return \add_query_arg( $args, $posts_page );
|
||||
}
|
||||
|
||||
$taxonomy_object = \get_taxonomy( $taxonomy->get_name() );
|
||||
$query_var = $taxonomy_object->query_var;
|
||||
|
||||
if ( ! $query_var ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$term = \get_term( $term_id );
|
||||
$args[ $query_var ] = $term->slug;
|
||||
|
||||
return \add_query_arg( $args, $posts_page );
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Readability_Score_Results;
|
||||
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Readability_Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Results_Not_Found_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface;
|
||||
|
||||
/**
|
||||
* The caching decorator to get readability score results.
|
||||
*/
|
||||
class Cached_Readability_Score_Results_Collector implements Score_Results_Collector_Interface {
|
||||
|
||||
public const READABILITY_SCORES_TRANSIENT = 'wpseo_readability_scores';
|
||||
|
||||
/**
|
||||
* The actual collector implementation.
|
||||
*
|
||||
* @var Readability_Score_Results_Collector
|
||||
*/
|
||||
private $readability_score_results_collector;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Readability_Score_Results_Collector $readability_score_results_collector The collector implementation to
|
||||
* use.
|
||||
*/
|
||||
public function __construct( Readability_Score_Results_Collector $readability_score_results_collector ) {
|
||||
$this->readability_score_results_collector = $readability_score_results_collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves readability score results for a content type.
|
||||
* Based on caching returns either the result or gets it from the collector.
|
||||
*
|
||||
* @param Readability_Score_Groups_Interface[] $score_groups All readability score groups.
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param int|null $term_id The ID of the term we're filtering for.
|
||||
* @param bool|null $is_troubleshooting Whether we're in troubleshooting mode.
|
||||
*
|
||||
* @return array<string, object|bool|float> The readability score results for a content type.
|
||||
*
|
||||
* @throws Score_Results_Not_Found_Exception When the query of getting score results fails.
|
||||
*/
|
||||
public function get_score_results(
|
||||
array $score_groups,
|
||||
Content_Type $content_type,
|
||||
?int $term_id,
|
||||
?bool $is_troubleshooting
|
||||
) {
|
||||
$content_type_name = $content_type->get_name();
|
||||
$transient_name = self::READABILITY_SCORES_TRANSIENT . '_' . $content_type_name . ( ( $term_id === null ) ? '' : '_' . $term_id );
|
||||
|
||||
$results = [];
|
||||
$transient = \get_transient( $transient_name );
|
||||
if ( $is_troubleshooting !== true && $transient !== false ) {
|
||||
$results['scores'] = \json_decode( $transient, false );
|
||||
$results['cache_used'] = true;
|
||||
$results['query_time'] = 0;
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
$results = $this->readability_score_results_collector->get_score_results( $score_groups, $content_type, $term_id, $is_troubleshooting );
|
||||
$results['cache_used'] = false;
|
||||
if ( $is_troubleshooting !== true ) {
|
||||
\set_transient( $transient_name, WPSEO_Utils::format_json_encode( $results['scores'] ), \MINUTE_IN_SECONDS );
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Readability_Score_Results;
|
||||
|
||||
use Yoast\WP\Lib\Model;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Readability_Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Results_Not_Found_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface;
|
||||
|
||||
/**
|
||||
* Getting readability score results from the indexable database table.
|
||||
*/
|
||||
class Readability_Score_Results_Collector implements Score_Results_Collector_Interface {
|
||||
|
||||
/**
|
||||
* Retrieves readability score results for a content type.
|
||||
*
|
||||
* @param Readability_Score_Groups_Interface[] $readability_score_groups All readability score groups.
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param int|null $term_id The ID of the term we're filtering for.
|
||||
* @param bool|null $is_troubleshooting Whether we're in troubleshooting mode.
|
||||
*
|
||||
* @return array<string, object|bool|float> The readability score results for a content type.
|
||||
*
|
||||
* @throws Score_Results_Not_Found_Exception When the query of getting score results fails.
|
||||
*/
|
||||
public function get_score_results( array $readability_score_groups, Content_Type $content_type, ?int $term_id, ?bool $is_troubleshooting ) {
|
||||
global $wpdb;
|
||||
$results = [];
|
||||
|
||||
$content_type_name = $content_type->get_name();
|
||||
$select = $this->build_select( $readability_score_groups, $is_troubleshooting );
|
||||
|
||||
$replacements = \array_merge(
|
||||
\array_values( $select['replacements'] ),
|
||||
[
|
||||
Model::get_table_name( 'Indexable' ),
|
||||
$content_type_name,
|
||||
]
|
||||
);
|
||||
|
||||
if ( $term_id === null ) {
|
||||
//phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $replacements is an array with the correct replacements.
|
||||
//phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select['fields'] is an array of simple strings with placeholders.
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT {$select['fields']}
|
||||
FROM %i AS I
|
||||
WHERE ( I.post_status = 'publish' OR I.post_status IS NULL )
|
||||
AND I.object_type = 'post'
|
||||
AND I.object_sub_type = %s",
|
||||
$replacements
|
||||
);
|
||||
//phpcs:enable
|
||||
}
|
||||
else {
|
||||
$replacements[] = $wpdb->term_relationships;
|
||||
$replacements[] = $term_id;
|
||||
|
||||
//phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $replacements is an array with the correct replacements.
|
||||
//phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select['fields'] is an array of simple strings with placeholders.
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT {$select['fields']}
|
||||
FROM %i AS I
|
||||
WHERE ( I.post_status = 'publish' OR I.post_status IS NULL )
|
||||
AND I.object_type = 'post'
|
||||
AND I.object_sub_type = %s
|
||||
AND I.object_id IN (
|
||||
SELECT object_id
|
||||
FROM %i
|
||||
WHERE term_taxonomy_id = %d
|
||||
)",
|
||||
$replacements
|
||||
);
|
||||
//phpcs:enable
|
||||
}
|
||||
|
||||
$start_time = \microtime( true );
|
||||
|
||||
//phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- $query is prepared above.
|
||||
//phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
|
||||
//phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
|
||||
$current_scores = $wpdb->get_row( $query );
|
||||
//phpcs:enable
|
||||
|
||||
if ( $current_scores === null ) {
|
||||
throw new Score_Results_Not_Found_Exception();
|
||||
}
|
||||
|
||||
$end_time = \microtime( true );
|
||||
|
||||
$results['scores'] = $current_scores;
|
||||
$results['query_time'] = ( $end_time - $start_time );
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the select statement for the readability scores query.
|
||||
*
|
||||
* @param Readability_Score_Groups_Interface[] $readability_score_groups All readability score groups.
|
||||
* @param bool|null $is_troubleshooting Whether we're in troubleshooting mode.
|
||||
*
|
||||
* @return array<string, string> The select statement for the readability scores query.
|
||||
*/
|
||||
private function build_select( array $readability_score_groups, ?bool $is_troubleshooting ): array {
|
||||
$select_fields = [];
|
||||
$select_replacements = [];
|
||||
|
||||
// When we don't troubleshoot, we're interested in the amount of posts in a group, when we troubleshoot we want to gather the actual IDs.
|
||||
$select_operation = ( $is_troubleshooting === true ) ? 'GROUP_CONCAT' : 'COUNT';
|
||||
$selected_info = ( $is_troubleshooting === true ) ? 'I.object_id' : '1';
|
||||
|
||||
foreach ( $readability_score_groups as $readability_score_group ) {
|
||||
$min = $readability_score_group->get_min_score();
|
||||
$max = $readability_score_group->get_max_score();
|
||||
$name = $readability_score_group->get_name();
|
||||
|
||||
if ( $min === null && $max === null ) {
|
||||
$select_fields[] = "{$select_operation}(CASE WHEN I.readability_score = 0 AND I.estimated_reading_time_minutes IS NULL THEN {$selected_info} END) AS %i";
|
||||
$select_replacements[] = $name;
|
||||
}
|
||||
else {
|
||||
$needs_ert = ( $min === 1 ) ? ' OR (I.readability_score = 0 AND I.estimated_reading_time_minutes IS NOT NULL)' : '';
|
||||
$select_fields[] = "{$select_operation}(CASE WHEN ( I.readability_score >= %d AND I.readability_score <= %d ){$needs_ert} THEN {$selected_info} END) AS %i";
|
||||
$select_replacements[] = $min;
|
||||
$select_replacements[] = $max;
|
||||
$select_replacements[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$select_fields = \implode( ', ', $select_fields );
|
||||
|
||||
return [
|
||||
'fields' => $select_fields,
|
||||
'replacements' => $select_replacements,
|
||||
];
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface;
|
||||
|
||||
/**
|
||||
* The interface of score result collectors.
|
||||
*/
|
||||
interface Score_Results_Collector_Interface {
|
||||
|
||||
/**
|
||||
* Retrieves the score results for a content type.
|
||||
*
|
||||
* @param Score_Groups_Interface[] $score_groups All score groups.
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param int|null $term_id The ID of the term we're filtering for.
|
||||
* @param bool|null $is_troubleshooting Whether we're in troubleshooting mode.
|
||||
*
|
||||
* @return array<string, string> The score results for a content type.
|
||||
*/
|
||||
public function get_score_results( array $score_groups, Content_Type $content_type, ?int $term_id, ?bool $is_troubleshooting );
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\SEO_Score_Results;
|
||||
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Results_Not_Found_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface;
|
||||
|
||||
/**
|
||||
* The caching decorator to get readability score results.
|
||||
*/
|
||||
class Cached_SEO_Score_Results_Collector implements Score_Results_Collector_Interface {
|
||||
|
||||
public const SEO_SCORES_TRANSIENT = 'wpseo_seo_scores';
|
||||
|
||||
/**
|
||||
* The actual collector implementation.
|
||||
*
|
||||
* @var SEO_Score_Results_Collector
|
||||
*/
|
||||
private $seo_score_results_collector;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param SEO_Score_Results_Collector $seo_score_results_collector The collector implementation to use.
|
||||
*/
|
||||
public function __construct( SEO_Score_Results_Collector $seo_score_results_collector ) {
|
||||
$this->seo_score_results_collector = $seo_score_results_collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the SEO score results for a content type.
|
||||
* Based on caching returns either the result or gets it from the collector.
|
||||
*
|
||||
* @param SEO_Score_Groups_Interface[] $score_groups All SEO score groups.
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param int|null $term_id The ID of the term we're filtering for.
|
||||
* @param bool|null $is_troubleshooting Whether we're in troubleshooting mode.
|
||||
*
|
||||
* @return array<string, object|bool|float> The SEO score results for a content type.
|
||||
*
|
||||
* @throws Score_Results_Not_Found_Exception When the query of getting score results fails.
|
||||
*/
|
||||
public function get_score_results(
|
||||
array $score_groups,
|
||||
Content_Type $content_type,
|
||||
?int $term_id,
|
||||
?bool $is_troubleshooting
|
||||
) {
|
||||
$content_type_name = $content_type->get_name();
|
||||
$transient_name = self::SEO_SCORES_TRANSIENT . '_' . $content_type_name . ( ( $term_id === null ) ? '' : '_' . $term_id );
|
||||
$results = [];
|
||||
$transient = \get_transient( $transient_name );
|
||||
if ( $is_troubleshooting !== true && $transient !== false ) {
|
||||
$results['scores'] = \json_decode( $transient, false );
|
||||
$results['cache_used'] = true;
|
||||
$results['query_time'] = 0;
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
$results = $this->seo_score_results_collector->get_score_results( $score_groups, $content_type, $term_id, $is_troubleshooting );
|
||||
$results['cache_used'] = false;
|
||||
if ( $is_troubleshooting !== true ) {
|
||||
\set_transient( $transient_name, WPSEO_Utils::format_json_encode( $results['scores'] ), \MINUTE_IN_SECONDS );
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\SEO_Score_Results;
|
||||
|
||||
use Yoast\WP\Lib\Model;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Results_Not_Found_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface;
|
||||
|
||||
/**
|
||||
* Getting SEO score results from the indexable database table.
|
||||
*/
|
||||
class SEO_Score_Results_Collector implements Score_Results_Collector_Interface {
|
||||
|
||||
/**
|
||||
* Retrieves the SEO score results for a content type.
|
||||
*
|
||||
* @param SEO_Score_Groups_Interface[] $seo_score_groups All SEO score groups.
|
||||
* @param Content_Type $content_type The content type.
|
||||
* @param int|null $term_id The ID of the term we're filtering for.
|
||||
* @param bool|null $is_troubleshooting Whether we're in troubleshooting mode.
|
||||
*
|
||||
* @return array<string, object|bool|float> The SEO score results for a content type.
|
||||
*
|
||||
* @throws Score_Results_Not_Found_Exception When the query of getting score results fails.
|
||||
*/
|
||||
public function get_score_results( array $seo_score_groups, Content_Type $content_type, ?int $term_id, ?bool $is_troubleshooting ): array {
|
||||
global $wpdb;
|
||||
$results = [];
|
||||
|
||||
$content_type_name = $content_type->get_name();
|
||||
$select = $this->build_select( $seo_score_groups, $is_troubleshooting );
|
||||
|
||||
$replacements = \array_merge(
|
||||
\array_values( $select['replacements'] ),
|
||||
[
|
||||
Model::get_table_name( 'Indexable' ),
|
||||
$content_type_name,
|
||||
]
|
||||
);
|
||||
|
||||
if ( $term_id === null ) {
|
||||
//phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $replacements is an array with the correct replacements.
|
||||
//phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select['fields'] is an array of simple strings with placeholders.
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT {$select['fields']}
|
||||
FROM %i AS I
|
||||
WHERE ( I.post_status = 'publish' OR I.post_status IS NULL )
|
||||
AND I.object_type = 'post'
|
||||
AND I.object_sub_type = %s
|
||||
AND ( I.is_robots_noindex IS NULL OR I.is_robots_noindex <> 1 )",
|
||||
$replacements
|
||||
);
|
||||
//phpcs:enable
|
||||
}
|
||||
else {
|
||||
$replacements[] = $wpdb->term_relationships;
|
||||
$replacements[] = $term_id;
|
||||
|
||||
//phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $replacements is an array with the correct replacements.
|
||||
//phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select['fields'] is an array of simple strings with placeholders.
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT {$select['fields']}
|
||||
FROM %i AS I
|
||||
WHERE ( I.post_status = 'publish' OR I.post_status IS NULL )
|
||||
AND I.object_type IN ('post')
|
||||
AND I.object_sub_type = %s
|
||||
AND ( I.is_robots_noindex IS NULL OR I.is_robots_noindex <> 1 )
|
||||
AND I.object_id IN (
|
||||
SELECT object_id
|
||||
FROM %i
|
||||
WHERE term_taxonomy_id = %d
|
||||
)",
|
||||
$replacements
|
||||
);
|
||||
//phpcs:enable
|
||||
}
|
||||
|
||||
$start_time = \microtime( true );
|
||||
|
||||
//phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- $query is prepared above.
|
||||
//phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
|
||||
//phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
|
||||
$current_scores = $wpdb->get_row( $query );
|
||||
//phpcs:enable
|
||||
|
||||
if ( $current_scores === null ) {
|
||||
throw new Score_Results_Not_Found_Exception();
|
||||
}
|
||||
|
||||
$end_time = \microtime( true );
|
||||
|
||||
$results['scores'] = $current_scores;
|
||||
$results['query_time'] = ( $end_time - $start_time );
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the select statement for the SEO scores query.
|
||||
*
|
||||
* @param SEO_Score_Groups_Interface[] $seo_score_groups All SEO score groups.
|
||||
* @param bool|null $is_troubleshooting Whether we're in troubleshooting mode.
|
||||
*
|
||||
* @return array<string, string> The select statement for the SEO scores query.
|
||||
*/
|
||||
private function build_select( array $seo_score_groups, ?bool $is_troubleshooting ): array {
|
||||
$select_fields = [];
|
||||
$select_replacements = [];
|
||||
|
||||
// When we don't troubleshoot, we're interested in the amount of posts in a group, when we troubleshoot we want to gather the actual IDs.
|
||||
$select_operation = ( $is_troubleshooting === true ) ? 'GROUP_CONCAT' : 'COUNT';
|
||||
$selected_info = ( $is_troubleshooting === true ) ? 'I.object_id' : '1';
|
||||
|
||||
foreach ( $seo_score_groups as $seo_score_group ) {
|
||||
$min = $seo_score_group->get_min_score();
|
||||
$max = $seo_score_group->get_max_score();
|
||||
$name = $seo_score_group->get_name();
|
||||
|
||||
if ( $min === null || $max === null ) {
|
||||
$select_fields[] = "{$select_operation}(CASE WHEN I.primary_focus_keyword_score = 0 OR I.primary_focus_keyword_score IS NULL THEN {$selected_info} END) AS %i";
|
||||
$select_replacements[] = $name;
|
||||
}
|
||||
else {
|
||||
$select_fields[] = "{$select_operation}(CASE WHEN I.primary_focus_keyword_score >= %d AND I.primary_focus_keyword_score <= %d THEN {$selected_info} END) AS %i";
|
||||
$select_replacements[] = $min;
|
||||
$select_replacements[] = $max;
|
||||
$select_replacements[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$select_fields = \implode( ', ', $select_fields );
|
||||
|
||||
return [
|
||||
'fields' => $select_fields,
|
||||
'replacements' => $select_replacements,
|
||||
];
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters;
|
||||
|
||||
/**
|
||||
* Domain object to add search console specific data to the parameters.
|
||||
*/
|
||||
class Search_Console_Parameters extends Parameters {
|
||||
|
||||
/**
|
||||
* The search dimensions to query.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $dimensions;
|
||||
|
||||
/**
|
||||
* Sets the dimension parameter.
|
||||
*
|
||||
* @param array<string> $dimensions The dimensions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_dimensions( array $dimensions ): void {
|
||||
$this->dimensions = $dimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the dimensions.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_dimensions(): array {
|
||||
return $this->dimensions;
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console;
|
||||
|
||||
use Google\Site_Kit_Dependencies\Google\Service\SearchConsole\ApiDataRow;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Search_Console\Failed_Request_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Search_Console\Unexpected_Response_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Comparison_Search_Ranking_Data;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Ranking_Data;
|
||||
|
||||
/**
|
||||
* The site API adapter to make calls to the Search Console API, via the Site_Kit plugin.
|
||||
*/
|
||||
class Site_Kit_Search_Console_Adapter {
|
||||
|
||||
/**
|
||||
* Holds the api call class.
|
||||
*
|
||||
* @var Site_Kit_Search_Console_Api_Call $site_kit_search_console_api_call
|
||||
*/
|
||||
private $site_kit_search_console_api_call;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Site_Kit_Search_Console_Api_Call $site_kit_search_console_api_call The api call class.
|
||||
*/
|
||||
public function __construct( Site_Kit_Search_Console_Api_Call $site_kit_search_console_api_call ) {
|
||||
$this->site_kit_search_console_api_call = $site_kit_search_console_api_call;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrapper method to do a Site Kit API request for Search Console.
|
||||
*
|
||||
* @param Search_Console_Parameters $parameters The parameters.
|
||||
*
|
||||
* @throws Failed_Request_Exception When the request responds with an error from Site Kit.
|
||||
* @throws Unexpected_Response_Exception When the request responds with an unexpected format.
|
||||
* @return Data_Container The Site Kit API response.
|
||||
*/
|
||||
public function get_data( Search_Console_Parameters $parameters ): Data_Container {
|
||||
$api_parameters = $this->build_parameters( $parameters );
|
||||
|
||||
$response = $this->site_kit_search_console_api_call->do_request( $api_parameters );
|
||||
|
||||
$this->validate_response( $response );
|
||||
|
||||
return $this->parse_response( $response->get_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrapper method to do a comparison Site Kit API request for Search Console.
|
||||
*
|
||||
* @param Search_Console_Parameters $parameters The parameters.
|
||||
*
|
||||
* @throws Failed_Request_Exception When the request responds with an error from Site Kit.
|
||||
* @throws Unexpected_Response_Exception When the request responds with an unexpected format.
|
||||
* @return Data_Container The Site Kit API response.
|
||||
*/
|
||||
public function get_comparison_data( Search_Console_Parameters $parameters ): Data_Container {
|
||||
$api_parameters = $this->build_parameters( $parameters );
|
||||
|
||||
// Since we're doing a comparison request, we need to increase the date range to the start of the previous period. We'll later split the data into two periods.
|
||||
$api_parameters['startDate'] = $parameters->get_compare_start_date();
|
||||
|
||||
$response = $this->site_kit_search_console_api_call->do_request( $api_parameters );
|
||||
|
||||
$this->validate_response( $response );
|
||||
|
||||
return $this->parse_comparison_response( $response->get_data(), $parameters->get_compare_end_date() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the parameters to be used in the Site Kit API request.
|
||||
*
|
||||
* @param Search_Console_Parameters $parameters The parameters.
|
||||
*
|
||||
* @return array<string, array<string, string>> The Site Kit API parameters.
|
||||
*/
|
||||
private function build_parameters( Search_Console_Parameters $parameters ): array {
|
||||
$api_parameters = [
|
||||
'startDate' => $parameters->get_start_date(),
|
||||
'endDate' => $parameters->get_end_date(),
|
||||
'dimensions' => $parameters->get_dimensions(),
|
||||
];
|
||||
|
||||
if ( $parameters->get_limit() !== 0 ) {
|
||||
$api_parameters['limit'] = $parameters->get_limit();
|
||||
}
|
||||
|
||||
return $api_parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a response for a comparison Site Kit API request for Search Analytics.
|
||||
*
|
||||
* @param ApiDataRow[] $response The response to parse.
|
||||
* @param string $compare_end_date The compare end date.
|
||||
*
|
||||
* @throws Unexpected_Response_Exception When the comparison request responds with an unexpected format.
|
||||
* @return Data_Container The parsed comparison Site Kit API response.
|
||||
*/
|
||||
private function parse_comparison_response( array $response, ?string $compare_end_date ): Data_Container {
|
||||
$data_container = new Data_Container();
|
||||
$comparison_search_ranking_data = new Comparison_Search_Ranking_Data();
|
||||
|
||||
foreach ( $response as $ranking_date ) {
|
||||
|
||||
if ( ! \is_a( $ranking_date, ApiDataRow::class ) ) {
|
||||
throw new Unexpected_Response_Exception();
|
||||
}
|
||||
|
||||
$ranking_data = new Search_Ranking_Data( $ranking_date->getClicks(), $ranking_date->getCtr(), $ranking_date->getImpressions(), $ranking_date->getPosition(), $ranking_date->getKeys()[0] );
|
||||
|
||||
// Now split the data into two periods.
|
||||
if ( $ranking_date->getKeys()[0] <= $compare_end_date ) {
|
||||
$comparison_search_ranking_data->add_previous_traffic_data( $ranking_data );
|
||||
}
|
||||
else {
|
||||
$comparison_search_ranking_data->add_current_traffic_data( $ranking_data );
|
||||
}
|
||||
}
|
||||
|
||||
$data_container->add_data( $comparison_search_ranking_data );
|
||||
|
||||
return $data_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a response for a Site Kit API request for Search Analytics.
|
||||
*
|
||||
* @param ApiDataRow[] $response The response to parse.
|
||||
*
|
||||
* @throws Unexpected_Response_Exception When the request responds with an unexpected format.
|
||||
* @return Data_Container The parsed Site Kit API response.
|
||||
*/
|
||||
private function parse_response( array $response ): Data_Container {
|
||||
$search_ranking_data_container = new Data_Container();
|
||||
|
||||
foreach ( $response as $ranking ) {
|
||||
|
||||
if ( ! \is_a( $ranking, ApiDataRow::class ) ) {
|
||||
throw new Unexpected_Response_Exception();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_transform_dashboard_subject_for_testing' - Allows overriding subjects like URLs for the dashboard, to facilitate testing in local environments.
|
||||
*
|
||||
* @param string $url The subject to be transformed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
$subject = \apply_filters( 'wpseo_transform_dashboard_subject_for_testing', $ranking->getKeys()[0] );
|
||||
|
||||
$search_ranking_data_container->add_data( new Search_Ranking_Data( $ranking->getClicks(), $ranking->getCtr(), $ranking->getImpressions(), $ranking->getPosition(), $subject ) );
|
||||
}
|
||||
|
||||
return $search_ranking_data_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the response coming from Search Console.
|
||||
*
|
||||
* @param WP_REST_Response $response The response we want to validate.
|
||||
*
|
||||
* @return void.
|
||||
*
|
||||
* @throws Failed_Request_Exception When the request responds with an error from Site Kit.
|
||||
* @throws Unexpected_Response_Exception When the request responds with an unexpected format.
|
||||
*/
|
||||
private function validate_response( WP_REST_Response $response ): void {
|
||||
if ( $response->is_error() ) {
|
||||
$error_data = $response->as_error()->get_error_data();
|
||||
$error_status_code = ( $error_data['status'] ?? 500 );
|
||||
throw new Failed_Request_Exception(
|
||||
\wp_kses_post(
|
||||
$response->as_error()
|
||||
->get_error_message()
|
||||
),
|
||||
(int) $error_status_code
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! \is_array( $response->get_data() ) ) {
|
||||
throw new Unexpected_Response_Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Class that hold the code to do the REST call to the Site Kit api.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Site_Kit_Search_Console_Api_Call {
|
||||
|
||||
/**
|
||||
* The search analytics API route path.
|
||||
*/
|
||||
private const SEARCH_CONSOLE_DATA_SEARCH_ANALYTICS_ROUTE = '/google-site-kit/v1/modules/search-console/data/searchanalytics';
|
||||
|
||||
/**
|
||||
* Runs the internal REST api call.
|
||||
*
|
||||
* @param array<string, array<string, string>> $api_parameters The api parameters.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function do_request( array $api_parameters ): WP_REST_Response {
|
||||
$request = new WP_REST_Request( 'GET', self::SEARCH_CONSOLE_DATA_SEARCH_ANALYTICS_ROUTE );
|
||||
$request->set_query_params( $api_parameters );
|
||||
return \rest_do_request( $request );
|
||||
}
|
||||
}
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies;
|
||||
|
||||
use WP_Taxonomy;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
|
||||
|
||||
/**
|
||||
* Class that collects taxonomies and relevant information.
|
||||
*/
|
||||
class Taxonomies_Collector {
|
||||
|
||||
/**
|
||||
* The taxonomy validator.
|
||||
*
|
||||
* @var Taxonomy_Validator
|
||||
*/
|
||||
private $taxonomy_validator;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Taxonomy_Validator $taxonomy_validator The taxonomy validator.
|
||||
*/
|
||||
public function __construct( Taxonomy_Validator $taxonomy_validator ) {
|
||||
$this->taxonomy_validator = $taxonomy_validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a custom pair of taxonomy/content type, that's been given by users via hooks.
|
||||
*
|
||||
* @param string $content_type The content type we're hooking for.
|
||||
*
|
||||
* @return Taxonomy|null The hooked filtering taxonomy.
|
||||
*/
|
||||
public function get_custom_filtering_taxonomy( string $content_type ) {
|
||||
/**
|
||||
* Filter: 'wpseo_{$content_type}_filtering_taxonomy' - Allows overriding which taxonomy filters the content type.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string $filtering_taxonomy The taxonomy that filters the content type.
|
||||
*/
|
||||
$filtering_taxonomy = \apply_filters( "wpseo_{$content_type}_filtering_taxonomy", '' );
|
||||
if ( $filtering_taxonomy !== '' ) {
|
||||
$taxonomy = $this->get_taxonomy( $filtering_taxonomy, $content_type );
|
||||
|
||||
if ( $taxonomy ) {
|
||||
return $taxonomy;
|
||||
}
|
||||
|
||||
\_doing_it_wrong(
|
||||
'Filter: \'wpseo_{$content_type}_filtering_taxonomy\'',
|
||||
'The `wpseo_{$content_type}_filtering_taxonomy` filter should return a public taxonomy, available in REST API, that is associated with that content type.',
|
||||
'YoastSEO v24.1'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fallback, WP-native category taxonomy, if it's associated with the specific content type.
|
||||
*
|
||||
* @param string $content_type The content type.
|
||||
*
|
||||
* @return Taxonomy|null The taxonomy object for the category taxonomy.
|
||||
*/
|
||||
public function get_fallback_taxonomy( string $content_type ): ?Taxonomy {
|
||||
return $this->get_taxonomy( 'category', $content_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy object that filters a specific content type.
|
||||
*
|
||||
* @param string $taxonomy_name The name of the taxonomy we're going to build the object for.
|
||||
* @param string $content_type The content type that the taxonomy object is filtering.
|
||||
*
|
||||
* @return Taxonomy|null The taxonomy object.
|
||||
*/
|
||||
public function get_taxonomy( string $taxonomy_name, string $content_type ): ?Taxonomy {
|
||||
$taxonomy = \get_taxonomy( $taxonomy_name );
|
||||
|
||||
if ( $this->taxonomy_validator->is_valid_taxonomy( $taxonomy, $content_type ) ) {
|
||||
return new Taxonomy( $taxonomy->name, $taxonomy->label, $this->get_taxonomy_rest_url( $taxonomy ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the REST API URL for the taxonomy.
|
||||
*
|
||||
* @param WP_Taxonomy $taxonomy The taxonomy we want to build the REST API URL for.
|
||||
*
|
||||
* @return string The REST API URL for the taxonomy.
|
||||
*/
|
||||
protected function get_taxonomy_rest_url( WP_Taxonomy $taxonomy ): string {
|
||||
$rest_base = ( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
|
||||
|
||||
$rest_namespace = ( $taxonomy->rest_namespace ) ? $taxonomy->rest_namespace : 'wp/v2';
|
||||
|
||||
return \rest_url( "{$rest_namespace}/{$rest_base}" );
|
||||
}
|
||||
}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies;
|
||||
|
||||
use WP_Taxonomy;
|
||||
|
||||
/**
|
||||
* Class that validates taxonomies.
|
||||
*/
|
||||
class Taxonomy_Validator {
|
||||
|
||||
/**
|
||||
* Returns whether the taxonomy in question is valid and associated with a given content type.
|
||||
*
|
||||
* @param WP_Taxonomy|false|null $taxonomy The taxonomy to check.
|
||||
* @param string $content_type The name of the content type to check.
|
||||
*
|
||||
* @return bool Whether the taxonomy in question is valid.
|
||||
*/
|
||||
public function is_valid_taxonomy( $taxonomy, string $content_type ): bool {
|
||||
return \is_a( $taxonomy, 'WP_Taxonomy' )
|
||||
&& $taxonomy->public
|
||||
&& $taxonomy->show_in_rest
|
||||
&& \in_array( $taxonomy->name, \get_object_taxonomies( $content_type ), true );
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Tracking;
|
||||
|
||||
/**
|
||||
* Interface for the Site Kit Usage Tracking Repository.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
interface Setup_Steps_Tracking_Repository_Interface {
|
||||
|
||||
/**
|
||||
* Sets an option inside the Site Kit usage options array..
|
||||
*
|
||||
* @param string $element_name The name of the array element to set.
|
||||
* @param string $element_value The value of the array element to set.
|
||||
*
|
||||
* @return bool False when the update failed, true when the update succeeded.
|
||||
*/
|
||||
public function set_setup_steps_tracking_element( string $element_name, string $element_value ): bool;
|
||||
|
||||
/**
|
||||
* Gets an option inside the Site Kit usage options array..
|
||||
*
|
||||
* @param string $element_name The name of the array element to get.
|
||||
*
|
||||
* @return string The value if present, empty string if not.
|
||||
*/
|
||||
public function get_setup_steps_tracking_element( string $element_name ): string;
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\Infrastructure\Tracking;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
|
||||
/**
|
||||
* Stores and retrieves data about Site Kit usage.
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Setup_Steps_Tracking_Repository implements Setup_Steps_Tracking_Repository_Interface {
|
||||
|
||||
/**
|
||||
* Holds the Options_Helper instance.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct( Options_Helper $options_helper ) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an option inside the Site Kit usage options array.
|
||||
*
|
||||
* @param string $element_name The name of the option to set.
|
||||
* @param string $element_value The value of the option to set.
|
||||
*
|
||||
* @return bool False when the update failed, true when the update succeeded.
|
||||
*/
|
||||
public function set_setup_steps_tracking_element( string $element_name, string $element_value ): bool {
|
||||
return $this->options_helper->set( 'site_kit_tracking_' . $element_name, $element_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an option inside the Site Kit usage options array.
|
||||
*
|
||||
* @param string $element_name The name of the option to get.
|
||||
*
|
||||
* @return string The value if present, empty string if not.
|
||||
*/
|
||||
public function get_setup_steps_tracking_element( string $element_name ): string {
|
||||
return $this->options_helper->get( 'site_kit_tracking_' . $element_name, '' );
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Configuration;
|
||||
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Enables the needed Site Kit capabilities for the SEO manager role.
|
||||
*/
|
||||
class Site_Kit_Capabilities_Integration implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* Registers needed filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'user_has_cap', [ $this, 'enable_site_kit_capabilities' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* The needed conditionals.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
// This cannot have the Admin Conditional since it also needs to run in Rest requests.
|
||||
return [ Google_Site_Kit_Feature_Conditional::class, Site_Kit_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Site Kit capabilities need to be enabled for a manager.
|
||||
*
|
||||
* @param array<string> $all_caps All the current capabilities of the current user.
|
||||
* @param array<string> $cap_to_check The capability to check against.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function enable_site_kit_capabilities( $all_caps, $cap_to_check ) {
|
||||
if ( ! isset( $cap_to_check[0] ) || ! \class_exists( Permissions::class ) ) {
|
||||
return $all_caps;
|
||||
}
|
||||
$user = \wp_get_current_user();
|
||||
$caps_to_check = [
|
||||
Permissions::SETUP,
|
||||
Permissions::VIEW_DASHBOARD,
|
||||
];
|
||||
if ( \in_array( $cap_to_check[0], $caps_to_check, true ) && \in_array( 'wpseo_manager', $user->roles, true ) ) {
|
||||
$all_caps[ $cap_to_check[0] ] = true;
|
||||
}
|
||||
|
||||
return $all_caps;
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Configuration;
|
||||
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface;
|
||||
use Yoast\WP\SEO\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Routes\Route_Interface;
|
||||
|
||||
/**
|
||||
* Registers a route to set whether the Site Kit configuration is permanently dismissed.
|
||||
*
|
||||
* @makePublic
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Site_Kit_Configuration_Dismissal_Route implements Route_Interface {
|
||||
|
||||
/**
|
||||
* The namespace for this route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE;
|
||||
|
||||
/**
|
||||
* The prefix for this route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_PREFIX = '/site_kit_configuration_permanent_dismissal';
|
||||
|
||||
/**
|
||||
* Holds the introductions collector instance.
|
||||
*
|
||||
* @var Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface
|
||||
*/
|
||||
private $permanently_dismissed_site_kit_configuration_repository;
|
||||
|
||||
/**
|
||||
* Holds the capabilit helper instance.
|
||||
*
|
||||
* @var Capability_Helper
|
||||
*/
|
||||
private $capability_helper;
|
||||
|
||||
/**
|
||||
* The needed conditionals.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
// This cannot have the Admin Conditional since it also needs to run in Rest requests.
|
||||
return [ Google_Site_Kit_Feature_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface $permanently_dismissed_site_kit_configuration_repository The repository.
|
||||
* @param Capability_Helper $capability_helper The capability helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface $permanently_dismissed_site_kit_configuration_repository,
|
||||
Capability_Helper $capability_helper
|
||||
) {
|
||||
$this->permanently_dismissed_site_kit_configuration_repository = $permanently_dismissed_site_kit_configuration_repository;
|
||||
$this->capability_helper = $capability_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
\register_rest_route(
|
||||
self::ROUTE_NAMESPACE,
|
||||
self::ROUTE_PREFIX,
|
||||
[
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'set_site_kit_configuration_permanent_dismissal' ],
|
||||
'permission_callback' => [ $this, 'check_capabilities' ],
|
||||
'args' => [
|
||||
'is_dismissed' => [
|
||||
'required' => true,
|
||||
'type' => 'bool',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
],
|
||||
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the Site Kit configuration is permanently dismissed.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error The success or failure response.
|
||||
*/
|
||||
public function set_site_kit_configuration_permanent_dismissal( WP_REST_Request $request ) {
|
||||
$is_dismissed = $request->get_param( 'is_dismissed' );
|
||||
|
||||
try {
|
||||
$result = $this->permanently_dismissed_site_kit_configuration_repository->set_site_kit_configuration_dismissal( $is_dismissed );
|
||||
} catch ( Exception $exception ) {
|
||||
return new WP_Error(
|
||||
'wpseo_set_site_kit_configuration_permanent_dismissal_error',
|
||||
$exception->getMessage(),
|
||||
(object) []
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'success' => $result,
|
||||
],
|
||||
( $result ) ? 200 : 400
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has the required capabilities.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check_capabilities() {
|
||||
return $this->capability_helper->current_user_can( 'wpseo_manage_options' );
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Configuration;
|
||||
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository_Interface;
|
||||
use Yoast\WP\SEO\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Routes\Route_Interface;
|
||||
|
||||
/**
|
||||
* Registers a route to set whether the Site Kit configuration is permanently dismissed.
|
||||
*
|
||||
* @makePublic
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Site_Kit_Consent_Management_Route implements Route_Interface {
|
||||
|
||||
/**
|
||||
* The namespace for this route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE;
|
||||
|
||||
/**
|
||||
* The prefix for this route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_PREFIX = '/site_kit_manage_consent';
|
||||
|
||||
/**
|
||||
* Holds the repository instance.
|
||||
*
|
||||
* @var Site_Kit_Consent_Repository_Interface
|
||||
*/
|
||||
private $site_kit_consent_repository;
|
||||
|
||||
/**
|
||||
* Holds the capabilit helper instance.
|
||||
*
|
||||
* @var Capability_Helper
|
||||
*/
|
||||
private $capability_helper;
|
||||
|
||||
/**
|
||||
* The needed conditionals.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
// This cannot have the Admin Conditional since it also needs to run in Rest requests.
|
||||
return [ Google_Site_Kit_Feature_Conditional::class, Site_Kit_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Site_Kit_Consent_Repository_Interface $site_kit_consent_repository The repository.
|
||||
* @param Capability_Helper $capability_helper The capability helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Site_Kit_Consent_Repository_Interface $site_kit_consent_repository,
|
||||
Capability_Helper $capability_helper
|
||||
) {
|
||||
$this->site_kit_consent_repository = $site_kit_consent_repository;
|
||||
$this->capability_helper = $capability_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
\register_rest_route(
|
||||
self::ROUTE_NAMESPACE,
|
||||
self::ROUTE_PREFIX,
|
||||
[
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'set_site_kit_consent' ],
|
||||
'permission_callback' => [ $this, 'check_capabilities' ],
|
||||
'args' => [
|
||||
'consent' => [
|
||||
'required' => true,
|
||||
'type' => 'bool',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
],
|
||||
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the Site Kit configuration is permanently dismissed.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error The success or failure response.
|
||||
*/
|
||||
public function set_site_kit_consent( WP_REST_Request $request ) {
|
||||
$consent = $request->get_param( 'consent' );
|
||||
|
||||
try {
|
||||
$result = $this->site_kit_consent_repository->set_site_kit_consent( $consent );
|
||||
} catch ( Exception $exception ) {
|
||||
return new WP_Error(
|
||||
'wpseo_set_site_kit_consent_error',
|
||||
$exception->getMessage(),
|
||||
(object) []
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'success' => $result,
|
||||
],
|
||||
( $result ) ? 200 : 400
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has the required capabilities.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check_capabilities() {
|
||||
return $this->capability_helper->current_user_can( 'wpseo_manage_options' );
|
||||
}
|
||||
}
|
||||
Executable
+276
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Scores;
|
||||
|
||||
use Exception;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WPSEO_Capability_Utils;
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Score_Results\Abstract_Score_Results_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Taxonomies\Taxonomies_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
use Yoast\WP\SEO\Routes\Route_Interface;
|
||||
|
||||
/**
|
||||
* Abstract scores route.
|
||||
*/
|
||||
abstract class Abstract_Scores_Route implements Route_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The namespace of the rout.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE;
|
||||
|
||||
/**
|
||||
* The prefix of the rout.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_PREFIX = null;
|
||||
|
||||
/**
|
||||
* The content types collector.
|
||||
*
|
||||
* @var Content_Types_Collector
|
||||
*/
|
||||
protected $content_types_collector;
|
||||
|
||||
/**
|
||||
* The taxonomies repository.
|
||||
*
|
||||
* @var Taxonomies_Repository
|
||||
*/
|
||||
protected $taxonomies_repository;
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* The scores repository.
|
||||
*
|
||||
* @var Abstract_Score_Results_Repository
|
||||
*/
|
||||
protected $score_results_repository;
|
||||
|
||||
/**
|
||||
* Sets the collectors.
|
||||
*
|
||||
* @required
|
||||
*
|
||||
* @param Content_Types_Collector $content_types_collector The content type collector.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_collectors( Content_Types_Collector $content_types_collector ) {
|
||||
$this->content_types_collector = $content_types_collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the repositories.
|
||||
*
|
||||
* @required
|
||||
*
|
||||
* @param Taxonomies_Repository $taxonomies_repository The taxonomies repository.
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_repositories(
|
||||
Taxonomies_Repository $taxonomies_repository,
|
||||
Indexable_Repository $indexable_repository
|
||||
) {
|
||||
$this->taxonomies_repository = $taxonomies_repository;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the route prefix.
|
||||
*
|
||||
* @return string The route prefix.
|
||||
*
|
||||
* @throws Exception If the ROUTE_PREFIX constant is not set in the child class.
|
||||
*/
|
||||
public static function get_route_prefix() {
|
||||
$class = static::class;
|
||||
$prefix = $class::ROUTE_PREFIX;
|
||||
|
||||
if ( $prefix === null ) {
|
||||
throw new Exception( 'Score route without explicit prefix' );
|
||||
}
|
||||
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes for scores.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
\register_rest_route(
|
||||
self::ROUTE_NAMESPACE,
|
||||
$this->get_route_prefix(),
|
||||
[
|
||||
[
|
||||
'methods' => 'GET',
|
||||
'callback' => [ $this, 'get_scores' ],
|
||||
'permission_callback' => [ $this, 'permission_manage_options' ],
|
||||
'args' => [
|
||||
'contentType' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
'taxonomy' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
'term' => [
|
||||
'required' => false,
|
||||
'type' => 'integer',
|
||||
'default' => null,
|
||||
'sanitize_callback' => static function ( $param ) {
|
||||
return \intval( $param );
|
||||
},
|
||||
],
|
||||
'troubleshooting' => [
|
||||
'required' => false,
|
||||
'type' => 'bool',
|
||||
'default' => null,
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scores of a specific content type.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The success or failure response.
|
||||
*/
|
||||
public function get_scores( WP_REST_Request $request ) {
|
||||
try {
|
||||
$content_type = $this->get_content_type( $request['contentType'] );
|
||||
$taxonomy = $this->get_taxonomy( $request['taxonomy'], $content_type );
|
||||
$term_id = $this->get_validated_term_id( $request['term'], $taxonomy );
|
||||
|
||||
$results = $this->score_results_repository->get_score_results( $content_type, $taxonomy, $term_id, $request['troubleshooting'] );
|
||||
} catch ( Exception $exception ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'error' => $exception->getMessage(),
|
||||
],
|
||||
$exception->getCode()
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
$results,
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content type object.
|
||||
*
|
||||
* @param string $content_type The content type.
|
||||
*
|
||||
* @return Content_Type|null The content type object.
|
||||
*
|
||||
* @throws Exception When the content type is invalid.
|
||||
*/
|
||||
protected function get_content_type( string $content_type ): ?Content_Type {
|
||||
$content_types = $this->content_types_collector->get_content_types()->get();
|
||||
|
||||
if ( isset( $content_types[ $content_type ] ) && \is_a( $content_types[ $content_type ], Content_Type::class ) ) {
|
||||
return $content_types[ $content_type ];
|
||||
}
|
||||
|
||||
throw new Exception( 'Invalid content type.', 400 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the taxonomy object.
|
||||
*
|
||||
* @param string $taxonomy The taxonomy.
|
||||
* @param Content_Type $content_type The content type that the taxonomy is filtering.
|
||||
*
|
||||
* @return Taxonomy|null The taxonomy object.
|
||||
*
|
||||
* @throws Exception When the taxonomy is invalid.
|
||||
*/
|
||||
protected function get_taxonomy( string $taxonomy, Content_Type $content_type ): ?Taxonomy {
|
||||
if ( $taxonomy === '' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$valid_taxonomy = $this->taxonomies_repository->get_content_type_taxonomy( $content_type->get_name() );
|
||||
|
||||
if ( $valid_taxonomy && $valid_taxonomy->get_name() === $taxonomy ) {
|
||||
return $valid_taxonomy;
|
||||
}
|
||||
|
||||
throw new Exception( 'Invalid taxonomy.', 400 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the term ID validated against the given taxonomy.
|
||||
*
|
||||
* @param int|null $term_id The term ID to be validated.
|
||||
* @param Taxonomy|null $taxonomy The taxonomy.
|
||||
*
|
||||
* @return int|null The validated term ID.
|
||||
*
|
||||
* @throws Exception When the term id is invalidated.
|
||||
*/
|
||||
protected function get_validated_term_id( ?int $term_id, ?Taxonomy $taxonomy ): ?int {
|
||||
if ( $term_id !== null && $taxonomy === null ) {
|
||||
throw new Exception( 'Term needs a provided taxonomy.', 400 );
|
||||
}
|
||||
|
||||
if ( $term_id === null && $taxonomy !== null ) {
|
||||
throw new Exception( 'Taxonomy needs a provided term.', 400 );
|
||||
}
|
||||
|
||||
if ( $term_id !== null ) {
|
||||
$term = \get_term( $term_id );
|
||||
if ( ! $term || \is_wp_error( $term ) ) {
|
||||
throw new Exception( 'Invalid term.', 400 );
|
||||
}
|
||||
|
||||
if ( $taxonomy !== null && $term->taxonomy !== $taxonomy->get_name() ) {
|
||||
throw new Exception( 'Invalid term.', 400 );
|
||||
}
|
||||
}
|
||||
|
||||
return $term_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission callback.
|
||||
*
|
||||
* @return bool True when user has the 'wpseo_manage_options' capability.
|
||||
*/
|
||||
public function permission_manage_options() {
|
||||
return WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' );
|
||||
}
|
||||
}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Scores;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Application\Score_Results\Readability_Score_Results\Readability_Score_Results_Repository;
|
||||
|
||||
/**
|
||||
* Registers a route to get readability scores.
|
||||
*/
|
||||
class Readability_Scores_Route extends Abstract_Scores_Route {
|
||||
|
||||
/**
|
||||
* The prefix of the route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_PREFIX = '/readability_scores';
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Readability_Score_Results_Repository $readability_score_results_repository The readability score results repository.
|
||||
*/
|
||||
public function __construct( Readability_Score_Results_Repository $readability_score_results_repository ) {
|
||||
$this->score_results_repository = $readability_score_results_repository;
|
||||
}
|
||||
}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Scores;
|
||||
|
||||
use Yoast\WP\SEO\Dashboard\Application\Score_Results\SEO_Score_Results\SEO_Score_Results_Repository;
|
||||
|
||||
/**
|
||||
* Registers a route to get SEO scores.
|
||||
*/
|
||||
class SEO_Scores_Route extends Abstract_Scores_Route {
|
||||
|
||||
/**
|
||||
* The prefix of the route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_PREFIX = '/seo_scores';
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param SEO_Score_Results_Repository $seo_score_results_repository The SEO score results repository.
|
||||
*/
|
||||
public function __construct( SEO_Score_Results_Repository $seo_score_results_repository ) {
|
||||
$this->score_results_repository = $seo_score_results_repository;
|
||||
}
|
||||
}
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Setup;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Redirect_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Setup flow interceptor class.
|
||||
*/
|
||||
class Setup_Flow_Interceptor implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The page name of the Site Kit Setup finished page.
|
||||
*/
|
||||
private const GOOGLE_SITE_KIT_SEARCH_CONSOLE_SETUP_FINISHED_PAGE = 'googlesitekit-splash';
|
||||
private const GOOGLE_SITE_KIT_ANALYTICS_SETUP_FINISHED_PAGE = 'googlesitekit-dashboard';
|
||||
|
||||
/**
|
||||
* The Site Kit configuration object.
|
||||
*
|
||||
* @var Site_Kit $site_kit_configuration
|
||||
*/
|
||||
private $site_kit_configuration;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Holds the redirect helper.
|
||||
*
|
||||
* @var Redirect_Helper $redirect_helper
|
||||
*/
|
||||
private $redirect_helper;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Current_Page_Helper $current_page_helper The current page helper.
|
||||
* @param Redirect_Helper $redirect_helper The redirect helper to abstract away the actual redirecting.
|
||||
* @param Site_Kit $site_kit_configuration The Site Kit configuration object.
|
||||
*/
|
||||
public function __construct( Current_Page_Helper $current_page_helper, Redirect_Helper $redirect_helper, Site_Kit $site_kit_configuration ) {
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->redirect_helper = $redirect_helper;
|
||||
$this->site_kit_configuration = $site_kit_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers our redirect back to our dashboard all the way at the end of the admin init to make sure everything from the Site Kit callback can be finished.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'admin_init', [ $this, 'intercept_site_kit_setup_flow' ], 999 );
|
||||
}
|
||||
|
||||
/**
|
||||
* The conditions for this integration to load.
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Google_Site_Kit_Feature_Conditional::class, Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we should intercept the final page from the Site Kit flow.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function intercept_site_kit_setup_flow() {
|
||||
if ( \get_transient( Setup_Url_Interceptor::SITE_KIT_SETUP_TRANSIENT ) === '1' && $this->is_site_kit_setup_completed_page() ) {
|
||||
\delete_transient( Setup_Url_Interceptor::SITE_KIT_SETUP_TRANSIENT );
|
||||
$this->redirect_helper->do_safe_redirect( \self_admin_url( 'admin.php?page=wpseo_dashboard&redirected_from_site_kit' ), 302, 'Yoast SEO' );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we are on the site kit setup completed page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_site_kit_setup_completed_page(): bool {
|
||||
$current_page = $this->current_page_helper->get_current_yoast_seo_page();
|
||||
$on_search_console_setup_page = $current_page === self::GOOGLE_SITE_KIT_SEARCH_CONSOLE_SETUP_FINISHED_PAGE;
|
||||
$on_analytics_setup_page = $current_page === self::GOOGLE_SITE_KIT_ANALYTICS_SETUP_FINISHED_PAGE;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$authentication_success_notification = isset( $_GET['notification'] ) && \sanitize_text_field( \wp_unslash( $_GET['notification'] ) ) === 'authentication_success';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
$analytics_4_slug = isset( $_GET['slug'] ) && \sanitize_text_field( \wp_unslash( $_GET['slug'] ) ) === 'analytics-4';
|
||||
|
||||
/**
|
||||
* This checks two scenarios
|
||||
* 1. The user only wants Search Console. In this case just checking if you are on the thank-you page from Site Kit is enough.
|
||||
* 2. The user also wants analytics. So we need to check another page and also check if the analytics 4 connection is finalized.
|
||||
*/
|
||||
return ( $on_search_console_setup_page && $authentication_success_notification ) || ( $on_analytics_setup_page && $authentication_success_notification && $analytics_4_slug && $this->site_kit_configuration->is_ga_connected() );
|
||||
}
|
||||
}
|
||||
Executable
+132
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Setup;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit;
|
||||
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Redirect_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
|
||||
/**
|
||||
* Setup url Interceptor class.
|
||||
*/
|
||||
class Setup_Url_Interceptor implements Integration_Interface {
|
||||
|
||||
/**
|
||||
* The page url where this check lives.
|
||||
*/
|
||||
public const PAGE = 'wpseo_page_site_kit_set_up';
|
||||
|
||||
/**
|
||||
* The name of the transient.
|
||||
*/
|
||||
public const SITE_KIT_SETUP_TRANSIENT = 'wpseo_site_kit_set_up_transient';
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper.
|
||||
*
|
||||
* @var Current_Page_Helper $current_page_helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Holds the redirect helper.
|
||||
*
|
||||
* @var Redirect_Helper $redirect_helper
|
||||
*/
|
||||
private $redirect_helper;
|
||||
|
||||
/**
|
||||
* The Site Kit configuration object.
|
||||
*
|
||||
* @var Site_Kit $site_kit_configuration
|
||||
*/
|
||||
private $site_kit_configuration;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Current_Page_Helper $current_page_helper The current page helper.
|
||||
* @param Site_Kit $site_kit_configuration The Site Kit configuration object.
|
||||
* @param Redirect_Helper $redirect_helper The redirect helper to abstract away the actual redirecting.
|
||||
*/
|
||||
public function __construct( Current_Page_Helper $current_page_helper, Site_Kit $site_kit_configuration, Redirect_Helper $redirect_helper ) {
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->redirect_helper = $redirect_helper;
|
||||
|
||||
$this->site_kit_configuration = $site_kit_configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* The conditions for this integration to load.
|
||||
*
|
||||
* @return string[] The conditionals.
|
||||
*/
|
||||
public static function get_conditionals() {
|
||||
return [ Google_Site_Kit_Feature_Conditional::class, Admin_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the interceptor code to the admin_init function.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_filter( 'admin_menu', [ $this, 'add_redirect_page' ] );
|
||||
\add_action( 'admin_init', [ $this, 'intercept_site_kit_setup_url_redirect' ], 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dummy page.
|
||||
*
|
||||
* We need to register this in between page.
|
||||
*
|
||||
* @param array<array<string>> $pages The pages.
|
||||
*
|
||||
* @return array<array<string>> The pages.
|
||||
*/
|
||||
public function add_redirect_page( $pages ) {
|
||||
\add_submenu_page(
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'wpseo_manage_options',
|
||||
self::PAGE
|
||||
);
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we are trying to reach a site kit setup url and sets the needed transient in between.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function intercept_site_kit_setup_url_redirect() {
|
||||
$allowed_setup_links = [
|
||||
$this->site_kit_configuration->get_install_url(),
|
||||
$this->site_kit_configuration->get_activate_url(),
|
||||
$this->site_kit_configuration->get_setup_url(),
|
||||
$this->site_kit_configuration->get_update_url(),
|
||||
];
|
||||
|
||||
// Are we on the in-between page?
|
||||
if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
|
||||
// Check if parameter is there and is valid.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
|
||||
if ( isset( $_GET['redirect_setup_url'] ) && \in_array( \wp_unslash( $_GET['redirect_setup_url'] ), $allowed_setup_links, true ) ) {
|
||||
// We overwrite the transient for each time this redirect is hit to keep refreshing the time.
|
||||
\set_transient( self::SITE_KIT_SETUP_TRANSIENT, 1, ( \MINUTE_IN_SECONDS * 15 ) );
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Only allowed pre verified links can end up here.
|
||||
$redirect_url = \wp_unslash( $_GET['redirect_setup_url'] );
|
||||
$this->redirect_helper->do_safe_redirect( $redirect_url, 302, 'Yoast SEO' );
|
||||
|
||||
}
|
||||
else {
|
||||
$this->redirect_helper->do_safe_redirect( \self_admin_url( 'admin.php?page=wpseo_dashboard' ), 302, 'Yoast SEO' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Time_Based_SEO_Metrics;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
use Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Search_Ranking_Compare_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Query_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Traffic\Organic_Sessions_Compare_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Application\Traffic\Organic_Sessions_Daily_Repository;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters;
|
||||
use Yoast\WP\SEO\Dashboard\Domain\Time_Based_SEO_Metrics\Repository_Not_Found_Exception;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4\Analytics_4_Parameters;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters;
|
||||
use Yoast\WP\SEO\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Routes\Route_Interface;
|
||||
|
||||
/**
|
||||
* Abstract scores route.
|
||||
*/
|
||||
final class Time_Based_SEO_Metrics_Route implements Route_Interface {
|
||||
|
||||
/**
|
||||
* The namespace of the route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE;
|
||||
|
||||
/**
|
||||
* The prefix of the route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_NAME = '/time_based_seo_metrics';
|
||||
|
||||
/**
|
||||
* The data provider for page based search rankings.
|
||||
*
|
||||
* @var Top_Page_Repository
|
||||
*/
|
||||
private $top_page_repository;
|
||||
|
||||
/**
|
||||
* The data provider for query based search rankings.
|
||||
*
|
||||
* @var Top_Query_Repository
|
||||
*/
|
||||
private $top_query_repository;
|
||||
|
||||
/**
|
||||
* The data provider for comparison organic session traffic.
|
||||
*
|
||||
* @var Organic_Sessions_Compare_Repository
|
||||
*/
|
||||
private $organic_sessions_compare_repository;
|
||||
|
||||
/**
|
||||
* The data provider for daily organic session traffic.
|
||||
*
|
||||
* @var Organic_Sessions_Daily_Repository
|
||||
*/
|
||||
private $organic_sessions_daily_repository;
|
||||
|
||||
/**
|
||||
* The data provider for searching ranking comparison.
|
||||
*
|
||||
* @var Search_Ranking_Compare_Repository
|
||||
*/
|
||||
private $search_ranking_compare_repository;
|
||||
|
||||
/**
|
||||
* Holds the capability helper instance.
|
||||
*
|
||||
* @var Capability_Helper
|
||||
*/
|
||||
private $capability_helper;
|
||||
|
||||
/**
|
||||
* Returns the needed conditionals.
|
||||
*
|
||||
* @return array<string> The conditionals that must be met to load this.
|
||||
*/
|
||||
public static function get_conditionals(): array {
|
||||
return [ Google_Site_Kit_Feature_Conditional::class, Site_Kit_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Top_Page_Repository $top_page_repository The data provider for page based search rankings.
|
||||
* @param Top_Query_Repository $top_query_repository The data provider for query based search rankings.
|
||||
* @param Organic_Sessions_Compare_Repository $organic_sessions_compare_repository The data provider for comparison organic session traffic.
|
||||
* @param Organic_Sessions_Daily_Repository $organic_sessions_daily_repository The data provider for daily organic session traffic.
|
||||
* @param Search_Ranking_Compare_Repository $search_ranking_compare_repository The data provider for searching ranking comparison.
|
||||
* @param Capability_Helper $capability_helper The capability helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Top_Page_Repository $top_page_repository,
|
||||
Top_Query_Repository $top_query_repository,
|
||||
Organic_Sessions_Compare_Repository $organic_sessions_compare_repository,
|
||||
Organic_Sessions_Daily_Repository $organic_sessions_daily_repository,
|
||||
Search_Ranking_Compare_Repository $search_ranking_compare_repository,
|
||||
Capability_Helper $capability_helper
|
||||
) {
|
||||
$this->top_page_repository = $top_page_repository;
|
||||
$this->top_query_repository = $top_query_repository;
|
||||
$this->organic_sessions_compare_repository = $organic_sessions_compare_repository;
|
||||
$this->organic_sessions_daily_repository = $organic_sessions_daily_repository;
|
||||
$this->search_ranking_compare_repository = $search_ranking_compare_repository;
|
||||
$this->capability_helper = $capability_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes for scores.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
\register_rest_route(
|
||||
self::ROUTE_NAMESPACE,
|
||||
self::ROUTE_NAME,
|
||||
[
|
||||
[
|
||||
'methods' => 'GET',
|
||||
'callback' => [ $this, 'get_time_based_seo_metrics' ],
|
||||
'permission_callback' => [ $this, 'permission_manage_options' ],
|
||||
'args' => [
|
||||
'limit' => [
|
||||
'type' => 'int',
|
||||
'sanitize_callback' => 'absint',
|
||||
'default' => 5,
|
||||
],
|
||||
'options' => [
|
||||
'type' => 'object',
|
||||
'required' => true,
|
||||
'properties' => [
|
||||
'widget' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time based SEO metrics.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The success or failure response.
|
||||
*
|
||||
* @throws Repository_Not_Found_Exception When the given widget name is not implemented yet.
|
||||
*/
|
||||
public function get_time_based_seo_metrics( WP_REST_Request $request ): WP_REST_Response {
|
||||
try {
|
||||
$widget_name = $request->get_param( 'options' )['widget'];
|
||||
|
||||
switch ( $widget_name ) {
|
||||
case 'query':
|
||||
$request_parameters = new Search_Console_Parameters();
|
||||
|
||||
$request_parameters = $this->set_date_range_parameters( $request_parameters );
|
||||
$request_parameters->set_limit( $request->get_param( 'limit' ) );
|
||||
$request_parameters->set_dimensions( [ 'query' ] );
|
||||
|
||||
$time_based_seo_metrics_container = $this->top_query_repository->get_data( $request_parameters );
|
||||
break;
|
||||
case 'page':
|
||||
$request_parameters = new Search_Console_Parameters();
|
||||
|
||||
$request_parameters = $this->set_date_range_parameters( $request_parameters );
|
||||
$request_parameters->set_limit( $request->get_param( 'limit' ) );
|
||||
$request_parameters->set_dimensions( [ 'page' ] );
|
||||
|
||||
$time_based_seo_metrics_container = $this->top_page_repository->get_data( $request_parameters );
|
||||
break;
|
||||
case 'organicSessionsDaily':
|
||||
$request_parameters = new Analytics_4_Parameters();
|
||||
|
||||
$request_parameters = $this->set_date_range_parameters( $request_parameters );
|
||||
$request_parameters->set_dimensions( [ 'date' ] );
|
||||
$request_parameters->set_metrics( [ 'sessions' ] );
|
||||
$request_parameters->set_dimension_filters( [ 'sessionDefaultChannelGrouping' => [ 'Organic Search' ] ] );
|
||||
$request_parameters->set_order_by( 'dimension', 'date' );
|
||||
|
||||
$time_based_seo_metrics_container = $this->organic_sessions_daily_repository->get_data( $request_parameters );
|
||||
break;
|
||||
case 'organicSessionsCompare':
|
||||
$request_parameters = new Analytics_4_Parameters();
|
||||
|
||||
$request_parameters = $this->set_date_range_parameters( $request_parameters );
|
||||
$request_parameters = $this->set_comparison_date_range_parameters( $request_parameters );
|
||||
$request_parameters->set_metrics( [ 'sessions' ] );
|
||||
$request_parameters->set_dimension_filters( [ 'sessionDefaultChannelGrouping' => [ 'Organic Search' ] ] );
|
||||
|
||||
$time_based_seo_metrics_container = $this->organic_sessions_compare_repository->get_data( $request_parameters );
|
||||
break;
|
||||
case 'searchRankingCompare':
|
||||
$request_parameters = new Search_Console_Parameters();
|
||||
|
||||
$request_parameters = $this->set_date_range_parameters( $request_parameters );
|
||||
$request_parameters = $this->set_comparison_date_range_parameters( $request_parameters );
|
||||
$request_parameters->set_dimensions( [ 'date' ] );
|
||||
|
||||
$time_based_seo_metrics_container = $this->search_ranking_compare_repository->get_data( $request_parameters );
|
||||
break;
|
||||
default:
|
||||
throw new Repository_Not_Found_Exception();
|
||||
}
|
||||
} catch ( Exception $exception ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'error' => $exception->getMessage(),
|
||||
],
|
||||
$exception->getCode()
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
$time_based_seo_metrics_container->to_array(),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets date range parameters.
|
||||
*
|
||||
* @param Parameters $request_parameters The request parameters.
|
||||
*
|
||||
* @return Parameters The request parameters with configured date range.
|
||||
*/
|
||||
public function set_date_range_parameters( Parameters $request_parameters ): Parameters {
|
||||
$date = $this->get_base_date();
|
||||
$date->modify( '-28 days' );
|
||||
$start_date = $date->format( 'Y-m-d' );
|
||||
|
||||
$date = $this->get_base_date();
|
||||
$date->modify( '-1 days' );
|
||||
$end_date = $date->format( 'Y-m-d' );
|
||||
|
||||
$request_parameters->set_start_date( $start_date );
|
||||
$request_parameters->set_end_date( $end_date );
|
||||
|
||||
return $request_parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets comparison date range parameters.
|
||||
*
|
||||
* @param Parameters $request_parameters The request parameters.
|
||||
*
|
||||
* @return Parameters The request parameters with configured comparison date range.
|
||||
*/
|
||||
public function set_comparison_date_range_parameters( Parameters $request_parameters ): Parameters {
|
||||
$date = $this->get_base_date();
|
||||
$date->modify( '-29 days' );
|
||||
$compare_end_date = $date->format( 'Y-m-d' );
|
||||
|
||||
$date->modify( '-27 days' );
|
||||
$compare_start_date = $date->format( 'Y-m-d' );
|
||||
|
||||
$request_parameters->set_compare_start_date( $compare_start_date );
|
||||
$request_parameters->set_compare_end_date( $compare_end_date );
|
||||
|
||||
return $request_parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base date.
|
||||
*
|
||||
* @return DateTime The base date.
|
||||
*/
|
||||
private function get_base_date() {
|
||||
/**
|
||||
* Filter: 'wpseo_custom_site_kit_base_date' - Allow the base date for Site Kit requests to be dynamically set.
|
||||
*
|
||||
* @param string $base_date The custom base date for Site Kit requests, defaults to 'now'.
|
||||
*/
|
||||
$base_date = \apply_filters( 'wpseo_custom_site_kit_base_date', 'now' );
|
||||
|
||||
try {
|
||||
return new DateTime( $base_date, new DateTimeZone( 'UTC' ) );
|
||||
} catch ( Exception $e ) {
|
||||
return new DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission callback.
|
||||
*
|
||||
* @return bool True when user has the 'wpseo_manage_options' capability.
|
||||
*/
|
||||
public function permission_manage_options() {
|
||||
return $this->capability_helper->current_user_can( 'wpseo_manage_options' );
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
|
||||
namespace Yoast\WP\SEO\Dashboard\User_Interface\Tracking;
|
||||
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional;
|
||||
use Yoast\WP\SEO\Dashboard\Infrastructure\Tracking\Setup_Steps_Tracking_Repository_Interface;
|
||||
use Yoast\WP\SEO\Helpers\Capability_Helper;
|
||||
use Yoast\WP\SEO\Main;
|
||||
use Yoast\WP\SEO\Routes\Route_Interface;
|
||||
|
||||
/**
|
||||
* Registers a route to keep track of the Site Kit usage.
|
||||
*
|
||||
* @makePublic
|
||||
*
|
||||
* @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
|
||||
*/
|
||||
class Setup_Steps_Tracking_Route implements Route_Interface {
|
||||
|
||||
/**
|
||||
* The namespace for this route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE;
|
||||
|
||||
/**
|
||||
* The prefix for this route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ROUTE_PREFIX = '/setup_steps_tracking';
|
||||
|
||||
/**
|
||||
* Holds the repository instance.
|
||||
*
|
||||
* @var Setup_Steps_Tracking_Repository_Interface
|
||||
*/
|
||||
private $setup_steps_tracking_repository;
|
||||
|
||||
/**
|
||||
* Holds the capability helper instance.
|
||||
*
|
||||
* @var Capability_Helper
|
||||
*/
|
||||
private $capability_helper;
|
||||
|
||||
/**
|
||||
* Returns the needed conditionals.
|
||||
*
|
||||
* @return array<string> The conditionals that must be met to load this.
|
||||
*/
|
||||
public static function get_conditionals(): array {
|
||||
return [ Google_Site_Kit_Feature_Conditional::class ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Setup_Steps_Tracking_Repository_Interface $setup_steps_tracking_repository The repository.
|
||||
* @param Capability_Helper $capability_helper The capability helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Setup_Steps_Tracking_Repository_Interface $setup_steps_tracking_repository,
|
||||
Capability_Helper $capability_helper
|
||||
) {
|
||||
$this->setup_steps_tracking_repository = $setup_steps_tracking_repository;
|
||||
$this->capability_helper = $capability_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
\register_rest_route(
|
||||
self::ROUTE_NAMESPACE,
|
||||
self::ROUTE_PREFIX,
|
||||
[
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'track_setup_steps' ],
|
||||
'permission_callback' => [ $this, 'check_capabilities' ],
|
||||
'args' => [
|
||||
'setup_widget_loaded' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'enum' => [ 'yes', 'no' ],
|
||||
],
|
||||
'first_interaction_stage' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'enum' => [ 'install', 'activate', 'setup', 'grantConsent', 'successfullyConnected' ],
|
||||
],
|
||||
'last_interaction_stage' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'enum' => [ 'install', 'activate', 'setup', 'grantConsent', 'successfullyConnected' ],
|
||||
],
|
||||
'setup_widget_temporarily_dismissed' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'enum' => [ 'yes', 'no' ],
|
||||
],
|
||||
'setup_widget_permanently_dismissed' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'enum' => [ 'yes', 'no' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores tracking information.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error The success or failure response.
|
||||
*/
|
||||
public function track_setup_steps( WP_REST_Request $request ) {
|
||||
$data = [
|
||||
'setup_widget_loaded' => $request->get_param( 'setupWidgetLoaded' ),
|
||||
'first_interaction_stage' => $request->get_param( 'firstInteractionStage' ),
|
||||
'last_interaction_stage' => $request->get_param( 'lastInteractionStage' ),
|
||||
'setup_widget_temporarily_dismissed' => $request->get_param( 'setupWidgetTemporarilyDismissed' ),
|
||||
'setup_widget_permanently_dismissed' => $request->get_param( 'setupWidgetPermanentlyDismissed' ),
|
||||
];
|
||||
|
||||
// Filter out null values from the data array.
|
||||
$data = \array_filter(
|
||||
$data,
|
||||
static function ( $value ) {
|
||||
return $value !== null;
|
||||
}
|
||||
);
|
||||
|
||||
// Check if all values are null then return an error that no valid params were passed.
|
||||
if ( empty( $data ) ) {
|
||||
return new WP_Error(
|
||||
'wpseo_set_site_kit_usage_tracking',
|
||||
\__( 'No valid parameters were passed.', 'wordpress-seo' ),
|
||||
[ 'status' => 400 ]
|
||||
);
|
||||
}
|
||||
|
||||
$result = true;
|
||||
foreach ( $data as $key => $value ) {
|
||||
try {
|
||||
$result = $this->setup_steps_tracking_repository->set_setup_steps_tracking_element( $key, $value );
|
||||
} catch ( Exception $exception ) {
|
||||
return new WP_Error(
|
||||
'wpseo_set_site_kit_usage_tracking',
|
||||
$exception->getMessage(),
|
||||
(object) []
|
||||
);
|
||||
}
|
||||
if ( ! $result ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'success' => $result,
|
||||
],
|
||||
( $result ) ? 200 : 400
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has the required capabilities.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check_capabilities() {
|
||||
return $this->capability_helper->current_user_can( 'wpseo_manage_options' );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user