Browse Source

New type of Attribute "CaseLog" (under construction !)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1166 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 14 years ago
parent
commit
617bbf39aa

+ 9 - 0
application/cmdbabstract.class.inc.php

@@ -1272,6 +1272,15 @@ EOF
 					$sHTMLValue = "<table><tr><td><textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">$sEditValue</textarea></td><td>{$sValidationField}</td></tr></table>";
 				break;
 
+				case 'CaseLog':
+					$aEventsList[] ='validate';
+					$aEventsList[] ='keyup';
+					$aEventsList[] ='change';
+					$sEditValue = $oAttDef->GetEditValue($value);
+					$sPreviousLog = $oAttDef->GetAsHTML($value);
+					$sHTMLValue = "<div style=\"overflow:auto;border:1px #999 solid; background:#fff;\"><table style=\"width:100%\"><tr><td><textarea class=\"resizable\" style=\"border:0;width:100%\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">$sEditValue</textarea>$sPreviousLog</td><td>{$sValidationField}</td></tr></table></div>";
+				break;
+
 				case 'HTML':
 					$oWidget = new UIHTMLEditorWidget($iId, $sAttCode, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationField, $value, $bMandatory);
 					$sHTMLValue = $oWidget->Display($oPage, $aArgs);

+ 230 - 24
core/attributedef.class.inc.php

@@ -27,6 +27,7 @@
 require_once('MyHelpers.class.inc.php');
 require_once('ormdocument.class.inc.php');
 require_once('ormpassword.class.inc.php');
