Add field attribute support for `RadioButtonFormField`
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / form / builder / field / wysiwyg / WysiwygFormField.class.php
CommitLineData
5158aa21 1<?php
71212588 2namespace wcf\system\form\builder\field\wysiwyg;
0dc68d99
MS
3use wcf\data\IMessageQuoteAction;
4use wcf\data\object\type\ObjectTypeCache;
f197a243 5use wcf\system\bbcode\BBCodeHandler;
5d086ddf 6use wcf\system\form\builder\data\processor\CustomFormDataProcessor;
71212588 7use wcf\system\form\builder\field\AbstractFormField;
71212588
MS
8use wcf\system\form\builder\field\IMaximumLengthFormField;
9use wcf\system\form\builder\field\IMinimumLengthFormField;
71212588
MS
10use wcf\system\form\builder\field\TMaximumLengthFormField;
11use wcf\system\form\builder\field\TMinimumLengthFormField;
5158aa21
MS
12use wcf\system\form\builder\field\validation\FormFieldValidationError;
13use wcf\system\form\builder\IFormDocument;
26e502ba
MS
14use wcf\system\form\builder\IObjectTypeFormNode;
15use wcf\system\form\builder\TObjectTypeFormNode;
5158aa21 16use wcf\system\html\input\HtmlInputProcessor;
a1a60aa6 17use wcf\system\message\censorship\Censorship;
0dc68d99 18use wcf\system\message\quote\MessageQuoteManager;
f197a243 19use wcf\system\WCF;
5158aa21
MS
20use 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 31class 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 */
e9154428 120 public function getFieldHtml() {
0dc68d99
MS
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
e9154428 136 return parent::getFieldHtml();
0dc68d99
MS
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}