N°637 Portal: Better feedback on exceptions and user session timeout on AJAX calls

SVN:trunk[4671]
This commit is contained in:
Guillaume Lajarige
2017-04-06 14:21:27 +00:00
parent 3773a88cc1
commit 1528d85f5f
15 changed files with 165 additions and 76 deletions

View File

@@ -42,10 +42,12 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'Portal:Button:Delete' => 'Smazat',
'Portal:EnvironmentBanner:Title' => 'You are currently in <strong>%1$s</strong> mode~~',
'Portal:EnvironmentBanner:GoToProduction' => 'Go back to PRODUCTION mode~~',
'Error:HTTP:404' => 'Stránka nenalezena',
'Error:HTTP:401' => 'Authentication~~',
'Error:HTTP:404' => 'Stránka nenalezena',
'Error:HTTP:500' => 'Jejda! Nastal problém',
'Error:HTTP:GetHelp' => 'Kontaktujte prosím administrátora, pokud problém přetrvá.',
'Error:XHR:Fail' => 'Data se nepodařilo načíst, kontaktujte prosím administrátora.',
'Portal:ErrorUserLoggedOut' => 'You are logged out and need to log in again in order to continue.~~',
'Portal:Datatables:Language:Processing' => 'Počkejte prosím',
'Portal:Datatables:Language:Search' => 'Filtr :',
'Portal:Datatables:Language:LengthMenu' => 'Zobrazit _MENU_ položek na stránku',

View File

@@ -39,11 +39,13 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Portal:Button:Delete' => 'Löschen',
'Portal:EnvironmentBanner:Title' => 'Sie sind im Moment im <strong>%1$s</strong> Modus',
'Portal:EnvironmentBanner:GoToProduction' => 'Zurück zum PRODUCTION Modus',
'Error:HTTP:404' => 'Seite nicht gefunden.',
'Error:HTTP:401' => 'Authentication~~',
'Error:HTTP:404' => 'Seite nicht gefunden.',
'Error:HTTP:500' => 'Oops! Es ist ein Fehler aufgetreten.',
'Error:HTTP:GetHelp' => 'Bitte kontaktieren Sie Ihren iTop administrator falls das Problem öfter auftaucht.',
'Error:XHR:Fail' => 'Konnte Daten nicht laden, bitte kontaktieren Sie Ihren iTop administrator',
'Portal:Datatables:Language:Processing' => 'Bitte warten...',
'Portal:ErrorUserLoggedOut' => 'You are logged out and need to log in again in order to continue.~~',
'Portal:Datatables:Language:Processing' => 'Bitte warten...',
'Portal:Datatables:Language:Search' => 'Filter :',
'Portal:Datatables:Language:LengthMenu' => 'Anzahl _MENU_ Einträge pro Seite',
'Portal:Datatables:Language:ZeroRecords' => 'Keine Resultate',

View File

