<title language="en">Privacy Policy</title>
<page>com.woltlab.wcf.PrivacyPolicy</page>
</item>
+ <item identifier="com.woltlab.wcf.Contact">
+ <menu>com.woltlab.wcf.FooterMenu</menu>
+ <title language="de">Kontakt</title>
+ <title language="en">Contact</title>
+ <page>com.woltlab.wcf.Contact</page>
+ </item>
</import>
</data>
<requireObjectID>1</requireObjectID>
<allowSpidersToIndex>1</allowSpidersToIndex>
</page>
+ <page identifier="com.woltlab.wcf.Contact">
+ <pageType>system</pageType>
+ <controller>wcf\form\ContactForm</controller>
+ <name language="de">Kontakt-Formular</name>
+ <name language="en">Contact Form</name>
+ <options>module_contact_form</options>
+ <allowSpidersToIndex>0</allowSpidersToIndex>
+
+ <content language="en">
+ <title>Contact</title>
+ </content>
+ <content language="de">
+ <title>Kontakt</title>
+ </content>
+ </page>
<!-- static -->
<page identifier="com.woltlab.wcf.Dashboard">
--- /dev/null
+{include file='header'}
+
+{include file='formError'}
+
+<form method="post" action="{link controller='Contact'}{/link}">
+ <section class="section">
+ <h2 class="sectionTitle">{lang}wcf.contact.sender.information{/lang}</h2>
+
+ <dl{if $errorField == 'name'} class="formError"{/if}>
+ <dt><label for="name">{lang}wcf.contact.sender{/lang}</label></dt>
+ <dd>
+ <input type="text" id="name" name="name" value="{$name}" required class="long">
+ {if $errorField == 'name'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.contact.sender.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'email'} class="formError"{/if}>
+ <dt><label for="email">{lang}wcf.user.email{/lang}</label></dt>
+ <dd>
+ <input type="email" id="email" name="email" value="{$email}" required class="medium">
+ {if $errorField == 'email'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.user.email.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='informationFields'}
+ </section>
+
+ <section class="section">
+ <h2 class="sectionTitle">{lang}wcf.contact.data{/lang}</h2>
+
+ {if $recipientList|count > 1}
+ <dl{if $errorField == 'recipientID'} class="formError"{/if}>
+ <dt><label for="recipientID">{lang}wcf.contact.recipientID{/lang}</label></dt>
+ <dd>
+ <select name="recipientID" id="recipientID">
+ <option value="">{lang}wcf.global.noSelection{/lang}</option>
+ {foreach from=$recipientList item=recipient}
+ <option value="{@$recipient->recipientID}">{$recipient}</option>
+ {/foreach}
+ </select>
+ {if $errorField == 'recipientID'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.contact.recipientID.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {/if}
+
+ {include file='customOptionFieldList'}
+
+ {event name='optionFields'}
+ </section>
+
+ {event name='sections'}
+
+ {include file='captcha' supportsAsyncCaptcha=true}
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+ {@SECURITY_TOKEN_INPUT_TAG}
+ </div>
+
+ <div class="section">
+ <p><span class="customOptionRequired">*</span> {lang}wcf.contact.options.required{/lang}</p>
+ </div>
+</form>
+
+{include file='footer'}
--- /dev/null
+{foreach from=$options item=optionData}
+ {assign var=option value=$optionData[object]}
+ <dl class="{if $errorType|is_array && $errorType[$option->optionName]|isset} formError{/if}">
+ <dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}><label for="{$option->optionName}">{lang}{$option->optionTitle}{/lang}</label>{if $option->required} <span class="customOptionRequired">*</span>{/if}</dt>
+ <dd>{@$optionData[html]}
+ <small>{lang __optional=true}{$option->optionDescription}{/lang}</small>
+
+ {if $errorType|is_array && $errorType[$option->optionName]|isset}
+ <small class="innerError">
+ {if $errorType[$option->optionName] == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.acp.customOption.error.{$errorType[$option->optionName]}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+{/foreach}
--- /dev/null
+{if $mimeType === 'text/plain'}
+{capture assign='content'}{lang}wcf.contact.mail.plaintext{/lang}{/capture}
+{include file='email_plaintext'}
+{else}
+ {capture assign='content'}
+ {lang}wcf.contact.mail.html{/lang}
+ {/capture}
+ {include file='email_html'}
+{/if}
<?php
namespace wcf\data\contact\option;
+use wcf\data\contact\recipient\ContactRecipient;
use wcf\data\custom\option\CustomOptionAction;
+use wcf\system\email\Email;
+use wcf\system\email\Mailbox;
+use wcf\system\email\mime\MimePartFacade;
+use wcf\system\email\mime\RecipientAwareTextMimePart;
+use wcf\system\language\LanguageFactory;
+use wcf\system\mail\Mail;
+use wcf\system\option\ContactOptionHandler;
/**
* Executes contact option related actions.
* @inheritDoc
*/
protected $permissionsUpdate = ['admin.contact.canManageContactForm'];
+
+ /**
+ * Sends an email to the selected recipient.
+ */
+ public function send() {
+ $defaultLanguage = LanguageFactory::getInstance()->getDefaultLanguage();
+
+ $recipient = new ContactRecipient($this->parameters['recipientID']);
+ /** @var ContactOptionHandler $optionHandler */
+ $optionHandler = $this->parameters['optionHandler'];
+
+ $options = [];
+ foreach ($optionHandler->getOptions() as $option) {
+ /** @var ContactOption $object */
+ $object = $option['object'];
+ $options[] = [
+ 'isMessage' => $object->isMessage(),
+ 'title' => $object->getLocalizedName($defaultLanguage),
+ 'value' => $object->getFormattedOptionValue()
+ ];
+ }
+
+ // build message data
+ $messageData = [
+ 'options' => $options,
+ 'recipient' => $recipient,
+ 'name' => $this->parameters['name']
+ ];
+
+ // build mail
+ $email = new Email();
+ $email->addRecipient(new Mailbox($recipient->email));
+ $email->setSubject($defaultLanguage->get('wcf.contact.mail.subject'));
+ $email->setBody(new MimePartFacade([
+ new RecipientAwareTextMimePart('text/html', 'email_contact', 'wcf', $messageData),
+ new RecipientAwareTextMimePart('text/plain', 'email_contact', 'wcf', $messageData)
+ ]));
+
+ // add reply-to tag
+ $email->setReplyTo(new Mailbox($this->parameters['email']));
+
+ // send mail
+ $email->send();
+ }
}
<?php
namespace wcf\data\contact\option;
use wcf\data\custom\option\CustomOptionEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\builder\ContactOptionCacheBuilder;
/**
* Provides functions to edit contact recipients.
* @method ContactOption getDecoratedObject()
* @mixin ContactOption
*/
-class ContactOptionEditor extends CustomOptionEditor {
+class ContactOptionEditor extends CustomOptionEditor implements IEditableCachedObject {
/**
* @inheritDoc
*/
protected static $baseClass = ContactOption::class;
+
+ /**
+ * @inheritDoc
+ */
+ public static function resetCache() {
+ ContactOptionCacheBuilder::getInstance()->reset();
+ }
}
<?php
namespace wcf\data\contact\recipient;
use wcf\data\DatabaseObject;
+use wcf\system\WCF;
/**
* Represents a contact recipient.
* @inheritDoc
*/
public function __toString() {
- return $this->name;
+ return WCF::getLanguage()->get($this->name);
}
/**
<?php
namespace wcf\data\custom\option;
+use wcf\data\language\Language;
use wcf\data\option\Option;
use wcf\system\bbcode\MessageParser;
use wcf\system\bbcode\SimpleMessageParser;
$this->optionValue = $value;
}
+ /**
+ * Attempts to return the localized option name.
+ *
+ * @param Language $language
+ * @return string
+ */
+ public function getLocalizedName(Language $language) {
+ if (preg_match('~^wcf\.contact\.option\d+$~', $this->optionTitle)) {
+ return $language->get($this->optionTitle);
+ }
+
+ return $this->optionTitle;
+ }
+
/**
* Returns the formatted value of this option.
*
public function getFormattedOptionValue() {
switch ($this->optionType) {
case 'boolean':
- return WCF::getLanguage()->get('wcf.acp.option.optionType.boolean.'.($this->optionValue ? 'yes' : 'no'));
+ return WCF::getLanguage()->get('wcf.acp.customOption.optionType.boolean.'.($this->optionValue ? 'yes' : 'no'));
case 'date':
$year = $month = $day = 0;
public function canDelete() {
return !$this->originIsSystem;
}
+
+ /**
+ * Returns true if this option represents a message-type value.
+ *
+ * @return boolean
+ */
+ public function isMessage() {
+ return ($this->optionType === 'textarea' || $this->optionType === 'message');
+ }
}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\contact\option\ContactOptionAction;
+use wcf\data\contact\recipient\ContactRecipientList;
+use wcf\system\email\Mailbox;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\option\ContactOptionHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Customizable contact form with selectable recipients.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Form
+ */
+class ContactForm extends AbstractCaptchaForm {
+ public $email = '';
+
+ public $name = '';
+
+ /**
+ * @inheritDoc
+ */
+ public $neededModules = ['MODULE_CONTACT_FORM'];
+
+ /**
+ * @var ContactOptionHandler
+ */
+ public $optionHandler;
+
+ /**
+ * recipient id
+ * @var integer
+ */
+ public $recipientID = 0;
+
+ /**
+ * @var ContactRecipientList
+ */
+ public $recipientList;
+
+ /**
+ * @inheritDoc
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ $this->optionHandler = new ContactOptionHandler(false);
+ $this->optionHandler->init();
+
+ $this->recipientList = new ContactRecipientList();
+ $this->recipientList->getConditionBuilder()->add("contact_recipient.isDisabled = ?", [0]);
+ $this->recipientList->readObjects();
+
+ if (count($this->recipientList) < 0) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ $this->optionHandler->readUserInput($_POST);
+
+ if (isset($_POST['email'])) $this->email = StringUtil::trim($_POST['email']);
+ if (isset($_POST['name'])) $this->name = StringUtil::trim($_POST['name']);
+ if (isset($_POST['recipientID'])) $this->recipientID = intval($_POST['recipientID']);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validate() {
+ // validate file options
+ $optionHandlerErrors = $this->optionHandler->validate();
+
+ parent::validate();
+
+ if (!empty($optionHandlerErrors)) {
+ throw new UserInputException('options', $optionHandlerErrors);
+ }
+
+ if (empty($this->email)) {
+ throw new UserInputException('email');
+ }
+ else {
+ try {
+ new Mailbox($this->email);
+ }
+ catch (\DomainException $e) {
+ throw new UserInputException('email', 'invalid');
+ }
+ }
+
+ if (empty($this->name)) {
+ throw new UserInputException('name');
+ }
+
+ $recipients = $this->recipientList->getObjects();
+ if (count($recipients) === 1) {
+ $this->recipientID = reset($recipients)->recipientID;
+ }
+ else {
+ if (!$this->recipientID) {
+ throw new UserInputException('recipientID');
+ }
+
+ $isValid = false;
+ foreach ($recipients as $recipient) {
+ if ($this->recipientID == $recipient->recipientID) {
+ $isValid = true;
+ break;
+ }
+ }
+
+ if (!$isValid) {
+ throw new UserInputException('recipientID', 'invalid');
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST) && WCF::getUser()->userID) {
+ $this->email = WCF::getUser()->email;
+ $this->name = WCF::getUser()->username;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function save() {
+ parent::save();
+
+ $this->objectAction = new ContactOptionAction([], 'send', [
+ 'email' => $this->email,
+ 'name' => $this->name,
+ 'optionHandler' => $this->optionHandler,
+ 'recipientID' => $this->recipientID
+ ]);
+ $this->objectAction->executeAction();
+
+ // call saved event
+ $this->saved();
+
+ HeaderUtil::delayedRedirect(
+ LinkHandler::getInstance()->getLink(''),
+ WCF::getLanguage()->get('wcf.contact.success')
+ );
+ exit;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign([
+ 'email' => $this->email,
+ 'name' => $this->name,
+ 'options' => $this->optionHandler->getOptions(),
+ 'recipientList' => $this->recipientList
+ ]);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\contact\option\ContactOptionList;
+
+/**
+ * Caches contact options.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Cache\Builder
+ */
+class ContactOptionCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @inheritDoc
+ */
+ public function rebuild(array $parameters) {
+ $list = new ContactOptionList();
+ $list->sqlSelects = "CONCAT('contactOption', CAST(contact_option.optionID AS CHAR)) AS optionName";
+ $list->readObjects();
+
+ return $list->getObjects();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option;
+use wcf\system\cache\builder\ContactOptionCacheBuilder;
+
+class ContactOptionHandler extends CustomOptionHandler {
+ /**
+ * @inheritDoc
+ */
+ protected function readCache() {
+ $this->cachedOptions = ContactOptionCacheBuilder::getInstance()->getData();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option;
+use wcf\data\custom\option\CustomOption;
+use wcf\data\option\Option;
+use wcf\system\exception\NotImplementedException;
+use wcf\system\exception\UserInputException;
+
+abstract class CustomOptionHandler extends OptionHandler {
+ /**
+ * Gets all options and option categories from cache.
+ */
+ protected function readCache() {
+ throw new NotImplementedException();
+
+ $this->cachedOptions = FileOptionCacheBuilder::getInstance()->getData();
+ }
+
+ /**
+ * Initializes active options.
+ */
+ public function init() {
+ if (!$this->didInit) {
+ // get active options
+ foreach ($this->cachedOptions as $option) {
+ if ($this->checkOption($option)) {
+ $this->options[$option->optionName] = $option;
+ }
+ }
+
+ // mark options as initialized
+ $this->didInit = true;
+ }
+ }
+
+ /**
+ * Returns the parsed options.
+ *
+ * @return array
+ */
+ public function getOptions() {
+ $parsedOptions = [];
+ foreach ($this->options as $option) {
+ $parsedOptions[] = $this->getOption($option->optionName);
+ }
+
+ return $parsedOptions;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function readData() {
+ /** @var CustomOption $option */
+ foreach ($this->options as $option) {
+ if (!isset($this->optionValues[$option->optionName])) {
+ $this->optionValues[$option->optionName] = $option->defaultValue;
+ }
+ }
+ }
+
+ /**
+ * Resets the option values.
+ */
+ public function resetOptionValues() {
+ $this->optionValues = [];
+ }
+
+ /**
+ * Returns the option values.
+ *
+ * @return array
+ */
+ public function getOptionValues() {
+ return $this->optionValues;
+ }
+
+ /**
+ * Sets the option values.
+ *
+ * @param array $values
+ */
+ public function setOptionValues(array $values) {
+ $this->optionValues = $values;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getOption($optionName) {
+ $optionData = parent::getOption($optionName);
+
+ if (isset($this->optionValues[$optionName])) {
+ /** @noinspection PhpUndefinedMethodInspection */
+ $optionData['object']->setOptionValue($this->optionValues[$optionName]);
+ }
+
+ return $optionData;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function validateOption(Option $option) {
+ /** @var CustomOption $option */
+
+ parent::validateOption($option);
+
+ if ($option->required && $option->optionType != 'boolean' && empty($this->optionValues[$option->optionName])) {
+ throw new UserInputException($option->optionName);
+ }
+ }
+}
width: 100%;
}
}
+
+.customOptionRequired {
+ color: rgba(204, 0, 1, 1) !important;
+}
</category>
<category name="wcf.contact">
+ <item name="wcf.contact.data"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Anfrage]]></item>
+ <item name="wcf.contact.mail.subject"><![CDATA[Neue Nachricht über das Kontakt-Formular]]></item>
+ <item name="wcf.contact.mail.plaintext"><![CDATA[Hallo,
+
+„{@$name}“ hat {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} über das Kontakt-Formular auf der Website {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}] folgende Nachricht gesandt:
+
+{foreach from=$options item=option}
+{@$option['title']}:{if !$option['isMessage']} {@$option['value']}{else}
+{@$option['value']}{/if}
+{/foreach}]]></item>
+ <item name="wcf.contact.mail.html"><![CDATA[<h2>Hallo,</h2>
+
+<p>„{$name}“ hat {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} über das Kontakt-Formular auf Website <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> folgende Nachricht gesandt:</p>
+
+{foreach from=$options item=option}
+<p><strong>{@$option['title']}:</strong>{if !$option['isMessage']} {@$option['value']}{else}
+{@$option['value']|newlineToBreak}{/if}</p>
+{/foreach}]]></item>
<item name="wcf.contact.option1"><![CDATA[Betreff]]></item>
<item name="wcf.contact.optionDescription1"><![CDATA[Kurze, prägnante Beschreibung der Anfrage.]]></item>
<item name="wcf.contact.option2"><![CDATA[Nachricht]]></item>
<item name="wcf.contact.recipient.name1"><![CDATA[Administrator]]></item>
+ <item name="wcf.contact.sender"><![CDATA[Absender]]></item>
+ <item name="wcf.contact.sender.information"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Angaben]]></item>
+ <item name="wcf.contact.success"><![CDATA[Ihre Nachricht wurde erfolgreich versandt.]]></item>
</category>
<category name="wcf.date">
</category>
<category name="wcf.contact">
+ <item name="wcf.contact.data"><![CDATA[Your Data]]></item>
+ <item name="wcf.contact.mail.subject"><![CDATA[Neue Nachricht über das Kontakt-Formular]]></item>
+ <item name="wcf.contact.mail.plaintext"><![CDATA[Hello,
+
+„{@$name}“ sent you a message on {@PAGE_TITLE|language} via the contact form:
+
+{foreach from=$options item=option}
+{@$option['title']}:{if !$option['isMessage']} {@$option['value']}{else}
+{@$option['value']}{/if}
+{/foreach}]]></item>
+ <item name="wcf.contact.mail.html"><![CDATA[<h2>Hello,</h2>
+
+<p>„{$name}“ sent you a message on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}</a> via the contact form:</p>
+
+{foreach from=$options item=option}
+<p><strong>{@$option['title']}:</strong>{if !$option['isMessage']} {@$option['value']}{else}
+{@$option['value']|newlineToBreak}{/if}</p>
+{/foreach}]]></item>
<item name="wcf.contact.option1"><![CDATA[Subject]]></item>
<item name="wcf.contact.optionDescription1"><![CDATA[Short and precise description of your inquiry.]]></item>
<item name="wcf.contact.option2"><![CDATA[Message]]></item>
<item name="wcf.contact.recipient.name1"><![CDATA[Administrator]]></item>
+ <item name="wcf.contact.sender"><![CDATA[Sender]]></item>
+ <item name="wcf.contact.sender.information"><![CDATA[Your Inquiry]]></item>
+ <item name="wcf.contact.success"><![CDATA[Message has been sent.]]></item>
+ </category>
+
+ <category name="wcf.contact">
+ <item name="wcf.contact.option1"><![CDATA[Subject]]></item>
+ <item name="wcf.contact.optionDescription1"><![CDATA[]]></item>
+ <item name="wcf.contact.option2"><![CDATA[Message]]></item>
+ <item name="wcf.contact.recipient.name1"><![CDATA[Administrator]]></item>
</category>
<category name="wcf.acp.contact">