+require_once('ormcaselog.class.inc.php');
 
 /**
  * MissingColumnException - sent if an attribute is being created but the column is missing in the row 
@@ -203,7 +204,7 @@ abstract class AttributeDefinition
 	public function GetNullValue() {return null;} 
 	public function IsNull($proposedValue) {return is_null($proposedValue);} 
 
-	public function MakeRealValue($proposedValue) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!)
+	public function MakeRealValue($proposedValue, $oHostObj) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!)
 	public function Equals($val1, $val2) {return ($val1 == $val2);}
 
 	public function GetSQLExpressions($sPrefix = '') {return array();} // returns suffix/expression pairs (1 in most of the cases), for READING (Select)
@@ -273,6 +274,11 @@ abstract class AttributeDefinition
 
 	public function GetAsHTML($sValue, $oHostObject = null)
 	{
+		if ($sValue instanceof ormCaseLog)
+		{
+			echo "<p>AttributeCode: ".$this->GetCode()."</p>";
+			echo debug_print_backtrace();
+		}
 		return Str::pure2html((string)$sValue);
 	}
 
@@ -645,7 +651,7 @@ class AttributeDBFieldVoid extends AttributeDefinition
 	public function IsScalar() {return true;} 
 	public function IsWritable() {return true;} 
 	public function GetSQLExpr()    {return $this->Get("sql");}
-	public function GetDefaultValue() {return $this->MakeRealValue("");}
+	public function GetDefaultValue() {return $this->MakeRealValue("", null);}
 	public function IsNullAllowed() {return false;}
 
 	// 
@@ -661,7 +667,7 @@ class AttributeDBFieldVoid extends AttributeDefinition
 
 	public function FromSQLToValue($aCols, $sPrefix = '')
 	{
-		$value = $this->MakeRealValue($aCols[$sPrefix.'']);
+		$value = $this->MakeRealValue($aCols[$sPrefix.''], null);
 		return $value;
 	}
 	public function GetSQLValues($value)
@@ -718,7 +724,7 @@ class AttributeDBField extends AttributeDBFieldVoid
 	{
 		return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed"));
 	}
-	public function GetDefaultValue() {return $this->MakeRealValue($this->Get("default_value"));}
+	public function GetDefaultValue() {return $this->MakeRealValue($this->Get("default_value"), null);}
 	public function IsNullAllowed() {return $this->Get("is_null_allowed");}
 }
 
@@ -801,7 +807,7 @@ class AttributeInteger extends AttributeDBField
 		return is_null($proposedValue);
 	} 
 
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (is_null($proposedValue)) return null;
 		if ($proposedValue === '') return null; // 0 is transformed into '' !
@@ -899,7 +905,7 @@ class AttributeDecimal extends AttributeDBField
 		return is_null($proposedValue);
 	} 
 
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (is_null($proposedValue)) return null;
 		if ($proposedValue == '') return null;
@@ -929,7 +935,7 @@ class AttributeBoolean extends AttributeInteger
 	public function GetEditClass() {return "Integer";}
 	protected function GetSQLCol() {return "TINYINT(1)";}
 	
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (is_null($proposedValue)) return null;
 		if ($proposedValue === '') return null;
@@ -1041,7 +1047,7 @@ class AttributeString extends AttributeDBField
 		return ($proposedValue == '');
 	} 
 
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (is_null($proposedValue)) return '';
 		return (string)$proposedValue;
@@ -1118,6 +1124,206 @@ class AttributeClass extends AttributeString
 }
 
 /**
+ * An attibute that stores a case log (i.e journal) 
+ *
+ * @package     iTopORM
+ */
+class AttributeCaseLog extends AttributeText
+{
+	public function GetBasicFilterOperators()
+	{
+		return array(
+			"="=>"equals",
+			"!="=>"differs from",
+			"Like"=>"equals (no case)",
+			"NotLike"=>"differs from (no case)",
+			"Contains"=>"contains",
+			"Begins with"=>"begins with",
+			"Finishes with"=>"finishes with"
+		);
+	}
+	public function GetBasicFilterLooseOperator()
+	{
+		return "Contains";
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		$sQValue = CMDBSource::Quote($value);
+		switch ($sOpCode)
+		{
+		case '=':
+		case '!=':
+			return $this->GetSQLExpr()." $sOpCode $sQValue";
+		case 'Begins with':
+			return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%");
+		case 'Finishes with':
+			return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value");
+		case 'Contains':
+			return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%");
+		case 'NotLike':
+			return $this->GetSQLExpr()." NOT LIKE $sQValue";
+		case 'Like':
+		default:
+			return $this->GetSQLExpr()." LIKE $sQValue";
+		}
+	} 
+
+	public function GetNullValue()
+	{
+		return '';
+	} 
+
+	public function IsNull($proposedValue)
+	{
+		if (!($proposedValue instanceof ormCaseLog))
+		{
+			return ($proposedValue == '');
+		}
+		return ($proposedValue->GetText() == '');
+	} 
+
+	public function ScalarToSQL($value)
+	{
+		if (!is_string($value) && !is_null($value))
+		{
+			throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetCode(), 'attribute' => $this->GetHostClass()));
+		}
+		return $value;
+	}
+	public function GetEditClass() {return "CaseLog";}
+	public function GetEditValue($sValue) { return ''; } // New 'edit' value is always blank since it will be appended to the existing log	
+	public function IsDirectField() {return true;} 
+	public function IsScalar() {return true;} 
+	public function IsWritable() {return true;} 
+	public function GetDefaultValue() {return new ormCaseLog();}
+	public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);}
+	public function RequiresIndex() { return false; }
+	public function Equals($val1, $val2) {return (count($val1->GetIndex()) == count($val2->GetIndex()));}
+	public function GetMaxSize() { return null; }
+	public function CheckFormat($value) { return true; }
+	
+
+
+	// Facilitate things: allow the user to Set the value from a string
+	public function MakeRealValue($proposedValue, $oHostObj)
+	{
+		if (!($proposedValue instanceof ormCaseLog))
+		{
+			// Append the new value if an instance of the object is supplied
+			if ($oHostObj != null)
+			{
+				$oCaseLog = clone($oHostObj->GetOriginal($this->GetCode()));
+			}
+			else
+			{
+				$oCaseLog = new ormCaseLog();
+			}
+			echo "Added log entry: $proposedValue";
+			$oCaseLog->AddLogEntry(parent::MakeRealValue($proposedValue, $oHostObj));
+			return $oCaseLog;
+		}
+		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;
+		$aColumns['_index'] = $sPrefix.'_index';
+		return $aColumns;
+	}
+
+	public function FromSQLToValue($aCols, $sPrefix = '')
+	{
+		if (!isset($aCols[$sPrefix]))
+		{
+			$sAvailable = implode(', ', array_keys($aCols));
+			throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
+		} 
+		$sLog = $aCols[$sPrefix];
+
+		if (!isset($aCols[$sPrefix.'_index'])) 
+		{
+			$sAvailable = implode(', ', array_keys($aCols));
+			throw new MissingColumnException("Missing column '".$sPrefix."_index' from {$sAvailable}");
+		} 
+		$aIndex = unserialize($aCols[$sPrefix.'_index']);
+
+		$value = new ormCaseLog($sLog, $aIndex);
+		return $value;
+	}
+
+	public function GetSQLValues($value)
+	{
+		if (!($value instanceOf ormCaseLog))
+		{
+			$value = new ormCaseLog('');
+		}
+		$aValues = array();
+		$aValues[$this->GetCode()] = $value->GetText();
+		$aValues[$this->GetCode().'_index'] = serialize($value->GetIndex());
+
+		return $aValues;
+	}
+
+	public function GetSQLColumns()
+	{
+		$aColumns = array();
+		$aColumns[$this->GetCode()] = 'LONGTEXT'; // 2^32 (4 Gb)
+		$aColumns[$this->GetCode().'_index'] = 'BLOB';
+		return $aColumns;
+	}
+
+	public function GetFilterDefinitions()
+	{
+		return array($this->GetCode() => new FilterFromAttribute($this));
+	}
+
+	public function GetAsHTML($value, $oHostObject = null)
+	{
+		if ($value instanceOf ormCaseLog)
+		{
+			return $value->GetAsHTML(null, false);
+		}
+		else
+		{
+			return '';
+		}
+	}
+
+
+	public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null)
+	{
+		if ($value instanceOf ormCaseLog)
+		{
+			return parent::GetAsCSV($value->GetText(), $sSeparator, $sTextQualifier, $oHostObject);
+		}
+		else
+		{
+			return '';
+		}
+	}
+	
+	public function GetAsXML($value, $oHostObject = null)
+	{
+		if ($value instanceOf ormCaseLog)
+		{
+			return parent::GetAsXML($value->GetText(), $oHostObject);
+		}
+		else
+		{
+			return '';
+		}
+	}
+}
+
+/**
  * An attibute that matches one of the language codes availables in the dictionnary 
  *
  * @package     iTopORM
@@ -1278,7 +1484,7 @@ class AttributeEncryptedString extends AttributeString
 		return array();
 	}
 
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (is_null($proposedValue)) return null;
 		return (string)$proposedValue;
@@ -1379,7 +1585,7 @@ class AttributeText extends AttributeString
 		return $sValue;
 	}
 
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		$sValue = $proposedValue;
 		if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
@@ -1631,10 +1837,10 @@ class AttributeEnum extends AttributeString
   	 * @param mixed $proposedValue The value to be set for the attribute
   	 * @return mixed The actual value that will be set
   	 */
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if ($proposedValue == '') return null;
-		return parent::MakeRealValue($proposedValue);
+		return parent::MakeRealValue($proposedValue, $oHostObj);
 	}
 }
 
