Commit | Line | Data |
---|---|---|
5158aa21 | 1 | <?php |
71212588 | 2 | namespace wcf\system\form\builder\field\wysiwyg; |
0dc68d99 MS |
3 | use wcf\data\IMessageQuoteAction; |
4 | use wcf\data\object\type\ObjectTypeCache; | |
f197a243 | 5 | use wcf\system\bbcode\BBCodeHandler; |
5d086ddf | 6 | use wcf\system\form\builder\data\processor\CustomFormDataProcessor; |
71212588 | 7 | use wcf\system\form\builder\field\AbstractFormField; |
71212588 MS |
8 | use wcf\system\form\builder\field\IMaximumLengthFormField; |
9 | use wcf\system\form\builder\field\IMinimumLengthFormField; | |
71212588 MS |
10 | use wcf\system\form\builder\field\TMaximumLengthFormField; |
11 | use wcf\system\form\builder\field\TMinimumLengthFormField; | |
5158aa21 MS |
12 | use wcf\system\form\builder\field\validation\FormFieldValidationError; |
13 | use wcf\system\form\builder\IFormDocument; | |
26e502ba MS |
14 | use wcf\system\form\builder\IObjectTypeFormNode; |
15 | use wcf\system\form\builder\TObjectTypeFormNode; | |
5158aa21 | 16 | use wcf\system\html\input\HtmlInputProcessor; |
a1a60aa6 | 17 | use wcf\system\message\censorship\Censorship; |
0dc68d99 | 18 | use wcf\system\message\quote\MessageQuoteManager; |
f197a243 | 19 | use wcf\system\WCF; |
5158aa21 MS |
20 | use wcf\util\StringUtil; |
21 | ||
22 | /** | |
23 | * Implementation of a form field for wysiwyg editors. | |
24 | * | |
25 | * @author Matthias Schmidt | |
7b7b9764 | 26 | * @copyright 2001-2019 WoltLab GmbH |
5158aa21 MS |
27 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
28 | * @package WoltLabSuite\Core\System\Form\Builder\Field | |
dd2d8c0c | 29 | * @since 5.2 |
5158aa21 | 30 | */ |
71212588 | 31 | class WysiwygFormField extends AbstractFormField implements IMaximumLengthFormField, IMinimumLengthFormField, IObjectTypeFormNode { |
5158aa21 MS |
32 | use TMaximumLengthFormField; |
33 | use TMinimumLengthFormField; | |
71212588 | 34 | use TObjectTypeFormNode; |
5158aa21 MS |
35 | |
36 | /** | |
37 | * identifier used to autosave the field value; if empty, autosave is disabled | |
38 | * @var string | |
39 | */ | |
9299c8e5 | 40 | protected $autosaveId = ''; |
5158aa21 | 41 | |
71212588 MS |
42 | /** |
43 | * input processor containing the wysiwyg text | |
44 | * @var HtmlInputProcessor | |
45 | */ | |
46 | protected $htmlInputProcessor; | |
47 | ||
5158aa21 MS |
48 | /** |
49 | * last time the field has been edited; if `0`, the last edit time is unknown | |
50 | * @var int | |
51 | */ | |
9299c8e5 | 52 | protected $lastEditTime = 0; |
5158aa21 MS |
53 | |
54 | /** | |
0dc68d99 MS |
55 | * quote-related data used to create the JavaScript quote manager |
56 | * @var null|array | |
57 | */ | |
58 | protected $quoteData; | |
59 | ||
60 | /** | |
61 | * is `true` if this form field supports attachments, otherwise `false` | |
71212588 | 62 | * @var boolean |
5158aa21 | 63 | */ |
71212588 MS |
64 | protected $supportAttachments = false; |
65 | ||
66 | /** | |
0dc68d99 | 67 | * is `true` if this form field supports mentions, otherwise `false` |
71212588 MS |
68 | * @var boolean |
69 | */ | |
70 | protected $supportMentions = false; | |
5158aa21 | 71 | |
0dc68d99 MS |
72 | /** |
73 | * is `true` if this form field supports quotes, otherwise `false` | |
74 | * @var boolean | |
75 | */ | |
76 | protected $supportQuotes = false; | |
77 | ||
59a624e9 | 78 | /** |
79 | * @inheritDoc | |
80 | */ | |
81 | protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value'; | |
82 | ||
5158aa21 MS |
83 | /** |
84 | * @inheritDoc | |
85 | */ | |
86 | protected $templateName = '__wysiwygFormField'; | |
87 | ||
88 | /** | |
89 | * Sets the identifier used to autosave the field value and returns this field. | |
90 | * | |
91 | * @param string $autosaveId identifier used to autosave field value | |
26e502ba | 92 | * @return WysiwygFormField this field |
5158aa21 | 93 | */ |
020e6570 | 94 | public function autosaveId($autosaveId) { |
9299c8e5 | 95 | $this->autosaveId = $autosaveId; |
5158aa21 MS |
96 | |
97 | return $this; | |
98 | } | |
99 | ||
0dc68d99 MS |
100 | /** |
101 | * @inheritDoc | |
102 | */ | |
103 | public function cleanup() { | |
104 | MessageQuoteManager::getInstance()->saved(); | |
105 | } | |
106 | ||
5158aa21 MS |
107 | /** |
108 | * Returns the identifier used to autosave the field value. If autosave is disabled, | |
9d68d5b2 | 109 | * an empty string is returned. |
5158aa21 MS |
110 | * |
111 | * @return string | |
112 | */ | |
7b43decd | 113 | public function getAutosaveId() { |
9299c8e5 | 114 | return $this->autosaveId; |
5158aa21 MS |
115 | } |
116 | ||
0dc68d99 MS |
117 | /** |
118 | * @inheritDoc | |
119 | */ | |
120 | public function getHtml() { | |
121 | if ($this->supportsQuotes()) { | |
122 | MessageQuoteManager::getInstance()->assignVariables(); | |
123 | } | |
124 | ||
f197a243 MS |
125 | /** @noinspection PhpUndefinedFieldInspection */ |
126 | $disallowedBBCodesPermission = $this->getObjectType()->disallowedBBCodesPermission; | |
127 | if ($disallowedBBCodesPermission === null) { | |
128 | $disallowedBBCodesPermission = 'user.message.disallowedBBCodes'; | |
129 | } | |
130 | ||
131 | BBCodeHandler::getInstance()->setDisallowedBBCodes(explode( | |
132 | ',', | |
133 | WCF::getSession()->getPermission($disallowedBBCodesPermission) | |
134 | )); | |
135 | ||
0dc68d99 MS |
136 | return parent::getHtml(); |
137 | } | |
138 | ||
5158aa21 MS |
139 | /** |
140 | * @inheritDoc | |
141 | */ | |
7b43decd | 142 | public function getObjectTypeDefinition() { |
5158aa21 MS |
143 | return 'com.woltlab.wcf.message'; |
144 | } | |
145 | ||
146 | /** | |
147 | * Returns the last time the field has been edited. If no last edit time has | |
148 | * been set, `0` is returned. | |
149 | * | |
150 | * @return int | |
151 | */ | |
7b43decd | 152 | public function getLastEditTime() { |
9299c8e5 | 153 | return $this->lastEditTime; |
5158aa21 MS |
154 | } |
155 | ||
0dc68d99 MS |
156 | /** |
157 | * Returns all quote data or specific quote data if an argument is given. | |
158 | * | |
159 | * @param null|string $index quote data index | |
160 | * @return string[]|string | |
161 | * | |
162 | * @throws \BadMethodCallException if quotes are not supported for this field | |
163 | * @throws \InvalidArgumentException if unknown quote data is requested | |
164 | */ | |
165 | public function getQuoteData($index = null) { | |
166 | if (!$this->supportQuotes()) { | |
167 | throw new \BadMethodCallException("Quotes are not supported."); | |
168 | } | |
169 | ||
170 | if ($index === null) { | |
171 | return $this->quoteData; | |
172 | } | |
173 | ||
174 | if (!isset($this->quoteData[$index])) { | |
175 | throw new \InvalidArgumentException("Unknown quote data '{$index}'."); | |
176 | } | |
177 | ||
178 | return $this->quoteData[$index]; | |
179 | } | |
180 | ||
5158aa21 MS |
181 | /** |
182 | * @inheritDoc | |
183 | */ | |
7b43decd | 184 | public function hasSaveValue() { |
071fff89 | 185 | return false; |
5158aa21 MS |
186 | } |
187 | ||
188 | /** | |
189 | * Sets the last time this field has been edited and returns this field. | |
190 | * | |
191 | * @param int $lastEditTime last time field has been edited | |
26e502ba | 192 | * @return WysiwygFormField this field |
5158aa21 | 193 | */ |
020e6570 | 194 | public function lastEditTime($lastEditTime) { |
9299c8e5 | 195 | $this->lastEditTime = $lastEditTime; |
5158aa21 MS |
196 | |
197 | return $this; | |
198 | } | |
199 | ||
200 | /** | |
201 | * @inheritDoc | |
202 | */ | |
7b43decd | 203 | public function populate() { |
5158aa21 MS |
204 | parent::populate(); |
205 | ||
5d086ddf | 206 | $this->getDocument()->getDataHandler()->addProcessor(new CustomFormDataProcessor('wysiwyg', function(IFormDocument $document, array $parameters) { |
8bdbb251 | 207 | if ($this->checkDependencies()) { |
f3a825e9 | 208 | $parameters[$this->getObjectProperty() . '_htmlInputProcessor'] = $this->htmlInputProcessor; |
8bdbb251 | 209 | } |
5158aa21 MS |
210 | |
211 | return $parameters; | |
212 | })); | |
213 | ||
214 | return $this; | |
215 | } | |
0dc68d99 MS |
216 | |
217 | /** | |
218 | * Sets the data required for advanced quote support for when quotable content is present | |
219 | * on the active page and returns this field. | |
220 | * | |
221 | * Calling this method automatically enables quote support for this field. | |
222 | * | |
223 | * @param string $objectType name of the relevant `com.woltlab.wcf.message.quote` object type | |
224 | * @param string $actionClass action class implementing `wcf\data\IMessageQuoteAction` | |
225 | * @param string[] $selectors selectors for the quotable content (required keys: `container`, `messageBody`, and `messageContent`) | |
226 | * @return static | |
227 | * | |
228 | * @throws \InvalidArgumentException if any of the given arguments is invalid | |
229 | */ | |
230 | public function quoteData($objectType, $actionClass, array $selectors = []) { | |
231 | if (ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.message.quote', $objectType) === null) { | |
232 | throw new \InvalidArgumentException("Unknown message quote object type '{$objectType}'."); | |
233 | } | |
234 | ||
235 | if (!class_exists($actionClass)) { | |
236 | throw new \InvalidArgumentException("Unknown class '{$actionClass}'"); | |
237 | } | |
238 | if (!is_subclass_of($actionClass, IMessageQuoteAction::class)) { | |
239 | throw new \InvalidArgumentException("'{$actionClass}' does not implement '" . IMessageQuoteAction::class . "'."); | |
240 | } | |
241 | ||
242 | if (!empty($selectors)) { | |
243 | foreach (['container', 'messageBody', 'messageContent'] as $selector) { | |
244 | if (!isset($selectors[$selector])) { | |
245 | throw new \InvalidArgumentException("Missing selector '{$selector}'."); | |
246 | } | |
247 | } | |
248 | } | |
249 | ||
250 | $this->supportQuotes(); | |
251 | ||
252 | $this->quoteData = [ | |
253 | 'actionClass' => $actionClass, | |
254 | 'objectType' => $objectType, | |
255 | 'selectors' => $selectors | |
256 | ]; | |
257 | ||
258 | return $this; | |
259 | } | |
5158aa21 MS |
260 | |
261 | /** | |
262 | * @inheritDoc | |
263 | */ | |
7b43decd | 264 | public function readValue() { |
4b683ecd MS |
265 | if ($this->getDocument()->hasRequestData($this->getPrefixedId())) { |
266 | $value = $this->getDocument()->getRequestData($this->getPrefixedId()); | |
267 | ||
268 | if (is_string($value)) { | |
9299c8e5 | 269 | $this->value = StringUtil::trim($value); |
4b683ecd | 270 | } |
5158aa21 MS |
271 | } |
272 | ||
0dc68d99 MS |
273 | if ($this->supportsQuotes()) { |
274 | MessageQuoteManager::getInstance()->readFormParameters(); | |
275 | } | |
276 | ||
5158aa21 MS |
277 | return $this; |
278 | } | |
279 | ||
71212588 MS |
280 | /** |
281 | * Sets if the form field supports attachments and returns this field. | |
282 | * | |
283 | * @param boolean $supportAttachments | |
26e502ba | 284 | * @return WysiwygFormField this field |
71212588 MS |
285 | */ |
286 | public function supportAttachments($supportAttachments = true) { | |
287 | $this->supportAttachments = $supportAttachments; | |
288 | ||
289 | return $this; | |
290 | } | |
291 | ||
292 | /** | |
293 | * Sets if the form field supports mentions and returns this field. | |
294 | * | |
295 | * @param boolean $supportMentions | |
26e502ba | 296 | * @return WysiwygFormField this field |
71212588 MS |
297 | */ |
298 | public function supportMentions($supportMentions = true) { | |
299 | $this->supportMentions = $supportMentions; | |
300 | ||
301 | return $this; | |
302 | } | |
303 | ||
0dc68d99 MS |
304 | /** |
305 | * Sets if the form field supports quotes and returns this field. | |
306 | * | |
307 | * @param boolean $supportQuotes | |
308 | * @return WysiwygFormField this field | |
309 | */ | |
310 | public function supportQuotes($supportQuotes = true) { | |
311 | $this->supportQuotes = $supportQuotes; | |
312 | ||
313 | if (!$this->supportsQuotes()) { | |
314 | // unset previously set quote data | |
315 | $this->quoteData = null; | |
316 | } | |
317 | else { | |
318 | MessageQuoteManager::getInstance()->readParameters(); | |
319 | } | |
320 | ||
321 | return $this; | |
322 | } | |
323 | ||
71212588 MS |
324 | /** |
325 | * Returns `true` if the form field supports attachments and returns `false` otherwise. | |
326 | * | |
327 | * Important: If this method returns `true`, it does not necessarily mean that attachment | |
328 | * support will also work as that is the task of `WysiwygAttachmentFormField`. This method | |
329 | * is primarily relevant to inform the JavaScript API that the field supports attachments | |
330 | * so that the relevant editor plugin is loaded. | |
331 | * | |
332 | * By default, attachments are not supported. | |
333 | * | |
334 | * @return boolean | |
335 | */ | |
336 | public function supportsAttachments() { | |
337 | return $this->supportAttachments; | |
338 | } | |
339 | ||
340 | /** | |
341 | * Returns `true` if the form field supports mentions and returns `false` otherwise. | |
342 | * | |
343 | * By default, mentions are not supported. | |
344 | * | |
345 | * @return boolean | |
346 | */ | |
347 | public function supportsMentions() { | |
348 | return $this->supportMentions; | |
349 | } | |
350 | ||
0dc68d99 MS |
351 | /** |
352 | * Returns `true` if the form field supports quotes and returns `false` otherwise. | |
353 | * | |
a11099b7 | 354 | * By default, quotes are not supported. |
0dc68d99 MS |
355 | * |
356 | * @return boolean | |
357 | */ | |
358 | public function supportsQuotes() { | |
477f6668 | 359 | return $this->supportQuotes; |
0dc68d99 MS |
360 | } |
361 | ||
5158aa21 MS |
362 | /** |
363 | * @inheritDoc | |
364 | */ | |
365 | public function validate() { | |
f197a243 MS |
366 | /** @noinspection PhpUndefinedFieldInspection */ |
367 | $disallowedBBCodesPermission = $this->getObjectType()->disallowedBBCodesPermission; | |
368 | if ($disallowedBBCodesPermission === null) { | |
369 | $disallowedBBCodesPermission = 'user.message.disallowedBBCodes'; | |
370 | } | |
371 | ||
372 | BBCodeHandler::getInstance()->setDisallowedBBCodes(explode( | |
373 | ',', | |
374 | WCF::getSession()->getPermission($disallowedBBCodesPermission) | |
375 | )); | |
376 | ||
6fc348d5 MS |
377 | $this->htmlInputProcessor = new HtmlInputProcessor(); |
378 | $this->htmlInputProcessor->process($this->getValue(), $this->getObjectType()->objectType); | |
379 | ||
380 | if ($this->isRequired() && $this->htmlInputProcessor->appearsToBeEmpty()) { | |
5158aa21 MS |
381 | $this->addValidationError(new FormFieldValidationError('empty')); |
382 | } | |
383 | else { | |
f197a243 MS |
384 | $disallowedBBCodes = $this->htmlInputProcessor->validate(); |
385 | if (!empty($disallowedBBCodes)) { | |
386 | $this->addValidationError(new FormFieldValidationError( | |
387 | 'disallowedBBCodes', | |
388 | 'wcf.message.error.disallowedBBCodes', | |
389 | ['disallowedBBCodes' => $disallowedBBCodes] | |
390 | )); | |
391 | } | |
392 | else { | |
393 | $message = $this->htmlInputProcessor->getTextContent(); | |
394 | $this->validateMinimumLength($message); | |
395 | $this->validateMaximumLength($message); | |
396 | ||
397 | if (empty($this->getValidationErrors()) && ENABLE_CENSORSHIP) { | |
398 | $result = Censorship::getInstance()->test($message); | |
399 | if ($result) { | |
400 | $this->addValidationError(new FormFieldValidationError( | |
401 | 'censoredWords', | |
402 | 'wcf.message.error.censoredWordsFound', | |
403 | ['censoredWords' => $result] | |
404 | )); | |
405 | } | |
a1a60aa6 MS |
406 | } |
407 | } | |
5158aa21 MS |
408 | } |
409 | ||
5158aa21 MS |
410 | parent::validate(); |
411 | } | |
412 | } |