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; 11a6bf47aaSNickeauuse ComboStrap\LogUtility; 12a6bf47aaSNickeauuse ComboStrap\PluginUtility; 13*0581ab2eSgerardnicouse dokuwiki\Form\Form; 14*0581ab2eSgerardnicouse dokuwiki\Form\InputElement; 15a6bf47aaSNickeauuse dokuwiki\Menu\Item\Login; 16a6bf47aaSNickeau 17a6bf47aaSNickeauif (!defined('DOKU_INC')) die(); 1837748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 19a6bf47aaSNickeau 20a6bf47aaSNickeau/** 21a6bf47aaSNickeau * Class action_plugin_combo_login 22a6bf47aaSNickeau * 23a6bf47aaSNickeau * $conf['rememberme'] 24a6bf47aaSNickeau */ 25a6bf47aaSNickeauclass action_plugin_combo_login extends DokuWiki_Action_Plugin 26a6bf47aaSNickeau{ 27a6bf47aaSNickeau 28a6bf47aaSNickeau 29a6bf47aaSNickeau const CANONICAL = Identity::CANONICAL; 30a6bf47aaSNickeau const TAG = "login"; 31a6bf47aaSNickeau const FORM_LOGIN_CLASS = "form-" . self::TAG; 32a6bf47aaSNickeau 33a6bf47aaSNickeau const CONF_ENABLE_LOGIN_FORM = "enableLoginForm"; 34a6bf47aaSNickeau 35a6bf47aaSNickeau 36*0581ab2eSgerardnico /** 37*0581ab2eSgerardnico * Update the old form 38*0581ab2eSgerardnico * @param Doku_Form $form 39*0581ab2eSgerardnico * @return void 40*0581ab2eSgerardnico */ 41*0581ab2eSgerardnico private static function updateDokuFormLogin(Doku_Form &$form) 42a6bf47aaSNickeau { 43a6bf47aaSNickeau /** 44*0581ab2eSgerardnico * The Login page is an admin page created via buffer 45a6bf47aaSNickeau * We print before the forms 46a6bf47aaSNickeau * to avoid a FOUC 47a6bf47aaSNickeau */ 484cadd4f8SNickeau print Identity::getHtmlStyleTag(self::TAG); 49a6bf47aaSNickeau 50a6bf47aaSNickeau 514cadd4f8SNickeau $form->params["class"] = Identity::FORM_IDENTITY_CLASS . " " . self::FORM_LOGIN_CLASS; 52a6bf47aaSNickeau 53a6bf47aaSNickeau 54a6bf47aaSNickeau /** 55a6bf47aaSNickeau * Heading 56a6bf47aaSNickeau */ 57a6bf47aaSNickeau $newFormContent[] = Identity::getHeaderHTML($form, self::FORM_LOGIN_CLASS); 58a6bf47aaSNickeau 59a6bf47aaSNickeau /** 60a6bf47aaSNickeau * Field 61a6bf47aaSNickeau */ 62a6bf47aaSNickeau foreach ($form->_content as $field) { 63a6bf47aaSNickeau if (!is_array($field)) { 64a6bf47aaSNickeau continue; 65a6bf47aaSNickeau } 66a6bf47aaSNickeau $fieldName = $field["name"]; 67a6bf47aaSNickeau if ($fieldName == null) { 68a6bf47aaSNickeau // this is not an input field 69a6bf47aaSNickeau if ($field["type"] == "submit") { 70a6bf47aaSNickeau /** 71a6bf47aaSNickeau * This is important to keep the submit element intact 72a6bf47aaSNickeau * for forms integration such as captcha 73a6bf47aaSNickeau * They search the submit button to insert before it 74a6bf47aaSNickeau */ 75a6bf47aaSNickeau $classes = "btn btn-primary btn-block"; 76a6bf47aaSNickeau if (isset($field["class"])) { 77a6bf47aaSNickeau $field["class"] = $field["class"] . " " . $classes; 78a6bf47aaSNickeau } else { 79a6bf47aaSNickeau $field["class"] = $classes; 80a6bf47aaSNickeau } 81a6bf47aaSNickeau $newFormContent[] = $field; 82a6bf47aaSNickeau } 83a6bf47aaSNickeau continue; 84a6bf47aaSNickeau } 85a6bf47aaSNickeau switch ($fieldName) { 86a6bf47aaSNickeau case "u": 87a6bf47aaSNickeau $loginText = $field["_text"]; 88a6bf47aaSNickeau $loginValue = $field["value"]; 89a6bf47aaSNickeau $loginHTMLField = <<<EOF 90a6bf47aaSNickeau<div class="form-floating"> 91a6bf47aaSNickeau <input type="text" id="inputUserName" class="form-control" placeholder="$loginText" required="required" autofocus="" name="u" value="$loginValue"> 92a6bf47aaSNickeau <label for="inputUserName">$loginText</label> 93a6bf47aaSNickeau</div> 94a6bf47aaSNickeauEOF; 95a6bf47aaSNickeau $newFormContent[] = $loginHTMLField; 96a6bf47aaSNickeau break; 97a6bf47aaSNickeau case "p": 98a6bf47aaSNickeau $passwordText = $field["_text"]; 99a6bf47aaSNickeau $passwordFieldHTML = <<<EOF 100a6bf47aaSNickeau<div class="form-floating"> 101a6bf47aaSNickeau <input type="password" id="inputPassword" class="form-control" placeholder="$passwordText" required="required" name="p"> 102a6bf47aaSNickeau <label for="inputPassword">$passwordText</label> 103a6bf47aaSNickeau</div> 104a6bf47aaSNickeauEOF; 105a6bf47aaSNickeau $newFormContent[] = $passwordFieldHTML; 106a6bf47aaSNickeau break; 107a6bf47aaSNickeau case "r": 108a6bf47aaSNickeau $rememberText = $field["_text"]; 109a6bf47aaSNickeau $rememberValue = $field["value"]; 110a6bf47aaSNickeau $rememberMeHtml = <<<EOF 111a6bf47aaSNickeau<div class="checkbox rememberMe"> 112a6bf47aaSNickeau <label><input type="checkbox" id="remember__me" name="r" value="$rememberValue"> $rememberText</label> 113a6bf47aaSNickeau</div> 114a6bf47aaSNickeauEOF; 115a6bf47aaSNickeau $newFormContent[] = $rememberMeHtml; 116a6bf47aaSNickeau break; 117a6bf47aaSNickeau default: 118a6bf47aaSNickeau $tag = self::TAG; 119a6bf47aaSNickeau LogUtility::msg("The $tag field name ($fieldName) is unknown", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 120a6bf47aaSNickeau 121a6bf47aaSNickeau 122a6bf47aaSNickeau } 123a6bf47aaSNickeau } 124a6bf47aaSNickeau 125a6bf47aaSNickeau 126a6bf47aaSNickeau $registerHtml = action_plugin_combo_registration::getRegisterLinkAndParagraph(); 127a6bf47aaSNickeau if (!empty($registerHtml)) { 128a6bf47aaSNickeau $newFormContent[] = $registerHtml; 129a6bf47aaSNickeau } 130a6bf47aaSNickeau $resendPwdHtml = action_plugin_combo_resend::getResendPasswordParagraphWithLinkToFormPage(); 131a6bf47aaSNickeau if (!empty($resendPwdHtml)) { 132a6bf47aaSNickeau $newFormContent[] = $resendPwdHtml; 133a6bf47aaSNickeau } 134a6bf47aaSNickeau 135a6bf47aaSNickeau /** 136a6bf47aaSNickeau * Set the new in place of the old one 137a6bf47aaSNickeau */ 138a6bf47aaSNickeau $form->_content = $newFormContent; 139a6bf47aaSNickeau } 140a6bf47aaSNickeau 141*0581ab2eSgerardnico 142*0581ab2eSgerardnico function register(Doku_Event_Handler $controller) 143c3437056SNickeau { 144*0581ab2eSgerardnico /** 145*0581ab2eSgerardnico * To modify the form and add class 146*0581ab2eSgerardnico * 147*0581ab2eSgerardnico * The event HTML_LOGINFORM_OUTPUT is deprecated 148*0581ab2eSgerardnico * for FORM_LOGIN_OUTPUT 149*0581ab2eSgerardnico * 150*0581ab2eSgerardnico * The difference is on the type of object that we got in the event 151*0581ab2eSgerardnico */ 152*0581ab2eSgerardnico if (PluginUtility::getConfValue(self::CONF_ENABLE_LOGIN_FORM, 1)) { 153*0581ab2eSgerardnico 154*0581ab2eSgerardnico /** 155*0581ab2eSgerardnico * Old event: Deprecated object passed by the event but still in use 156*0581ab2eSgerardnico * https://www.dokuwiki.org/devel:event:html_loginform_output 157*0581ab2eSgerardnico */ 158*0581ab2eSgerardnico $controller->register_hook('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handle_login_html', array()); 159*0581ab2eSgerardnico 160*0581ab2eSgerardnico /** 161*0581ab2eSgerardnico * New Event: using the new object but only in use in 162*0581ab2eSgerardnico * the {@link https://codesearch.dokuwiki.org/xref/dokuwiki/lib/plugins/authad/action.php authad plugin} 163*0581ab2eSgerardnico * (ie login against active directory) 164*0581ab2eSgerardnico * 165*0581ab2eSgerardnico * https://www.dokuwiki.org/devel:event:form_login_output 166*0581ab2eSgerardnico */ 167*0581ab2eSgerardnico $controller->register_hook('FORM_LOGIN_OUTPUT', 'BEFORE', $this, 'handle_login_html', array()); 168c3437056SNickeau } 169a6bf47aaSNickeau 170*0581ab2eSgerardnico 171*0581ab2eSgerardnico } 172*0581ab2eSgerardnico 173*0581ab2eSgerardnico function handle_login_html(&$event, $param): void 174*0581ab2eSgerardnico { 175*0581ab2eSgerardnico 176*0581ab2eSgerardnico $form = &$event->data; 177*0581ab2eSgerardnico $class = get_class($form); 178*0581ab2eSgerardnico switch ($class) { 179*0581ab2eSgerardnico case Doku_Form::class: 180*0581ab2eSgerardnico /** 181*0581ab2eSgerardnico * Old one 182*0581ab2eSgerardnico * @var Doku_Form $form 183*0581ab2eSgerardnico */ 184*0581ab2eSgerardnico self::updateDokuFormLogin($form); 185*0581ab2eSgerardnico return; 186*0581ab2eSgerardnico case dokuwiki\Form\Form::class; 187*0581ab2eSgerardnico /** 188*0581ab2eSgerardnico * New One 189*0581ab2eSgerardnico * @var Form $form 190*0581ab2eSgerardnico */ 191*0581ab2eSgerardnico self::updateNewFormLogin($form); 192*0581ab2eSgerardnico return; 193*0581ab2eSgerardnico } 194*0581ab2eSgerardnico 195*0581ab2eSgerardnico 196*0581ab2eSgerardnico } 197*0581ab2eSgerardnico 198*0581ab2eSgerardnico 199a6bf47aaSNickeau /** 200a6bf47aaSNickeau * Login 201a6bf47aaSNickeau * @return string 202a6bf47aaSNickeau */ 203*0581ab2eSgerardnico public static function getLoginParagraphWithLinkToFormPage(): string 204a6bf47aaSNickeau { 205a6bf47aaSNickeau 206a6bf47aaSNickeau $loginPwLink = (new Login())->asHtmlLink('', false); 207a6bf47aaSNickeau global $lang; 208a6bf47aaSNickeau $loginText = $lang['btn_login']; 209a6bf47aaSNickeau return <<<EOF 210a6bf47aaSNickeau<p class="login">$loginText ? : $loginPwLink</p> 211a6bf47aaSNickeauEOF; 212a6bf47aaSNickeau 213a6bf47aaSNickeau } 214*0581ab2eSgerardnico 215*0581ab2eSgerardnico /** 216*0581ab2eSgerardnico * https://www.dokuwiki.org/devel:form - documentation 217*0581ab2eSgerardnico * @param Form $form 218*0581ab2eSgerardnico * @return void 219*0581ab2eSgerardnico */ 220*0581ab2eSgerardnico private static function updateNewFormLogin(Form &$form) 221*0581ab2eSgerardnico { 222*0581ab2eSgerardnico /** 223*0581ab2eSgerardnico * The Login page is an admin page created via buffer 224*0581ab2eSgerardnico * We print before the forms 225*0581ab2eSgerardnico * to avoid a FOUC 226*0581ab2eSgerardnico */ 227*0581ab2eSgerardnico print Identity::getHtmlStyleTag(self::TAG); 228*0581ab2eSgerardnico 229*0581ab2eSgerardnico 230*0581ab2eSgerardnico $form->addClass(Identity::FORM_IDENTITY_CLASS . " " . self::FORM_LOGIN_CLASS); 231*0581ab2eSgerardnico 232*0581ab2eSgerardnico /** 233*0581ab2eSgerardnico * Heading 234*0581ab2eSgerardnico */ 235*0581ab2eSgerardnico $headerHTML = Identity::getHeaderHTML($form, self::FORM_LOGIN_CLASS); 236*0581ab2eSgerardnico if ($headerHTML != "") { 237*0581ab2eSgerardnico $form->addHTML($headerHTML, 1); 238a6bf47aaSNickeau } 239a6bf47aaSNickeau 240*0581ab2eSgerardnico $brPositionElement = [4, 5]; // 4 and 6 but when you delete 4, it's on 5 241*0581ab2eSgerardnico foreach ($brPositionElement as $brPosition) { 242*0581ab2eSgerardnico $fieldBr = $form->getElementAt($brPosition); 243*0581ab2eSgerardnico if ($fieldBr->val() === "<br>\n") { 244*0581ab2eSgerardnico $form->removeElement($brPosition); 245*0581ab2eSgerardnico } else { 246*0581ab2eSgerardnico LogUtility::msg("Internal: the login br $brPosition element was not found and not deleted"); 247*0581ab2eSgerardnico } 248*0581ab2eSgerardnico } 249*0581ab2eSgerardnico 250*0581ab2eSgerardnico /** 251*0581ab2eSgerardnico * Fieldset delete 252*0581ab2eSgerardnico */ 253*0581ab2eSgerardnico $elementsTypeToDelete = ["fieldsetopen", "fieldsetclose"]; 254*0581ab2eSgerardnico foreach ($elementsTypeToDelete as $type) { 255*0581ab2eSgerardnico $field = $form->findPositionByType($type); 256*0581ab2eSgerardnico if ($field != false) { 257*0581ab2eSgerardnico $form->removeElement($field); 258*0581ab2eSgerardnico } 259*0581ab2eSgerardnico } 260*0581ab2eSgerardnico 261*0581ab2eSgerardnico /** 262*0581ab2eSgerardnico * Field 263*0581ab2eSgerardnico */ 264*0581ab2eSgerardnico $submitButtonPosition = $form->findPositionByAttribute("type", "submit"); 265*0581ab2eSgerardnico if ($submitButtonPosition == false) { 266*0581ab2eSgerardnico LogUtility::msg("Internal error: No submit button found"); 267*0581ab2eSgerardnico return; 268*0581ab2eSgerardnico } 269*0581ab2eSgerardnico /** 270*0581ab2eSgerardnico * This is important to keep the submit element intact 271*0581ab2eSgerardnico * for forms integration such as captcha 272*0581ab2eSgerardnico * They search the submit button to insert before it 273*0581ab2eSgerardnico */ 274*0581ab2eSgerardnico $form->getElementAt($submitButtonPosition) 275*0581ab2eSgerardnico ->addClass("btn") 276*0581ab2eSgerardnico ->addClass("btn-primary") 277*0581ab2eSgerardnico ->addClass("btn-block") 278*0581ab2eSgerardnico ->addClass("mb-2"); 279*0581ab2eSgerardnico 280*0581ab2eSgerardnico $userPosition = $form->findPositionByAttribute("name", "u"); 281*0581ab2eSgerardnico if ($userPosition == false) { 282*0581ab2eSgerardnico LogUtility::msg("Internal error: No user field found"); 283*0581ab2eSgerardnico return; 284*0581ab2eSgerardnico } 285*0581ab2eSgerardnico /** 286*0581ab2eSgerardnico * @var InputElement $userField 287*0581ab2eSgerardnico */ 288*0581ab2eSgerardnico $userField = $form->getElementAt($userPosition); 289*0581ab2eSgerardnico $newUserField = new InputElement($userField->getType(), "u"); 290*0581ab2eSgerardnico $loginText = $userField->getLabel()->val(); 291*0581ab2eSgerardnico foreach ($userField->attrs() as $keyAttr => $valueAttr) { 292*0581ab2eSgerardnico $newUserField->attr($keyAttr, $valueAttr); 293*0581ab2eSgerardnico } 294*0581ab2eSgerardnico $newUserField->addClass("form-control"); 295*0581ab2eSgerardnico $newUserField->attr("placeholder", $loginText); 296*0581ab2eSgerardnico $newUserField->attr("required", "required"); 297*0581ab2eSgerardnico $newUserField->attr("autofocus", ""); 298*0581ab2eSgerardnico $userFieldId = $userField->attr("id"); 299*0581ab2eSgerardnico 300*0581ab2eSgerardnico $form->replaceElement($newUserField, $userPosition); 301*0581ab2eSgerardnico 302*0581ab2eSgerardnico $form->addHTML("<div class=\"form-floating\">", $userPosition); 303*0581ab2eSgerardnico $form->addHTML("<label for=\"$userFieldId\">$loginText</label>", $userPosition + 2); 304*0581ab2eSgerardnico $form->addHTML("</div>", $userPosition + 3); 305*0581ab2eSgerardnico 306*0581ab2eSgerardnico 307*0581ab2eSgerardnico $pwdPosition = $form->findPositionByAttribute("name", "p"); 308*0581ab2eSgerardnico if ($pwdPosition == false) { 309*0581ab2eSgerardnico LogUtility::msg("Internal error: No password field found"); 310*0581ab2eSgerardnico return; 311*0581ab2eSgerardnico } 312*0581ab2eSgerardnico $pwdField = $form->getElementAt($pwdPosition); 313*0581ab2eSgerardnico $newPwdField = new InputElement($pwdField->getType(), "p"); 314*0581ab2eSgerardnico foreach ($pwdField->attrs() as $keyAttr => $valueAttr) { 315*0581ab2eSgerardnico $newPwdField->attr($keyAttr, $valueAttr); 316*0581ab2eSgerardnico } 317*0581ab2eSgerardnico $newPwdField->addClass("form-control"); 318*0581ab2eSgerardnico $passwordText = $pwdField->getLabel()->val(); 319*0581ab2eSgerardnico $newPwdField->attr("placeholder", $passwordText); 320*0581ab2eSgerardnico $newPwdField->attr("required", "required"); 321*0581ab2eSgerardnico $pwdFieldId = $newPwdField->attr("id"); 322*0581ab2eSgerardnico if(empty($pwdFieldId)){ 323*0581ab2eSgerardnico $pwdFieldId = "input__password"; 324*0581ab2eSgerardnico $newPwdField->id($pwdFieldId); 325*0581ab2eSgerardnico } 326*0581ab2eSgerardnico $form->replaceElement($newPwdField, $pwdPosition); 327*0581ab2eSgerardnico 328*0581ab2eSgerardnico 329*0581ab2eSgerardnico $form->addHTML("<div class=\"form-floating\">", $pwdPosition); 330*0581ab2eSgerardnico $form->addHTML("<label for=\"$pwdFieldId\">$passwordText</label>", $pwdPosition + 2); 331*0581ab2eSgerardnico $form->addHTML("</div>", $pwdPosition + 3); 332*0581ab2eSgerardnico 333*0581ab2eSgerardnico 334*0581ab2eSgerardnico $rememberPosition = $form->findPositionByAttribute("name", "r"); 335*0581ab2eSgerardnico if ($rememberPosition == false) { 336*0581ab2eSgerardnico LogUtility::msg("Internal error: No remember field found"); 337*0581ab2eSgerardnico return; 338*0581ab2eSgerardnico } 339*0581ab2eSgerardnico $rememberField = $form->getElementAt($rememberPosition); 340*0581ab2eSgerardnico $newRememberField = new InputElement($rememberField->getType(), "r"); 341*0581ab2eSgerardnico foreach ($rememberField->attrs() as $keyAttr => $valueAttr) { 342*0581ab2eSgerardnico $newRememberField->attr($keyAttr, $valueAttr); 343*0581ab2eSgerardnico } 344*0581ab2eSgerardnico $newRememberField->addClass("form-check-input"); 345*0581ab2eSgerardnico $form->replaceElement($newRememberField, $rememberPosition); 346*0581ab2eSgerardnico 347*0581ab2eSgerardnico $remberText = $rememberField->getLabel()->val(); 348*0581ab2eSgerardnico $remFieldId = $newRememberField->attr("id"); 349*0581ab2eSgerardnico 350*0581ab2eSgerardnico $form->addHTML("<div class=\"form-check py-2\">", $rememberPosition); 351*0581ab2eSgerardnico $form->addHTML("<label for=\"$remFieldId\" class=\"form-check-label\">$remberText</label>", $rememberPosition + 2); 352*0581ab2eSgerardnico $form->addHTML("</div>", $rememberPosition + 3); 353*0581ab2eSgerardnico 354*0581ab2eSgerardnico 355*0581ab2eSgerardnico 356*0581ab2eSgerardnico// $registerHtml = action_plugin_combo_registration::getRegisterLinkAndParagraph(); 357*0581ab2eSgerardnico// if (!empty($registerHtml)) { 358*0581ab2eSgerardnico// $newFormContent[] = $registerHtml; 359*0581ab2eSgerardnico// } 360*0581ab2eSgerardnico// 361*0581ab2eSgerardnico// $resendPwdHtml = action_plugin_combo_resend::getResendPasswordParagraphWithLinkToFormPage(); 362*0581ab2eSgerardnico// if (!empty($resendPwdHtml)) { 363*0581ab2eSgerardnico// $newFormContent[] = $resendPwdHtml; 364*0581ab2eSgerardnico// } 365*0581ab2eSgerardnico 366*0581ab2eSgerardnico 367*0581ab2eSgerardnico } 368*0581ab2eSgerardnico 369*0581ab2eSgerardnico} 370