@@ -1766,7 +1972,7 @@ class AttributeDateTime extends AttributeDBField
 		}
 	}
 	
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (is_null($proposedValue))
 		{
@@ -1901,7 +2107,7 @@ class AttributeDuration extends AttributeInteger
 		return 0;
 	}
 
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (is_null($proposedValue)) return null;
 		if (!is_numeric($proposedValue)) return null;
@@ -2146,7 +2352,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
 		return ($proposedValue == 0);
 	} 
 
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (is_null($proposedValue)) return 0;
 		if ($proposedValue === '') return 0;
@@ -2337,10 +2543,10 @@ class AttributeExternalField extends AttributeDefinition
 		return $oExtAttDef->IsNull($proposedValue);
 	} 
 
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		$oExtAttDef = $this->GetExtAttDef();
-		return $oExtAttDef->MakeRealValue($proposedValue);
+		return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj);
 	}
 
 	public function ScalarToSQL($value)
@@ -2434,7 +2640,7 @@ class AttributeBlob extends AttributeDefinition
 
 
 	// Facilitate things: allow the user to Set the value from a string
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (!is_object($proposedValue))
 		{
@@ -2580,7 +2786,7 @@ class AttributeOneWayPassword extends AttributeDefinition
 	public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);}
 
 	// Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted)
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		$oPassword = $proposedValue;
 		if (!is_object($oPassword))
