Add EmailLogListPage
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / language / Language.class.php
1 <?php
2
3 namespace wcf\data\language;
4
5 use wcf\data\DatabaseObject;
6 use wcf\data\devtools\missing\language\item\DevtoolsMissingLanguageItemAction;
7 use wcf\system\language\LanguageFactory;
8 use wcf\system\WCF;
9 use wcf\util\StringUtil;
10
11 /**
12 * Represents a language.
13 *
14 * @author Alexander Ebert
15 * @copyright 2001-2019 WoltLab GmbH
16 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
17 * @package WoltLabSuite\Core\Data\Language
18 *
19 * @property-read int $languageID unique id of the language
20 * @property-read string $languageCode code of the language according to ISO 639-1
21 * @property-read string $languageName name of the language within the language itself
22 * @property-read string $countryCode code of the country using the language according to ISO 3166-1, used to determine the language's country flag
23 * @property-read int $isDefault is `1` if the language is the default language, otherwise `0`
24 * @property-read int $hasContent is `1` if the language can be selected when creating language-specific content, otherwise `0`
25 * @property-read int $isDisabled is `1` if the language is disabled and thus not selectable, otherwise `0`
26 */
27 class Language extends DatabaseObject
28 {
29 /**
30 * list of language items
31 * @var string[]
32 */
33 protected $items = [];
34
35 /**
36 * list of dynamic language items
37 * @var string[]
38 */
39 protected $dynamicItems = [];
40
41 /**
42 * instance of LanguageEditor
43 * @var LanguageEditor
44 */
45 private $editor;
46
47 /**
48 * id of the active package
49 * @var int
50 */
51 public $packageID = PACKAGE_ID;
52
53 /**
54 * contains categories currently being loaded as array keys
55 * @var bool[]
56 */
57 protected $categoriesBeingLoaded = [];
58
59 /**
60 * Returns the name of this language.
61 *
62 * @return string
63 */
64 public function __toString()
65 {
66 return $this->languageName;
67 }
68
69 /**
70 * Returns the fixed language code of this language.
71 *
72 * @return string
73 */
74 public function getFixedLanguageCode()
75 {
76 return LanguageFactory::fixLanguageCode($this->languageCode);
77 }
78
79 /**
80 * Returns the page direction of this language.
81 *
82 * @return string
83 */
84 public function getPageDirection()
85 {
86 return $this->get('wcf.global.pageDirection');
87 }
88
89 /**
90 * Returns a single language variable.
91 *
92 * @param string $item
93 * @param bool $optional
94 * @return string
95 */
96 public function get($item, $optional = false)
97 {
98 if (
99 \defined('ENABLE_DEBUG_MODE')
100 && ENABLE_DEBUG_MODE
101 && \defined('ENABLE_DEVELOPER_TOOLS')
102 && ENABLE_DEVELOPER_TOOLS
103 && \is_array($optional)
104 && !empty($optional)
105 ) {
106 throw new \InvalidArgumentException("The second parameter of Language::get() does not support non-empty arrays. Did you mean to use Language::getDynamicVariable()?");
107 }
108
109 if (!isset($this->items[$item])) {
110 // load category file
111 $explodedItem = \explode('.', $item);
112 if (\count($explodedItem) < 3) {
113 return $item;
114 }
115
116 // attempt to load the most specific category
117 $this->loadCategory($explodedItem[0] . '.' . $explodedItem[1] . '.' . $explodedItem[2]);
118 if (!isset($this->items[$item])) {
119 $this->loadCategory($explodedItem[0] . '.' . $explodedItem[1]);
120 }
121 }
122
123 // return language variable
124 if (isset($this->items[$item])) {
125 return $this->items[$item];
126 }
127
128 // do not output value if there was no match and the item looks like a valid language item
129 if ($optional && \preg_match('~^([a-zA-Z0-9-_]+\.)+[a-zA-Z0-9-_]+$~', $item)) {
130 return '';
131 }
132
133 if (
134 \defined('ENABLE_DEVELOPER_TOOLS')
135 && ENABLE_DEVELOPER_TOOLS
136 && \defined('LOG_MISSING_LANGUAGE_ITEMS')
137 && LOG_MISSING_LANGUAGE_ITEMS
138 && \preg_match('~^([a-zA-Z0-9-_]+\.)+[a-zA-Z0-9-_]+$~', $item)
139 ) {
140 (new DevtoolsMissingLanguageItemAction([], 'logLanguageItem', [
141 'language' => $this,
142 'languageItem' => $item,
143 ]))->executeAction();
144 }
145
146 // return plain input
147 return $item;
148 }
149
150 /**
151 * Executes template scripting in a language variable.
152 *
153 * @param string $item
154 * @param array $variables
155 * @param bool $optional
156 * @return string result
157 */
158 public function getDynamicVariable($item, array $variables = [], $optional = false)
159 {
160 $staticItem = $this->get($item, $optional);
161 if (!$staticItem) {
162 return '';
163 }
164
165 if (isset($this->dynamicItems[$item])) {
166 // assign active language
167 $variables['__language'] = $this;
168
169 return WCF::getTPL()->fetchString($this->dynamicItems[$item], $variables);
170 }
171
172 if (
173 \defined('ENABLE_DEVELOPER_TOOLS')
174 && ENABLE_DEVELOPER_TOOLS
175 && \defined('LOG_MISSING_LANGUAGE_ITEMS')
176 && LOG_MISSING_LANGUAGE_ITEMS
177 && $staticItem === $item
178 && \preg_match('~^([a-zA-Z0-9-_]+\.)+[a-zA-Z0-9-_]+$~', $item)
179 ) {
180 (new DevtoolsMissingLanguageItemAction([], 'logLanguageItem', [
181 'language' => $this,
182 'languageItem' => $item,
183 ]))->executeAction();
184 }
185
186 return $staticItem;
187 }
188
189 /**
190 * Shortcut method to reduce the code repetition in the compiled template code.
191 *
192 * @param string $item
193 * @param mixed[] $tagStackData
194 * @return string
195 * @since 5.2
196 */
197 public function tplGet($item, array &$tagStackData)
198 {
199 $optional = !empty($tagStackData['__optional']);
200
201 if (!empty($tagStackData['__literal'])) {
202 $value = $this->get($item, $optional);
203 } else {
204 $value = $this->getDynamicVariable($item, $tagStackData, $optional);
205 }
206
207 if (!empty($tagStackData['__encode'])) {
208 return StringUtil::encodeHTML($value);
209 }
210
211 return $value;
212 }
213
214 /**
215 * Loads category files.
216 *
217 * @param string $category
218 * @return bool
219 */
220 protected function loadCategory($category)
221 {
222 if (!LanguageFactory::getInstance()->isValidCategory($category)) {
223 return false;
224 }
225
226 // search language file
227 $filename = WCF_DIR . 'language/' . $this->languageID . '_' . $category . '.php';
228 if (!@\file_exists($filename)) {
229 if (isset($this->categoriesBeingLoaded[$category])) {
230 throw new \LogicException("Circular dependency detected! Cannot load category '{$category}' while it is already being loaded.");
231 }
232
233 if ($this->editor === null) {
234 $this->editor = new LanguageEditor($this);
235 }
236
237 // rebuild language file
238 $languageCategory = LanguageFactory::getInstance()->getCategory($category);
239 if ($languageCategory === null) {
240 return false;
241 }
242
243 $this->categoriesBeingLoaded[$category] = true;
244
245 $this->editor->updateCategory($languageCategory);
246
247 unset($this->categoriesBeingLoaded[$category]);
248 }
249
250 // include language file
251 @include_once($filename);
252
253 return true;
254 }
255
256 /**
257 * Returns true if given items includes template scripting.
258 *
259 * @param string $item
260 * @return bool
261 */
262 public function isDynamicItem($item)
263 {
264 if (isset($this->dynamicItems[$item])) {
265 return true;
266 }
267
268 return false;
269 }
270
271 /**
272 * Returns language icon path.
273 *
274 * @return string
275 */
276 public function getIconPath()
277 {
278 return WCF::getPath() . 'icon/flag/' . $this->countryCode . '.svg';
279 }
280
281 /**
282 * Returns a list of available languages.
283 *
284 * @return Language[]
285 */
286 public function getLanguages()
287 {
288 return LanguageFactory::getInstance()->getLanguages();
289 }
290
291 /**
292 * Sets the package id when a language object is unserialized.
293 */
294 public function __wakeup()
295 {
296 $this->packageID = PACKAGE_ID;
297 }
298 }