PluginProbe
Polylang / 2.9.1
Polylang v2.9.1
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.9.1, at integrations/wpseo/wpseo.php

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