PluginProbe
Polylang / 3.5
Polylang v3.5
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 / include / links-directory.php

links-directory.php in Polylang 3.5, at include/links-directory.php

318 lines 10.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 * Links model for use when the language code is added in the url as a directory
8 * for example mysite.com/en/something.
9 *
10 * @since 1.2
11 */
12 class PLL_Links_Directory extends PLL_Links_Permalinks {
13 /**
14 * Relative path to the home url.
15 *
16 * @var string
17 */
18 protected $home_relative;
19
20 /**
21 * Constructor.
22 *
23 * @since 1.2
24 *
25 * @param PLL_Model $model PLL_Model instance.
26 */
27 public function __construct( &$model ) {
28 parent::__construct( $model );
29
30 $this->home_relative = home_url( '/', 'relative' );
31 }
32
33 /**
34 * Adds hooks for rewrite rules.
35 *
36 * @since 1.6
37 *
38 * @return void
39 */
40 public function init() {
41 add_action( 'pll_prepare_rewrite_rules', array( $this, 'prepare_rewrite_rules' ) ); // Ensure it's hooked before `self::do_prepare_rewrite_rules()` is called.
42
43 parent::init();
44
45 if ( did_action( 'setup_theme' ) ) {
46 $this->add_permastruct();
47 } else {
48 add_action( 'setup_theme', array( $this, 'add_permastruct' ), 2 );
49 }
50 }
51
52 /**
53 * Adds the language code in a url.
54 *
55 * @since 1.2
56 * @since 3.4 Accepts now a language slug.
57 *
58 * @param string $url The url to modify.
59 * @param PLL_Language|string|false $language Language object or slug.
60 * @return string The modified url.
61 */
62 public function add_language_to_link( $url, $language ) {
63 if ( $language instanceof PLL_Language ) {
64 $language = $language->slug;
65 }
66
67 if ( ! empty( $language ) ) {
68 $base = $this->options['rewrite'] ? '' : 'language/';
69 $slug = $this->options['default_lang'] === $language && $this->options['hide_default'] ? '' : $base . $language . '/';
70 $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '#^https?://#', '://', $this->home . '/' . $this->root );
71
72 if ( false === strpos( $url, $new = $root . $slug ) ) {
73 $pattern = preg_quote( $root, '#' );
74 $pattern = '#' . $pattern . '#';
75 return preg_replace( $pattern, $new, $url, 1 ); // Only once.
76 }
77 }
78 return $url;
79 }
80
81 /**
82 * Returns the url without the language code.
83 *
84 * @since 1.2
85 *
86 * @param string $url The url to modify.
87 * @return string The modified url.
88 */
89 public function remove_language_from_link( $url ) {
90 $languages = $this->model->get_languages_list(
91 array(
92 'hide_default' => $this->options['hide_default'],
93 'fields' => 'slug',
94 )
95 );
96
97 if ( ! empty( $languages ) ) {
98 $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '#^https?://#', '://', $this->home . '/' . $this->root );
99
100 $pattern = preg_quote( $root, '@' );
101 $pattern = '@' . $pattern . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')(([?#])|(/|$))@';
102 $url = preg_replace( $pattern, $root . '$3', $url );
103 }
104 return $url;
105 }
106
107 /**
108 * Returns the language based on the language code in the url.
109 *
110 * @since 1.2
111 * @since 2.0 Add the $url argument.
112 *
113 * @param string $url Optional, defaults to the current url.
114 * @return string The language slug.
115 */
116 public function get_language_from_url( $url = '' ) {
117 if ( empty( $url ) ) {
118 $url = pll_get_requested_url();
119 }
120
121 $path = (string) wp_parse_url( $url, PHP_URL_PATH );
122 $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root;
123
124 $pattern = (string) wp_parse_url( $root . ( $this->options['rewrite'] ? '' : 'language/' ), PHP_URL_PATH );
125 $pattern = preg_quote( $pattern, '#' );
126 $pattern = '#^' . $pattern . '(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')(/|$)#';
127 return preg_match( $pattern, trailingslashit( $path ), $matches ) ? $matches[1] : ''; // $matches[1] is the slug of the requested language.
128 }
129
130 /**
131 * Returns the home url in a given language.
132 *
133 * @since 1.3.1
134 * @since 3.4 Accepts now a language slug.
135 *
136 * @param PLL_Language|string $language Language object or slug.
137 * @return string
138 */
139 public function home_url( $language ) {
140 if ( $language instanceof PLL_Language ) {
141 $language = $language->slug;
142 }
143
144 $base = $this->options['rewrite'] ? '' : 'language/';
145 $slug = $this->options['default_lang'] === $language && $this->options['hide_default'] ? '' : '/' . $this->root . $base . $language;
146 return trailingslashit( $this->home . $slug );
147 }
148
149 /**
150 * Optionally removes 'language' in permalinks so that we get http://www.myblog/en/ instead of http://www.myblog/language/en/.
151 *
152 * @since 1.2
153 *
154 * @return void
155 */
156 public function add_permastruct() {
157 // Language information always in front of the uri ( 'with_front' => false ).
158 if ( $this->model->has_languages() ) {
159 add_permastruct( 'language', $this->options['rewrite'] ? '%language%' : 'language/%language%', array( 'with_front' => false ) );
160 }
161 }
162
163 /**
164 * Prepares the rewrite rules filters.
165 *
166 * @since 0.8.1
167 * @since 3.5 Hooked to `pll_prepare_rewrite_rules` and remove `$pre` parameter.
168 *
169 * @return void
170 */
171 public function prepare_rewrite_rules() {
172 /*
173 * Don't modify the rules if there is no languages created yet and make sure
174 * to add the filters only once and if all custom post types and taxonomies
175 * have been registered.
176 */
177 if ( ! $this->model->has_languages() || ! did_action( 'wp_loaded' ) || has_filter( 'language_rewrite_rules', '__return_empty_array' ) ) {
178 return;
179 }
180
181 add_filter( 'language_rewrite_rules', '__return_empty_array' ); // Suppress the rules created by WordPress for our taxonomy.
182
183 foreach ( $this->get_rewrite_rules_filters_with_callbacks() as $rule => $callback ) {
184 add_filter( $rule, $callback );
185 }
186 }
187
188 /**
189 * The rewrite rules !
190 *
191 * Always make sure that the default language is at the end in case the language information is hidden for default language.
192 * Thanks to brbrbr http://wordpress.org/support/topic/plugin-polylang-rewrite-rules-not-correct.
193 *
194 * @since 0.8.1
195 *
196 * @param string[] $rules Rewrite rules.
197 * @return string[] Modified rewrite rules.
198 */
199 public function rewrite_rules( $rules ) {
200 $filter = str_replace( '_rewrite_rules', '', current_filter() );
201
202 global $wp_rewrite;
203 $newrules = array();
204
205 $languages = $this->model->get_languages_list( array( 'fields' => 'slug' ) );
206 if ( $this->options['hide_default'] ) {
207 $languages = array_diff( $languages, array( $this->options['default_lang'] ) );
208 }
209
210 if ( ! empty( $languages ) ) {
211 $slug = $wp_rewrite->root . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')/';
212 }
213
214 // For custom post type archives.
215 $cpts = array_intersect( $this->model->get_translated_post_types(), get_post_types( array( '_builtin' => false ) ) );
216 $cpts = $cpts ? '#post_type=(' . implode( '|', $cpts ) . ')#' : '';
217
218 foreach ( $rules as $key => $rule ) {
219 if ( ! is_string( $rule ) || ! is_string( $key ) ) {
220 // Protection against a bug in Sendinblue for WooCommerce. See: https://wordpress.org/support/topic/bug-introduced-in-rewrite-rules/
221 continue;
222 }
223
224 // Special case for translated post types and taxonomies to allow canonical redirection.
225 if ( $this->options['force_lang'] && in_array( $filter, array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies() ) ) ) {
226
227 /**
228 * Filters the rewrite rules to modify.
229 *
230 * @since 1.9.1
231 *
232 * @param bool $modify Whether to modify or not the rule, defaults to true.
233 * @param array $rule Original rewrite rule.
234 * @param string $filter Current set of rules being modified.
235 * @param string|bool $archive Custom post post type archive name or false if it is not a cpt archive.
236 */
237 if ( isset( $slug ) && apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, false ) ) {
238 $newrules[ $slug . str_replace( $wp_rewrite->root, '', ltrim( $key, '^' ) ) ] = str_replace(
239 array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ),
240 array( '[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&' ),
241 $rule
242 ); // Should be enough!
243 }
244
245 $newrules[ $key ] = $rule;
246 }
247
248 // Rewrite rules filtered by language.
249 elseif ( in_array( $filter, $this->always_rewrite ) || in_array( $filter, $this->model->get_filtered_taxonomies() ) || ( $cpts && preg_match( $cpts, $rule, $matches ) && ! strpos( $rule, 'name=' ) ) || ( 'rewrite_rules_array' != $filter && $this->options['force_lang'] ) ) {
250
251 /** This filter is documented in include/links-directory.php */
252 if ( apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, empty( $matches[1] ) ? false : $matches[1] ) ) {
253 if ( isset( $slug ) ) {
254 $newrules[ $slug . str_replace( $wp_rewrite->root, '', ltrim( $key, '^' ) ) ] = str_replace(
255 array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ),
256 array( '[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&' ),
257 $rule
258 ); // Should be enough!
259 }
260
261 if ( $this->options['hide_default'] ) {
262 $newrules[ $key ] = str_replace( '?', '?lang=' . $this->options['default_lang'] . '&', $rule );
263 }
264 } else {
265 $newrules[ $key ] = $rule;
266 }
267 }
268
269 // Unmodified rules.
270 else {
271 $newrules[ $key ] = $rule;
272 }
273 }
274
275 // The home rewrite rule.
276 if ( 'root' == $filter && isset( $slug ) ) {
277 $newrules[ $slug . '?$' ] = $wp_rewrite->index . '?lang=$matches[1]';
278 }
279
280 return $newrules;
281 }
282
283 /**
284 * Removes hooks to filter rewrite rules, called when switching blog @see {PLL_Base::switch_blog()}.
285 * See `self::prepare_rewrite_rules()` for added hooks.
286 *
287 * @since 3.5
288 *
289 * @return void
290 */
291 public function remove_filters() {
292 parent::remove_filters();
293
294 foreach ( $this->get_rewrite_rules_filters_with_callbacks() as $rule => $callback ) {
295 remove_filter( $rule, $callback );
296 }
297 }
298
299 /**
300 * Returns *all* rewrite rules filters with their associated callbacks.
301 *
302 * @since 3.5
303 *
304 * @return callable[] Array of hook names as key and callbacks as values.
305 */
306 protected function get_rewrite_rules_filters_with_callbacks() {
307 $filters = array(
308 'rewrite_rules_array' => array( $this, 'rewrite_rules' ), // Needed for post type archives.
309 );
310
311 foreach ( $this->get_rewrite_rules_filters() as $type ) {
312 $filters[ $type . '_rewrite_rules' ] = array( $this, 'rewrite_rules' );
313 }
314
315 return $filters;
316 }
317 }
318