From d6ec870033743fcc07f8b6c71cb32450cc75abb6 Mon Sep 17 00:00:00 2001 From: Moritz Strohm <strohm@data-quest.de> Date: Fri, 16 Dec 2022 13:44:16 +0000 Subject: [PATCH] StEP 1596, closes #1596 Closes #1596 Merge request studip/studip!1038 --- app/controllers/accessibility/forms.php | 175 ++++++++++++++++++ .../accessibility/forms/report_barrier.php | 2 + ...dd_accessibility_receiver_email_config.php | 33 ++++ lib/classes/forms/Form.php | 69 ++++++- lib/classes/forms/Link.php | 166 +++++++++++++++++ lib/classes/forms/Part.php | 36 ++++ lib/classes/forms/Text.php | 72 +++++++ lib/navigation/FooterNavigation.php | 9 + locale/de/LC_MAILS/report_barrier.php | 21 +++ locale/en/LC_MAILS/report_barrier.php | 21 +++ templates/forms/form.php | 4 +- 11 files changed, 599 insertions(+), 9 deletions(-) create mode 100644 app/controllers/accessibility/forms.php create mode 100644 app/views/accessibility/forms/report_barrier.php create mode 100644 db/migrations/5.3.15_add_accessibility_receiver_email_config.php create mode 100644 lib/classes/forms/Link.php create mode 100644 lib/classes/forms/Text.php create mode 100644 locale/de/LC_MAILS/report_barrier.php create mode 100644 locale/en/LC_MAILS/report_barrier.php diff --git a/app/controllers/accessibility/forms.php b/app/controllers/accessibility/forms.php new file mode 100644 index 00000000000..71ab2612f9f --- /dev/null +++ b/app/controllers/accessibility/forms.php @@ -0,0 +1,175 @@ +<?php +class Accessibility_FormsController extends StudipController +{ + protected $with_session = true; + + public function report_barrier_action() + { + PageLayout::setTitle(_('Barriere melden')); + + $this->page = Request::get('page'); + + $user = User::findCurrent(); + $user_salutation = ''; + if (!empty($user)) { + if ($user->geschlecht == 1) { + $user_salutation = _('Herr'); + } elseif ($user->geschlecht == 2) { + $user_salutation = _('Frau'); + } elseif ($user->geschlecht == 3) { + $user_salutation = _('divers'); + } + } + + $this->form = \Studip\Forms\Form::create(); + $this->form->addInput( + new \Studip\Forms\HiddenInput( + 'page', + '', + $this->page + ) + ); + $details_part = new \Studip\Forms\Fieldset(_('Angaben zur gefundenen Barriere')); + $details_part->addInput( + new \Studip\Forms\SelectInput( + 'barrier_type', + _('Um welche Art von Barriere handelt es sich?'), + '', + [ + 'options' => [ + _('Inhalte auf dieser Seite (z.B. PDF, Bilder oder Lernmodule)') => _('Inhalte auf dieser Seite (z.B. PDF, Bilder oder Lernmodule)'), + _('Ein Problem mit der Seite selbst oder der Navigation') => _('Ein Problem mit der Seite selbst oder der Navigation'), + _('Sonstiges') => _('Sonstiges') + ] + ] + ) + )->setRequired(); + $details_part->addInput( + new \Studip\Forms\TextareaInput( + 'barrier_details', + _('Beschreiben Sie die Barriere'), + '' + ) + )->setRequired(); + $this->form->addPart($details_part); + $personal_data_part = new \Studip\Forms\Fieldset(_('Ihre persönlichen Daten')); + $personal_data_part->addText(sprintf('<p>%s</p>', _('Geben Sie bitte Ihren Namen und Ihre E-Mail-Adresse an. Optional können Sie auch Ihre Telefonnummer angeben.'))); + $personal_data_part->addInput( + new \Studip\Forms\SelectInput( + 'salutation', + _('Anrede'), + $user_salutation, + [ + 'options' => [ + _('Keine Angabe') => _('Keine Angabe'), + _('Frau') => _('Frau'), + _('Herr') => _('Herr'), + _('divers') => _('divers') + ] + ] + ) + ); + $personal_data_part->addInput( + new \Studip\Forms\TextInput( + 'name', + _('Vorname und Nachname'), + $user ? sprintf('%s %s', $user->vorname, $user->nachname) : '' + ) + )->setRequired(); + $personal_data_part->addInput( + new \Studip\Forms\TextInput( + 'phone_number', + _('Telefonnummer'), + $user ? ($user->privatcell ?: $user->privatnr) : '' + ) + ); + $personal_data_part->addInput( + new \Studip\Forms\TextInput( + 'email_address', + _('E-Mail-Adresse'), + $user ? $user->email : '' + ) + )->setRequired(); + $privacy_url = Config::get()->PRIVACY_URL; + + if (is_internal_url($privacy_url)) { + $personal_data_part->addLink( + _('Datenschutzerklärung lesen'), + URLHelper::getURL($privacy_url, ['cancel_login' => '1']), + Icon::create('link-intern'), + ['data-dialog' => 'size=big'] + ); + } else { + $personal_data_part->addLink( + _('Datenschutzerklärung lesen'), + URLHelper::getURL($privacy_url), + Icon::create('link-extern'), + ['target' => '_blank'] + ); + } + $personal_data_part->addInput( + new \Studip\Forms\CheckboxInput( + 'confirm_privacy', + _('Ich habe die Datenschutzerklärung gelesen und akzeptiere sie.'), + '' + ) + )->setRequired(); + $this->form->addPart($personal_data_part); + $this->form->setSaveButtonText(_('Barriere melden')); + $this->form->setSaveButtonName('report'); + $this->form->setURL($this->report_barrierURL()); + $this->form->addStoreCallback( + function ($form, $form_values) { + $recipients = Config::get()->ACCESSIBILITY_RECEIVER_EMAIL; + if (empty($recipients)) { + //Fallback: Use the UNI_CONTACT mail address: + $recipients = [$GLOBALS['UNI_CONTACT']]; + } + //Get the sender and their language: + $sender = User::findCurrent(); + //Default to the system default language: + $lang = explode('_', $GLOBALS['DEFAULT_LANGUAGE'])[0]; + if ($sender) { + //Use the senders language since the choices in the form + //are in their language as well. + $lang = explode('_', getUserLanguage($sender->id))[0]; + } + //Format the senders name according to the salutation. + $formatted_name = ''; + if ($form_values['salutation'] === _('Keine Angabe')) { + $formatted_name = $form_values['name']; + } elseif ($form_values['salutation'] === _('divers')) { + $formatted_name = sprintf('%s (%s)', $form_values['name'], $form_values['salutation']); + } else { + $formatted_name = sprintf('%s %s', $form_values['salutation'], $form_values['name']); + } + //Build the mail text: + $template = $GLOBALS['template_factory']->open("../locale/{$lang}/LC_MAILS/report_barrier.php"); + $template->set_attributes([ + 'sender' => $sender, + 'page' => $form_values['page'], + 'barrier_type' => $form_values['barrier_type'], + 'barrier_details' => $form_values['barrier_details'], + 'formatted_name' => $formatted_name, + 'phone_number' => $form_values['phone_number'], + 'email_address' => $form_values['email_address'] + ]); + $mail_text = $template->render(); + + foreach ($recipients as $mail_address) { + //Send the mail: + $mail = new StudipMail(); + $mail->addRecipient($mail_address) + ->setReplyToEmail($form_values['email_address']) + ->setSubject(_('Meldung einer Barriere in Stud.IP')) + ->setBodyText($mail_text) + ->send(); + } + + $form->setSuccessMessage(_('Ihre Meldung einer Barriere wurde weitergeleitet.')); + return 1; + } + ); + $this->form->autoStore(); + } +} diff --git a/app/views/accessibility/forms/report_barrier.php b/app/views/accessibility/forms/report_barrier.php new file mode 100644 index 00000000000..5dd8d7d9089 --- /dev/null +++ b/app/views/accessibility/forms/report_barrier.php @@ -0,0 +1,2 @@ +<?= MessageBox::info(_('Auf dieser Seite können Sie eine Barriere melden, die die Nutzbarkeit von Stud.IP für Sie einschränkt. Füllen Sie dazu das untenstehende Formular aus.'))->hideClose() ?></p> +<?= $form->render() ?> diff --git a/db/migrations/5.3.15_add_accessibility_receiver_email_config.php b/db/migrations/5.3.15_add_accessibility_receiver_email_config.php new file mode 100644 index 00000000000..d3d73c242ae --- /dev/null +++ b/db/migrations/5.3.15_add_accessibility_receiver_email_config.php @@ -0,0 +1,33 @@ +<?php + +class AddAccessibilityReceiverEmailConfig extends Migration +{ + public function description() + { + return 'Adds the configuration ACCESSIBILITY_RECEIVER_EMAIL, if it doesn\'t exist yet.'; + } + + protected function up() + { + $db = DBManager::get(); + + $db->exec( + "INSERT IGNORE INTO `config` + (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`) + VALUES + ( + 'ACCESSIBILITY_RECEIVER_EMAIL', 'array', 'global', '', 'accessibility', + 'Die E-Mail-Adressen der Personen, die beim Melden einer Barriere benachrichtigt werden sollen.', + UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + )" + ); + } + + protected function down() + { + $db = DBManager::get(); + + $db->exec("DELETE FROM `config_values` WHERE `field` = 'ACCESSIBILITY_RECEIVER_EMAIL'"); + $db->exec("DELETE FROM `config` WHERE `field` = 'ACCESSIBILITY_RECEIVER_EMAIL'"); + } +} diff --git a/lib/classes/forms/Form.php b/lib/classes/forms/Form.php index b588ddb1a48..ba0258d2c4d 100644 --- a/lib/classes/forms/Form.php +++ b/lib/classes/forms/Form.php @@ -6,7 +6,7 @@ class Form extends Part { //models: - protected $afterStore = []; + protected $store_callbacks = []; //internals protected $inputs = []; @@ -14,7 +14,12 @@ class Form extends Part //appearance in html-form protected $url = null; + protected $save_button_text = ''; + protected $save_button_name = ''; + protected $autoStore = false; + protected $success_message = ''; + protected $collapsable = false; //to identify a form element @@ -57,6 +62,8 @@ class Form extends Part final public function __construct(...$parts) { parent::__construct(...$parts); + //Set a default for the success message: + $this->success_message = _('Daten wurden gespeichert.'); } /** @@ -160,6 +167,48 @@ class Form extends Part return $this->url; } + /** + * Sets the text for the "save" button in the form. + * + * @param string $text The text for the button to save the form. + * @return $this + */ + public function setSaveButtonText(string $text): Form + { + $this->save_button_text = $text; + return $this; + } + + /** + * @return string The text for the "save" button in the form. + */ + public function getSaveButtonText() : string + { + return $this->save_button_text ?: _('Speichern'); + } + + public function setSaveButtonName(string $name): Form + { + $this->save_button_name = $name; + return $this; + } + + public function getSaveButtonName() : string + { + return $this->save_button_name ?: $this->getSaveButtonText(); + } + + public function setSuccessMessage(string $success_message): Form + { + $this->success_message = $success_message; + return $this; + } + + public function getSuccessMessage() : string + { + return $this->success_message; + } + public function setCollapsable($collapsing = true) { $this->collapsable = $collapsing; @@ -182,7 +231,9 @@ class Form extends Part $this->autoStore = true; if (\Request::isPost() && \Request::isAjax() && !\Request::isDialog()) { $this->store(); - \PageLayout::postSuccess(_('Daten wurden gespeichert.')); + if ($this->success_message) { + \PageLayout::postSuccess($this->success_message); + } page_close(); die(); } @@ -200,9 +251,9 @@ class Form extends Part * @param callable $c * @return Form $this */ - public function addAfterStoreCallback(Callable $c) + public function addStoreCallback(Callable $c): Form { - $this->afterStore[] = $c; + $this->store_callbacks[] = $c; return $this; } @@ -240,11 +291,15 @@ class Form extends Part $stored = 0; //store by each input + $all_values = []; foreach ($this->getAllInputs() as $input) { $value = $this->getStorableValueFromRequest($input); if ($value !== null) { $callback = $this->getStoringCallback($input); - $stored += $callback($value, $input); + if (is_callable($callback)) { + $stored += $callback($value, $input); + } + $all_values[$input->getName()] = $value; } } @@ -255,9 +310,9 @@ class Form extends Part } } - foreach ($this->afterStore as $callback) { + foreach ($this->store_callbacks as $callback) { if (is_callable($callback)) { - $stored += call_user_func($callback, $this); + $stored += call_user_func($callback, $this, $all_values); } else { //throw warning if callback is not available: if ($callback === null) { diff --git a/lib/classes/forms/Link.php b/lib/classes/forms/Link.php new file mode 100644 index 00000000000..6f26f28ef73 --- /dev/null +++ b/lib/classes/forms/Link.php @@ -0,0 +1,166 @@ +<?php +namespace Studip\Forms; + +/** + * The Link class represents a part of a form that displays a link. + */ +class Link extends Part +{ + protected $url; + protected $label; + protected $icon; + protected $attributes = []; + + public function __construct(string $url, string $label, \Icon $icon = null) + { + $this->url = $url; + $this->label = $label; + $this->icon = $icon; + } + + /** + * Sets the url for the link. + * + * @param string $url + * @return $this + */ + public function setURL(string $url): Link + { + $this->url = $url; + return $this; + } + + /** + * Returns the url for the link. + * @return string + */ + public function getURL(): string + { + return $this->url; + } + + /** + * Sets the label for the link. + * + * @param string $label + * @return $this + */ + public function setLabel(string $label): Link + { + $this->label = $label; + return $this; + } + + /** + * Returns the label for the link. + * + * @return string + */ + public function getLabel() : string + { + return $this->label; + } + + /** + * Sets the icon for the link. May be null to remove the icon. + * + * @param \Icon $icon + * @return $this + */ + public function setIcon(\Icon $icon = null): Link + { + $this->icon = $icon; + return $this; + } + + /** + * Returns the icon for the link. + * @return \Icon|null + */ + public function getIcon(): ?\Icon + { + return $this->icon; + } + + /** + * Replaces all attributes for the link. + * + * @param array $attributes + * @return $this + */ + public function setAttributes(array $attributes): Link + { + $this->attributes = $attributes; + return $this; + } + + /** + * Adds/appends attributes to the current attributes for the link. + * + * @param array $attributes + * @return $this + */ + public function addAttributes(array $attributes): Link + { + $this->attributes = array_merge($this->attributes, $attributes); + return $this; + } + + /** + * Sets a single attribute for the link. + * + * @param string $key + * @param mixed $value + * @return $this + */ + public function setAttribute(string $key, $value): Link + { + $this->attributes[$key] = $value; + return $this; + } + + /** + * Returns the attributes for the link. + * @return array + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * Removes an attribute. + * + * @param string $key + * @param bool $throw_exception Throw an exception if the attribute does not exists (default: false) + * @return $this + */ + public function removeAttribute(string $key, bool $throw_exception = false): Link + { + if (!isset($this->attributes[$key]) && $throw_exception) { + throw new \RuntimeException("No attribute {$key} defined"); + } + + if (isset($this->attributes[$key])) { + unset($this->attributes[$key]); + } + + return $this; + } + + /** + * "Renders" the text: Either return it directly, if it is HTML or call htmlReady first before returning it. + * + * @return string The text that shall be placed in the form, either as HTML or plain text. + */ + public function render() + { + return sprintf( + '<div class="formpart"><a href="%1$s" %2$s>%3$s %4$s</a></div>', + \URLHelper::getLink($this->url, [], true), + arrayToHtmlAttributes($this->attributes), + $this->icon ? $this->icon->asImg(['class' => 'text-bottom']) : '', + htmlReady($this->label) + ); + } +} diff --git a/lib/classes/forms/Part.php b/lib/classes/forms/Part.php index 79c573bf761..4831945d13e 100644 --- a/lib/classes/forms/Part.php +++ b/lib/classes/forms/Part.php @@ -76,6 +76,42 @@ abstract class Part return $input; } + /** + * Adds a text block inside the form. + * + * @param string $text The text to be added. + * @param bool $text_is_html Whether the text is HTML (true) or plain text (false). Defaults to true. + * @return Text The added text form part. + */ + public function addText(string $text, bool $text_is_html = true): Text + { + $text_part = new Text(); + $text_part->setText($text, $text_is_html); + $text_part->setParent($this); + $this->parts[] = $text_part; + return $text_part; + } + + /** + * Adds a link as a form part. + * + * @param string $title The title of the link. + * @param string $url The URL of the link. + * @param \Icon|null $icon The icon to be used for the link. + * @param array $attributes Additional link attributes. + * + * @return Link The Text form element containing the link as HTML. + */ + public function addLink(string $title, string $url, ?\Icon $icon = null, array $attributes = []): Link + { + $link = new Link($url, $title, $icon); + $link->setAttributes($attributes); + + $this->addPart($link); + + return $link; + } + /** * Renders this Part object. This could be a section or any other HTML element with child-elements. * @return string diff --git a/lib/classes/forms/Text.php b/lib/classes/forms/Text.php new file mode 100644 index 00000000000..611ff832ac7 --- /dev/null +++ b/lib/classes/forms/Text.php @@ -0,0 +1,72 @@ +<?php + +namespace Studip\Forms; + +/** + * The Text class represents a part of a form that just displays text. + * The text can either be HTML or unformatted text. + */ +class Text extends Part +{ + /** + * The text to be displayed. + */ + protected $text = ''; + + /** + * This attribute defines whether to interpret the text as HTML (true) or as plain text (false). + */ + protected $text_is_html = true; + + /** + * Sets the text that shall be displayed in this form part. + * + * @param string $text The text to be displayed. + * @param bool $text_is_html Whether the text is HTML (true) or plain text. Defaults to true. + * @return $this This form part. + */ + public function setText(string $text, bool $text_is_html = true): Text + { + $this->text = $text; + $this->text_is_html = $text_is_html; + return $this; + } + + /** + * @return string The "raw form" of the text that shall be displayed. + */ + public function getText() : string + { + return $this->text; + } + + /** + * @return bool Whether the text is HTML (true) or not (false). + */ + public function isHtmlText() : bool + { + return $this->text_is_html; + } + + /** + * "Renders" the text: Either return it directly, if it is HTML or call htmlReady first before returning it. + * + * @return string The text that shall be placed in the form, either as HTML or plain text. + */ + public function render() + { + if ($this->text_is_html) { + return $this->text; + } else { + return htmlReady($this->text); + } + } + + /** + * @see Text::render() + */ + public function renderWithCondition() + { + return $this->render(); + } +} diff --git a/lib/navigation/FooterNavigation.php b/lib/navigation/FooterNavigation.php index a24dc119b54..ac62ff8a4cb 100644 --- a/lib/navigation/FooterNavigation.php +++ b/lib/navigation/FooterNavigation.php @@ -47,5 +47,14 @@ class FooterNavigation extends Navigation $privacy_url = URLHelper::getURL($privacy_url, ['cancel_login' => '1']); } $this->addSubNavigation('privacy', new Navigation(_('Datenschutz'), $privacy_url)); + + $this->addSubNavigation( + 'report_barrier', + new Navigation( + _('Barriere melden'), + URLHelper::getURL('dispatch.php/accessibility/forms/report_barrier', ['page' => Request::url()]), + ['data-dialog' => ''] + ) + ); } } diff --git a/locale/de/LC_MAILS/report_barrier.php b/locale/de/LC_MAILS/report_barrier.php new file mode 100644 index 00000000000..7d7c28b0eeb --- /dev/null +++ b/locale/de/LC_MAILS/report_barrier.php @@ -0,0 +1,21 @@ +Hallo, + + +Die folgende Barriere, die Personen einschränkt bzw. behindert, wurde in Stud.IP entdeckt: + +<?= $barrier_type ?> + + +<?= $barrier_details ?> + + +Die Barriere befindet sich auf der Seite: <?= $page ?> + + +Kontakt für Rückfragen: + +<?= $formatted_name ?> + +E-Mail: <?= $email_address ?> + +Telefon: <?= $phone_number ?> diff --git a/locale/en/LC_MAILS/report_barrier.php b/locale/en/LC_MAILS/report_barrier.php new file mode 100644 index 00000000000..6f5907b2ea4 --- /dev/null +++ b/locale/en/LC_MAILS/report_barrier.php @@ -0,0 +1,21 @@ +Hello + + +The following barrier which restrict or impede persons has been discovered in Stud.IP: + +<?= $barrier_type ?> + + +<?= $barrier_details ?> + + +The barrier has been found on the page: <?= $page ?> + + +Contact details for request: + +<?= $formatted_name ?> + +E-Mail: <?= $email_address ?> + +Phone: <?= $phone_number ?> diff --git a/templates/forms/form.php b/templates/forms/form.php index 2e1a661fbcc..21eb9c80f83 100644 --- a/templates/forms/form.php +++ b/templates/forms/form.php @@ -62,12 +62,12 @@ $form_id = md5(uniqid()); </div> <? if (!Request::isDialog()) : ?> <footer> - <?= \Studip\Button::create(_('Speichern'), null, ['form' => $form_id]) ?> + <?= \Studip\Button::create($form->getSaveButtonText(), $form->getSaveButtonName(), ['form' => $form_id]) ?> </footer> <? endif ?> </form> <? if (Request::isDialog()) : ?> <footer data-dialog-button> - <?= \Studip\Button::create(_('Speichern'), null, ['form' => $form_id]) ?> + <?= \Studip\Button::create($form->getSaveButtonText(), $form->getSaveButtonName(), ['form' => $form_id]) ?> </footer> <? endif ?> -- GitLab