array of 'option' => value return array_merge(parent::ListExpectedParams(), array("states", "goal_computing", "working_time_computing", "thresholds")); } public function GetEditClass() { return "StopWatch"; } public static function IsBasedOnDBColumns() { return true; } public static function IsScalar() { return true; } public function IsWritable() { return true; } public function GetDefaultValue(DBObject $oHostObject = null) { return $this->NewStopWatch(); } /** * @param ormStopWatch $value * @param DBObject $oHostObj * * @return string */ public function GetEditValue($value, $oHostObj = null) { return $value->GetTimeSpent(); } public function GetStates() { return $this->Get('states'); } public function AlwaysLoadInTables() { // Each and every stop watch is accessed for computing the highlight code (DBObject::GetHighlightCode()) return true; } /** * 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(); // Warning: a stopwatch does not have any 'sql' property, so its SQL column is equal to its attribute code !! } $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.'_triggered'] = $sPrefix.$sThPrefix.'_triggered'; $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.'_triggered'; $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']) ); foreach ($this->ListThresholds() as $iThreshold => $aDefinition) { $sThPrefix = '_'.$iThreshold; $value->DefineThreshold( $iThreshold, self::DateToSeconds($aCols[$sPrefix.$sThPrefix.'_deadline']), (bool)($aCols[$sPrefix.$sThPrefix.'_passed'] == 1), (bool)($aCols[$sPrefix.$sThPrefix.'_triggered'] == 1), $aCols[$sPrefix.$sThPrefix.'_overrun'], array_key_exists('highlight', $aDefinition) ? $aDefinition['highlight'] : null ); } 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.'_triggered'] = $value->IsThresholdTriggered($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($bFullSpec = false) { $aColumns = array(); $aColumns[$this->GetCode().'_timespent'] = 'INT(11) UNSIGNED'; $aColumns[$this->GetCode().'_started'] = 'DATETIME'; $aColumns[$this->GetCode().'_laststart'] = 'DATETIME'; $aColumns[$this->GetCode().'_stopped'] = 'DATETIME'; foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sPrefix = $this->GetCode().'_'.$iThreshold; $aColumns[$sPrefix.'_deadline'] = 'DATETIME'; $aColumns[$sPrefix.'_passed'] = 'TINYINT(1) UNSIGNED'; $aColumns[$sPrefix.'_triggered'] = 'TINYINT(1)'; $aColumns[$sPrefix.'_overrun'] = 'INT(11) UNSIGNED'; } return $aColumns; } public function GetMagicFields() { $aRes = [ $this->GetCode().'_started', $this->GetCode().'_laststart', $this->GetCode().'_stopped', ]; foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sPrefix = $this->GetCode().'_'.$iThreshold; $aRes[] = $sPrefix.'_deadline'; $aRes[] = $sPrefix.'_passed'; $aRes[] = $sPrefix.'_triggered'; $aRes[] = $sPrefix.'_overrun'; } return $aRes; } public function GetBasicFilterOperators() { return array(); } public function GetBasicFilterLooseOperator() { return '='; } public function GetBasicFilterSQLExpr($sOpCode, $value) { return 'true'; } /** * @param ormStopWatch $value * @param DBObject $oHostObject * @param bool $bLocalize * * @return string */ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { if (is_object($value)) { return $value->GetAsHTML($this, $oHostObject); } return ''; } /** * @param ormStopWatch $value * @param string $sSeparator * @param string $sTextQualifier * @param null $oHostObject * @param bool $bLocalize * @param bool $bConvertToPlainText * * @return string */ public function GetAsCSV( $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false ) { return $value->GetTimeSpent(); } /** * @param ormStopWatch $value * @param DBObject $oHostObject * @param bool $bLocalize * * @return mixed */ public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { return $value->GetTimeSpent(); } public function ListThresholds() { return $this->Get('thresholds'); } public function Fingerprint($value) { $sFingerprint = ''; if (is_object($value)) { $sFingerprint = $value->GetAsHTML($this); } return $sFingerprint; } /** * To expose internal values: Declare an attribute AttributeSubItem * and implement the GetSubItemXXXX verbs * * @param string $sItemCode * * @return array * @throws CoreException */ public function GetSubItemSQLExpression($sItemCode) { $sPrefix = $this->GetCode(); switch ($sItemCode) { case 'timespent': return array('' => $sPrefix.'_timespent'); case 'started': return array('' => $sPrefix.'_started'); case 'laststart': return array('' => $sPrefix.'_laststart'); case 'stopped': return array('' => $sPrefix.'_stopped'); } foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': return array('' => $sPrefix.'_'.$iThreshold.'_deadline'); case 'passed': return array('' => $sPrefix.'_'.$iThreshold.'_passed'); case 'triggered': return array('' => $sPrefix.'_'.$iThreshold.'_triggered'); case 'overrun': return array('' => $sPrefix.'_'.$iThreshold.'_overrun'); } } } throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode()); } /** * @param string $sItemCode * @param ormStopWatch $value * @param DBObject $oHostObject * * @return mixed * @throws CoreException */ public function GetSubItemValue($sItemCode, $value, $oHostObject = null) { $oStopWatch = $value; switch ($sItemCode) { case 'timespent': return $oStopWatch->GetTimeSpent(); case 'started': return $oStopWatch->GetStartDate(); case 'laststart': return $oStopWatch->GetLastStartDate(); case 'stopped': return $oStopWatch->GetStopDate(); } foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': return $oStopWatch->GetThresholdDate($iThreshold); case 'passed': return $oStopWatch->IsThresholdPassed($iThreshold); case 'triggered': return $oStopWatch->IsThresholdTriggered($iThreshold); case 'overrun': return $oStopWatch->GetOverrun($iThreshold); } } } throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode()); } public function GetSubItemSearchType($sItemCode) { switch ($sItemCode) { case 'timespent': return static::SEARCH_WIDGET_TYPE_NUMERIC; //seconds case 'started': case 'laststart': case 'stopped': return static::SEARCH_WIDGET_TYPE_DATE_TIME; //timestamp } foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': return static::SEARCH_WIDGET_TYPE_DATE_TIME; //timestamp case 'passed': case 'triggered': return static::SEARCH_WIDGET_TYPE_ENUM; //booleans, used in conjuction with GetSubItemAllowedValues and IsSubItemNullAllowed case 'overrun': return static::SEARCH_WIDGET_TYPE_NUMERIC; //seconds } } } return static::SEARCH_WIDGET_TYPE_RAW; } public function GetSubItemAllowedValues($sItemCode, $aArgs = array(), $sContains = '') { foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'passed': case 'triggered': return array( 0 => $this->GetBooleanLabel(0), 1 => $this->GetBooleanLabel(1), ); } } } return null; } public function IsSubItemNullAllowed($sItemCode, $bDefaultValue) { foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'passed': case 'triggered': return false; } } } return $bDefaultValue; } protected function GetBooleanLabel($bValue) { $sDictKey = $bValue ? 'yes' : 'no'; return Dict::S('BooleanLabel:'.$sDictKey, 'def:'.$sDictKey); } public function GetSubItemAsHTMLForHistory($sItemCode, $sValue) { $sHtml = null; switch ($sItemCode) { case 'timespent': $sHtml = (int)$sValue ? Str::pure2html(AttributeDuration::FormatDuration($sValue)) : null; break; case 'started': case 'laststart': case 'stopped': $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; break; default: foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; break; case 'passed': case 'triggered': $sHtml = $this->GetBooleanLabel((int)$sValue); break; case 'overrun': $sHtml = (int)$sValue > 0 ? Str::pure2html(AttributeDuration::FormatDuration((int)$sValue)) : ''; } } } } return $sHtml; } public function GetSubItemAsPlainText($sItemCode, $value) { $sRet = $value; switch ($sItemCode) { case 'timespent': $sRet = AttributeDuration::FormatDuration($value); break; case 'started': case 'laststart': case 'stopped': if (is_null($value)) { $sRet = ''; // Undefined } else { $oDateTime = new DateTime(); $oDateTime->setTimestamp($value); $oDateTimeFormat = AttributeDateTime::GetFormat(); $sRet = $oDateTimeFormat->Format($oDateTime); } break; default: foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': if ($value) { if (is_int($value)) { $sDate = date(AttributeDateTime::GetInternalFormat(), $value); $sRet = AttributeDeadline::FormatDeadline($sDate); } else { $sRet = $value; } } else { $sRet = ''; } break; case 'passed': case 'triggered': $sRet = $this->GetBooleanLabel($value); break; case 'overrun': $sRet = AttributeDuration::FormatDuration($value); break; } } } } return $sRet; } public function GetSubItemAsHTML($sItemCode, $value) { $sHtml = $value; switch ($sItemCode) { case 'timespent': $sHtml = Str::pure2html(AttributeDuration::FormatDuration($value)); break; case 'started': case 'laststart': case 'stopped': if (is_null($value)) { $sHtml = ''; // Undefined } else { $oDateTime = new DateTime(); $oDateTime->setTimestamp($value); $oDateTimeFormat = AttributeDateTime::GetFormat(); $sHtml = Str::pure2html($oDateTimeFormat->Format($oDateTime)); } break; default: foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': if ($value) { $sDate = date(AttributeDateTime::GetInternalFormat(), $value); $sHtml = Str::pure2html(AttributeDeadline::FormatDeadline($sDate)); } else { $sHtml = ''; } break; case 'passed': case 'triggered': $sHtml = $this->GetBooleanLabel($value); break; case 'overrun': $sHtml = Str::pure2html(AttributeDuration::FormatDuration($value)); break; } } } } return $sHtml; } public function GetSubItemAsCSV( $sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"', $bConvertToPlainText = false ) { $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, (string)$value); $sRet = $sTextQualifier.$sEscaped.$sTextQualifier; switch ($sItemCode) { case 'timespent': $sRet = $sTextQualifier.AttributeDuration::FormatDuration($value).$sTextQualifier; break; case 'started': case 'laststart': case 'stopped': if ($value !== null) { $oDateTime = new DateTime(); $oDateTime->setTimestamp($value); $oDateTimeFormat = AttributeDateTime::GetFormat(); $sRet = $sTextQualifier.$oDateTimeFormat->Format($oDateTime).$sTextQualifier; } break; default: foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': if ($value != '') { $oDateTime = new DateTime(); $oDateTime->setTimestamp($value); $oDateTimeFormat = AttributeDateTime::GetFormat(); $sRet = $sTextQualifier.$oDateTimeFormat->Format($oDateTime).$sTextQualifier; } break; case 'passed': case 'triggered': $sRet = $sTextQualifier.$this->GetBooleanLabel($value).$sTextQualifier; break; case 'overrun': $sRet = $sTextQualifier.AttributeDuration::FormatDuration($value).$sTextQualifier; break; } } } } return $sRet; } public function GetSubItemAsXML($sItemCode, $value) { $sRet = Str::pure2xml((string)$value); switch ($sItemCode) { case 'timespent': case 'started': case 'laststart': case 'stopped': break; default: foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': case 'overrun': break; case 'triggered': case 'passed': $sRet = $this->GetBooleanLabel($value); break; } } } } return $sRet; } /** * Implemented for the HTML spreadsheet format! * * @param string $sItemCode * @param ormStopWatch $value * * @return false|string */ public function GetSubItemAsEditValue($sItemCode, $value) { $sRet = $value; switch ($sItemCode) { case 'timespent': break; case 'started': case 'laststart': case 'stopped': if (is_null($value)) { $sRet = ''; // Undefined } else { $sRet = date((string)AttributeDateTime::GetFormat(), $value); } break; default: foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); switch ($sThresholdCode) { case 'deadline': if ($value) { $sRet = date((string)AttributeDateTime::GetFormat(), $value); } else { $sRet = ''; } break; case 'passed': case 'triggered': $sRet = $this->GetBooleanLabel($value); break; case 'overrun': break; } } } } return $sRet; } /** * @inheritDoc */ public function HasAValue($proposedValue): bool { // A stopwatch always has a value return true; } public function RecordAttChange(DBObject $oObject, $original, $value): void { // Stop watches - record changes for sub items only (they are visible, the rest is not visible) // foreach ($this->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef) { $item_value = $this->GetSubItemValue($oSubItemAttDef->Get('item_code'), $value, $oObject); $item_original = $this->GetSubItemValue($oSubItemAttDef->Get('item_code'), $original, $oObject); if ($item_value != $item_original) { $oMyChangeOp = MetaModel::NewObject(CMDBChangeOpSetAttributeScalar::class); $oMyChangeOp->Set("objclass", get_class($oObject)); $oMyChangeOp->Set("objkey", $oObject->GetKey()); $oMyChangeOp->Set("attcode", $sSubItemAttCode); $oMyChangeOp->Set("oldvalue", $item_original); $oMyChangeOp->Set("newvalue", $item_value); $oMyChangeOp->DBInsertNoReload(); } } } }