@@ -38,11 +38,13 @@ Dict::Add('EN US', 'English', 'English', array(
'Portal:Button:Delete' => 'Delete',
'Portal:EnvironmentBanner:Title' => 'You are currently in <strong>%1$s</strong> mode',
'Portal:EnvironmentBanner:GoToProduction' => 'Go back to PRODUCTION mode',
'Error:HTTP:404' => 'Page not found',
'Error:HTTP:401' => 'Authentication',
'Error:HTTP:404' => 'Page not found',
'Error:HTTP:500' => 'Oops! An error has occured.',
'Error:HTTP:GetHelp' => 'Please contact your iTop administrator if the problem keeps happening.',
'Error:XHR:Fail' => 'Could not load data, please contact your iTop administrator',
'Portal:Datatables:Language:Processing' => 'Please wait...',
'Portal:ErrorUserLoggedOut' => 'You are logged out and need to log in again in order to continue.',
'Portal:Datatables:Language:Processing' => 'Please wait...',
'Portal:Datatables:Language:Search' => 'Filter:',
'Portal:Datatables:Language:LengthMenu' => 'Display _MENU_ items per page',
'Portal:Datatables:Language:ZeroRecords' => 'No result',

View File

@@ -38,11 +38,13 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Portal:Button:Delete' => 'Borrar',
'Portal:EnvironmentBanner:Title' => 'You are currently in <strong>%1$s</strong> mode~~',
'Portal:EnvironmentBanner:GoToProduction' => 'Go back to PRODUCTION mode~~',
'Error:HTTP:404' => 'Página no encontrada',
'Error:HTTP:401' => 'Authentication~~',
'Error:HTTP:404' => 'Página no encontrada',
'Error:HTTP:500' => '¡Vaya! Ha ocurrido un error.',
'Error:HTTP:GetHelp' => 'Póngase en contacto con el administrador de iTop si el problema persiste.',
'Error:XHR:Fail' => 'No se pudieron cargar datos, póngase en contacto con su administrador de iTop',
'Portal:Datatables:Language:Processing' => 'Por favor esperar...',
'Portal:ErrorUserLoggedOut' => 'You are logged out and need to log in again in order to continue.~~',
'Portal:Datatables:Language:Processing' => 'Por favor esperar...',
'Portal:Datatables:Language:Search' => 'Filtrar:',
'Portal:Datatables:Language:LengthMenu' => 'Mostrar _MENU_ elementos por página',
'Portal:Datatables:Language:ZeroRecords' => 'Sin resultados',

View File

@@ -38,10 +38,12 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Portal:Button:Delete' => 'Supprimer',
'Portal:EnvironmentBanner:Title' => 'Vous êtes dans le mode <strong>%1$s</strong>',
'Portal:EnvironmentBanner:GoToProduction' => 'Retourner au mode PRODUCTION',
'Error:HTTP:404' => 'Page non trouvée',
'Error:HTTP:401' => 'Authentification',
'Error:HTTP:404' => 'Page non trouvée',
'Error:HTTP:500' => 'Oups ! Une erreur est survenue.',
'Error:HTTP:GetHelp' => 'Si le problème persiste, veuillez contacter votre administrateur iTop.',
'Error:XHR:Fail' => 'Impossible de charger les données, veuillez contacter votre administrateur iTop si le problème persiste',
'Error:XHR:Fail' => 'Impossible de charger les données, veuillez contacter votre administrateur iTop si le problème persiste.',
'Portal:ErrorUserLoggedOut' => 'Vous êtes déconnecté et devez vous reconnecter pour continuer.',
'Portal:Datatables:Language:Processing' => 'Veuillez patienter...',
'Portal:Datatables:Language:Search' => 'Filtrer :',
'Portal:Datatables:Language:LengthMenu' => 'Afficher _MENU_ éléments par page',

View File

@@ -36,11 +36,13 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Portal:Button:Add' => 'Toevoegen',
'Portal:Button:Remove' => 'Verwijderen',
'Portal:Button:Delete' => 'Verwijderen',
'Error:HTTP:404' => 'Pagina kan niet worden gevonden',
'Error:HTTP:401' => 'Authentication~~',
'Error:HTTP:404' => 'Pagina kan niet worden gevonden',
'Error:HTTP:500' => 'Oeps! Er is een fout opgetreden',
'Error:HTTP:GetHelp' => 'Neem contact op met de beheerder als dit probleem zich blijft voordoen',
'Error:XHR:Fail' => 'De data kan niet worden geladen, neem contact op met de beheerder',
'Portal:Datatables:Language:Processing' => 'Even geduld...',
'Portal:ErrorUserLoggedOut' => 'You are logged out and need to log in again in order to continue.~~',
'Portal:Datatables:Language:Processing' => 'Even geduld...',
'Portal:Datatables:Language:Search' => 'Filter :',
'Portal:Datatables:Language:LengthMenu' => 'Toon _MENU_ items per pagina',
'Portal:Datatables:Language:ZeroRecords' => 'Geen resultaten',

View File

@@ -275,7 +275,7 @@ class ObjectController extends AbstractController
public function CreateFromFactoryAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sEncodedMethodName)
{
$sMethodName = base64_decode($sEncodedMethodName);
// Checking that the factory method is valid
if (!is_callable($sMethodName))
{

View File

@@ -28,14 +28,11 @@
sUrl = AddParameterToUrl(sUrl, 'ar_token', '{{ ar_token }}');
// Loading form
oModalElem.find('.modal-content').load(sUrl, function(oData, sStatus, oXHR){
var oResponse = (oXHR.responseJSON !== undefined) ? oXHR.responseJSON : JSON.parse(oXHR.responseText);
// Note : This could be refactored for a global use
oModalElem.html( $('#modal-for-alert').html() );
oModalElem.find('.modal-title').html(oResponse.error_title);
oModalElem.find('.modal-body .alert').html(oResponse.error_message)
.removeClass('alert-success alert-info alert-warning alert-danger')
.addClass('alert-danger');
if(sStatus === 'error')
{
// Hiding modal in case of error as the general AJAX error handler will display a message
oModalElem.modal('hide');
}
});
});
});

