N°659 Console & Portal now we have same object save error messages and sessionmessages

* create new CoreCannotSaveException
* throw this exception in DBInsertNoReload/DBUpdate if CheckToWrite fails
* console : change UI.php code to catch this exception instead of calling CheckToWrite itself (was called twice :(( )
* portal : specific catch for the new exception
* portal : get and displays session messages
This commit is contained in:
Pierre Goiffon
2018-10-19 15:09:31 +02:00
parent 027b0fcff7
commit def4c54d26
8 changed files with 231 additions and 95 deletions

View File

@@ -1581,38 +1581,14 @@ EOD
}
}
/**
* @param string $sTitle
* @param string[] $aIssues
*
* @see AddHeaderMessage
*/
public function AddHeaderMessageForErrors($sTitle, $aIssues)
{
$sContent = "<span><strong>$sTitle</strong></span>";
if (count($aIssues) == 1)
{
$sContent .= " <span>{$aIssues[0]}</span>";
}
else
{
$sContent .= '\n<ul>';
foreach ($aIssues as $sError)
{
$sContent .= "\n<li>$sError";
}
$sContent .= '</ul>';
}
$this->AddHeaderMessage($sContent, 'message_error');
}
/**
* Adds in the page a container with the header_message CSS class
*
* @param string $sContent
* @param string $sCssClasses CSS classes to add to the container
*
* @since 2.6
*/
public function AddHeaderMessage($sContent, $sCssClasses = 'message_info')
{

View File

@@ -107,6 +107,81 @@ class CoreException extends Exception
}
}
/**
* Class CoreCannotSaveObjectException
*
* Specialized exception to raise if {@link DBObject::CheckToWrite()} fails, which allow easy data retrieval
*
* @see \DBObject::DBInsertNoReload()
* @see \DBObject::DBUpdate()
*
* @since 2.6 N°659 uniqueness constraint
*/
class CoreCannotSaveObjectException extends CoreException
{
/** @var string[] */
private $aIssues;
/** @var int */
private $iObjectId;
/** @var string */
private $sObjectClass;
/**
* CoreCannotSaveObjectException constructor.
*
* @param array $aContextData containing at least those keys : issues, id, class
*/
public function __construct($aContextData)
{
$this->aIssues = $aContextData['issues'];
$this->iObjectId = $aContextData['id'];
$this->sObjectClass = $aContextData['class'];
$sIssues = implode(', ', $this->aIssues);
parent::__construct($sIssues, $aContextData);
}
/**
* @return string
*/
public function getHtmlMessage()
{
$sTitle = Dict::S('UI:Error:SaveFailed');
$sContent = "<span><strong>{$sTitle}</strong></span>";
if (count($this->aIssues) == 1)
{
$sContent .= " <span>{$this->aIssues[0]}</span>";
}
else
{
$sContent .= '\n<ul>';
foreach ($this->aIssues as $sError)
{
$sContent .= "\n<li>$sError";
}
$sContent .= '</ul>';
}
return $sContent;
}
public function getIssues()
{
return $this->aIssues;
}
public function getObjectId()
{
return $this->iObjectId;
}
public function getObjectClass()
{
return $this->sObjectClass;
}
}
class CoreWarning extends CoreException
{
}

View File

