PluginProbe
Polylang / 2.8.1
Polylang v2.8.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.8.1, at integrations/wpseo/wpseo.php

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