View File

@@ -35,11 +35,11 @@
{% block pPageLiveScriptHelpers %}
{{ parent() }}
// Helpers used for brick's opening target
var SetActionUrl = function(oElem, sUrl)
{
oElem.attr('href', sUrl);
};
var SetActionOpeningTarget = function(oElem, sMode)
{
if(sMode === '{{ constant('Combodo\\iTop\\Portal\\Brick\\PortalBrick::ENUM_OPENING_TARGET_MODAL') }}')

View File

@@ -172,22 +172,27 @@
{% endif %}
},
"error": function(oData, sError, sThrow){
// Error returned by the framework
if(oData.responseJSON !== undefined && oData.responseJSON !== null)
{
var oResponse = oData.responseJSON;
// If we encounter an error
if(oResponse.exception !== undefined)
{
// Note : This could be refactored for a global use
$('#{{ sTableId }}').closest('.modal').html( $('#modal-for-alert').html() );
var oModalElem = $('#{{ sTableId }}').closest('.modal');
oModalElem.find('.modal-title').html(oResponse.error_title);
oModalElem.find('.modal-body .alert').html(oResponse.error_message)
.removeClass('alert-success alert-info alert-warning alert-danger')
.addClass('alert-danger');
oModalElem.modal('show');
{# Hiding modal in case of error as the general AJAX error handler will display a message #}
{% if tIsModal is defined and tIsModal == true %}
$('#{{ sFormId }}').closest('.modal').modal('hide');
{% endif %}
}
}
// Global failure
else
{
{# Hiding modal in case of error as the general AJAX error handler will display a message #}
{% if tIsModal is defined and tIsModal == true %}
$('#{{ sFormId }}').closest('.modal').modal('hide');
{% endif %}
}
}
}
});

View File

