PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.9
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.9
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 19.0 All 129 releases
wordpress-seo / src / integrations / third-party / woocommerce.php

woocommerce.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.9, at src/integrations/third-party/woocommerce.php

367 lines 9.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yoast\WP\SEO\Integrations\Third_Party;
4
5 use WPSEO_Replace_Vars;
6 use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
7 use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
8 use Yoast\WP\SEO\Helpers\Options_Helper;
9 use Yoast\WP\SEO\Helpers\Pagination_Helper;
10 use Yoast\WP\SEO\Integrations\Integration_Interface;
11 use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
12 use Yoast\WP\SEO\Models\Indexable;
13 use Yoast\WP\SEO\Presentations\Indexable_Presentation;
14 use Yoast\WP\SEO\Repositories\Indexable_Repository;
15
16 /**
17 * WooCommerce integration.
18 */
19 class WooCommerce implements Integration_Interface {
20
21 /**
22 * The options helper.
23 *
24 * @var Options_Helper
25 */
26 private $options;
27
28 /**
29 * The WPSEO Replace Vars object.
30 *
31 * @var WPSEO_Replace_Vars
32 */
33 private $replace_vars;
34
35 /**
36 * The memoizer for the meta tags context.
37 *
38 * @var Meta_Tags_Context_Memoizer
39 */
40 protected $context_memoizer;
41
42 /**
43 * The indexable repository.
44 *
45 * @var Indexable_Repository
46 */
47 private $repository;
48
49 /**
50 * The pagination helper.
51 *
52 * @var Pagination_Helper
53 */
54 protected $pagination_helper;
55
56 /**
57 * Returns the conditionals based in which this loadable should be active.
58 *
59 * @return array
60 */
61 public static function get_conditionals() {
62 return [ WooCommerce_Conditional::class, Front_End_Conditional::class ];
63 }
64
65 /**
66 * WooCommerce constructor.
67 *
68 * @param Options_Helper $options The options helper.
69 * @param WPSEO_Replace_Vars $replace_vars The replace vars helper.
70 * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer.
71 * @param Indexable_Repository $repository The indexable repository.
72 * @param Pagination_Helper $pagination_helper The paginataion helper.
73 */
74 public function __construct(
75 Options_Helper $options,
76 WPSEO_Replace_Vars $replace_vars,
77 Meta_Tags_Context_Memoizer $context_memoizer,
78 Indexable_Repository $repository,
79 Pagination_Helper $pagination_helper
80 ) {
81 $this->options = $options;
82 $this->replace_vars = $replace_vars;
83 $this->context_memoizer = $context_memoizer;
84 $this->repository = $repository;
85 $this->pagination_helper = $pagination_helper;
86 }
87
88 /**
89 * Initializes the integration.
90 *
91 * This is the place to register hooks and filters.
92 *
93 * @return void
94 */
95 public function register_hooks() {
96 \add_filter( 'wpseo_frontend_page_type_simple_page_id', [ $this, 'get_page_id' ] );
97 \add_filter( 'wpseo_breadcrumb_indexables', [ $this, 'add_shop_to_breadcrumbs' ] );
98
99 \add_filter( 'wpseo_title', [ $this, 'title' ], 10, 2 );
100 \add_filter( 'wpseo_metadesc', [ $this, 'description' ], 10, 2 );
101 \add_filter( 'wpseo_canonical', [ $this, 'canonical' ], 10, 2 );
102 \add_filter( 'wpseo_adjacent_rel_url', [ $this, 'adjacent_rel_url' ], 10, 3 );
103 }
104
105 /**
106 * Returns the correct canonical when WooCommerce is enabled.
107 *
108 * @param string $canonical The current canonical.
109 * @param Indexable_Presentation|null $presentation The indexable presentation.
110 *
111 * @return string The correct canonical.
112 */
113 public function canonical( $canonical, $presentation = null ) {
114 if ( ! $this->is_shop_page() ) {
115 return $canonical;
116 }
117
118 $url = $this->get_shop_paginated_link( 'curr', $presentation );
119
120 if ( $url ) {
121 return $url;
122 }
123
124 return $canonical;
125 }
126
127 /**
128 * Returns correct adjacent pages when WooCommerce is enabled.
129 *
130 * @param string $link The current link.
131 * @param string $rel Link relationship, prev or next.
132 * @param Indexable_Presentation|null $presentation The indexable presentation.
133 *
134 * @return string The correct link.
135 */
136 public function adjacent_rel_url( $link, $rel, $presentation = null ) {
137 if ( ! $this->is_shop_page() ) {
138 return $link;
139 }
140
141 if ( $rel !== 'next' && $rel !== 'prev' ) {
142 return $link;
143 }
144
145 $url = $this->get_shop_paginated_link( $rel, $presentation );
146
147 if ( $url ) {
148 return $url;
149 }
150
151 return $link;
152 }
153
154 /**
155 * Adds a breadcrumb for the shop page.
156 *
157 * @param Indexable[] $indexables The array with indexables.
158 *
159 * @return Indexable[] The indexables to be shown in the breadcrumbs, with the shop page added.
160 */
161 public function add_shop_to_breadcrumbs( $indexables ) {
162 $shop_page_id = $this->get_shop_page_id();
163
164 if ( $shop_page_id < 1 ) {
165 return $indexables;
166 }
167
168 foreach ( $indexables as $index => $indexable ) {
169 if ( $indexable->object_type === 'post-type-archive' && $indexable->object_sub_type === 'product' ) {
170 $indexables[ $index ] = $this->repository->find_by_id_and_type( $shop_page_id, 'post' );
171 }
172 }
173
174 return $indexables;
175 }
176
177 /**
178 * Returns the ID of the WooCommerce shop page when the currently opened page is the shop page.
179 *
180 * @param int $page_id The page id.
181 *
182 * @return int The Page ID of the shop.
183 */
184 public function get_page_id( $page_id ) {
185 if ( ! $this->is_shop_page() ) {
186 return $page_id;
187 }
188
189 return $this->get_shop_page_id();
190 }
191
192 /**
193 * Handles the title.
194 *
195 * @param string $title The title.
196 * @param Indexable_Presentation|null $presentation The indexable presentation.
197 *
198 * @return string The title to use.
199 */
200 public function title( $title, $presentation = null ) {
201 $presentation = $this->ensure_presentation( $presentation );
202
203 if ( $presentation->model->title ) {
204 return $title;
205 }
206
207 if ( ! $this->is_shop_page() ) {
208 return $title;
209 }
210
211 if ( ! \is_archive() ) {
212 return $title;
213 }
214
215 $shop_page_id = $this->get_shop_page_id();
216 if ( $shop_page_id < 1 ) {
217 return $title;
218 }
219
220 $product_template_title = $this->get_product_template( 'title-product', $shop_page_id );
221 if ( $product_template_title ) {
222 return $product_template_title;
223 }
224
225 return $title;
226 }
227
228 /**
229 * Handles the meta description.
230 *
231 * @param string $description The title.
232 * @param Indexable_Presentation|null $presentation The indexable presentation.
233 *
234 * @return string The description to use.
235 */
236 public function description( $description, $presentation = null ) {
237 $presentation = $this->ensure_presentation( $presentation );
238
239 if ( $presentation->model->description ) {
240 return $description;
241 }
242
243 if ( ! $this->is_shop_page() ) {
244 return $description;
245 }
246
247 if ( ! \is_archive() ) {
248 return $description;
249 }
250
251 $shop_page_id = $this->get_shop_page_id();
252 if ( $shop_page_id < 1 ) {
253 return $description;
254 }
255
256 $product_template_description = $this->get_product_template( 'metadesc-product', $shop_page_id );
257 if ( $product_template_description ) {
258 return $product_template_description;
259 }
260
261 return $description;
262 }
263
264 /**
265 * Checks if the current page is a WooCommerce shop page.
266 *
267 * @return bool True when the page is a shop page.
268 */
269 protected function is_shop_page() {
270 if ( ! \is_shop() ) {
271 return false;
272 }
273
274 if ( \is_search() ) {
275 return false;
276 }
277
278 return true;
279 }
280
281 /**
282 * Uses template for the given option name and replace the replacement variables on it.
283 *
284 * @param string $option_name The option name to get the template for.
285 * @param string $shop_page_id The page id to retrieve template for.
286 *
287 * @return string The rendered value.
288 */
289 protected function get_product_template( $option_name, $shop_page_id ) {
290 $template = $this->options->get( $option_name );
291 $page = \get_post( $shop_page_id );
292
293 return $this->replace_vars->replace( $template, $page );
294 }
295
296 /**
297 * Returns the id of the set WooCommerce shop page.
298 *
299 * @return int The ID of the set page.
300 */
301 protected function get_shop_page_id() {
302 if ( ! \function_exists( 'wc_get_page_id' ) ) {
303 return -1;
304 }
305
306 return \wc_get_page_id( 'shop' );
307 }
308
309 /**
310 * Get paginated link for shop page.
311 *
312 * @param string $rel Link relationship, prev or next or curr.
313 * @param Indexable_Presentation|null $presentation The indexable presentation.
314 *
315 * @return string|null The link.
316 */
317 protected function get_shop_paginated_link( $rel, $presentation = null ) {
318 $presentation = $this->ensure_presentation( $presentation );
319
320 $permalink = $presentation->get_permalink();
321 if ( ! $permalink ) {
322 return null;
323 }
324
325 $current_page = \max( 1, $this->pagination_helper->get_current_archive_page_number() );
326
327 if ( $rel === 'curr' && $current_page === 1 ) {
328 return $permalink;
329 }
330
331 if ( $rel === 'curr' && $current_page > 1 ) {
332 return $this->pagination_helper->get_paginated_url( $permalink, $current_page );
333 }
334
335 if ( $rel === 'prev' && $current_page === 2 ) {
336 return $permalink;
337 }
338
339 if ( $rel === 'prev' && $current_page > 2 ) {
340 return $this->pagination_helper->get_paginated_url( $permalink, ( $current_page - 1 ) );
341 }
342
343 if ( $rel === 'next' && $current_page < $this->pagination_helper->get_number_of_archive_pages() ) {
344 return $this->pagination_helper->get_paginated_url( $permalink, ( $current_page + 1 ) );
345 }
346
347 return null;
348 }
349
350 /**
351 * Ensures a presentation is available.
352 *
353 * @param Indexable_Presentation $presentation The indexable presentation.
354 *
355 * @return Indexable_Presentation The presentation, taken from the current page if the input was invalid.
356 */
357 protected function ensure_presentation( $presentation ) {
358 if ( \is_a( $presentation, Indexable_Presentation::class ) ) {
359 return $presentation;
360 }
361
362 $context = $this->context_memoizer->for_current_page();
363
364 return $context->presentation;
365 }
366 }
367