Use the FormBuilder to create/edit captcha questions
authorCyperghost <olaf_schmitz_1@t-online.de>
Thu, 14 Nov 2024 12:40:51 +0000 (13:40 +0100)
committerCyperghost <olaf_schmitz_1@t-online.de>
Thu, 14 Nov 2024 12:40:51 +0000 (13:40 +0100)
wcfsetup/install/files/acp/templates/captchaQuestionAdd.tpl
wcfsetup/install/files/lib/acp/form/CaptchaQuestionAddForm.class.php
wcfsetup/install/files/lib/acp/form/CaptchaQuestionEditForm.class.php
wcfsetup/install/files/lib/data/TI18nDatabaseObjectAction.class.php
wcfsetup/install/files/lib/data/captcha/question/CaptchaQuestionAction.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 64d2d423e1e67dce7437e89296a3566060bbff62..636ce79406a72bec15c5b929e7d4bb3ed122a8af 100644 (file)
        </nav>
 </header>
 
-{include file='shared_formNotice'}
-
-<form id="adForm" method="post" action="{if $action == 'add'}{link controller='CaptchaQuestionAdd'}{/link}{else}{link controller='CaptchaQuestionEdit' id=$captchaQuestion->questionID}{/link}{/if}">
-       <div class="section">
-               <dl{if $errorField == 'question'} class="formError"{/if}>
-                       <dt><label for="question">{lang}wcf.acp.captcha.question.question{/lang}</label></dt>
-                       <dd>
-                               <input type="text" id="question" name="question" value="{$i18nPlainValues[question]}" required autofocus class="long">
-                               {if $errorField == 'question'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {elseif $errorType == 'multilingual'}
-                                                       {lang}wcf.global.form.error.multilingual{/lang}
-                                               {else}
-                                                       {lang}wcf.acp.captcha.question.question.error.{$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                       </dd>
-               </dl>
-               {include file='shared_multipleLanguageInputJavascript' elementIdentifier='question' forceSelection=false}
-               
-               <dl{if $errorField == 'answers'} class="formError"{/if}>
-                       <dt><label for="answers">{lang}wcf.acp.captcha.question.answers{/lang}</label></dt>
-                       <dd>
-                               <textarea id="answers" name="answers" cols="40" rows="10">{$i18nPlainValues[answers]}</textarea>
-                               <small>{lang}wcf.acp.captcha.question.answers.description{/lang}</small>
-                               {if $errorField == 'answers'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {elseif $errorType == 'multilingual'}
-                                                       {lang}wcf.global.form.error.multilingual{/lang}
-                                               {else}
-                                                       {lang}wcf.acp.captcha.question.answers.error.{$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                       </dd>
-               </dl>
-               {include file='shared_multipleLanguageInputJavascript' elementIdentifier='answers' forceSelection=false}
-               
-               <dl>
-                       <dt></dt>
-                       <dd>
-                               <label><input type="checkbox" name="isDisabled" value="1"{if $isDisabled} checked{/if}> {lang}wcf.acp.captcha.question.isDisabled{/lang}</label>
-                       </dd>
-               </dl>
-               
-               {event name='dataFields'}
-       </div>
-       
-       {event name='sections'}
-       
-       <div class="formSubmit">
-               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
-               {csrfToken}
-       </div>
-</form>
+{unsafe:$form->getHtml()}
 
 {include file='footer'}
index 502054afb5f5c89891e7c051bfca753c4af0fa2e..95df4f71481d3511056194fe5d7e7e6e3155473f 100644 (file)
@@ -2,42 +2,35 @@
 
 namespace wcf\acp\form;
 
+use wcf\data\captcha\question\CaptchaQuestion;
 use wcf\data\captcha\question\CaptchaQuestionAction;
-use wcf\data\captcha\question\CaptchaQuestionEditor;
-use wcf\form\AbstractForm;
-use wcf\system\exception\UserInputException;
-use wcf\system\language\I18nHandler;
+use wcf\data\language\Language;
+use wcf\form\AbstractFormBuilderForm;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\field\BooleanFormField;
+use wcf\system\form\builder\field\MultilineTextFormField;
+use wcf\system\form\builder\field\TextFormField;
+use wcf\system\form\builder\field\validation\FormFieldValidationError;
+use wcf\system\form\builder\field\validation\FormFieldValidator;
+use wcf\system\language\LanguageFactory;
 use wcf\system\Regex;
-use wcf\system\request\LinkHandler;
-use wcf\system\WCF;
-use wcf\util\StringUtil;
 
 /**
  * Shows the form to create a new captcha question.
  *
- * @author  Matthias Schmidt
- * @copyright   2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @author      Olaf Braun, Matthias Schmidt
+ * @copyright   2001-2024 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ *
+ * @property CaptchaQuestion $formObject
  */
-class CaptchaQuestionAddForm extends AbstractForm
+class CaptchaQuestionAddForm extends AbstractFormBuilderForm
 {
     /**
      * @inheritDoc
      */
     public $activeMenuItem = 'wcf.acp.menu.link.captcha.question.add';
 
-    /**
-     * invalid regex in answers
-     * @var string
-     */
-    public $invalidRegex = '';
-
-    /**
-     * 1 if the question is disabled
-     * @var int
-     */
-    public $isDisabled = 0;
-
     /**
      * @inheritDoc
      */
@@ -46,155 +39,76 @@ class CaptchaQuestionAddForm extends AbstractForm
     /**
      * @inheritDoc
      */
-    public function assignVariables()
-    {
-        parent::assignVariables();
-
-        I18nHandler::getInstance()->assignVariables();
-
-        WCF::getTPL()->assign([
-            'action' => 'add',
-            'isDisabled' => $this->isDisabled,
-            'invalidRegex' => $this->invalidRegex,
-        ]);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function readFormParameters()
-    {
-        parent::readFormParameters();
-
-        I18nHandler::getInstance()->readValues();
-
-        if (isset($_POST['isDisabled'])) {
-            $this->isDisabled = 1;
-        }
-    }
+    public $objectActionClass = CaptchaQuestionAction::class;
 
     /**
      * @inheritDoc
      */
-    public function readParameters()
-    {
-        parent::readParameters();
-
-        I18nHandler::getInstance()->register('question');
-        I18nHandler::getInstance()->register('answers');
-    }
+    public $objectEditLinkController = CaptchaQuestionEditForm::class;
 
-    /**
-     * @inheritDoc
-     */
-    public function save()
+    #[\Override]
+    protected function createForm()
     {
-        parent::save();
-
-        $this->objectAction = new CaptchaQuestionAction([], 'create', [
-            'data' => \array_merge($this->additionalFields, [
-                'answers' => I18nHandler::getInstance()->isPlainValue('answers') ? I18nHandler::getInstance()->getValue('answers') : '',
-                'isDisabled' => $this->isDisabled,
-                'question' => I18nHandler::getInstance()->isPlainValue('question') ? I18nHandler::getInstance()->getValue('question') : '',
-            ]),
-        ]);
-        $returnValues = $this->objectAction->executeAction();
-        $questionID = $returnValues['returnValues']->questionID;
-
-        // set i18n values
-        $questionUpdates = [];
-        if (!I18nHandler::getInstance()->isPlainValue('question')) {
-            I18nHandler::getInstance()->save(
-                'question',
-                'wcf.captcha.question.question.question' . $questionID,
-                'wcf.captcha.question',
-                1
-            );
-
-            $questionUpdates['question'] = 'wcf.captcha.question.question.question' . $questionID;
-        }
-        if (!I18nHandler::getInstance()->isPlainValue('answers')) {
-            I18nHandler::getInstance()->save(
-                'answers',
-                'wcf.captcha.question.answers.question' . $questionID,
-                'wcf.captcha.question',
-                1
-            );
-
-            $questionUpdates['answers'] = 'wcf.captcha.question.answers.question' . $questionID;
-        }
-
-        if (!empty($questionUpdates)) {
-            $questionEditor = new CaptchaQuestionEditor($returnValues['returnValues']);
-            $questionEditor->update($questionUpdates);
-        }
-
-        $this->saved();
-
-        // reset values
-        I18nHandler::getInstance()->reset();
-        $this->isDisabled = 0;
-
-        // show success message
-        WCF::getTPL()->assign([
-            'success' => true,
-            'objectEditLink' => LinkHandler::getInstance()->getControllerLink(
-                CaptchaQuestionEditForm::class,
-                ['id' => $questionID]
-            ),
+        parent::createForm();
+
+        $this->form->appendChildren([
+            FormContainer::create('general')
+                ->appendChildren([
+                    TextFormField::create('question')
+                        ->label('wcf.acp.captcha.question.question')
+                        ->i18n()
+                        ->languageItemPattern('wcf.captcha.question.question.question\d+')
+                        ->required(),
+                    MultilineTextFormField::create('answers')
+                        ->label('wcf.acp.captcha.question.answers')
+                        ->i18n()
+                        ->languageItemPattern('wcf.captcha.question.answers.question\d+')
+                        ->required()
+                        ->addValidator(
+                            new FormFieldValidator('regexValidator', function (MultilineTextFormField $formField) {
+                                $value = $formField->getValue();
+
+                                if ($formField->hasPlainValue()) {
+                                    $this->validateAnswer($value, $formField);
+                                } else {
+                                    foreach ($value as $languageID => $languageValue) {
+                                        $this->validateAnswer(
+                                            $languageValue,
+                                            $formField,
+                                            LanguageFactory::getInstance()->getLanguage($languageID)
+                                        );
+                                    }
+                                }
+                            })
+                        ),
+                    BooleanFormField::create('isDisabled')
+                        ->label('wcf.acp.captcha.question.isDisabled')
+                        ->value(false)
+                ])
         ]);
     }
 
-    /**
-     * @inheritDoc
-     */
-    public function validate()
-    {
-        parent::validate();
-
-        // validate question
-        if (!I18nHandler::getInstance()->validateValue('question')) {
-            if (I18nHandler::getInstance()->isPlainValue('question')) {
-                throw new UserInputException('question');
-            } else {
-                throw new UserInputException('question', 'multilingual');
-            }
+    protected function validateAnswer(
+        string $answer,
+        MultilineTextFormField $formField,
+        ?Language $language = null
+    ): void {
+        if (!\str_starts_with('~', $answer) || !\str_ends_with('~', $answer)) {
+            return;
         }
 
-        // validate answers
-        if (!I18nHandler::getInstance()->validateValue('answers')) {
-            if (I18nHandler::getInstance()->isPlainValue('answers')) {
-                throw new UserInputException('answers');
-            } else {
-                throw new UserInputException('answers', 'multilingual');
-            }
-        }
-
-        if (I18nHandler::getInstance()->isPlainValue('answers')) {
-            $answers = \explode("\n", StringUtil::unifyNewlines(I18nHandler::getInstance()->getValue('answers')));
-            foreach ($answers as $answer) {
-                if (\mb_substr($answer, 0, 1) == '~' && \mb_substr($answer, -1, 1) == '~') {
-                    $regexLength = \mb_strlen($answer) - 2;
-                    if (!$regexLength || !Regex::compile(\mb_substr($answer, 1, $regexLength))->isValid()) {
-                        $this->invalidRegex = $answer;
-
-                        throw new UserInputException('answers', 'invalidRegex');
-                    }
-                }
-            }
-        }
-        foreach (I18nHandler::getInstance()->getValues('answers') as $languageAnswers) {
-            $answers = \explode("\n", StringUtil::unifyNewlines($languageAnswers));
-            foreach ($answers as $answer) {
-                if (\mb_substr($answer, 0, 1) == '~' && \mb_substr($answer, -1, 1) == '~') {
-                    $regexLength = \mb_strlen($answer) - 2;
-                    if (!$regexLength || !Regex::compile(\mb_substr($answer, 1, $regexLength))->isValid()) {
-                        $this->invalidRegex = $answer;
-
-                        throw new UserInputException('answers', 'invalidRegex');
-                    }
-                }
-            }
+        $regexLength = \mb_strlen($answer) - 2;
+        if (!$regexLength || !Regex::compile(\mb_substr($answer, 1, $regexLength))->isValid()) {
+            $formField->addValidationError(
+                new FormFieldValidationError(
+                    'invalidRegex',
+                    'wcf.acp.captcha.question.answers.error.invalidRegex',
+                    [
+                        'invalidRegex' => $answer,
+                        'language' => $language
+                    ]
+                )
+            );
         }
     }
 }
index 5532e18c5f5cb61ec107ceb9fa553a7ae07d60f7..100053ad805d74aeea79dbd182ec11a7840e8735 100644 (file)
@@ -2,12 +2,10 @@
 
 namespace wcf\acp\form;
 
+use CuyZ\Valinor\Mapper\MappingError;
 use wcf\data\captcha\question\CaptchaQuestion;
-use wcf\data\captcha\question\CaptchaQuestionAction;
-use wcf\form\AbstractForm;
+use wcf\http\Helper;
 use wcf\system\exception\IllegalLinkException;
-use wcf\system\language\I18nHandler;
-use wcf\system\WCF;
 
 /**
  * Shows the form to edit an existing captcha question.
@@ -23,119 +21,32 @@ class CaptchaQuestionEditForm extends CaptchaQuestionAddForm
      */
     public $activeMenuItem = 'wcf.acp.menu.link.captcha.question.list';
 
-    /**
-     * edited captcha question
-     * @var CaptchaQuestion
-     */
-    public $captchaQuestion;
-
-    /**
-     * id of the edited captcha question
-     * @var int
-     */
-    public $captchaQuestionID = 0;
-
     /**
      * @inheritDoc
      */
-    public function assignVariables()
-    {
-        parent::assignVariables();
-
-        I18nHandler::getInstance()->assignVariables(!empty($_POST));
-
-        WCF::getTPL()->assign([
-            'action' => 'edit',
-            'captchaQuestion' => $this->captchaQuestion,
-        ]);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function readData()
-    {
-        parent::readData();
-
-        if (empty($_POST)) {
-            I18nHandler::getInstance()->setOptions(
-                'question',
-                1,
-                $this->captchaQuestion->question,
-                'wcf.captcha.question.question.question\d+'
-            );
-            I18nHandler::getInstance()->setOptions(
-                'answers',
-                1,
-                $this->captchaQuestion->answers,
-                'wcf.captcha.question.question.answers\d+'
-            );
-
-            $this->isDisabled = $this->captchaQuestion->isDisabled;
-        }
-    }
+    public $formAction = 'edit';
 
-    /**
-     * @inheritDoc
-     */
+    #[\Override]
     public function readParameters()
     {
         parent::readParameters();
 
-        if (isset($_REQUEST['id'])) {
-            $this->captchaQuestionID = \intval($_REQUEST['id']);
-        }
-        $this->captchaQuestion = new CaptchaQuestion($this->captchaQuestionID);
-        if (!$this->captchaQuestion->questionID) {
-            throw new IllegalLinkException();
-        }
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function save()
-    {
-        AbstractForm::save();
-
-        if (I18nHandler::getInstance()->isPlainValue('question')) {
-            if ($this->captchaQuestion->question == 'wcf.captcha.question.question.question' . $this->captchaQuestion->questionID) {
-                I18nHandler::getInstance()->remove($this->captchaQuestion->question);
-            }
-        } else {
-            I18nHandler::getInstance()->save(
-                'question',
-                'wcf.captcha.question.question.question' . $this->captchaQuestion->questionID,
-                'wcf.captcha.question',
-                1
+        try {
+            $queryParameters = Helper::mapQueryParameters(
+                $_GET,
+                <<<'EOT'
+                    array {
+                        id: positive-int
+                    }
+                    EOT
             );
-        }
+            $this->formObject = new CaptchaQuestion($queryParameters['id']);
 
-        if (I18nHandler::getInstance()->isPlainValue('answers')) {
-            if ($this->captchaQuestion->answers == 'wcf.captcha.question.question.answers' . $this->captchaQuestion->questionID) {
-                I18nHandler::getInstance()->remove($this->captchaQuestion->answers);
+            if (!$this->formObject->getObjectID()) {
+                throw new IllegalLinkException();
             }
-        } else {
-            I18nHandler::getInstance()->save(
-                'answers',
-                'wcf.captcha.question.question.answers' . $this->captchaQuestion->questionID,
-                'wcf.captcha.question',
-                1
-            );
+        } catch (MappingError) {
+            throw new IllegalLinkException();
         }
-
-        $this->objectAction = new CaptchaQuestionAction([$this->captchaQuestion], 'update', [
-            'data' => \array_merge($this->additionalFields, [
-                'answers' => I18nHandler::getInstance()->isPlainValue('answers') ? I18nHandler::getInstance()->getValue('answers') : 'wcf.captcha.question.question.answers' . $this->captchaQuestion->questionID,
-                'isDisabled' => $this->isDisabled,
-                'question' => I18nHandler::getInstance()->isPlainValue('question') ? I18nHandler::getInstance()->getValue('question') : 'wcf.captcha.question.question.question' . $this->captchaQuestion->questionID,
-            ]),
-        ]);
-        $this->objectAction->executeAction();
-
-        $this->saved();
-
-        // show success message
-        WCF::getTPL()->assign('success', true);
     }
 }
index 68fa37ed79697cb9a3dfb4fa20639969d91fbb79..36166f3facc1e0445c617cde4682019651952df2 100644 (file)
@@ -25,23 +25,23 @@ trait TI18nDatabaseObjectAction
      */
     protected function deleteI18nValues(): void
     {
-        $langaugeItems = [];
+        $languageItems = [];
         foreach ($this->getObjects() as $object) {
             foreach ($this->getI18nSaveTypes() as $name => $regex) {
                 if ($object->$name === \str_replace('\d+', $object->getObjectID(), $regex)) {
-                    $langaugeItems[] = $object->$name;
+                    $languageItems[] = $object->$name;
                 }
             }
         }
-        $this->deleteI18nItems($langaugeItems);
+        $this->deleteI18nItems($languageItems);
     }
 
     /**
      * Deletes language items and clears the language cache.
      */
-    private function deleteI18nItems(array $langaugeItems): void
+    private function deleteI18nItems(array $languageItems): void
     {
-        if ($langaugeItems !== []) {
+        if ($languageItems === []) {
             return;
         }
 
@@ -53,7 +53,7 @@ trait TI18nDatabaseObjectAction
         $languageCategoryID = $statement->fetchSingleColumn();
 
         $conditions = new PreparedStatementConditionBuilder();
-        $conditions->add('languageItem IN (?)', [$langaugeItems]);
+        $conditions->add('languageItem IN (?)', [$languageItems]);
         $conditions->add('packageID = ?', [$this->getPackageID()]);
         $conditions->add('languageCategoryID = ?', [$languageCategoryID]);
 
index b3939440ec13336fb8e34f22aae2fc69ba25df77..669abae287685fc151a2d95f919d1a65516fde70 100644 (file)
@@ -5,6 +5,7 @@ namespace wcf\data\captcha\question;
 use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\IToggleAction;
 use wcf\data\TDatabaseObjectToggle;
+use wcf\data\TI18nDatabaseObjectAction;
 
 /**
  * Executes captcha question-related actions.
@@ -13,13 +14,14 @@ use wcf\data\TDatabaseObjectToggle;
  * @copyright   2001-2019 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  *
- * @method  CaptchaQuestion         create()
  * @method  CaptchaQuestionEditor[]     getObjects()
  * @method  CaptchaQuestionEditor       getSingleObject()
+ * @property    CaptchaQuestionEditor[] $objects
  */
 class CaptchaQuestionAction extends AbstractDatabaseObjectAction implements IToggleAction
 {
     use TDatabaseObjectToggle;
+    use TI18nDatabaseObjectAction;
 
     /**
      * @inheritDoc
@@ -30,4 +32,48 @@ class CaptchaQuestionAction extends AbstractDatabaseObjectAction implements ITog
      * @inheritDoc
      */
     protected $permissionsUpdate = ['admin.captcha.canManageCaptchaQuestion'];
+
+    #[\Override]
+    public function getI18nSaveTypes(): array
+    {
+        return [
+            'question' => 'wcf.captcha.question.question.question\d+',
+            'answers' => 'wcf.captcha.question.answers.question\d+',
+        ];
+    }
+
+    #[\Override]
+    public function getLanguageCategory(): string
+    {
+        return 'wcf.captcha.question';
+    }
+
+    #[\Override]
+    public function getPackageID(): int
+    {
+        return PACKAGE_ID;
+    }
+
+    #[\Override]
+    public function update()
+    {
+        parent::update();
+
+        foreach ($this->objects as $object) {
+            $this->saveI18nValue($object->getDecoratedObject());
+        }
+    }
+
+    #[\Override]
+    public function create()
+    {
+        // Question column doesn't have a default value
+        $this->parameters['data']['question'] = $this->parameters['data']['question'] ?? '';
+
+        $captchaQuestion = parent::create();
+
+        $this->saveI18nValue($captchaQuestion);
+
+        return $captchaQuestion;
+    }
 }
index 62793c611b2d0ef447b54b5fac2ac1de56bb82bb..1ba6c21f594dc8351ec8336bc752858ebe12c8c9 100644 (file)
                <item name="wcf.acp.captcha.question.add"><![CDATA[Captcha-Frage hinzufügen]]></item>
                <item name="wcf.acp.captcha.question.answers"><![CDATA[Antworten]]></item>
                <item name="wcf.acp.captcha.question.answers.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} pro Zeile eine mögliche Antwort ein. Antworten, die mit „~“ beginnen und enden, werden als reguläre Ausdrücke interpretiert.]]></item>
-               <item name="wcf.acp.captcha.question.answers.error.invalidRegex"><![CDATA[Der reguläre Ausdruck „{$invalidRegex}“ ist ungültig.]]></item>
+               <item name="wcf.acp.captcha.question.answers.error.invalidRegex"><![CDATA[Der reguläre Ausdruck „{$invalidRegex}“ ist ungültig{if $language} für die Sprache „{$language}“{/if}.]]></item>
                <item name="wcf.acp.captcha.question.edit"><![CDATA[Captcha-Frage bearbeiten]]></item>
                <item name="wcf.acp.captcha.question.isDisabled"><![CDATA[Frage deaktivieren]]></item>
                <item name="wcf.acp.captcha.question.list"><![CDATA[Captcha-Fragen]]></item>
index e213f89ae24f3e8424d9473f5173c7328ff185ec..9bf835587e3ae892a1ab77b1523a326e2b8597f8 100644 (file)
                <item name="wcf.acp.captcha.question.add"><![CDATA[Add Captcha Question]]></item>
                <item name="wcf.acp.captcha.question.answers"><![CDATA[Answers]]></item>
                <item name="wcf.acp.captcha.question.answers.description"><![CDATA[Answers beginning and ending with “~” will be interpreted as regular expressions. Enter one answer per line.]]></item>
-               <item name="wcf.acp.captcha.question.answers.error.invalidRegex"><![CDATA[The regular expression “{$invalidRegex}” is invalid.]]></item>
+               <item name="wcf.acp.captcha.question.answers.error.invalidRegex"><![CDATA[The regular expression “{$invalidRegex}” is invalid{if $language} for language “{$language}”{/if}.]]></item>
                <item name="wcf.acp.captcha.question.edit"><![CDATA[Edit Captcha Question]]></item>
                <item name="wcf.acp.captcha.question.isDisabled"><![CDATA[Disable captcha question]]></item>
                <item name="wcf.acp.captcha.question.list"><![CDATA[Captcha Questions]]></item>