});
</script>
-<form method="{@$form->getMethod()}" {*
- *}action="{@$form->getAction()}" {*
- *}id="{@$form->getId()}"{*
- *}{if !$form->getClasses()|empty} class="{implode from=$form->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
- *}{foreach from=$form->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
-*}>
+{if $form->isAjax()}
+ <section id="{@$form->getId()}"{*
+ *}{if !$form->getClasses()|empty} class="{implode from=$form->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{foreach from=$form->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}>
+{else}
+ <form method="{@$form->getMethod()}" {*
+ *}action="{@$form->getAction()}" {*
+ *}id="{@$form->getId()}"{*
+ *}{if !$form->getClasses()|empty} class="{implode from=$form->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{foreach from=$form->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}>
+{/if}
{foreach from=$form item='child'}
{if $child->isAvailable()}
{@$child->getHtml()}
{/if}
{@SECURITY_TOKEN_INPUT_TAG}
-</form>
+{if $form->isAjax()}
+ </section>
+{else}
+ </form>
+{/if}
<script data-relocate="true">
{* after all dependencies have been added, check them *}
--- /dev/null
+{include file='__formFieldHeader'}
+
+<ol class="sortableList"></ol>
+
+{include file='__formFieldFooter'}
+
+{js application='wcf' file='WCF.Poll' bundle='WCF.Combined'}
+<script data-relocate="true">
+ require(['Dom/Traverse', 'Dom/Util', 'Language'], function(DomTraverse, DomUtil, Language) {
+ Language.addObject({
+ 'wcf.poll.button.addOption': '{lang}wcf.poll.button.addOption{/lang}',
+ 'wcf.poll.button.removeOption': '{lang}wcf.poll.button.removeOption{/lang}'
+ });
+
+ new WCF.Poll.Management(
+ DomUtil.identify(DomTraverse.childByTag(elById('{@$field->getPrefixedId()}Container'), 'DD')),
+ [ {implode from=$field->getValue() item=pollOption}{ optionID: {@$pollOption[optionID]}, optionValue: '{$pollOption[optionValue]|encodeJS}' }{/implode} ],
+ {@POLL_MAX_OPTIONS},
+ '{if $field->getDocument()->isAjax()}{@$field->getWysiwygId()}{/if}',
+ '{@$field->getPrefixedId()}'
+ );
+ });
+</script>
--- /dev/null
+{include file='__formFieldHeader'}
+
+<ul id="{@$field->getPrefixedID()}_attachmentList" {*
+ *}class="formAttachmentList"{*
+ *}{if !$field->getAttachmentHandler()->getAttachmentList()|count} style="display: none"{/if} {*
+ *}data-enable-thumbnails="{if ATTACHMENT_ENABLE_THUMBNAILS}true{else}false{/if}"{*
+*}>
+ {foreach from=$field->getAttachmentHandler()->getAttachmentList() item=$attachment}
+ <li class="box64" {*
+ *}data-object-id="{@$attachment->attachmentID}" {*
+ *}data-height="{@$attachment->height}" {*
+ *}data-width="{@$attachment->width}" {*
+ *}data-is-image="{@$attachment->isImage}"{*
+ *}>
+ {if $attachment->tinyThumbnailType}
+ <img src="{link controller='Attachment' object=$attachment}tiny=1{/link}" alt="" class="attachmentTinyThumbnail">
+ {else}
+ <span class="icon icon64 fa-{@$attachment->getIconName()}"></span>
+ {/if}
+
+ <div>
+ <div>
+ <p><a href="{link controller='Attachment' object=$attachment}{/link}" target="_blank"{if $attachment->isImage} title="{$attachment->filename}" class="jsImageViewer"{/if}>{$attachment->filename}</a></p>
+ <small>{@$attachment->filesize|filesize}</small>
+ </div>
+
+ <ul class="buttonGroup">
+ <li><span class="button small jsDeleteButton" data-object-id="{@$attachment->attachmentID}" data-confirm-message="{lang}wcf.attachment.delete.sure{/lang}">{lang}wcf.global.button.delete{/lang}</span></li>
+ {if $attachment->isImage}
+ {if $attachment->thumbnailType}
+ <li><span class="button small jsButtonAttachmentInsertThumbnail" data-object-id="{@$attachment->attachmentID}" data-url="{link controller='Attachment' object=$attachment}thumbnail=1{/link}">{lang}wcf.attachment.insertThumbnail{/lang}</span></li>
+ {/if}
+ <li><span class="button small jsButtonAttachmentInsertFull" data-object-id="{@$attachment->attachmentID}" data-url="{link controller='Attachment' object=$attachment}{/link}">{lang}wcf.attachment.insertFull{/lang}</span></li>
+ {else}
+ <li><span class="button small jsButtonInsertAttachment" data-object-id="{@$attachment->attachmentID}">{lang}wcf.attachment.insert{/lang}</span></li>
+ {/if}
+ </ul>
+ </div>
+ </li>
+ {/foreach}
+</ul>
+<div id="{@$field->getPrefixedID()}_uploadButton" class="formAttachmentButtons" data-max-size="{@$field->getAttachmentHandler()->getMaxSize()}"></div>
+
+{js application='wcf' file='WCF.Attachment' bundle='WCF.Combined'}
+<script data-relocate="true">
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.attachment.upload.error.invalidExtension': '{lang}wcf.attachment.upload.error.invalidExtension{/lang}',
+ 'wcf.attachment.upload.error.tooLarge': '{lang}wcf.attachment.upload.error.tooLarge{/lang}',
+ 'wcf.attachment.upload.error.reachedLimit': '{lang}wcf.attachment.upload.error.reachedLimit{/lang}',
+ 'wcf.attachment.upload.error.reachedRemainingLimit': '{lang}wcf.attachment.upload.error.reachedRemainingLimit{/lang}',
+ 'wcf.attachment.upload.error.uploadFailed': '{lang}wcf.attachment.upload.error.uploadFailed{/lang}',
+ 'wcf.attachment.upload.error.uploadPhpLimit': '{lang}wcf.attachment.upload.error.uploadPhpLimit{/lang}',
+ 'wcf.attachment.insert': '{lang}wcf.attachment.insert{/lang}',
+ 'wcf.attachment.insertAll': '{lang}wcf.attachment.insertAll{/lang}',
+ 'wcf.attachment.insertFull': '{lang}wcf.attachment.insertFull{/lang}',
+ 'wcf.attachment.insertThumbnail': '{lang}wcf.attachment.insertThumbnail{/lang}',
+ 'wcf.attachment.delete.sure': '{lang}wcf.attachment.delete.sure{/lang}'
+ });
+
+ new WCF.Attachment.Upload(
+ $('#{@$field->getPrefixedID()}_uploadButton'),
+ $('#{@$field->getPrefixedID()}_attachmentList'),
+ '{@$field->getAttachmentHandler()->getObjectType()->objectType}',
+ '{@$field->getAttachmentHandler()->getObjectID()}',
+ '{$field->getAttachmentHandler()->getTmpHashes()[0]|encodeJS}',
+ '{@$field->getAttachmentHandler()->getParentObjectID()}',
+ {@$field->getAttachmentHandler()->getMaxCount()},
+ '{@$field->getWysiwygId()}'
+ );
+ new WCF.Action.Delete('wcf\\data\\attachment\\AttachmentAction', '.formAttachmentList > li');
+ });
+</script>
+
+<input type="hidden" name="{@$field->getPrefixedID()}_tmpHash" value="{$field->getAttachmentHandler()->getTmpHashes()[0]}">
+
+{include file='__formFieldFooter'}
*}id="{@$field->getPrefixedId()}" {*
*}name="{@$field->getPrefixedId()}" {*
*}class="wysiwygTextarea" {*
- *}data-disable-attachments="true"{*
+ *}data-disable-attachments="{if $field->supportsAttachments()}false{else}true{/if}"{*
+ *}data-support-mention="{if $field->supportsMentions()}true{else}false{/if}"{*
*}{if $field->getAutosaveId() !== null}{*
*} data-autosave="{@$field->getAutosaveId()}"{*
*}{if $field->getLastEditTime() !== 0}{*
--- /dev/null
+<button id="{@$button->getPrefixedId()}"{*
+ *}{if !$button->getClasses()|empty} class="{implode from=$button->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{foreach from=$button->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}{if $button->getAccessKey()} accesskey="{$button->getAccessKey()}"{/if}{*
+*}>{$button->getLabel()}</button>
+
+<script data-relocate="true">
+ require(['Language'], function(Language) {
+ Language.addObject({
+ 'wcf.global.preview': '{lang}wcf.global.preview{/lang}'
+ });
+
+ new WCF.Message.DefaultPreview({
+ messageFieldID: '{@$button->getWysiwygId()}',
+ previewButtonID: '{@$button->getPrefixedId()}',
+ messageObjectType: '{@$button->getObjectType()->objectType}',
+ messageObjectID: '{@$button->getObjectId()}'
+ });
+ });
+</script>
--- /dev/null
+{include file='__tabTabMenuFormContainer'}
+
+<script data-relocate="true">
+ $(function() {
+ {if $container->children()|count > 1}
+ new WCF.Message.SmileyCategories('{@$container->getWysiwygId()}');
+ {/if}
+
+ new WCF.Message.Smilies('{@$container->getWysiwygId()}');
+ });
+</script>
--- /dev/null
+<ul class="inlineList smileyList">
+ {foreach from=$field->getSmilies() item=smiley}
+ <li><a title="{lang}{$smiley->smileyTitle}{/lang}" class="jsTooltip jsSmiley">{@$smiley->getHtml()}</a></li>
+ {/foreach}
+</ul>
--- /dev/null
+{include file='__tabMenuFormContainer'}
+
+{js application='wcf' file='WCF.Message' bundle='WCF.Combined'}
+<script data-relocate="true">
+ $(function() {
+ $('.messageTabMenu').messageTabMenu();
+ });
+</script>
"__multipleSelectionFormField",
"__nonEmptyFormFieldDependency",
"__numericFormField",
+ "__pollOptionsFormField",
"__radioButtonFormField",
"__singleSelectionFormField",
"__tabFormContainer",
"__userFormField",
"__usernameFormField",
"__valueFormFieldDependency",
+ "__wysiwygAttachmentFormField",
"__wysiwygCmsToolbar",
"__wysiwygFormField",
+ "__wysiwygPreviewFormButton",
+ "__wysiwygSmileyFormContainer",
+ "__wysiwygSmileyFormField",
"aclPermissionJavaScript",
"articleAdd",
"articleAddDialog",
});
</script>
-<form method="{@$form->getMethod()}" {*
- *}action="{@$form->getAction()}" {*
- *}id="{@$form->getId()}"{*
- *}{if !$form->getClasses()|empty} class="{implode from=$form->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
- *}{foreach from=$form->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
-*}>
+{if $form->isAjax()}
+ <section id="{@$form->getId()}"{*
+ *}{if !$form->getClasses()|empty} class="{implode from=$form->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{foreach from=$form->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}>
+{else}
+ <form method="{@$form->getMethod()}" {*
+ *}action="{@$form->getAction()}" {*
+ *}id="{@$form->getId()}"{*
+ *}{if !$form->getClasses()|empty} class="{implode from=$form->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{foreach from=$form->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}>
+{/if}
{foreach from=$form item='child'}
{if $child->isAvailable()}
{@$child->getHtml()}
{/if}
{@SECURITY_TOKEN_INPUT_TAG}
-</form>
+{if $form->isAjax()}
+ </section>
+{else}
+ </form>
+{/if}
<script data-relocate="true">
{* after all dependencies have been added, check them *}
--- /dev/null
+{include file='__formFieldHeader'}
+
+<ol class="sortableList"></ol>
+
+{include file='__formFieldFooter'}
+
+{js application='wcf' file='WCF.Poll' bundle='WCF.Combined'}
+<script data-relocate="true">
+ require(['Dom/Traverse', 'Dom/Util', 'Language'], function(DomTraverse, DomUtil, Language) {
+ Language.addObject({
+ 'wcf.poll.button.addOption': '{lang}wcf.poll.button.addOption{/lang}',
+ 'wcf.poll.button.removeOption': '{lang}wcf.poll.button.removeOption{/lang}'
+ });
+
+ new WCF.Poll.Management(
+ DomUtil.identify(DomTraverse.childByTag(elById('{@$field->getPrefixedId()}Container'), 'DD')),
+ [ {implode from=$field->getValue() item=pollOption}{ optionID: {@$pollOption[optionID]}, optionValue: '{$pollOption[optionValue]|encodeJS}' }{/implode} ],
+ {@POLL_MAX_OPTIONS},
+ '{if $field->getDocument()->isAjax()}{@$field->getWysiwygId()}{/if}',
+ '{@$field->getPrefixedId()}'
+ );
+ });
+</script>
--- /dev/null
+{include file='__formFieldHeader'}
+
+<ul id="{@$field->getPrefixedID()}_attachmentList" {*
+ *}class="formAttachmentList"{*
+ *}{if !$field->getAttachmentHandler()->getAttachmentList()|count} style="display: none"{/if} {*
+ *}data-enable-thumbnails="{if ATTACHMENT_ENABLE_THUMBNAILS}true{else}false{/if}"{*
+*}>
+ {foreach from=$field->getAttachmentHandler()->getAttachmentList() item=$attachment}
+ <li class="box64" {*
+ *}data-object-id="{@$attachment->attachmentID}" {*
+ *}data-height="{@$attachment->height}" {*
+ *}data-width="{@$attachment->width}" {*
+ *}data-is-image="{@$attachment->isImage}"{*
+ *}>
+ {if $attachment->tinyThumbnailType}
+ <img src="{link controller='Attachment' object=$attachment}tiny=1{/link}" alt="" class="attachmentTinyThumbnail">
+ {else}
+ <span class="icon icon64 fa-{@$attachment->getIconName()}"></span>
+ {/if}
+
+ <div>
+ <div>
+ <p><a href="{link controller='Attachment' object=$attachment}{/link}" target="_blank"{if $attachment->isImage} title="{$attachment->filename}" class="jsImageViewer"{/if}>{$attachment->filename}</a></p>
+ <small>{@$attachment->filesize|filesize}</small>
+ </div>
+
+ <ul class="buttonGroup">
+ <li><span class="button small jsDeleteButton" data-object-id="{@$attachment->attachmentID}" data-confirm-message="{lang}wcf.attachment.delete.sure{/lang}">{lang}wcf.global.button.delete{/lang}</span></li>
+ {if $attachment->isImage}
+ {if $attachment->thumbnailType}
+ <li><span class="button small jsButtonAttachmentInsertThumbnail" data-object-id="{@$attachment->attachmentID}" data-url="{link controller='Attachment' object=$attachment}thumbnail=1{/link}">{lang}wcf.attachment.insertThumbnail{/lang}</span></li>
+ {/if}
+ <li><span class="button small jsButtonAttachmentInsertFull" data-object-id="{@$attachment->attachmentID}" data-url="{link controller='Attachment' object=$attachment}{/link}">{lang}wcf.attachment.insertFull{/lang}</span></li>
+ {else}
+ <li><span class="button small jsButtonInsertAttachment" data-object-id="{@$attachment->attachmentID}">{lang}wcf.attachment.insert{/lang}</span></li>
+ {/if}
+ </ul>
+ </div>
+ </li>
+ {/foreach}
+</ul>
+<div id="{@$field->getPrefixedID()}_uploadButton" class="formAttachmentButtons" data-max-size="{@$field->getAttachmentHandler()->getMaxSize()}"></div>
+
+{js application='wcf' file='WCF.Attachment' bundle='WCF.Combined'}
+<script data-relocate="true">
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.attachment.upload.error.invalidExtension': '{lang}wcf.attachment.upload.error.invalidExtension{/lang}',
+ 'wcf.attachment.upload.error.tooLarge': '{lang}wcf.attachment.upload.error.tooLarge{/lang}',
+ 'wcf.attachment.upload.error.reachedLimit': '{lang}wcf.attachment.upload.error.reachedLimit{/lang}',
+ 'wcf.attachment.upload.error.reachedRemainingLimit': '{lang}wcf.attachment.upload.error.reachedRemainingLimit{/lang}',
+ 'wcf.attachment.upload.error.uploadFailed': '{lang}wcf.attachment.upload.error.uploadFailed{/lang}',
+ 'wcf.attachment.upload.error.uploadPhpLimit': '{lang}wcf.attachment.upload.error.uploadPhpLimit{/lang}',
+ 'wcf.attachment.insert': '{lang}wcf.attachment.insert{/lang}',
+ 'wcf.attachment.insertAll': '{lang}wcf.attachment.insertAll{/lang}',
+ 'wcf.attachment.insertFull': '{lang}wcf.attachment.insertFull{/lang}',
+ 'wcf.attachment.insertThumbnail': '{lang}wcf.attachment.insertThumbnail{/lang}',
+ 'wcf.attachment.delete.sure': '{lang}wcf.attachment.delete.sure{/lang}'
+ });
+
+ new WCF.Attachment.Upload(
+ $('#{@$field->getPrefixedID()}_uploadButton'),
+ $('#{@$field->getPrefixedID()}_attachmentList'),
+ '{@$field->getAttachmentHandler()->getObjectType()->objectType}',
+ '{@$field->getAttachmentHandler()->getObjectID()}',
+ '{$field->getAttachmentHandler()->getTmpHashes()[0]|encodeJS}',
+ '{@$field->getAttachmentHandler()->getParentObjectID()}',
+ {@$field->getAttachmentHandler()->getMaxCount()},
+ '{@$field->getWysiwygId()}'
+ );
+ new WCF.Action.Delete('wcf\\data\\attachment\\AttachmentAction', '.formAttachmentList > li');
+ });
+</script>
+
+<input type="hidden" name="{@$field->getPrefixedID()}_tmpHash" value="{$field->getAttachmentHandler()->getTmpHashes()[0]}">
+
+{include file='__formFieldFooter'}
*}id="{@$field->getPrefixedId()}" {*
*}name="{@$field->getPrefixedId()}" {*
*}class="wysiwygTextarea" {*
- *}data-disable-attachments="true"{*
+ *}data-disable-attachments="{if $field->supportsAttachments()}false{else}true{/if}"{*
+ *}data-support-mention="{if $field->supportsMentions()}true{else}false{/if}"{*
*}{if $field->getAutosaveId() !== null}{*
*} data-autosave="{@$field->getAutosaveId()}"{*
*}{if $field->getLastEditTime() !== 0}{*
--- /dev/null
+<button id="{@$button->getPrefixedId()}"{*
+ *}{if !$button->getClasses()|empty} class="{implode from=$button->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{foreach from=$button->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}{if $button->getAccessKey()} accesskey="{$button->getAccessKey()}"{/if}{*
+*}>{$button->getLabel()}</button>
+
+<script data-relocate="true">
+ require(['Language'], function(Language) {
+ Language.addObject({
+ 'wcf.global.preview': '{lang}wcf.global.preview{/lang}'
+ });
+
+ new WCF.Message.DefaultPreview({
+ messageFieldID: '{@$button->getWysiwygId()}',
+ previewButtonID: '{@$button->getPrefixedId()}',
+ messageObjectType: '{@$button->getObjectType()->objectType}',
+ messageObjectID: '{@$button->getObjectId()}'
+ });
+ });
+</script>
--- /dev/null
+{include file='__tabTabMenuFormContainer'}
+
+<script data-relocate="true">
+ $(function() {
+ {if $container->children()|count > 1}
+ new WCF.Message.SmileyCategories('{@$container->getWysiwygId()}');
+ {/if}
+
+ new WCF.Message.Smilies('{@$container->getWysiwygId()}');
+ });
+</script>
--- /dev/null
+<ul class="inlineList smileyList">
+ {foreach from=$field->getSmilies() item=smiley}
+ <li><a title="{lang}{$smiley->smileyTitle}{/lang}" class="jsTooltip jsSmiley">{@$smiley->getHtml()}</a></li>
+ {/foreach}
+</ul>
--- /dev/null
+{include file='__tabMenuFormContainer'}
+
+{js application='wcf' file='WCF.Message' bundle='WCF.Combined'}
+<script data-relocate="true">
+ $(function() {
+ $('#{@$container->getPrefixedId()}').messageTabMenu();
+ });
+</script>
if ($name === undefined) {
$name = $tab.wcfIdentify();
- console.debug("[wcf.messageTabMenu] Missing name attribute, assuming generic ID '" + $name + "'");
}
}
* @param {int} maxOptions
* @param {string} editorId
*/
- init: function (containerID, optionList, maxOptions, editorId) {
+ init: function (containerID, optionList, maxOptions, editorId, fieldName) {
this._count = 0;
this._maxOptions = maxOptions || -1;
this._container = $('#' + containerID).children('ol:eq(0)');
+ this._fieldName = fieldName || 'pollOptions';
+
if (!this._container.length) {
console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");
return;
var $formSubmit = this._container.parents('form').find('.formSubmit');
for (var $i = 0, $length = $options.length; $i < $length; $i++) {
- $('<input type="hidden" name="pollOptions[' + $i + ']">').val($options[$i]).appendTo($formSubmit);
+ $('<input type="hidden" name="' + this._fieldName + '[' + $i + ']">').val($options[$i]).appendTo($formSubmit);
}
}
}
if (form === null) {
throw new Error("Unknown element with id '" + formId + "'");
}
- if (form.tagName !== 'FORM') {
- var dialogContent = DomTraverse.parentByClass(form, 'dialogContent');
-
- if (dialogContent === null) {
- throw new Error("Element with id '" + formId + "' is no form.");
- }
- }
if (_forms.has(form)) {
throw new Error("Form with id '" + formId + "' has already been registered.");
* and `false` otherwise
* @var boolean
*/
- protected $ajax;
+ protected $ajax = false;
/**
* buttons registered for this form document
--- /dev/null
+<?php
+namespace wcf\system\form\builder;
+
+/**
+ * Provides methods to get and set the id of the related `WysiwygFormField` form field for wysiwyg-
+ * related form nodes.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Container\Wysiwyg
+ * @since 5.2
+ */
+trait TWysiwygFormNode {
+ /**
+ * id of the related `WysiwygFormField` form field
+ * @var string
+ */
+ protected $wysiwygId;
+
+ /**
+ * Returns id of the related `WysiwygFormField` form field.
+ *
+ * @return string
+ * @throws \BadMethodCallException if the id of the related `WysiwygFormField` form field is unknown
+ */
+ public function getWysiwygId() {
+ if ($this->wysiwygId === null) {
+ throw new \BadMethodCallException("The id of the related 'WysiwygFormField' form field is unknown.");
+ }
+
+ return $this->wysiwygId;
+ }
+
+ /**
+ * Sets the id of the related `WysiwygFormField` form field and returns this field.
+ *
+ * @param string $wysiwygId
+ * @return static this field
+ */
+ public function wysiwygId($wysiwygId) {
+ $this->wysiwygId = $wysiwygId;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\button\wysiwyg;
+use wcf\system\form\builder\button\FormButton;
+use wcf\system\form\builder\field\IObjectTypeFormNode;
+use wcf\system\form\builder\field\TObjectTypeFormNode;
+use wcf\system\form\builder\TWysiwygFormNode;
+
+/**
+ * Represents a preview button for a wysiwyg field.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Button\Wysiwyg
+ * @since 5.2
+ */
+class WysiwygPreviewFormButton extends FormButton implements IObjectTypeFormNode {
+ use TObjectTypeFormNode;
+ use TWysiwygFormNode;
+
+ /**
+ * id of the previewed message
+ * @var integer
+ */
+ protected $objectId = 0;
+
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = '__wysiwygPreviewFormButton';
+
+ /**
+ * Creates a new instance of `WysiwygPreviewFormButton`.
+ */
+ public function __construct() {
+ $this->label('wcf.global.button.preview');
+ }
+
+ /**
+ * Returns the id of the previewed message.
+ *
+ * By default, `0` is returned.
+ *
+ * @return integer
+ */
+ public function getObjectId() {
+ return $this->objectId;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getObjectTypeDefinition() {
+ return 'com.woltlab.wcf.message';
+ }
+
+ /**
+ * Sets the id of the previewed message and returns this button.
+ *
+ * @param integer $objectId id of previewed message
+ * @return WysiwygPreviewFormButton this button
+ */
+ public function objectId($objectId) {
+ $this->objectId = $objectId;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\container\wysiwyg;
+use wcf\data\IStorableObject;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\attachment\AttachmentHandler;
+use wcf\system\event\EventHandler;
+use wcf\system\form\builder\button\wysiwyg\WysiwygPreviewFormButton;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\container\TabFormContainer;
+use wcf\system\form\builder\field\wysiwyg\WysiwygAttachmentFormField;
+use wcf\system\form\builder\field\wysiwyg\WysiwygFormField;
+use wcf\system\form\builder\IFormNode;
+use wcf\system\form\builder\TWysiwygFormNode;
+
+/**
+ * Represents the whole container with a WYSIWYG editor and the associated tab menu below it with
+ * support for smilies, attchments, settings, and polls.
+ *
+ * Instead of having to manually set up each individual component, this form container allows to
+ * simply create an instance of this class, set some required data for some components, and the
+ * setup is complete.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Container\Wysiwyg
+ * @since 5.2
+ */
+class WysiwygFormContainer extends FormContainer {
+ use TWysiwygFormNode;
+
+ /**
+ * attachment form field
+ * @var WysiwygAttachmentFormField
+ */
+ protected $attachmentField;
+
+ /**
+ * attachment-related data used to create an `AttachmentHandler` object for the attachment
+ * form field
+ * @var null|array
+ */
+ protected $attachmentData;
+
+ /**
+ * name of the relevant message object type
+ * @var string
+ */
+ protected $messageObjectType;
+
+ /**
+ * id of the edited object
+ * @var integer
+ */
+ protected $objectId;
+
+ /**
+ * pre-select attribute of the tab menu
+ * @var string
+ */
+ protected $preselect = 'true';
+
+ /**
+ * name of the relevant poll object type
+ * @var string
+ */
+ protected $pollObjectType;
+
+ /**
+ * poll form container
+ * @var WysiwygPollFormContainer
+ */
+ protected $pollContainer;
+
+ /**
+ * settings form container
+ * @var FormContainer
+ */
+ protected $settingsContainer;
+
+ /**
+ * setting nodes that will be added to the settings container when it is created
+ * @var IFormNode[]
+ */
+ protected $settingsNodes = [];
+
+ /**
+ * form container for smiley categories
+ * @var WysiwygSmileyFormContainer
+ */
+ protected $smiliesContainer;
+
+ /**
+ * is `true` if the wysiwyg form field should support mentions, otherwise `false`
+ * @var boolean
+ */
+ protected $supportMentions = false;
+
+ /**
+ * is `true` if smilies are supported for this container, otherwise `false`
+ * @var boolean
+ */
+ protected $supportSmilies = false;
+
+ /**
+ * actual wysiwyg form field
+ * @var WysiwygFormNode
+ */
+ protected $wysiwygField;
+
+ /**
+ * @inheritDoc
+ */
+ public static function create($id) {
+ // the actual id is used for the form field containing the text
+ return parent::create($id . 'Container');
+ }
+
+ /**
+ * Adds a node that will be appended to the settings form container when it is built and
+ * returns this container.
+ *
+ * @param IFormNode $settingsNode added settings node
+ * @return WysiwygFormContainer this form field container
+ */
+ public function addSettingsNode(IFormNode $settingsNode) {
+ if ($this->settingsContainer !== null) {
+ // if settings container has already been created, add it directly
+ $this->settingsContainer->appendChild($settingsNode);
+ }
+ else {
+ $this->settingsNodes[] = $settingsNode;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds nodes that will be appended to the settings form container when it is built and
+ * returns this container.
+ *
+ * @param IFormNode[] $settingsNodes added settings nodes
+ * @return WysiwygFormContainer this form field container
+ */
+ public function addSettingsNodes(array $settingsNodes) {
+ foreach ($settingsNodes as $settingsNode) {
+ $this->addSettingsNode($settingsNode);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function loadValuesFromObject(IStorableObject $object) {
+ $this->objectId = $object->getObjectID();
+
+ return parent::loadValuesFromObject($object);
+ }
+
+ /**
+ * Sets the attachment-related data used to create an `AttachmentHandler` object for the
+ * attachment form field. If no attachment data is set, attachments are not supported.
+ *
+ * By default, no attachment data is set.
+ *
+ * @param null|string $objectType name of attachment object type or `null` to unset previous attachment data
+ * @param integer $parentObjectID id of the parent of the object the attachments belong to or `0` if no such parent exists
+ * @return WysiwygFormContainer this form container
+ * @throws \BadMethodCallException if the attachment form field has already been initialized
+ */
+ public function attachmentData($objectType = null, $parentObjectID = 0) {
+ if ($this->attachmentField !== null) {
+ throw new \BadMethodCallException("The attachment form field has already been initialized. Use the atatchment form field directly to manipulate attachment data.");
+ }
+
+ if ($objectType === null) {
+ $this->attachmentData = null;
+ }
+ else {
+ if (ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', $objectType) === null) {
+ throw new \InvalidArgumentException("Unknown attachment object type '{$objectType}'.");
+ }
+
+ $this->attachmentData = [
+ 'objectType' => $objectType,
+ 'parentObjectID' => $parentObjectID
+ ];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the form field handling attachments.
+ *
+ * @return WysiwygAttachmentFormField
+ * @throws \BadMethodCallException if the form field container has not been populated yet/form has not been built yet
+ */
+ public function getAttachmentField() {
+ if ($this->attachmentField === null) {
+ throw new \BadMethodCallException("Wysiwyg form field can only be requested after the form has been built.");
+ }
+
+ return $this->attachmentField;
+ }
+
+ /**
+ * Returns the id of the edited object or `0` if no object is edited.
+ *
+ * @return integer
+ */
+ public function getObjectId() {
+ return $this->objectId;
+ }
+
+ /**
+ * Returns the value of the wysiwyg tab menu's `data-preselect` attribute used to determine
+ * which tab is preselected.
+ *
+ * By default, `'true'` is returned which is used to pre-select the first tab.
+ *
+ * @return string
+ */
+ public function getPreselect() {
+ return $this->preselect;
+ }
+
+ /**
+ * Returns the wysiwyg form container with all poll-related fields.
+ *
+ * @return WysiwygPollFormContainer
+ * @throws \BadMethodCallException if the form field container has not been populated yet/form has not been built yet
+ */
+ public function getPollContainer() {
+ if ($this->pollContainer === null) {
+ throw new \BadMethodCallException("Wysiwyg form field can only be requested after the form has been built.");
+ }
+
+ return $this->pollContainer;
+ }
+
+ /**
+ * Returns the form container for all settings-related fields.
+ *
+ * @return FormContainer
+ * @throws \BadMethodCallException if the form field container has not been populated yet/form has not been built yet
+ */
+ public function getSettingsContainer() {
+ if ($this->settingsContainer === null) {
+ throw new \BadMethodCallException("Wysiwyg form field can only be requested after the form has been built.");
+ }
+
+ return $this->settingsContainer;
+ }
+
+ /**
+ * Returns the form container for smiley categories.
+ *
+ * @return WysiwygSmileyFormContainer
+ * @throws \BadMethodCallException if the form field container has not been populated yet/form has not been built yet
+ */
+ public function getSmiliesContainer() {
+ if ($this->smiliesContainer === null) {
+ throw new \BadMethodCallException("Smilies form field container can only be requested after the form has been built.");
+ }
+
+ return $this->smiliesContainer;
+ }
+
+ /**
+ * Returns the wysiwyg form field handling the actual text.
+ *
+ * @return WysiwygFormField
+ * @throws \BadMethodCallException if the form field container has not been populated yet/form has not been built yet
+ */
+ public function getWysiwygField() {
+ if ($this->wysiwygField === null) {
+ throw new \BadMethodCallException("Wysiwyg form field can only be requested after the form has been built.");
+ }
+
+ return $this->wysiwygField;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function id($id) {
+ $this->wysiwygId(substr($id, 0, -strlen('Container')));
+
+ return parent::id($id);
+ }
+
+ /**
+ * Sets the message object type used by the wysiwyg form field.
+ *
+ * @param string $messageObjectType message object type for wysiwyg form field
+ * @return WysiwygFormContainer this container
+ * @throws \InvalidArgumentException if the given string is no message object type
+ */
+ public function messageObjectType($messageObjectType) {
+ if (ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.message', $messageObjectType) === null) {
+ throw new \InvalidArgumentException("Unknown message object type '{$messageObjectType}'.");
+ }
+
+ if ($this->wysiwygField !== null) {
+ $this->wysiwygField->objectType($messageObjectType);
+ }
+ else {
+ $this->messageObjectType = $messageObjectType;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the poll object type used by the poll form field container.
+ *
+ * By default, no poll object type is set, thus the poll form field container is not available.
+ *
+ * @param string $pollObjectType poll object type for wysiwyg form field
+ * @return WysiwygFormContainer this container
+ * @throws \InvalidArgumentException if the given string is no poll object type
+ */
+ public function pollObjectType($pollObjectType = true) {
+ if (ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.poll', $pollObjectType) === null) {
+ throw new \InvalidArgumentException("Unknown poll object type '{$pollObjectType}'.");
+ }
+
+ if ($this->pollContainer !== null) {
+ $this->pollContainer->objectType($pollObjectType);
+ }
+ else {
+ $this->pollObjectType = $pollObjectType;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function populate() {
+ parent::populate();
+
+ $this->wysiwygField = WysiwygFormField::create($this->wysiwygId)
+ ->objectType($this->messageObjectType)
+ ->supportAttachments($this->attachmentData !== null)
+ ->supportMentions($this->supportMentions);
+ $this->smiliesContainer = WysiwygSmileyFormContainer::create($this->wysiwygId . 'SmiliesTab')
+ ->wysiwygId($this->getWysiwygId())
+ ->label('wcf.message.smilies')
+ ->available($this->supportSmilies);
+ $this->attachmentField = WysiwygAttachmentFormField::create($this->wysiwygId . 'Attachments')
+ ->wysiwygId($this->getWysiwygId());
+ $this->settingsContainer = FormContainer::create($this->wysiwygId . 'SettingsContainer')
+ ->appendChildren($this->settingsNodes);
+ $this->pollContainer = WysiwygPollFormContainer::create($this->wysiwygId . 'PollContainer')
+ ->wysiwygId($this->getWysiwygId());
+ if ($this->pollObjectType) {
+ $this->pollContainer->objectType($this->pollObjectType);
+ }
+
+ $this->appendChildren([
+ $this->wysiwygField,
+ WysiwygTabMenuFormContainer::create($this->wysiwygId . 'Tabs')
+ ->attribute('data-preselect', $this->getPreselect())
+ ->attribute('data-wysiwyg-container-id', $this->wysiwygId)
+ ->useAnchors(false)
+ ->appendChildren([
+ $this->smiliesContainer,
+
+ TabFormContainer::create($this->wysiwygId . 'AttachmentsTab')
+ ->addClass('formAttachmentContent')
+ ->label('wcf.attachment.attachments')
+ ->appendChild(
+ FormContainer::create($this->wysiwygId . 'AttachmentsContainer')
+ ->appendChild($this->attachmentField)
+ ),
+
+ TabFormContainer::create($this->wysiwygId . 'SettingsTab')
+ ->label('wcf.message.settings')
+ ->appendChild($this->settingsContainer)
+ ->available(MODULE_SMILEY),
+
+ TabFormContainer::create($this->wysiwygId . 'PollTab')
+ ->label('wcf.poll.management')
+ ->appendChild($this->pollContainer)
+ ])
+ ]);
+
+ if ($this->attachmentData !== null) {
+ $this->attachmentField->attachmentHandler(
+ // the temporary hash may not be empty (at the same time as the
+ // object id) and it will be changed anyway by the called method
+ new AttachmentHandler(
+ $this->attachmentData['objectType'],
+ $this->getObjectId(),
+ '.',
+ $this->attachmentData['parentObjectID']
+ )
+ );
+ }
+
+ $this->getDocument()->addButton(
+ WysiwygPreviewFormButton::create($this->getWysiwygId() . 'PreviewButton')
+ ->objectType($this->messageObjectType)
+ ->wysiwygId($this->getWysiwygId())
+ ->objectId($this->getObjectId())
+ );
+
+ EventHandler::getInstance()->fireAction($this, 'populate');
+ }
+
+ /**
+ * Sets the value of the wysiwyg tab menu's `data-preselect` attribute used to determine which
+ * tab is preselected.
+ *
+ * @param string $preselect id of preselected tab, `'true'` for first tab, or non-existing id for no preselected tab
+ * @return WysiwygFormContainer
+ */
+ public function preselect($preselect = 'true') {
+ $this->preselect = $preselect;
+
+ return $this;
+ }
+
+ /**
+ * Sets if mentions are supported by the editor field and returns this form container.
+ *
+ * By default, mentions are not supported.
+ *
+ * @param boolean $supportMention
+ * @return WysiwygFormContainer this form container
+ * @throws \BadMethodCallException if the wysiwyg form field has already been initialized
+ */
+ public function supportMentions($supportMentions = true) {
+ if ($this->wysiwygField !== null) {
+ throw new \BadMethodCallException("The wysiwyg form field has already been initialized. Use the wysiwyg form field directly to manipulate mention support.");
+ }
+
+ $this->supportMentions = $supportMentions;
+
+ return $this;
+ }
+
+ /**
+ * Sets if smilies are supported for this form container and returns this form container.
+ *
+ * By default, smilies are not supported.
+ *
+ * @param boolean $supportSmilies
+ * @return WysiwygFormContainer this form container
+ * @throws \BadMethodCallException if the poll container has already been initialized
+ */
+ public function supportSmilies($supportSmilies = true) {
+ if ($this->smiliesContainer !== null) {
+ throw new \BadMethodCallException("The smilies form container has already been initialized. Use the smilies container directly to manipulate poll support.");
+ }
+
+ $this->supportSmilies = $supportSmilies;
+
+ return $this;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\container\wysiwyg;
+use wcf\data\IPollContainer;
+use wcf\data\IStorableObject;
+use wcf\data\poll\Poll;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\field\BooleanFormField;
+use wcf\system\form\builder\field\data\processor\CustomFormFieldDataProcessor;
+use wcf\system\form\builder\field\DateFormField;
+use wcf\system\form\builder\field\IntegerFormField;
+use wcf\system\form\builder\field\IObjectTypeFormNode;
+use wcf\system\form\builder\field\poll\PollOptionsFormField;
+use wcf\system\form\builder\field\TextFormField;
+use wcf\system\form\builder\field\TObjectTypeFormNode;
+use wcf\system\form\builder\field\validation\FormFieldValidationError;
+use wcf\system\form\builder\field\validation\FormFieldValidator;
+use wcf\system\form\builder\IFormDocument;
+use wcf\system\form\builder\TWysiwygFormNode;
+use wcf\system\poll\IPollHandler;
+
+/**
+ * Represents the form container for the poll-related fields below a WYSIWYG editor.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Container\Wysiwyg
+ * @since 5.2
+ */
+class WysiwygPollFormContainer extends FormContainer implements IObjectTypeFormNode {
+ use TObjectTypeFormNode;
+ use TWysiwygFormNode;
+
+ /**
+ * form field to set the end date of the poll
+ * @var DateFormField
+ */
+ protected $endTimeField;
+
+ /**
+ * form field to set if votes can be changed
+ * @var BooleanFormField
+ */
+ protected $isChangeableField;
+
+ /**
+ * form field to set if the poll results are public
+ * @var BooleanFormField
+ */
+ protected $isPublicField;
+
+ /**
+ * form field to set the maximum number of votes per user
+ * @var IntegerFormField
+ */
+ protected $maxVotesField;
+
+ /**
+ * form field to set the available poll answers
+ * @var PollOptionsFormField
+ */
+ protected $optionsField;
+
+ /**
+ * poll belonging to the edited object
+ * @var null|Poll
+ */
+ protected $poll;
+
+ /**
+ * form field to set the question of the poll
+ * @var TextFormField
+ */
+ protected $questionField;
+
+ /**
+ * form field to set whether viewing the poll results requires voting
+ * @var BooleanFormField
+ */
+ protected $resultsRequireVoteField;
+
+ /**
+ * form field to set whether the poll answers are sorted by votes when viewing the results
+ * @var BooleanFormField
+ */
+ protected $sortByVotesField;
+
+ const FIELD_NAMES = [
+ 'endTime',
+ 'isChangeable',
+ 'isPublic',
+ 'maxVotes',
+ 'options',
+ 'question',
+ 'resultsRequireVote',
+ 'sortByVotes'
+ ];
+
+ /**
+ * Returns form field to set the end date of the poll.
+ *
+ * @return DateFormField
+ * @throws \BadMethodCallException if the form field has not been populated yet/form has not been built yet
+ */
+ public function getEndTimeField() {
+ if ($this->endTimeField === null) {
+ throw new \BadMethodCallException("Poll form field can only be requested after the form has been built.");
+ }
+
+ return $this->endTimeField;
+ }
+
+ /**
+ * Returns the form field to set if votes can be changed.
+ *
+ * @return BooleanFormField
+ * @throws \BadMethodCallException if the form field has not been populated yet/form has not been built yet
+ */
+ public function getIsChangeableField() {
+ if ($this->isChangeableField === null) {
+ throw new \BadMethodCallException("Poll form field can only be requested after the form has been built.");
+ }
+
+ return $this->isChangeableField;
+ }
+
+ /**
+ * Returns the form field to set if the poll results are public.
+ *
+ * @return BooleanFormField
+ * @throws \BadMethodCallException if the form field has not been populated yet/form has not been built yet
+ */
+ public function getIsPublicField() {
+ if ($this->isPublicField === null) {
+ throw new \BadMethodCallException("Poll form field can only be requested after the form has been built.");
+ }
+
+ return $this->isPublicField;
+ }
+
+ /**
+ * Returns the form field to set the maximum number of votes per user.
+ *
+ * @return IntegerFormField
+ * @throws \BadMethodCallException if the form field has not been populated yet/form has not been built yet
+ */
+ public function getMaxVotesField() {
+ if ($this->maxVotesField === null) {
+ throw new \BadMethodCallException("Poll form field can only be requested after the form has been built.");
+ }
+
+ return $this->maxVotesField;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getObjectTypeDefinition() {
+ return 'com.woltlab.wcf.poll';
+ }
+
+ /**
+ * Returns the form field to set the available poll answers.
+ *
+ * @return PollOptionsFormField
+ * @throws \BadMethodCallException if the form field has not been populated yet/form has not been built yet
+ */
+ public function getOptionsField() {
+ if ($this->optionsField === null) {
+ throw new \BadMethodCallException("Poll form field can only be requested after the form has been built.");
+ }
+
+ return $this->optionsField;
+ }
+
+ /**
+ * Returns the form field to set the question of the poll.
+ *
+ * @return TextFormField
+ * @throws \BadMethodCallException if the form field has not been populated yet/form has not been built yet
+ */
+ public function getQuestionField() {
+ if ($this->questionField === null) {
+ throw new \BadMethodCallException("Poll form field can only be requested after the form has been built.");
+ }
+
+ return $this->questionField;
+ }
+
+ /**
+ * Returns the form field to set whether viewing the poll results requires voting.
+ *
+ * @return BooleanFormField
+ * @throws \BadMethodCallException if the form field has not been populated yet/form has not been built yet
+ */
+ public function getResultsRequireVoteField() {
+ if ($this->resultsRequireVoteField === null) {
+ throw new \BadMethodCallException("Poll form field can only be requested after the form has been built.");
+ }
+
+ return $this->resultsRequireVoteField;
+ }
+
+ /**
+ * Returns the form field to set whether the poll answers are sorted by votes when viewing
+ * the results.
+ *
+ * @return BooleanFormField
+ * @throws \BadMethodCallException if the form field has not been populated yet/form has not been built yet
+ */
+ public function getSortByVotesField() {
+ if ($this->sortByVotesField === null) {
+ throw new \BadMethodCallException("Poll form field can only be requested after the form has been built.");
+ }
+
+ return $this->sortByVotesField;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isAvailable() {
+ return parent::isAvailable() && $this->objectType !== null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function loadValuesFromObject(IStorableObject $object) {
+ if ($object instanceof IPollContainer && $object->getPollID() !== null) {
+ $this->poll = new Poll($object->getPollID());
+ if (!$this->poll->pollID) {
+ $this->poll = null;
+ }
+ else {
+ // `isPublic` cannot be changed when editing polls
+ $this->getIsPublicField()->isAvailable(false);
+ }
+
+ $this->getQuestionField()->value($this->poll->question);
+ $this->getOptionsField()->value($this->poll->getOptions());
+ $this->getEndTimeField()->value($this->poll->endTime);
+ $this->getMaxVotesField()->value($this->poll->maxVotes);
+ $this->getIsChangeableField()->value($this->poll->isChangeable);
+ $this->getIsPublicField()->value($this->poll->isPublic);
+ $this->getResultsRequireVoteField()->value($this->poll->resultsRequireVote);
+ $this->getSortByVotesField()->value($this->poll->sortByVotes);
+ }
+
+ return parent::loadValuesFromObject($object);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function populate() {
+ parent::populate();
+
+ $id = $this->wysiwygId . 'Poll';
+
+ // add data handler to group poll data into a sub-array of parameters
+ $this->getDocument()->getDataHandler()->add(new CustomFormFieldDataProcessor($id, function(IFormDocument $document, array $parameters) use($id) {
+ if (!$this->isAvailable()) {
+ return $parameters;
+ }
+
+ $wysiwygId = $this->getWysiwygId();
+
+ foreach (self::FIELD_NAMES as $fieldName) {
+ $parameters[$wysiwygId . '_pollData'][$fieldName] = $parameters['data'][$id . '_' . $fieldName];
+ unset($parameters['data'][$id . '_' . $fieldName]);
+ }
+
+ // this will always add a poll array to the parameters but
+ // `PollManager::savePoll()` is capable of correctly detecting
+ // when, based on the given data, nothing has to be done
+
+ return $parameters;
+ }));
+
+ $this->questionField = TextFormField::create($id . '_question')
+ ->label('wcf.poll.question')
+ ->maximumLength(255);
+
+ // if either options or question is given, the other must also be given
+ $this->optionsField = PollOptionsFormField::create($id . '_options')
+ ->wysiwygId($this->getWysiwygId())
+ ->addValidator(new FormFieldValidator('empty', function(PollOptionsFormField $formField) use ($id) {
+ /** @var TextFormField $questionFormField */
+ $questionFormField = $formField->getDocument()->getNodeById($id . '_question');
+
+ if (empty($formField->getValue()) && $questionFormField->getValue() !== '') {
+ $formField->addValidationError(new FormFieldValidationError('empty'));
+ }
+ else if (!empty($formField->getValue()) && $questionFormField->getValue() === '') {
+ $questionFormField->addValidationError(new FormFieldValidationError('empty'));
+ }
+ }));
+
+ $this->endTimeField = DateFormField::create($id . '_endTime')
+ ->label('wcf.poll.endTime')
+ ->supportTime()
+ ->addValidator(new FormFieldValidator('futureTime', function(DateFormField $formField) use ($id) {
+ $endTime = $formField->getSaveValue();
+
+ if ($endTime && $endTime <= TIME_NOW) {
+ if ($this->poll === null || $this->poll->endTime >= TIME_NOW) {
+ $formField->addValidationError(new FormFieldValidationError(
+ 'invalid',
+ 'wcf.poll.endTime.error.invalid'
+ ));
+ }
+ }
+ }));
+
+ $this->maxVotesField = IntegerFormField::create($id . '_maxVotes')
+ ->label('wcf.poll.maxVotes')
+ ->minimum(1)
+ ->maximum(POLL_MAX_OPTIONS)
+ ->value(1);
+
+ $this->isChangeableField = BooleanFormField::create($id . '_isChangeable')
+ ->label('wcf.poll.isChangeable');
+
+ /** @var IPollHandler $pollHandler */
+ $pollHandler = null;
+ if ($this->objectType !== null) {
+ $pollHandler = $this->getObjectType()->getProcessor();
+ }
+
+ $this->isPublicField = BooleanFormField::create($id . '_isPublic')
+ ->label('wcf.poll.isPublic')
+ ->available($pollHandler !== null && $pollHandler->canStartPublicPoll());
+
+ $this->resultsRequireVoteField = BooleanFormField::create($id . '_resultsRequireVote')
+ ->label('wcf.poll.resultsRequireVote')
+ ->description('wcf.poll.resultsRequireVote.description');
+
+ $this->sortByVotesField = BooleanFormField::create($id . '_sortByVotes')
+ ->label('wcf.poll.sortByVotes');
+
+ $this->appendChildren([
+ $this->getQuestionField(),
+ $this->getOptionsField(),
+ $this->getEndTimeField(),
+ $this->getMaxVotesField(),
+ $this->getIsChangeableField(),
+ $this->getIsPublicField(),
+ $this->getResultsRequireVoteField(),
+ $this->getSortByVotesField()
+ ]);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\container\wysiwyg;
+use wcf\data\smiley\SmileyCache;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\container\TabFormContainer;
+use wcf\system\form\builder\container\TabTabMenuFormContainer;
+use wcf\system\form\builder\field\wysiwyg\WysiwygSmileyFormField;
+use wcf\system\form\builder\TWysiwygFormNode;
+use wcf\util\StringUtil;
+
+/**
+ * Represents the tab for the smiley-related fields below a WYSIWYG editor.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Container\Wysiwyg
+ * @since 5.2
+ */
+class WysiwygSmileyFormContainer extends TabTabMenuFormContainer {
+ use TWysiwygFormNode;
+
+ /**
+ * name of container template
+ * @var string
+ */
+ protected $templateName = '__wysiwygSmileyFormContainer';
+
+ /**
+ * Creates a new instance of `WysiwygSmileyFormContainer`.
+ */
+ public function __construct() {
+ $this->attribute('data-preselect', 'true')
+ ->attribute('data-collapsible', 'false')
+ ->useAnchors(false);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function populate() {
+ parent::populate();
+
+ $smileyCategories = SmileyCache::getInstance()->getCategories();
+
+ foreach ($smileyCategories as $smileyCategory) {
+ $smileyCategory->loadSmilies();
+ if (count($smileyCategory) > 0) {
+ $this->appendChild(
+ TabFormContainer::create($this->getId() . '_smileyCategoryTab' . $smileyCategory->categoryID)
+ ->label(StringUtil::encodeHTML($smileyCategory->getTitle()))
+ ->removeClass('tabMenuContent')
+ ->addClass('messageTabMenuContent')
+ ->appendChild(
+ FormContainer::create($this->getId() . '_smileyCategoryContainer' . $smileyCategory->categoryID)
+ ->removeClass('section')
+ ->appendChild(
+ WysiwygSmileyFormField::create($this->getId() . '_smileyCategory' . $smileyCategory->categoryID)
+ ->smilies(SmileyCache::getInstance()->getCategorySmilies($smileyCategory->categoryID ?: null))
+ )
+ )
+ );
+ }
+ }
+
+ if (count($this->children()) > 1) {
+ $this->addClass('messageTabMenu');
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\container\wysiwyg;
+use wcf\system\form\builder\container\TabMenuFormContainer;
+use wcf\system\form\builder\IFormChildNode;
+
+/**
+ * Represents a container whose children are tabs of a wysiwyg tab menu.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Container
+ * @since 5.2
+ */
+class WysiwygTabMenuFormContainer extends TabMenuFormContainer {
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = '__wysiwygTabMenuFormContainer';
+
+ /**
+ * Creates a new instance of `WysiwygTabMenuFormContainer`.
+ */
+ public function __construct() {
+ $this->removeClass('section')
+ ->removeClass('tabMenuContainer')
+ ->addClass('messageTabMenu');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function appendChild(IFormChildNode $child) {
+ $child->removeClass('tabMenuContent')
+ ->addClass('messageTabMenuContent');
+
+ return parent::appendChild($child);
+ }
+}
* @package WoltLabSuite\Core\System\Form\Builder\Field
* @since 5.2
*/
-class CaptchaFormField extends AbstractFormField implements IObjectTypeFormField {
+class CaptchaFormField extends AbstractFormField implements IObjectTypeFormNode {
use TDefaultIdFormField;
- use TObjectTypeFormField {
+ use TObjectTypeFormNode {
objectType as defaultObjectType;
}
+++ /dev/null
-<?php
-namespace wcf\system\form\builder\field;
-use wcf\data\object\type\ObjectType;
-use wcf\system\exception\InvalidObjectTypeException;
-
-/**
- * Represents a form field that relies on a specific object type.
- *
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package WoltLabSuite\Core\System\Form\Builder\Field
- * @since 5.2
- */
-interface IObjectTypeFormField {
- /**
- * Returns the object type.
- *
- * @return ObjectType object type
- *
- * @throws \BadMethodCallException if object type has not been set
- */
- public function getObjectType();
-
- /**
- * Sets the name of the object type and returns this field.
- *
- * @param string $objectType object type name
- * @return IObjectTypeFormField this field
- *
- * @throws \BadMethodCallException if object type has already been set
- * @throws \UnexpectedValueException if object type definition returned by `getObjectTypeDefinition()` is unknown
- * @throws InvalidObjectTypeException if given object type name is invalid
- */
- public function objectType($objectType);
-
- /**
- * Returns the name of the object type definition the set object type must be of.
- *
- * @return string name of object type's definition
- */
- public function getObjectTypeDefinition();
-}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\field;
+use wcf\data\object\type\ObjectType;
+use wcf\system\exception\InvalidObjectTypeException;
+
+/**
+ * Represents a form node that relies on a specific object type.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Field
+ * @since 5.2
+ */
+interface IObjectTypeFormNode {
+ /**
+ * Returns the object type.
+ *
+ * @return ObjectType object type
+ *
+ * @throws \BadMethodCallException if object type has not been set
+ */
+ public function getObjectType();
+
+ /**
+ * Sets the name of the object type and returns this field.
+ *
+ * @param string $objectType object type name
+ * @return IObjectTypeFormNode this field
+ *
+ * @throws \BadMethodCallException if object type has already been set
+ * @throws \UnexpectedValueException if object type definition returned by `getObjectTypeDefinition()` is unknown
+ * @throws InvalidObjectTypeException if given object type name is invalid
+ */
+ public function objectType($objectType);
+
+ /**
+ * Returns the name of the object type definition the set object type must be of.
+ *
+ * @return string name of object type's definition
+ */
+ public function getObjectTypeDefinition();
+}
+++ /dev/null
-<?php
-namespace wcf\system\form\builder\field;
-use wcf\data\object\type\ObjectType;
-use wcf\data\object\type\ObjectTypeCache;
-use wcf\system\exception\InvalidObjectTypeException;
-
-/**
- * Provides default implementations of `IObjectTypeFormField` methods.
- *
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package WoltLabSuite\Core\System\Form\Builder\Field
- * @since 5.2
- */
-trait TObjectTypeFormField {
- /**
- * object type
- * @var null|ObjectType
- */
- protected $objectType;
-
- /**
- * Returns the object type.
- *
- * @return ObjectType object type
- *
- * @throws \BadMethodCallException if object type has not been set
- */
- public function getObjectType() {
- if ($this->objectType === null) {
- throw new \BadMethodCallException("Object type has not been set.");
- }
-
- return $this->objectType;
- }
-
- /**
- * Sets the name of the object type and returns this field.
- *
- * @param string $objectType object type name
- * @return static this field
- *
- * @throws \BadMethodCallException if object type has already been set
- * @throws \UnexpectedValueException if object type definition returned by `getObjectTypeDefinition()` is unknown
- * @throws InvalidObjectTypeException if given object type name is invalid
- */
- public function objectType($objectType) {
- if ($this->objectType !== null) {
- throw new \BadMethodCallException("Object type has already been set.");
- }
-
- if (ObjectTypeCache::getInstance()->getDefinitionByName($this->getObjectTypeDefinition()) === null) {
- throw new \UnexpectedValueException("Unknown definition name '{$this->getObjectTypeDefinition()}'.");
- }
-
- $this->objectType = ObjectTypeCache::getInstance()->getObjectTypeByName($this->getObjectTypeDefinition(), $objectType);
- if ($this->objectType === null) {
- throw new InvalidObjectTypeException($objectType, $this->getObjectTypeDefinition());
- }
-
- return $this;
- }
-
- /**
- * Returns the name of the object type definition the set object type must be of.
- *
- * @return string name of object type's definition
- */
- abstract public function getObjectTypeDefinition();
-}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\field;
+use wcf\data\object\type\ObjectType;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\exception\InvalidObjectTypeException;
+
+/**
+ * Provides default implementations of `IObjectTypeFormNode` methods.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Field
+ * @since 5.2
+ */
+trait TObjectTypeFormNode {
+ /**
+ * object type
+ * @var null|ObjectType
+ */
+ protected $objectType;
+
+ /**
+ * Returns the object type.
+ *
+ * @return ObjectType object type
+ *
+ * @throws \BadMethodCallException if object type has not been set
+ */
+ public function getObjectType() {
+ if ($this->objectType === null) {
+ throw new \BadMethodCallException("Object type has not been set.");
+ }
+
+ return $this->objectType;
+ }
+
+ /**
+ * Sets the name of the object type and returns this field.
+ *
+ * @param string $objectType object type name
+ * @return static this field
+ *
+ * @throws \BadMethodCallException if object type has already been set
+ * @throws \UnexpectedValueException if object type definition returned by `getObjectTypeDefinition()` is unknown
+ * @throws InvalidObjectTypeException if given object type name is invalid
+ */
+ public function objectType($objectType) {
+ if ($this->objectType !== null) {
+ throw new \BadMethodCallException("Object type has already been set.");
+ }
+
+ if (ObjectTypeCache::getInstance()->getDefinitionByName($this->getObjectTypeDefinition()) === null) {
+ throw new \UnexpectedValueException("Unknown definition name '{$this->getObjectTypeDefinition()}'.");
+ }
+
+ $this->objectType = ObjectTypeCache::getInstance()->getObjectTypeByName($this->getObjectTypeDefinition(), $objectType);
+ if ($this->objectType === null) {
+ throw new InvalidObjectTypeException($objectType, $this->getObjectTypeDefinition());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the name of the object type definition the set object type must be of.
+ *
+ * @return string name of object type's definition
+ */
+ abstract public function getObjectTypeDefinition();
+}
+++ /dev/null
-<?php
-namespace wcf\system\form\builder\field;
-use wcf\system\form\builder\field\data\processor\CustomFormFieldDataProcessor;
-use wcf\system\form\builder\field\validation\FormFieldValidationError;
-use wcf\system\form\builder\IFormDocument;
-use wcf\system\html\input\HtmlInputProcessor;
-use wcf\util\StringUtil;
-
-/**
- * Implementation of a form field for wysiwyg editors.
- *
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package WoltLabSuite\Core\System\Form\Builder\Field
- * @since 5.2
- */
-class WysiwygFormField extends AbstractFormField implements IMaximumLengthFormField, IMinimumLengthFormField, IObjectTypeFormField {
- use TMaximumLengthFormField;
- use TMinimumLengthFormField;
- use TObjectTypeFormField;
-
- /**
- * identifier used to autosave the field value; if empty, autosave is disabled
- * @var string
- */
- protected $autosaveId = '';
-
- /**
- * last time the field has been edited; if `0`, the last edit time is unknown
- * @var int
- */
- protected $lastEditTime = 0;
-
- /**
- * input processor containing the wysiwyg text
- * @var HtmlInputProcessor
- */
- protected $htmlInputProcessor;
-
- /**
- * @inheritDoc
- */
- protected $templateName = '__wysiwygFormField';
-
- /**
- * Sets the identifier used to autosave the field value and returns this field.
- *
- * @param string $autosaveId identifier used to autosave field value
- * @return WysiwygFormField this field
- */
- public function autosaveId($autosaveId) {
- $this->autosaveId = $autosaveId;
-
- return $this;
- }
-
- /**
- * Returns the identifier used to autosave the field value. If autosave is disabled,
- * an empty string is returned.
- *
- * @return string
- */
- public function getAutosaveId() {
- return $this->autosaveId;
- }
-
- /**
- * @inheritDoc
- */
- public function getObjectTypeDefinition() {
- return 'com.woltlab.wcf.message';
- }
-
- /**
- * Returns the last time the field has been edited. If no last edit time has
- * been set, `0` is returned.
- *
- * @return int
- */
- public function getLastEditTime() {
- return $this->lastEditTime;
- }
-
- /**
- * @inheritDoc
- */
- public function hasSaveValue() {
- return false;
- }
-
- /**
- * Sets the last time this field has been edited and returns this field.
- *
- * @param int $lastEditTime last time field has been edited
- * @return WysiwygFormField this field
- */
- public function lastEditTime($lastEditTime) {
- $this->lastEditTime = $lastEditTime;
-
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function populate() {
- parent::populate();
-
- $this->getDocument()->getDataHandler()->add(new CustomFormFieldDataProcessor('wysiwyg', function(IFormDocument $document, array $parameters) {
- if ($this->checkDependencies()) {
- $parameters[$this->getObjectProperty() . '_htmlInputProcessor'] = $this->htmlInputProcessor;
- }
-
- return $parameters;
- }));
-
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function readValue() {
- if ($this->getDocument()->hasRequestData($this->getPrefixedId())) {
- $value = $this->getDocument()->getRequestData($this->getPrefixedId());
-
- if (is_string($value)) {
- $this->value = StringUtil::trim($value);
- }
- }
-
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function validate() {
- if ($this->isRequired() && $this->getValue() === '') {
- $this->addValidationError(new FormFieldValidationError('empty'));
- }
- else {
- $this->validateMinimumLength($this->getValue());
- $this->validateMaximumLength($this->getValue());
- }
-
- $this->htmlInputProcessor = new HtmlInputProcessor();
- $this->htmlInputProcessor->process($this->getValue(), $this->getObjectType()->objectType);
-
- parent::validate();
- }
-}
use wcf\system\acl\ACLHandler;
use wcf\system\form\builder\field\AbstractFormField;
use wcf\system\form\builder\field\data\processor\CustomFormFieldDataProcessor;
-use wcf\system\form\builder\field\IObjectTypeFormField;
-use wcf\system\form\builder\field\TObjectTypeFormField;
+use wcf\system\form\builder\field\IObjectTypeFormNode;
+use wcf\system\form\builder\field\TObjectTypeFormNode;
use wcf\system\form\builder\IFormDocument;
/**
* @package WoltLabSuite\Core\System\Form\Builder\Field\Acl
* @since 5.2
*/
-class AclFormField extends AbstractFormField implements IObjectTypeFormField {
- use TObjectTypeFormField;
+class AclFormField extends AbstractFormField implements IObjectTypeFormNode {
+ use TObjectTypeFormNode;
/**
* name of/filter for the name(s) of the shown acl option categories
--- /dev/null
+<?php
+namespace wcf\system\form\builder\field\poll;
+use wcf\data\poll\option\PollOption;
+use wcf\system\form\builder\field\AbstractFormField;
+use wcf\system\form\builder\TWysiwygFormNode;
+use wcf\util\ArrayUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Represents the form field to manage poll options/answers.
+ *
+ * This form field should not be used idenpendently but only via `WysiwygPollFormContainer`.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Field\Poll
+ * @since 5.2
+ */
+class PollOptionsFormField extends AbstractFormField {
+ use TWysiwygFormNode;
+
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = '__pollOptionsFormField';
+
+ /**
+ * @inheritDoc
+ */
+ protected $value = [];
+
+ /**
+ * Creates a new instance of `PollOptionsFormField`.
+ */
+ public function __construct() {
+ $this->label('wcf.poll.options')
+ ->description('wcf.poll.options.description')
+ ->addClass('pollOptionContainer');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function readValue() {
+ if ($this->getDocument()->hasRequestData($this->getPrefixedId()) && is_array($this->getDocument()->getRequestData($this->getPrefixedId()))) {
+ $value = array_slice(
+ ArrayUtil::trim($this->getDocument()->getRequestData($this->getPrefixedId())),
+ 0,
+ POLL_MAX_OPTIONS
+ );
+
+ $this->value = [];
+ foreach ($value as $showOrder => $option) {
+ list($optionID, $optionValue) = explode('_', $option, 2);
+ $this->value[$showOrder] = [
+ 'optionID' => intval($optionID),
+ 'optionValue' => StringUtil::trim($optionValue)
+ ];
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function value($value) {
+ $pollOptions = [];
+
+ foreach ($value as $pollOption) {
+ if ($pollOption instanceof PollOption) {
+ $pollOptions[] = [
+ 'optionID' => $pollOption->optionID,
+ 'optionValue' => $pollOption->optionValue
+ ];
+ }
+ else if (is_array($pollOption) && isset($pollOptions['optionID']) && isset($pollOptions['optionValue'])) {
+ $pollOptions[] = [
+ 'optionID' => $pollOptions['optionID'],
+ 'optionValue' => $pollOptions['optionValue']
+ ];
+ }
+ else {
+ throw new \InvalidArgumentException("Given value array contains invalid value of type " . gettype($pollOption) . ".");
+ }
+ }
+
+ return parent::value($value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validate() {
+ parent::validate();
+
+ // ensure maximum length that is already validated via JavaScript
+ foreach ($this->value as &$value) {
+ $value = mb_substr($value, 0, 255);
+ }
+ unset($value);
+ }
+}
use wcf\data\IStorableObject;
use wcf\system\form\builder\field\AbstractFormField;
use wcf\system\form\builder\field\data\processor\CustomFormFieldDataProcessor;
-use wcf\system\form\builder\field\IObjectTypeFormField;
+use wcf\system\form\builder\field\IObjectTypeFormNode;
use wcf\system\form\builder\field\TDefaultIdFormField;
-use wcf\system\form\builder\field\TObjectTypeFormField;
+use wcf\system\form\builder\field\TObjectTypeFormNode;
use wcf\system\form\builder\IFormDocument;
use wcf\system\tagging\TagEngine;
use wcf\util\ArrayUtil;
* @package WoltLabSuite\Core\System\Form\Builder\Field\Tag
* @since 5.2
*/
-class TagFormField extends AbstractFormField implements IObjectTypeFormField {
+class TagFormField extends AbstractFormField implements IObjectTypeFormNode {
use TDefaultIdFormField;
- use TObjectTypeFormField;
+ use TObjectTypeFormNode;
/**
* @inheritDoc
--- /dev/null
+<?php
+namespace wcf\system\form\builder\field\wysiwyg;
+use wcf\system\attachment\AttachmentHandler;
+use wcf\system\form\builder\field\AbstractFormField;
+use wcf\system\form\builder\field\data\processor\CustomFormFieldDataProcessor;
+use wcf\system\form\builder\IFormDocument;
+use wcf\system\form\builder\TWysiwygFormNode;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Represents the form field to manage attachments for a wysiwyg form container.
+ *
+ * If no attachment handler has been set, this field is not available.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Field\Wysiwyg
+ * @since 5.2
+ */
+class WysiwygAttachmentFormField extends AbstractFormField {
+ use TWysiwygFormNode;
+
+ /**
+ * attachment handler
+ * @var null|AttachmentHandler
+ */
+ protected $attachmentHandler;
+
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = '__wysiwygAttachmentFormField';
+
+ /**
+ * Creates a new instance of `WysiwygAttachmentFormField`.
+ */
+ public function __construct() {
+ $this->addClass('wide');
+ }
+
+ /**
+ * Sets the attachment handler object for the uploaded attachments. If `null` is given,
+ * the previously set attachment handler is unset.
+ *
+ * For the initial attachment handler set by this method, the temporary hashes will be
+ * automatically set by either reading them from the session variables if the form handles
+ * AJAX requests or by creating a new one. If the temporary hashes are read from session,
+ * the session variable will be unregistered afterwards.
+ *
+ * @param null|AttachmentHandler $attachmentHandler
+ * @return WysiwygAttachmentFormField
+ */
+ public function attachmentHandler(AttachmentHandler $attachmentHandler = null) {
+ if ($this->attachmentHandler === null && $attachmentHandler !== null) {
+ $tmpHash = StringUtil::getRandomID();
+ if ($this->getDocument()->isAjax()) {
+ $sessionTmpHash = WCF::getSession()->getVar('__wcfAttachmentTmpHash');
+ if ($sessionTmpHash !== null) {
+ $tmpHash = $sessionTmpHash;
+
+ WCF::getSession()->unregister('__wcfAttachmentTmpHash');
+ }
+ }
+
+ $attachmentHandler->setTmpHashes([$tmpHash]);
+ }
+
+ $this->attachmentHandler = $attachmentHandler;
+
+ if ($this->attachmentHandler !== null) {
+ $this->description('wcf.attachment.upload.limits', [
+ 'attachmentHandler' => $this->attachmentHandler
+ ]);
+ }
+ else {
+ $this->description();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the attachment handler object for the uploaded attachments or `null` if no attachment
+ * upload is supported.
+ *
+ * @return null|AttachmentHandler
+ */
+ public function getAttachmentHandler() {
+ return $this->attachmentHandler;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function hasSaveValue() {
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isAvailable() {
+ return parent::isAvailable() && $this->getAttachmentHandler() !== null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function populate() {
+ parent::populate();
+
+ $this->getDocument()->getDataHandler()->add(new CustomFormFieldDataProcessor($this->getId(), function(IFormDocument $document, array $parameters) {
+ if ($this->getAttachmentHandler() !== null) {
+ $parameters[$this->getWysiwygId() . '_attachmentHandler'] = $this->getAttachmentHandler();
+ }
+
+ return $parameters;
+ }));
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function readValue() {
+ if ($this->getDocument()->hasRequestData($this->getPrefixedId() . '_tmpHash')) {
+ $tmpHash = $this->getDocument()->getRequestData($this->getPrefixedId() . '_tmpHash');
+ if (is_string($tmpHash)) {
+ $this->getAttachmentHandler()->setTmpHashes([$tmpHash]);
+ }
+ else if (is_array($tmpHash)) {
+ $this->getAttachmentHandler()->setTmpHashes($tmpHash);
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\field\wysiwyg;
+use wcf\system\form\builder\field\AbstractFormField;
+use wcf\system\form\builder\field\data\processor\CustomFormFieldDataProcessor;
+use wcf\system\form\builder\field\IMaximumLengthFormField;
+use wcf\system\form\builder\field\IMinimumLengthFormField;
+use wcf\system\form\builder\field\IObjectTypeFormNode;
+use wcf\system\form\builder\field\TMaximumLengthFormField;
+use wcf\system\form\builder\field\TMinimumLengthFormField;
+use wcf\system\form\builder\field\TObjectTypeFormNode;
+use wcf\system\form\builder\field\validation\FormFieldValidationError;
+use wcf\system\form\builder\IFormDocument;
+use wcf\system\html\input\HtmlInputProcessor;
+use wcf\util\StringUtil;
+
+/**
+ * Implementation of a form field for wysiwyg editors.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Field
+ * @since 5.2
+ */
+class WysiwygFormField extends AbstractFormField implements IMaximumLengthFormField, IMinimumLengthFormField, IObjectTypeFormNode {
+ use TMaximumLengthFormField;
+ use TMinimumLengthFormField;
+ use TObjectTypeFormNode;
+
+ /**
+ * identifier used to autosave the field value; if empty, autosave is disabled
+ * @var string
+ */
+ protected $autosaveId = '';
+
+ /**
+ * input processor containing the wysiwyg text
+ * @var HtmlInputProcessor
+ */
+ protected $htmlInputProcessor;
+
+ /**
+ * last time the field has been edited; if `0`, the last edit time is unknown
+ * @var int
+ */
+ protected $lastEditTime = 0;
+
+ /**
+ * is `true` if this form field should support attachments, otherwise `false`
+ * @var boolean
+ */
+ protected $supportAttachments = false;
+
+ /**
+ * is `true` if this form field should support mentions, otherwise `false`
+ * @var boolean
+ */
+ protected $supportMentions = false;
+
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = '__wysiwygFormField';
+
+ /**
+ * Sets the identifier used to autosave the field value and returns this field.
+ *
+ * @param string $autosaveId identifier used to autosave field value
+ *
+ * @return WysiwygFormNode this field
+ */
+ public function autosaveId($autosaveId) {
+ $this->autosaveId = $autosaveId;
+
+ return $this;
+ }
+
+ /**
+ * Returns the identifier used to autosave the field value. If autosave is disabled,
+ * an empty string is returned.
+ *
+ * @return string
+ */
+ public function getAutosaveId() {
+ return $this->autosaveId;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getObjectTypeDefinition() {
+ return 'com.woltlab.wcf.message';
+ }
+
+ /**
+ * Returns the last time the field has been edited. If no last edit time has
+ * been set, `0` is returned.
+ *
+ * @return int
+ */
+ public function getLastEditTime() {
+ return $this->lastEditTime;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function hasSaveValue() {
+ return false;
+ }
+
+ /**
+ * Sets the last time this field has been edited and returns this field.
+ *
+ * @param int $lastEditTime last time field has been edited
+ *
+ * @return WysiwygFormNode this field
+ */
+ public function lastEditTime($lastEditTime) {
+ $this->lastEditTime = $lastEditTime;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function populate() {
+ parent::populate();
+
+ $this->getDocument()->getDataHandler()->add(new CustomFormFieldDataProcessor('wysiwyg', function(IFormDocument $document, array $parameters) {
+ if ($this->checkDependencies()) {
+ $parameters[$this->getObjectProperty() . '_htmlInputProcessor'] = $this->htmlInputProcessor;
+ }
+
+ return $parameters;
+ }));
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function readValue() {
+ if ($this->getDocument()->hasRequestData($this->getPrefixedId())) {
+ $value = $this->getDocument()->getRequestData($this->getPrefixedId());
+
+ if (is_string($value)) {
+ $this->value = StringUtil::trim($value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets if the form field supports attachments and returns this field.
+ *
+ * @param boolean $supportAttachments
+ *
+ * @return WysiwygFormNode
+ */
+ public function supportAttachments($supportAttachments = true) {
+ $this->supportAttachments = $supportAttachments;
+
+ return $this;
+ }
+
+ /**
+ * Sets if the form field supports mentions and returns this field.
+ *
+ * @param boolean $supportMentions
+ *
+ * @return WysiwygFormNode
+ */
+ public function supportMentions($supportMentions = true) {
+ $this->supportMentions = $supportMentions;
+
+ return $this;
+ }
+
+ /**
+ * Returns `true` if the form field supports attachments and returns `false` otherwise.
+ *
+ * Important: If this method returns `true`, it does not necessarily mean that attachment
+ * support will also work as that is the task of `WysiwygAttachmentFormField`. This method
+ * is primarily relevant to inform the JavaScript API that the field supports attachments
+ * so that the relevant editor plugin is loaded.
+ *
+ * By default, attachments are not supported.
+ *
+ * @return boolean
+ */
+ public function supportsAttachments() {
+ return $this->supportAttachments;
+ }
+
+ /**
+ * Returns `true` if the form field supports mentions and returns `false` otherwise.
+ *
+ * By default, mentions are not supported.
+ *
+ * @return boolean
+ */
+ public function supportsMentions() {
+ return $this->supportMentions;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validate() {
+ if ($this->isRequired() && $this->getValue() === '') {
+ $this->addValidationError(new FormFieldValidationError('empty'));
+ }
+ else {
+ $this->validateMinimumLength($this->getValue());
+ $this->validateMaximumLength($this->getValue());
+ }
+
+ $this->htmlInputProcessor = new HtmlInputProcessor();
+ $this->htmlInputProcessor->process($this->getValue(), $this->getObjectType()->objectType);
+
+ parent::validate();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\form\builder\field\wysiwyg;
+use wcf\data\smiley\Smiley;
+use wcf\system\form\builder\field\AbstractFormField;
+
+/**
+ * Implementation of a form field for the list smilies of a certain category used by a wysiwyg
+ * form container.
+ *
+ * This is no really a form field in that it does not read any data but only prints data.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Form\Builder\Field
+ * @since 5.2
+ */
+class WysiwygSmileyFormField extends AbstractFormField {
+ /**
+ * list of available smilies
+ * @var Smiley[]
+ */
+ protected $smilies = [];
+
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = '__wysiwygSmileyFormField';
+
+ /**
+ * Returns the list of available smilies.
+ *
+ * @return Smiley[]
+ */
+ public function getSmilies() {
+ return $this->smilies;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function hasSaveValue() {
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isAvailable() {
+ return parent::isAvailable() && !empty($this->smilies);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function readValue() {
+ // does nothing
+ }
+
+ /**
+ * Sets the list of available smilies.
+ *
+ * @param Smiley[] $smilies available smilies
+ * @return WysiwygSmileyFormField this form field
+ */
+ public function smilies(array $smilies) {
+ foreach ($smilies as $smiley) {
+ if (!is_object($smiley)) {
+ throw new \InvalidArgumentException("Given value array contains invalid value of type " . gettype($smiley) . ".");
+ }
+ else if (!($smiley instanceof Smiley)) {
+ throw new \InvalidArgumentException("Given value array contains invalid object of class " . get_class($smiley) . ".");
+ }
+ }
+
+ $this->smilies = $smilies;
+
+ return $this;
+ }
+}
/* attachments tab in editor */
.formAttachmentContent {
- > .formAttachmentList {
+ .formAttachmentList {
display: flex;
flex-wrap: wrap;
margin-left: 0 !important;
}
@include screen-md-up {
- > .formAttachmentList {
+ .formAttachmentList {
margin-right: -20px;
> li {
> dl {
margin-top: 0 !important;
+ }
+
+ > dl > dd > div,
+ .formAttachmentButtons {
+ align-items: center;
+ display: flex;
- > dd > div {
- align-items: center;
- display: flex;
+ > .button {
+ flex: 0 0 auto;
- > .button {
- flex: 0 0 auto;
-
- &:not(:first-child) {
- margin-left: 10px;
- }
- }
-
- & + small {
- margin-top: 10px !important;
+ &:not(:first-child) {
+ margin-left: 10px;
}
}
+
+ & + small {
+ margin-top: 10px !important;
+ }
}
}
> .messageTabMenuContent {
display: none;
+ &:not(.messageTabMenu) {
+ > nav.menu {
+ display: none;
+ }
+ }
+
&.active {
background-color: $wcfContentBackground;
display: block;
margin-top: 0;
}
+
+ > .section:first-child {
+ margin-top: 0;
+ }
}
// prevent double formatting with nested tab menus
> ul {
@include inlineList;
+ border: 0;
+
> li {
outline: 0;
width: 100%;
}
+.messageTabMenu > nav.tabMenu,
.messageTabMenuNavigation {
> ul {
background-color: $wcfContentBackground;
padding: 10px 20px;
@include userSelectNone;
+ @include wcfFontDefault;
@include screen-md-up {
> .icon {