1a6bf47aaSNickeau<?php 2a6bf47aaSNickeau/** 3a6bf47aaSNickeau * Action Component 4a6bf47aaSNickeau * Add a button in the edit toolbar 5a6bf47aaSNickeau * 6a6bf47aaSNickeau * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7a6bf47aaSNickeau * @author Nicolas GERARD 8a6bf47aaSNickeau */ 9a6bf47aaSNickeau 10a6bf47aaSNickeauuse ComboStrap\Identity; 1104fd306cSNickeauuse ComboStrap\IdentityFormsHelper; 12a6bf47aaSNickeauuse ComboStrap\LogUtility; 13a6bf47aaSNickeauuse ComboStrap\PluginUtility; 1404fd306cSNickeauuse ComboStrap\Site; 1504fd306cSNickeauuse ComboStrap\SiteConfig; 160581ab2eSgerardnicouse dokuwiki\Form\Form; 170581ab2eSgerardnicouse dokuwiki\Form\InputElement; 18a6bf47aaSNickeauuse dokuwiki\Menu\Item\Login; 19a6bf47aaSNickeau 20a6bf47aaSNickeauif (!defined('DOKU_INC')) die(); 2137748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 22a6bf47aaSNickeau 23a6bf47aaSNickeau/** 24a6bf47aaSNickeau * Class action_plugin_combo_login 25a6bf47aaSNickeau * 26a6bf47aaSNickeau * $conf['rememberme'] 27a6bf47aaSNickeau */ 28a6bf47aaSNickeauclass action_plugin_combo_login extends DokuWiki_Action_Plugin 29a6bf47aaSNickeau{ 30a6bf47aaSNickeau 31a6bf47aaSNickeau 32a6bf47aaSNickeau const CANONICAL = Identity::CANONICAL; 33a6bf47aaSNickeau const TAG = "login"; 34a6bf47aaSNickeau const FORM_LOGIN_CLASS = "form-" . self::TAG; 35a6bf47aaSNickeau 36a6bf47aaSNickeau const CONF_ENABLE_LOGIN_FORM = "enableLoginForm"; 3704fd306cSNickeau const FIELD_SET_TO_DELETE = ["fieldsetopen", "fieldsetclose"]; 38a6bf47aaSNickeau 39a6bf47aaSNickeau 400581ab2eSgerardnico /** 410581ab2eSgerardnico * Update the old form 420581ab2eSgerardnico * @param Doku_Form $form 430581ab2eSgerardnico * @return void 440581ab2eSgerardnico */ 450581ab2eSgerardnico private static function updateDokuFormLogin(Doku_Form &$form) 46a6bf47aaSNickeau { 47a6bf47aaSNickeau /** 480581ab2eSgerardnico * The Login page is an admin page created via buffer 49a6bf47aaSNickeau * We print before the forms 50a6bf47aaSNickeau * to avoid a FOUC 51a6bf47aaSNickeau */ 5204fd306cSNickeau print IdentityFormsHelper::getHtmlStyleTag(self::TAG); 53a6bf47aaSNickeau 54a6bf47aaSNickeau 554cadd4f8SNickeau $form->params["class"] = Identity::FORM_IDENTITY_CLASS . " " . self::FORM_LOGIN_CLASS; 56a6bf47aaSNickeau 57a6bf47aaSNickeau 58a6bf47aaSNickeau /** 59a6bf47aaSNickeau * Heading 60a6bf47aaSNickeau */ 6104fd306cSNickeau $newFormContent[] = IdentityFormsHelper::getHeaderHTML($form, self::FORM_LOGIN_CLASS); 62a6bf47aaSNickeau 63a6bf47aaSNickeau /** 64a6bf47aaSNickeau * Field 65a6bf47aaSNickeau */ 66a6bf47aaSNickeau foreach ($form->_content as $field) { 67a6bf47aaSNickeau if (!is_array($field)) { 68a6bf47aaSNickeau continue; 69a6bf47aaSNickeau } 70a6bf47aaSNickeau $fieldName = $field["name"]; 71a6bf47aaSNickeau if ($fieldName == null) { 72a6bf47aaSNickeau // this is not an input field 73a6bf47aaSNickeau if ($field["type"] == "submit") { 74a6bf47aaSNickeau /** 75a6bf47aaSNickeau * This is important to keep the submit element intact 76a6bf47aaSNickeau * for forms integration such as captcha 77a6bf47aaSNickeau * They search the submit button to insert before it 78a6bf47aaSNickeau */ 79a6bf47aaSNickeau $classes = "btn btn-primary btn-block"; 80a6bf47aaSNickeau if (isset($field["class"])) { 81a6bf47aaSNickeau $field["class"] = $field["class"] . " " . $classes; 82a6bf47aaSNickeau } else { 83a6bf47aaSNickeau $field["class"] = $classes; 84a6bf47aaSNickeau } 85a6bf47aaSNickeau $newFormContent[] = $field; 86a6bf47aaSNickeau } 87a6bf47aaSNickeau continue; 88a6bf47aaSNickeau } 89a6bf47aaSNickeau switch ($fieldName) { 90a6bf47aaSNickeau case "u": 91a6bf47aaSNickeau $loginText = $field["_text"]; 92a6bf47aaSNickeau $loginValue = $field["value"]; 93a6bf47aaSNickeau $loginHTMLField = <<<EOF 94a6bf47aaSNickeau<div class="form-floating"> 95*858cf17eSNico <input type="text" id="inputUserName" class="form-control" placeholder="$loginText" autocomplete="username" aria-label="Username" required="required" autofocus="" name="u" value="$loginValue"> 96a6bf47aaSNickeau <label for="inputUserName">$loginText</label> 97a6bf47aaSNickeau</div> 98a6bf47aaSNickeauEOF; 99a6bf47aaSNickeau $newFormContent[] = $loginHTMLField; 100a6bf47aaSNickeau break; 101a6bf47aaSNickeau case "p": 102a6bf47aaSNickeau $passwordText = $field["_text"]; 103a6bf47aaSNickeau $passwordFieldHTML = <<<EOF 104a6bf47aaSNickeau<div class="form-floating"> 105*858cf17eSNico <input type="password" id="inputPassword" class="form-control" aria-label="Password" placeholder="$passwordText" required="required" name="p"> 106a6bf47aaSNickeau <label for="inputPassword">$passwordText</label> 107a6bf47aaSNickeau</div> 108a6bf47aaSNickeauEOF; 109a6bf47aaSNickeau $newFormContent[] = $passwordFieldHTML; 110a6bf47aaSNickeau break; 111a6bf47aaSNickeau case "r": 112a6bf47aaSNickeau $rememberText = $field["_text"]; 113a6bf47aaSNickeau $rememberValue = $field["value"]; 114a6bf47aaSNickeau $rememberMeHtml = <<<EOF 115a6bf47aaSNickeau<div class="checkbox rememberMe"> 116a6bf47aaSNickeau <label><input type="checkbox" id="remember__me" name="r" value="$rememberValue"> $rememberText</label> 117a6bf47aaSNickeau</div> 118a6bf47aaSNickeauEOF; 119a6bf47aaSNickeau $newFormContent[] = $rememberMeHtml; 120a6bf47aaSNickeau break; 121a6bf47aaSNickeau default: 122a6bf47aaSNickeau $tag = self::TAG; 123a6bf47aaSNickeau LogUtility::msg("The $tag field name ($fieldName) is unknown", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 124a6bf47aaSNickeau 125a6bf47aaSNickeau 126a6bf47aaSNickeau } 127a6bf47aaSNickeau } 128a6bf47aaSNickeau 129a6bf47aaSNickeau 130a6bf47aaSNickeau $registerHtml = action_plugin_combo_registration::getRegisterLinkAndParagraph(); 131a6bf47aaSNickeau if (!empty($registerHtml)) { 132a6bf47aaSNickeau $newFormContent[] = $registerHtml; 133a6bf47aaSNickeau } 134a6bf47aaSNickeau $resendPwdHtml = action_plugin_combo_resend::getResendPasswordParagraphWithLinkToFormPage(); 135a6bf47aaSNickeau if (!empty($resendPwdHtml)) { 136a6bf47aaSNickeau $newFormContent[] = $resendPwdHtml; 137a6bf47aaSNickeau } 138a6bf47aaSNickeau 139a6bf47aaSNickeau /** 140a6bf47aaSNickeau * Set the new in place of the old one 141a6bf47aaSNickeau */ 142a6bf47aaSNickeau $form->_content = $newFormContent; 143a6bf47aaSNickeau } 144a6bf47aaSNickeau 1450581ab2eSgerardnico 1460581ab2eSgerardnico function register(Doku_Event_Handler $controller) 147c3437056SNickeau { 1480581ab2eSgerardnico /** 1490581ab2eSgerardnico * To modify the form and add class 1500581ab2eSgerardnico * 1510581ab2eSgerardnico * The event HTML_LOGINFORM_OUTPUT is deprecated 1520581ab2eSgerardnico * for FORM_LOGIN_OUTPUT 1530581ab2eSgerardnico * 1540581ab2eSgerardnico * The difference is on the type of object that we got in the event 1550581ab2eSgerardnico */ 15604fd306cSNickeau if (SiteConfig::getConfValue(self::CONF_ENABLE_LOGIN_FORM, 1)) { 1570581ab2eSgerardnico 1580581ab2eSgerardnico /** 1590581ab2eSgerardnico * Old event: Deprecated object passed by the event but still in use 1600581ab2eSgerardnico * https://www.dokuwiki.org/devel:event:html_loginform_output 1610581ab2eSgerardnico */ 1620581ab2eSgerardnico $controller->register_hook('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handle_login_html', array()); 1630581ab2eSgerardnico 1640581ab2eSgerardnico /** 1650581ab2eSgerardnico * New Event: using the new object but only in use in 1660581ab2eSgerardnico * the {@link https://codesearch.dokuwiki.org/xref/dokuwiki/lib/plugins/authad/action.php authad plugin} 1670581ab2eSgerardnico * (ie login against active directory) 1680581ab2eSgerardnico * 1690581ab2eSgerardnico * https://www.dokuwiki.org/devel:event:form_login_output 1700581ab2eSgerardnico */ 1710581ab2eSgerardnico $controller->register_hook('FORM_LOGIN_OUTPUT', 'BEFORE', $this, 'handle_login_html', array()); 172c3437056SNickeau } 173a6bf47aaSNickeau 1740581ab2eSgerardnico 1750581ab2eSgerardnico } 1760581ab2eSgerardnico 1770581ab2eSgerardnico function handle_login_html(&$event, $param): void 1780581ab2eSgerardnico { 1790581ab2eSgerardnico 1800581ab2eSgerardnico $form = &$event->data; 1810581ab2eSgerardnico $class = get_class($form); 1820581ab2eSgerardnico switch ($class) { 1830581ab2eSgerardnico case Doku_Form::class: 1840581ab2eSgerardnico /** 1850581ab2eSgerardnico * Old one 1860581ab2eSgerardnico * @var Doku_Form $form 1870581ab2eSgerardnico */ 1880581ab2eSgerardnico self::updateDokuFormLogin($form); 1890581ab2eSgerardnico return; 1900581ab2eSgerardnico case dokuwiki\Form\Form::class; 1910581ab2eSgerardnico /** 1920581ab2eSgerardnico * New One 1930581ab2eSgerardnico * @var Form $form 1940581ab2eSgerardnico */ 1950581ab2eSgerardnico self::updateNewFormLogin($form); 1960581ab2eSgerardnico return; 1970581ab2eSgerardnico } 1980581ab2eSgerardnico 1990581ab2eSgerardnico 2000581ab2eSgerardnico } 2010581ab2eSgerardnico 2020581ab2eSgerardnico 203a6bf47aaSNickeau /** 204a6bf47aaSNickeau * Login 205a6bf47aaSNickeau * @return string 206a6bf47aaSNickeau */ 2070581ab2eSgerardnico public static function getLoginParagraphWithLinkToFormPage(): string 208a6bf47aaSNickeau { 209a6bf47aaSNickeau 210a6bf47aaSNickeau $loginPwLink = (new Login())->asHtmlLink('', false); 211a6bf47aaSNickeau global $lang; 212a6bf47aaSNickeau $loginText = $lang['btn_login']; 213a6bf47aaSNickeau return <<<EOF 214a6bf47aaSNickeau<p class="login">$loginText ? : $loginPwLink</p> 215a6bf47aaSNickeauEOF; 216a6bf47aaSNickeau 217a6bf47aaSNickeau } 2180581ab2eSgerardnico 2190581ab2eSgerardnico /** 2200581ab2eSgerardnico * https://www.dokuwiki.org/devel:form - documentation 2210581ab2eSgerardnico * @param Form $form 2220581ab2eSgerardnico * @return void 2230581ab2eSgerardnico */ 2240581ab2eSgerardnico private static function updateNewFormLogin(Form &$form) 2250581ab2eSgerardnico { 2260581ab2eSgerardnico /** 2270581ab2eSgerardnico * The Login page is an admin page created via buffer 2280581ab2eSgerardnico * We print before the forms 2290581ab2eSgerardnico * to avoid a FOUC 2300581ab2eSgerardnico */ 23104fd306cSNickeau print IdentityFormsHelper::getHtmlStyleTag(self::TAG); 2320581ab2eSgerardnico 2330581ab2eSgerardnico 2340581ab2eSgerardnico $form->addClass(Identity::FORM_IDENTITY_CLASS . " " . self::FORM_LOGIN_CLASS); 2350581ab2eSgerardnico 23604fd306cSNickeau 2370581ab2eSgerardnico /** 2380581ab2eSgerardnico * Heading 2390581ab2eSgerardnico */ 24004fd306cSNickeau $headerHTML = IdentityFormsHelper::getHeaderHTML($form, self::FORM_LOGIN_CLASS); 2410581ab2eSgerardnico if ($headerHTML != "") { 2420581ab2eSgerardnico $form->addHTML($headerHTML, 1); 243a6bf47aaSNickeau } 244a6bf47aaSNickeau 2450581ab2eSgerardnico 2460581ab2eSgerardnico /** 24704fd306cSNickeau * Fieldset and br delete 2480581ab2eSgerardnico */ 24904fd306cSNickeau IdentityFormsHelper::deleteFieldSetAndBrFromForm($form); 2500581ab2eSgerardnico 2510581ab2eSgerardnico /** 2520581ab2eSgerardnico * Field 2530581ab2eSgerardnico */ 25404fd306cSNickeau IdentityFormsHelper::toBootStrapSubmitButton($form); 2550581ab2eSgerardnico 25604fd306cSNickeau /** 25704fd306cSNickeau * Name 25804fd306cSNickeau */ 2590581ab2eSgerardnico $userPosition = $form->findPositionByAttribute("name", "u"); 26004fd306cSNickeau if ($userPosition === false) { 2610581ab2eSgerardnico LogUtility::msg("Internal error: No user field found"); 2620581ab2eSgerardnico return; 2630581ab2eSgerardnico } 2640581ab2eSgerardnico /** 2650581ab2eSgerardnico * @var InputElement $userField 2660581ab2eSgerardnico */ 2670581ab2eSgerardnico $userField = $form->getElementAt($userPosition); 2680581ab2eSgerardnico $newUserField = new InputElement($userField->getType(), "u"); 2690581ab2eSgerardnico $loginText = $userField->getLabel()->val(); 2700581ab2eSgerardnico foreach ($userField->attrs() as $keyAttr => $valueAttr) { 2710581ab2eSgerardnico $newUserField->attr($keyAttr, $valueAttr); 2720581ab2eSgerardnico } 2730581ab2eSgerardnico $newUserField->addClass("form-control"); 2740581ab2eSgerardnico $newUserField->attr("placeholder", $loginText); 2750581ab2eSgerardnico $newUserField->attr("required", "required"); 2760581ab2eSgerardnico $newUserField->attr("autofocus", ""); 277*858cf17eSNico $newUserField->attr("autocomplete", "username"); 278*858cf17eSNico $newUserField->attr("aria-label", "username"); 2790581ab2eSgerardnico $userFieldId = $userField->attr("id"); 2800581ab2eSgerardnico 2810581ab2eSgerardnico $form->replaceElement($newUserField, $userPosition); 2820581ab2eSgerardnico 2830581ab2eSgerardnico $form->addHTML("<div class=\"form-floating\">", $userPosition); 2840581ab2eSgerardnico $form->addHTML("<label for=\"$userFieldId\">$loginText</label>", $userPosition + 2); 2850581ab2eSgerardnico $form->addHTML("</div>", $userPosition + 3); 2860581ab2eSgerardnico 2870581ab2eSgerardnico 2880581ab2eSgerardnico $pwdPosition = $form->findPositionByAttribute("name", "p"); 28904fd306cSNickeau if ($pwdPosition === false) { 2900581ab2eSgerardnico LogUtility::msg("Internal error: No password field found"); 2910581ab2eSgerardnico return; 2920581ab2eSgerardnico } 2930581ab2eSgerardnico $pwdField = $form->getElementAt($pwdPosition); 2940581ab2eSgerardnico $newPwdField = new InputElement($pwdField->getType(), "p"); 2950581ab2eSgerardnico foreach ($pwdField->attrs() as $keyAttr => $valueAttr) { 2960581ab2eSgerardnico $newPwdField->attr($keyAttr, $valueAttr); 2970581ab2eSgerardnico } 2980581ab2eSgerardnico $newPwdField->addClass("form-control"); 2990581ab2eSgerardnico $passwordText = $pwdField->getLabel()->val(); 3000581ab2eSgerardnico $newPwdField->attr("placeholder", $passwordText); 3010581ab2eSgerardnico $newPwdField->attr("required", "required"); 302*858cf17eSNico $newPwdField->attr("aria-label", "password"); 3030581ab2eSgerardnico $pwdFieldId = $newPwdField->attr("id"); 3040581ab2eSgerardnico if (empty($pwdFieldId)) { 3050581ab2eSgerardnico $pwdFieldId = "input__password"; 3060581ab2eSgerardnico $newPwdField->id($pwdFieldId); 3070581ab2eSgerardnico } 3080581ab2eSgerardnico $form->replaceElement($newPwdField, $pwdPosition); 3090581ab2eSgerardnico 3100581ab2eSgerardnico 3110581ab2eSgerardnico $form->addHTML("<div class=\"form-floating\">", $pwdPosition); 3120581ab2eSgerardnico $form->addHTML("<label for=\"$pwdFieldId\">$passwordText</label>", $pwdPosition + 2); 3130581ab2eSgerardnico $form->addHTML("</div>", $pwdPosition + 3); 3140581ab2eSgerardnico 3150581ab2eSgerardnico 3160581ab2eSgerardnico $rememberPosition = $form->findPositionByAttribute("name", "r"); 31704fd306cSNickeau if ($rememberPosition === false) { 3180581ab2eSgerardnico LogUtility::msg("Internal error: No remember field found"); 3190581ab2eSgerardnico return; 3200581ab2eSgerardnico } 3210581ab2eSgerardnico $rememberField = $form->getElementAt($rememberPosition); 3220581ab2eSgerardnico $newRememberField = new InputElement($rememberField->getType(), "r"); 3230581ab2eSgerardnico foreach ($rememberField->attrs() as $keyAttr => $valueAttr) { 3240581ab2eSgerardnico $newRememberField->attr($keyAttr, $valueAttr); 3250581ab2eSgerardnico } 3260581ab2eSgerardnico $newRememberField->addClass("form-check-input"); 3270581ab2eSgerardnico $form->replaceElement($newRememberField, $rememberPosition); 3280581ab2eSgerardnico 3290581ab2eSgerardnico $remberText = $rememberField->getLabel()->val(); 3300581ab2eSgerardnico $remFieldId = $newRememberField->attr("id"); 3310581ab2eSgerardnico 3320581ab2eSgerardnico $form->addHTML("<div class=\"form-check py-2\">", $rememberPosition); 3330581ab2eSgerardnico $form->addHTML("<label for=\"$remFieldId\" class=\"form-check-label\">$remberText</label>", $rememberPosition + 2); 3340581ab2eSgerardnico $form->addHTML("</div>", $rememberPosition + 3); 3350581ab2eSgerardnico 3360581ab2eSgerardnico 3370581ab2eSgerardnico// $registerHtml = action_plugin_combo_registration::getRegisterLinkAndParagraph(); 3380581ab2eSgerardnico// if (!empty($registerHtml)) { 3390581ab2eSgerardnico// $newFormContent[] = $registerHtml; 3400581ab2eSgerardnico// } 3410581ab2eSgerardnico// 3420581ab2eSgerardnico// $resendPwdHtml = action_plugin_combo_resend::getResendPasswordParagraphWithLinkToFormPage(); 3430581ab2eSgerardnico// if (!empty($resendPwdHtml)) { 3440581ab2eSgerardnico// $newFormContent[] = $resendPwdHtml; 3450581ab2eSgerardnico// } 3460581ab2eSgerardnico 3470581ab2eSgerardnico } 3480581ab2eSgerardnico 3490581ab2eSgerardnico} 35004fd306cSNickeau 351