Commit 9099b64c authored by Frank Nägler's avatar Frank Nägler Committed by Anja Leichsenring
Browse files

[!!!][FEATURE] BE Login form API

With the new login screen (introduced in 7.2) the possibility to
override or extend the login template was removed.
The old globals ``$GLOBALS['TBE_STYLES']['htmlTemplates']`` was
removed without replacement.

With this patch we introduce a new way to extend the login form and
add a BE login form API.

Resolves: #66669
Releases: master
Change-Id: I36ab4cdabbab55f370d1fd19b7212cf2f858db57
Reviewed-on: http://review.typo3.org/39234

Reviewed-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Reviewed-by: default avatarFrank Nägler <typo3@naegler.net>
Tested-by: default avatarFrank Nägler <typo3@naegler.net>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 11a544b3
......@@ -22,4 +22,3 @@ require __DIR__ . '/init.php';
$loginController = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Controller\LoginController::class);
$loginController->main();
$loginController->printContent();
<?php
namespace TYPO3\CMS\Backend\LoginProvider;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Controller\LoginController;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
* Interface for Backend Login providers
*/
interface LoginProviderInterface {
/**
* Render the login HTML
*
* Implement this method and set the template for your form.
* This is also the right place to assign data to the view
* and add necessary JavaScript resources to the page renderer.
*
* A good example is EXT:openid
*
* Example:
* $view->setTemplatePathAndFilename($pathAndFilename);
* $view->assign('foo', 'bar');
*
* @param StandaloneView $view
* @param PageRenderer $pageRenderer
* @param LoginController $loginController
*
* @return void
*/
public function render(StandaloneView $view, PageRenderer $pageRenderer, LoginController $loginController);
}
<?php
namespace TYPO3\CMS\Backend\LoginProvider;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Controller\LoginController;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
use TYPO3\CMS\Fluid\View\StandaloneView;
/**
* Class UsernamePasswordLoginProvider
*/
class UsernamePasswordLoginProvider implements LoginProviderInterface {
const SIGNAL_getPageRenderer = 'getPageRenderer';
/**
* @param StandaloneView $view
* @param PageRenderer $pageRenderer
* @param LoginController $loginController
* @throws \UnexpectedValueException
*/
public function render(StandaloneView $view, PageRenderer $pageRenderer, LoginController $loginController) {
GeneralUtility::makeInstance(ObjectManager::class)->get(Dispatcher::class)->dispatch(__CLASS__, self::SIGNAL_getPageRenderer, array($pageRenderer));
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/UserPassLogin');
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/UserPassLoginForm.html'));
if (GeneralUtility::getIndpEnv('TYPO3_SSL')) {
$view->assign('presetUsername', GeneralUtility::_GP('u'));
$view->assign('presetPassword', GeneralUtility::_GP('p'));
}
}
}
......@@ -34,6 +34,54 @@ Have a nice day.</source>
<trans-unit id="foldertreeview.noFolders.message">
<source>You do not have access to any folder. Please ask your administrator to fix access permissions for your account.</source>
</trans-unit>
<trans-unit id="login.link">
<source>Login with username and password</source>
</trans-unit>
<trans-unit id="login.username">
<source>Username</source>
</trans-unit>
<trans-unit id="login.password">
<source>Password</source>
</trans-unit>
<trans-unit id="login.error.capslock">
<source>Attention: Caps lock enabled!</source>
</trans-unit>
<trans-unit id="login.interface">
<source>Interface</source>
</trans-unit>
<trans-unit id="login.news.previous">
<source>Previous</source>
</trans-unit>
<trans-unit id="login.news.next">
<source>Next</source>
</trans-unit>
<trans-unit id="login.error.message">
<source>Your login attempt did not succeed</source>
</trans-unit>
<trans-unit id="login.error.description">
<source>Make sure to spell your username and password correctly, including upper/lowercase characters.</source>
</trans-unit>
<trans-unit id="login.error.javascript">
<source>Please activate JavaScript!</source>
</trans-unit>
<trans-unit id="login.error.cookies">
<source>Please activate Cookies!</source>
</trans-unit>
<trans-unit id="login.process">
<source>Verifying Login Data ...</source>
</trans-unit>
<trans-unit id="login.submit">
<source>Login</source>
</trans-unit>
<trans-unit id="login.copyrightLink">
<source>More about TYPO3</source>
</trans-unit>
<trans-unit id="login.typo3Logo">
<source>TYPO3 logo</source>
</trans-unit>
<trans-unit id="login.donate">
<source>Donate</source>
</trans-unit>
</body>
</file>
</xliff>
<div class="typo3-login">
<div class="typo3-login-container">
<div class="typo3-login-wrap">
<div class="panel panel-lg panel-login">
<div class="panel-body">
<div class="typo3-login-logo">
<img src="{logo}" class="typo3-login-image" alt="" />
</div>
<f:if condition="{formType} == 'LoginForm'">
<f:then>
<f:if condition="{hasLoginError}">
<div class="t3js-login-error" id="t3-login-error">
<div class="alert alert-danger">
<strong><f:translate key="login.error.message" /></strong>
<p><f:translate key="login.error.description" /></p>
</div>
</div>
</f:if>
<noscript>
<f:be.infobox message="{f:translate(key: 'login.error.javascript')}" state="2" />
</noscript>
<div class="hidden t3js-login-error-nocookies">
<f:be.infobox message="{f:translate(key: 'login.error.cookies')}" state="2" />
</div>
<div class="typo3-login-form t3js-login-formfields">
<form action="?loginProvider={loginProviderIdentifier}" method="post" name="loginform" id="typo3-login-form">
<f:form.hidden name="login_status" value="login" />
<f:form.hidden name="userident" id="t3-field-userident" class="t3js-login-userident-field" value="" />
<f:form.hidden name="redirect_url" value="{redirectUrl}" />
<f:form.hidden name="loginRefresh" value="{loginRefresh}" />
<f:render partial="Login/InterfaceSelector" arguments="{_all}" />
<f:render section="loginFormFields" />
<div class="form-group" id="t3-login-submit-section">
<button class="btn btn-block btn-login t3js-login-submit" id="t3-login-submit" type="submit" name="commandLI" data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> {f:translate(key: 'login.process')}" autocomplete="off">
<f:translate key="login.submit" />
</button>
</div>
<ul class="list-unstyled typo3-login-links">
<f:for each="{loginProviders}" as="provider" key="providerKey">
<f:if condition="{provider.label}">
<f:if condition="{loginProviderIdentifier} != {providerKey}">
<li class="t3js-loginprovider-switch" data-providerkey="{providerKey}"><a href="?loginProvider={providerKey}"><i class="fa fa-fw {provider.icon-class}"></i> <span><f:translate key="{provider.label}" /></span></a></li>
</f:if>
</f:if>
</f:for>
</ul>
</form>
</div>
</f:then>
<f:else>
<form action="../../../../../../index.php" method="post" name="loginform">
<input type="hidden" name="login_status" value="logout" />
<div class="t3-login-box-body">
<div class="t3-login-logout-form">
<div class="t3-login-username">
<div class="t3-login-label t3-username">
<f:translate key="login.username" />
</div>
<div class="t3-username-current">
{backendUser.username}
</div>
</div>
<f:if condition="{showInterfaceSelector}">
<f:then>
<div class="t3-login-interface">
<div class="t3-login-label t3-interface-selector">
<f:translate key="login.interface" />
</div>
<f:form.select name="users" options="{interfaces}" optionValueField="jumpScript" optionLabelField="label" />
</div>
</f:then>
<f:else>
<f:if condition="{interface}">
<f:form.hidden name="interface" value="{interface}" />
</f:if>
</f:else>
</f:if>
<input type="hidden" name="p_field" value="" />
<input class="btn btn-block btn-lg" type="submit" name="commandLO" value="{f:translate(key: 'login.submit')}" id="t3-login-submit" />
</div>
</div>
</form>
</f:else>
</f:if>
</div>
<f:render partial="LoginNews" arguments="{_all}" />
<div class="panel-footer">
<div class="login-copyright-wrap">
<a href="#loginCopyright" class="typo3-login-copyright-link collapsed" data-toggle="collapse" aria-expanded="false" aria-controls="loginCopyright">
<span><f:translate key="login.copyrightLink" /></span>
<img src="{images.typo3}" alt="{f:translate(key: 'login.typo3Logo')}" width="70" height="20" />
</a>
<div id="loginCopyright" class="collapse">
<div class="typo3-login-copyright-text">
<p>
<f:format.raw>{copyright}</f:format.raw>
</p>
<ul class="list-unstyled">
<li><a href="http://typo3.org" target="_blank" class="t3-login-link-typo3"><i class="fa fa-external-link"></i> TYPO3.org</a></li>
<li><a href="http://typo3.org/donate/" target="_blank" class="t3-login-link-donate"><i class="fa fa-external-link"></i> <f:translate key="login.donate" /></a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<f:if condition="{showInterfaceSelector}">
<f:then>
<div class="form-group t3js-login-interface-section" id="t3-login-interface-section">
<div class="form-control-wrap">
<div class="form-control-holder">
<label for="t3-interfaces"><f:translate key="login.interface" /></label>
<f:form.select id="t3-interfaces" class="form-control" name="interface" options="{interfaces}" optionValueField="interface" optionLabelField="label" />
</div>
</div>
</div>
</f:then>
<f:else>
<f:if condition="{interface}">
<f:form.hidden name="interface" value="{interface}" />
</f:if>
</f:else>
</f:if>
<f:if condition="{loginNewsItems}">
<div class="panel-carousel carousel slide t3js-login-news-carousel" id="loginNews" data-interval="0" data-ride="carousel">
<div class="carousel-inner" role="listbox">
<f:for each="{loginNewsItems}" as="item" iteration="loginNewsIterator">
<div class="item{f:if(condition: loginNewsIterator.isFirst,then: ' active')}">
<h3 class="typo3-login-news-heading">{item.header}</h3>
<f:format.html>{item.content}</f:format.html>
<span class="text-muted">{item.date}</span>
</div>
</f:for>
</div>
<a class="left typo3-login-carousel-control" href="#loginNews" role="button" data-slide="prev">
<i class="fa fa-angle-left"></i>
<span class="sr-only"><f:translate key="login.news.previous" /></span>
</a>
<a class="right typo3-login-carousel-control" href="#loginNews" role="button" data-slide="next">
<i class="fa fa-angle-right"></i>
<span class="sr-only"><f:translate key="login.news.next" /></span>
</a>
</div>
</f:if>
<div class="typo3-login">
<div class="typo3-login-container">
<div class="typo3-login-wrap">
<div class="panel panel-lg panel-login">
<div class="panel-body">
<div class="typo3-login-logo">
<img src="{logo}" class="typo3-login-image" />
</div>
<f:render section="{formType}" arguments="{_all}" />
</div>
<f:render section="loginNews" arguments="{_all}" />
<div class="panel-footer">
<div class="login-copyright-wrap">
<a href="#loginCopyright" class="typo3-login-copyright-link collapsed" data-toggle="collapse" aria-expanded="false" aria-controls="loginCopyright">
<span><f:translate key="{labelPrefixPath}copyright.link" /></span>
<img src="{images.typo3}" alt="{f:translate(key: '{labelPrefixPath}typo3.logo')}" width="70" height="20" />
</a>
<div id="loginCopyright" class="collapse">
<div class="typo3-login-copyright-text">
<p>
<f:format.raw>{copyright}</f:format.raw>
</p>
<ul class="list-unstyled">
<li><a href="http://typo3.org" target="_blank" class="t3-login-link-typo3"><i class="fa fa-external-link"></i> TYPO3.org</a></li>
<li><a href="http://typo3.org/donate/" target="_blank" class="t3-login-link-donate"><i class="fa fa-external-link"></i> <f:translate key="{labelPrefixPath}labels.donate" /></a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<f:section name="loginForm">
<f:if condition="{hasLoginError}">
<div class="t3js-login-error" id="t3-login-error">
<div class="alert alert-danger">
<strong><f:translate key="{labelPrefixPath}error.login.title" /></strong>
<p><f:translate key="{labelPrefixPath}error.login.description" /></p>
</div>
</div>
</f:if>
<noscript>
<f:be.infobox message="{f:translate(key: '{labelPrefixPath}error.javascript')}" state="2" />
</noscript>
<div class="hidden t3js-login-error-nocookies">
<f:be.infobox message="{f:translate(key: '{labelPrefixPath}error.cookies')}" state="2" />
</div>
<div class="typo3-login-form t3js-login-formfields">
<f:format.raw>{formTag}</f:format.raw>
<f:if condition="{interfaceSelector}">
<div class="form-group t3js-login-interface-section" id="t3-login-interface-section">
<div class="form-control-wrap">
<f:format.raw>{interfaceSelector}</f:format.raw>
</div>
</div>
</f:if>
<div class="form-group t3js-login-username-section" id="t3-login-username-section">
<div class="form-control-wrap">
<div class="form-control-holder">
<input type="text" id="t3-username" name="username" value="{presetUsername}" placeholder="{f:translate(key: '{labelPrefixPath}labels.username')}" class="form-control input-login t3js-clearable t3js-login-username-field" required="required">
<div class="form-notice-capslock hidden t3js-login-alert-capslock">
<img src="{images.capslock}" width="14" height="14" alt="{f:translate(key: '{labelPrefixPath}error.capslock')}" title="{f:translate(key: '{labelPrefixPath}error.capslock')}" />
</div>
</div>
</div>
</div>
<div class="form-group t3js-login-password-section" id="t3-login-password-section">
<div class="form-control-wrap">
<div class="form-control-holder">
<input type="password" id="t3-password" name="p_field" value="{presetPassword}" placeholder="{f:translate(key: '{labelPrefixPath}labels.password')}" class="form-control input-login t3js-clearable t3js-login-password-field" required="required">
<div class="form-notice-capslock hidden t3js-login-alert-capslock">
<img src="{images.capslock}" width="14" height="14" alt="{f:translate(key: '{labelPrefixPath}error.capslock')}" title="{f:translate(key: '{labelPrefixPath}error.capslock')}" />
</div>
</div>
</div>
</div>
<f:if condition="{isOpenIdLoaded}">
<div class="form-group hidden t3js-login-openid-section" id="t3-login-openid_url-section">
<div class="input-group">
<input type="text" id="openid_url" name="openid_url" value="{presetOpenId}" placeholder="{f:translate(key: '{labelPrefixPath}labels.openId')}" class="form-control input-login t3js-clearable t3js-login-openid-field" />
<div class="input-group-addon">
<span class="fa fa-openid"></span>
</div>
</div>
</div>
</f:if>
<div class="form-group" id="t3-login-submit-section">
<button class="btn btn-block btn-login t3js-login-submit" id="t3-login-submit" type="submit" name="commandLI" data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> {f:translate(key: '{labelPrefixPath}login_process')}" autocomplete="off">
<f:translate key="{labelPrefixPath}labels.submitLogin" />
</button>
</div>
<f:if condition="{isOpenIdLoaded}">
<ul class="list-unstyled typo3-login-links">
<li>
<a class="t3js-login-switch-to-openid">
<i class="fa fa-fw fa-openid"></i> <span><f:translate key="{labelPrefixPath}labels.switchToOpenId" /></span>
</a>
<a class="t3js-login-switch-to-default hidden">
<i class="fa fa-fw fa-key"></i> <span><f:translate key="{labelPrefixPath}labels.switchToDefault" /></span>
</a>
</li>
</ul>
</f:if>
</form>
</div>
</f:section>
<f:section name="logoutForm">
<form action="index.php" method="post" name="loginform">
<input type="hidden" name="login_status" value="logout" />
<div class="t3-login-box-body">
<div class="t3-login-logout-form">
<div class="t3-login-username">
<div class="t3-login-label t3-username">
<f:translate key="{labelPrefixPath}labels.username" />
</div>
<div class="t3-username-current">
{backendUser.username}
</div>
</div>
<f:if condition="{showInterfaceSelector}">
<div class="t3-login-interface">
<div class="t3-login-label t3-interface-selector">
<f:translate key="{labelPrefixPath}labels.interface" />
</div>
<f:format.raw>{interfaceSelectorJump}</f:format.raw>
</div>
</f:if>
<input type="hidden" name="p_field" value="" />
<input class="btn btn-block btn-lg" type="submit" name="commandLO" value="{f:translate(key: '{labelPrefixPath}labels.submitLogin')}" id="t3-login-submit" />
</div>
</div>
</form>
</f:section>
<f:section name="loginNews">
<f:if condition="{loginNewsItems}">
<div class="panel-carousel carousel slide t3js-login-news-carousel" id="loginNews" data-interval="0" data-ride="carousel">
<div class="carousel-inner" role="listbox">
<f:for each="{loginNewsItems}" as="item" iteration="loginNewsIterator">
<div class="item{f:if(condition: loginNewsIterator.isFirst,then: ' active')}">
<h3 class="typo3-login-news-heading">{item.header}</h3>
<f:format.html>{item.content}</f:format.html>
<span class="text-muted">{item.date}</span>
</div>
</f:for>
</div>
<a class="left typo3-login-carousel-control" href="#loginNews" role="button" data-slide="prev">
<i class="fa fa-angle-left"></i>
<span class="sr-only"><f:translate key="{labelPrefixPath}labels.previous" /></span>
</a>
<a class="right typo3-login-carousel-control" href="#loginNews" role="button" data-slide="next">
<i class="fa fa-angle-right"></i>
<span class="sr-only"><f:translate key="{labelPrefixPath}labels.next" /></span>
</a>
</div>
</f:if>
</f:section>
\ No newline at end of file
<f:layout name="Login" />
<f:section name="loginFormFields">
<div class="form-group t3js-login-username-section" id="t3-login-username-section">
<div class="form-control-wrap">
<div class="form-control-holder">
<input type="text" id="t3-username" name="username" value="{presetUsername}" placeholder="{f:translate(key: 'login.username')}" class="form-control input-login t3js-clearable t3js-login-username-field" autofocus="autofocus" required="required">
<div class="form-notice-capslock hidden t3js-login-alert-capslock">
<img src="{images.capslock}" width="14" height="14" alt="{f:translate(key: 'login.error.capslock')}" title="{f:translate(key: 'login.error.capslock')}" />
</div>
</div>
</div>
</div>
<div class="form-group t3js-login-password-section" id="t3-login-password-section">
<div class="form-control-wrap">
<div class="form-control-holder">
<input type="password" id="t3-password" name="p_field" value="{presetPassword}" placeholder="{f:translate(key: 'login.password')}" class="form-control input-login t3js-clearable t3js-login-password-field" required="required">
<div class="form-notice-capslock hidden t3js-login-alert-capslock">
<img src="{images.capslock}" width="14" height="14" alt="{f:translate(key: 'login.error.capslock')}" title="{f:translate(key: 'login.error.capslock')}" />
</div>
</div>
</div>
</div>
</f:section>
......@@ -19,88 +19,16 @@ define('TYPO3/CMS/Backend/Login', ['jquery', 'TYPO3/CMS/Backend/jquery.clearable
options: {
loginForm: '#typo3-login-form',
interfaceField: '.t3js-login-interface-field',
interfaceSection: '.t3js-login-interface-section',
usernameField: '.t3js-login-username-field',
usernameSection: '.t3js-login-username-section',
passwordField: '.t3js-login-password-field',
passwordSection: '.t3js-login-password-section',
openIdField: '.t3js-login-openid-field',
openIdSection: '.t3js-login-openid-section',
useridentField: '.t3js-login-userident-field',
submitButton: '.t3js-login-submit',
error: '.t3js-login-error',
errorNoCookies: '.t3js-login-error-nocookies',
formFields: '.t3js-login-formfields',
switchOpenIdSelector: '.t3js-login-switch-to-openid',
switchDefaultSelector: '.t3js-login-switch-to-default',
submitHandler: null
}
},
options = BackendLogin.options;
// Checks whether capslock is enabled (returns TRUE if enabled, false otherwise)
// thanks to http://24ways.org/2007/capturing-caps-lock
BackendLogin.isCapslockEnabled = function(e) {
var ev = e ? e : window.event;
if (!ev) {
return;
}
// get key pressed
var which = -1;
if (ev.which) {
which = ev.which;
} else if (ev.keyCode) {
which = ev.keyCode;
}
// get shift status
var shift_status = false;
if (ev.shiftKey) {
shift_status = ev.shiftKey;
} else if (ev.modifiers) {
shift_status = !!(ev.modifiers & 4);
}
return (which >= 65 && which <= 90 && !shift_status)
|| (which >= 97 && which <= 122 && shift_status);
};
/**
* Change to Interface for OpenId login and save the selection to a cookie
*/
BackendLogin.switchToOpenId = function(e) {
if (!$(this).hasClass("disabled")) {
$(options.switchOpenIdSelector).addClass('hidden');
$(options.switchDefaultSelector).removeClass('hidden');
$(options.interfaceSection).addClass('hidden');
$(options.passwordSection + ', ' + options.usernameSection).addClass('hidden');
$(options.openIdSection).removeClass('hidden');
$(options.openIdField).trigger('focus');
$(options.usernameField).val('openid_url');
$(options.passwordField).val('');
BackendLogin.setLogintypeCookie('openid');
} else {
return false;
}
};
/**
* Change to Interface for default login and save the selection to a cookie