diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index 7f7ea9edc..ff0bc46c8 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -1881,7 +1881,7 @@ EOF
$sHTMLValue = UIExtKeyWidget::DisplayFromAttCode($oPage, $sAttCode, $sClass, $oAttDef->GetLabel(), $oAllowedValues, $value, $iId, $bMandatory, $sFieldName, $sFormPrefix, $aExtKeyParams);
$sHTMLValue .= "\n";
break;
-
+
case 'RedundancySetting':
$sHTMLValue = '
';
$sHTMLValue .= '';
@@ -1945,6 +1945,18 @@ EOF
$oPage->add_ready_script("$('#{$iId}').bind('validate', function(evt, sFormId) { return ValidateCustomFields('$iId', sFormId) } );"); // Custom validation function
break;
+ case 'ObjectAttcode':
+ $iFieldSize = $oAttDef->GetMaxSize();
+ if (is_array($sDisplayValue))
+ {
+ $sDisplayValue = implode(', ', $sDisplayValue);
+ }
+ $sHTMLValue = "{$sValidationSpan}{$sReloadSpan}";
+ $aEventsList[] ='validate';
+ $aEventsList[] ='keyup';
+ $aEventsList[] ='change';
+ break;
+
case 'String':
default:
$aEventsList[] ='validate';
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 251ed5d27..f454d542c 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -7639,6 +7639,222 @@ class AttributePropertySet extends AttributeTable
}
}
+class AttributeObjectAttCode extends AttributeDBFieldVoid
+{
+ static public function ListExpectedParams()
+ {
+ return array_merge(parent::ListExpectedParams(), array('is_null_allowed', 'class'));
+ }
+
+ public function GetDefaultValue(DBObject $oHostObject = null)
+ {
+ return null;
+ }
+
+ public function IsNullAllowed()
+ {
+ return $this->Get("is_null_allowed");
+ }
+
+ public function GetEditClass()
+ {
+ return "ObjectAttcode";
+ }
+
+ public function GetEditValue($value, $oHostObj = null)
+ {
+ if (is_string($value))
+ {
+ return $value;
+ }
+ if (is_array($value))
+ {
+ return implode(', ', $value);
+ }
+ return '';
+ }
+
+ protected function GetSQLCol($bFullSpec = false)
+ {
+ $iLen = $this->GetMaxSize();
+ return "VARCHAR($iLen)"
+ .CMDBSource::GetSqlStringColumnDefinition()
+ .($bFullSpec ? $this->GetSQLColSpec() : '');
+ }
+
+ public function GetMaxSize()
+ {
+ return 255;
+ }
+
+ /**
+ * @param array $aCols
+ * @param string $sPrefix
+ *
+ * @return mixed
+ * @throws \CoreException
+ * @throws \Exception
+ */
+ public function FromSQLToValue($aCols, $sPrefix = '')
+ {
+ $sValue = $aCols["$sPrefix"];
+
+ return $this->MakeRealValue($sValue, null);
+ }
+
+ /**
+ * @param $aCols
+ * @param string $sPrefix
+ *
+ * @return mixed
+ * @throws \Exception
+ */
+ public function FromImportToValue($aCols, $sPrefix = '')
+ {
+ $sValue = $aCols["$sPrefix"];
+
+ return $this->MakeRealValue($sValue, null);
+ }
+
+ /**
+ * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!
+ *
+ * @param $proposedValue
+ * @param \DBObject $oHostObj
+ *
+ * @return mixed
+ * @throws \Exception
+ */
+ public function MakeRealValue($proposedValue, $oHostObj)
+ {
+ $aAllowedAttributes = array();
+ $sClass = '';
+ if (!empty($oHostObj))
+ {
+ $sTargetClass = $this->Get('class');
+ $sClass = $oHostObj->Get($sTargetClass);
+ $aAllowedAttributes = MetaModel::GetAttributesList($sClass);
+ }
+ if (is_string($proposedValue) && !empty($proposedValue))
+ {
+ $proposedValue = trim("$proposedValue");
+ $proposedValue = explode(',', $proposedValue);
+ $aValues = array();
+ foreach($proposedValue as $sValue)
+ {
+ $sAttCode = trim($sValue);
+ if (empty($aAllowedAttributes) || in_array($sAttCode, $aAllowedAttributes))
+ {
+ $aValues[] = $sAttCode;
+ }
+ else
+ {
+ throw new CoreUnexpectedValue("The attribute {$sAttCode} does not exist in class {$sClass}");
+ }
+ }
+ return $aValues;
+ }
+
+ return $proposedValue;
+ }
+
+ /**
+ * Get the value from a given string (plain text, CSV import)
+ *
+ * @param string $sProposedValue
+ * @param bool $bLocalizedValue
+ * @param string $sSepItem
+ * @param string $sSepAttribute
+ * @param string $sSepValue
+ * @param string $sAttributeQualifier
+ *
+ * @return mixed null if no match could be found
+ * @throws \Exception
+ */
+ public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
+ {
+ return $this->MakeRealValue($sProposedValue, null);
+ }
+
+ public function GetNullValue()
+ {
+ return null;
+ }
+
+ public function IsNull($proposedValue)
+ {
+ return empty($proposedValue);
+ }
+
+ /**
+ * To be overloaded for localized enums
+ *
+ * @param $sValue
+ *
+ * @return string label corresponding to the given value (in plain text)
+ * @throws \CoreWarning
+ * @throws \Exception
+ */
+ public function GetValueLabel($sValue)
+ {
+ if (is_array($sValue))
+ {
+ return implode(', ', $sValue);
+ }
+ return $sValue;
+ }
+
+ /**
+ * @param string $sValue
+ * @param null $oHostObj
+ *
+ * @return string
+ * @throws \CoreWarning
+ */
+ public function GetAsPlainText($sValue, $oHostObj = null)
+ {
+ return $this->GetValueLabel($sValue);
+ }
+
+ /**
+ * @param $value
+ *
+ * @return string
+ * @throws \CoreWarning
+ */
+ public function ScalarToSQL($value)
+ {
+ if (empty($value))
+ {
+ return '';
+ }
+ if (is_array($value))
+ {
+ return implode(', ', $value);
+ }
+ return $value;
+ }
+
+ /**
+ * @param $value
+ * @param \DBObject $oHostObject
+ * @param bool $bLocalize
+ *
+ * @return string|null
+ *
+ * @throws \CoreException
+ * @throws \Exception
+ */
+ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
+ {
+ if (is_array($value))
+ {
+ return implode(', ', $value);
+ }
+ return $value;
+ }
+}
+
/**
* The attribute dedicated to the friendly name automatic attribute (not written)
*
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 1bc5f5e3a..f5d98b553 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -1998,6 +1998,16 @@ abstract class DBObject implements iDisplay
// Save the original values (will be reset to the new values when the object get written to the DB)
$aOriginalValues = $this->m_aOrigValues;
+ // Activate any existing trigger
+ $sClass = get_class($this);
+ $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
+ $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectUpdate AS t WHERE t.target_class IN ('$sClassList')"));
+ while ($oTrigger = $oSet->Fetch())
+ {
+ /** @var \Trigger $oTrigger */
+ $oTrigger->DoActivate($this->ToArgs('this'));
+ }
+
$bHasANewExternalKeyValue = false;
$aHierarchicalKeys = array();
$aDBChanges = array();
diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php
index b792660e8..92e937210 100644
--- a/core/dict.class.inc.php
+++ b/core/dict.class.inc.php
@@ -197,7 +197,7 @@ class Dict
/**
* Initialize a the entries for a given language (replaces the former Add() method)
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
- * @param hash $aEntries Hash array of dictionnary entries
+ * @param array $aEntries Hash array of dictionnary entries
*/
public static function SetEntries($sLanguageCode, $aEntries)
{
diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php
index 0b824a661..778813415 100644
--- a/core/trigger.class.inc.php
+++ b/core/trigger.class.inc.php
@@ -194,7 +194,7 @@ abstract class TriggerOnObject extends Trigger
{
/** @var \DBObject $oObject */
$oObject = $aContextArgs['this->object()'];
- $bGo = $this->IsTargetObject($oObject->GetKey());
+ $bGo = $this->IsTargetObject($oObject->GetKey(), $oObject->ListChanges());
}
if ($bGo)
{
@@ -204,6 +204,7 @@ abstract class TriggerOnObject extends Trigger
/**
* @param $iObjectId
+ * @param array $aChanges
*
* @return bool
* @throws \CoreException
@@ -212,7 +213,7 @@ abstract class TriggerOnObject extends Trigger
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
- public function IsTargetObject($iObjectId)
+ public function IsTargetObject($iObjectId, $aChanges = array())
{
$sFilter = trim($this->Get('filter'));
if (strlen($sFilter) > 0)
@@ -401,6 +402,64 @@ class TriggerOnObjectCreate extends TriggerOnObject
}
}
+/**
+ * Class TriggerOnObjectCreate
+ */
+class TriggerOnObjectUpdate extends TriggerOnObject
+{
+ /**
+ * @throws \CoreException
+ * @throws \Exception
+ */
+ public static function Init()
+ {
+ $aParams = array
+ (
+ "category" => "grant_by_profile,core/cmdb,application",
+ "key_type" => "autoincrement",
+ "name_attcode" => "description",
+ "state_attcode" => "",
+ "reconc_keys" => array('description'),
+ "db_table" => "priv_trigger_onobjupdate",
+ "db_key_field" => "id",
+ "db_finalclass_field" => "",
+ "display_template" => "",
+ );
+ MetaModel::Init_Params($aParams);
+ MetaModel::Init_InheritAttributes();
+ MetaModel::Init_AddAttribute(new AttributeObjectAttCode('target_attcodes', array("allowed_values" => null, "class" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "depends_on" => array('target_class'))));
+
+ // Display lists
+ MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'target_attcodes', 'action_list')); // Attributes to be displayed for the complete details
+ MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
+ // Search criteria
+ MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
+ }
+
+ public function IsTargetObject($iObjectId, $aChanges = array())
+ {
+ if (!parent::IsTargetObject($iObjectId, $aChanges))
+ {
+ return false;
+ }
+
+ // Check the attribute
+ $aAttCodes = $this->Get('target_attcodes');
+ if (empty($aAttCodes))
+ {
+ return true;
+ }
+ foreach($aAttCodes as $sAttCode)
+ {
+ if (array_key_exists($sAttCode, $aChanges))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
/**
* Class lnkTriggerAction
*/
diff --git a/dictionaries/en.dictionary.itop.core.php b/dictionaries/en.dictionary.itop.core.php
index 39ffe0999..e67683343 100644
--- a/dictionaries/en.dictionary.itop.core.php
+++ b/dictionaries/en.dictionary.itop.core.php
@@ -578,6 +578,17 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:TriggerOnObjectCreate+' => 'Trigger on object creation of [a child class of] the given class',
));
+//
+// Class: TriggerOnObjectUpdate
+//
+
+Dict::Add('EN US', 'English', 'English', array(
+ 'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)',
+ 'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class',
+ 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target attributes',
+ 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
+));
+
//
// Class: TriggerOnThresholdReached
//
diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php
index e83db72d7..2f8b77ffc 100644
--- a/dictionaries/fr.dictionary.itop.core.php
+++ b/dictionaries/fr.dictionary.itop.core.php
@@ -61,6 +61,10 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:TriggerOnStateLeave+' => '',
'Class:TriggerOnObjectCreate' => 'Déclencheur sur la création d\'un objet',
'Class:TriggerOnObjectCreate+' => '',
+ 'Class:TriggerOnObjectUpdate' => 'Déclencheur sur la modification d\'un objet',
+ 'Class:TriggerOnObjectUpdate+' => '',
+ 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Attributs cible',
+ 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
'Class:lnkTriggerAction' => 'Actions-Déclencheur',
'Class:lnkTriggerAction+' => '',
'Class:lnkTriggerAction/Attribute:action_id' => 'Action',