Commit | Line | Data |
---|---|---|
dcc2332d | 1 | <?php |
a9229942 | 2 | |
dcc2332d | 3 | namespace wcf\system\bbcode; |
a9229942 | 4 | |
7a23a706 | 5 | use wcf\data\bbcode\BBCode; |
dcc2332d | 6 | use wcf\data\bbcode\BBCodeCache; |
74c960dd | 7 | use wcf\system\application\ApplicationHandler; |
2ef8729b | 8 | use wcf\system\package\license\LicenseApi; |
dcc2332d | 9 | use wcf\system\SingletonFactory; |
e12c6ef4 | 10 | use wcf\system\WCF; |
74c960dd | 11 | use wcf\util\ArrayUtil; |
070c42e0 | 12 | use wcf\util\JSON; |
e046a126 | 13 | use wcf\util\StringUtil; |
dcc2332d MW |
14 | |
15 | /** | |
16 | * Handles BBCodes displayed as buttons within the WYSIWYG editor. | |
a9229942 TD |
17 | * |
18 | * @author Alexander Ebert | |
19 | * @copyright 2001-2019 WoltLab GmbH | |
20 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
dcc2332d | 21 | */ |
a9229942 TD |
22 | class BBCodeHandler extends SingletonFactory |
23 | { | |
24 | /** | |
25 | * list of BBCodes displayed as buttons | |
26 | * @var BBCode[] | |
27 | */ | |
28 | protected $buttonBBCodes = []; | |
29 | ||
30 | /** | |
31 | * list of BBCodes disallowed for usage | |
32 | * @var BBCode[] | |
33 | */ | |
34 | protected $disallowedBBCodes = []; | |
35 | ||
36 | /** | |
37 | * list of BBCodes which contain raw code (disabled BBCode parsing) | |
38 | * @var BBCode[] | |
39 | */ | |
40 | protected $sourceBBCodes; | |
41 | ||
42 | /** | |
43 | * meta information about highlighters | |
44 | * @var mixed[] | |
45 | */ | |
46 | protected $highlighterMeta; | |
47 | ||
48 | /** | |
49 | * @inheritDoc | |
50 | */ | |
51 | protected function init() | |
52 | { | |
53 | foreach (BBCodeCache::getInstance()->getBBCodes() as $bbcode) { | |
54 | if ($bbcode->showButton) { | |
55 | $this->buttonBBCodes[] = $bbcode; | |
56 | } | |
57 | } | |
58 | } | |
59 | ||
60 | /** | |
61 | * Returns true if the BBCode with the given tag is available in the WYSIWYG editor. | |
a9229942 | 62 | */ |
63b4c880 | 63 | public function isAvailableBBCode(string $bbCodeTag, bool $overrideFormattingRemoval = false): bool |
a9229942 | 64 | { |
63b4c880 AE |
65 | if ($overrideFormattingRemoval === false) { |
66 | if ($bbCodeTag === "color" && \FORMATTING_REMOVE_COLOR) { | |
67 | return false; | |
68 | } | |
5536beb9 | 69 | |
63b4c880 AE |
70 | if ($bbCodeTag === "font" && \FORMATTING_REMOVE_FONT) { |
71 | return false; | |
72 | } | |
5536beb9 | 73 | |
63b4c880 AE |
74 | if ($bbCodeTag === "size" && \FORMATTING_REMOVE_SIZE) { |
75 | return false; | |
76 | } | |
5536beb9 AE |
77 | } |
78 | ||
a9229942 TD |
79 | return !\in_array($bbCodeTag, $this->disallowedBBCodes); |
80 | } | |
81 | ||
82 | /** | |
83 | * Returns all bbcodes. | |
84 | * | |
85 | * @return BBCode[] | |
86 | */ | |
87 | public function getBBCodes() | |
88 | { | |
89 | return BBCodeCache::getInstance()->getBBCodes(); | |
90 | } | |
91 | ||
92 | /** | |
93 | * Returns a list of BBCodes displayed as buttons. | |
94 | * | |
95 | * @param bool $excludeCoreBBCodes do not return bbcodes that are available by default | |
96 | * @return BBCode[] | |
97 | */ | |
98 | public function getButtonBBCodes($excludeCoreBBCodes = false) | |
99 | { | |
100 | $buttons = []; | |
101 | $coreBBCodes = [ | |
102 | 'align', | |
103 | 'b', | |
a42f47f6 | 104 | 'code', |
a9229942 | 105 | 'color', |
a42f47f6 | 106 | 'html', |
a9229942 TD |
107 | 'i', |
108 | 'img', | |
109 | 'list', | |
110 | 's', | |
111 | 'size', | |
112 | 'sub', | |
113 | 'sup', | |
114 | 'quote', | |
a42f47f6 | 115 | 'spoiler', |
a9229942 | 116 | 'table', |
a42f47f6 | 117 | 'tt', |
a9229942 TD |
118 | 'u', |
119 | 'url', | |
120 | ]; | |
121 | foreach ($this->buttonBBCodes as $bbcode) { | |
122 | if ($excludeCoreBBCodes && \in_array($bbcode->bbcodeTag, $coreBBCodes)) { | |
123 | continue; | |
124 | } | |
125 | ||
126 | if ($this->isAvailableBBCode($bbcode->bbcodeTag)) { | |
127 | $buttons[] = $bbcode; | |
128 | } | |
129 | } | |
130 | ||
131 | return $buttons; | |
132 | } | |
133 | ||
a9229942 TD |
134 | /** |
135 | * Sets the disallowed BBCodes. | |
136 | * | |
137 | * @param string[] $bbCodes | |
138 | */ | |
139 | public function setDisallowedBBCodes(array $bbCodes) | |
140 | { | |
141 | $this->disallowedBBCodes = $bbCodes; | |
142 | } | |
143 | ||
144 | /** | |
145 | * Returns a list of BBCodes which contain raw code (disabled BBCode parsing) | |
146 | * | |
147 | * @return BBCode[] | |
148 | * @deprecated 3.1 - This method is no longer supported. | |
149 | */ | |
150 | public function getSourceBBCodes() | |
151 | { | |
152 | return []; | |
153 | } | |
154 | ||
155 | /** | |
156 | * Returns metadata about the highlighters. | |
157 | * | |
158 | * @return string[][] | |
159 | */ | |
160 | public function getHighlighterMeta() | |
161 | { | |
162 | if ($this->highlighterMeta === null) { | |
163 | $this->highlighterMeta = JSON::decode(\preg_replace( | |
164 | '/.*\/\*!START\*\/\s*const\s*metadata\s*=\s*(.*)\s*;\s*\/\*!END\*\/.*/s', | |
165 | '\\1', | |
166 | \file_get_contents(WCF_DIR . '/js/WoltLabSuite/Core/prism-meta.js') | |
167 | )); | |
168 | } | |
169 | ||
170 | return $this->highlighterMeta; | |
171 | } | |
172 | ||
173 | /** | |
174 | * Returns a list of known highlighters. | |
175 | * | |
176 | * @return string[] | |
177 | */ | |
178 | public function getHighlighters() | |
179 | { | |
180 | return \array_keys($this->getHighlighterMeta()); | |
181 | } | |
182 | ||
e046a126 AE |
183 | /** |
184 | * Returns the list of languages that are available for selection in the | |
185 | * UI of CKEditor’s code block. | |
186 | * | |
187 | * @return list<string> | |
188 | * @since 6.0 | |
189 | */ | |
190 | public function getCodeBlockLanguages(): array | |
191 | { | |
192 | return \explode("\n", StringUtil::unifyNewlines(\MESSAGE_PUBLIC_HIGHLIGHTERS)); | |
193 | } | |
194 | ||
a9229942 TD |
195 | /** |
196 | * Returns a list of hostnames that are permitted as image sources. | |
197 | * | |
198 | * @return string[] | |
199 | * @since 5.2 | |
200 | */ | |
201 | public function getImageExternalSourceWhitelist() | |
202 | { | |
203 | $hosts = []; | |
204 | // Hide these hosts unless external sources are actually denied. | |
205 | if (!IMAGE_ALLOW_EXTERNAL_SOURCE) { | |
88960b62 TD |
206 | $hosts = ArrayUtil::trim(\explode( |
207 | "\n", | |
208 | \sprintf( | |
209 | "%s\n%s", | |
210 | \IMAGE_EXTERNAL_SOURCE_WHITELIST, | |
211 | \INTERNAL_HOSTNAMES | |
212 | ) | |
213 | )); | |
a9229942 TD |
214 | } |
215 | ||
2e238f48 | 216 | $hosts[] = ApplicationHandler::getInstance()->getDomainName(); |
a9229942 TD |
217 | |
218 | return \array_unique($hosts); | |
219 | } | |
e12c6ef4 AE |
220 | |
221 | /** | |
222 | * Exports a require.js requirement for the localization of the editor based | |
223 | * on the current locale. Returns an empty string when there is no available | |
224 | * localization or the locale equals the bundled value 'en'. | |
225 | * | |
226 | * @since 6.0 | |
227 | */ | |
228 | public function getEditorLocalization(): string | |
229 | { | |
230 | $availableTranslations = [ | |
231 | 'af', | |
232 | 'ar', | |
233 | 'ast', | |
234 | 'az', | |
235 | 'bg', | |
236 | 'bn', | |
237 | 'bs', | |
238 | 'ca', | |
239 | 'cs', | |
240 | 'da', | |
241 | 'de-ch', | |
242 | 'de', | |
243 | 'el', | |
244 | 'en-au', | |
245 | 'en-gb', | |
246 | 'eo', | |
247 | 'es-co', | |
248 | 'es', | |
249 | 'et', | |
250 | 'eu', | |
251 | 'fa', | |
252 | 'fi', | |
253 | 'fr', | |
254 | 'gl', | |
255 | 'gu', | |
256 | 'he', | |
257 | 'hi', | |
258 | 'hr', | |
259 | 'hu', | |
260 | 'id', | |
261 | 'it', | |
262 | 'ja', | |
263 | 'jv', | |
264 | 'kk', | |
265 | 'km', | |
266 | 'kn', | |
267 | 'ko', | |
268 | 'ku', | |
269 | 'lt', | |
270 | 'lv', | |
271 | 'ms', | |
272 | 'nb', | |
273 | 'ne', | |
274 | 'nl', | |
275 | 'no', | |
276 | 'oc', | |
277 | 'pl', | |
278 | 'pt-br', | |
279 | 'pt', | |
280 | 'ro', | |
281 | 'ru', | |
282 | 'si', | |
283 | 'sk', | |
284 | 'sl', | |
285 | 'sq', | |
286 | 'sr-latn', | |
287 | 'sr', | |
288 | 'sv', | |
289 | 'th', | |
290 | 'tk', | |
291 | 'tr', | |
292 | 'tt', | |
293 | 'ug', | |
294 | 'uk', | |
295 | 'ur', | |
296 | 'uz', | |
297 | 'vi', | |
298 | 'zh-cn', | |
299 | 'zh', | |
300 | ]; | |
301 | ||
2c52ac36 | 302 | $locale = \strtolower(WCF::getLanguage()->getBcp47()); |
e12c6ef4 AE |
303 | if (\in_array($locale, $availableTranslations, true)) { |
304 | return \sprintf( | |
305 | '"ckeditor5-translation/%s",', | |
306 | $locale | |
307 | ); | |
308 | } | |
309 | ||
2c52ac36 AE |
310 | // Some languages offer both specialized variants for certain locales |
311 | // but also provide a "generic" variant. For example, "en-gb" and "en". | |
312 | [$languageCode] = \explode('-', $locale, 2); | |
313 | if (\in_array($languageCode, $availableTranslations, true)) { | |
314 | return \sprintf( | |
315 | '"ckeditor5-translation/%s",', | |
316 | $languageCode | |
317 | ); | |
e12c6ef4 AE |
318 | } |
319 | ||
2c52ac36 AE |
320 | // The default locale "en" is part of the generated bundle, we must not |
321 | // yield any module if this locale is (implicitly) requested. | |
e12c6ef4 AE |
322 | return ""; |
323 | } | |
2ef8729b AE |
324 | |
325 | /** | |
326 | * @since 6.0 | |
327 | */ | |
328 | public function getCkeditorLicenseKey(): string | |
329 | { | |
895acf5e TD |
330 | $licenseApi = new LicenseApi(); |
331 | $licenseData = $licenseApi->readFromFile(); | |
332 | ||
19781b8e | 333 | if ($licenseData === null) { |
2ef8729b AE |
334 | return ''; |
335 | } | |
336 | ||
19781b8e | 337 | return $licenseData->license['ckeditorLicenseKey'] ?? ''; |
2ef8729b | 338 | } |
dcc2332d | 339 | } |