diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 0469a4d38..caed4161b 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -399,11 +399,13 @@ class bizDocument extends logRealObject MetaModel::Init_AddAttribute(new AttributeEnum("scope", array("label"=>"scope", "description"=>"Scope of this document", "allowed_values"=>new ValueSetEnum("organization,hardware support"), "sql"=>"scope", "default_value"=>"organization", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("label"=>"Contents", "description"=>"File content", "depends_on"=>array()))); + MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("scope"); MetaModel::Init_AddFilterFromAttribute("description"); - MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'scope','description')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'scope', 'description', 'contents')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'scope')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope')); // Criteria of the std search form diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index ccc6e8a05..df2db249d 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1,7 +1,15 @@ m_sCode;} public function GetLabel() {return $this->Get("label");} public function GetDescription() {return $this->Get("description");} - public function GetValuesDef() {return $this->Get("allowed_values");} - public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} + public function GetValuesDef() {return null;} + public function GetPrerequisiteAttributes() {return array();} //public function IsSearchableStd() {return $this->Get("search_std");} //public function IsSearchableGlobal() {return $this->Get("search_global");} //public function IsMandatory() {return $this->Get("is_mandatory");} @@ -136,13 +143,12 @@ abstract class AttributeDefinition //public function GetCheckRegExp() {return $this->Get("regexp");} //public function GetCheckFunc() {return $this->Get("checkfunc");} - // Definition: real value is what will be stored in memory and maintained by MetaModel - // DBObject::Set() relies on MakeRealValue() - // MetaModel::MakeQuery() relies on RealValueToSQLValue() - // DBObject::FromRow() relies on SQLToRealValue() 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 RealValueToSQLValue($value) {return $value;} // format value as a valuable SQL literal (quoted outside) - public function SQLValueToRealValue($value) {return $value;} // take the result of a fetch... and make it a PHP variable + + public function GetSQLExpressions() {return array();} // returns suffix/expression pairs (1 in most of the cases), for READING (Select) + public function FromSQLToValue($aCols, $sPrefix = '') {return null;} // returns a value out of suffix/value pairs, for SELECT result interpretation + public function GetSQLColumns() {return array();} // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation) + public function GetSQLValues($value) {return array();} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update) public function GetJSCheckFunc() { @@ -167,7 +173,6 @@ abstract class AttributeDefinition return call_user_func($sComputeFunc); } - abstract public function DBGetUsedFields(); abstract public function GetDefaultValue(); // @@ -181,17 +186,17 @@ abstract class AttributeDefinition public function GetAsHTML($sValue) { - return Str::pure2html($sValue); + return Str::pure2html((string)$sValue); } public function GetAsXML($sValue) { - return Str::pure2xml($sValue); + return Str::pure2xml((string)$sValue); } public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',') { - return str_replace($sSeparator, $sSepEscape, $sValue); + return str_replace($sSeparator, $sSepEscape, (string)$sValue); } public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') @@ -216,23 +221,23 @@ class AttributeLinkedSet extends AttributeDefinition { static protected function ListExpectedParams() { - return array_merge(parent::ListExpectedParams(), array("depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max")); + return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max")); } public function GetType() {return "Array of objects";} public function GetTypeDesc() {return "Any kind of objects [subclass] of the same class";} public function GetEditClass() {return "List";} - public function GetDBFieldType() {return "N/A";} // should be moved out of the AttributeDef root class public function IsWritable() {return true;} public function IsLinkSet() {return true;} + public function GetValuesDef() {return $this->Get("allowed_values");} + public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} public function GetDefaultValue() {return DBObjectSet::FromScratch($this->Get('linked_class'));} public function GetLinkedClass() {return $this->Get('linked_class');} public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');} - public function DBGetUsedFields() {return array();} public function GetBasicFilterOperators() {return array();} public function GetBasicFilterLooseOperator() {return '';} public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';} @@ -286,25 +291,55 @@ class AttributeDBFieldVoid extends AttributeDefinition { static protected function ListExpectedParams() { - return array_merge(parent::ListExpectedParams(), array("depends_on", "sql")); + return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql")); } + // To be overriden, used in GetSQLColumns + protected function GetSQLCol() {return "VARCHAR(255)";} + public function GetType() {return "Void";} public function GetTypeDesc() {return "Any kind of value, from the DB";} public function GetEditClass() {return "String";} - public function GetDBFieldType() {return "VARCHAR(255)";} + public function GetValuesDef() {return $this->Get("allowed_values");} + public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} + public function IsDirectField() {return true;} public function IsScalar() {return true;} public function IsWritable() {return true;} public function GetSQLExpr() {return $this->Get("sql");} public function GetDefaultValue() {return "";} public function IsNullAllowed() {return false;} - public function DBGetUsedFields() + + protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) + protected function SQLToScalar($value) {return $value;} // take the result of a fetch... and make it a PHP variable + + public function GetSQLExpressions() { - // #@# bugge a mort... a suivre... - return array($this->Get("sql")); - } + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determine by the existence of one column with an empty suffix + $aColumns[''] = $this->Get("sql"); + return $aColumns; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + $value = $this->MakeRealValue($aCols[$sPrefix.'']); + return $value; + } + public function GetSQLValues($value) + { + $aValues = array(); + $aValues[$this->Get("sql")] = $this->ScalarToSQL($value); + return $aValues; + } + + public function GetSQLColumns() + { + $aColumns = array(); + $aColumns[$this->Get("sql")] = $this->GetSQLCol(); + return $aColumns; + } public function GetBasicFilterOperators() { @@ -371,7 +406,7 @@ class AttributeInteger extends AttributeDBField public function GetType() {return "Integer";} public function GetTypeDesc() {return "Numeric value (could be negative)";} public function GetEditClass() {return "String";} - public function GetDBFieldType() {return "INT";} + protected function GetSQLCol() {return "INT";} public function GetBasicFilterOperators() { @@ -427,12 +462,12 @@ class AttributeInteger extends AttributeDBField //return intval($proposedValue); could work as well return (int)$proposedValue; } - public function RealValueToSQLValue($value) + public function ScalarToSQL($value) { assert(is_numeric($value)); return $value; // supposed to be an int } - public function SQLValueToRealValue($value) + public function SQLToScalar($value) { // Use cast (int) or intval() ? return (int)$value; @@ -461,7 +496,7 @@ class AttributeString extends AttributeDBField public function GetType() {return "String";} public function GetTypeDesc() {return "Alphanumeric string";} public function GetEditClass() {return "String";} - public function GetDBFieldType() {return "VARCHAR(255)";} + protected function GetSQLCol() {return "VARCHAR(255)";} public function GetBasicFilterOperators() { @@ -510,7 +545,7 @@ class AttributeString extends AttributeDBField // throw new CoreException("Failed to change the type of '$proposedValue' to a string"); // } } - public function RealValueToSQLValue($value) + public function ScalarToSQL($value) { if (!is_string($value)) { @@ -518,7 +553,7 @@ class AttributeString extends AttributeDBField } return $value; } - public function SQLValueToRealValue($value) + public function SQLToScalar($value) { return $value; } @@ -544,7 +579,7 @@ class AttributePassword extends AttributeString } public function GetEditClass() {return "Password";} - public function GetDBFieldType() {return "VARCHAR(64)";} + protected function GetSQLCol() {return "VARCHAR(64)";} } /** @@ -562,7 +597,7 @@ class AttributeText extends AttributeString public function GetType() {return "Text";} public function GetTypeDesc() {return "Multiline character string";} public function GetEditClass() {return "Text";} - public function GetDBFieldType() {return "TEXT";} + protected function GetSQLCol() {return "TEXT";} public function GetAsHTML($sValue) { @@ -601,7 +636,7 @@ class AttributeEnum extends AttributeString public function GetType() {return "Enum";} public function GetTypeDesc() {return "List of predefined alphanumeric strings";} public function GetEditClass() {return "String";} - public function GetDBFieldType() + protected function GetSQLCol() { $oValDef = $this->GetValuesDef(); if ($oValDef) @@ -672,7 +707,7 @@ class AttributeDate extends AttributeDBField public function GetType() {return "Date";} public function GetTypeDesc() {return "Date and time";} public function GetEditClass() {return "Date";} - public function GetDBFieldType() {return "TIMESTAMP";} + protected function GetSQLCol() {return "TIMESTAMP";} // #@# THIS HAS TO REVISED // Having null not allowed was interpreted by mySQL @@ -768,7 +803,7 @@ class AttributeDate extends AttributeDBField throw new CoreException("Invalid type for a date (found ".gettype($proposedValue)." and accepting string/int/DateTime)"); return null; } - public function RealValueToSQLValue($value) + public function ScalarToSQL($value) { if (empty($value)) { @@ -777,7 +812,7 @@ class AttributeDate extends AttributeDBField } return $value; } - public function SQLValueToRealValue($value) + public function SQLToScalar($value) { return $value; } @@ -825,7 +860,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid public function GetType() {return "Extkey";} public function GetTypeDesc() {return "Link to another object";} public function GetEditClass() {return "ExtKey";} - public function GetDBFieldType() {return "INT";} + protected function GetSQLCol() {return "INT";} public function IsExternalKey($iType = EXTKEY_RELATIVE) {return true;} public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");} @@ -904,11 +939,11 @@ class AttributeExternalField extends AttributeDefinition public function GetType() {return "ExtkeyField";} public function GetTypeDesc() {return "Field of an object pointed to by the current object";} public function GetEditClass() {return "ExtField";} - public function GetDBFieldType() + protected function GetSQLCol() { // throw new CoreException("external attribute: does it make any sense to request its type ?"); $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetDBFieldType(); + return $oExtAttDef->GetSQLCol(); } public function IsExternalKey($iType = EXTKEY_RELATIVE) @@ -974,12 +1009,6 @@ class AttributeExternalField extends AttributeDefinition $oExtAttDef = $this->GetExtAttDef(); return $oExtAttDef->GetSQLExpr(); } - public function DBGetUsedFields() - { - // No field is used but the one defined in the field of the external class - // #@# so what ? - return array(); - } public function GetDefaultValue() { @@ -1006,7 +1035,7 @@ class AttributeExternalField extends AttributeDefinition public function GetBasicFilterSQLExpr($sOpCode, $value) { $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value); + return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value); } public function MakeRealValue($proposedValue) @@ -1014,17 +1043,29 @@ class AttributeExternalField extends AttributeDefinition $oExtAttDef = $this->GetExtAttDef(); return $oExtAttDef->MakeRealValue($proposedValue); } - public function RealValueToSQLValue($value) + public function ScalarToSQL($value) { // This one could be used in case of filtering only $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->RealValueToSQLValue($value); + return $oExtAttDef->ScalarToSQL($value); } - public function SQLValueToRealValue($value) + public function SQLToScalar($value) { $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->SQLValueToRealValue($value); + return $oExtAttDef->SQLToScalar($value); } + + + // Do not overload GetSQLExpression here because this is handled in the joins + //public function GetSQLExpressions() {return array();} + + // Here, we get the data... + public function FromSQLToValue($aCols, $sPrefix = '') + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->FromSQLToValue($aCols, $sPrefix); + } + public function GetAsHTML($value) { $oExtAttDef = $this->GetExtAttDef(); @@ -1078,4 +1119,123 @@ class AttributeURL extends AttributeString } } +/** + * Data column, consisting in TWO columns in the DB + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeBlob extends AttributeDefinition +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("depends_on")); + } + + public function GetType() {return "Blob";} + public function GetTypeDesc() {return "Document";} + public function GetEditClass() {return "Document";} + + public function IsDirectField() {return true;} + public function IsScalar() {return true;} + public function IsWritable() {return true;} + public function GetDefaultValue() {return "";} + public function IsNullAllowed() {return false;} + + // Facilitate things: allow the user to Set the value from a string + public function MakeRealValue($proposedValue) + { + if (!is_object($proposedValue)) + { + return new ormDocument($proposedValue, 'text/plain'); + } + return $proposedValue; + } + + public function GetSQLExpressions() + { + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $this->GetCode().'_mimetype'; + $aColumns['_data'] = $this->GetCode().'_data'; + $aColumns['_filename'] = $this->GetCode().'_filename'; + return $aColumns; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + if (!isset($aCols[$sPrefix])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); + } + $sMimeType = $aCols[$sPrefix]; + + if (!isset($aCols[$sPrefix.'_data'])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '".$sPrefix."_data' from {$sAvailable}"); + } + $data = $aCols[$sPrefix.'_data']; + + if (!isset($aCols[$sPrefix.'_filename'])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '".$sPrefix."_filename' from {$sAvailable}"); + } + $sFileName = $aCols[$sPrefix.'_filename']; + + $value = new ormDocument($data, $sMimeType, $sFileName); + return $value; + } + + public function GetSQLValues($value) + { + // #@# Optimization: do not load blobs anytime + // As per mySQL doc, selecting blob columns will prevent mySQL from + // using memory in case a temporary table has to be created + // (temporary tables created on disk) + // We will have to remove the blobs from the list of attributes when doing the select + // then the use of Get() should finalize the load + $aValues = array(); + $aValues[$this->GetCode().'_data'] = $value->GetData(); + $aValues[$this->GetCode().'_mimetype'] = $value->GetMimeType(); + $aValues[$this->GetCode().'_filename'] = $value->GetFileName(); + return $aValues; + } + + public function GetSQLColumns() + { + $aColumns = array(); + $aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb) + $aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'; + $aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'; + return $aColumns; + } + + public function GetBasicFilterOperators() + { + return array(); + } + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return 'true'; + } + + public function GetAsHTML($value) + { + return $value->GetAsHTML(); + } +} + + ?> diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 8779c7ca8..5c4fc1a07 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -145,7 +145,7 @@ class CMDBChangeOpDelete extends CMDBChangeOp /** - * Record the modification of an attribute + * Record the modification of an attribute (abstract) * * @package iTopORM * @author Romain Quetiez @@ -175,11 +175,50 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"Attribute", "description"=>"code of the modified property", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("attcode"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list + } +} + +/** + * Record the modification of a scalar attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "property change", + "description" => "Object scalar properties change tracking", + "key_type" => "", + "key_label" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_scalar", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("oldvalue", array("label"=>"Previous value", "description"=>"previous value of the attribute", "allowed_values"=>null, "sql"=>"oldvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("newvalue", array("label"=>"New value", "description"=>"new value of the attribute", "allowed_values"=>null, "sql"=>"newvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); - MetaModel::Init_AddFilterFromAttribute("attcode"); MetaModel::Init_AddFilterFromAttribute("oldvalue"); MetaModel::Init_AddFilterFromAttribute("newvalue"); @@ -235,13 +274,84 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp $sTo = MetaModel::GetHyperLink($sTargetClass, $sNewValue); $sResult = "$sAttName set to $sTo (previous: $sFrom)"; } + elseif ($oAttDef instanceOf AttributeBlob) + { + $sResult = "#@# Issue... found an attribute for which other type of tracking should be made"; + } else { - $sResult = "$sAttName set too $sNewValue (previous value: $sOldValue)"; + $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; } } return $sResult; } } +/** + * Record the modification of a blob + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "object data change", + "description" => "Object data change tracking", + "key_type" => "", + "key_label" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_data", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeBlob("prevdata", array("label"=>"Previous data", "description"=>"previous contents of the attribute", "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + // Temporary, until we change the options of GetDescription() -needs a more global revision + $bIsHtml = true; + + $sResult = ''; + $oTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $oTargetSearch = new DBObjectSearch($oTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $oPrevDoc = $this->Get('prevdata'); + $sDocView = $oPrevDoc->GetAsHtml(); + $sResult = "$sAttName changed (previous value: $sDocView)"; + } + return $sResult; + } +} + + ?> diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 6608f3218..79b448071 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -162,22 +162,49 @@ abstract class CMDBObject extends DBObject { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); if ($oAttDef->IsLinkSet()) continue; // #@# temporary - $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttribute"); - $oMyChangeOp->Set("change", $oChange->GetKey()); - $oMyChangeOp->Set("objclass", get_class($this)); - $oMyChangeOp->Set("objkey", $this->GetKey()); - $oMyChangeOp->Set("attcode", $sAttCode); - if (array_key_exists($sAttCode, $aOrigValues)) + + if ($oAttDef instanceOf AttributeBlob) { - $sOriginalValue = $aOrigValues[$sAttCode]; + // Data blobs + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + + if (array_key_exists($sAttCode, $aOrigValues)) + { + $original = $aOrigValues[$sAttCode]; + } + else + { + $original = new ormDocument(); + } + $oMyChangeOp->Set("prevdata", $original); + $iId = $oMyChangeOp->DBInsertNoReload(); } else { - $sOriginalValue = 'undefined'; + // Scalars + // + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + + if (array_key_exists($sAttCode, $aOrigValues)) + { + $sOriginalValue = $aOrigValues[$sAttCode]; + } + else + { + $sOriginalValue = 'undefined'; + } + $oMyChangeOp->Set("oldvalue", $sOriginalValue); + $oMyChangeOp->Set("newvalue", $value); + $iId = $oMyChangeOp->DBInsertNoReload(); } - $oMyChangeOp->Set("oldvalue", $sOriginalValue); - $oMyChangeOp->Set("newvalue", $value); - $iId = $oMyChangeOp->DBInsertNoReload(); } } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 9412add37..431132097 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -207,11 +207,14 @@ abstract class DBObject // Skip links (could not be loaded by the mean of this query) if ($oAttDef->IsLinkSet()) continue; - if (array_key_exists($sAttCode, $aRow)) + // Note: we assume that, for a given attribute, if it can be loaded, + // then one column will be found with an empty suffix, the others have a suffix + if (isset($aRow[$sAttCode])) { - $sValue = $oAttDef->SQLValueToRealValue($aRow[$sAttCode]); - $this->m_aCurrValues[$sAttCode] = $sValue; - $this->m_aOrigValues[$sAttCode] = $sValue; + $value = $oAttDef->FromSQLToValue($aRow, $sAttCode); + + $this->m_aCurrValues[$sAttCode] = $value; + $this->m_aOrigValues[$sAttCode] = $value; $this->m_aLoadedAtt[$sAttCode] = true; } else @@ -613,11 +616,12 @@ abstract class DBObject { // Skip this attribute if not defined in this table if (!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode)) continue; - if ($oAttDef->IsDirectField()) + $aAttColumns = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]); + foreach($aAttColumns as $sColumn => $sValue) { - $aFieldsToWrite[] = $oAttDef->GetSQLExpr(); - $aValuesToWrite[] = CMDBSource::Quote($oAttDef->RealValueToSQLValue($this->m_aCurrValues[$sAttCode])); - } + $aFieldsToWrite[] = $sColumn; + $aValuesToWrite[] = CMDBSource::Quote($sValue); + } } if (count($aValuesToWrite) == 0) return false; diff --git a/core/filterdef.class.inc.php b/core/filterdef.class.inc.php index 8a2d7d096..25e816089 100644 --- a/core/filterdef.class.inc.php +++ b/core/filterdef.class.inc.php @@ -77,8 +77,7 @@ abstract class FilterDefinition abstract public function GetOperators(); // returns an opcode abstract public function GetLooseOperator(); - abstract public function GetFilterSQLExpr($sOpCode, $value); - abstract public function TemporaryGetSQLCol(); + abstract public function GetSQLExpressions(); // Wrapper - no need for overloading this one public function GetOpDescription($sOpCode) @@ -137,32 +136,11 @@ class FilterPrivateKey extends FilterDefinition return "IN"; } - public function GetFilterSQLExpr($sOpCode, $value) + public function GetSQLExpressions() { - $sFieldName = $this->Get("id_field"); - // #@# not obliged to quote... these are numbers !!! - $sQValue = CMDBSource::Quote($value); - switch($sOpCode) - { - case "IN": - if (!is_array($sQValue)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); - return "$sFieldName IN (".implode(", ", $sQValue).")"; - - case "NOTIN": - if (!is_array($sQValue)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); - return "$sFieldName NOT IN (".implode(", ", $sQValue).")"; - - case "!=": - return $sFieldName." != ".$sQValue; - - case "=": - default: - return $sFieldName." = ".$sQValue; - } - } - public function TemporaryGetSQLCol() - { - return $this->Get("id_field"); + return array( + '' => $this->Get("id_field"), + ); } } @@ -228,16 +206,10 @@ class FilterFromAttribute extends FilterDefinition return $oAttDef->GetBasicFilterLooseOperator(); } - public function GetFilterSQLExpr($sOpCode, $value) + public function GetSQLExpressions() { $oAttDef = $this->Get("refattribute"); - return $oAttDef->GetBasicFilterSQLExpr($sOpCode, $value); - } - - public function TemporaryGetSQLCol() - { - $oAttDef = $this->Get("refattribute"); - return $oAttDef->GetSQLExpr(); + return $oAttDef->GetSQLExpressions(); } } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index c05c47399..c40bc220b 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1560,15 +1560,17 @@ abstract class MetaModel if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue; // Skip this attribute if not writable (means that it does not correspond - if (count($oAttDef->DBGetUsedFields()) == 0) continue; + if (count($oAttDef->GetSQLExpressions()) == 0) continue; // Update... // if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) { assert ($oAttDef->IsDirectField()); - // Later, we'll have to use $oAttDef->GetDBField(); - $aUpdateValues[$oAttDef->GetSQLExpr()] = $oAttDef->RealValueToSQLValue($aValues[$sAttCode]); + foreach ($oAttDef->GetSQLValues($aValues[$sAttCode]) as $sColumn => $sValue) + { + $aUpdateValues[$sColumn] = $sValue; + } } // Select... @@ -1585,7 +1587,10 @@ abstract class MetaModel { // standard field, or external key // add it to the output - $aSelect[$sAttAlias] = new FieldExpression($oAttDef->GetSQLExpr(), $sTableAlias); + foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) + { + $aSelect[$sAttAlias.$sColId] = new FieldExpression($sSQLExpr, $sTableAlias); + } } } @@ -1597,8 +1602,12 @@ abstract class MetaModel if (self::$m_aFilterOrigins[$sTargetClass][$sFltCode] != $sTableClass) continue; // #@# todo - aller plus loin... a savoir que la table de translation doit contenir une "Expression" - // non-sens: $aTranslation[$sTargetAlias][$sFltCode] = array($sTableAlias, $oFltAtt->GetFilterSQLExpr(opcode, operand)); - $aTranslation[$sTargetAlias][$sFltCode] = array($sTableAlias, $oFltAtt->TemporaryGetSQLCol()); + foreach($oFltAtt->GetSQLExpressions() as $sColID => $sFltExpr) + { + // Note: I did not test it with filters relying on several expressions... + // as long as sColdID is empty, this is working, otherwise... ? + $aTranslation[$sTargetAlias][$sFltCode.$sColID] = array($sTableAlias, $sFltExpr); + } } // #@# todo - See what a full text search condition should be @@ -1660,8 +1669,10 @@ abstract class MetaModel } // Translate mainclass.extfield => remoteclassalias.remotefieldcode $oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode); - $sRemoteAttExpr = $oRemoteAttDef->GetSQLExpr(); - $aIntermediateTranslation[$sTargetAlias][$sAttCode] = array($sKeyClassAlias, $sRemoteAttExpr); + foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr) + { + $aIntermediateTranslation[$sTargetAlias.$sColID][$sAttCode] = array($sKeyClassAlias, $sRemoteAttExpr); + } //#@# debug - echo "

$sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)

\n"; } $oConditionTree = $oConditionTree->Translate($aIntermediateTranslation, false); @@ -1669,7 +1680,7 @@ abstract class MetaModel self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); $oSelectExtKey = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oExtFilter, $aExpAtts); - $sLocalKeyField = $oKeyAttDef->GetSQLExpr(); + $sLocalKeyField = current($oKeyAttDef->GetSQLExpressions()); // get the first column for an external key $sExternalKeyField = self::DBGetKey($sKeyClass); self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); if ($oKeyAttDef->IsNullAllowed()) @@ -2081,9 +2092,8 @@ abstract class MetaModel // Skip this attribute if not originaly defined in this class if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; - foreach($oAttDef->DBGetUsedFields() as $sField) + foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType) { - $sDBFieldType = $oAttDef->GetDBFieldType(); $sFieldSpecs = $oAttDef->IsNullAllowed() ? "$sDBFieldType NULL" : "$sDBFieldType NOT NULL"; if (!CMDBSource::IsField($sTable, $sField)) { @@ -2298,7 +2308,7 @@ abstract class MetaModel $sRemoteTable = self::DBGetTable($sRemoteClass); $sRemoteKey = self::DBGetKey($sRemoteClass); - $sExtKeyField = $oAttDef->GetSQLExpr(); + $sExtKeyField = current($oAttDef->GetSQLExpressions()); // get the first column for an external key // Note: a class/table may have an external key on itself $sSelBase = "SELECT DISTINCT maintable.`$sKeyField` AS id, maintable.`$sExtKeyField` AS extkey FROM `$sTable` AS maintable LEFT JOIN `$sRemoteTable` ON maintable.`$sExtKeyField` = `$sRemoteTable`.`$sRemoteKey`"; @@ -2344,7 +2354,7 @@ abstract class MetaModel { $sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true)); - $sMyAttributeField = $oAttDef->GetSQLExpr(); + $sMyAttributeField = current($oAttDef->GetSQLExpressions()); // get the first column for the moment $sDefaultValue = $oAttDef->GetDefaultValue(); $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE maintable.`$sMyAttributeField` NOT IN ($sExpectedValues)"; self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record having a column ('$sAttCode') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); diff --git a/core/ormdocument.class.inc.php b/core/ormdocument.class.inc.php new file mode 100644 index 000000000..54b8cecb7 --- /dev/null +++ b/core/ormdocument.class.inc.php @@ -0,0 +1,78 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + */ + +class ormDocument +{ + protected $m_data; + protected $m_sMimeType; + protected $m_sFileName; + + /** + * Constructor + */ + public function __construct($data = null, $sMimeType = 'text/plain', $sFileName = '') + { + $this->m_data = $data; + $this->m_sMimeType = $sMimeType; + $this->m_sFileName = $sFileName; + } + + public function __toString() + { + return MyHelpers::beautifulstr($this->m_data, 100, true); + } + + public function GetMimeType() + { + return $this->m_sMimeType; + } + public function GetMainMimeType() + { + $iSeparatorPos = strpos($this->m_sMimeType, '/'); + if ($iSeparatorPos > 0) + { + return substr($this->m_sMimeType, 0, $iSeparatorPos); + } + return $this->m_sMimeType; + } + + public function GetData() + { + return $this->m_data; + } + + public function GetFileName() + { + return $this->m_sFileName; + } + + public function GetAsHTML() + { + $data = $this->GetData(); + + switch ($this->GetMainMimeType()) + { + case 'text': + return "
".htmlentities(MyHelpers::beautifulstr($data, 1000, true))."
\n"; + + case 'application': + return "binary data for ".$this->GetMimeType().', size: '.strlen($data).' byte(s).'; + + case 'html': + default: + return "
".htmlentities(MyHelpers::beautifulstr($data, 1000, true))."
\n"; + } + } +} +?> diff --git a/pages/schema.php b/pages/schema.php index 9827de69a..b7162f5f4 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -338,13 +338,20 @@ function DisplayClassDetails($oPage, $sClass) $sAllowedValues = ""; $oAllowedValuesDef = $oAttDef->GetValuesDef(); $sMoreInfo = ""; - if (is_subclass_of($oAttDef, 'AttributeDBFieldVoid')) + + $aCols = array(); + foreach($oAttDef->GetSQLColumns() as $sCol => $sFieldDesc) { + $aCols[] = "$sCol: $sFieldDesc"; + } + if (count($aCols) > 0) + { + $sCols = implode(', ', $aCols); + $aMoreInfo = array(); - $aMoreInfo[] = "Column: ".$oAttDef->GetSQLExpr().""; + $aMoreInfo[] = "Column(s): $sCols"; $aMoreInfo[] = "Default: '".$oAttDef->GetDefaultValue()."'"; $aMoreInfo[] = $oAttDef->IsNullAllowed() ? "Null allowed" : "Null NOT allowed"; - //$aMoreInfo[] = $oAttDef->DBGetUsedFields(); $sMoreInfo .= implode(', ', $aMoreInfo); }