PluginProbe
Polylang / 3.0.1
Polylang v3.0.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 3.0.1, at integrations/wpseo/wpseo.php

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