Completed
Pull Request — master (#9435)
by Georg
18:55
created
lib/private/L10N/Factory.php 1 patch
Indentation   +408 added lines, -408 removed lines patch added patch discarded remove patch
@@ -40,412 +40,412 @@
 block discarded – undo
40 40
  */
41 41
 class Factory implements IFactory {
42 42
 
43
-	/** @var string */
44
-	protected $requestLanguage = '';
45
-
46
-	/**
47
-	 * cached instances
48
-	 * @var array Structure: Lang => App => \OCP\IL10N
49
-	 */
50
-	protected $instances = [];
51
-
52
-	/**
53
-	 * @var array Structure: App => string[]
54
-	 */
55
-	protected $availableLanguages = [];
56
-
57
-	/**
58
-	 * @var array Structure: string => callable
59
-	 */
60
-	protected $pluralFunctions = [];
61
-
62
-	/** @var IConfig */
63
-	protected $config;
64
-
65
-	/** @var IRequest */
66
-	protected $request;
67
-
68
-	/** @var IUserSession */
69
-	protected $userSession;
70
-
71
-	/** @var string */
72
-	protected $serverRoot;
73
-
74
-	/**
75
-	 * @param IConfig $config
76
-	 * @param IRequest $request
77
-	 * @param IUserSession $userSession
78
-	 * @param string $serverRoot
79
-	 */
80
-	public function __construct(IConfig $config,
81
-								IRequest $request,
82
-								IUserSession $userSession,
83
-								$serverRoot) {
84
-		$this->config = $config;
85
-		$this->request = $request;
86
-		$this->userSession = $userSession;
87
-		$this->serverRoot = $serverRoot;
88
-	}
89
-
90
-	/**
91
-	 * Get a language instance
92
-	 *
93
-	 * @param string $app
94
-	 * @param string|null $lang
95
-	 * @return \OCP\IL10N
96
-	 */
97
-	public function get($app, $lang = null) {
98
-		$app = \OC_App::cleanAppId($app);
99
-		if ($lang !== null) {
100
-			$lang = str_replace(array('\0', '/', '\\', '..'), '', (string) $lang);
101
-		}
102
-
103
-		$forceLang = $this->config->getSystemValue('force_language', false);
104
-		if (is_string($forceLang)) {
105
-			$lang = $forceLang;
106
-		}
107
-
108
-		if ($lang === null || !$this->languageExists($app, $lang)) {
109
-			$lang = $this->findLanguage($app);
110
-		}
111
-
112
-		if (!isset($this->instances[$lang][$app])) {
113
-			$this->instances[$lang][$app] = new L10N(
114
-				$this, $app, $lang,
115
-				$this->getL10nFilesForApp($app, $lang)
116
-			);
117
-		}
118
-
119
-		return $this->instances[$lang][$app];
120
-	}
121
-
122
-	/**
123
-	 * Find the best language
124
-	 *
125
-	 * @param string|null $app App id or null for core
126
-	 * @return string language If nothing works it returns 'en'
127
-	 */
128
-	public function findLanguage($app = null) {
129
-		if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) {
130
-			return $this->requestLanguage;
131
-		}
132
-
133
-		$forceLang = $this->config->getSystemValue('force_language', false);
134
-		if (is_string($forceLang)) {
135
-			if ($this->languageExists($app, $forceLang)) {
136
-				return $forceLang;
137
-			}
138
-		}
139
-
140
-		/**
141
-		 * At this point Nextcloud might not yet be installed and thus the lookup
142
-		 * in the preferences table might fail. For this reason we need to check
143
-		 * whether the instance has already been installed
144
-		 *
145
-		 * @link https://github.com/owncloud/core/issues/21955
146
-		 */
147
-		if($this->config->getSystemValue('installed', false)) {
148
-			$userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() :  null;
149
-			if(!is_null($userId)) {
150
-				$userLang = $this->config->getUserValue($userId, 'core', 'lang', null);
151
-			} else {
152
-				$userLang = null;
153
-			}
154
-		} else {
155
-			$userId = null;
156
-			$userLang = null;
157
-		}
158
-
159
-		if ($userLang) {
160
-			$this->requestLanguage = $userLang;
161
-			if ($this->languageExists($app, $userLang)) {
162
-				return $userLang;
163
-			}
164
-		}
165
-
166
-		try {
167
-			// Try to get the language from the Request
168
-			$lang = $this->getLanguageFromRequest($app);
169
-			if ($userId !== null && $app === null && !$userLang) {
170
-				$this->config->setUserValue($userId, 'core', 'lang', $lang);
171
-			}
172
-			return $lang;
173
-		} catch (LanguageNotFoundException $e) {
174
-			// Finding language from request failed fall back to default language
175
-			$defaultLanguage = $this->config->getSystemValue('default_language', false);
176
-			if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) {
177
-				return $defaultLanguage;
178
-			}
179
-		}
180
-
181
-		// We could not find any language so fall back to english
182
-		return 'en';
183
-	}
184
-
185
-	/**
186
-	 * Find all available languages for an app
187
-	 *
188
-	 * @param string|null $app App id or null for core
189
-	 * @return array an array of available languages
190
-	 */
191
-	public function findAvailableLanguages($app = null) {
192
-		$key = $app;
193
-		if ($key === null) {
194
-			$key = 'null';
195
-		}
196
-
197
-		// also works with null as key
198
-		if (!empty($this->availableLanguages[$key])) {
199
-			return $this->availableLanguages[$key];
200
-		}
201
-
202
-		$available = ['en']; //english is always available
203
-		$dir = $this->findL10nDir($app);
204
-		if (is_dir($dir)) {
205
-			$files = scandir($dir);
206
-			if ($files !== false) {
207
-				foreach ($files as $file) {
208
-					if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
209
-						$available[] = substr($file, 0, -5);
210
-					}
211
-				}
212
-			}
213
-		}
214
-
215
-		// merge with translations from theme
216
-		$theme = $this->config->getSystemValue('theme');
217
-		if (!empty($theme)) {
218
-			$themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot));
219
-
220
-			if (is_dir($themeDir)) {
221
-				$files = scandir($themeDir);
222
-				if ($files !== false) {
223
-					foreach ($files as $file) {
224
-						if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
225
-							$available[] = substr($file, 0, -5);
226
-						}
227
-					}
228
-				}
229
-			}
230
-		}
231
-
232
-		$this->availableLanguages[$key] = $available;
233
-		return $available;
234
-	}
235
-
236
-	/**
237
-	 * @param string|null $app App id or null for core
238
-	 * @param string $lang
239
-	 * @return bool
240
-	 */
241
-	public function languageExists($app, $lang) {
242
-		if ($lang === 'en') {//english is always available
243
-			return true;
244
-		}
245
-
246
-		$languages = $this->findAvailableLanguages($app);
247
-		return array_search($lang, $languages) !== false;
248
-	}
249
-
250
-	/**
251
-	 * @param string|null $app
252
-	 * @return string
253
-	 * @throws LanguageNotFoundException
254
-	 */
255
-	private function getLanguageFromRequest($app) {
256
-		$header = $this->request->getHeader('ACCEPT_LANGUAGE');
257
-		if ($header !== '') {
258
-			$available = $this->findAvailableLanguages($app);
259
-
260
-			// E.g. make sure that 'de' is before 'de_DE'.
261
-			sort($available);
262
-
263
-			$preferences = preg_split('/,\s*/', strtolower($header));
264
-			foreach ($preferences as $preference) {
265
-				list($preferred_language) = explode(';', $preference);
266
-				$preferred_language = str_replace('-', '_', $preferred_language);
267
-
268
-				foreach ($available as $available_language) {
269
-					if ($preferred_language === strtolower($available_language)) {
270
-						return $this->respectDefaultLanguage($app, $available_language);
271
-					}
272
-				}
273
-
274
-				// Fallback from de_De to de
275
-				foreach ($available as $available_language) {
276
-					if (substr($preferred_language, 0, 2) === $available_language) {
277
-						return $available_language;
278
-					}
279
-				}
280
-			}
281
-		}
282
-
283
-		throw new LanguageNotFoundException();
284
-	}
285
-
286
-	/**
287
-	 * if default language is set to de_DE (formal German) this should be
288
-	 * preferred to 'de' (non-formal German) if possible
289
-	 *
290
-	 * @param string|null $app
291
-	 * @param string $lang
292
-	 * @return string
293
-	 */
294
-	protected function respectDefaultLanguage($app, $lang) {
295
-		$result = $lang;
296
-		$defaultLanguage = $this->config->getSystemValue('default_language', false);
297
-
298
-		// use formal version of german ("Sie" instead of "Du") if the default
299
-		// language is set to 'de_DE' if possible
300
-		if (is_string($defaultLanguage) &&
301
-			strtolower($lang) === 'de' &&
302
-			strtolower($defaultLanguage) === 'de_de' &&
303
-			$this->languageExists($app, 'de_DE')
304
-		) {
305
-			$result = 'de_DE';
306
-		}
307
-
308
-		return $result;
309
-	}
310
-
311
-	/**
312
-	 * Checks if $sub is a subdirectory of $parent
313
-	 *
314
-	 * @param string $sub
315
-	 * @param string $parent
316
-	 * @return bool
317
-	 */
318
-	private function isSubDirectory($sub, $parent) {
319
-		// Check whether $sub contains no ".."
320
-		if(strpos($sub, '..') !== false) {
321
-			return false;
322
-		}
323
-
324
-		// Check whether $sub is a subdirectory of $parent
325
-		if (strpos($sub, $parent) === 0) {
326
-			return true;
327
-		}
328
-
329
-		return false;
330
-	}
331
-
332
-	/**
333
-	 * Get a list of language files that should be loaded
334
-	 *
335
-	 * @param string $app
336
-	 * @param string $lang
337
-	 * @return string[]
338
-	 */
339
-	// FIXME This method is only public, until OC_L10N does not need it anymore,
340
-	// FIXME This is also the reason, why it is not in the public interface
341
-	public function getL10nFilesForApp($app, $lang) {
342
-		$languageFiles = [];
343
-
344
-		$i18nDir = $this->findL10nDir($app);
345
-		$transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json';
346
-
347
-		if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/')
348
-				|| $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/')
349
-				|| $this->isSubDirectory($transFile, $this->serverRoot . '/settings/l10n/')
350
-				|| $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/')
351
-			)
352
-			&& file_exists($transFile)) {
353
-			// load the translations file
354
-			$languageFiles[] = $transFile;
355
-		}
356
-
357
-		// merge with translations from theme
358
-		$theme = $this->config->getSystemValue('theme');
359
-		if (!empty($theme)) {
360
-			$transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot));
361
-			if (file_exists($transFile)) {
362
-				$languageFiles[] = $transFile;
363
-			}
364
-		}
365
-
366
-		return $languageFiles;
367
-	}
368
-
369
-	/**
370
-	 * find the l10n directory
371
-	 *
372
-	 * @param string $app App id or empty string for core
373
-	 * @return string directory
374
-	 */
375
-	protected function findL10nDir($app = null) {
376
-		if (in_array($app, ['core', 'lib', 'settings'])) {
377
-			if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) {
378
-				return $this->serverRoot . '/' . $app . '/l10n/';
379
-			}
380
-		} else if ($app && \OC_App::getAppPath($app) !== false) {
381
-			// Check if the app is in the app folder
382
-			return \OC_App::getAppPath($app) . '/l10n/';
383
-		}
384
-		return $this->serverRoot . '/core/l10n/';
385
-	}
386
-
387
-
388
-	/**
389
-	 * Creates a function from the plural string
390
-	 *
391
-	 * Parts of the code is copied from Habari:
392
-	 * https://github.com/habari/system/blob/master/classes/locale.php
393
-	 * @param string $string
394
-	 * @return string
395
-	 */
396
-	public function createPluralFunction($string) {
397
-		if (isset($this->pluralFunctions[$string])) {
398
-			return $this->pluralFunctions[$string];
399
-		}
400
-
401
-		if (preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) {
402
-			// sanitize
403
-			$nplurals = preg_replace( '/[^0-9]/', '', $matches[1] );
404
-			$plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] );
405
-
406
-			$body = str_replace(
407
-				array( 'plural', 'n', '$n$plurals', ),
408
-				array( '$plural', '$n', '$nplurals', ),
409
-				'nplurals='. $nplurals . '; plural=' . $plural
410
-			);
411
-
412
-			// add parents
413
-			// important since PHP's ternary evaluates from left to right
414
-			$body .= ';';
415
-			$res = '';
416
-			$p = 0;
417
-			$length = strlen($body);
418
-			for($i = 0; $i < $length; $i++) {
419
-				$ch = $body[$i];
420
-				switch ( $ch ) {
421
-					case '?':
422
-						$res .= ' ? (';
423
-						$p++;
424
-						break;
425
-					case ':':
426
-						$res .= ') : (';
427
-						break;
428
-					case ';':
429
-						$res .= str_repeat( ')', $p ) . ';';
430
-						$p = 0;
431
-						break;
432
-					default:
433
-						$res .= $ch;
434
-				}
435
-			}
436
-
437
-			$body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
438
-			$function = create_function('$n', $body);
439
-			$this->pluralFunctions[$string] = $function;
440
-			return $function;
441
-		} else {
442
-			// default: one plural form for all cases but n==1 (english)
443
-			$function = create_function(
444
-				'$n',
445
-				'$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);'
446
-			);
447
-			$this->pluralFunctions[$string] = $function;
448
-			return $function;
449
-		}
450
-	}
43
+    /** @var string */
44
+    protected $requestLanguage = '';
45
+
46
+    /**
47
+     * cached instances
48
+     * @var array Structure: Lang => App => \OCP\IL10N
49
+     */
50
+    protected $instances = [];
51
+
52
+    /**
53
+     * @var array Structure: App => string[]
54
+     */
55
+    protected $availableLanguages = [];
56
+
57
+    /**
58
+     * @var array Structure: string => callable
59
+     */
60
+    protected $pluralFunctions = [];
61
+
62
+    /** @var IConfig */
63
+    protected $config;
64
+
65
+    /** @var IRequest */
66
+    protected $request;
67
+
68
+    /** @var IUserSession */
69
+    protected $userSession;
70
+
71
+    /** @var string */
72
+    protected $serverRoot;
73
+
74
+    /**
75
+     * @param IConfig $config
76
+     * @param IRequest $request
77
+     * @param IUserSession $userSession
78
+     * @param string $serverRoot
79
+     */
80
+    public function __construct(IConfig $config,
81
+                                IRequest $request,
82
+                                IUserSession $userSession,
83
+                                $serverRoot) {
84
+        $this->config = $config;
85
+        $this->request = $request;
86
+        $this->userSession = $userSession;
87
+        $this->serverRoot = $serverRoot;
88
+    }
89
+
90
+    /**
91
+     * Get a language instance
92
+     *
93
+     * @param string $app
94
+     * @param string|null $lang
95
+     * @return \OCP\IL10N
96
+     */
97
+    public function get($app, $lang = null) {
98
+        $app = \OC_App::cleanAppId($app);
99
+        if ($lang !== null) {
100
+            $lang = str_replace(array('\0', '/', '\\', '..'), '', (string) $lang);
101
+        }
102
+
103
+        $forceLang = $this->config->getSystemValue('force_language', false);
104
+        if (is_string($forceLang)) {
105
+            $lang = $forceLang;
106
+        }
107
+
108
+        if ($lang === null || !$this->languageExists($app, $lang)) {
109
+            $lang = $this->findLanguage($app);
110
+        }
111
+
112
+        if (!isset($this->instances[$lang][$app])) {
113
+            $this->instances[$lang][$app] = new L10N(
114
+                $this, $app, $lang,
115
+                $this->getL10nFilesForApp($app, $lang)
116
+            );
117
+        }
118
+
119
+        return $this->instances[$lang][$app];
120
+    }
121
+
122
+    /**
123
+     * Find the best language
124
+     *
125
+     * @param string|null $app App id or null for core
126
+     * @return string language If nothing works it returns 'en'
127
+     */
128
+    public function findLanguage($app = null) {
129
+        if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) {
130
+            return $this->requestLanguage;
131
+        }
132
+
133
+        $forceLang = $this->config->getSystemValue('force_language', false);
134
+        if (is_string($forceLang)) {
135
+            if ($this->languageExists($app, $forceLang)) {
136
+                return $forceLang;
137
+            }
138
+        }
139
+
140
+        /**
141
+         * At this point Nextcloud might not yet be installed and thus the lookup
142
+         * in the preferences table might fail. For this reason we need to check
143
+         * whether the instance has already been installed
144
+         *
145
+         * @link https://github.com/owncloud/core/issues/21955
146
+         */
147
+        if($this->config->getSystemValue('installed', false)) {
148
+            $userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() :  null;
149
+            if(!is_null($userId)) {
150
+                $userLang = $this->config->getUserValue($userId, 'core', 'lang', null);
151
+            } else {
152
+                $userLang = null;
153
+            }
154
+        } else {
155
+            $userId = null;
156
+            $userLang = null;
157
+        }
158
+
159
+        if ($userLang) {
160
+            $this->requestLanguage = $userLang;
161
+            if ($this->languageExists($app, $userLang)) {
162
+                return $userLang;
163
+            }
164
+        }
165
+
166
+        try {
167
+            // Try to get the language from the Request
168
+            $lang = $this->getLanguageFromRequest($app);
169
+            if ($userId !== null && $app === null && !$userLang) {
170
+                $this->config->setUserValue($userId, 'core', 'lang', $lang);
171
+            }
172
+            return $lang;
173
+        } catch (LanguageNotFoundException $e) {
174
+            // Finding language from request failed fall back to default language
175
+            $defaultLanguage = $this->config->getSystemValue('default_language', false);
176
+            if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) {
177
+                return $defaultLanguage;
178
+            }
179
+        }
180
+
181
+        // We could not find any language so fall back to english
182
+        return 'en';
183
+    }
184
+
185
+    /**
186
+     * Find all available languages for an app
187
+     *
188
+     * @param string|null $app App id or null for core
189
+     * @return array an array of available languages
190
+     */
191
+    public function findAvailableLanguages($app = null) {
192
+        $key = $app;
193
+        if ($key === null) {
194
+            $key = 'null';
195
+        }
196
+
197
+        // also works with null as key
198
+        if (!empty($this->availableLanguages[$key])) {
199
+            return $this->availableLanguages[$key];
200
+        }
201
+
202
+        $available = ['en']; //english is always available
203
+        $dir = $this->findL10nDir($app);
204
+        if (is_dir($dir)) {
205
+            $files = scandir($dir);
206
+            if ($files !== false) {
207
+                foreach ($files as $file) {
208
+                    if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
209
+                        $available[] = substr($file, 0, -5);
210
+                    }
211
+                }
212
+            }
213
+        }
214
+
215
+        // merge with translations from theme
216
+        $theme = $this->config->getSystemValue('theme');
217
+        if (!empty($theme)) {
218
+            $themeDir = $this->serverRoot . '/themes/' . $theme . substr($dir, strlen($this->serverRoot));
219
+
220
+            if (is_dir($themeDir)) {
221
+                $files = scandir($themeDir);
222
+                if ($files !== false) {
223
+                    foreach ($files as $file) {
224
+                        if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
225
+                            $available[] = substr($file, 0, -5);
226
+                        }
227
+                    }
228
+                }
229
+            }
230
+        }
231
+
232
+        $this->availableLanguages[$key] = $available;
233
+        return $available;
234
+    }
235
+
236
+    /**
237
+     * @param string|null $app App id or null for core
238
+     * @param string $lang
239
+     * @return bool
240
+     */
241
+    public function languageExists($app, $lang) {
242
+        if ($lang === 'en') {//english is always available
243
+            return true;
244
+        }
245
+
246
+        $languages = $this->findAvailableLanguages($app);
247
+        return array_search($lang, $languages) !== false;
248
+    }
249
+
250
+    /**
251
+     * @param string|null $app
252
+     * @return string
253
+     * @throws LanguageNotFoundException
254
+     */
255
+    private function getLanguageFromRequest($app) {
256
+        $header = $this->request->getHeader('ACCEPT_LANGUAGE');
257
+        if ($header !== '') {
258
+            $available = $this->findAvailableLanguages($app);
259
+
260
+            // E.g. make sure that 'de' is before 'de_DE'.
261
+            sort($available);
262
+
263
+            $preferences = preg_split('/,\s*/', strtolower($header));
264
+            foreach ($preferences as $preference) {
265
+                list($preferred_language) = explode(';', $preference);
266
+                $preferred_language = str_replace('-', '_', $preferred_language);
267
+
268
+                foreach ($available as $available_language) {
269
+                    if ($preferred_language === strtolower($available_language)) {
270
+                        return $this->respectDefaultLanguage($app, $available_language);
271
+                    }
272
+                }
273
+
274
+                // Fallback from de_De to de
275
+                foreach ($available as $available_language) {
276
+                    if (substr($preferred_language, 0, 2) === $available_language) {
277
+                        return $available_language;
278
+                    }
279
+                }
280
+            }
281
+        }
282
+
283
+        throw new LanguageNotFoundException();
284
+    }
285
+
286
+    /**
287
+     * if default language is set to de_DE (formal German) this should be
288
+     * preferred to 'de' (non-formal German) if possible
289
+     *
290
+     * @param string|null $app
291
+     * @param string $lang
292
+     * @return string
293
+     */
294
+    protected function respectDefaultLanguage($app, $lang) {
295
+        $result = $lang;
296
+        $defaultLanguage = $this->config->getSystemValue('default_language', false);
297
+
298
+        // use formal version of german ("Sie" instead of "Du") if the default
299
+        // language is set to 'de_DE' if possible
300
+        if (is_string($defaultLanguage) &&
301
+            strtolower($lang) === 'de' &&
302
+            strtolower($defaultLanguage) === 'de_de' &&
303
+            $this->languageExists($app, 'de_DE')
304
+        ) {
305
+            $result = 'de_DE';
306
+        }
307
+
308
+        return $result;
309
+    }
310
+
311
+    /**
312
+     * Checks if $sub is a subdirectory of $parent
313
+     *
314
+     * @param string $sub
315
+     * @param string $parent
316
+     * @return bool
317
+     */
318
+    private function isSubDirectory($sub, $parent) {
319
+        // Check whether $sub contains no ".."
320
+        if(strpos($sub, '..') !== false) {
321
+            return false;
322
+        }
323
+
324
+        // Check whether $sub is a subdirectory of $parent
325
+        if (strpos($sub, $parent) === 0) {
326
+            return true;
327
+        }
328
+
329
+        return false;
330
+    }
331
+
332
+    /**
333
+     * Get a list of language files that should be loaded
334
+     *
335
+     * @param string $app
336
+     * @param string $lang
337
+     * @return string[]
338
+     */
339
+    // FIXME This method is only public, until OC_L10N does not need it anymore,
340
+    // FIXME This is also the reason, why it is not in the public interface
341
+    public function getL10nFilesForApp($app, $lang) {
342
+        $languageFiles = [];
343
+
344
+        $i18nDir = $this->findL10nDir($app);
345
+        $transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json';
346
+
347
+        if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/')
348
+                || $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/')
349
+                || $this->isSubDirectory($transFile, $this->serverRoot . '/settings/l10n/')
350
+                || $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/')
351
+            )
352
+            && file_exists($transFile)) {
353
+            // load the translations file
354
+            $languageFiles[] = $transFile;
355
+        }
356
+
357
+        // merge with translations from theme
358
+        $theme = $this->config->getSystemValue('theme');
359
+        if (!empty($theme)) {
360
+            $transFile = $this->serverRoot . '/themes/' . $theme . substr($transFile, strlen($this->serverRoot));
361
+            if (file_exists($transFile)) {
362
+                $languageFiles[] = $transFile;
363
+            }
364
+        }
365
+
366
+        return $languageFiles;
367
+    }
368
+
369
+    /**
370
+     * find the l10n directory
371
+     *
372
+     * @param string $app App id or empty string for core
373
+     * @return string directory
374
+     */
375
+    protected function findL10nDir($app = null) {
376
+        if (in_array($app, ['core', 'lib', 'settings'])) {
377
+            if (file_exists($this->serverRoot . '/' . $app . '/l10n/')) {
378
+                return $this->serverRoot . '/' . $app . '/l10n/';
379
+            }
380
+        } else if ($app && \OC_App::getAppPath($app) !== false) {
381
+            // Check if the app is in the app folder
382
+            return \OC_App::getAppPath($app) . '/l10n/';
383
+        }
384
+        return $this->serverRoot . '/core/l10n/';
385
+    }
386
+
387
+
388
+    /**
389
+     * Creates a function from the plural string
390
+     *
391
+     * Parts of the code is copied from Habari:
392
+     * https://github.com/habari/system/blob/master/classes/locale.php
393
+     * @param string $string
394
+     * @return string
395
+     */
396
+    public function createPluralFunction($string) {
397
+        if (isset($this->pluralFunctions[$string])) {
398
+            return $this->pluralFunctions[$string];
399
+        }
400
+
401
+        if (preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) {
402
+            // sanitize
403
+            $nplurals = preg_replace( '/[^0-9]/', '', $matches[1] );
404
+            $plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] );
405
+
406
+            $body = str_replace(
407
+                array( 'plural', 'n', '$n$plurals', ),
408
+                array( '$plural', '$n', '$nplurals', ),
409
+                'nplurals='. $nplurals . '; plural=' . $plural
410
+            );
411
+
412
+            // add parents
413
+            // important since PHP's ternary evaluates from left to right
414
+            $body .= ';';
415
+            $res = '';
416
+            $p = 0;
417
+            $length = strlen($body);
418
+            for($i = 0; $i < $length; $i++) {
419
+                $ch = $body[$i];
420
+                switch ( $ch ) {
421
+                    case '?':
422
+                        $res .= ' ? (';
423
+                        $p++;
424
+                        break;
425
+                    case ':':
426
+                        $res .= ') : (';
427
+                        break;
428
+                    case ';':
429
+                        $res .= str_repeat( ')', $p ) . ';';
430
+                        $p = 0;
431
+                        break;
432
+                    default:
433
+                        $res .= $ch;
434
+                }
435
+            }
436
+
437
+            $body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
438
+            $function = create_function('$n', $body);
439
+            $this->pluralFunctions[$string] = $function;
440
+            return $function;
441
+        } else {
442
+            // default: one plural form for all cases but n==1 (english)
443
+            $function = create_function(
444
+                '$n',
445
+                '$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);'
446
+            );
447
+            $this->pluralFunctions[$string] = $function;
448
+            return $function;
449
+        }
450
+    }
451 451
 }
Please login to merge, or discard this patch.