PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.9
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.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 27.9, at src/integrations/third-party/woocommerce.php

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