From b6bdba58a8e090cb144bae382457c2e439d8a72a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michaela=20Br=C3=BCckner?= Date: Tue, 2 Jan 2024 08:32:34 +0000 Subject: Step 2660 closes #2660 Closes #2660 Merge request studip/studip!2124 --- app/controllers/admin/login_style.php | 223 +++++++++++++++++++++ app/controllers/admin/loginstyle.php | 153 -------------- app/views/admin/login_style/edit_faq.php | 30 +++ app/views/admin/login_style/index.php | 78 +++---- app/views/admin/login_style/login_faq.php | 57 ++++++ app/views/admin/login_style/newpic.php | 5 +- app/views/new_password/index.php | 2 +- .../5.5.16_add_tooltip_fields_for_login.php | 71 +++++++ db/migrations/5.5.17_add_login_faq_table.php | 29 +++ db/migrations/5.5.18_add_login_faq_config.php | 53 +++++ lib/classes/auth_plugins/CASUserDataMapping.php | 3 +- .../auth_plugins/StudipAuthAbstract.class.php | 40 ++++ lib/classes/auth_plugins/StudipAuthLdap.class.php | 1 + .../auth_plugins/StudipAuthStandard.class.php | 1 + lib/models/LoginFaq.class.php | 32 +++ lib/navigation/AdminNavigation.php | 13 +- lib/navigation/LoginNavigation.php | 19 +- lib/navigation/StartNavigation.php | 2 +- lib/phplib/Seminar_Auth.class.php | 28 +++ lib/seminar_open.php | 7 + locale/en/LC_MESSAGES/studip.po | 2 +- public/index.php | 44 +--- .../assets/javascripts/bootstrap/application.js | 65 ++++++ resources/assets/stylesheets/scss/index.scss | 85 +++++++- resources/assets/stylesheets/scss/responsive.scss | 7 +- resources/assets/stylesheets/scss/variables.scss | 3 + templates/_standard_loginform.php | 76 +++++++ templates/loginform.php | 167 +++++++++------ tests/e2e/login.spec.ts | 2 +- 29 files changed, 991 insertions(+), 307 deletions(-) create mode 100644 app/controllers/admin/login_style.php delete mode 100644 app/controllers/admin/loginstyle.php create mode 100644 app/views/admin/login_style/edit_faq.php create mode 100644 app/views/admin/login_style/login_faq.php create mode 100644 db/migrations/5.5.16_add_tooltip_fields_for_login.php create mode 100644 db/migrations/5.5.17_add_login_faq_table.php create mode 100644 db/migrations/5.5.18_add_login_faq_config.php create mode 100644 lib/models/LoginFaq.class.php create mode 100644 templates/_standard_loginform.php diff --git a/app/controllers/admin/login_style.php b/app/controllers/admin/login_style.php new file mode 100644 index 0000000..615e777 --- /dev/null +++ b/app/controllers/admin/login_style.php @@ -0,0 +1,223 @@ + + * @license GPL2 or any later version + * @category Stud.IP + * @package admin + * @since 4.0 + */ + +class Admin_LoginStyleController extends AuthenticatedController +{ + protected $_autobind = true; + /** + * common tasks for all actions + * + * @param String $action Action that has been called + * @param Array $args List of arguments + */ + public function before_filter(&$action, &$args) + { + parent::before_filter($action, $args); + + $GLOBALS['perm']->check('root'); + + //setting title and navigation + PageLayout::setTitle(_('Hintergrundbilder für den Startbildschirm')); + Navigation::activateItem('/admin/locations/loginstyle'); + + $views = new ViewsWidget(); + $views->addLink( + _('Bilder'), + $this->indexURL() + )->setActive($action === 'index'); + + $views->addLink( + _('Hinweise zum Login'), + $this->login_faqURL() + )->setActive($action === 'login_faq'); + + Sidebar::Get()->addWidget($views); + } + + /** + * Display all available background pictures + */ + public function index_action() + { + // Setup sidebar + $this->setSidebar('index'); + $this->pictures = LoginBackground::findBySQL("1 ORDER BY `background_id`"); + } + + /** + * Provides a form for uploading a new picture. + */ + public function newpic_action() + { + } + + /** + * Adds a new picture ass possible login background. + */ + public function add_pic_action() + { + CSRFProtection::verifyRequest(); + $success = 0; + foreach ($_FILES['pictures']['name'] as $index => $filename) { + if ($_FILES['pictures']['error'][$index] !== UPLOAD_ERR_OK) { + continue; + } + + $extension = pathinfo($filename, PATHINFO_EXTENSION); + $extension = strtolower($extension); + if (!in_array($extension, ['gif', 'jpeg', 'jpg', 'png'])) { + continue; + } + + $entry = new LoginBackground(); + $entry->filename = $filename; + $entry->desktop = Request::int('desktop', 0); + $entry->mobile = Request::int('mobile', 0); + if ($entry->store()) { + $destination = LoginBackground::getPictureDirectory() . DIRECTORY_SEPARATOR + . $entry->id . '.' . $extension; + if (move_uploaded_file($_FILES['pictures']['tmp_name'][$index], $destination)) { + $success++; + } else { + $entry->delete(); + } + } + } + + if ($success > 0) { + PageLayout::postSuccess(sprintf(ngettext( + 'Ein Bild wurde hochgeladen.', + '%u Bilder wurden hochgeladen', + $success + ), $success)); + } + + $fail = count($_FILES['pictures']['name']) - $success; + if ($fail > 0) { + PageLayout::postError(sprintf(ngettext( + 'Ein Bild konnte nicht hochgeladen werden.', + '%u Bilder konnten nicht hochgeladen werden.', + $fail + ), $fail)); + } + $this->relocate($this->indexURL()); + } + + /** + * Deletes the given picture. + * @param string $id the picture to delete + */ + public function delete_pic_action($id) + { + CSRFProtection::verifyUnsafeRequest(); + $pic = LoginBackground::find($id); + if ($pic->in_release) { + PageLayout::postError(_('Dieses Bild wird vom System mitgeliefert und kann daher nicht gelöscht werden.')); + } elseif ($pic->delete()) { + PageLayout::postSuccess(_('Das Bild wurde gelöscht.')); + } else { + PageLayout::postError(_('Das Bild konnte nicht gelöscht werden.')); + } + + $this->relocate($this->indexURL()); + } + + /** + * (De-)activate the given picture for given view. + * @param string $id the picture to change activation for + * @param string $view one of 'desktop', 'mobile', view to (de-) activate picture for + * @param string $newStatus new activation status for given view. + */ + public function activation_action($id, $view, $newStatus) + { + CSRFProtection::verifyUnsafeRequest(); + if (!in_array($view, ['desktop', 'mobile'])) { + throw new InvalidArgumentException('You may not change this attribute.'); + } + + $pic = LoginBackground::find($id); + $pic->$view = $newStatus; + if ($pic->store()) { + PageLayout::postSuccess(_('Der Aktivierungsstatus wurde gespeichert.')); + } else { + PageLayout::postSuccess(_('Der Aktivierungsstatus konnte nicht gespeichert werden.')); + } + $this->relocate($this->indexURL()); + } + + + /** + * FAQ part of login page + */ + public function login_faq_action() + { + PageLayout::setTitle(_('Hinweise zum Login für den Startbildschirm')); + + $this->setSidebar('login_faq'); + $this->faq_entries = LoginFaq::findBySql('1'); + } + + public function edit_faq_action(LoginFaq $entry = null) + { + PageLayout::setTitle( + $entry->isNew() ? _('Hinweistext hinzufügen') : _('Hinweistext bearbeiten') + ); + } + + public function store_faq_action(LoginFaq $entry = null) + { + CSRFProtection::verifyRequest(); + + $entry->setData([ + 'title' => trim(Request::get('title')), + 'description' => trim(Request::get('description')), + ]); + + if ($entry->store()) { + PageLayout::postSuccess(_('Hinweistext wurde gespeichert.')); + } + + $this->relocate($this->login_faqURL()); + } + + public function delete_faq_action(LoginFaq $entry) + { + CSRFProtection::verifyRequest(); + + if ($entry->delete()) { + PageLayout::postSuccess(_('Der Hinweistext wurde gelöscht.')); + } + + $this->relocate($this->login_faqURL()); + } + + /** + * Adds the content to sidebar + */ + protected function setSidebar($action) + { + $links = new ActionsWidget(); + if ($action === 'index') { + $links->addLink( + _('Bild hinzufügen'), + $this->newpicURL(), + Icon::create('add') + )->asDialog('size=auto'); + } else if ($action === 'login_faq') { + $links->addLink( + _('Hinweistext hinzufügen'), + $this->edit_faqURL(), + Icon::create('add') + )->asDialog(); + } + Sidebar::get()->addWidget($links); + } +} diff --git a/app/controllers/admin/loginstyle.php b/app/controllers/admin/loginstyle.php deleted file mode 100644 index f998b3a..0000000 --- a/app/controllers/admin/loginstyle.php +++ /dev/null @@ -1,153 +0,0 @@ - - * @license GPL2 or any later version - * @category Stud.IP - * @package admin - * @since 4.0 - */ - -class Admin_LoginStyleController extends AuthenticatedController -{ - /** - * common tasks for all actions - * - * @param String $action Action that has been called - * @param Array $args List of arguments - */ - public function before_filter(&$action, &$args) - { - parent::before_filter($action, $args); - - // user must have root permission - $GLOBALS['perm']->check('root'); - - //setting title and navigation - PageLayout::setTitle(_('Hintergrundbilder für den Startbildschirm')); - Navigation::activateItem('/admin/locations/loginstyle'); - - // Setup sidebar - $this->setSidebar(); - } - - /** - * Display all available background pictures - */ - public function index_action() - { - $this->pictures = LoginBackground::findBySQL("1 ORDER BY `background_id`"); - } - - /** - * Provides a form for uploading a new picture. - */ - public function newpic_action() - { - } - - /** - * Adds a new picture ass possible login background. - */ - public function add_action() - { - CSRFProtection::verifyRequest(); - $success = 0; - foreach ($_FILES['pictures']['name'] as $index => $filename) { - if ($_FILES['pictures']['error'][$index] !== UPLOAD_ERR_OK) { - continue; - } - - $extension = pathinfo($filename, PATHINFO_EXTENSION); - $extension = strtolower($extension); - if (!in_array($extension, ['gif', 'jpeg', 'jpg', 'png'])) { - continue; - } - - $entry = new LoginBackground(); - $entry->filename = $filename; - $entry->desktop = Request::int('desktop', 0); - $entry->mobile = Request::int('mobile', 0); - if ($entry->store()) { - $destination = LoginBackground::getPictureDirectory() . DIRECTORY_SEPARATOR - . $entry->id . '.' . $extension; - if (move_uploaded_file($_FILES['pictures']['tmp_name'][$index], $destination)) { - $success++; - } else { - $entry->delete(); - } - } - } - - if ($success > 0) { - PageLayout::postSuccess(sprintf(ngettext( - 'Ein Bild wurde hochgeladen.', - '%u Bilder wurden hochgeladen', - $success - ), $success)); - } - - $fail = count($_FILES['pictures']['name']) - $success; - if ($fail > 0) { - PageLayout::postError(sprintf(ngettext( - 'Ein Bild konnte nicht hochgeladen werden.', - '%u Bilder konnten nicht hochgeladen werden.', - $fail - ), $fail)); - } - $this->relocate('admin/loginstyle'); - } - - /** - * Deletes the given picture. - * @param $id the picture to delete - */ - public function delete_action($id) - { - $pic = LoginBackground::find($id); - if ($pic->in_release) { - PageLayout::postError(_('Dieses Bild wird vom System mitgeliefert und kann daher nicht gelöscht werden.')); - } elseif ($pic->delete()) { - PageLayout::postSuccess(_('Das Bild wurde gelöscht.')); - } else { - PageLayout::postError(_('Das Bild konnte nicht gelöscht werden.')); - } - - $this->relocate('admin/loginstyle'); - } - - /** - * (De-)activate the given picture for given view. - * @param $id the picture to change activation for - * @param $view one of 'desktop', 'mobile', view to (de-) activate picture for - * @param $newStatus new activation status for given view. - */ - public function activation_action($id, $view, $newStatus) - { - $pic = LoginBackground::find($id); - $pic->$view = $newStatus; - if ($pic->store()) { - PageLayout::postSuccess(_('Der Aktivierungsstatus wurde gespeichert.')); - } else { - PageLayout::postSuccess(_('Der Aktivierungsstatus konnte nicht gespeichert werden.')); - } - $this->relocate('admin/loginstyle'); - } - - /** - * Adds the content to sidebar - */ - protected function setSidebar() - { - $sidebar = Sidebar::get(); - - $links = new ActionsWidget(); - $links->addLink( - _('Bild hinzufügen'), - $this->url_for('admin/loginstyle/newpic'), - Icon::create('add', 'clickable') - )->asDialog('size=auto'); - $sidebar->addWidget($links); - } -} diff --git a/app/views/admin/login_style/edit_faq.php b/app/views/admin/login_style/edit_faq.php new file mode 100644 index 0000000..66020a0 --- /dev/null +++ b/app/views/admin/login_style/edit_faq.php @@ -0,0 +1,30 @@ + +
+ + + + + + +
+ +
+ +
diff --git a/app/views/admin/login_style/index.php b/app/views/admin/login_style/index.php index 195cc6a..24c0b70 100644 --- a/app/views/admin/login_style/index.php +++ b/app/views/admin/login_style/index.php @@ -4,28 +4,31 @@ * @var Admin_LoginStyleController $controller */ ?> - 0) : ?> +
+ + + 0) : ?> - - - + + + - - - - - - + + + + + + getDimensions(); - ?> + ?>
filename) ?> @@ -37,34 +40,41 @@ - id}/desktop", (int) !$pic->desktop) ?>"> - desktop ? Icon::ROLE_CLICKABLE : Icon::ROLE_INACTIVE)->asImg(32, [ - 'title' => $pic->desktop - ? _('Bild nicht mehr für die Desktopansicht verwenden') - : _('Bild für die Desktopansicht verwenden') - ]) ?> - - id}/mobile", (int) !$pic->mobile) ?>"> - mobile ? Icon::ROLE_CLICKABLE : Icon::ROLE_INACTIVE)->asImg(32, [ + desktop ? Icon::ROLE_CLICKABLE : Icon::ROLE_INACTIVE)->asInput( + 32, + [ + 'title' => $pic->mobile + ? _('Bild nicht mehr für die Mobilansicht verwenden') + : _('Bild für die Mobilansicht verwenden'), + 'formaction' => $controller->activationURL($pic->id, 'desktop', (int) !$pic->desktop) + ] + )?> + + mobile ? Icon::ROLE_CLICKABLE : Icon::ROLE_INACTIVE)->asInput( + 32, + [ 'title' => $pic->mobile - ? _('Bild nicht mehr für die Mobilansicht verwenden') - : _('Bild für die Mobilansicht verwenden') - ]) ?> - + ? _('Bild nicht mehr für die Mobilansicht verwenden') + : _('Bild für die Mobilansicht verwenden'), + 'formaction' => $controller->activationURL($pic->id, 'mobile', (int) !$pic->mobile) + ] + )?> - in_release): ?> - id}") ?>"> - asImg([ - 'title' => _('Bild löschen'), - 'data-confirm' => _('Soll das Bild wirklich gelöscht werden?'), - ]) ?> - - + in_release): ?> + asInput( + [ + 'title' => _('Bild löschen'), + 'data-confirm' => _('Soll das Bild wirklich gelöscht werden?'), + 'formaction' => $controller->delete_picURL($pic->id) + ] + )?> +
- + - + +
diff --git a/app/views/admin/login_style/login_faq.php b/app/views/admin/login_style/login_faq.php new file mode 100644 index 0000000..4ed19e1 --- /dev/null +++ b/app/views/admin/login_style/login_faq.php @@ -0,0 +1,57 @@ + +
+ + + + + + + + + + + 0) : ?> + + + + + + + + + + + + + +
title) ?> + setContext($entry->title) + ->addLink( + $controller->edit_faqURL($entry), + _('Hinweistext bearbeiten'), + Icon::create('edit'), + ['data-dialog' => 'size=medium'] + )->addButton( + 'delete', + _('Hinweistext löschen'), + Icon::create('trash'), + [ + 'formaction' => $controller->delete_faqURL($entry), + 'data-confirm' => sprintf( + _('Wollen Sie den Hinweistext "%s" wirklich löschen?'), + $entry->title + ), + 'data-dialog' => 'size=auto', + ] + ) + ?> +
+ +
+
diff --git a/app/views/admin/login_style/newpic.php b/app/views/admin/login_style/newpic.php index d5bc74a..c5f7fbf 100644 --- a/app/views/admin/login_style/newpic.php +++ b/app/views/admin/login_style/newpic.php @@ -3,7 +3,8 @@ * @var Admin_LoginStyleController $controller */ ?> -
+ +
@@ -33,6 +34,6 @@
- url_for('loginstyle/index')) ?> + indexURL()) ?>
diff --git a/app/views/new_password/index.php b/app/views/new_password/index.php index 9cddc31..d044629 100644 --- a/app/views/new_password/index.php +++ b/app/views/new_password/index.php @@ -6,7 +6,7 @@
diff --git a/db/migrations/5.5.16_add_tooltip_fields_for_login.php b/db/migrations/5.5.16_add_tooltip_fields_for_login.php new file mode 100644 index 0000000..f4d536b --- /dev/null +++ b/db/migrations/5.5.16_add_tooltip_fields_for_login.php @@ -0,0 +1,71 @@ +prepare($query); + $statement->execute([ + 'name' => 'USERNAME_TOOLTIP_TEXT', + 'value' => 'Geben Sie hier Ihren Stud.IP-Benutzernamen ein.', + 'type' => 'i18n', + 'section' => 'Loginseite', + 'range' => 'global', + 'description' => 'Text für den Tooltip des Benutzernamens auf der Loginseite' + ]); + + $statement->execute([ + 'name' => 'PASSWORD_TOOLTIP_TEXT', + 'value' => 'Geben Sie hier Ihr Stud.IP-Passwort ein. Achten Sie bei der Eingabe auf Groß- und Kleinschreibung.', + 'type' => 'i18n', + 'section' => 'Loginseite', + 'range' => 'global', + 'description' => 'Text für den Tooltip des Benutzernamens auf der Loginseite' + ]); + + $statement->execute([ + 'name' => 'USERNAME_TOOLTIP_ACTIVATED', + 'value' => '1', + 'type' => 'boolean', + 'section' => 'Loginseite', + 'range' => 'global', + 'description' => 'Soll der Tooltip beim Benutzernamen auf der Loginseite sichtbar sein?' + ]); + + $statement->execute([ + 'name' => 'PASSWORD_TOOLTIP_ACTIVATED', + 'value' => '1', + 'type' => 'boolean', + 'section' => 'Loginseite', + 'range' => 'global', + 'description' => 'Soll der Tooltip beim Passwort auf der Loginseite sichtbar sein?' + ]); + + } + + public function down() + { + $query = "DELETE `config`, `config_values`, `i18n` + FROM `config` + LEFT JOIN `config_values` USING (`field`) + LEFT JOIN `i18n` + ON `table` = 'config' + AND `field` = 'value' + AND `object_id` = MD5(`config`.`field`) + WHERE `field` IN ( + 'USERNAME_TOOLTIP_TEXT', + 'PASSWORD_TOOLTIP_TEXT', + 'USERNAME_TOOLTIP_ACTIVATED', + 'PASSWORD_TOOLTIP_ACTIVATED' + )"; + DBManager::get()->exec($query); + } +} diff --git a/db/migrations/5.5.17_add_login_faq_table.php b/db/migrations/5.5.17_add_login_faq_table.php new file mode 100644 index 0000000..2a99909 --- /dev/null +++ b/db/migrations/5.5.17_add_login_faq_table.php @@ -0,0 +1,29 @@ +exec($query); + } + + public function down() + { + DBManager::get()->exec('DROP TABLE IF EXISTS `login_faq`'); + + $query = "DELETE FROM `i18n` + WHERE `table` = 'login_faq`"; + DBManager::get()->exec($query); + } +} diff --git a/db/migrations/5.5.18_add_login_faq_config.php b/db/migrations/5.5.18_add_login_faq_config.php new file mode 100644 index 0000000..3196e34 --- /dev/null +++ b/db/migrations/5.5.18_add_login_faq_config.php @@ -0,0 +1,53 @@ +prepare($query); + $statement->execute([ + 'name' => 'LOGIN_FAQ_TITLE', + 'value' => 'Hinweise zum Login', + 'type' => 'i18n', + 'section' => 'Loginseite', + 'range' => 'global', + 'description' => 'Überschrift für den FAQ-Bereich auf der Loginseite' + ]); + + $statement->execute([ + 'name' => 'LOGIN_FAQ_VISIBILITY', + 'value' => '1', + 'type' => 'boolean', + 'section' => 'Loginseite', + 'range' => 'global', + 'description' => 'Soll der FAQ-Bereich auf der Loginseite sichtbar sein?' + ]); + + } + + public function down() + { + $query = "DELETE `config`, `config_values`, `i18n` + FROM `config` + LEFT JOIN `config_values` USING (`field`) + LEFT JOIN `i18n` + ON `table` = 'config' + AND `field` = 'value' + AND `object_id` = MD5(`config`.`field`) + WHERE `field` IN ( + 'LOGIN_FAQ_TITLE', + 'LOGIN_FAQ_VISIBILITY', + 'USERNAME_TOOLTIP_ACTIVATED', + 'PASSWORD_TOOLTIP_ACTIVATED' + )"; + DBManager::get()->exec($query); + } +} diff --git a/lib/classes/auth_plugins/CASUserDataMapping.php b/lib/classes/auth_plugins/CASUserDataMapping.php index 03c9cd2..166df55 100644 --- a/lib/classes/auth_plugins/CASUserDataMapping.php +++ b/lib/classes/auth_plugins/CASUserDataMapping.php @@ -1,4 +1,4 @@ - \ No newline at end of file diff --git a/lib/classes/auth_plugins/StudipAuthAbstract.class.php b/lib/classes/auth_plugins/StudipAuthAbstract.class.php index d1bcfac..fd2d4e9 100644 --- a/lib/classes/auth_plugins/StudipAuthAbstract.class.php +++ b/lib/classes/auth_plugins/StudipAuthAbstract.class.php @@ -91,6 +91,14 @@ class StudipAuthAbstract public $error_head; /** + * toggles display of standard login + * + * + * @var bool $show_login + */ + public $show_login; + + /** * @var $plugin_instances */ private static $plugin_instances; @@ -121,6 +129,38 @@ class StudipAuthAbstract } /** + * static method to check if SSO login is enabled + * + * @return bool + */ + public static function isSSOEnabled(): bool + { + self::getInstance(); + foreach (self::$plugin_instances as $auth_plugin) { + if ($auth_plugin instanceof StudipAuthSSO) { + return true; + } + } + return false; + } + + /** + * static method to check if standard login is enabled + * + * @return bool + */ + public static function isLoginEnabled(): bool + { + self::getInstance(); + foreach (self::$plugin_instances as $auth_plugin) { + if ($auth_plugin->show_login === true) { + return true; + } + } + return false; + } + + /** * static method to check authentication in all plugins * * if authentication fails in one plugin, the error message is stored and the next plugin is used diff --git a/lib/classes/auth_plugins/StudipAuthLdap.class.php b/lib/classes/auth_plugins/StudipAuthLdap.class.php index 5721cf4..7cb8686 100644 --- a/lib/classes/auth_plugins/StudipAuthLdap.class.php +++ b/lib/classes/auth_plugins/StudipAuthLdap.class.php @@ -40,6 +40,7 @@ class StudipAuthLdap extends StudipAuthAbstract public $username_attribute = 'uid'; public $ldap_filter; public $bad_char_regex = '/[^0-9_a-zA-Z]/'; + public $show_login = true; public $conn = null; public $user_data = null; diff --git a/lib/classes/auth_plugins/StudipAuthStandard.class.php b/lib/classes/auth_plugins/StudipAuthStandard.class.php index 1a2c2ba..5bb3e65 100644 --- a/lib/classes/auth_plugins/StudipAuthStandard.class.php +++ b/lib/classes/auth_plugins/StudipAuthStandard.class.php @@ -37,6 +37,7 @@ class StudipAuthStandard extends StudipAuthAbstract { var $bad_char_regex = false; + public $show_login = true; /** * diff --git a/lib/models/LoginFaq.class.php b/lib/models/LoginFaq.class.php new file mode 100644 index 0000000..036f0f7 --- /dev/null +++ b/lib/models/LoginFaq.class.php @@ -0,0 +1,32 @@ + + * @copyright 2023 data-quest + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 5.5 +*/ +class LoginFaq extends SimpleORMap +{ + /** + * @param array $config + */ + protected static function configure($config = []) + { + $config['db_table'] = 'login_faq'; + + $config['i18n'] = ['title', 'description']; + + parent::configure($config); + } +} diff --git a/lib/navigation/AdminNavigation.php b/lib/navigation/AdminNavigation.php index 12bf53a..7d9cd2d 100644 --- a/lib/navigation/AdminNavigation.php +++ b/lib/navigation/AdminNavigation.php @@ -110,7 +110,6 @@ class AdminNavigation extends Navigation } $navigation->addSubNavigation('sem_classes', new Navigation(_('Veranstaltungskategorien'), 'dispatch.php/admin/sem_classes/overview')); - $navigation->addSubNavigation('loginstyle', new Navigation(_('Startbildschirm'), 'dispatch.php/admin/loginstyle')); $navigation->addSubNavigation( 'content_terms_of_use', new Navigation( @@ -157,7 +156,6 @@ class AdminNavigation extends Navigation 'dispatch.php/admin/accessibility_info_text/index' ) ); - } if ($GLOBALS['perm']->have_perm('admin')) { @@ -165,6 +163,17 @@ class AdminNavigation extends Navigation $navigation->addSubNavigation('stock_images', $pool); } + if ($perm->have_perm('root')) { + $navigation->addSubNavigation( + 'loginstyle', + new Navigation( + _('Startseite'), + 'dispatch.php/admin/login_style' + ) + ); + + } + $this->addSubNavigation('locations', $navigation); // global config / user administration diff --git a/lib/navigation/LoginNavigation.php b/lib/navigation/LoginNavigation.php index db21236..96ec90e 100644 --- a/lib/navigation/LoginNavigation.php +++ b/lib/navigation/LoginNavigation.php @@ -23,13 +23,20 @@ class LoginNavigation extends Navigation { parent::initSubNavigation(); - $navigation = new Navigation(_('Login'), 'index.php?again=yes'); - $navigation->setDescription(_('für registrierte NutzerInnen')); - $this->addSubNavigation('login', $navigation); - + $standard_login_active = false; foreach (StudipAuthAbstract::getInstance() as $auth_plugin) { + if ($auth_plugin->show_login && !$standard_login_active) { + $navigation = new Navigation(_('Login'), ''); + $navigation->setDescription($auth_plugin->login_description ?: _('für registrierte Nutzende')); + $navigation->setLinkAttributes([ + 'id' => 'toggle-login' + ]); + $navigation->setURL('#login-form'); + $this->addSubNavigation('standard_login', $navigation); + $standard_login_active = true; + } if ($auth_plugin instanceof StudipAuthSSO && isset($auth_plugin->login_description)) { - $navigation = new Navigation($auth_plugin->plugin_fullname . ' ' . _('Login'), 'index.php?again=yes&sso=' . $auth_plugin->plugin_name); + $navigation = new Navigation($auth_plugin->plugin_fullname . ' ' . _('Login'), '?sso=' . $auth_plugin->plugin_name); $navigation->setDescription($auth_plugin->login_description); $this->addSubNavigation('login_' . $auth_plugin->plugin_name, $navigation); } @@ -37,7 +44,7 @@ class LoginNavigation extends Navigation if (Config::get()->ENABLE_SELF_REGISTRATION) { $navigation = new Navigation(_('Registrieren'), 'register1.php'); - $navigation->setDescription(_('um NutzerIn zu werden')); + $navigation->setDescription(_('um das System erstmalig zu nutzen')); $this->addSubNavigation('register', $navigation); } diff --git a/lib/navigation/StartNavigation.php b/lib/navigation/StartNavigation.php index 3d3719e..df94412 100644 --- a/lib/navigation/StartNavigation.php +++ b/lib/navigation/StartNavigation.php @@ -22,7 +22,7 @@ class StartNavigation extends Navigation { $url = (is_object($GLOBALS['user']) && $GLOBALS['user']->id != 'nobody') ? 'dispatch.php/start' - : 'index.php'; + : 'index.php?again=yes'; parent::__construct(_('Start'), $url); } diff --git a/lib/phplib/Seminar_Auth.class.php b/lib/phplib/Seminar_Auth.class.php index 30a6d46..80f6cea 100644 --- a/lib/phplib/Seminar_Auth.class.php +++ b/lib/phplib/Seminar_Auth.class.php @@ -301,6 +301,27 @@ class Seminar_Auth throw new AccessDeniedException(); } + // if desired, switch to high contrast stylesheet and store when user logs in + if (Request::get('unset_contrast')) { + unset($_SESSION['contrast']); + PageLayout::removeStylesheet('accessibility.css'); + + } + if (Request::get('set_contrast') ) { + $_SESSION['contrast'] = true; + PageLayout::addStylesheet('accessibility.css'); + + } + + // evaluate language clicks + // has to be done before seminar_open to get switching back to german (no init of i18n at all)) + if (Request::get('set_language')) { + if (array_key_exists(Request::get('set_language'), $GLOBALS['INSTALLED_LANGUAGES'])) { + $_SESSION['forced_language'] = Request::get('set_language'); + $_SESSION['_language'] = Request::get('set_language'); + } + } + $this->check_environment(); PageLayout::setBodyElementId('login'); @@ -322,6 +343,13 @@ class Seminar_Auth $login_template->set_attribute('error_msg', $this->error_msg); $login_template->set_attribute('uname', (isset($this->auth["uname"]) ? $this->auth["uname"] : Request::username('loginname'))); $login_template->set_attribute('self_registration_activated', Config::get()->ENABLE_SELF_REGISTRATION); + + $query = "SHOW TABLES LIKE 'login_faq'"; + $result = DBManager::get()->query($query); + + if ($result && $result->rowCount() > 0) { + $login_template->set_attribute('faq_entries', LoginFaq::findBySQL("1")); + } } PageLayout::setHelpKeyword('Basis.AnmeldungLogin'); $header_template = $GLOBALS['template_factory']->open('header'); diff --git a/lib/seminar_open.php b/lib/seminar_open.php index c3e15c3..0ca9991 100644 --- a/lib/seminar_open.php +++ b/lib/seminar_open.php @@ -105,6 +105,13 @@ if ($auth->is_authenticated() && is_object($user) && $user->id != "nobody") { UserConfig::get($GLOBALS['user']->id)->store('USER_HIGH_CONTRAST', $_SESSION['contrast']); unset($_SESSION['contrast']); } + // store last language click + if (!empty($_SESSION['forced_language'])) { + User::findCurrent()->preferred_language = $_SESSION['forced_language']; + User::findCurrent()->store(); + $_SESSION['_language'] = $_SESSION['forced_language']; + } + $_SESSION['forced_language'] = null; $user_did_login = true; } diff --git a/locale/en/LC_MESSAGES/studip.po b/locale/en/LC_MESSAGES/studip.po index 0ff799a..29266c9 100644 --- a/locale/en/LC_MESSAGES/studip.po +++ b/locale/en/LC_MESSAGES/studip.po @@ -41881,7 +41881,7 @@ msgid "Sie haben keine alten empfangenen Nachrichten" msgstr "You do not have any old received messages" #: lib/navigation/LoginNavigation.php:27 -msgid "für registrierte NutzerInnen" +msgid "für registrierte Nutzende" msgstr "for registered users" #: lib/navigation/LoginNavigation.php:40 diff --git a/public/index.php b/public/index.php index c32fe49..2a38355 100644 --- a/public/index.php +++ b/public/index.php @@ -59,51 +59,9 @@ if ($auth->is_authenticated() && $user->id != 'nobody') { closeObject(); include 'lib/seminar_open.php'; // initialise Stud.IP-Session +$auth->login_if($user->id === 'nobody'); // if new start page is in use, redirect there (if logged in) if ($auth->is_authenticated() && $user->id != 'nobody') { header('Location: ' . URLHelper::getURL('dispatch.php/start')); - die; } - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * L O G I N - P A G E ( N O B O D Y - U S E R ) * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -PageLayout::setHelpKeyword("Basis.Startseite"); // set keyword for new help -PageLayout::setTitle(_("Startseite")); -Navigation::activateItem('/start'); -PageLayout::setTabNavigation(NULL); // disable display of tabs - -// Start of Output -include 'lib/include/html_head.inc.php'; // Output of html head -include 'lib/include/header.php'; - -// Prüfen, ob PortalPlugins vorhanden sind. -// TODO: Remove for Stud.IP 6.0 -/** @deprecated */ -$portalplugins = PluginEngine::getPlugins('PortalPlugin'); -$layout = $GLOBALS['template_factory']->open('shared/index_box'); - -$plugin_contents = []; -foreach ($portalplugins as $portalplugin) { - $template = $portalplugin->getPortalTemplate(); - - if ($template) { - $plugin_contents[] = $template->render(NULL, $layout); - $layout->clear_attributes(); - } -} - - -$index_nobody_template = $GLOBALS['template_factory']->open('index_nobody'); -$index_nobody_template->set_attributes([ - 'plugin_contents' => $plugin_contents, - 'logout' => Request::bool('logout'), -]); - -echo $index_nobody_template->render(); - -page_close(); - -include 'lib/include/html_end.inc.php'; diff --git a/resources/assets/javascripts/bootstrap/application.js b/resources/assets/javascripts/bootstrap/application.js index 1629b9b..830f2c0 100644 --- a/resources/assets/javascripts/bootstrap/application.js +++ b/resources/assets/javascripts/bootstrap/application.js @@ -355,3 +355,68 @@ jQuery(document).on('click', 'a[data-behaviour~="ajax-toggle"]', function (event $('#open_variable').attr('value', $(this).parent('fieldset').data('open')); }); }(jQuery)); + +STUDIP.domReady(function () { + const loginForm = document.getElementById('login-form'); + if (!loginForm) { + return; + } + + const passwordInput = document.getElementById('password'); + const usernameInput = document.getElementById('loginname'); + const passwordCapsText = document.getElementById('password-caps'); + const iconPasswordVisible = document.getElementById('visible-password'); + const iconPasswordInVisible = document.getElementById('invisible-password'); + + [usernameInput, passwordInput].forEach((input) => { + input.addEventListener('keydown', (event) => { + if (event.getModifierState('CapsLock')) { + passwordCapsText.style.display = 'block'; + } else { + passwordCapsText.style.display = 'none'; + } + }); + }); + + const toggleLogin = document.getElementById('toggle-login'); + if (toggleLogin) { + loginForm.addEventListener('transitionend', (event) => { + if (event.propertyName !== 'max-height') { + return; + } + + if (!loginForm.classList.contains('hide')) { + usernameInput.scrollIntoView({ + behavior: 'smooth' + }); + usernameInput.focus(); + } else { + loginForm.setAttribute('hidden', ''); + } + }); + + toggleLogin.addEventListener('click', (event) => { + if (loginForm.classList.contains('hide')) { + loginForm.removeAttribute('hidden'); + } + + setTimeout(() => { + loginForm.classList.toggle('hide'); + }, 0); + + event.preventDefault(); + }); + } + + document.getElementById('password-toggle').addEventListener('click', () => { + if (passwordInput.type === 'password') { + passwordInput.type = 'text'; + iconPasswordVisible.style.display = 'none'; + iconPasswordInVisible.style.display = ''; + } else { + passwordInput.type = 'password'; + iconPasswordVisible.style.display = ''; + iconPasswordInVisible.style.display = 'none'; + } + }); +}); diff --git a/resources/assets/stylesheets/scss/index.scss b/resources/assets/stylesheets/scss/index.scss index 2ecda5c..e9234de 100644 --- a/resources/assets/stylesheets/scss/index.scss +++ b/resources/assets/stylesheets/scss/index.scss @@ -13,6 +13,16 @@ $gap-between-boxes: calc($login-page-margin / 2); #content { grid-column: 1 / 3; grid-row: 2 / 2; + + &.loginpage { + display: flex; + flex-direction: row; + flex-wrap: wrap; + column-gap: 20px; + row-gap: 20px; + align-items: flex-start; + flex-basis: 450px; + } } #background-desktop { @@ -36,14 +46,24 @@ $gap-between-boxes: calc($login-page-margin / 2); } } +#login_flex { + display: flex; + flex-direction: row; + column-gap: 20px; + flex-wrap: wrap; + row-gap: 20px; + align-items: flex-start; +} + #loginbox { - background-color: rgba(255, 255, 255, 0.8); - box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5); + background-color: var(--white); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.5); padding: 20px; width: 450px; + float: left; header { - margin: 10px 0 0 10px; + margin: 0 0 0 0; h1 { border-bottom: 0; @@ -56,7 +76,7 @@ $gap-between-boxes: calc($login-page-margin / 2); list-style-type: none; margin: 0; width: 450px; - padding: 0 10px; + padding-inline-start: 0; .login_link { display: inline-block; @@ -93,6 +113,10 @@ $gap-between-boxes: calc($login-page-margin / 2); } } + #contrast { + padding-bottom: 0; + } + div.login_info { border-top: 1px solid var(--light-gray-color); font-size: 0.8em; @@ -110,6 +134,38 @@ $gap-between-boxes: calc($login-page-margin / 2); margin-left: 12px; } } + + + input#loginname, + input#password { + display: initial; + } + + input#password { + padding-right: 28px; + } + + #password-toggle { + position: absolute; + right: 7px; + bottom: 0; + cursor: pointer; + + #visible-password, + #invisible-password { + } + } +} + +#faq_box { + background-color: var(--white); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.5); + padding: 20px; + width: 450px; + float: left; + > header { + margin: 0 0 0 0; + } } #login-plugin-contents { @@ -128,3 +184,24 @@ $gap-between-boxes: calc($login-page-margin / 2); width: 418px; } } + +::-ms-reveal { + display: none; +} + + +#login-form { + max-height: 300px; + overflow: hidden; + transition: max-height var(--transition-duration-slow) linear; + + &.hide { + max-height: 0px; + } + + #submit_login { + margin-top: 0 !important; + float: left !important; + + } +} diff --git a/resources/assets/stylesheets/scss/responsive.scss b/resources/assets/stylesheets/scss/responsive.scss index a1f2a67..01c7403 100644 --- a/resources/assets/stylesheets/scss/responsive.scss +++ b/resources/assets/stylesheets/scss/responsive.scss @@ -897,7 +897,8 @@ html:not(.responsive-display):not(.fullscreen-mode) { left: 0; } - #loginbox { + #loginbox, + #faq_box { box-shadow: unset; margin: 0; width: calc(100vw - 40px); @@ -912,6 +913,10 @@ html:not(.responsive-display):not(.fullscreen-mode) { } } } + + #faq_box { + margin: -20px 0 0 0; + } } } diff --git a/resources/assets/stylesheets/scss/variables.scss b/resources/assets/stylesheets/scss/variables.scss index ef5329e..a915e76 100644 --- a/resources/assets/stylesheets/scss/variables.scss +++ b/resources/assets/stylesheets/scss/variables.scss @@ -43,6 +43,7 @@ $drag_and_drop_z_index: 1000; $drag_and_drop_border: 1px solid $base-color; $transition-duration: .3s; +$transition-duration-slow: .5s; // Layout $page-margin: 15px; @@ -171,8 +172,10 @@ $grid-gap: 0; #{"--"}group-color-8: $brown; #{"--"}transition-duration: $transition-duration; + #{"--"}transition-duration-slow: $transition-duration-slow; @media (prefers-reduced-motion) { #{"--"}transition-duration: 0s; + #{"--"}transition-duration-slow: 0s; } } diff --git a/templates/_standard_loginform.php b/templates/_standard_loginform.php new file mode 100644 index 0000000..0a8be59 --- /dev/null +++ b/templates/_standard_loginform.php @@ -0,0 +1,76 @@ + + +
+> +
+ + + +
+ + + + + +
+ 'submit_login']); ?> + + ENABLE_REQUEST_NEW_PASSWORD_BY_USER && in_array('Standard', $GLOBALS['STUDIP_AUTH_PLUGIN'])): ?> + + + "> + + + +
+
diff --git a/templates/loginform.php b/templates/loginform.php index 348ccee..deb9768 100644 --- a/templates/loginform.php +++ b/templates/loginform.php @@ -1,6 +1,8 @@ -
+
- - - %1$s'), - $GLOBALS['UNI_CONTACT'] - ) - ]) ?> - - +
+ + + %1$s'), + $GLOBALS['UNI_CONTACT'] + ) + ]) ?> + -
-
+ + +
-

- -

+

UNI_NAME_CLEAN) ?>

-
- -
-
- -
- - - - - 1], true)) ?> - -
- ENABLE_REQUEST_NEW_PASSWORD_BY_USER && in_array('Standard', $GLOBALS['STUDIP_AUTH_PLUGIN'])): ?> - - - "> - - - - - / - - - - + + render_partial('_standard_loginform', [ + 'hidden' => false, + ]) ?> + + + + + render_partial('_standard_loginform', [ + 'hidden' => empty($loginerror), + ]) ?> + + +
+ + + + +
+ $temp_language): ?> + $temp_language['name'], 'size' => '24']) ?> + + + + +
+ +
+ + asImg(24) ?> + + + + asImg(24) ?> + + + +
+ +
+ LOGIN_FAQ_VISIBILITY && count($faq_entries) > 0) : ?> +
+

LOGIN_FAQ_TITLE) ?>

+ + + +
+ +
+ +