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