@@ -2727,7 +2933,7 @@ class AttributeTable extends AttributeText
 	}
 
 	// Facilitate things: allow the user to Set the value from a string
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (!is_array($proposedValue))
 		{
@@ -2743,12 +2949,12 @@ class AttributeTable extends AttributeText
 			$value = @unserialize($aCols[$sPrefix.'']);
 			if ($value === false)
 			{
-				$value = $this->MakeRealValue($aCols[$sPrefix.'']);
+				$value = $this->MakeRealValue($aCols[$sPrefix.''], null);
 			}
 		}
 		catch(Exception $e)
 		{
-			$value = $this->MakeRealValue($aCols[$sPrefix.'']);
+			$value = $this->MakeRealValue($aCols[$sPrefix.''], null);
 		}
 
 		return $value;
@@ -2797,7 +3003,7 @@ class AttributePropertySet extends AttributeTable
 	protected function GetSQLCol() {return "TEXT";}
 
 	// Facilitate things: allow the user to Set the value from a string
-	public function MakeRealValue($proposedValue)
+	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (!is_array($proposedValue))
 		{
@@ -2853,7 +3059,7 @@ class AttributeComputedFieldVoid extends AttributeDefinition
 	public function IsScalar() {return true;} 
 	public function IsWritable() {return false;} 
 	public function GetSQLExpr()    {return null;}
-	public function GetDefaultValue() {return $this->MakeRealValue("");}
+	public function GetDefaultValue() {return $this->MakeRealValue("", null);}
 	public function IsNullAllowed() {return false;}
 
 	// 

+ 4 - 0
core/cmdbobject.class.inc.php

@@ -189,6 +189,10 @@ abstract class CMDBObject extends DBObject
 				if (array_key_exists($sAttCode, $aOrigValues))
 				{
 					$original = $aOrigValues[$sAttCode];
+					if ($original instanceof ormCaseLog)
+					{
+						$original = $original->GetText();
+					}
 				}
 				else
 				{

+ 1 - 1
core/dbobject.class.php

@@ -339,7 +339,7 @@ abstract class DBObject
 			}
 		}
 
-		$realvalue = $oAttDef->MakeRealValue($value);
+		$realvalue = $oAttDef->MakeRealValue($value, $this);
 		$this->m_aCurrValues[$sAttCode] = $realvalue;
 
 		// The object has changed, reset caches

+ 104 - 0
core/ormcaselog.class.inc.php

