PluginProbe
Polylang / 3.0
Polylang v3.0
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 / translate-option.php

translate-option.php in Polylang 3.0, at include/translate-option.php

332 lines 11.0 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 * Registers and translates strings in an option.
8 * When a string is updated in an original option, the translations of the old string are assigned to the new original string.
9 *
10 * @since 2.9
11 */
12 class PLL_Translate_Option {
13
14 /**
15 * Array of option keys to translate.
16 *
17 * @var array
18 */
19 private $keys;
20
21 /**
22 * Array of updated strings.
23 *
24 * @var array
25 */
26 private $updated_strings = array();
27
28 /**
29 * Constructor
30 *
31 * @since 2.9
32 *
33 * @param string $name Option name.
34 * @param array $keys Recursive array of option keys to translate in the form:
35 * @example array(
36 * 'option_key_to_translate_1' => 1,
37 * 'option_key_to_translate_2' => 1,
38 * 'my_group' => array(
39 * 'sub_key_to_translate_1' => 1,
40 * 'sub_key_to_translate_2' => 1,
41 * ),
42 * )
43 *
44 * Note: only keys are interpreted. Any scalar can be used as values.
45 * @param array $args {
46 * Optional. Array of arguments for registering the option.
47 *
48 * @type string $context The group in which the strings will be registered.
49 * @type string $sanitize_callback A callback function that sanitizes the option's value.
50 * }
51 */
52 public function __construct( $name, $keys = array(), $args = array() ) {
53 // Registers the strings.
54 $context = isset( $args['context'] ) ? $args['context'] : 'Polylang';
55 $this->register_string_recursive( $context, $name, get_option( $name ), $keys );
56
57 // Translates the strings.
58 $this->keys = $keys;
59 add_filter( 'option_' . $name, array( $this, 'translate' ) ); // Make sure to add this filter after options are registered.
60
61 // Filters updated values.
62 add_filter( 'pre_update_option_' . $name, array( $this, 'pre_update_option' ), 10, 3 );
63 add_action( 'update_option_' . $name, array( $this, 'update_option' ) );
64
65 // Sanitizes translated strings.
66 if ( empty( $args['sanitize_callback'] ) ) {
67 add_filter( 'pll_sanitize_string_translation', array( $this, 'sanitize_option' ), 10, 2 );
68 } else {
69 add_filter( 'pll_sanitize_string_translation', $args['sanitize_callback'], 10, 3 );
70 }
71 }
72
73 /**
74 * Translates the strings registered for an option.
75 *
76 * @since 1.0
77 *
78 * @param mixed $value Either a string to translate or a list of strings to translate.
79 * @return mixed Translated string(s).
80 */
81 public function translate( $value ) {
82 return $this->translate_string_recursive( $value, $this->keys );
83 }
84
85 /**
86 * Recursively translates the strings registered for an option.
87 *
88 * @since 1.0
89 *
90 * @param mixed $values Either a string to translate or a list of strings to translate.
91 * @param array|bool $key Array of option keys to translate.
92 * @return array|string Translated string(s)
93 */
94 protected function translate_string_recursive( $values, $key ) {
95 $children = is_array( $key ) ? $key : array();
96
97 if ( is_array( $values ) || is_object( $values ) ) {
98 if ( count( $children ) ) {
99 foreach ( $children as $name => $child ) {
100 if ( is_array( $values ) && isset( $values[ $name ] ) ) {
101 $values[ $name ] = $this->translate_string_recursive( $values[ $name ], $child );
102 continue;
103 }
104
105 if ( is_object( $values ) && isset( $values->$name ) ) {
106 $values->$name = $this->translate_string_recursive( $values->$name, $child );
107 continue;
108 }
109
110 $pattern = '#^' . str_replace( '*', '(?:.+)', $name ) . '$#';
111
112 foreach ( $values as $n => &$value ) {
113 // The first case could be handled by the next one, but we avoid calls to preg_match here.
114 if ( '*' === $name || ( false !== strpos( $name, '*' ) && preg_match( $pattern, $n ) ) ) {
115 $value = $this->translate_string_recursive( $value, $child );
116 }
117 }
118 }
119 } else {
120 // Parent key is a wildcard and no sub-key has been whitelisted.
121 foreach ( $values as &$value ) {
122 $value = $this->translate_string_recursive( $value, $key );
123 }
124 }
125 } else {
126 $values = pll__( $values );
127 }
128
129 return $values;
130 }
131
132 /**
133 * Recursively registers strings for an option.
134 *
135 * @since 1.0
136 * @since 2.7 Signature modified
137 *
138 * @param string $context The group in which the strings will be registered.
139 * @param string $option Option name.
140 * @param mixed $values Option value.
141 * @param array|bool $key Array of option keys to translate.
142 * @return void
143 */
144 protected function register_string_recursive( $context, $option, $values, $key ) {
145 if ( is_object( $values ) ) {
146 $values = (array) $values;
147 }
148
149 if ( is_array( $values ) ) {
150 $children = is_array( $key ) ? $key : array();
151
152 if ( count( $children ) ) {
153 foreach ( $children as $name => $child ) {
154 if ( isset( $values[ $name ] ) ) {
155 $this->register_string_recursive( $context, $name, $values[ $name ], $child );
156 continue;
157 }
158
159 $pattern = '#^' . str_replace( '*', '(?:.+)', $name ) . '$#';
160
161 foreach ( $values as $n => $value ) {
162 // The first case could be handled by the next one, but we avoid calls to preg_match here.
163 if ( '*' === $name || ( false !== strpos( $name, '*' ) && preg_match( $pattern, $n ) ) ) {
164 $this->register_string_recursive( $context, $n, $value, $child );
165 }
166 }
167 }
168 } else {
169 foreach ( $values as $n => $value ) {
170 // Parent key is a wildcard and no sub-key has been whitelisted.
171 $this->register_string_recursive( $context, $n, $value, $key );
172 }
173 }
174 } else {
175 PLL_Admin_Strings::register_string( $option, $values, $context, true );
176 }
177 }
178
179 /**
180 * Filters an option before it is updated.
181 *
182 * This is the step 1 in the update process, in which we prevent the update of
183 * strings to their translations by filtering them out, and we store the updated strings
184 * for the next step.
185 *
186 * @since 2.9
187 *
188 * @param mixed $value The new, unserialized option value.
189 * @param mixed $old_value The old (filtered) option value.
190 * @param string $name Option name.
191 * @return mixed
192 */
193 public function pre_update_option( $value, $old_value, $name ) {
194 // Stores the unfiltered old option value before it is updated in DB.
195 remove_filter( 'option_' . $name, array( $this, 'translate' ), 10, 2 );
196 $unfiltered_old_value = get_option( $name );
197 add_filter( 'option_' . $name, array( $this, 'translate' ), 20, 2 );
198
199 // Load strings translations according to the admin language filter
200 $locale = pll_current_language( 'locale' );
201 if ( empty( $locale ) ) {
202 $locale = pll_default_language( 'locale' );
203 }
204 PLL()->load_strings_translations( $locale );
205
206 // Filters out the strings which would be updated to their translations and stores the updated strings.
207 $value = $this->check_value_recursive( $unfiltered_old_value, $value, $this->keys );
208
209 return $value;
210 }
211
212 /**
213 * Updates the string translations to keep the same translated value when updating the original option.
214 *
215 * This is the step 2 in the update process. Knowing all strings that have been updated,
216 * we remove the old strings from the strings translations and replace them by
217 * the new strings with the old translations.
218 *
219 * @since 2.9
220 *
221 * @return void
222 */
223 public function update_option() {
224 $curlang = pll_current_language();
225
226 if ( ! empty( $this->updated_strings ) ) {
227 foreach ( pll_languages_list() as $lang ) {
228
229 $language = PLL()->model->get_language( $lang );
230 $mo = new PLL_MO();
231 $mo->import_from_db( $language );
232
233 foreach ( $this->updated_strings as $old_string => $string ) {
234 $translation = $mo->translate( $old_string );
235 if ( ( empty( $curlang ) && $translation === $old_string ) || $lang === $curlang ) {
236 $translation = $string;
237 }
238
239 // Removes the old entry and replace it by the new one, with the same translation.
240 $mo->delete_entry( $old_string );
241 $mo->add_entry( $mo->make_entry( $string, $translation ) );
242 }
243
244 $mo->export_to_db( $language );
245 }
246 }
247 }
248
249 /**
250 * Recursively compares the updated strings to the translation of the old string.
251 *
252 * This is the heart of the update process. If an updated string is found to be
253 * the same as the translation of the old string, we restore the old string to
254 * prevent the update in {@see PLL_Translate_Option::pre_update_option()}, otherwise
255 * the updated string is stored in {@see PLL_Translate_Option::updated_strings} to be able to
256 * later assign the translations to the new value in {@see PLL_Translate_Option::update_option()}.
257 *
258 * @since 2.9
259 *
260 * @param mixed $old_values The old option value.
261 * @param mixed $values The new option value..
262 * @param array|bool $key Array of option keys to translate.
263 * @return mixed
264 */
265 protected function check_value_recursive( $old_values, $values, $key ) {
266 $children = is_array( $key ) ? $key : array();
267
268 if ( is_array( $values ) || is_object( $values ) ) {
269 if ( count( $children ) ) {
270 foreach ( $children as $name => $child ) {
271 if ( is_array( $values ) && is_array( $old_values ) && isset( $old_values[ $name ], $values[ $name ] ) ) {
272 $values[ $name ] = $this->check_value_recursive( $old_values[ $name ], $values[ $name ], $child );
273 continue;
274 }
275
276 if ( is_object( $values ) && is_object( $old_values ) && isset( $old_values->$name, $values->$name ) ) {
277 $values->$name = $this->check_value_recursive( $old_values->$name, $values->$name, $child );
278 continue;
279 }
280
281 $pattern = '#^' . str_replace( '*', '(?:.+)', $name ) . '$#';
282
283 foreach ( $values as $n => $value ) {
284 // The first case could be handled by the next one, but we avoid calls to preg_match here.
285 if ( '*' === $name || ( false !== strpos( $name, '*' ) && preg_match( $pattern, $n ) ) ) {
286 if ( is_array( $values ) && is_array( $old_values ) && isset( $old_values[ $n ] ) ) {
287 $values[ $n ] = $this->check_value_recursive( $old_values[ $n ], $value, $child );
288 }
289
290 if ( is_object( $values ) && is_object( $old_values ) && isset( $old_values->$n ) ) {
291 $values->$n = $this->check_value_recursive( $old_values->$n, $value, $child );
292 }
293 }
294 }
295 }
296 } else {
297 // Parent key is a wildcard and no sub-key has been whitelisted.
298 foreach ( $values as $n => $value ) {
299 if ( is_array( $values ) && is_array( $old_values ) && isset( $old_values[ $n ] ) ) {
300 $values[ $n ] = $this->check_value_recursive( $old_values[ $n ], $value, $key );
301 }
302
303 if ( is_object( $values ) && is_object( $old_values ) && isset( $old_values->$n ) ) {
304 $values->$n = $this->check_value_recursive( $old_values->$n, $value, $key );
305 }
306 }
307 }
308 } elseif ( $old_values !== $values ) {
309 if ( pll__( $old_values ) === $values ) {
310 $values = $old_values; // Prevents updating the value to its translation.
311 } else {
312 $this->updated_strings[ $old_values ] = $values; // Stores the updated strings.
313 }
314 }
315
316 return $values;
317 }
318
319 /**
320 * Sanitizes the option value.
321 *
322 * @since 2.9
323 *
324 * @param string $value The unsanitised value.
325 * @param string $name The name of the option.
326 * @return string Sanitized value.
327 */
328 public function sanitize_option( $value, $name ) {
329 return sanitize_option( $name, $value );
330 }
331 }
332