Skip to content

Commit 4088072

Browse files
authored
Merge pull request #6277 from WoltLab/6.2-form-options
Form options
2 parents c840afe + ece77b9 commit 4088072

84 files changed

Lines changed: 3095 additions & 487 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

com.woltlab.wcf/objectType.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@
157157
<definitionname>com.woltlab.wcf.message</definitionname>
158158
<disallowedBBCodesPermission>user.signature.disallowedBBCodes</disallowedBBCodesPermission>
159159
</type>
160+
<type>
161+
<name>com.woltlab.wcf.genericFormOption</name>
162+
<definitionname>com.woltlab.wcf.message</definitionname>
163+
</type>
160164
<type>
161165
<name>com.woltlab.wcf.user.signature</name>
162166
<definitionname>com.woltlab.wcf.attachment.objectType</definitionname>
@@ -223,6 +227,11 @@
223227
<definitionname>com.woltlab.wcf.attachment.objectType</definitionname>
224228
<classname>wcf\system\attachment\ContactAttachmentObjectType</classname>
225229
</type>
230+
<type>
231+
<name>com.woltlab.wcf.contact.form</name>
232+
<definitionname>com.woltlab.wcf.file</definitionname>
233+
<classname>wcf\system\file\processor\ContactFormFileProcessor</classname>
234+
</type>
226235
<!-- page -->
227236
<type>
228237
<name>com.woltlab.wcf.page.recentActivityEvent</name>
@@ -1722,6 +1731,10 @@
17221731
<name>com.woltlab.wcf.lostPasswordForm</name>
17231732
<definitionname>com.woltlab.wcf.floodControl</definitionname>
17241733
</type>
1734+
<type>
1735+
<name>com.woltlab.wcf.contactForm</name>
1736+
<definitionname>com.woltlab.wcf.floodControl</definitionname>
1737+
</type>
17251738
<type>
17261739
<name>com.woltlab.wcf.search</name>
17271740
<definitionname>com.woltlab.wcf.floodControl</definitionname>

com.woltlab.wcf/package.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,11 @@
4949

5050
<instruction type="script">acp/install_com.woltlab.wcf_step2.php</instruction>
5151
</instructions>
52+
53+
<!--
54+
Required order of the following steps for the update to 6.2:
55+
<instruction type="database" run="standalone">acp/database/update_com.woltlab.wcf_62_step1.php</instruction>
56+
<instruction type="script">acp/update_com.woltlab.wcf_6.2_contactOptions.php</instruction>
57+
<instruction type="database" run="standalone">acp/database/update_com.woltlab.wcf_62_step2.php</instruction>
58+
-->
5259
</package>
Lines changed: 1 addition & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,5 @@
11
{include file='header'}
22

3-
{include file='shared_formError'}
4-
5-
<form method="post" action="{link controller='Contact'}{/link}">
6-
<section class="section">
7-
<h2 class="sectionTitle">{lang}wcf.contact.sender.information{/lang}</h2>
8-
9-
<dl{if $errorField == 'name'} class="formError"{/if}>
10-
<dt><label for="name">{lang}wcf.contact.sender{/lang}</label> <span class="customOptionRequired">*</span></dt>
11-
<dd>
12-
<input type="text" id="name" name="name" value="{$name}" required class="long">
13-
{if $errorField == 'name'}
14-
<small class="innerError">
15-
{if $errorType == 'empty'}
16-
{lang}wcf.global.form.error.empty{/lang}
17-
{else}
18-
{lang}wcf.contact.sender.error.{@$errorType}{/lang}
19-
{/if}
20-
</small>
21-
{/if}
22-
</dd>
23-
</dl>
24-
25-
<dl{if $errorField == 'email'} class="formError"{/if}>
26-
<dt><label for="email">{lang}wcf.user.email{/lang}</label> <span class="customOptionRequired">*</span></dt>
27-
<dd>
28-
<input type="email" id="email" name="email" value="{$email}" required class="medium">
29-
{if $errorField == 'email'}
30-
<small class="innerError">
31-
{if $errorType == 'empty'}
32-
{lang}wcf.global.form.error.empty{/lang}
33-
{else}
34-
{lang}wcf.user.email.error.{@$errorType}{/lang}
35-
{/if}
36-
</small>
37-
{/if}
38-
</dd>
39-
</dl>
40-
41-
{event name='informationFields'}
42-
</section>
43-
44-
<section class="section">
45-
<h2 class="sectionTitle">{lang}wcf.contact.data{/lang}</h2>
46-
47-
{if $recipientList|count > 1}
48-
<dl{if $errorField == 'recipientID'} class="formError"{/if}>
49-
<dt><label for="recipientID">{lang}wcf.contact.recipientID{/lang}</label> <span class="customOptionRequired">*</span></dt>
50-
<dd>
51-
<select name="recipientID" id="recipientID" required>
52-
<option value="">{lang}wcf.global.noSelection{/lang}</option>
53-
{foreach from=$recipientList item=recipient}
54-
<option value="{$recipient->recipientID}"{if $recipient->recipientID == $recipientID} selected{/if}>{$recipient}</option>
55-
{/foreach}
56-
</select>
57-
{if $errorField == 'recipientID'}
58-
<small class="innerError">
59-
{if $errorType == 'empty'}
60-
{lang}wcf.global.form.error.empty{/lang}
61-
{else}
62-
{lang}wcf.contact.recipientID.error.{@$errorType}{/lang}
63-
{/if}
64-
</small>
65-
{/if}
66-
</dd>
67-
</dl>
68-
{/if}
69-
70-
{include file='customOptionFieldList'}
71-
72-
{event name='optionFields'}
73-
74-
{if CONTACT_FORM_ENABLE_ATTACHMENTS && !$attachmentHandler|empty && $attachmentHandler->canUpload()}
75-
<div class="contactFormAttachments">
76-
{include file='shared_messageFormAttachments' wysiwygSelector=''}
77-
</div>
78-
{/if}
79-
</section>
80-
81-
{event name='sections'}
82-
83-
{include file='shared_captcha'}
84-
85-
<div class="formSubmit">
86-
<input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
87-
{csrfToken}
88-
</div>
89-
</form>
90-
91-
<p class="formFieldRequiredNotice">
92-
<span class="formFieldRequired">*</span>
93-
{lang}wcf.global.form.required{/lang}
94-
</p>
3+
{unsafe:$form->getHtml()}
954

