diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index 115aa6d50..66f1e85c2 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -1875,6 +1875,10 @@ EOF
$sHTMLValue .= " {$sValidationField}\n";
break;
+ case 'StopWatch':
+ $sHTMLValue = "The edition of a stopwatch is not allowed!!!";
+ break;
+
case 'List':
// Not editable for now...
$sHTMLValue = '';
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index b71e7cbda..50c7f9860 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -26,6 +26,7 @@
require_once('MyHelpers.class.inc.php');
require_once('ormdocument.class.inc.php');
+require_once('ormstopwatch.class.inc.php');
require_once('ormpassword.class.inc.php');
require_once('ormcaselog.class.inc.php');
@@ -95,7 +96,7 @@ abstract class AttributeDefinition
protected $m_sCode;
private $m_aParams = array();
protected $m_sHostClass = '!undefined!';
- protected function Get($sParamName) {return $this->m_aParams[$sParamName];}
+ public function Get($sParamName) {return $this->m_aParams[$sParamName];}
protected function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));}
protected function GetOptional($sParamName, $default)
@@ -3199,6 +3200,243 @@ class AttributeBlob extends AttributeDefinition
return ''; // Not exportable in XML, or as CDATA + some subtags ??
}
}
+
+/**
+ * A stop watch is an ormStopWatch object, it is stored as several columns in the database
+ *
+ * @package iTopORM
+ */
+class AttributeStopWatch extends AttributeDefinition
+{
+ static public function ListExpectedParams()
+ {
+ // The list of thresholds must be an array of iPercent => array of 'option' => value
+ return array_merge(parent::ListExpectedParams(), array("states", "goal_computing", "working_time_computing", "thresholds"));
+ }
+
+ public function GetEditClass() {return "StopWatch";}
+
+ public function IsDirectField() {return true;}
+ public function IsScalar() {return true;}
+ public function IsWritable() {return false;}
+ public function GetDefaultValue() {return $this->NewStopWatch();}
+ //public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);}
+
+ public function GetStates()
+ {
+ return $this->Get('states');
+ }
+
+ /**
+ * Construct a brand new (but configured) stop watch
+ */
+ public function NewStopWatch()
+ {
+ $oSW = new ormStopWatch();
+ foreach ($this->ListThresholds() as $iThreshold => $aFoo)
+ {
+ $oSW->DefineThreshold($iThreshold);
+ }
+ return $oSW;
+ }
+
+ // Facilitate things: allow the user to Set the value from a string
+ public function MakeRealValue($proposedValue, $oHostObj)
+ {
+ if (!$proposedValue instanceof ormStopWatch)
+ {
+ return $this->NewStopWatch();
+ }
+ return $proposedValue;
+ }
+
+ public function GetSQLExpressions($sPrefix = '')
+ {
+ if ($sPrefix == '')
+ {
+ $sPrefix = $this->GetCode();
+ }
+ $aColumns = array();
+ // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
+ $aColumns[''] = $sPrefix.'_timespent';
+ $aColumns['_started'] = $sPrefix.'_started';
+ $aColumns['_laststart'] = $sPrefix.'_laststart';
+ $aColumns['_stopped'] = $sPrefix.'_stopped';
+ foreach ($this->ListThresholds() as $iThreshold => $aFoo)
+ {
+ $sThPrefix = '_'.$iThreshold;
+ $aColumns[$sThPrefix.'_deadline'] = $sPrefix.$sThPrefix.'_deadline';
+ $aColumns[$sThPrefix.'_passed'] = $sPrefix.$sThPrefix.'_passed';
+ $aColumns[$sThPrefix.'_overrun'] = $sPrefix.$sThPrefix.'_overrun';
+ }
+ return $aColumns;
+ }
+
+ public static function DateToSeconds($sDate)
+ {
+ if (is_null($sDate))
+ {
+ return null;
+ }
+ $oDateTime = new DateTime($sDate);
+ $iSeconds = $oDateTime->format('U');
+ return $iSeconds;
+ }
+
+ public static function SecondsToDate($iSeconds)
+ {
+ if (is_null($iSeconds))
+ {
+ return null;
+ }
+ return date("Y-m-d H:i:s", $iSeconds);
+ }
+
+ public function FromSQLToValue($aCols, $sPrefix = '')
+ {
+ $aExpectedCols = array($sPrefix, $sPrefix.'_started', $sPrefix.'_laststart', $sPrefix.'_stopped');
+ foreach ($this->ListThresholds() as $iThreshold => $aFoo)
+ {
+ $sThPrefix = '_'.$iThreshold;
+ $aExpectedCols[] = $sPrefix.$sThPrefix.'_deadline';
+ $aExpectedCols[] = $sPrefix.$sThPrefix.'_passed';
+ $aExpectedCols[] = $sPrefix.$sThPrefix.'_overrun';
+ }
+ foreach ($aExpectedCols as $sExpectedCol)
+ {
+ if (!array_key_exists($sExpectedCol, $aCols))
+ {
+ $sAvailable = implode(', ', array_keys($aCols));
+ throw new MissingColumnException("Missing column '$sExpectedCol' from {$sAvailable}");
+ }
+ }
+
+ $value = new ormStopWatch(
+ $aCols[$sPrefix],
+ self::DateToSeconds($aCols[$sPrefix.'_started']),
+ self::DateToSeconds($aCols[$sPrefix.'_laststart']),
+ self::DateToSeconds($aCols[$sPrefix.'_stopped'])
+ );
+
+ $aThresholds = array();
+ foreach ($this->ListThresholds() as $iThreshold => $aFoo)
+ {
+ $sThPrefix = '_'.$iThreshold;
+ $value->DefineThreshold(
+ $iThreshold,
+ self::DateToSeconds($aCols[$sPrefix.$sThPrefix.'_deadline']),
+ (bool)($aCols[$sPrefix.$sThPrefix.'_passed'] == 1),
+ $aCols[$sPrefix.$sThPrefix.'_overrun']
+ );
+ }
+
+ return $value;
+ }
+
+ public function GetSQLValues($value)
+ {
+ if ($value instanceOf ormStopWatch)
+ {
+ $aValues = array();
+ $aValues[$this->GetCode().'_timespent'] = $value->GetTimeSpent();
+ $aValues[$this->GetCode().'_started'] = self::SecondsToDate($value->GetStartDate());
+ $aValues[$this->GetCode().'_laststart'] = self::SecondsToDate($value->GetLastStartDate());
+ $aValues[$this->GetCode().'_stopped'] = self::SecondsToDate($value->GetStopDate());
+
+ foreach ($this->ListThresholds() as $iThreshold => $aFoo)
+ {
+ $sPrefix = $this->GetCode().'_'.$iThreshold;
+ $aValues[$sPrefix.'_deadline'] = self::SecondsToDate($value->GetThresholdDate($iThreshold));
+ $aValues[$sPrefix.'_passed'] = $value->IsThresholdPassed($iThreshold) ? '1' : '0';
+ $aValues[$sPrefix.'_overrun'] = $value->GetOverrun($iThreshold);
+ }
+ }
+ else
+ {
+ $aValues = array();
+ $aValues[$this->GetCode().'_timespent'] = '';
+ $aValues[$this->GetCode().'_started'] = '';
+ $aValues[$this->GetCode().'_laststart'] = '';
+ $aValues[$this->GetCode().'_stopped'] = '';
+ }
+ return $aValues;
+ }
+
+ public function GetSQLColumns()
+ {
+ $aColumns = array();
+ $aColumns[$this->GetCode().'_timespent'] = 'INT(11) UNSIGNED DEFAULT 0';
+ $aColumns[$this->GetCode().'_started'] = 'DATETIME NULL';
+ $aColumns[$this->GetCode().'_laststart'] = 'DATETIME NULL';
+ $aColumns[$this->GetCode().'_stopped'] = 'DATETIME NULL';
+ foreach ($this->ListThresholds() as $iThreshold => $aFoo)
+ {
+ $sPrefix = $this->GetCode().'_'.$iThreshold;
+ $aColumns[$sPrefix.'_deadline'] = 'DATETIME NULL';
+ $aColumns[$sPrefix.'_passed'] = 'TINYINT(1) NULL';
+ $aColumns[$sPrefix.'_overrun'] = 'INT(11) UNSIGNED NULL';
+ }
+ return $aColumns;
+ }
+
+ public function GetFilterDefinitions()
+ {
+ //return array();
+ // still not working... see later...
+ $aRes = array(
+ $this->GetCode() => new FilterFromAttribute($this),
+ $this->GetCode().'_started' => new FilterFromAttribute($this, '_started'),
+ $this->GetCode().'_laststart' => new FilterFromAttribute($this, '_laststart'),
+ $this->GetCode().'_stopped' => new FilterFromAttribute($this, '_stopped')
+ );
+ foreach ($this->ListThresholds() as $iThreshold => $aFoo)
+ {
+ $sPrefix = $this->GetCode().'_'.$iThreshold;
+ $aRes[$sPrefix.'_deadline'] = new FilterFromAttribute($this, '_deadline');
+ $aRes[$sPrefix.'_passed'] = new FilterFromAttribute($this, '_passed');
+ $aRes[$sPrefix.'_overrun'] = new FilterFromAttribute($this, '_overrun');
+ }
+ return $aRes;
+ }
+
+ public function GetBasicFilterOperators()
+ {
+ return array();
+ }
+ public function GetBasicFilterLooseOperator()
+ {
+ return '=';
+ }
+
+ public function GetBasicFilterSQLExpr($sOpCode, $value)
+ {
+ return 'true';
+ }
+
+ public function GetAsHTML($value, $oHostObject = null)
+ {
+ if (is_object($value))
+ {
+ return $value->GetAsHTML($this, $oHostObject);
+ }
+ }
+
+ public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null)
+ {
+ return $value->GetTimeSpent();
+ }
+
+ public function GetAsXML($value, $oHostObject = null)
+ {
+ return $value->GetTimeSpent();
+ }
+
+ public function ListThresholds()
+ {
+ return $this->Get('thresholds');
+ }
+}
+
/**
* One way encrypted (hashed) password
*/
diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php
index 5c0aa9051..9a36938d1 100644
--- a/core/cmdbobject.class.inc.php
+++ b/core/cmdbobject.class.inc.php
@@ -195,6 +195,29 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
+ elseif ($oAttDef instanceOf AttributeStopWatch)
+ {
+ // Stop watches
+ // TEMPORARY IMPLEMENTATION
+ $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
+ $oMyChangeOp->Set("change", $oChange->GetKey());
+ $oMyChangeOp->Set("objclass", get_class($this));
+ $oMyChangeOp->Set("objkey", $this->GetKey());
+ $oMyChangeOp->Set("attcode", $sAttCode);
+
+ // Temporary - working thanks to ormStopWatch::__toString()
+ if (array_key_exists($sAttCode, $aOrigValues))
+ {
+ $sOriginalValue = $aOrigValues[$sAttCode];
+ }
+ else
+ {
+ $sOriginalValue = 'undefined';
+ }
+ $oMyChangeOp->Set("oldvalue", $sOriginalValue);
+ $oMyChangeOp->Set("newvalue", $value);
+ $iId = $oMyChangeOp->DBInsertNoReload();
+ }
elseif ($oAttDef instanceOf AttributeCaseLog)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
diff --git a/core/computing.inc.php b/core/computing.inc.php
new file mode 100644
index 000000000..fc04f1758
--- /dev/null
+++ b/core/computing.inc.php
@@ -0,0 +1,121 @@
+
+ * @author Romain Quetiez
+ * @author Denis Flaven
+ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
+ */
+
+/**
+ * Metric computing for stop watches
+ */
+interface iMetricComputer
+{
+ public static function GetDescription();
+ public function ComputeMetric($oObject);
+}
+
+/**
+ * Working time computing for stop watches
+ */
+interface iWorkingTimeComputer
+{
+ public static function GetDescription();
+
+ /**
+ * Get the date/time corresponding to a given delay in the future from the present
+ * considering only the valid (open) hours for a specified object
+ * @param $oObject DBObject The object for which to compute the deadline
+ * @param $iDuration integer The duration (in seconds) in the future
+ * @param $oStartDate DateTime The starting point for the computation
+ * @return DateTime The date/time for the deadline
+ */
+ public function GetDeadline($oObject, $iDuration, DateTime $oStartDate);
+
+ /**
+ * Get duration (considering only open hours) elapsed bewteen two given DateTimes
+ * @param $oObject DBObject The object for which to compute the duration
+ * @param $oStartDate DateTime The starting point for the computation (default = now)
+ * @param $oEndDate DateTime The ending point for the computation (default = now)
+ * @return integer The duration (number of seconds) of open hours elapsed between the two dates
+ */
+ public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate);
+}
+
+/**
+ * Default implementation oof deadline computing: NO deadline
+ */
+class DefaultMetricComputer implements iMetricComputer
+{
+ public static function GetDescription()
+ {
+ return "Null";
+ }
+
+ public function ComputeMetric($oObject)
+ {
+ return null;
+ }
+}
+
+/**
+ * Default implementation of working time computing
+ */
+class DefaultWorkingTimeComputer implements iWorkingTimeComputer
+{
+ public static function GetDescription()
+ {
+ return "24x7, no holidays";
+ }
+
+ /**
+ * Get the date/time corresponding to a given delay in the future from the present
+ * considering only the valid (open) hours for a specified object
+ * @param $oObject DBObject The object for which to compute the deadline
+ * @param $iDuration integer The duration (in seconds) in the future
+ * @param $oStartDate DateTime The starting point for the computation
+ * @return DateTime The date/time for the deadline
+ */
+ public function GetDeadline($oObject, $iDuration, DateTime $oStartDate)
+ {
+ //echo "GetDeadline - default: ".$oStartDate->format('Y-m-d H:i:s')." + $iDuration
\n";
+ // Default implementation: 24x7, no holidays: to compute the deadline, just add
+ // the specified duration to the given date/time
+ $oResult = clone $oStartDate;
+ $oResult->modify('+'.$iDuration.' seconds');
+ return $oResult;
+ }
+
+ /**
+ * Get duration (considering only open hours) elapsed bewteen two given DateTimes
+ * @param $oObject DBObject The object for which to compute the duration
+ * @param $oStartDate DateTime The starting point for the computation (default = now)
+ * @param $oEndDate DateTime The ending point for the computation (default = now)
+ * @return integer The duration (number of seconds) of open hours elapsed between the two dates
+ */
+ public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate)
+ {
+ //echo "GetOpenDuration - default: ".$oStartDate->format('Y-m-d H:i:s')." to ".$oEndDate->format('Y-m-d H:i:s')."
\n";
+ return abs($oEndDate->format('U') - $oStartDate->format('U'));
+ }
+}
+
+
+?>
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 6f404009a..787b3eccf 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -259,7 +259,14 @@ abstract class DBObject
$value = $oAttDef->FromSQLToValue($aRow, $sAttRef);
$this->m_aCurrValues[$sAttCode] = $value;
- $this->m_aOrigValues[$sAttCode] = $value;
+ if (is_object($value))
+ {
+ $this->m_aOrigValues[$sAttCode] = clone $value;
+ }
+ else
+ {
+ $this->m_aOrigValues[$sAttCode] = $value;
+ }
$this->m_aLoadedAtt[$sAttCode] = true;
}
else
@@ -357,6 +364,7 @@ abstract class DBObject
}
$realvalue = $oAttDef->MakeRealValue($value, $this);
+
$this->m_aCurrValues[$sAttCode] = $realvalue;
// The object has changed, reset caches
@@ -1313,6 +1321,26 @@ abstract class DBObject
throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
}
+ // Stop watches
+ $sState = $this->GetState();
+ if ($sState != '')
+ {
+ foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
+ {
+ if ($oAttDef instanceof AttributeStopWatch)
+ {
+ if (in_array($sState, $oAttDef->GetStates()))
+ {
+ // Start the stop watch and compute the deadlines
+ $oSW = $this->Get($sAttCode);
+ $oSW->Start($this, $oAttDef);
+ $oSW->ComputeDeadlines($this, $oAttDef);
+ $this->Set($sAttCode, $oSW);
+ }
+ }
+ }
+ }
+
// First query built upon on the root class, because the ID must be created first
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
@@ -1396,6 +1424,25 @@ abstract class DBObject
throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead");
}
+ // Stop watches
+ $sState = $this->GetState();
+ if ($sState != '')
+ {
+ foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
+ {
+ if ($oAttDef instanceof AttributeStopWatch)
+ {
+ if (in_array($sState, $oAttDef->GetStates()))
+ {
+ // Compute or recompute the deadlines
+ $oSW = $this->Get($sAttCode);
+ $oSW->ComputeDeadlines($this, $oAttDef);
+ $this->Set($sAttCode, $oSW);
+ }
+ }
+ }
+ }
+
$this->DoComputeValues();
$this->OnUpdate();
@@ -1637,6 +1684,10 @@ abstract class DBObject
return MetaModel::EnumTransitions(get_class($this), $sState);
}
+ /**
+ * Designed as an action to be called when a stop watch threshold times out
+ * or from within the framework
+ */
public function ApplyStimulus($sStimulusCode)
{
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
@@ -1687,11 +1738,44 @@ abstract class DBObject
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
+
+ // Stop watches
+ foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
+ {
+ if ($oAttDef instanceof AttributeStopWatch)
+ {
+ $oSW = $this->Get($sAttCode);
+ if (in_array($sNewState, $oAttDef->GetStates()))
+ {
+ $oSW->Start($this, $oAttDef);
+ }
+ else
+ {
+ $oSW->Stop($this, $oAttDef);
+ }
+ $this->Set($sAttCode, $oSW);
+ }
+ }
}
return $bSuccess;
}
+ /**
+ * Designed as an action to be called when a stop watch threshold times out
+ */
+ public function ResetStopWatch($sAttCode)
+ {
+ $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+ if (!$oAttDef instanceof AttributeStopWatch)
+ {
+ throw new CoreException("Invalid stop watch id: '$sAttCode'");
+ }
+ $oSW = $this->Get($sAttCode);
+ $oSW->Reset($this, $oAttDef);
+ $this->Set($sAttCode, $oSW);
+ }
+
// Make standard context arguments
// Note: Needs to be reviewed because it is currently called once per attribute when an object is written (CheckToWrite / CheckValue)
// Several options here:
diff --git a/core/filterdef.class.inc.php b/core/filterdef.class.inc.php
index 09c0c488a..06dfe64cf 100644
--- a/core/filterdef.class.inc.php
+++ b/core/filterdef.class.inc.php
@@ -155,12 +155,13 @@ class FilterFromAttribute extends FilterDefinition
return array_merge(parent::ListExpectedParams(), array("refattribute"));
}
- public function __construct($oRefAttribute, $aParam = array())
+ public function __construct($oRefAttribute, $sSuffix = '')
{
// In this very specific case, the code is the one of the attribute
// (this to get a very very simple syntax upon declaration)
+ $aParam = array();
$aParam["refattribute"] = $oRefAttribute;
- parent::__construct($oRefAttribute->GetCode(), $aParam);
+ parent::__construct($oRefAttribute->GetCode().$sSuffix, $aParam);
}
public function GetType() {return "Basic";}
diff --git a/core/metamodel.class.php b/core/metamodel.class.php
index 79456ca0b..d2bb2c214 100644
--- a/core/metamodel.class.php
+++ b/core/metamodel.class.php
@@ -18,6 +18,7 @@ require_once(APPROOT.'core/modulehandler.class.inc.php');
require_once(APPROOT.'core/querybuildercontext.class.inc.php');
require_once(APPROOT.'core/querymodifier.class.inc.php');
require_once(APPROOT.'core/metamodelmodifier.inc.php');
+require_once(APPROOT.'core/computing.inc.php');
/**
* Metamodel
@@ -2591,7 +2592,7 @@ abstract class MetaModel
// add it to the output
foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr)
{
- if (array_key_exists($sAttCode, $aExpectedAtts))
+ if (array_key_exists($sAttCode.$sColId, $aExpectedAtts))
{
$oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sTableAlias);
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
diff --git a/core/ormstopwatch.class.inc.php b/core/ormstopwatch.class.inc.php
new file mode 100644
index 000000000..f39c45c7f
--- /dev/null
+++ b/core/ormstopwatch.class.inc.php
@@ -0,0 +1,440 @@
+
+ * @author Romain Quetiez
+ * @author Denis Flaven
+ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
+ */
+
+
+/**
+ * ormStopWatch
+ * encapsulate the behavior of a stop watch that will be stored as an attribute of class AttributeStopWatch
+ *
+ * @package itopORM
+ */
+class ormStopWatch
+{
+ protected $iTimeSpent; // seconds
+ protected $iStarted; // unix time (seconds)
+ protected $iLastStart; // unix time (seconds)
+ protected $iStopped; // unix time (seconds)
+ protected $aThresholds;
+
+ /**
+ * Constructor
+ */
+ public function __construct($iTimeSpent = 0, $iStarted = null, $iLastStart = null, $iStopped = null)
+ {
+ $this->iTimeSpent = (int) $iTimeSpent;
+ $this->iStarted = $iStarted;
+ $this->iLastStart = $iLastStart;
+ $this->iStopped = $iStopped;
+
+ $this->aThresholds = array();
+ }
+
+ public function DefineThreshold($iPercent, $tDeadline = null, $bPassed = false, $iOverrun = 0)
+ {
+ $this->aThresholds[$iPercent] = array(
+ 'deadline' => $tDeadline, // unix time (seconds)
+ 'passed' => $bPassed,
+ 'overrun' => $iOverrun
+ );
+ }
+
+ public function MarkThresholdAsPassed($iPercent)
+ {
+ $this->aThresholds[$iPercent]['passed'] = true;
+ }
+
+ public function __toString()
+ {
+ return (string)$this->iTimeSpent;
+ }
+
+ public function GetTimeSpent()
+ {
+ return $this->iTimeSpent;
+ }
+
+ public function GetStartDate()
+ {
+ return $this->iStarted;
+ }
+
+ public function GetLastStartDate()
+ {
+ return $this->iLastStart;
+ }
+
+ public function GetStopDate()
+ {
+ return $this->iStopped;
+ }
+
+ public function GetThresholdDate($iPercent)
+ {
+ if (array_key_exists($iPercent, $this->aThresholds))
+ {
+ return $this->aThresholds[$iPercent]['deadline'];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public function GetOverrun($iPercent)
+ {
+ if (array_key_exists($iPercent, $this->aThresholds))
+ {
+ return $this->aThresholds[$iPercent]['overrun'];
+ }
+ else
+ {
+ return null;
+ }
+ }
+ public function IsThresholdPassed($iPercent)
+ {
+ if (array_key_exists($iPercent, $this->aThresholds))
+ {
+ return $this->aThresholds[$iPercent]['passed'];
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public function GetAsHTML($oAttDef, $oHostObject = null)
+ {
+ $aProperties = array();
+
+ $aProperties['States'] = implode(', ', $oAttDef->GetStates());
+
+ if (is_null($this->iLastStart))
+ {
+ if (is_null($this->iStarted))
+ {
+ $aProperties['Elapsed'] = 'never started';
+ }
+ else
+ {
+ $aProperties['Elapsed'] = $this->iTimeSpent.' s';
+ }
+ }
+ else
+ {
+ $iElapsedTemp = $this->ComputeDuration($oHostObject, $oAttDef, $this->iLastStart, time());
+ $aProperties['Elapsed'] = $this->iTimeSpent.' + '.$iElapsedTemp.' s +
';
+ }
+
+ $aProperties['Started'] = $oAttDef->SecondsToDate($this->iStarted);
+ $aProperties['LastStart'] = $oAttDef->SecondsToDate($this->iLastStart);
+ $aProperties['Stopped'] = $oAttDef->SecondsToDate($this->iStopped);
+
+ foreach ($this->aThresholds as $iPercent => $aThresholdData)
+ {
+ if ($aThresholdData['passed'])
+ {
+ if ($aThresholdData['overrun'])
+ {
+ $aProperties[$iPercent.'%'] = $oAttDef->SecondsToDate($aThresholdData['deadline'])." PASSED by ".$aThresholdData['overrun']." seconds";
+ }
+ else
+ {
+ $aProperties[$iPercent.'%'] = $oAttDef->SecondsToDate($aThresholdData['deadline'])." PASSED";
+ }
+ }
+ else
+ {
+ $aProperties[$iPercent.'%'] = $oAttDef->SecondsToDate($aThresholdData['deadline']);
+ }
+ }
+ $sRes = "";
+ $sRes .= "";
+ foreach ($aProperties as $sProperty => $sValue)
+ {
+ $sRes .= "";
+ $sCell = str_replace("\n", "
\n", $sValue);
+ $sRes .= "$sProperty | $sCell | ";
+ $sRes .= "
";
+ }
+ $sRes .= "";
+ $sRes .= "
";
+ return $sRes;
+ }
+
+ protected function ComputeGoal($oObject, $oAttDef)
+ {
+ $sMetricComputer = $oAttDef->Get('goal_computing');
+ $oComputer = new $sMetricComputer();
+ $aCallSpec = array($oComputer, 'ComputeMetric');
+ if (!is_callable($aCallSpec))
+ {
+ throw new CoreException("Unknown class/verb '$sMetricComputer/ComputeMetric'");
+ }
+ $iRet = call_user_func($aCallSpec, $oObject);
+ return $iRet;
+ }
+
+ protected function ComputeDeadline($oObject, $oAttDef, $iStartTime, $iDurationSec)
+ {
+ $sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
+ $aCallSpec = array($sWorkingTimeComputer, '__construct');
+ if (!is_callable($aCallSpec))
+ {
+ //throw new CoreException("Pas de constructeur pour $sWorkingTimeComputer!");
+ }
+ $oComputer = new $sWorkingTimeComputer();
+ $aCallSpec = array($oComputer, 'GetDeadline');
+ if (!is_callable($aCallSpec))
+ {
+ throw new CoreException("Unknown class/verb '$sWorkingTimeComputer/GetDeadline'");
+ }
+ // GetDeadline($oObject, $iDuration, DateTime $oStartDate)
+ $oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2
+ $oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate);
+ $iRet = $oDeadline->format('U');
+ return $iRet;
+ }
+
+ protected function ComputeDuration($oObject, $oAttDef, $iStartTime, $iEndTime)
+ {
+ $sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
+ $oComputer = new $sWorkingTimeComputer();
+ $aCallSpec = array($oComputer, 'GetOpenDuration');
+ if (!is_callable($aCallSpec))
+ {
+ throw new CoreException("Unknown class/verb '$sWorkingTimeComputer/GetOpenDuration'");
+ }
+ // GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate)
+ $oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2
+ $oEndDate = new DateTime('@'.$iEndTime);
+ $iRet = call_user_func($aCallSpec, $oObject, $oStartDate, $oEndDate);
+ return $iRet;
+ }
+
+ public function Reset($oObject, $oAttDef)
+ {
+ $this->iTimeSpent = 0;
+ $this->iStarted = null;
+ $this->iLastStart = null;
+ $this->iStopped = null;
+
+ foreach ($this->aThresholds as $iPercent => &$aThresholdData)
+ {
+ $aThresholdData['passed'] = false;
+ $aThresholdData['deadline'] = null;
+ $aThresholdData['overrun'] = null;
+ }
+ }
+
+ /**
+ * Start or continue
+ * It is the responsibility of the caller to compute the deadlines
+ * (to avoid computing twice for the same result)
+ */
+ public function Start($oObject, $oAttDef)
+ {
+ if (!is_null($this->iLastStart))
+ {
+ // Already started
+ return false;
+ }
+
+ if (is_null($this->iStarted))
+ {
+ $this->iStarted = time();
+ }
+ $this->iLastStart = time();
+ $this->iStopped = null;
+
+ return true;
+ }
+
+ /**
+ * Compute or recompute the goal and threshold deadlines
+ */
+ public function ComputeDeadlines($oObject, $oAttDef)
+ {
+ if (is_null($this->iLastStart))
+ {
+ // Currently stopped - do nothing
+ return false;
+ }
+
+ $iDurationGoal = $this->ComputeGoal($oObject, $oAttDef);
+ foreach ($this->aThresholds as $iPercent => &$aThresholdData)
+ {
+ if (!$aThresholdData['passed'])
+ {
+ if (is_null($iDurationGoal))
+ {
+ // No limit: leave null thresholds
+ $aThresholdData['deadline'] = null;
+ }
+ else
+ {
+ $iThresholdDuration = round($iPercent * $iDurationGoal / 100);
+ $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
+ // OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iStarted, $iThresholdDuration);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Stop counting if not already done
+ */
+ public function Stop($oObject, $oAttDef)
+ {
+ if (is_null($this->iLastStart))
+ {
+ // Already stopped
+ return false;
+ }
+
+ $iElapsed = $this->ComputeDuration($oObject, $oAttDef, $this->iLastStart, time());
+ $this->iTimeSpent = $this->iTimeSpent + $iElapsed;
+
+ foreach ($this->aThresholds as $iPercent => &$aThresholdData)
+ {
+ if ($aThresholdData['passed'])
+ {
+ if (is_null($aThresholdData['overrun']))
+ {
+ // First stop after the deadline is passed
+ $iOverrun = $this->ComputeDuration($oObject, $oAttDef, $aThresholdData['deadline'], time());
+ $aThresholdData['overrun'] = $iOverrun;
+ }
+ else
+ {
+ // Accumulate from last start
+ $aThresholdData['overrun'] += $iElapsed;
+ }
+ }
+ $aThresholdData['deadline'] = null;
+ }
+
+ $this->iLastStart = null;
+ $this->iStopped = time();
+
+ return true;
+ }
+}
+
+/**
+ * CheckStopWatchThresholds
+ * Implements the automatic actions
+ *
+ * @package itopORM
+ */
+class CheckStopWatchThresholds implements iBackgroundProcess
+{
+ public function GetPeriodicity()
+ {
+ return 10; // seconds
+ }
+
+ public function Process($iTimeLimit)
+ {
+ foreach (MetaModel::GetClasses() as $sClass)
+ {
+ foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
+ {
+ if ($oAttDef instanceof AttributeStopWatch)
+ {
+ foreach ($oAttDef->ListThresholds() as $iThreshold => $aThresholdData)
+ {
+ $iPercent = $aThresholdData['percent']; // could be different than the index !
+
+ $sExpression = "SELECT $sClass WHERE {$sAttCode}_{$iThreshold}_passed = 0 AND {$sAttCode}_{$iThreshold}_deadline < NOW()";
+ echo $sExpression."
\n";
+ $oFilter = DBObjectSearch::FromOQL($sExpression);
+ $aList = array();
+ $oSet = new DBObjectSet($oFilter);
+ while ((time() < $iTimeLimit) && ($oObj = $oSet->Fetch()))
+ {
+ $sClass = get_class($oObj);
+
+ $aList[] = $sClass.'::'.$oObj->GetKey().' '.$sAttCode.' '.$iThreshold;
+ echo $sClass.'::'.$oObj->GetKey().' '.$sAttCode.' '.$iThreshold."
\n";
+
+ // Execute planned actions
+ //
+ foreach ($aThresholdData['actions'] as $aActionData)
+ {
+ $sVerb = $aActionData['verb'];
+ $aParams = $aActionData['params'];
+ $sParams = implode(', ', $aParams);
+ //echo "Calling: $sVerb($sParams)
\n";
+ $aCallSpec = array($oObj, $sVerb);
+ call_user_func_array($aCallSpec, $aParams);
+ }
+
+ // Mark the threshold as "passed"
+ //
+ $oSW = $oObj->Get($sAttCode);
+ $oSW->MarkThresholdAsPassed($iThreshold);
+ $oObj->Set($sAttCode, $oSW);
+
+ if($oObj->IsModified())
+ {
+ // Todo - factorize so that only one single change will be instantiated
+ $oMyChange = new CMDBChange();
+ $oMyChange->Set("date", time());
+ $oMyChange->Set("userinfo", "Automatic - threshold passed");
+ $iChangeId = $oMyChange->DBInsertNoReload();
+
+ $oObj->DBUpdateTracked($oMyChange, true /*skip security*/);
+ }
+
+ // Activate any existing trigger
+ //
+ $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
+ $oSet = new DBObjectSet(
+ DBObjectSearch::FromOQL("SELECT TriggerOnThresholdReached AS t WHERE t.target_class IN ('$sClassList') AND stop_watch_code=:stop_watch_code AND threshold_index = :threshold_index"),
+ array(), // order by
+ array('stop_watch_code' => $sAttCode, 'threshold_index' => $iThreshold)
+ );
+ while ($oTrigger = $oSet->Fetch())
+ {
+ $oTrigger->DoActivate($oObj->ToArgs('this'));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $iProcessed = count($aList);
+ return "Encountered $iProcessed passed threshold(s)";
+ }
+}
+
+
+?>
diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php
index 2dddb3f64..bbbc759a8 100644
--- a/core/trigger.class.inc.php
+++ b/core/trigger.class.inc.php
@@ -278,4 +278,35 @@ class lnkTriggerAction extends cmdbAbstractObject
MetaModel::Init_SetZListItems('advanced_search', array('action_id', 'trigger_id', 'order')); // Criteria of the advanced search form
}
}
+
+class TriggerOnThresholdReached extends TriggerOnObject
+{
+ public static function Init()
+ {
+ $aParams = array
+ (
+ "category" => "core/cmdb,bizmodel",
+ "key_type" => "autoincrement",
+ "name_attcode" => "description",
+ "state_attcode" => "",
+ "reconc_keys" => array('description'),
+ "db_table" => "priv_trigger_threshold",
+ "db_key_field" => "id",
+ "db_finalclass_field" => "",
+ "display_template" => "",
+ );
+ MetaModel::Init_Params($aParams);
+ MetaModel::Init_InheritAttributes();
+
+ MetaModel::Init_AddAttribute(new AttributeString("stop_watch_code", array("allowed_values"=>null, "sql"=>"stop_watch_code", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeString("threshold_index", array("allowed_values"=>null, "sql"=>"threshold_index", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+
+ // Display lists
+ MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'stop_watch_code', 'threshold_index', 'action_list')); // Attributes to be displayed for the complete details
+ MetaModel::Init_SetZListItems('list', array('target_class', 'threshold_index', 'threshold_index')); // Attributes to be displayed for a list
+ // Search criteria
+ MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
+// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
+ }
+}
?>
diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php
index a4ff43a8b..798ee059a 100644
--- a/setup/compiler.class.inc.php
+++ b/setup/compiler.class.inc.php
@@ -654,6 +654,51 @@ EOF;
{
$aParameters['depends_on'] = $sDependencies;
}
+ elseif ($sAttType == 'AttributeStopWatch')
+ {
+ $oStates = $oField->GetUniqueElement('states');
+ $oStateNodes = $oStates->getElementsByTagName('state');
+ $aStates = array();
+ foreach($oStateNodes as $oState)
+ {
+ $aStates[] = '"'.$oState->GetAttribute('id').'"';
+ }
+ $aParameters['states'] = 'array('.implode(', ', $aStates).')';
+
+ $aParameters['goal_computing'] = $this->GetPropString($oField, 'goal', 'DefaultMetricComputer'); // Optional, no deadline by default
+ $aParameters['working_time_computing'] = $this->GetPropString($oField, 'working_time', 'DefaultWorkingTimeComputer'); // Optional, defaults to 24x7
+
+ $oThresholds = $oField->GetUniqueElement('thresholds');
+ $oThresholdNodes = $oThresholds->getElementsByTagName('threshold');
+ $aThresholds = array();
+ foreach($oThresholdNodes as $oThreshold)
+ {
+ $iPercent = $this->GetPropNumber($oThreshold, 'percent');
+
+ $oActions = $oThreshold->GetUniqueElement('actions');
+ $oActionNodes = $oActions->getElementsByTagName('action');
+ $aActions = array();
+ foreach($oActionNodes as $oAction)
+ {
+ $oParams = $oAction->GetOptionalElement('params');
+ $aActionParams = array();
+ if ($oParams)
+ {
+ $oParamNodes = $oParams->getElementsByTagName('param');
+ foreach($oParamNodes as $oParam)
+ {
+ $aActionParams[] = self::QuoteForPHP($oParam->textContent);
+ }
+ }
+ $sActionParams = 'array('.implode(', ', $aActionParams).')';
+ $sVerb = $this->GetPropString($oAction, 'verb');
+ $aActions[] = "array('verb' => $sVerb, 'params' => $sActionParams)";
+ }
+ $sActions = 'array('.implode(', ', $aActions).')';
+ $aThresholds[] = $iPercent." => array('percent' => $iPercent, 'actions' => $sActions)";
+ }
+ $aParameters['thresholds'] = 'array('.implode(', ', $aThresholds).')';
+ }
else
{
$aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')"