PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.6
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.6
28.6 28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 All 130 releases
wordpress-seo / src / dashboard / user-interface / scores / abstract-scores-route.php

abstract-scores-route.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.6, at src/dashboard/user-interface/scores/abstract-scores-route.php

278 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4 namespace Yoast\WP\SEO\Dashboard\User_Interface\Scores;
5
6 use Exception;
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WPSEO_Capability_Utils;
10 use Yoast\WP\SEO\Conditionals\No_Conditionals;
11 use Yoast\WP\SEO\Dashboard\Application\Score_Results\Abstract_Score_Results_Repository;
12 use Yoast\WP\SEO\Dashboard\Application\Taxonomies\Taxonomies_Repository;
13 use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type;
14 use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy;
15 use Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector;
16 use Yoast\WP\SEO\Main;
17 use Yoast\WP\SEO\Repositories\Indexable_Repository;
18 use Yoast\WP\SEO\Routes\Route_Interface;
19
20 /**
21 * Abstract scores route.
22 */
23 abstract class Abstract_Scores_Route implements Route_Interface {
24
25 use No_Conditionals;
26
27 /**
28 * The namespace of the rout.
29 *
30 * @var string
31 */
32 public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE;
33
34 /**
35 * The prefix of the rout.
36 *
37 * @var string
38 */
39 public const ROUTE_PREFIX = null;
40
41 /**
42 * The content types collector.
43 *
44 * @var Content_Types_Collector
45 */
46 protected $content_types_collector;
47
48 /**
49 * The taxonomies repository.
50 *
51 * @var Taxonomies_Repository
52 */
53 protected $taxonomies_repository;
54
55 /**
56 * The indexable repository.
57 *
58 * @var Indexable_Repository
59 */
60 protected $indexable_repository;
61
62 /**
63 * The scores repository.
64 *
65 * @var Abstract_Score_Results_Repository
66 */
67 protected $score_results_repository;
68
69 /**
70 * Sets the collectors.
71 *
72 * @required
73 *
74 * @param Content_Types_Collector $content_types_collector The content type collector.
75 *
76 * @return void
77 */
78 public function set_collectors( Content_Types_Collector $content_types_collector ) {
79 $this->content_types_collector = $content_types_collector;
80 }
81
82 /**
83 * Sets the repositories.
84 *
85 * @required
86 *
87 * @param Taxonomies_Repository $taxonomies_repository The taxonomies repository.
88 * @param Indexable_Repository $indexable_repository The indexable repository.
89 *
90 * @return void
91 */
92 public function set_repositories(
93 Taxonomies_Repository $taxonomies_repository,
94 Indexable_Repository $indexable_repository
95 ) {
96 $this->taxonomies_repository = $taxonomies_repository;
97 $this->indexable_repository = $indexable_repository;
98 }
99
100 /**
101 * Returns the route prefix.
102 *
103 * @return string The route prefix.
104 *
105 * @throws Exception If the ROUTE_PREFIX constant is not set in the child class.
106 */
107 public static function get_route_prefix() {
108 $class = static::class;
109 $prefix = $class::ROUTE_PREFIX;
110
111 if ( $prefix === null ) {
112 throw new Exception( 'Score route without explicit prefix' );
113 }
114
115 return $prefix;
116 }
117
118 /**
119 * Registers routes for scores.
120 *
121 * @return void
122 */
123 public function register_routes() {
124 \register_rest_route(
125 self::ROUTE_NAMESPACE,
126 $this->get_route_prefix(),
127 [
128 [
129 'methods' => 'GET',
130 'callback' => [ $this, 'get_scores' ],
131 'permission_callback' => [ $this, 'permission_manage_options' ],
132 'args' => [
133 'contentType' => [
134 'required' => true,
135 'type' => 'string',
136 'sanitize_callback' => 'sanitize_text_field',
137 ],
138 'taxonomy' => [
139 'required' => false,
140 'type' => 'string',
141 'default' => '',
142 'sanitize_callback' => 'sanitize_text_field',
143 ],
144 'term' => [
145 'required' => false,
146 'type' => 'integer',
147 'default' => null,
148 'sanitize_callback' => static function ( $param ) {
149 return (int) $param;
150 },
151 ],
152 'troubleshooting' => [
153 'required' => false,
154 'type' => 'bool',
155 'default' => null,
156 'sanitize_callback' => 'rest_sanitize_boolean',
157 ],
158 ],
159 ],
160 ],
161 );
162 }
163
164 /**
165 * Gets the scores of a specific content type.
166 *
167 * @param WP_REST_Request $request The request object.
168 *
169 * @return WP_REST_Response The success or failure response.
170 */
171 public function get_scores( WP_REST_Request $request ) {
172 try {
173 $content_type = $this->get_content_type( $request['contentType'] );
174 $taxonomy = $this->get_taxonomy( $request['taxonomy'], $content_type );
175 $term_id = $this->get_validated_term_id( $request['term'], $taxonomy );
176
177 $results = $this->score_results_repository->get_score_results( $content_type, $taxonomy, $term_id, $request['troubleshooting'] );
178 } catch ( Exception $exception ) {
179 return new WP_REST_Response(
180 [
181 'error' => $exception->getMessage(),
182 ],
183 $exception->getCode(),
184 );
185 }
186
187 return new WP_REST_Response(
188 $results,
189 200,
190 );
191 }
192
193 /**
194 * Gets the content type object.
195 *
196 * @param string $content_type The content type.
197 *
198 * @return Content_Type|null The content type object.
199 *
200 * @throws Exception When the content type is invalid.
201 */
202 protected function get_content_type( string $content_type ): ?Content_Type {
203 $content_types = $this->content_types_collector->get_content_types()->get();
204
205 if ( isset( $content_types[ $content_type ] ) && \is_a( $content_types[ $content_type ], Content_Type::class ) ) {
206 return $content_types[ $content_type ];
207 }
208
209 throw new Exception( 'Invalid content type.', 400 );
210 }
211
212 /**
213 * Gets the taxonomy object.
214 *
215 * @param string $taxonomy The taxonomy.
216 * @param Content_Type $content_type The content type that the taxonomy is filtering.
217 *
218 * @return Taxonomy|null The taxonomy object.
219 *
220 * @throws Exception When the taxonomy is invalid.
221 */
222 protected function get_taxonomy( string $taxonomy, Content_Type $content_type ): ?Taxonomy {
223 if ( $taxonomy === '' ) {
224 return null;
225 }
226
227 $valid_taxonomy = $this->taxonomies_repository->get_content_type_taxonomy( $content_type->get_name() );
228
229 if ( $valid_taxonomy && $valid_taxonomy->get_name() === $taxonomy ) {
230 return $valid_taxonomy;
231 }
232
233 throw new Exception( 'Invalid taxonomy.', 400 );
234 }
235
236 /**
237 * Gets the term ID validated against the given taxonomy.
238 *
239 * @param int|null $term_id The term ID to be validated.
240 * @param Taxonomy|null $taxonomy The taxonomy.
241 *
242 * @return int|null The validated term ID.
243 *
244 * @throws Exception When the term id is invalidated.
245 */
246 protected function get_validated_term_id( ?int $term_id, ?Taxonomy $taxonomy ): ?int {
247 if ( $term_id !== null && $taxonomy === null ) {
248 throw new Exception( 'Term needs a provided taxonomy.', 400 );
249 }
250
251 if ( $term_id === null && $taxonomy !== null ) {
252 throw new Exception( 'Taxonomy needs a provided term.', 400 );
253 }
254
255 if ( $term_id !== null ) {
256 $term = \get_term( $term_id );
257 if ( ! $term || \is_wp_error( $term ) ) {
258 throw new Exception( 'Invalid term.', 400 );
259 }
260
261 if ( $taxonomy !== null && $term->taxonomy !== $taxonomy->get_name() ) {
262 throw new Exception( 'Invalid term.', 400 );
263 }
264 }
265
266 return $term_id;
267 }
268
269 /**
270 * Permission callback.
271 *
272 * @return bool True when user has the 'wpseo_manage_options' capability.
273 */
274 public function permission_manage_options() {
275 return WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' );
276 }
277 }
278