@@ -68,7 +68,7 @@
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/latinise/latinise.min.js'|add_itop_version }}"></script>
{# Visible.js to check if an element is visible on screen #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery-visible/js/jquery.visible.min.js'|add_itop_version }}"></script>
{# Base64.js #}
{# Base64.js #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery-base64/js/jquery.base64.min.js'|add_itop_version }}"></script>
{# Moment.js #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/moment/js/moment.min.js'|add_itop_version }}"></script>
@@ -314,10 +314,12 @@
{% block pPageLiveScripts %}
<script type="text/javascript">
{% block pPageLiveScriptHelpers %}
// Helper to get the application root url
var GetAbsoluteUrlAppRoot = function()
{
return '{{ app['combodo.absolute_url'] }}';
};
// Helper to add a parameter to an url
var AddParameterToUrl = function(sUrl, sParamName, sParamValue)
{
sUrl += (sUrl.split('?')[1] ? '&':'?') + sParamName + '=' + sParamValue;
@@ -327,6 +329,33 @@
{
return '<div class="content_loader"><div class="icon glyphicon glyphicon-refresh"></div><div class="message">{{ 'Page:PleaseWait'|dict_s }}</div></div>';
}
var ShowLoginDialog = function()
{
var oModalElem = $('#modal-for-alert').clone();
oModalElem.attr('id', '');
oModalElem.find('.modal-content .modal-header .modal-title').html('{{ 'Error:HTTP:401'|dict_s|escape('js') }}');
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').html('{{ 'Portal:ErrorUserLoggedOut'|dict_s|escape('js') }}');
oModalElem.find('.modal-content .modal-body button').replaceWith( $('<button type="button" class="btn btn-primary" onclick="javascript:window.location.reload();">{{ 'UI:LogOff:ClickHereToLoginAgain'|dict_s|escape('js') }}</button>') );
oModalElem.appendTo('body');
oModalElem.modal('show');
};
var ShowErrorDialog = function(sBody, sTitle)
{
if(sTitle === undefined)
{
sTitle = '{{ 'Error:HTTP:500'|dict_s|escape('js') }}';
}
if(sBody === undefined)
{
sBody = '{{ 'Error:XHR:Fail'|dict_s|escape('js') }}';
}
var oModalElem = $('#modal-for-alert');
oModalElem.find('.modal-content .modal-header .modal-title').html(sTitle);
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').html(sBody);
oModalElem.modal('show');
};
{% endblock %}
$(document).ready(function(){
@@ -354,11 +383,25 @@
if( (sModalContent === '') || (sModalContent.replace(/[\n\r\t]+/g, '') === GetContentLoaderTemplate()) )
{
$(oEvent.target).find('.modal-content').html($('#modal-for-alert .modal-content').html());
$(oEvent.target).find('.modal-content .modal-header .modal-title').text('{{ 'Error:HTTP:500'|dict_s }}');
$(oEvent.target).find('.modal-content .modal-body .alert').addClass('alert-danger').text('{{ 'Error:XHR:Fail'|dict_s }}');
$(oEvent.target).modal('hide');
}
});
// Handle AJAX errors (exceptions (500), logout (401), ...)
$(document).ajaxError(function(oEvent, oXHR, oSettings, sError){
if(oXHR.status === 401)
{
ShowLoginDialog();
}
else if(oXHR.status === 404)
{
ShowErrorDialog('{{ 'UI:ObjectDoesNotExist'|dict_s|escape('js') }}', '{{ 'Error:HTTP:404'|dict_s|escape('js') }}');
}
else
{
ShowErrorDialog();
}
});
{% endblock %}
});
</script>

View File

@@ -41,30 +41,19 @@ require_once __DIR__ . '/../src/helpers/scopevalidatorhelper.class.inc.php';
require_once __DIR__ . '/../src/helpers/securityhelper.class.inc.php';
require_once __DIR__ . '/../src/helpers/applicationhelper.class.inc.php';
use \Silex\Application;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
// Checking user rights and prompt if needed
LoginWebPage::DoLoginEx(PORTAL_ID);
if (UserRights::GetContactId() == 0)
{
die(Dict::S('Portal:ErrorNoContactForThisUser'));
}
// Stacking context tag so it knows we are in the portal
$oContex = new ContextTag('GUI:Portal');
$oContex2 = new ContextTag('Portal:' . PORTAL_MODULE_ID);
if (!defined('DISABLE_DATA_LOCALIZER_PORTAL'))
{
ApplicationContext::SetPluginProperty('QueryLocalizerPlugin', 'language_code', UserRights::GetUserLanguage());
}
// Checking if debug param is on
$bDebug = (isset($_REQUEST['debug']) && ($_REQUEST['debug'] === 'true') );
// Initializing Silex framework
$oKPI = new ExecutionKPI();
$oApp = new Silex\Application();
$oApp = new Application();
// Registring optional silex components
$oApp->register(new Combodo\iTop\Portal\Provider\UrlGeneratorServiceProvider());
@@ -83,36 +72,57 @@ $oApp->register(new Silex\Provider\TwigServiceProvider(), array(
$oApp->register(new Silex\Provider\HttpFragmentServiceProvider());
$oKPI->ComputeAndReport('Initialization of the Silex application');
// Configuring Silex application
$oApp['debug'] = $bDebug;
$oApp['combodo.current_environment'] = utils::GetCurrentEnvironment();
$oApp['combodo.absolute_url'] = utils::GetAbsoluteUrlAppRoot();
$oApp['combodo.portal.base.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/';
$oApp['combodo.portal.base.absolute_path'] = MODULESROOT . '/itop-portal-base/portal/web/';
$oApp['combodo.portal.instance.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . PORTAL_MODULE_ID . '/';
$oApp['combodo.portal.instance.id'] = PORTAL_MODULE_ID;
$oApp['combodo.portal.instance.conf'] = array();
$oApp['combodo.portal.instance.routes'] = array();
$oApp->before(function(Symfony\Component\HttpFoundation\Request $oRequest, Silex\Application $oApp) use ($bDebug){
// Checking user rights and prompt if needed (401 HTTP code returned if XHR request)
$iExitMethod = ($oRequest->isXmlHttpRequest()) ? LoginWebPage::EXIT_RETURN : LoginWebPage::EXIT_PROMPT;
$iLogonRes = LoginWebPage::DoLoginEx(PORTAL_ID, false, $iExitMethod);
if( ($iExitMethod === LoginWebPage::EXIT_RETURN) && ($iLogonRes != 0) )
{
$oApp->abort(401, Dict::S('Portal:ErrorUserLoggedOut'));
}
// Registering error/exception handler in order to transform php error to exception
ApplicationHelper::RegisterExceptionHandler($oApp);
if (UserRights::GetContactId() == 0)
{
$oApp->abort(500, Dict::S('Portal:ErrorNoContactForThisUser'));
}
// Preparing portal foundations (Can't use Silex autoload through composer as we don't follow PSR conventions -filenames, functions-)
$oKPI = new ExecutionKPI();
ApplicationHelper::LoadControllers();
ApplicationHelper::LoadRouters();
ApplicationHelper::RegisterRoutes($oApp);
ApplicationHelper::LoadBricks();
ApplicationHelper::LoadFormManagers();
ApplicationHelper::RegisterTwigExtensions($oApp['twig']);
$oKPI->ComputeAndReport('Loading portal files (routers, controllers, ...)');
// Enabling datalocalizer if needed
if (!defined('DISABLE_DATA_LOCALIZER_PORTAL'))
{
ApplicationContext::SetPluginProperty('QueryLocalizerPlugin', 'language_code', UserRights::GetUserLanguage());
}
// Loading portal configuration from the module design
$oKPI = new ExecutionKPI();
ApplicationHelper::LoadPortalConfiguration($oApp);
$oKPI->ComputeAndReport('Parsing portal configuration');
// Loading current user
ApplicationHelper::LoadCurrentUser($oApp);
// Configuring Silex application
$oApp['debug'] = $bDebug;
$oApp['combodo.current_environment'] = utils::GetCurrentEnvironment();
$oApp['combodo.absolute_url'] = utils::GetAbsoluteUrlAppRoot();
$oApp['combodo.portal.base.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/';
$oApp['combodo.portal.base.absolute_path'] = MODULESROOT . '/itop-portal-base/portal/web/';
$oApp['combodo.portal.instance.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . PORTAL_MODULE_ID . '/';
$oApp['combodo.portal.instance.id'] = PORTAL_MODULE_ID;
$oApp['combodo.portal.instance.conf'] = array();
$oApp['combodo.portal.instance.routes'] = array();
// Registering error/exception handler in order to transform php error to exception
ApplicationHelper::RegisterExceptionHandler($oApp);
// Preparing portal foundations (Can't use Silex autoload through composer as we don't follow PSR conventions -filenames, functions-)
$oKPI = new ExecutionKPI();
ApplicationHelper::LoadControllers();
ApplicationHelper::LoadRouters();
ApplicationHelper::RegisterRoutes($oApp);
ApplicationHelper::LoadBricks();
ApplicationHelper::LoadFormManagers();
ApplicationHelper::RegisterTwigExtensions($oApp['twig']);
$oKPI->ComputeAndReport('Loading portal files (routers, controllers, ...)');
// Loading portal configuration from the module design
$oKPI = new ExecutionKPI();
ApplicationHelper::LoadPortalConfiguration($oApp);
$oKPI->ComputeAndReport('Parsing portal configuration');
// Loading current user
ApplicationHelper::LoadCurrentUser($oApp);
}, Application::EARLY_EVENT);
// Running application
$oKPI = new ExecutionKPI();

View File

@@ -23,10 +23,12 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'Portal:Button:Add' => 'Добавить',
'Portal:Button:Remove' => 'Удалить',
'Portal:Button:Delete' => 'Удалить',
'Error:HTTP:401' => 'Authentication~~',
'Error:HTTP:404' => 'Страница не найдена',
'Error:HTTP:500' => 'Упс! Произошла ошибка.',
'Error:XHR:Fail' => 'Не удалось загрузить данные. Пожалуйста, свяжитесь с вашим администратором iTop.',
'Error:HTTP:GetHelp' => 'Пожалуйста, свяжитесь с вашим администратором iTop, если проблема сохраняется.',
'Portal:ErrorUserLoggedOut' => 'You are logged out and need to log in again in order to continue.~~',
'Portal:Datatables:Language:Processing' => 'Пожалуйста, подождите...',
'Portal:Datatables:Language:Search' => 'Фильтр :',
'Portal:Datatables:Language:LengthMenu' => 'Показывать _MENU_ элементов на странице',

View File

@@ -451,6 +451,13 @@ EOF
sFormPath: '{$this->oField->GetFormPath()}',
sFieldId: '{$this->oField->GetId()}',
aObjectIdsToIgnore : aObjectIdsToIgnore
},
function(sResponseText, sStatus, oXHR){
// Hiding modal in case of error as the general AJAX error handler will display a message
if(sStatus === 'error')
{
oModalElem.modal('hide');
}
}
);
oModalElem.modal('show');

View File

@@ -75,7 +75,6 @@ class BsSelectObjectFieldRenderer extends FieldRenderer
$oCountSet = new DBObjectSet($oSearch);
$iSetCount = $oCountSet->Count();
// Note : Autocomplete/Search is disabled for template fields as they are not external keys, thus they will just be displayed as regular select.
//$bRegularSelect = ($iSetCount <= $this->oField->GetMaximumComboLength());
$bRegularSelect = ( ($iSetCount <= $this->oField->GetMaximumComboLength()) || ($this->oField->GetSearchEndpoint() === null) || ($this->oField->GetSearchEndpoint() === '') );
unset($oCountSet);
@@ -386,7 +385,14 @@ EOF
{
sFormPath: '{$this->oField->GetFormPath()}',
sFieldId: '{$this->oField->GetId()}'
}
},
function(sResponseText, sStatus, oXHR){
// Hiding modal in case of error as the general AJAX error handler will display a message
if(sStatus === 'error')
{
oModalElem.modal('hide');
}
}
);
oModalElem.modal('show');
});
@@ -433,7 +439,14 @@ EOF
formmanager_class: $(this).closest('.portal_form_handler').portal_form_handler('getOptions').formmanager_class,
formmanager_data: JSON.stringify($(this).closest('.portal_form_handler').portal_form_handler('getOptions').formmanager_data),
current_values: $(this).closest('.portal_form_handler').portal_form_handler('getCurrentValues')
}
},
function(sResponseText, sStatus, oXHR){
// Hiding modal in case of error as the general AJAX error handler will display a message
if(sStatus === 'error')
{
oModalElem.modal('hide');
}
}
);
oModalElem.modal('show');
});