Commit 8837d082 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Passage à Bootstrap 5

parent b0f595f5
Pipeline #13003 passed with stage
in 34 seconds
CHANGELOG
=========
5.0.0
-----
- Migration vers Bootstrap 5 (front-end).
- Infos à propos de la connexion dans le menu principal : simple bouton 'Connexion' si aucun utilisateur connecté ;
bouton 'Déconnexion' déplacé dans le popover.
4.0.2
-----
- RoleFormatter : correction pour exploiter la méthode __toString() si présente.
......@@ -13,6 +19,7 @@ CHANGELOG
-----
- Passage de Zend à Laminas
3.2.10
-----
- Possibilité d'activer ou non (en config) les logs des échecs d'authentification LDAP.
......
......@@ -8,7 +8,7 @@
}
],
"require": {
"unicaen/app": "^4.0",
"unicaen/app": "^5.0",
"unicaen/bjy-authorize": "^4.0",
"jasig/phpcas": "^1.3",
"ramsey/uuid": "^3.7",
......
......@@ -35,4 +35,4 @@ class AuthFactory
return $storage;
}
}
}
\ No newline at end of file
<?php
namespace UnicaenAuth\View\Helper;
/**
* Aide de vue générant le lien et les infos concernant la connexion à l'application.
* Aide de vue dessinant :
* - le nom de l'utilisateur connecté éventuel ;
* - le lien de connexion/déconnexion.
*
* @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
* @author Unicaen
*/
class AppConnection extends \UnicaenApp\View\Helper\AppConnection
{
/**
* Retourne le code HTML généré par cette aide de vue.
*
* @return string
*/
public function __toString()
public function __toString(): string
{
$connexion = [];
$parts = [];
if (($tmp = $this->getView()->plugin('userCurrent'))) {
$connexion[] = "" . $tmp;
/** @var \UnicaenAuth\View\Helper\UserCurrent $userCurrentHelper */
$userCurrentHelper = $this->getView()->plugin('userCurrent');
if ($userCurrentHelper) {
if ($html = "" . $userCurrentHelper) {
$parts[] = $html;
}
}
if (($tmp = $this->getView()->plugin('userConnection'))) {
$connexion[] = "" . $tmp;
/** @var \UnicaenAuth\View\Helper\UserConnection $userConnectionHelper */
$userConnectionHelper = $this->getView()->plugin('userConnection');
if ($userConnectionHelper) {
// On ne dessine que le lien de Connexion.
// Le lien de déconnexion est dessiné dans le popover dédié à l'utilisateur connecté.
if ($html = $userConnectionHelper->addClass('btn btn-success')->renderConnection()) {
$parts[] = $html;
}
}
return implode(' | ', $connexion);
return implode(' | ', $parts);
}
}
\ No newline at end of file
<?php
namespace UnicaenAuth\View\Helper;
/**
* Aide de vue de génération du lien de connexion/déconnexion à l'appli selon qu'un
* utilisateur est connecté ou pas.
* Aide de vue dessinant :
* - soit le lien de connexion à l'appli : {@see renderConnection()}.
* - soit le lien de déconnexion : {@see renderDisconnection()}.
* - soit l'un ou l'autre selon qu'un utilisateur est connecté ou pas : {@see render()}.
*
* @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
* @author Unicaen
*/
class UserConnection extends UserAbstract
{
/**
* Point d'entrée.
*
* @return UserConnection
*/
public function __invoke()
protected $template = '<a class="%s" href="%s" title="%s">%s</a>';
protected $classes = [
'user-connection',
//'navbar-link',
];
public function __invoke(): self
{
return $this;
}
public function addClass(string $class): self
{
$this->classes[] = $class;
return $this;
}
/**
* Retourne le code HTML généré par cette aide de vue.
*
* @return string
*/
public function __toString()
{
$template = '%s';
$out = sprintf($template, $this->createConnectionLink());
return $this->render();
}
public function render(): string
{
if ($this->getIdentity()) {
return $this->renderDisconnection();
} else {
return $this->renderConnection();
}
}
/**
* @return string Lien de connexion, ou '' si l'utilisateur est déjà connecté.
*/
public function renderConnection(): string
{
if ($this->getIdentity()) {
return '';
}
$urlHelper = $this->getView()->plugin('url');
$href = $urlHelper('zfcuser/login');
$lib = "Connexion";
$title = "Cliquez pour vous authentifier";
if ($this->getTranslator()) {
$lib = $this->getTranslator()->translate($lib, $this->getTranslatorTextDomain());
$title = $this->getTranslator()->translate($title, $this->getTranslatorTextDomain());
}
$classes = implode(' ', $this->classes);
return sprintf($this->template, $classes, $href, $title, $lib);
}
/**
* @return string Lien de déconnexion, ou '' si aucun utilisateur n'est connecté.
*/
public function renderDisconnection(): string
{
if (!$this->getIdentity()) {
return '';
}
$urlHelper = $this->getView()->plugin('url');
$href = $urlHelper('zfcuser/logout');
$lib = "Déconnexion";
$title = "Cliquez pour vous déconnecter";
if ($this->getTranslator()) {
$lib = $this->getTranslator()->translate($lib, $this->getTranslatorTextDomain());
$title = $this->getTranslator()->translate($title, $this->getTranslatorTextDomain());
}
$classes = implode(' ', $this->classes);
return $out;
return sprintf($this->template, $classes, $href, $title, $lib);
}
/**
*
* @return string
* @deprecated Utiliser {@see render()}
*/
protected function createConnectionLink()
{
......@@ -42,11 +106,11 @@ class UserConnection extends UserAbstract
$urlHelper = $this->getView()->plugin('url');
$template = '<a class="navbar-link user-connection" href="%s" title="%s">%s</a>';
$template = '<a class="btn btn-lg btn-outline-primary navbar-link user-connection" href="%s" title="%s">%s</a>';
if (!$identity) {
$href = $urlHelper('zfcuser/login');
$lib = "Connexion";
$title = "Affiche le formulaire d'authentification";
$title = "Cliquez pour vous authentifier";
}
else {
$href = $urlHelper('zfcuser/logout');
......
......@@ -2,11 +2,14 @@
namespace UnicaenAuth\View\Helper;
/**
* Aide de vue affichant toutes les infos concernant l'utilisateur courant.
* C'est à dire :
* - "Aucun" + lien de connexion OU BIEN nom de l'utilisateur connecté + lien de déconnexion
* - profil de l'utilisateur connecté
* - infos administratives sur l'utilisateur
* Aide de vue affichant toutes les infos concernant l'utilisateur connecté.
* C'est à dire le nom de l'utilisateur connecté & son rôle courant.
*
* Lorsqu'on clique sur le nom, s'affiche un popover
* présentant les rôles sélectionnables, des infos administratives, le formulaire d'usurpation (si habilité)
* et le lien de déconnexion.
*
* Si aucun utilisateur n'est connecté, cette aide de vue renvoit ''.
*
* @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
*/
......@@ -37,65 +40,74 @@ class UserCurrent extends UserAbstract
*/
public function __toString(): string
{
$id = 'user-current-info';
$userStatusHelper = $this->getView()->plugin('userStatus'); /* @var $userStatusHelper UserStatus */
$status = $userStatusHelper(false);
if (! $this->getIdentity()) {
return '';
}
//
// Sous la forme d'un lien :
//
// - Nom de l'utilisateur connecté, le cas échéant.
/* @var $userStatusHelper UserStatus */
$userStatusHelper = $this->view->plugin('userStatus');
$nom = $userStatusHelper(false);
// - Rôle courant.
$role = '';
$userProfileSelectable = true;
if ($this->getIdentity()) {
if ($userProfileSelectable) {
$role = $this->getUserContext()->getSelectedIdentityRole();
// cas où aucun rôle n'est sélectionné : on affiche le 1er rôle sélectionnable ou sinon "user"
if ($role === null) {
$selectableRoles = $this->getUserContext()->getSelectableIdentityRoles();
$role = current($selectableRoles) ?: $this->getUserContext()->getIdentityRole('user');
}
$status .= sprintf(", <small class='role-libelle'>%s</small>", !method_exists($role, '__toString') ? $role->getRoleId() : $role);
}
$userProfileHelper = $this->getView()->plugin('userProfile'); /* @var $userProfileHelper UserProfile */
$userProfileHelper->setUserProfileSelectable($userProfileSelectable);
$userInfoHelper = $this->getView()->plugin('userInfo'); /* @var $userInfoHelper UserInfo */
$content = $userProfileHelper . $userInfoHelper($this->getAffectationFineSiDispo());
}
else {
$content = _("Aucun");
if ($this->getTranslator()) {
$content = $this->getTranslator()->translate($content, $this->getTranslatorTextDomain());
if ($userProfileSelectable) {
$role = $this->getUserContext()->getSelectedIdentityRole();
// cas où aucun rôle n'est sélectionné : on affiche le 1er rôle sélectionnable ou sinon "user"
if ($role === null) {
$selectableRoles = $this->getUserContext()->getSelectableIdentityRoles();
$role = current($selectableRoles) ?: $this->getUserContext()->getIdentityRole('user');
}
$role = sprintf(
"<br/><small class='role-libelle'>%s</small>",
!method_exists($role, '__toString') ? $role->getRoleId() : $role
);
}
$content = htmlspecialchars(preg_replace('/\r\n|\n|\r/', '', $content));
//
// Dans un popover :
//
// - Rôles sélectionnables.
/* @var \UnicaenAuth\View\Helper\UserProfile $userProfileHelper */
$userProfileHelper = $this->view->plugin('userProfile');
$userProfileHelper->setUserProfileSelectable($userProfileSelectable);
$roles = (string) $userProfileHelper;
// - Infos administratives.
/* @var \UnicaenAuth\View\Helper\UserInfo $userInfoHelper */
$userInfoHelper = $this->view->plugin('userInfo');
$infos = (string) $userInfoHelper($this->getAffectationFineSiDispo());
// - Usurpation d'identité.
/* @var \UnicaenAuth\View\Helper\UserUsurpationHelper $userUsurpationHelper */
$userUsurpationHelper = $this->view->plugin('userUsurpation');
$usurpation = (string) $userUsurpationHelper;
// - Lien de déconnexion.
/** @var \UnicaenAuth\View\Helper\UserConnection $userConnectionHelper */
$userConnectionHelper = $this->getView()->plugin('userConnection');
$deconnexion = $userConnectionHelper->addClass('btn btn-lg btn-outline-success')->renderDisconnection();
$title = _("Utilisateur connecté à l'application");
if ($this->getTranslator()) {
$title = $this->getTranslator()->translate($title, $this->getTranslatorTextDomain());
}
$linkText = $nom . $role;
$popoverContent = htmlspecialchars(preg_replace('/\r\n|\n|\r/', '',
$roles .
$infos .
$usurpation .
'<hr>' .
'<div class="text-center">' . $deconnexion . '</div>'
));
$out = <<<EOS
<a class="navbar-link"
id="$id"
title="$title"
data-placement="bottom"
data-toggle="popover"
data-html="true"
data-content="$content"
href="#">$status<span class="caret"></span></a>
EOS;
$out .= PHP_EOL;
$js = <<<EOS
$(function() {
$("#$id").popover({ html: true, sanitize: false, container: '#navbar' });
});
return <<<EOS
<a class="navbar-link dropdown-toggle"
id="user-current-info"
data-bs-placement="bottom"
data-bs-toggle="popover"
data-bs-container="#navbar"
data-bs-html="true"
data-bs-sanitize="false"
data-bs-content="$popoverContent"
href="#">$linkText<span class="caret"></span></a>
EOS;
$this->getView()->plugin('inlineScript')->offsetSetScript(1000, $js);
return $out;
}
/**
......
......@@ -96,17 +96,14 @@ class UserInfo extends UserAbstract
}
}
else {
$template = "<strong>Informations administratives :</strong> <br>%s";
$aucuneAffDispo = _("Aucune information disponible.");
if ($this->getTranslator()) {
$aucuneAffDispo = $this->getTranslator()->translate($aucuneAffDispo, $this->getTranslatorTextDomain());
}
$out .= $aucuneAffDispo;
$out .= sprintf($template, $aucuneAffDispo);
}
// formulaire d'usurpation d'identité
$userUsurpationHelper = $this->view->plugin('userUsurpation'); /* @var $userUsurpationHelper \UnicaenAuth\View\Helper\UserUsurpationHelper */
$out .= $userUsurpationHelper();
return $out;
}
......
......@@ -36,7 +36,7 @@ class UserProfile extends UserAbstract
*/
public function render()
{
$title = _("Profil utilisateur");
$title = _("Rôle utilisateur");
$unknown = _("Inconnu");
$none = _("Aucun");
......
......@@ -2,6 +2,7 @@
namespace UnicaenAuth\View\Helper;
use Laminas\Permissions\Acl\Role\RoleInterface;
use Laminas\View\Helper\HeadScript;
/**
* Aide de vue permettant à l'utilisateur de sélectionner son profil courant parmi
......@@ -79,18 +80,15 @@ class UserProfileSelect extends UserAbstract
$url = $this->getView()->url('utilisateur/default', ['action' => 'selectionner-profil']);
$redirectUrl = $this->getView()->url($this->redirectRoute ?: 'home');
$html .= <<<EOS
<script>
$(function() {
$("input.$inputClass").change(function() { submitProfile(); }).tooltip({ delay: 500, placement: 'left' });
});
function submitProfile()
{
$js = <<<EOS
$(function() {
$("body").on("change", "input.$inputClass", function() {
$("body *").css('cursor', 'wait');
$.post("$url", $(".$formClass").serializeArray(), function() { $(location).attr('href', "$redirectUrl"); });
}
</script>
});
});
EOS;
$this->view->inlineScript(HeadScript::SCRIPT, $js);
return $html;
}
......
......@@ -26,8 +26,6 @@ class UserStatus extends UserAbstract
protected $usurpationEnCours = false;
/**
* Retourne l'instance de ce view helper.
*
* @param boolean $displayConnectionLink Inclure ou pas le lien de connexion/déconnexion ?
* @return self
*/
......@@ -40,18 +38,21 @@ class UserStatus extends UserAbstract
}
/**
* Retourne le code HTML généré par cette aide de vue.
*
* @return string
*/
public function __toString(): string
{
$parts = [];
$parts[] = $this->createStatusContainer();
// Nom de l'utilisateur connecté, le cas échéant.
if (($identity = $this->getIdentity())) {
$parts[] = $this->createStatusContainer();
}
if ($this->getDisplayConnectionLink()) {
$userConnectionHelper = $this->getView()->plugin('userConnection'); /* @var $userConnectionHelper UserConnection */
// Lien de connexion ou déconnexion, si demandé.
if ($this->displayConnectionLink) {
/* @var $userConnectionHelper UserConnection */
$userConnectionHelper = $this->getView()->plugin('userConnection');
$parts[] = (string) $userConnectionHelper;
}
......@@ -66,35 +67,29 @@ class UserStatus extends UserAbstract
*/
protected function createStatusContainer(): string
{
if (($identity = $this->getIdentity())) {
if (method_exists($identity, '__toString')) {
$name = (string) $identity;
}
elseif (method_exists($identity, 'getDisplayName')) {
$name = $identity->getDisplayName();
}
elseif (method_exists($identity, 'getUsername')) {
$name = $identity->getUsername();
}
elseif (method_exists($identity, 'getId')) {
$name = $identity->getId();
}
else {
$name = sprintf('<span title="Erreur: identité inattendue (%s)">???</span>',
is_object($identity) ? get_class($identity) : gettype($identity));
}
$identity = $this->getIdentity();
if (method_exists($identity, '__toString')) {
$name = (string) $identity;
}
elseif (method_exists($identity, 'getDisplayName')) {
$name = $identity->getDisplayName();
}
elseif (method_exists($identity, 'getUsername')) {
$name = $identity->getUsername();
}
elseif (method_exists($identity, 'getId')) {
$name = $identity->getId();
}
else {
$name = _("Vous n'êtes pas connecté(e)");
if ($this->getTranslator()) {
$name = $this->getTranslator()->translate($name, $this->getTranslatorTextDomain());
}
$name = sprintf('<span title="Erreur: identité inattendue (%s)">???</span>',
is_object($identity) ? get_class($identity) : gettype($identity));
}
$classUser = $this->usurpationEnCours ? 'fa-theater-masks' : 'fa-user';
return <<<EOS
<span class="fa $classUser"></span> <span id="user-status-name"><strong>$name</strong></span>
<span id="user-status-icon" class="fa $classUser"></span> <span id="user-status-name"><strong>$name</strong></span>
EOS;
}
......
......@@ -11,6 +11,7 @@ use Laminas\Form\Element\Text;
use Laminas\Form\Form;
use Laminas\Form\View\Helper\Form as FormHelper;
use Laminas\Form\View\Helper\FormElement;
use Laminas\View\Helper\HeadScript;
use Laminas\View\Renderer\PhpRenderer;
/**
......@@ -137,34 +138,38 @@ class UserUsurpationHelper extends UserAbstract
$identity = $form->get('identity');
$submit = $form->get('submit');
$identity->setAttribute('class', $identity->getAttribute('class') . ' form-control');
/** @var FormHelper $formHelper */
$formHelper = $this->view->plugin('form');
/** @var FormControlGroup $formControlGroupHelper */
$formControlGroupHelper = $this->view->plugin('formControlGroup');
/** @var FormElement $formElementHelper */
$formElementHelper = $this->view->plugin('formElement');
$html = '';
$html .= $formHelper->openTag($form);
$html .= "<div><strong>Usurpation d'identité :</strong></div>";
$html .= $formControlGroupHelper->__invoke($identity);
$html .= $formControlGroupHelper->__invoke($submit);
$html .= '<div class="row justify-content-start">';
$html .= '<div class="col-9 pe-1">' . $formElementHelper->__invoke($identity) . '</div>' . PHP_EOL;
$html .= '<div class="col-3 ps-0">' . $formElementHelper->__invoke($submit) . '</div>' . PHP_EOL;
$html .= '</div>';
$html .= $formHelper->closeTag();
$formId = $form->getAttribute('id');
$html .= <<<EOS
<script>
var form = $("#$formId").submit(function() {
$("body *").css('cursor', 'wait');
});
var input = form.find(".user-usurpation-input").on('input', function() {
updateUsurpationSubmit();
});
function updateUsurpationSubmit() {
form.find(".user-usurpation-submit").prop("disabled", input.val().length === 0);
}
updateUsurpationSubmit();
</script>
$js = <<<EOS
$(function() {
// le bouton 'Usurper' est interdit si le champ de saisie est vide
$("#$formId .user-usurpation-submit").attr("disabled", true);
$("body")
.on("input", "#$formId .user-usurpation-input", function() {
$(".user-usurpation-submit").prop("disabled", $(this).val().length == 0);
})
.on("submit", "#$formId", function() {
$("body *").css('cursor', 'wait');
});
});
EOS;
$this->view->inlineScript(HeadScript::SCRIPT, $js);
return $html;
}
......@@ -209,6 +214,7 @@ EOS;
$submit = new Submit('submit');
$submit->setValue("Usurper");
$submit->setAttributes([
'disabled' => !$this->asButton,
'class' => 'user-usurpation-submit btn btn-danger',
]);
......
......@@ -28,10 +28,10 @@ use Laminas\Form\Form;
<?php if ($messages = $this->flashMessenger('zfcuser-login-form')): ?>
<?php foreach ($messages as $message): ?>
<div class="messenger alert alert-danger ">
<button type="button" class="close" title="Fermer cette alerte" data-dismiss="alert">×</button>
<