965
{include file='footer'}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<input type="hidden" {*
2+
*}id="{$field->getPrefixedId()}" {*
3+
*}name="{$field->getPrefixedId()}" {*
4+
*}value="{$field->getValue()}"{*
5+
*}>
6+
7+
<script data-relocate="true">
8+
require(['WoltLabSuite/Core/Form/Builder/Field/SelectOptions'], ({ setup }) => {
9+
{jsphrase name='wcf.form.selectOptions.key'}
10+
{jsphrase name='wcf.form.selectOptions.value'}
11+
12+
const availableLanguages = { {implode from=$availableLanguages key=languageID item=languageName}{$languageID}: '{unsafe:$languageName|encodeJS}'{/implode} };
13+
14+
setup(document.getElementById('{unsafe:$field->getPrefixedId()|encodeJS}'), availableLanguages);
15+
});
16+
</script>
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { identify } from "WoltLabSuite/Core/Dom/Util";
2+
import { getPhrase } from "WoltLabSuite/Core/Language";
3+
import { getValues, init as initI18n } from "WoltLabSuite/Core/Language/Input";
4+
import Sortable from "sortablejs";
5+
6+
type Data = {
7+
key: string;
8+
value: Record<string, string>;
9+
};
10+
11+
type Languages = Record<string, string>;
12+
13+
let _languages: Languages;
14+
15+
export function setup(formField: HTMLInputElement, languages: Languages): void {
16+
_languages = languages;
17+
18+
const ul = createUi(formField);
19+
20+
formField.form?.addEventListener("submit", () => {
21+
setHiddenValue(formField);
22+
});
23+
24+
new Sortable(ul, {
25+
direction: "vertical",
26+
animation: 150,
27+
fallbackOnBody: true,
28+
draggable: "li",
29+
handle: ".selectOptionsListItem__handle",
30+
});
31+
}
32+
33+
function createUi(formField: HTMLInputElement): HTMLUListElement {
34+
const ul = document.createElement("ul");
35+
ul.classList.add("selectOptionsList");
36+
formField.parentElement?.append(ul);
37+
38+
if (formField.value) {
39+
const data = JSON.parse(formField.value) as Data[];
40+
data.forEach((option) => {
41+
createRow(ul, option);
42+
});
43+
} else {
44+
createRow(ul);
45+
}
46+
47+
return ul;
48+
}
49+
50+
function createRow(ul: HTMLUListElement, option?: Data, autoFocus: boolean = false): void {
51+
const li = document.createElement("li");
52+
li.classList.add("selectOptionsListItem");
53+
ul.append(li);
54+
55+
const addButton = getAddButton();
56+
addButton.addEventListener("click", () => {
57+
createRow(ul, undefined, true);
58+
});
59+
60+
const deleteButton = getDeleteButton();
61+
deleteButton.addEventListener("click", () => {
62+
li.remove();
63+
64+
if (!ul.childElementCount) {
65+
createRow(ul);
66+
}
67+
});
68+
69+
const keyInput = getKeyInput();
70+
keyInput.addEventListener("keydown", (event) => {
71+
if (event.key === "Enter") {
72+
event.preventDefault();
73+
createRow(ul, undefined, true);
74+
}
75+
});
76+
keyInput.value = option ? option.key : "";
77+
78+
const equalsIcon = document.createElement("fa-icon");
79+
equalsIcon.setIcon("equals");
80+
81+
const valueInput = getValueInput();
82+
valueInput.addEventListener("keydown", (event) => {
83+
if (event.key === "Enter") {
84+
event.preventDefault();
85+
createRow(ul, undefined, true);
86+
}
87+
});
88+
89+
li.append(getSortableHandle(), addButton, deleteButton, keyInput, equalsIcon, valueInput);
90+
91+
const hasI18nValues = option && !Object.hasOwn(option.value, 0);
92+
93+
initI18n(identify(valueInput), hasI18nValues ? option.value : {}, _languages, false);
94+
95+
if (!hasI18nValues) {
96+
valueInput.value = option?.value[0] ?? "";
97+
}
98+
99+
if (autoFocus) {
100+
keyInput.focus();
101+
}
102+
}
103+
104+
function getAddButton(): HTMLButtonElement {
105+
const addIcon = document.createElement("fa-icon");
106+
addIcon.setIcon("plus");
107+
108+
const addButton = document.createElement("button");
109+
addButton.type = "button";
110+
addButton.append(addIcon);
111+
addButton.classList.add("jsTooltip");
112+
addButton.title = getPhrase("wcf.global.button.add");
113+
114+
return addButton;
115+
}
116+
117+
function getDeleteButton(): HTMLButtonElement {
118+
const deleteIcon = document.createElement("fa-icon");
119+
deleteIcon.setIcon("xmark");
120+
121+
const deleteButton = document.createElement("button");
122+
deleteButton.type = "button";
123+
deleteButton.append(deleteIcon);
124+
deleteButton.classList.add("jsTooltip");
125+
deleteButton.title = getPhrase("wcf.global.button.delete");
126+
127+
return deleteButton;
128+
}
129+
130+
function getKeyInput(): HTMLInputElement {
131+
const keyInput = document.createElement("input");
132+
keyInput.classList.add("selectOptionsListItem__key");
133+
keyInput.placeholder = getPhrase("wcf.form.selectOptions.key");
134+
keyInput.type = "text";
135+
keyInput.required = true;
136+
137+
return keyInput;
138+
}
139+
140+
function getValueInput(): HTMLInputElement {
141+
const valueInput = document.createElement("input");
142+
valueInput.classList.add("selectOptionsListItem__value");
143+
valueInput.placeholder = getPhrase("wcf.form.selectOptions.value");
144+
valueInput.type = "text";
145+
valueInput.required = true;
146+
147+
return valueInput;
148+
}
149+
150+
function getSortableHandle(): HTMLElement {
151+
const icon = document.createElement("fa-icon");
152+
icon.setIcon("up-down");
153+
const handle = document.createElement("span");
154+
handle.append(icon);
155+
handle.classList.add("selectOptionsListItem__handle");
156+
157+
return handle;
158+
}
159+
160+
function setHiddenValue(formField: HTMLInputElement): void {
161+
const data: Data[] = [];
162+
163+
formField.parentElement?.querySelectorAll(".selectOptionsListItem").forEach((li) => {
164+
const key = li.querySelector<HTMLInputElement>(".selectOptionsListItem__key")!.value;
165+
const valueInput = li.querySelector<HTMLInputElement>(".selectOptionsListItem__value")!;
166+
167+
data.push({
168+
key,
169+
value: Object.fromEntries(getValues(valueInput.id)),
170+
});
171+
});
172+
173+
formField.value = JSON.stringify(data);
174+
}

wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2.php renamed to wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2_step1.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
*/
1010

1111
use wcf\system\database\table\column\IntDatabaseTableColumn;
12+
use wcf\system\database\table\column\MediumtextDatabaseTableColumn;
13+
use wcf\system\database\table\column\TextDatabaseTableColumn;
14+
use wcf\system\database\table\column\TinyintDatabaseTableColumn;
1215
use wcf\system\database\table\index\DatabaseTableForeignKey;
1316
use wcf\system\database\table\PartialDatabaseTable;
1417

@@ -46,5 +49,13 @@
4649
->referencedTable('wcf1_file')
4750
->referencedColumns(['fileID'])
4851
->onDelete('SET NULL'),
49-
])
52+
]),
53+
PartialDatabaseTable::create('wcf1_contact_option')
54+
->columns([
55+
MediumtextDatabaseTableColumn::create('configuration'),
56+
]),
57+
PartialDatabaseTable::create('wcf1_file')
58+
->columns([
59+
IntDatabaseTableColumn::create('uploadTime'),
60+
]),
5061
];
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/**
4+
* Updates the database layout during the update from 6.1 to 6.2.
5+
*
6+
* @author Olaf Braun
7+
* @copyright 2001-2024 WoltLab GmbH
8+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
9+
*/
10+
11+
use wcf\system\database\table\column\MediumtextDatabaseTableColumn;
12+
use wcf\system\database\table\column\TextDatabaseTableColumn;
13+
use wcf\system\database\table\column\TinyintDatabaseTableColumn;
14+
use wcf\system\database\table\PartialDatabaseTable;
15+
16+
return [
17+
PartialDatabaseTable::create('wcf1_contact_option')
18+
->columns([
19+
MediumtextDatabaseTableColumn::create('defaultValue')
20+
->drop(),
21+
TextDatabaseTableColumn::create('validationPattern')
22+
->drop(),
23+
MediumtextDatabaseTableColumn::create('selectOptions')
24+
->drop(),
25+
TinyintDatabaseTableColumn::create('required')
26+
->drop(),
27+
]),
28+
];

0 commit comments

Comments
 (0)