@@ -0,0 +1,104 @@
+<?php
+// Copyright (C) 2011 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+define('CASELOG_DATE_FORMAT', 'Y-m-d H:i:s');
+define('CASELOG_HEADER_FORMAT', 'On %1$s, %2$s wrote:');
+define('CASELOG_VISIBLE_ITEMS', 2);
+define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\n\n");
+
+//require_once(APPROOT.'/core/userrights.class.inc.php');
+//require_once(APPROOT.'/application/webpage.class.inc.php');
+
+/**
+ * Class to store a "case log" in a structured way, keeping track of its successive entries
+ */
+class ormCaseLog {
+	protected $m_sLog;
+	protected $m_aIndex;
+	
+	/**
+	 * Initializes the log with the first (initial) entry
+	 * @param $sLog string The text of the whole case log
+	 * @param $aIndex hash The case log index
+	 */
+	public function __construct($sLog = '', $aIndex = array())
+	{
+		$this->m_sLog = $sLog;
+		$this->m_aIndex = $aIndex;
+	}
+	
+	public function GetText()
+	{
+		return $this->m_sLog;
+	}
+	
+	public function GetIndex()
+	{
+		return $this->m_aIndex;
+	}
+	
+	public function GetAsHTML(WebPage $oP = null, $bEditMode = false)
+	{
+		$sHtml = '';
+		$iPos = strlen($this->m_sLog);
+		for($index=0; $index < count($this->m_aIndex); $index++)
+		{
+			if ($index < count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS)
+			{
+				$sOpen = '';
+				$sDisplay = 'style="display:none;"';
+			}
+			else
+			{
+				$sOpen = ' open';
+				$sDisplay = '';
+			}
+			$iStart = $iPos - $this->m_aIndex[$index]['text_length'];
+			$sTextEntry = substr($this->m_sLog, $iStart, $this->m_aIndex[$index]['text_length']);
+			$iPos = $iStart - $this->m_aIndex[$index]['separator_length'];
+			$sEntry = '<div class="caselog_header'.$sOpen.'">';
+			$sEntry .= sprintf(CASELOG_HEADER_FORMAT, $this->m_aIndex[$index]['date']->format(CASELOG_DATE_FORMAT), $this->m_aIndex[$index]['user_name']);
+			$sEntry .= '</div>';
+			$sEntry .= '<div class="caselog_entry"'.$sDisplay.'>';
+			$sEntry .= str_replace("\n", "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
+			$sEntry .= '</div>';
+			$sHtml = $sEntry . $sHtml;
+		}
+		$sHtml = '<div class="caselog">'.$sHtml.'</div>';
+		return $sHtml;
+	}
+	
+	/**
+	 * Add a new entry to the log and updates the internal index
+	 * @param $sText string The text of the new entry 
+	 */
+	public function AddLogEntry($sText)
+	{
+		$sDate = date(CASELOG_DATE_FORMAT);
+		$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, UserRights::GetUserFriendlyName(), UserRights::GetUserId());
+		$iSepLength = strlen($sSeparator);
+		$iTextlength = strlen($sText);
+		$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
+		$this->m_aIndex[] = array(
+			'user_name' => UserRights::GetUserFriendlyName(),	
+			'user_id' => UserRights::GetUserId(),	
+			'date' => new DateTime(),	
+			'text_length' => $iTextlength,	
+			'separator_length' => $iSepLength,	
+		);
+	}
+}
+?>

+ 20 - 1
css/light-grey.css

@@ -144,7 +144,9 @@ td.label {
     color: #000000;
     nobackground-color:#f6f6f6;
     padding: 0.25em;
-    font-weight:bold;	
+    font-weight:bold;
+    vertical-align: top;
+    text-align: right;	
 }
 
 .ui-widget-content td a, p a, p a:visited, td a, td a:visited {
@@ -963,4 +965,21 @@ span.form_validation {
 	-moz-border-radius: 10px;
 	-webkit-border-radius: 10px;
 	border-radius: 10px;
+}
+.caselog_header {
+	padding:3px;
+	border-top:1px solid #fff;
+	background: #ddd url(../images/plus.gif) left no-repeat;
+	padding-left: 16px;
+	cursor: pointer;
+	width:100%;
+}
+.caselog_header.open {
+	background: #ddd url(../images/minus.gif) left no-repeat;
+}
+.caselog_entry {
+	padding:3px;
+	padding-left: 16px;
+	border-bottom:1px #999 solid;
+	width:100%;
 }