PluginProbe
Polylang / 2.8.2
Polylang v2.8.2
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.2, at integrations/wpseo/wpseo.php

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