/**
* Abstract class that implements some common and useful methods for displaying
* the objects
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
define('OBJECT_PROPERTIES_TAB', 'ObjectProperties');
define('HILIGHT_CLASS_CRITICAL', 'red');
define('HILIGHT_CLASS_WARNING', 'orange');
define('HILIGHT_CLASS_OK', 'green');
define('HILIGHT_CLASS_NONE', '');
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/applicationextension.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/application/applicationcontext.class.inc.php');
require_once(APPROOT.'/application/ui.linkswidget.class.inc.php');
require_once(APPROOT.'/application/ui.linksdirectwidget.class.inc.php');
require_once(APPROOT.'/application/ui.passwordwidget.class.inc.php');
require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php');
require_once(APPROOT.'/application/datatable.class.inc.php');
/**
* All objects to be displayed in the application (either as a list or as details)
* must implement this interface.
*/
interface iDisplay
{
/**
* Maps the given context parameter name to the appropriate filter/search code for this class
* @param string $sContextParam Name of the context parameter, i.e. 'org_id'
* @return string Filter code, i.e. 'customer_id'
*/
public static function MapContextParam($sContextParam);
/**
* This function returns a 'hilight' CSS class, used to hilight a given row in a table
* There are currently (i.e defined in the CSS) 4 possible values HILIGHT_CLASS_CRITICAL,
* HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
* To Be overridden by derived classes
* @param void
* @return String The desired higlight class for the object/row
*/
public function GetHilightClass();
/**
* Returns the relative path to the page that handles the display of the object
* @return string
*/
public static function GetUIPage();
/**
* Displays the details of the object
*/
public function DisplayDetails(WebPage $oPage, $bEditMode = false);
}
abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
protected $m_iFormId; // The ID of the form used to edit the object (when in edition mode !)
static $iGlobalFormId = 1;
/**
* returns what will be the next ID for the forms
*/
public static function GetNextFormId()
{
return 1 + self::$iGlobalFormId;
}
public static function GetUIPage()
{
return 'UI.php';
}
/**
* Set a message diplayed to the end-user next time this object will be displayed
* Messages are uniquely identified so that plugins can override standard messages (the final work is given to the last plugin to set the message for a given message id)
* In practice, standard messages are recorded at the end but they will not overwrite existing messages
*
* @param string $sClass The class of the object (must be the final class)
* @param int $iKey The identifier of the object
* @param string $sMessageId Your id or one of the well-known ids: 'create', 'update' and 'apply_stimulus'
* @param string $sMessage The HTML message (must be correctly escaped)
* @param string $sSeverity Any of the following: ok, info, error.
* @param float $fRank Ordering of the message: smallest displayed first (can be negative)
* @param bool $bMustNotExist Do not alter any existing message (considering the id)
*
*/
public static function SetSessionMessage($sClass, $iKey, $sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false)
{
$sMessageKey = $sClass.'::'.$iKey;
if (!isset($_SESSION['obj_messages'][$sMessageKey]))
{
$_SESSION['obj_messages'][$sMessageKey] = array();
}
if (!$bMustNotExist || !array_key_exists($sMessageId, $_SESSION['obj_messages'][$sMessageKey]))
{
$_SESSION['obj_messages'][$sMessageKey][$sMessageId] = array(
'rank' => $fRank,
'severity' => $sSeverity,
'message' => $sMessage
);
}
}
function DisplayBareHeader(WebPage $oPage, $bEditMode = false)
{
// Standard Header with name, actions menu and history block
//
// Is there a message for this object ??
$sMessageKey = get_class($this).'::'.$this->GetKey();
if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, $_SESSION['obj_messages']))
{
$aMessages = array();
$aRanks = array();
foreach ($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData)
{
$sMsgClass = 'message_'.$aMessageData['severity'];
$aMessages[] = "
".$aMessageData['message']."
";
$aRanks[] = $aMessageData['rank'];
}
array_multisort($aRanks, $aMessages);
foreach ($aMessages as $sMessage)
{
$oPage->add($sMessage);
}
unset($_SESSION['obj_messages'][$sMessageKey]);
}
// action menu
$oSingletonFilter = new DBObjectSearch(get_class($this));
$oSingletonFilter->AddCondition('id', $this->GetKey(), '=');
$oBlock = new MenuBlock($oSingletonFilter, 'details', false);
$oBlock->Display($oPage, -1);
// Master data sources
$sSynchroIcon = '';
$oReplicaSet = $this->GetMasterReplica();
$bSynchronized = false;
$oCreatorTask = null;
$bCanBeDeletedByTask = false;
$bCanBeDeletedByUser = true;
$aMasterSources = array();
if ($oReplicaSet->Count() > 0)
{
$bSynchronized = true;
while($aData = $oReplicaSet->FetchAssoc())
{
// Assumption: $aData['datasource'] will not be null because the data source id is always set...
$sApplicationURL = $aData['datasource']->GetApplicationUrl($this, $aData['replica']);
$sLink = $aData['datasource']->GetName();
if (!empty($sApplicationURL))
{
$sLink = "".$aData['datasource']->GetName()."";
}
if ($aData['replica']->Get('status_dest_creator') == 1)
{
$oCreatorTask = $aData['datasource'];
$bCreatedByTask = true;
}
else
{
$bCreatedByTask = false;
}
if ($bCreatedByTask)
{
$sDeletePolicy = $aData['datasource']->Get('delete_policy');
if (($sDeletePolicy == 'delete') || ($sDeletePolicy == 'update_then_delete'))
{
$bCanBeDeletedByTask = true;
}
$sUserDeletePolicy = $aData['datasource']->Get('user_delete_policy');
if ($sUserDeletePolicy == 'nobody')
{
$bCanBeDeletedByUser = false;
}
elseif (($sUserDeletePolicy == 'administrators') && !UserRights::IsAdministrator())
{
$bCanBeDeletedByUser = false;
}
else // everybody...
{
}
}
$aMasterSources[$aData['datasource']->GetKey()]['datasource'] = $aData['datasource'];
$aMasterSources[$aData['datasource']->GetKey()]['url'] = $sLink;
$aMasterSources[$aData['datasource']->GetKey()]['last_synchro'] = $aData['replica']->Get('status_last_seen');
}
if (is_object($oCreatorTask))
{
$sTaskUrl = $aMasterSources[$oCreatorTask->GetKey()]['url'];
if (!$bCanBeDeletedByUser)
{
$sTip = "
\n";
}
// Check if the current class has some sub-classes
if (isset($aExtraParams['baseClass']))
{
$sRootClass = $aExtraParams['baseClass'];
}
else
{
$sRootClass = $sClassName;
}
$aSubClasses = MetaModel::GetSubclasses($sRootClass);
if (count($aSubClasses) > 0)
{
$aOptions = array();
$aOptions[MetaModel::GetName($sRootClass)] = "
\n");
}
break;
case 'application':
switch($oDoc->GetMimeType())
{
case 'application/pdf':
$oPage->add("\n");
break;
default:
$oPage->add(Dict::S('UI:Document:NoPreview'));
}
break;
case 'image':
$oPage->add("\n");
break;
default:
$oPage->add(Dict::S('UI:Document:NoPreview'));
}
}
// $m_highlightComparison[previous][new] => next value
protected static $m_highlightComparison = array(
HILIGHT_CLASS_CRITICAL => array(
HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_WARNING => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_OK => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_NONE => HILIGHT_CLASS_CRITICAL,
),
HILIGHT_CLASS_WARNING => array(
HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING,
HILIGHT_CLASS_OK => HILIGHT_CLASS_WARNING,
HILIGHT_CLASS_NONE => HILIGHT_CLASS_WARNING,
),
HILIGHT_CLASS_OK => array(
HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING,
HILIGHT_CLASS_OK => HILIGHT_CLASS_OK,
HILIGHT_CLASS_NONE => HILIGHT_CLASS_OK,
),
HILIGHT_CLASS_NONE => array(
HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING,
HILIGHT_CLASS_OK => HILIGHT_CLASS_OK,
HILIGHT_CLASS_NONE => HILIGHT_CLASS_NONE,
),
);
/**
* This function returns a 'hilight' CSS class, used to hilight a given row in a table
* There are currently (i.e defined in the CSS) 4 possible values HILIGHT_CLASS_CRITICAL,
* HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
* To Be overridden by derived classes
* @param void
* @return String The desired higlight class for the object/row
*/
public function GetHilightClass()
{
// Possible return values are:
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
$current = HILIGHT_CLASS_NONE; // Not hilighted by default
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
{
$new = $oExtensionInstance->GetHilightClass($this);
@$current = self::$m_highlightComparison[$current][$new];
}
return $current;
}
/**
* Re-order the fields based on their inter-dependencies
* @params hash @aFields field_code => array_of_depencies
* @return array Ordered array of fields or throws an exception
*/
public static function OrderDependentFields($aFields)
{
$bCircular = false;
$aResult = array();
$iCount = 0;
do
{
$bSet = false;
$iCount++;
foreach($aFields as $sFieldCode => $aDeps)
{
foreach($aDeps as $key => $sDependency)
{
if (in_array($sDependency, $aResult))
{
// Dependency is resolved, remove it
unset($aFields[$sFieldCode][$key]);
}
}
if (count($aFields[$sFieldCode]) == 0)
{
// No more pending depencies for this field, add it to the list
$aResult[] = $sFieldCode;
unset($aFields[$sFieldCode]);
$bSet = true;
}
}
}
while($bSet && (count($aFields) > 0));
if (count($aFields) > 0)
{
$sMessage = "Error: Circular dependencies between the fields (or field missing in ZList) !
".print_r($aFields, true)."
";
throw(new Exception($sMessage));
}
return $aResult;
}
/**
* Get the list of actions to be displayed as 'shortcuts' (i.e buttons) instead of inside the Actions popup menu
* @param $sFinalClass string The actual class of the objects for which to display the menu
* @return Array the list of menu codes (i.e dictionary entries) that can be displayed as shortcuts next to the actions menu
*/
public static function GetShortcutActions($sFinalClass)
{
$sShortcutActions = MetaModel::GetConfig()->Get('shortcut_actions');
$aShortcutActions = explode(',', $sShortcutActions);
return $aShortcutActions;
}
/**
* Maps the given context parameter name to the appropriate filter/search code for this class
* @param string $sContextParam Name of the context parameter, i.e. 'org_id'
* @return string Filter code, i.e. 'customer_id'
*/
public static function MapContextParam($sContextParam)
{
if ($sContextParam == 'menu')
{
return null;
}
else
{
return $sContextParam;
}
}
/**
* Updates the object from a flat array of values
* @param $aAttList array $aAttList array of attcode
* @param $aErrors array Returns information about slave attributes
* @param $sTargetState string Target state for which to evaluate the writeable attributes (=current state is empty)
* @return array of attcodes that can be used for writing on the current object
*/
public function GetWriteableAttList($aAttList, &$aErrors, $sTargetState = '')
{
if (!is_array($aAttList))
{
$aAttList = $this->FlattenZList(MetaModel::GetZListItems(get_class($this), 'details'));
// Special case to process the case log, if any...
// WARNING: if you change this also check the functions DisplayModifyForm and DisplayCaseLog
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
{
if ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$aVoid = array();
$iFlags = $this->GetAttributeFlags($sAttCode, $aVoid, $sTargetState);
}
if ($oAttDef instanceof AttributeCaseLog)
{
if (!($iFlags & (OPT_ATT_HIDDEN|OPT_ATT_SLAVE|OPT_ATT_READONLY)))
{
// The case log is editable, append it to the list of fields to retrieve
$aAttList[] = $sAttCode;
}
}
}
}
$aWriteableAttList = array();
foreach($aAttList as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$aVoid = array();
$iFlags = $this->GetAttributeFlags($sAttCode, $aVoid, $sTargetState);
}
if ($oAttDef->IsWritable())
{
if ( $iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY))
{
// Non-visible, or read-only attribute, do nothing
}
elseif($iFlags & OPT_ATT_SLAVE)
{
$aErrors[$sAttCode] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel());
}
else
{
$aWriteableAttList[$sAttCode] = $oAttDef;
}
}
}
return $aWriteableAttList;
}
/**
* Updates the object from a flat array of values
* @param string $aValues array of attcode => scalar or array (N-N links)
* @return void
*/
public function UpdateObjectFromArray($aValues)
{
foreach($aValues as $sAttCode => $value)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
{
$aLinks = $value;
$sLinkedClass = $oAttDef->GetLinkedClass();
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
$oLinkedSet = DBObjectSet::FromScratch($sLinkedClass);
if (is_array($aLinks))
{
foreach($aLinks as $id => $aData)
{
if (is_numeric($id))
{
if ($id < 0)
{
// New link to be created, the opposite of the id (-$id) is the ID of the remote object
$oLink = MetaModel::NewObject($sLinkedClass);
$oLink->Set($sExtKeyToRemote, -$id);
$oLink->Set($sExtKeyToMe, $this->GetKey());
}
else
{
// Existing link, potentially to be updated...
$oLink = MetaModel::GetObject($sLinkedClass, $id);
}
// Now populate the attributes
foreach($aData as $sName => $value)
{
if (MetaModel::IsValidAttCode($sLinkedClass, $sName))
{
$oLinkAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sName);
if ($oLinkAttDef->IsWritable())
{
$oLink->Set($sName, $value);
}
}
}
$oLinkedSet->AddObject($oLink);
}
}
}
$this->Set($sAttCode, $oLinkedSet);
}
elseif ($oAttDef->GetEditClass() == 'Document')
{
// There should be an uploaded file with the named attr_
$oDocument = $value['fcontents'];
if (!$oDocument->IsEmpty())
{
// A new file has been uploaded
$this->Set($sAttCode, $oDocument);
}
}
elseif ($oAttDef->GetEditClass() == 'One Way Password')
{
// Check if the password was typed/changed
$aPwdData = $value;
if (!is_null($aPwdData) && $aPwdData['changed'])
{
// The password has been changed or set
$this->Set($sAttCode, $aPwdData['value']);
}
}
elseif ($oAttDef->GetEditClass() == 'Duration')
{
$aDurationData = $value;
if (!is_array($aDurationData)) continue;
$iValue = (((24*$aDurationData['d'])+$aDurationData['h'])*60 +$aDurationData['m'])*60 + $aDurationData['s'];
$this->Set($sAttCode, $iValue);
$previousValue = $this->Get($sAttCode);
if ($previousValue !== $iValue)
{
$this->Set($sAttCode, $iValue);
}
}
else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() && ($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE))
{
$oLinkset = $this->Get($sAttCode);
$sLinkedClass = $oLinkset->GetClass();
$aObjSet = array();
$oLinkset->Rewind();
$bModified = false;
while($oLink = $oLinkset->Fetch())
{
if (in_array($oLink->GetKey(), $value['to_be_deleted']))
{
// The link is to be deleted, don't copy it in the array
$bModified = true;
}
else
{
$aObjSet[] = $oLink;
}
}
if (array_key_exists('to_be_created', $value) && (count($value['to_be_created']) > 0))
{
// Now handle the lniks to be created
foreach($value['to_be_created'] as $aData)
{
$sSubClass = $aData['class'];
if ( ($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass)) )
{
$aObjData = $aData['data'];
$oLink = new $sSubClass;
$oLink->UpdateObjectFromArray($aObjData);
$aObjSet[] = $oLink;
$bModified = true;
}
}
}
if ($bModified)
{
$oNewSet = DBObjectSet::FromArray($oLinkset->GetClass(), $aObjSet);
$this->Set($sAttCode, $oNewSet);
}
}
else
{
if (!is_null($value))
{
$aAttributes[$sAttCode] = trim($value);
$previousValue = $this->Get($sAttCode);
if ($previousValue !== $aAttributes[$sAttCode])
{
$this->Set($sAttCode, $aAttributes[$sAttCode]);
}
}
}
}
}
/**
* Updates the object from the POSTed parameters (form)
*/
public function UpdateObjectFromPostedForm($sFormPrefix = '', $aAttList = null, $sTargetState = '')
{
if (is_null($aAttList))
{
$aAttList = array_keys(MetaModel::ListAttributeDefs(get_class($this)));
}
$aValues = array();
foreach($aAttList as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->GetEditClass() == 'Document')
{
$value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'));
}
else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() && ($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE))
{
$aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}', 'raw_data'), true);
$aToBeCreated = array();
foreach($aRawToBeCreated as $aData)
{
$sSubFormPrefix = $aData['formPrefix'];
$sObjClass = $aData['class'];
$aObjData = array();
foreach($aData as $sKey => $value)
{
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches))
{
$aObjData[$aMatches[1]] = $value;
}
}
$aToBeCreated[] = array('class' => $sObjClass, 'data' => $aObjData);
}
$value = array('to_be_created' => $aToBeCreated,
'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', 'raw_data'), true) );
}
else
{
$value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
}
if (!is_null($value))
{
$aValues[$sAttCode] = $value;
}
}
$aErrors = array();
$aFinalValues = array();
foreach($this->GetWriteableAttList(array_keys($aValues), $aErrors, $sTargetState) as $sAttCode => $oAttDef)
{
$aFinalValues[$sAttCode] = $aValues[$sAttCode];
}
$this->UpdateObjectFromArray($aFinalValues);
// Invoke extensions after the update of the object from the form
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnFormSubmit($this, $sFormPrefix);
}
return $aErrors;
}
/**
* Updates the object from a given page argument
*/
public function UpdateObjectFromArg($sArgName, $aAttList = null, $sTargetState = '')
{
if (is_null($aAttList))
{
$aAttList = array_keys(MetaModel::ListAttributeDefs(get_class($this)));
}
$aRawValues = utils::ReadParam($sArgName, array(), '', 'raw_data');
$aValues = array();
foreach($aAttList as $sAttCode)
{
if (isset($aRawValues[$sAttCode]))
{
$aValues[$sAttCode] = $aRawValues[$sAttCode];
}
}
$aErrors = array();
$aFinalValues = array();
foreach($this->GetWriteableAttList(array_keys($aValues), $aErrors, $sTargetState) as $sAttCode => $oAttDef)
{
$aFinalValues[$sAttCode] = $aValues[$sAttCode];
}
$this->UpdateObjectFromArray($aFinalValues);
return $aErrors;
}
public function DBInsertNoReload()
{
$res = parent::DBInsertNoReload();
// Invoke extensions after insertion (the object must exist, have an id, etc.)
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
}
return $res;
}
protected function DBCloneTracked_Internal($newKey = null)
{
$oNewObj = parent::DBCloneTracked_Internal($newKey);
// Invoke extensions after insertion (the object must exist, have an id, etc.)
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
}
return $oNewObj;
}
public function DBUpdate()
{
$res = parent::DBUpdate();
// Invoke extensions after the update (could be before)
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
}
return $res;
}
protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues)
{
// Todo - invoke the extension
return parent::BulkUpdateTracked_Internal($oFilter, $aValues);
}
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
{
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
}
return parent::DBDeleteTracked_Internal($oDeletionPlan);
}
public function IsModified()
{
if (parent::IsModified())
{
return true;
}
// Plugins
//
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
if ($oExtensionInstance->OnIsModified($this))
{
return true;
}
}
return false;
}
public function DoCheckToWrite()
{
parent::DoCheckToWrite();
// Plugins
//
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
if (count($aNewIssues) > 0)
{
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
}
}
// User rights
//
$aChanges = $this->ListChanges();
if (count($aChanges) > 0)
{
$aForbiddenFields = array();
foreach ($this->ListChanges() as $sAttCode => $value)
{
$bUpdateAllowed = UserRights::IsActionAllowedOnAttribute(get_class($this), $sAttCode, UR_ACTION_MODIFY, DBObjectSet::FromObject($this));
if (!$bUpdateAllowed)
{
$oAttCode = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$aForbiddenFields[] = $oAttCode->GetLabel();
}
}
if (count($aForbiddenFields) > 0)
{
// Security issue
$this->m_bSecurityIssue = true;
$this->m_aCheckIssues[] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields',implode(', ', $aForbiddenFields));
}
}
}
protected function DoCheckToDelete(&$oDeletionPlan)
{
parent::DoCheckToDelete($oDeletionPlan);
// Plugins
//
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
if (count($aNewIssues) > 0)
{
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
}
}
// User rights
//
$bDeleteAllowed = UserRights::IsActionAllowed(get_class($this), UR_ACTION_DELETE, DBObjectSet::FromObject($this));
if (!$bDeleteAllowed)
{
// Security issue
$this->m_bSecurityIssue = true;
$this->m_aDeleteIssues[] = Dict::S('UI:Delete:NotAllowedToDelete');
}
}
/**
* Special display where the case log uses the whole "screen" at the bottom of the "Properties" tab
*/
public function DisplayCaseLog(WebPage $oPage, $sAttCode, $sComment = '', $sPrefix = '', $bEditMode = false)
{
$oPage->SetCurrentTab(Dict::S('UI:PropertiesTab'));
$sClass = get_class($this);
if ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$iFlags = $this->GetAttributeFlags($sAttCode);
}
if ( $iFlags & OPT_ATT_HIDDEN)
{
// The case log is hidden do nothing
}
else
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$sInputId = $this->m_iFormId.'_'.$sAttCode;
if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)))
{
// Check if the attribute is not read-only because of a synchro...
$aReasons = array();
$sSynchroIcon = '';
if ($iFlags & OPT_ATT_SLAVE)
{
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
$sSynchroIcon = " ";
$sTip = '';
foreach($aReasons as $aRow)
{
$sTip .= "
Synchronized with {$aRow['name']} - {$aRow['description']}
";
}
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
// Attribute is read-only
$sHTMLValue = $this->GetAsHTML($sAttCode);
$sHTMLValue .= '';
$aFieldsMap[$sAttCode] = $sInputId;
$sComment .= $sSynchroIcon;
}
else
{
$sValue = $this->Get($sAttCode);
$sDisplayValue = $this->GetEditValue($sAttCode);
$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
$sHTMLValue = '';
if ($sComment != '')
{
$sHTMLValue = ''.$sComment.' ';
}
$sHTMLValue .= "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'';
$aFieldsMap[$sAttCode] = $sInputId;
}
//$aVal = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
$oPage->add('');
}
}
public function GetExpectedAttributes($sCurrentState, $sStimulus, $bOnlyNewOnes)
{
$aTransitions = $this->EnumTransitions();
$aStimuli = MetaModel::EnumStimuli(get_class($this));
if (!isset($aTransitions[$sStimulus]))
{
// Invalid stimulus
throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel()));
}
$aTransition = $aTransitions[$sStimulus];
$sTargetState = $aTransition['target_state'];
$aTargetStates = MetaModel::EnumStates(get_class($this));
$aTargetState = $aTargetStates[$sTargetState];
$aCurrentState = $aTargetStates[$this->GetState()];
$aExpectedAttributes = $aTargetState['attribute_list'];
$aCurrentAttributes = $aCurrentState['attribute_list'];
$aComputedAttributes = array();
foreach($aExpectedAttributes as $sAttCode => $iExpectCode)
{
if (!array_key_exists($sAttCode, $aCurrentAttributes))
{
$aComputedAttributes[$sAttCode] = $iExpectCode;
}
else
{
if ( !($aCurrentAttributes[$sAttCode] & (OPT_ATT_HIDDEN|OPT_ATT_READONLY)) )
{
$iExpectCode = $iExpectCode & ~(OPT_ATT_MUSTPROMPT|OPT_ATT_MUSTCHANGE); // Already prompted/changed, reset the flags
}
//TODO: better check if the attribute is not *null*
if ( ($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) != ''))
{
$iExpectCode = $iExpectCode & ~(OPT_ATT_MANDATORY); // If the attribute is present, then no need to request its presence
}
$aComputedAttributes[$sAttCode] = $iExpectCode;
}
$aComputedAttributes[$sAttCode] = $aComputedAttributes[$sAttCode] & ~(OPT_ATT_READONLY|OPT_ATT_HIDDEN); // Don't care about this form now
if ($aComputedAttributes[$sAttCode] == 0)
{
unset($aComputedAttributes[$sAttCode]);
}
}
return $aComputedAttributes;
}
/**
* Display a form for modifying several objects at once
* The form will be submitted to the current page, with the specified additional values
*/
public static function DisplayBulkModifyForm($oP, $sClass, $aSelectedObj, $sCustomOperation, $sCancelUrl, $aExcludeAttributes = array(), $aContextData = array())
{
if (count($aSelectedObj) > 0)
{
$iAllowedCount = count($aSelectedObj);
$sSelectedObj = implode(',', $aSelectedObj);
$sOQL = "SELECT $sClass WHERE id IN (".$sSelectedObj.")";
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL));
// Compute the distribution of the values for each field to determine which of the "scalar" fields are homogenous
$aList = MetaModel::ListAttributeDefs($sClass);
$aValues = array();
foreach($aList as $sAttCode => $oAttDef)
{
if ($oAttDef->IsScalar())
{
$aValues[$sAttCode] = array();
}
}
while($oObj = $oSet->Fetch())
{
foreach($aList as $sAttCode => $oAttDef)
{
if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
{
$currValue = $oObj->Get($sAttCode);
if ($oAttDef instanceof AttributeCaseLog)
{
$currValue = ' '; // Don't put an empty string, in case the field would be considered as mandatory...
}
if (is_object($currValue)) continue; // Skip non scalar values...
if(!array_key_exists($currValue, $aValues[$sAttCode]))
{
$aValues[$sAttCode][$currValue] = array('count' => 1, 'display' => $oObj->GetAsHTML($sAttCode));
}
else
{
$aValues[$sAttCode][$currValue]['count']++;
}
}
}
}
// Now create an object that has values for the homogenous values only
$oDummyObj = new $sClass(); // @@ What if the class is abstract ?
$aComments = array();
function MyComparison($a, $b) // Sort descending
{
if ($a['count'] == $b['count'])
{
return 0;
}
return ($a['count'] > $b['count']) ? -1 : 1;
}
$iFormId = cmdbAbstractObject::GetNextFormId(); // Identifier that prefixes all the form fields
$sReadyScript = '';
$aDependsOn = array();
$sFormPrefix = '2_';
foreach($aList as $sAttCode => $oAttDef)
{
$aPrerequisites = MetaModel::GetPrequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one
if (count($aPrerequisites) > 0)
{
// When 'enabling' a field, all its prerequisites must be enabled too
$sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aPrerequisites)."']";
$oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n");
}
$aDependents = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one
if (count($aDependents) > 0)
{
// When 'disabling' a field, all its dependent fields must be disabled too
$sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']";
$oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n");
}
if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
{
if ($oAttDef->GetEditClass() == 'One Way Password')
{
$sTip = "Unknown values";
$sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );";
$oDummyObj->Set($sAttCode, null);
$aComments[$sAttCode] = '';
$aComments[$sAttCode] .= '
?
';
$sReadyScript .= 'ToogleField(false, \''.$iFormId.'_'.$sAttCode.'\');'."\n";
}
else
{
$iCount = count($aValues[$sAttCode]);
if ($iCount == 1)
{
// Homogenous value
reset($aValues[$sAttCode]);
$aKeys = array_keys($aValues[$sAttCode]);
$currValue = $aKeys[0]; // The only value is the first key
//echo "
';
}
$sReadyScript .= 'ToogleField('.(($iCount == 1) ? 'true': 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n";
}
}
}
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
if (($sStateAttCode != '') && ($oDummyObj->GetState() == ''))
{
// Hmmm, it's not gonna work like this ! Set a default value for the "state"
// Maybe we should use the "state" that is the most common among the objects...
$aMultiValues = $aValues[$sStateAttCode];
uasort($aMultiValues, 'MyComparison');
foreach($aMultiValues as $sCurrValue => $aVal)
{
$oDummyObj->Set($sStateAttCode, $sCurrValue);
break;
}
//$oStateAtt = MetaModel::GetAttributeDef($sClass, $sStateAttCode);
//$oDummyObj->Set($sStateAttCode, $oStateAtt->GetDefaultValue());
}
$oP->add("
\n");
$oP->set_title(Dict::Format('UI:Modify_N_ObjectsOf_Class', count($aSelectedObj), $sClass));
if (!$bPreview)
{
// Not in preview mode, do the update for real
$sTransactionId = utils::ReadPostedParam('transaction_id', '');
if (!utils::IsTransactionValid($sTransactionId, false))
{
throw new Exception(Dict::S('UI:Error:ObjectAlreadyUpdated'));
}
utils::RemoveTransaction($sTransactionId);
}
foreach($aSelectedObj as $iId)
{
$oObj = MetaModel::GetObject($sClass, $iId);
$aErrors = $oObj->UpdateObjectFromPostedForm('');
$bResult = (count($aErrors) == 0);
if ($bResult)
{
list($bResult, $aErrors) = $oObj->CheckToWrite(true /* Enforce Read-only fields */);
}
if ($bPreview)
{
$sStatus = $bResult ? Dict::S('UI:BulkModifyStatusOk') : Dict::S('UI:BulkModifyStatusError');
}
else
{
$sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped');
}
$sCSSClass = $bResult ? HILIGHT_CLASS_NONE : HILIGHT_CLASS_CRITICAL;
$sChecked = $bResult ? 'checked' : '';
$sDisabled = $bResult ? '' : 'disabled';
$aRows[] = array(
'form::select' => "",
'object' => $oObj->GetHyperlink(),
'status' => $sStatus,
'errors' => '
'.($bResult ? '': implode('
', $aErrors)).'
',
'@class' => $sCSSClass,
);
if ($bResult && (!$bPreview))
{
$oObj->DBUpdate();
}
}
$oP->Table($aHeaders, $aRows);
if ($bPreview)
{
$sFormAction = $_SERVER['SCRIPT_NAME']; // No parameter in the URL, the only parameter will be the ones passed through the form
// Form to submit:
$oP->add("\n");
}
else
{
$oP->add("\n");
}
}
/**
* Perform all the needed checks to delete one (or more) objects
*/
public static function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bPreview, $sCustomOperation, $aContextData = array())
{
$oDeletionPlan = new DeletionPlan();
foreach($aObjects as $oObj)
{
if ($bPreview)
{
$oObj->CheckToDelete($oDeletionPlan);
}
else
{
$oObj->DBDeleteTracked(CMDBObject::GetCurrentChange(), null, $oDeletionPlan);
}
}
if ($bPreview)
{
if (count($aObjects) == 1)
{
$oObj = $aObjects[0];
$oP->add("