PluginProbe
Polylang / 2.8.3
Polylang v2.8.3
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / integrations / wpseo / wpseo.php

wpseo.php in Polylang 2.8.3, at integrations/wpseo/wpseo.php

517 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 /**
7 * Manages the compatibility with Yoast SEO
8 * Version tested: 11.5
9 *
10 * @since 2.3
11 */
12 class PLL_WPSEO {
13 /**
14 * Translate options and add specific filters and actions
15 *
16 * @since 1.6.4
17 */
18 public function init() {
19 if ( PLL() instanceof PLL_Frontend ) {
20 add_filter( 'option_wpseo_titles', array( $this, 'wpseo_translate_titles' ) );
21
22 // Filters sitemap queries to remove inactive language or to get
23 // one sitemap per language when using multiple domains or subdomains
24 // because WPSEO does not accept several domains or subdomains in one sitemap
25 add_filter( 'wpseo_posts_join', array( $this, 'wpseo_posts_join' ), 10, 2 );
26 add_filter( 'wpseo_posts_where', array( $this, 'wpseo_posts_where' ), 10, 2 );
27 add_filter( 'wpseo_typecount_join', array( $this, 'wpseo_posts_join' ), 10, 2 );
28 add_filter( 'wpseo_typecount_where', array( $this, 'wpseo_posts_where' ), 10, 2 );
29
30 if ( PLL()->options['force_lang'] > 1 ) {
31 add_filter( 'wpseo_enable_xml_sitemap_transient_caching', '__return_false' ); // Disable cache! otherwise WPSEO keeps only one domain (thanks to Junaid Bhura)
32 add_filter( 'home_url', array( $this, 'wpseo_home_url' ), 10, 2 ); // Fix home_url
33 add_action( 'setup_theme', array( $this, 'maybe_deactivate_sitemap' ) ); // Deactivate sitemaps for inactive languages.
34 } else {
35 // Get all terms in all languages when the language is set from the content or directory name
36 add_filter( 'get_terms_args', array( $this, 'wpseo_remove_terms_filter' ) );
37 add_action( 'pre_get_posts', array( $this, 'before_sitemap' ), 0 ); // Needs to be fired before WPSEO_Sitemaps::redirect()
38 }
39
40 add_filter( 'pll_home_url_white_list', array( $this, 'wpseo_home_url_white_list' ) );
41 if ( version_compare( WPSEO_VERSION, '14.0', '<' ) ) {
42 add_action( 'wpseo_opengraph', array( $this, 'wpseo_ogp' ), 2 );
43 } else {
44 add_filter( 'wpseo_frontend_presenters', array( $this, 'wpseo_frontend_presenters' ) );
45 }
46 add_filter( 'wpseo_canonical', array( $this, 'wpseo_canonical' ) );
47 add_filter( 'wpseo_frontend_presentation', array( $this, 'frontend_presentation' ) );
48 add_filter( 'wpseo_breadcrumb_indexables', array( $this, 'breadcrumb_indexables' ) );
49 } else {
50 add_action( 'admin_init', array( $this, 'wpseo_register_strings' ) );
51
52 // Primary category
53 add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ) );
54 add_filter( 'pll_translate_post_meta', array( $this, 'translate_post_meta' ), 10, 3 );
55 }
56 }
57
58 /**
59 * Registers strings for custom post types and custom taxonomies titles and meta descriptions
60 *
61 * @since 2.0
62 */
63 public function wpseo_register_strings() {
64 $options = get_option( 'wpseo_titles' );
65 foreach ( get_post_types( array( 'public' => true, '_builtin' => false ) ) as $t ) {
66 if ( pll_is_translated_post_type( $t ) ) {
67 $this->_wpseo_register_strings( $options, array( 'title-' . $t, 'metadesc-' . $t ) );
68 }
69 }
70 foreach ( get_post_types( array( 'has_archive' => true, '_builtin' => false ) ) as $t ) {
71 if ( pll_is_translated_post_type( $t ) ) {
72 $this->_wpseo_register_strings( $options, array( 'title-ptarchive-' . $t, 'metadesc-ptarchive-' . $t, 'bctitle-ptarchive-' . $t ) );
73 }
74 }
75 foreach ( get_taxonomies( array( 'public' => true, '_builtin' => false ) ) as $t ) {
76 if ( pll_is_translated_taxonomy( $t ) ) {
77 $this->_wpseo_register_strings( $options, array( 'title-tax-' . $t, 'metadesc-tax-' . $t ) );
78 }
79 }
80 }
81
82 /**
83 * Helper function to translate custom post types and custom taxonomies titles and meta descriptions
84 *
85 * @since 2.1.6
86 *
87 * @param array $options
88 * @param array $titles
89 * @return array
90 */
91 protected function _wpseo_translate_titles( $options, $titles ) {
92 foreach ( $titles as $title ) {
93 if ( ! empty( $options[ $title ] ) ) {
94 $options[ $title ] = pll__( $options[ $title ] );
95 }
96 }
97 return $options;
98 }
99
100 /**
101 * Translates strings for custom post types and custom taxonomies titles and meta descriptions
102 *
103 * @since 2.0
104 *
105 * @param array $options
106 * @return array
107 */
108 public function wpseo_translate_titles( $options ) {
109 if ( PLL() instanceof PLL_Frontend ) {
110 foreach ( get_post_types( array( 'public' => true, '_builtin' => false ) ) as $t ) {
111 if ( pll_is_translated_post_type( $t ) ) {
112 $options = $this->_wpseo_translate_titles( $options, array( 'title-' . $t, 'metadesc-' . $t ) );
113 }
114 }
115 foreach ( get_post_types( array( 'has_archive' => true, '_builtin' => false ) ) as $t ) {
116 if ( pll_is_translated_post_type( $t ) ) {
117 $options = $this->_wpseo_translate_titles( $options, array( 'title-ptarchive-' . $t, 'metadesc-ptarchive-' . $t, 'bctitle-ptarchive-' . $t ) );
118 }
119 }
120 foreach ( get_taxonomies( array( 'public' => true, '_builtin' => false ) ) as $t ) {
121 if ( pll_is_translated_taxonomy( $t ) ) {
122 $options = $this->_wpseo_translate_titles( $options, array( 'title-tax-' . $t, 'metadesc-tax-' . $t ) );
123 }
124 }
125 }
126 return $options;
127 }
128
129 /**
130 * Fixes the home url as well as the stylesheet url
131 * Only when using multiple domains or subdomains
132 *
133 * @since 1.6.4
134 *
135 * @param string $url
136 * @param string $path
137 * @return $url
138 */
139 public function wpseo_home_url( $url, $path ) {
140 if ( empty( $path ) ) {
141 $path = ltrim( wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' );
142 }
143
144 if ( 'sitemap_index.xml' === $path || preg_match( '#([^/]+?)-sitemap([0-9]+)?\.xml|([a-z]+)?-?sitemap\.xsl#', $path ) ) {
145 $url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang );
146 }
147
148 return $url;
149 }
150
151 /**
152 * Get active languages for the sitemaps
153 *
154 * @since 2.0
155 *
156 * @return array list of active language slugs, empty if all languages are active
157 */
158 protected function wpseo_get_active_languages() {
159 $languages = PLL()->model->get_languages_list();
160 if ( wp_list_filter( $languages, array( 'active' => false ) ) ) {
161 return wp_list_pluck( wp_list_filter( $languages, array( 'active' => false ), 'NOT' ), 'slug' );
162 }
163 return array();
164 }
165
166 /**
167 * Modifies the sql request for posts sitemaps
168 * Only when using multiple domains or subdomains or if some languages are not active
169 *
170 * @since 1.6.4
171 *
172 * @param string $sql JOIN clause
173 * @param string $post_type
174 * @return string
175 */
176 public function wpseo_posts_join( $sql, $post_type ) {
177 return pll_is_translated_post_type( $post_type ) && ( PLL()->options['force_lang'] > 1 || $this->wpseo_get_active_languages() ) ? $sql . PLL()->model->post->join_clause() : $sql;
178 }
179
180 /**
181 * Modifies the sql request for posts sitemaps
182 * Only when using multiple domains or subdomains or if some languages are not active
183 *
184 * @since 1.6.4
185 *
186 * @param string $sql WHERE clause
187 * @param string $post_type
188 * @return string
189 */
190 public function wpseo_posts_where( $sql, $post_type ) {
191 if ( pll_is_translated_post_type( $post_type ) ) {
192 if ( PLL()->options['force_lang'] > 1 ) {
193 return $sql . PLL()->model->post->where_clause( PLL()->curlang );
194 }
195
196 if ( $languages = $this->wpseo_get_active_languages() ) {
197 return $sql . PLL()->model->post->where_clause( $languages );
198 }
199 }
200 return $sql;
201 }
202
203 /**
204 * Removes the language filter (and remove inactive languages) for the taxonomy sitemaps
205 * Only when the language is set from the content or directory name
206 *
207 * @since 1.0.3
208 *
209 * @param array $args get_terms arguments
210 * @return array modified list of arguments
211 */
212 public function wpseo_remove_terms_filter( $args ) {
213 if ( isset( $GLOBALS['wp_query']->query['sitemap'] ) ) {
214 $args['lang'] = implode( ',', $this->wpseo_get_active_languages() );
215 }
216 return $args;
217 }
218
219 /**
220 * Deactivates the sitemap for inactive languages when using subdomains or multiple domains
221 *
222 * @since 2.6.1
223 */
224 public function maybe_deactivate_sitemap() {
225 global $wpseo_sitemaps;
226
227 if ( isset( $wpseo_sitemaps ) ) {
228 $active_languages = $this->wpseo_get_active_languages();
229 if ( ! empty( $active_languages ) && ! in_array( pll_current_language(), $active_languages ) ) {
230 remove_action( 'pre_get_posts', array( $wpseo_sitemaps, 'redirect' ), 1 );
231 }
232 }
233 }
234
235 /**
236 * Add filters before the sitemap is evaluated and outputed
237 *
238 * @since 2.6
239 *
240 * @param object $query Instance of WP_Query being filtered.
241 */
242 public function before_sitemap( $query ) {
243 $type = $query->get( 'sitemap' );
244
245 // Add the post post type archives in all languages to the sitemap
246 // Add the homepages for all languages to the sitemap when the front page displays posts
247 if ( $type && pll_is_translated_post_type( $type ) && ( 'post' !== $type || ! get_option( 'page_on_front' ) ) ) {
248 add_filter( "wpseo_sitemap_{$type}_content", array( $this, 'add_post_type_archive' ) );
249 }
250 }
251
252 /**
253 * Generates a post type archive sitemap url
254 *
255 * @since 2.6.1
256 *
257 * @param string $link The url.
258 * @param string $post_type The post type name.
259 * @return string Formatted sitemap url.
260 */
261 protected function format_sitemap_url( $link, $post_type ) {
262 global $wpseo_sitemaps;
263
264 return $wpseo_sitemaps->renderer->sitemap_url(
265 array(
266 'loc' => $link,
267 'mod' => WPSEO_Sitemaps::get_last_modified_gmt( $post_type ),
268 'pri' => 1,
269 'chf' => 'daily',
270 )
271 );
272 }
273
274 /**
275 * Adds the home and post type archives urls for all (active) languages to the sitemap
276 *
277 * @since 2.6
278 *
279 * @param string $str additional urls to sitemap post
280 * @return string
281 */
282 public function add_post_type_archive( $str ) {
283 $post_type = substr( substr( current_filter(), 14 ), 0, -8 );
284 $post_type_obj = get_post_type_object( $post_type );
285 $languages = wp_list_filter( PLL()->model->get_languages_list(), array( 'active' => false ), 'NOT' );
286
287 if ( 'post' === $post_type ) {
288 if ( ! empty( PLL()->options['hide_default'] ) ) {
289 // The home url is of course already added by WPSEO.
290 $languages = wp_list_filter( $languages, array( 'slug' => pll_default_language() ), 'NOT' );
291 }
292
293 foreach ( $languages as $lang ) {
294 $str .= $this->format_sitemap_url( pll_home_url( $lang->slug ), $post_type );
295 }
296 } elseif ( $post_type_obj->has_archive ) {
297 // Exclude cases where a post type archive is attached to a page (ex: WooCommerce).
298 $slug = ( true === $post_type_obj->has_archive ) ? $post_type_obj->rewrite['slug'] : $post_type_obj->has_archive;
299
300 if ( ! wpcom_vip_get_page_by_path( $slug ) ) {
301 // The post type archive in the current language is already added by WPSEO.
302 $languages = wp_list_filter( $languages, array( 'slug' => pll_current_language() ), 'NOT' );
303
304 foreach ( $languages as $lang ) {
305 PLL()->curlang = $lang; // Switch the language to get the correct archive link.
306 $link = get_post_type_archive_link( $post_type );
307 $str .= $this->format_sitemap_url( $link, $post_type );
308 }
309 }
310 }
311
312 return $str;
313 }
314
315 /**
316 * Filters home url
317 *
318 * @since 1.1.2
319 *
320 * @param array $arr
321 * @return array
322 */
323 public function wpseo_home_url_white_list( $arr ) {
324 return array_merge( $arr, array( array( 'file' => 'wordpress-seo' ) ) );
325 }
326
327 /**
328 * Get alternate language codes for Opengraph
329 *
330 * @since 2.7.3
331 *
332 * @return array
333 */
334 protected function get_ogp_alternate_languages() {
335 $alternates = array();
336
337 foreach ( PLL()->model->get_languages_list() as $language ) {
338 if ( PLL()->curlang->slug !== $language->slug && PLL()->links->get_translation_url( $language ) && isset( $language->facebook ) ) {
339 $alternates[] = $language->facebook;
340 }
341 }
342
343 // There is a risk that 2 languages have the same Facebook locale. So let's make sure to output each locale only once.
344 return array_unique( $alternates );
345 }
346
347 /**
348 * Adds opengraph support for translations
349 *
350 * @since 1.6
351 */
352 public function wpseo_ogp() {
353 global $wpseo_og;
354
355 // WPSEO already deals with the locale
356 if ( did_action( 'pll_init' ) && method_exists( $wpseo_og, 'og_tag' ) ) {
357 foreach ( $this->get_ogp_alternate_languages() as $lang ) {
358 $wpseo_og->og_tag( 'og:locale:alternate', $lang );
359 }
360 }
361 }
362
363 /**
364 * Adds opengraph support for translations
365 *
366 * @since 2.7.3
367 *
368 * @param array $presenters An array of objects implementing Abstract_Indexable_Presenter
369 * @return array
370 */
371 public function wpseo_frontend_presenters( $presenters ) {
372 $_presenters = array();
373
374 foreach ( $presenters as $presenter ) {
375 $_presenters[] = $presenter;
376 if ( $presenter instanceof Yoast\WP\SEO\Presenters\Open_Graph\Locale_Presenter ) {
377 foreach ( $this->get_ogp_alternate_languages() as $lang ) {
378 $_presenters[] = new PLL_WPSEO_OGP( $lang );
379 }
380 }
381 }
382 return $_presenters;
383 }
384
385 /**
386 * Fixes the canonical front page url as unlike WP, WPSEO does not add a trailing slash to the canonical front page url
387 *
388 * @since 1.7.10
389 *
390 * @param string $url
391 * @return $url
392 */
393 public function wpseo_canonical( $url ) {
394 return is_front_page( $url ) && get_option( 'permalink_structure' ) ? trailingslashit( $url ) : $url;
395 }
396
397 /**
398 * Fixes the links and strings stored in the indexable table since Yoast SEO 14.0
399 *
400 * @since 2.8.2
401 *
402 * @param object $presentation The indexable presentation.
403 * @return object
404 */
405 public function frontend_presentation( $presentation ) {
406 switch ( $presentation->model->object_type ) {
407 case 'home-page':
408 $presentation->model->permalink = pll_home_url();
409 $presentation->model->title = WPSEO_Options::get( 'title-home-wpseo' );
410 $presentation->model->description = WPSEO_Options::get( 'title-home-wpseo' );
411 break;
412
413 case 'post-type-archive':
414 $presentation->model->permalink = get_post_type_archive_link( $presentation->model->object_sub_type );
415 $presentation->model->title = WPSEO_Options::get( 'title-ptarchive-' . $presentation->model->object_sub_type );
416 $presentation->model->description = WPSEO_Options::get( 'metadesc-ptarchive-' . $presentation->model->object_sub_type );
417 break;
418
419 case 'user':
420 $presentation->model->permalink = get_author_posts_url( $presentation->model->object_id );
421 break;
422
423 case 'system-page':
424 if ( '404' === $presentation->model->object_sub_type ) {
425 $presentation->model->title = WPSEO_Options::get( 'title-404-wpseo' );
426 }
427 break;
428 }
429
430 return $presentation;
431 }
432
433 /**
434 * Fixes the breadcrumb links and strings stored in the indexable table since Yoast SEO 14.0
435 *
436 * @since 2.8.3
437 *
438 * @param array $indexables An array of Indexable objects.
439 * @return object
440 */
441 public function breadcrumb_indexables( $indexables ) {
442 foreach ( $indexables as &$indexable ) {
443 switch ( $indexable->object_type ) {
444 case 'home-page':
445 $indexable->permalink = pll_home_url();
446 $indexable->breadcrumb_title = pll__( WPSEO_Options::get( 'breadcrumbs-home' ) );
447 break;
448
449 case 'post-type-archive':
450 $indexable->permalink = get_post_type_archive_link( $indexable->object_sub_type );
451 $indexable->breadcrumb_title = pll__( WPSEO_Options::get( 'bctitle-ptarchive-' . $indexable->object_sub_type ) );
452 break;
453 }
454 }
455
456 return $indexables;
457 }
458
459 /**
460 * Helper function to register strings for custom post types and custom taxonomies titles and meta descriptions
461 *
462 * @since 2.1.6
463 *
464 * @param array $options
465 * @param array $titles
466 * @return array
467 */
468 protected function _wpseo_register_strings( $options, $titles ) {
469 foreach ( $titles as $title ) {
470 if ( ! empty( $options[ $title ] ) ) {
471 pll_register_string( $title, $options[ $title ], 'wordpress-seo' );
472 }
473 }
474 return $options;
475 }
476
477 /**
478 * Synchronize the primary term
479 *
480 * @since 2.3.3
481 *
482 * @param array $keys List of custom fields names.
483 * @return array
484 */
485 public function copy_post_metas( $keys ) {
486 $taxonomies = get_taxonomies(
487 array(
488 'hierarchical' => true,
489 'public' => true,
490 )
491 );
492
493 foreach ( $taxonomies as $taxonomy ) {
494 $keys[] = '_yoast_wpseo_primary_' . $taxonomy;
495 }
496
497 return $keys;
498 }
499
500 /**
501 * Translate the primary term during the synchronization process
502 *
503 * @since 2.3.3
504 *
505 * @param int $value Meta value.
506 * @param string $key Meta key.
507 * @param string $lang Language of target.
508 * @return int
509 */
510 public function translate_post_meta( $value, $key, $lang ) {
511 if ( false !== strpos( $key, '_yoast_wpseo_primary_' ) ) {
512 $value = pll_get_term( $value, $lang );
513 }
514 return $value;
515 }
516 }
517