@@ -1905,8 +1905,18 @@ abstract class DBObject implements iDisplay
return $this->m_iKey;
}
// Insert of record for the new object into the database
// Returns the key of the newly created object
/**
* Insert of record for the new object into the database
*
* @return int key of the newly created object
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException if {@link CheckToWrite()} returns issues
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
*/
public function DBInsertNoReload()
{
if ($this->m_bIsInDB)
@@ -1940,8 +1950,7 @@ abstract class DBObject implements iDisplay
list($bRes, $aIssues) = $this->CheckToWrite();
if (!$bRes)
{
$sIssues = implode(', ', $aIssues);
throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
throw new CoreCannotSaveObjectException(array('issues' => $aIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
}
// Stop watches
@@ -2152,7 +2161,13 @@ abstract class DBObject implements iDisplay
$this->m_iKey = self::GetNextTempId(get_class($this));
}
// Update a record
/**
* Update an object in DB
*
* @return int object key
* @throws \CoreException
* @throws \CoreCannotSaveObjectException if {@link CheckToWrite()} returns issues
*/
public function DBUpdate()
{
if (!$this->m_bIsInDB)
@@ -2205,8 +2220,7 @@ abstract class DBObject implements iDisplay
list($bRes, $aIssues) = $this->CheckToWrite();
if (!$bRes)
{
$sIssues = implode(', ', $aIssues);
throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
throw new CoreCannotSaveObjectException(array('issues' => $aIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
}
// Save the original values (will be reset to the new values when the object get written to the DB)
@@ -2326,13 +2340,19 @@ abstract class DBObject implements iDisplay
$this->RecordAttChanges($aChanges, $aOriginalValues);
}
}
catch (Exception $e)
catch (CoreCannotSaveObjectException $e)
{
unset($aUpdateReentrance[$sKey]);
throw $e;
}
catch (Exception $e)
{
throw $e;
}
finally
{
unset($aUpdateReentrance[$sKey]);
}
unset($aUpdateReentrance[$sKey]);
return $this->m_iKey;
}
@@ -2342,7 +2362,13 @@ abstract class DBObject implements iDisplay
return $this->DBUpdate();
}
// Make the current changes persistent - clever wrapper for Insert or Update
/**
* Make the current changes persistent - clever wrapper for Insert or Update
*
* @return int
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
*/
public function DBWrite()
{
if ($this->m_bIsInDB)

View File

@@ -19,29 +19,30 @@
namespace Combodo\iTop\Portal\Form;
use Exception;
use Silex\Application;
use utils;
use Dict;
use IssueLog;
use UserRights;
use MetaModel;
use CMDBSource;
use DBObject;
use DBObjectSet;
use DBSearch;
use DBObjectSearch;
use InlineImage;
use ormTagSet;
use AttachmentPlugIn;
use AttributeDateTime;
use AttributeTagSet;
use AttachmentPlugIn;
use Combodo\iTop\Form\FormManager;
use Combodo\iTop\Form\Form;
use CMDBSource;
use Combodo\iTop\Form\Field\Field;
use Combodo\iTop\Form\Field\FileUploadField;
use Combodo\iTop\Form\Field\LabelField;
use Combodo\iTop\Form\Form;
use Combodo\iTop\Form\FormManager;
use Combodo\iTop\Portal\Helper\ApplicationHelper;
use CoreCannotSaveObjectException;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use DBSearch;
use Dict;
use Exception;
use InlineImage;
use IssueLog;
use MetaModel;
use ormTagSet;
use Silex\Application;
use UserRights;
use utils;
/**
* Description of objectformmanager
@@ -976,7 +977,14 @@ class ObjectFormManager extends FormManager
// Writing object to DB
$bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified());
$bWasModified = $this->oObject->IsModified();
$this->oObject->DBWrite();
try
{
$this->oObject->DBWrite();
}
catch (CoreCannotSaveObjectException $e)
{
throw new Exception($e->getHtmlMessage());
}
// Finalizing images link to object, otherwise it will be cleaned by the GC
InlineImage::FinalizeInlineImages($this->oObject);
// Finalizing attachments link to object

View File

@@ -20,6 +20,7 @@
namespace Combodo\iTop\Portal\Helper;
use ApplicationContext;
use cmdbAbstractObject;
use Combodo\iTop\Portal\Brick\AbstractBrick;
use Combodo\iTop\Portal\Brick\PortalBrick;
use DBObjectSearch;
@@ -30,12 +31,10 @@ use Exception;
use iPortalUIExtension;
use IssueLog;
use MetaModel;
use cmdbAbstractObject;
use ModuleDesign;
use Silex\Application;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Twig_Environment;
use Twig_SimpleFilter;
@@ -1343,6 +1342,45 @@ class ApplicationHelper
return $aUIExtensions;
}
public static function LoadSessionMessages(Application $oApp)
{
$aAllMessages = array();
if ((array_key_exists('obj_messages', $_SESSION)) && (!empty($_SESSION['obj_messages'])))
{
foreach ($_SESSION['obj_messages'] as $sMessageKey => $aMessageObjectData)
{
$aObjectMessages = array();
$aRanks = array();
foreach ($aMessageObjectData as $sMessageId => $aMessageData)
{
$sMsgClass = 'alert alert-';
switch ($aMessageData['severity'])
{
case 'info':
$sMsgClass .= 'info';
break;
case 'error':
$sMsgClass .= 'danger';
break;
case 'ok':
default:
$sMsgClass .= 'success';
break;
}
$aObjectMessages[] = array('cssClass' => $sMsgClass, 'message' => $aMessageData['message']);
$aRanks[] = $aMessageData['rank'];
}
unset($_SESSION['obj_messages'][$sMessageKey]);
array_multisort($aRanks, $aObjectMessages);
foreach ($aObjectMessages as $aObjectMessage)
{
$aAllMessages[] = $aObjectMessage;
}
}
}
$oApp['combodo.current_user.session_messages'] = $aAllMessages;
}
/**
* Generate the form data for the $sClass.
* Form will look like the "Properties" tab of a $sClass object in the console.

View File

@@ -313,7 +313,17 @@
<div class="container-fluid" id="main-wrapper">
<div class="row">
<div class="col-xs-12 col-sm-9 col-md-10 col-sm-offset-3 col-md-offset-2">
{% if app['combodo.current_user.session_messages']|length > 0 %}
<section class="row" id="session-messages">
<div class="col-xs-12">
{% for sessionMessage in app['combodo.current_user.session_messages'] %}
<div class="{{ sessionMessage['cssClass'] }}">{{ sessionMessage['message'] }}</div>
{% endfor %}
</div>
</section>
{% endif %}
<section class="row" id="main-header">
{% block pMainHeader %}
{% endblock %}

View File

@@ -45,8 +45,8 @@ require_once __DIR__ . '/../src/helpers/lifecyclevalidatorhelper.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;
use Silex\Application;
// Stacking context tag so it knows we are in the portal
$oContex = new ContextTag('GUI:Portal');
@@ -141,6 +141,7 @@ $oApp->before(function(Symfony\Component\HttpFoundation\Request $oRequest, Silex
// Loading portal configuration from the module design
$oKPI = new ExecutionKPI();
ApplicationHelper::LoadPortalConfiguration($oApp);
ApplicationHelper::LoadSessionMessages($oApp);
$oKPI->ComputeAndReport('Parsing portal configuration');
// Loading current user
ApplicationHelper::LoadCurrentUser($oApp);

View File

@@ -920,40 +920,40 @@ EOF
}
else
{
list($bRes, $aIssues) = $oObj->CheckToWrite(); // called also in DBUpdate()
if ($bRes)
try
{
try
{
CMDBSource::Query('START TRANSACTION');
$oObj->DBUpdate();
CMDBSource::Query('COMMIT');
$sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName());
$sSeverity = 'ok';
}
catch(DeleteException $e)
{
CMDBSource::Query('ROLLBACK');
// Say two things: 1) Don't be afraid nothing was modified
$sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName());
$sSeverity = 'info';
cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), 'UI:Class_Object_NotUpdated', $sMessage, $sSeverity, 0, true /* must not exist */);
// 2) Ok, there was some trouble indeed
$sMessage = $e->getMessage();
$sSeverity = 'error';
$bDisplayDetails = true;
}
utils::RemoveTransaction($sTransactionId);
CMDBSource::Query('START TRANSACTION');
$oObj->DBUpdate();
CMDBSource::Query('COMMIT');
$sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName());
$sSeverity = 'ok';
}
else
catch (CoreCannotSaveObjectException $e)
{
$bDisplayDetails = false;
// Found issues, explain and give the user a second chance
//
$oP->AddHeaderMessageForErrors(Dict::S('UI:Error:SaveFailed'), $aIssues);
$oObj->DisplayModifyForm($oP, array('wizard_container' => true)); // wizard_container: display the wizard border and the title
CMDBSource::Query('ROLLBACK');
$bDisplayDetails = false;
$aIssues = $e->getIssues();
$oP->AddHeaderMessage($e->getHtmlMessage(), 'message_error');
$oObj->DisplayModifyForm($oP,
array('wizard_container' => true)); // wizard_container: display the wizard border and the title
}
catch (DeleteException $e)
{
CMDBSource::Query('ROLLBACK');
// Say two things:
// - 1) Don't be afraid nothing was modified
$sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName());
$sSeverity = 'info';
cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), 'UI:Class_Object_NotUpdated', $sMessage,
$sSeverity, 0, true /* must not exist */);
// - 2) Ok, there was some trouble indeed
$sMessage = $e->getMessage();
$sSeverity = 'error';
$bDisplayDetails = true;
}
utils::RemoveTransaction($sTransactionId);
}
}
if ($bDisplayDetails)
@@ -1101,18 +1101,18 @@ EOF
$sClass = get_class($oObj);
$sClassLabel = MetaModel::GetName($sClass);
list($bRes, $aIssues) = $oObj->CheckToWrite(); // called also in DBInsertNoReload()
if ($bRes)
try
{
$oObj->DBInsertNoReload(); // No need to reload
$oObj->DBInsertNoReload();// No need to reload
utils::RemoveTransaction($sTransactionId);
$oP->set_title(Dict::S('UI:PageTitle:ObjectCreated'));
// Compute the name, by reloading the object, even if it disappeared from the silo
$oObj = MetaModel::GetObject($sClass, $oObj->GetKey(), true /* Must be found */, true /* Allow All Data*/);
$sName = $oObj->GetName();
$sName = $oObj->GetName();
$sMessage = Dict::Format('UI:Title:Object_Of_Class_Created', $sName, $sClassLabel);
$sNextAction = utils::ReadPostedParam('next_action', '');
if (!empty($sNextAction))
{
@@ -1125,14 +1125,16 @@ EOF
ReloadAndDisplay($oP, $oObj, 'create', $sMessage, 'ok');
}
}
else
catch (CoreCannotSaveObjectException $e)
{
// Found issues, explain and give the user a second chance
//
$aIssues = $e->getIssues();
$oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel));
$oP->add("<h1>".MetaModel::GetClassIcon($sClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."</h1>\n");
$oP->add("<div class=\"wizContainer\">\n");
$oP->AddHeaderMessageForErrors(Dict::S('UI:Error:SaveFailed'), $aIssues);
$oP->AddHeaderMessage($e->getHtmlMessage(), 'message_error');
cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj);
$oP->add("</div>\n");
}