m_aParams[$sParamName];
}
public function GetIndexLength()
{
$iMaxLength = $this->GetMaxSize();
if (is_null($iMaxLength))
{
return null;
}
if ($iMaxLength > static::INDEX_LENGTH)
{
return static::INDEX_LENGTH;
}
return $iMaxLength;
}
public function IsParam($sParamName)
{
return (array_key_exists($sParamName, $this->m_aParams));
}
protected function GetOptional($sParamName, $default)
{
if (array_key_exists($sParamName, $this->m_aParams))
{
return $this->m_aParams[$sParamName];
}
else
{
return $default;
}
}
/**
* AttributeDefinition constructor.
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
*/
public function __construct($sCode, $aParams)
{
$this->m_sCode = $sCode;
$this->m_aParams = $aParams;
$this->ConsistencyCheck();
$this->aCSSClasses = array('attribute');
}
public function GetParams()
{
return $this->m_aParams;
}
public function HasParam($sParam)
{
return array_key_exists($sParam, $this->m_aParams);
}
public function SetHostClass($sHostClass)
{
$this->m_sHostClass = $sHostClass;
}
public function GetHostClass()
{
return $this->m_sHostClass;
}
/**
* @return array
*
* @throws \CoreException
*/
public function ListSubItems()
{
$aSubItems = array();
foreach(MetaModel::ListAttributeDefs($this->m_sHostClass) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeSubItem)
{
if ($oAttDef->Get('target_attcode') == $this->m_sCode)
{
$aSubItems[$sAttCode] = $oAttDef;
}
}
}
return $aSubItems;
}
// Note: I could factorize this code with the parameter management made for the AttributeDef class
// to be overloaded
public static function ListExpectedParams()
{
return array();
}
/**
* @throws \Exception
*/
protected function ConsistencyCheck()
{
// Check that any mandatory param has been specified
//
$aExpectedParams = static::ListExpectedParams();
foreach($aExpectedParams as $sParamName)
{
if (!array_key_exists($sParamName, $this->m_aParams))
{
$aBacktrace = debug_backtrace();
$sTargetClass = $aBacktrace[2]["class"];
$sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"];
throw new Exception("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)");
}
}
}
/**
* Check the validity of the given value
*
* @param \DBObject $oHostObject
* @param $value Object error if any, null otherwise
*
* @return bool
*/
public function CheckValue(DBObject $oHostObject, $value)
{
// later: factorize here the cases implemented into DBObject
return true;
}
// table, key field, name field
/**
* @return string
* @deprecated never used
*/
public function ListDBJoins()
{
return "";
// e.g: return array("Site", "infrid", "name");
}
public function GetFinalAttDef()
{
return $this;
}
/**
* Deprecated - use IsBasedOnDBColumns instead
*
* @return bool
*/
public function IsDirectField()
{
return static::IsBasedOnDBColumns();
}
/**
* Returns true if the attribute value is built after DB columns
*
* @return bool
*/
public static function IsBasedOnDBColumns()
{
return false;
}
/**
* Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via
* GetOQLExpression)
*
* @return bool
*/
public static function IsBasedOnOQLExpression()
{
return false;
}
/**
* Returns true if the attribute value can be shown as a string
*
* @return bool
*/
public static function IsScalar()
{
return false;
}
/**
* Returns true if the attribute value is a set of related objects (1-N or N-N)
*
* @return bool
*/
public static function IsLinkSet()
{
return false;
}
/**
* @param int $iType
*
* @return bool true if the attribute is an external key, either directly (RELATIVE to the host class), or
* indirectly (ABSOLUTELY)
*/
public function IsExternalKey($iType = EXTKEY_RELATIVE)
{
return false;
}
/**
* @return bool true if the attribute value is an external key, pointing to the host class
*/
public static function IsHierarchicalKey()
{
return false;
}
/**
* @return bool true if the attribute value is stored on an object pointed to be an external key
*/
public static function IsExternalField()
{
return false;
}
/**
* @return bool true if the attribute can be written (by essence : metamodel field option)
* @see \DBObject::IsAttributeReadOnlyForCurrentState() for a specific object instance (depending on its workflow)
*/
public function IsWritable()
{
return false;
}
/**
* @return bool true if the attribute has been added automatically by the framework
*/
public function IsMagic()
{
return $this->GetOptional('magic', false);
}
/**
* @return bool true if the attribute value is kept in the loaded object (in memory)
*/
public static function LoadInObject()
{
return true;
}
/**
* @return bool true if the attribute value comes from the database in one way or another
*/
public static function LoadFromDB()
{
return true;
}
/**
* @return bool true if the attribute should be loaded anytime (in addition to the column selected by the user)
*/
public function AlwaysLoadInTables()
{
return $this->GetOptional('always_load_in_tables', false);
}
/**
* @param \DBObject $oHostObject
*
* @return mixed Must return the value if LoadInObject returns false
*/
public function GetValue($oHostObject)
{
return null;
}
/**
* Returns true if the attribute must not be stored if its current value is "null" (Cf. IsNull())
*
* @return bool
*/
public function IsNullAllowed()
{
return true;
}
/**
* Returns the attribute code (identifies the attribute in the host class)
*
* @return string
*/
public function GetCode()
{
return $this->m_sCode;
}
/**
* Find the corresponding "link" attribute on the target class, if any
*
* @return null | AttributeDefinition
*/
public function GetMirrorLinkAttribute()
{
return null;
}
/**
* Helper to browse the hierarchy of classes, searching for a label
*
* @param string $sDictEntrySuffix
* @param string $sDefault
* @param bool $bUserLanguageOnly
*
* @return string
* @throws \Exception
*/
protected function SearchLabel($sDictEntrySuffix, $sDefault, $bUserLanguageOnly)
{
$sLabel = Dict::S('Class:'.$this->m_sHostClass.$sDictEntrySuffix, '', $bUserLanguageOnly);
if (strlen($sLabel) == 0)
{
// Nothing found: go higher in the hierarchy (if possible)
//
$sLabel = $sDefault;
$sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
if ($sParentClass)
{
if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode))
{
$oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
$sLabel = $oAttDef->SearchLabel($sDictEntrySuffix, $sDefault, $bUserLanguageOnly);
}
}
}
return $sLabel;
}
/**
* @param string|null $sDefault if null, will return the attribute code replacing "_" by " "
*
* @return string
*
* @throws \Exception
*/
public function GetLabel($sDefault = null)
{
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, null, true /*user lang*/);
if (is_null($sLabel))
{
// If no default value is specified, let's define the most relevant one for developping purposes
if (is_null($sDefault))
{
$sDefault = str_replace('_', ' ', $this->m_sCode);
}
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, $sDefault, false);
}
return $sLabel;
}
/**
* To be overloaded for localized enums
*
* @param string $sValue
*
* @return string label corresponding to the given value (in plain text)
*/
public function GetValueLabel($sValue)
{
return $sValue;
}
/**
* Get the value from a given string (plain text, CSV import)
*
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return mixed null if no match could be found
*/
public function MakeValueFromString(
$sProposedValue,
$bLocalizedValue = false,
$sSepItem = null,
$sSepAttribute = null,
$sSepValue = null,
$sAttributeQualifier = null
) {
return $this->MakeRealValue($sProposedValue, null);
}
/**
* Parses a search string coming from user input
*
* @param string $sSearchString
*
* @return string
*/
public function ParseSearchString($sSearchString)
{
return $sSearchString;
}
/**
* @return string
*
* @throws \Exception
*/
public function GetLabel_Obsolete()
{
// Written for compatibility with a data model written prior to version 0.9.1
if (array_key_exists('label', $this->m_aParams))
{
return $this->m_aParams['label'];
}
else
{
return $this->GetLabel();
}
}
/**
* @param string|null $sDefault
*
* @return string
*
* @throws \Exception
*/
public function GetDescription($sDefault = null)
{
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', null, true /*user lang*/);
if (is_null($sLabel))
{
// If no default value is specified, let's define the most relevant one for developping purposes
if (is_null($sDefault))
{
$sDefault = '';
}
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', $sDefault, false);
}
return $sLabel;
}
/**
* @param string|null $sDefault
*
* @return string
*
* @throws \Exception
*/
public function GetHelpOnEdition($sDefault = null)
{
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', null, true /*user lang*/);
if (is_null($sLabel))
{
// If no default value is specified, let's define the most relevant one for developping purposes
if (is_null($sDefault))
{
$sDefault = '';
}
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', $sDefault, false);
}
return $sLabel;
}
public function GetHelpOnSmartSearch()
{
$aParents = array_merge(array(get_class($this) => get_class($this)), class_parents($this));
foreach($aParents as $sClass)
{
$sHelp = Dict::S("Core:$sClass?SmartSearch", '-missing-');
if ($sHelp != '-missing-')
{
return $sHelp;
}
}
return '';
}
/**
* @return string
*
* @throws \Exception
*/
public function GetDescription_Obsolete()
{
// Written for compatibility with a data model written prior to version 0.9.1
if (array_key_exists('description', $this->m_aParams))
{
return $this->m_aParams['description'];
}
else
{
return $this->GetDescription();
}
}
public function GetTrackingLevel()
{
return $this->GetOptional('tracking_level', ATTRIBUTE_TRACKING_ALL);
}
/**
* @return \ValueSetObjects
*/
public function GetValuesDef()
{
return null;
}
public function GetPrerequisiteAttributes($sClass = null)
{
return array();
}
public function GetNullValue()
{
return null;
}
public function IsNull($proposedValue)
{
return is_null($proposedValue);
}
/**
* force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!
*
* @param $proposedValue
* @param $oHostObj
*
* @return mixed
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
return $proposedValue;
}
public function Equals($val1, $val2)
{
return ($val1 == $val2);
}
/**
* @param string $sPrefix
*
* @return array suffix/expression pairs (1 in most of the cases), for READING (Select)
*/
public function GetSQLExpressions($sPrefix = '')
{
return array();
}
/**
* @param array $aCols
* @param string $sPrefix
*
* @return mixed a value out of suffix/value pairs, for SELECT result interpretation
*/
public function FromSQLToValue($aCols, $sPrefix = '')
{
return null;
}
/**
* @param bool $bFullSpec
*
* @return array column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation)
* @see \CMDBSource::GetFieldSpec()
*/
public function GetSQLColumns($bFullSpec = false)
{
return array();
}
/**
* @param $value
*
* @return array column/value pairs (1 in most of the cases), for WRITING (Insert, Update)
*/
public function GetSQLValues($value)
{
return array();
}
public function RequiresIndex()
{
return false;
}
public function RequiresFullTextIndex()
{
return false;
}
public function CopyOnAllTables()
{
return false;
}
public function GetOrderBySQLExpressions($sClassAlias)
{
// Note: This is the responsibility of this function to place backticks around column aliases
return array('`'.$sClassAlias.$this->GetCode().'`');
}
public function GetOrderByHint()
{
return '';
}
// Import - differs slightly from SQL input, but identical in most cases
//
public function GetImportColumns()
{
return $this->GetSQLColumns();
}
public function FromImportToValue($aCols, $sPrefix = '')
{
$aValues = array();
foreach($this->GetSQLExpressions($sPrefix) as $sAlias => $sExpr)
{
// This is working, based on the assumption that importable fields
// are not computed fields => the expression is the name of a column
$aValues[$sPrefix.$sAlias] = $aCols[$sExpr];
}
return $this->FromSQLToValue($aValues, $sPrefix);
}
public function GetValidationPattern()
{
return '';
}
public function CheckFormat($value)
{
return true;
}
public function GetMaxSize()
{
return null;
}
/**
* @return mixed|null
* @deprecated never used
*/
public function MakeValue()
{
$sComputeFunc = $this->Get("compute_func");
if (empty($sComputeFunc))
{
return null;
}
return call_user_func($sComputeFunc);
}
abstract public function GetDefaultValue(DBObject $oHostObject = null);
//
// To be overloaded in subclasses
//
abstract public function GetBasicFilterOperators(); // returns an array of "opCode"=>"description"
abstract public function GetBasicFilterLooseOperator(); // returns an "opCode"
//abstract protected GetBasicFilterHTMLInput();
abstract public function GetBasicFilterSQLExpr($sOpCode, $value);
public function GetFilterDefinitions()
{
return array();
}
public function GetEditValue($sValue, $oHostObj = null)
{
return (string)$sValue;
}
/**
* For fields containing a potential markup, return the value without this markup
*
* @param string $sValue
* @param \DBObject $oHostObj
*
* @return string
*/
public function GetAsPlainText($sValue, $oHostObj = null)
{
return (string)$this->GetEditValue($sValue, $oHostObj);
}
/**
* Helper to get a value that will be JSON encoded
* The operation is the opposite to FromJSONToValue
*
* @param $value
*
* @return string
*/
public function GetForJSON($value)
{
// In most of the cases, that will be the expected behavior...
return $this->GetEditValue($value);
}
/**
* Helper to form a value, given JSON decoded data
* The operation is the opposite to GetForJSON
*
* @param $json
*
* @return mixed
*/
public function FromJSONToValue($json)
{
// Passthrough in most of the cases
return $json;
}
/**
* Override to display the value in the GUI
*
* @param string $sValue
* @param \DBObject $oHostObject
* @param bool $bLocalize
*
* @return string
*/
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
return Str::pure2html((string)$sValue);
}
/**
* Override to export the value in XML
*
* @param string $sValue
* @param \DBObject $oHostObject
* @param bool $bLocalize
*
* @return mixed
*/
public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true)
{
return Str::pure2xml((string)$sValue);
}
/**
* Override to escape the value when read by DBObject::GetAsCSV()
*
* @param string $sValue
* @param string $sSeparator
* @param string $sTextQualifier
* @param \DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return string
*/
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
) {
return (string)$sValue;
}
/**
* Override to differentiate a value displayed in the UI or in the history
*
* @param string $sValue
* @param \DBObject $oHostObject
* @param bool $bLocalize
*
* @return string
*/
public function GetAsHTMLForHistory($sValue, $oHostObject = null, $bLocalize = true)
{
return $this->GetAsHTML($sValue, $oHostObject, $bLocalize);
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\StringField';
}
/**
* Override to specify Field class
*
* When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the
* $oFormField is passed, MakeFormField behave more like a Prepare.
*
* @param \DBObject $oObject
* @param \Combodo\iTop\Form\Field\Field $oFormField
*
* @return null
* @throws \CoreException
* @throws \Exception
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
// This is a fallback in case the AttributeDefinition subclass has no overloading of this function.
if ($oFormField === null)
{
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
//$oFormField->SetReadOnly(true);
}
$oFormField->SetLabel($this->GetLabel());
// Attributes flags
// - Retrieving flags for the current object
if ($oObject->IsNew())
{
$iFlags = $oObject->GetInitialStateAttributeFlags($this->GetCode());
}
else
{
$iFlags = $oObject->GetAttributeFlags($this->GetCode());
}
// - Comparing flags
if ($this->IsWritable() && (!$this->IsNullAllowed() || (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY)))
{
$oFormField->SetMandatory(true);
}
if ((!$oObject->IsNew() || !$oFormField->GetMandatory()) && (($iFlags & OPT_ATT_READONLY) === OPT_ATT_READONLY))
{
$oFormField->SetReadOnly(true);
}
// CurrentValue
$oFormField->SetCurrentValue($oObject->Get($this->GetCode()));
// Validation pattern
if ($this->GetValidationPattern() !== '')
{
$oFormField->AddValidator(new \Combodo\iTop\Form\Validator\Validator($this->GetValidationPattern()));
}
// Metadata
$oFormField->AddMetadata('attribute-code', $this->GetCode());
$oFormField->AddMetadata('attribute-type', get_class($this));
if($this::IsScalar())
{
$oFormField->AddMetadata('value-raw', $oObject->Get($this->GetCode()));
}
return $oFormField;
}
/**
* List the available verbs for 'GetForTemplate'
*/
public function EnumTemplateVerbs()
{
return array(
'' => 'Plain text (unlocalized) representation',
'html' => 'HTML representation',
'label' => 'Localized representation',
'text' => 'Plain text representation (without any markup)',
);
}
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param mixed $value The current value of the field
* @param string $sVerb The verb specifying the representation of the value
* @param \DBObject $oHostObject
* @param bool $bLocalize Whether or not to localize the value
*
* @return mixed|null|string
*
* @throws \Exception
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
if ($this->IsScalar())
{
switch ($sVerb)
{
case '':
return $value;
case 'html':
return $this->GetAsHtml($value, $oHostObject, $bLocalize);
case 'label':
return $this->GetEditValue($value);
case 'text':
return $this->GetAsPlainText($value);
break;
default:
throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject));
}
}
return null;
}
/**
* @param array $aArgs
* @param string $sContains
*
* @return array|null
* @throws \CoreException
* @throws \OQLException
*/
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$oValSetDef = $this->GetValuesDef();
if (!$oValSetDef)
{
return null;
}
return $oValSetDef->GetValues($aArgs, $sContains);
}
/**
* Explain the change of the attribute (history)
*
* @param string $sOldValue
* @param string $sNewValue
* @param string $sLabel
*
* @return string
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \OQLException
* @throws \Exception
*/
public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null)
{
if (is_null($sLabel))
{
$sLabel = $this->GetLabel();
}
$sNewValueHtml = $this->GetAsHTMLForHistory($sNewValue);
$sOldValueHtml = $this->GetAsHTMLForHistory($sOldValue);
if ($this->IsExternalKey())
{
/** @var \AttributeExternalKey $this */
$sTargetClass = $this->GetTargetClass();
$sOldValueHtml = (int)$sOldValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sOldValue) : null;
$sNewValueHtml = (int)$sNewValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sNewValue) : null;
}
if ((($this->GetType() == 'String') || ($this->GetType() == 'Text')) &&
(strlen($sNewValue) > strlen($sOldValue)))
{
// Check if some text was not appended to the field
if (substr($sNewValue, 0, strlen($sOldValue)) == $sOldValue) // Text added at the end
{
$sDelta = $this->GetAsHTML(substr($sNewValue, strlen($sOldValue)));
$sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel);
}
else
{
if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning
{
$sDelta = $this->GetAsHTML(substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue)));
$sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel);
}
else
{
if (strlen($sOldValue) == 0)
{
$sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml);
}
else
{
if (is_null($sNewValue))
{
$sNewValueHtml = Dict::S('UI:UndefinedObject');
}
$sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel,
$sNewValueHtml, $sOldValueHtml);
}
}
}
}
else
{
if (strlen($sOldValue) == 0)
{
$sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml);
}
else
{
if (is_null($sNewValue))
{
$sNewValueHtml = Dict::S('UI:UndefinedObject');
}
$sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml,
$sOldValueHtml);
}
}
return $sResult;
}
/**
* Parses a string to find some smart search patterns and build the corresponding search/OQL condition
* Each derived class is reponsible for defining and processing their own smart patterns, the base class
* does nothing special, and just calls the default (loose) operator
*
* @param string $sSearchText The search string to analyze for smart patterns
* @param \FieldExpression $oField
* @param array $aParams Values of the query parameters
*
* @return \Expression The search condition to be added (AND) to the current search
*
* @throws \CoreException
*/
public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams)
{
$sParamName = $oField->GetParent().'_'.$oField->GetName();
$oRightExpr = new VariableExpression($sParamName);
$sOperator = $this->GetBasicFilterLooseOperator();
switch ($sOperator)
{
case 'Contains':
$aParams[$sParamName] = "%$sSearchText%";
$sSQLOperator = 'LIKE';
break;
default:
$sSQLOperator = $sOperator;
$aParams[$sParamName] = $sSearchText;
}
$oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr);
return $oNewCondition;
}
/**
* Tells if an attribute is part of the unique fingerprint of the object (used for comparing two objects)
* All attributes which value is not based on a value from the object itself (like ExternalFields or LinkedSet)
* must be excluded from the object's signature
*
* @return boolean
*/
public function IsPartOfFingerprint()
{
return true;
}
/**
* The part of the current attribute in the object's signature, for the supplied value
*
* @param mixed $value The value of this attribute for the object
*
* @return string The "signature" for this field/attribute
*/
public function Fingerprint($value)
{
return (string)$value;
}
}
class AttributeDashboard extends AttributeDefinition
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(),
array("definition_file", "is_user_editable"));
}
public function GetDashboard()
{
$sAttCode = $this->GetCode();
$sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $sAttCode);
$sFilePath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$this->Get('definition_file');
return RuntimeDashboard::GetDashboard($sFilePath, $sClass.'__'.$sAttCode);
}
public function IsUserEditable()
{
return $this->Get('is_user_editable');
}
public function IsWritable()
{
return false;
}
public function GetEditClass()
{
return "";
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return null;
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return '';
}
/**
* @inheritdoc
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
return null;
}
// if this verb returns false, then GetValue must be implemented
public static function LoadInObject()
{
return false;
}
public function GetValue($oHostObject)
{
return '';
}
}
/**
* Set of objects directly linked to an object, and being part of its definition
*
* @package iTopORM
*/
class AttributeLinkedSet extends AttributeDefinition
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(),
array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max"));
}
public function GetEditClass()
{
return "LinkedSet";
}
public function IsWritable()
{
return true;
}
public static function IsLinkSet()
{
return true;
}
public function IsIndirect()
{
return false;
}
public function GetValuesDef()
{
return $this->Get("allowed_values");
}
public function GetPrerequisiteAttributes($sClass = null)
{
return $this->Get("depends_on");
}
/**
* @param \DBObject|null $oHostObject
*
* @return \ormLinkSet
*
* @throws \Exception
* @throws \CoreException
* @throws \CoreWarning
*/
public function GetDefaultValue(DBObject $oHostObject = null)
{
if ($oHostObject === null)
{
return null;
}
$sLinkClass = $this->GetLinkedClass();
$sExtKeyToMe = $this->GetExtKeyToMe();
// The class to target is not the current class, because if this is a derived class,
// it may differ from the target class, then things start to become confusing
/** @var \AttributeExternalKey $oRemoteExtKeyAtt */
$oRemoteExtKeyAtt = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToMe);
$sMyClass = $oRemoteExtKeyAtt->GetTargetClass();
$oMyselfSearch = new DBObjectSearch($sMyClass);
if ($oHostObject !== null)
{
$oMyselfSearch->AddCondition('id', $oHostObject->GetKey(), '=');
}
$oLinkSearch = new DBObjectSearch($sLinkClass);
$oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe);
if ($this->IsIndirect())
{
// Join the remote class so that the archive flag will be taken into account
/** @var \AttributeLinkedSetIndirect $this */
$sExtKeyToRemote = $this->GetExtKeyToRemote();
/** @var \AttributeExternalKey $oExtKeyToRemote */
$oExtKeyToRemote = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToRemote);
$sRemoteClass = $oExtKeyToRemote->GetTargetClass();
if (MetaModel::IsArchivable($sRemoteClass))
{
$oRemoteSearch = new DBObjectSearch($sRemoteClass);
/** @var \AttributeLinkedSetIndirect $this */
$oLinkSearch->AddCondition_PointingTo($oRemoteSearch, $this->GetExtKeyToRemote());
}
}
$oLinks = new DBObjectSet($oLinkSearch);
$oLinkSet = new ormLinkSet($this->GetHostClass(), $this->GetCode(), $oLinks);
return $oLinkSet;
}
public function GetTrackingLevel()
{
return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_default'));
}
public function GetEditMode()
{
return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
}
public function GetLinkedClass()
{
return $this->Get('linked_class');
}
public function GetExtKeyToMe()
{
return $this->Get('ext_key_to_me');
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return '';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return '';
}
/**
* @param string $sValue
* @param \DBObject $oHostObject
* @param bool $bLocalize
*
* @return string|null
*
* @throws \CoreException
*/
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (is_object($sValue) && ($sValue instanceof ormLinkSet))
{
$sValue->Rewind();
$aItems = array();
while ($oObj = $sValue->Fetch())
{
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef)
{
if ($sAttCode == $this->GetExtKeyToMe())
{
continue;
}
if ($oAttDef->IsExternalField())
{
continue;
}
$sAttValue = $oObj->GetAsHTML($sAttCode);
if (strlen($sAttValue) > 0)
{
$aAttributes[] = $sAttValue;
}
}
$sAttributes = implode(', ', $aAttributes);
$aItems[] = $sAttributes;
}
return implode(' ', $aItems);
}
return null;
}
/**
* @param string $sValue
* @param \DBObject $oHostObject
* @param bool $bLocalize
*
* @return string
*
* @throws \CoreException
*/
public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true)
{
if (is_object($sValue) && ($sValue instanceof ormLinkSet))
{
$sValue->Rewind();
$sRes = "\n";
while ($oObj = $sValue->Fetch())
{
$sObjClass = get_class($oObj);
$sRes .= "<$sObjClass id=\"".$oObj->GetKey()."\">\n";
// Show only relevant information (hide the external key to the current object)
foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
{
if ($sAttCode == 'finalclass')
{
if ($sObjClass == $this->GetLinkedClass())
{
// Simplify the output if the exact class could be determined implicitely
continue;
}
}
if ($sAttCode == $this->GetExtKeyToMe())
{
continue;
}
if ($oAttDef->IsExternalField())
{
/** @var \AttributeExternalField $oAttDef */
if ($oAttDef->GetKeyAttCode() == $this->GetExtKeyToMe())
{
continue;
}
/** @var AttributeExternalField $oAttDef */
if ($oAttDef->IsFriendlyName())
{
continue;
}
}
if ($oAttDef instanceof AttributeFriendlyName)
{
continue;
}
if (!$oAttDef->IsScalar())
{
continue;
}
$sAttValue = $oObj->GetAsXML($sAttCode, $bLocalize);
$sRes .= "<$sAttCode>$sAttValue$sAttCode>\n";
}
$sRes .= "$sObjClass>\n";
}
$sRes .= "\n";
}
else
{
$sRes = '';
}
return $sRes;
}
/**
* @param $sValue
* @param string $sSeparator
* @param string $sTextQualifier
* @param \DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return mixed|string
* @throws \CoreException
*/
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
) {
$sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator');
$sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator');
$sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator');
$sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier');
if (is_object($sValue) && ($sValue instanceof ormLinkSet))
{
$sValue->Rewind();
$aItems = array();
while ($oObj = $sValue->Fetch())
{
$sObjClass = get_class($oObj);
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
{
if ($sAttCode == 'finalclass')
{
if ($sObjClass == $this->GetLinkedClass())
{
// Simplify the output if the exact class could be determined implicitely
continue;
}
}
if ($sAttCode == $this->GetExtKeyToMe())
{
continue;
}
if ($oAttDef->IsExternalField())
{
continue;
}
if (!$oAttDef->IsBasedOnDBColumns())
{
continue;
}
if (!$oAttDef->IsScalar())
{
continue;
}
$sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '', $bLocalize);
if (strlen($sAttValue) > 0)
{
$sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier,
$sAttCode.$sSepValue.$sAttValue);
$aAttributes[] = $sAttributeQualifier.$sAttributeData.$sAttributeQualifier;
}
}
$sAttributes = implode($sSepAttribute, $aAttributes);
$aItems[] = $sAttributes;
}
$sRes = implode($sSepItem, $aItems);
}
else
{
$sRes = '';
}
$sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes);
$sRes = $sTextQualifier.$sRes.$sTextQualifier;
return $sRes;
}
/**
* List the available verbs for 'GetForTemplate'
*/
public function EnumTemplateVerbs()
{
return array(
'' => 'Plain text (unlocalized) representation',
'html' => 'HTML representation (unordered list)',
);
}
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param mixed $value The current value of the field
* @param string $sVerb The verb specifying the representation of the value
* @param DBObject $oHostObject The object
* @param bool $bLocalize Whether or not to localize the value
*
* @return string
* @throws \Exception
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
$sRemoteName = $this->IsIndirect() ?
/** @var \AttributeLinkedSetIndirect $this */
$this->GetExtKeyToRemote().'_friendlyname' : 'friendlyname';
$oLinkSet = clone $value; // Workaround/Safety net for Trac #887
$iLimit = MetaModel::GetConfig()->Get('max_linkset_output');
$iCount = 0;
$aNames = array();
foreach($oLinkSet as $oItem)
{
if (($iLimit > 0) && ($iCount == $iLimit))
{
$iTotal = $oLinkSet->Count();
$aNames[] = '... '.Dict::Format('UI:TruncatedResults', $iCount, $iTotal);
break;
}
$aNames[] = $oItem->Get($sRemoteName);
$iCount++;
}
switch ($sVerb)
{
case '':
return implode("\n", $aNames);
case 'html':
return '
'.implode("
", $aNames).'
';
default:
throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject));
}
}
public function DuplicatesAllowed()
{
return false;
} // No duplicates for 1:n links, never
public function GetImportColumns()
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'TEXT';
return $aColumns;
}
/**
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return \DBObjectSet|mixed
* @throws \CSVParserException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*/
public function MakeValueFromString(
$sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
$sAttributeQualifier = null
) {
if (is_null($sSepItem) || empty($sSepItem))
{
$sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator');
}
if (is_null($sSepAttribute) || empty($sSepAttribute))
{
$sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator');
}
if (is_null($sSepValue) || empty($sSepValue))
{
$sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator');
}
if (is_null($sAttributeQualifier) || empty($sAttributeQualifier))
{
$sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier');
}
$sTargetClass = $this->Get('linked_class');
$sInput = str_replace($sSepItem, "\n", $sProposedValue);
$oCSVParser = new CSVParser($sInput, $sSepAttribute, $sAttributeQualifier);
$aInput = $oCSVParser->ToArray(0 /* do not skip lines */);
$aLinks = array();
foreach($aInput as $aRow)
{
// 1st - get the values, split the extkey->searchkey specs, and eventually get the finalclass value
$aExtKeys = array();
$aValues = array();
foreach($aRow as $sCell)
{
$iSepPos = strpos($sCell, $sSepValue);
if ($iSepPos === false)
{
// Houston...
throw new CoreException('Wrong format for link attribute specification', array('value' => $sCell));
}
$sAttCode = trim(substr($sCell, 0, $iSepPos));
$sValue = substr($sCell, $iSepPos + strlen($sSepValue));
if (preg_match('/^(.+)->(.+)$/', $sAttCode, $aMatches))
{
$sKeyAttCode = $aMatches[1];
$sRemoteAttCode = $aMatches[2];
$aExtKeys[$sKeyAttCode][$sRemoteAttCode] = $sValue;
if (!MetaModel::IsValidAttCode($sTargetClass, $sKeyAttCode))
{
throw new CoreException('Wrong attribute code for link attribute specification',
array('class' => $sTargetClass, 'attcode' => $sKeyAttCode));
}
/** @var \AttributeExternalKey $oKeyAttDef */
$oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode);
$sRemoteClass = $oKeyAttDef->GetTargetClass();
if (!MetaModel::IsValidAttCode($sRemoteClass, $sRemoteAttCode))
{
throw new CoreException('Wrong attribute code for link attribute specification',
array('class' => $sRemoteClass, 'attcode' => $sRemoteAttCode));
}
}
else
{
if (!MetaModel::IsValidAttCode($sTargetClass, $sAttCode))
{
throw new CoreException('Wrong attribute code for link attribute specification',
array('class' => $sTargetClass, 'attcode' => $sAttCode));
}
$oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttCode);
$aValues[$sAttCode] = $oAttDef->MakeValueFromString($sValue, $bLocalizedValue, $sSepItem,
$sSepAttribute, $sSepValue, $sAttributeQualifier);
}
}
// 2nd - Instanciate the object and set the value
if (isset($aValues['finalclass']))
{
$sLinkClass = $aValues['finalclass'];
if (!is_subclass_of($sLinkClass, $sTargetClass))
{
throw new CoreException('Wrong class for link attribute specification',
array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
}
}
elseif (MetaModel::IsAbstract($sTargetClass))
{
throw new CoreException('Missing finalclass for link attribute specification');
}
else
{
$sLinkClass = $sTargetClass;
}
$oLink = MetaModel::NewObject($sLinkClass);
foreach($aValues as $sAttCode => $sValue)
{
$oLink->Set($sAttCode, $sValue);
}
// 3rd - Set external keys from search conditions
foreach($aExtKeys as $sKeyAttCode => $aReconciliation)
{
$oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode);
$sKeyClass = $oKeyAttDef->GetTargetClass();
$oExtKeyFilter = new DBObjectSearch($sKeyClass);
$aReconciliationDesc = array();
foreach($aReconciliation as $sRemoteAttCode => $sValue)
{
$oExtKeyFilter->AddCondition($sRemoteAttCode, $sValue, '=');
$aReconciliationDesc[] = "$sRemoteAttCode=$sValue";
}
$oExtKeySet = new DBObjectSet($oExtKeyFilter);
switch ($oExtKeySet->Count())
{
case 0:
$sReconciliationDesc = implode(', ', $aReconciliationDesc);
throw new CoreException("Found no match",
array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc));
break;
case 1:
$oRemoteObj = $oExtKeySet->Fetch();
$oLink->Set($sKeyAttCode, $oRemoteObj->GetKey());
break;
default:
$sReconciliationDesc = implode(', ', $aReconciliationDesc);
throw new CoreException("Found several matches",
array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc));
// Found several matches, ambiguous
}
}
// Check (roughly) if such a link is valid
$aErrors = array();
foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef)
{
if ($oAttDef->IsExternalKey())
{
/** @var \AttributeExternalKey $oAttDef */
if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(),
$oAttDef->GetTargetClass())))
{
continue; // Don't check the key to self
}
}
if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed())
{
$aErrors[] = $sAttCode;
}
}
if (count($aErrors) > 0)
{
throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors));
}
$aLinks[] = $oLink;
}
$oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
return $oSet;
}
/**
* Helper to get a value that will be JSON encoded
* The operation is the opposite to FromJSONToValue
*
* @param \ormLinkSet $value
*
* @return array
* @throws \CoreException
*/
public function GetForJSON($value)
{
$aRet = array();
if (is_object($value) && ($value instanceof ormLinkSet))
{
$value->Rewind();
while ($oObj = $value->Fetch())
{
$sObjClass = get_class($oObj);
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
{
if ($sAttCode == 'finalclass')
{
if ($sObjClass == $this->GetLinkedClass())
{
// Simplify the output if the exact class could be determined implicitely
continue;
}
}
if ($sAttCode == $this->GetExtKeyToMe())
{
continue;
}
if ($oAttDef->IsExternalField())
{
continue;
}
if (!$oAttDef->IsBasedOnDBColumns())
{
continue;
}
if (!$oAttDef->IsScalar())
{
continue;
}
$attValue = $oObj->Get($sAttCode);
$aAttributes[$sAttCode] = $oAttDef->GetForJSON($attValue);
}
$aRet[] = $aAttributes;
}
}
return $aRet;
}
/**
* Helper to form a value, given JSON decoded data
* The operation is the opposite to GetForJSON
*
* @param $json
*
* @return \DBObjectSet
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function FromJSONToValue($json)
{
$sTargetClass = $this->Get('linked_class');
$aLinks = array();
foreach($json as $aValues)
{
if (isset($aValues['finalclass']))
{
$sLinkClass = $aValues['finalclass'];
if (!is_subclass_of($sLinkClass, $sTargetClass))
{
throw new CoreException('Wrong class for link attribute specification',
array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
}
}
elseif (MetaModel::IsAbstract($sTargetClass))
{
throw new CoreException('Missing finalclass for link attribute specification');
}
else
{
$sLinkClass = $sTargetClass;
}
$oLink = MetaModel::NewObject($sLinkClass);
foreach($aValues as $sAttCode => $sValue)
{
$oLink->Set($sAttCode, $sValue);
}
// Check (roughly) if such a link is valid
$aErrors = array();
foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef)
{
if ($oAttDef->IsExternalKey())
{
/** @var AttributeExternalKey $oAttDef */
if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(),
$oAttDef->GetTargetClass())))
{
continue; // Don't check the key to self
}
}
if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed())
{
$aErrors[] = $sAttCode;
}
}
if (count($aErrors) > 0)
{
throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors));
}
$aLinks[] = $oLink;
}
$oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
return $oSet;
}
/**
* @param $proposedValue
* @param $oHostObj
*
* @return mixed
* @throws \Exception
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if ($proposedValue === null)
{
$sLinkedClass = $this->GetLinkedClass();
$aLinkedObjectsArray = array();
$oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray);
return new ormLinkSet(
get_class($oHostObj),
$this->GetCode(),
$oSet
);
}
return $proposedValue;
}
/**
* @param ormLinkSet $val1
* @param ormLinkSet $val2
*
* @return bool
*/
public function Equals($val1, $val2)
{
if ($val1 === $val2)
{
$bAreEquivalent = true;
}
else
{
$bAreEquivalent = ($val2->HasDelta() === false);
}
return $bAreEquivalent;
}
/**
* Find the corresponding "link" attribute on the target class, if any
*
* @return null | AttributeDefinition
* @throws \Exception
*/
public function GetMirrorLinkAttribute()
{
$oRemoteAtt = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToMe());
return $oRemoteAtt;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\LinkedSetField';
}
/**
* @param \DBObject $oObject
* @param \Combodo\iTop\Form\Field\LinkedSetField $oFormField
*
* @return \Combodo\iTop\Form\Field\LinkedSetField
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \Exception
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null)
{
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
// Setting target class
if (!$this->IsIndirect())
{
$sTargetClass = $this->GetLinkedClass();
}
else
{
/** @var \AttributeExternalKey $oRemoteAttDef */
/** @var \AttributeLinkedSetIndirect $this */
$oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
$sTargetClass = $oRemoteAttDef->GetTargetClass();
/** @var \AttributeLinkedSetIndirect $this */
$oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote());
}
$oFormField->SetTargetClass($sTargetClass);
$oFormField->SetIndirect($this->IsIndirect());
// Setting attcodes to display
$aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list'));
// - Adding friendlyname attribute to the list is not already in it
$sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass);
if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay))
{
$aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay);
}
// - Adding attribute labels
$aAttributesToDisplay = array();
foreach($aAttCodesToDisplay as $sAttCodeToDisplay)
{
$oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay);
$aAttributesToDisplay[$sAttCodeToDisplay] = $oAttDefToDisplay->GetLabel();
}
$oFormField->SetAttributesToDisplay($aAttributesToDisplay);
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
public function IsPartOfFingerprint()
{
return false;
}
}
/**
* Set of objects linked to an object (n-n), and being part of its definition
*
* @package iTopORM
*/
class AttributeLinkedSetIndirect extends AttributeLinkedSet
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("ext_key_to_remote"));
}
public function IsIndirect()
{
return true;
}
public function GetExtKeyToRemote()
{
return $this->Get('ext_key_to_remote');
}
public function GetEditClass()
{
return "LinkedSet";
}
public function DuplicatesAllowed()
{
return $this->GetOptional("duplicates", false);
} // The same object may be linked several times... or not...
public function GetTrackingLevel()
{
return $this->GetOptional('tracking_level',
MetaModel::GetConfig()->Get('tracking_level_linked_set_indirect_default'));
}
/**
* Find the corresponding "link" attribute on the target class, if any
*
* @return null | AttributeDefinition
* @throws \CoreException
*/
public function GetMirrorLinkAttribute()
{
$oRet = null;
/** @var \AttributeExternalKey $oExtKeyToRemote */
$oExtKeyToRemote = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
$sRemoteClass = $oExtKeyToRemote->GetTargetClass();
foreach(MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef)
{
if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect)
{
continue;
}
if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass())
{
continue;
}
if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote())
{
continue;
}
if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe())
{
continue;
}
$oRet = $oRemoteAttDef;
break;
}
return $oRet;
}
}
/**
* Abstract class implementing default filters for a DB column
*
* @package iTopORM
*/
class AttributeDBFieldVoid extends AttributeDefinition
{
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql"));
}
// To be overriden, used in GetSQLColumns
protected function GetSQLCol($bFullSpec = false)
{
return 'VARCHAR(255)'
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
protected function GetSQLColSpec()
{
$default = $this->ScalarToSQL($this->GetDefaultValue());
if (is_null($default))
{
$sRet = '';
}
else
{
if (is_numeric($default))
{
// Though it is a string in PHP, it will be considered as a numeric value in MySQL
// Then it must not be quoted here, to preserve the compatibility with the value returned by CMDBSource::GetFieldSpec
$sRet = " DEFAULT $default";
}
else
{
$sRet = " DEFAULT ".CMDBSource::Quote($default);
}
}
return $sRet;
}
public function GetEditClass()
{
return "String";
}
public function GetValuesDef()
{
return $this->Get("allowed_values");
}
public function GetPrerequisiteAttributes($sClass = null)
{
return $this->Get("depends_on");
}
public static function IsBasedOnDBColumns()
{
return true;
}
public static function IsScalar()
{
return true;
}
public function IsWritable()
{
return !$this->IsMagic();
}
public function GetSQLExpr()
{
return $this->Get("sql");
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->MakeRealValue("", $oHostObject);
}
public function IsNullAllowed()
{
return false;
}
//
protected function ScalarToSQL($value)
{
return $value;
} // format value as a valuable SQL literal (quoted outside)
public function GetSQLExpressions($sPrefix = '')
{
$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->Get("sql");
return $aColumns;
}
public function FromSQLToValue($aCols, $sPrefix = '')
{
$value = $this->MakeRealValue($aCols[$sPrefix.''], null);
return $value;
}
public function GetSQLValues($value)
{
$aValues = array();
$aValues[$this->Get("sql")] = $this->ScalarToSQL($value);
return $aValues;
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->Get("sql")] = $this->GetSQLCol($bFullSpec);
return $aColumns;
}
public function GetFilterDefinitions()
{
return array($this->GetCode() => new FilterFromAttribute($this));
}
public function GetBasicFilterOperators()
{
return array("=" => "equals", "!=" => "differs from");
}
public function GetBasicFilterLooseOperator()
{
return "=";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode)
{
case '!=':
return $this->GetSQLExpr()." != $sQValue";
break;
case '=':
default:
return $this->GetSQLExpr()." = $sQValue";
}
}
}
/**
* Base class for all kind of DB attributes, with the exception of external keys
*
* @package iTopORM
*/
class AttributeDBField extends AttributeDBFieldVoid
{
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed"));
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->MakeRealValue($this->Get("default_value"), $oHostObject);
}
public function IsNullAllowed()
{
return $this->Get("is_null_allowed");
}
}
/**
* Map an integer column to an attribute
*
* @package iTopORM
*/
class AttributeInteger extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "String";
}
protected function GetSQLCol($bFullSpec = false)
{
return "INT(11)".($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetValidationPattern()
{
return "^[0-9]+$";
}
public function GetBasicFilterOperators()
{
return array(
"!=" => "differs from",
"=" => "equals",
">" => "greater (strict) than",
">=" => "greater than",
"<" => "less (strict) than",
"<=" => "less than",
"in" => "in"
);
}
public function GetBasicFilterLooseOperator()
{
// Unless we implement an "equals approximately..." or "same order of magnitude"
return "=";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode)
{
case '!=':
return $this->GetSQLExpr()." != $sQValue";
break;
case '>':
return $this->GetSQLExpr()." > $sQValue";
break;
case '>=':
return $this->GetSQLExpr()." >= $sQValue";
break;
case '<':
return $this->GetSQLExpr()." < $sQValue";
break;
case '<=':
return $this->GetSQLExpr()." <= $sQValue";
break;
case 'in':
if (!is_array($value))
{
throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
}
return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')";
break;
case '=':
default:
return $this->GetSQLExpr()." = \"$value\"";
}
}
public function GetNullValue()
{
return null;
}
public function IsNull($proposedValue)
{
return is_null($proposedValue);
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue))
{
return null;
}
if ($proposedValue === '')
{
return null;
} // 0 is transformed into '' !
return (int)$proposedValue;
}
public function ScalarToSQL($value)
{
assert(is_numeric($value) || is_null($value));
return $value; // supposed to be an int
}
}
/**
* An external key for which the class is defined as the value of another attribute
*
* @package iTopORM
*/
class AttributeObjectKey extends AttributeDBFieldVoid
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('class_attcode', 'is_null_allowed'));
}
public function GetEditClass()
{
return "String";
}
protected function GetSQLCol($bFullSpec = false)
{
return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return 0;
}
public function IsNullAllowed()
{
return $this->Get("is_null_allowed");
}
public function GetBasicFilterOperators()
{
return parent::GetBasicFilterOperators();
}
public function GetBasicFilterLooseOperator()
{
return parent::GetBasicFilterLooseOperator();
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return parent::GetBasicFilterSQLExpr($sOpCode, $value);
}
public function GetNullValue()
{
return 0;
}
public function IsNull($proposedValue)
{
return ($proposedValue == 0);
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue))
{
return 0;
}
if ($proposedValue === '')
{
return 0;
}
if (MetaModel::IsValidObject($proposedValue))
{
/** @var \DBObject $proposedValue */
return $proposedValue->GetKey();
}
return (int)$proposedValue;
}
}
/**
* Display an integer between 0 and 100 as a percentage / horizontal bar graph
*
* @package iTopORM
*/
class AttributePercentage extends AttributeInteger
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
$iWidth = 5; // Total width of the percentage bar graph, in em...
$iValue = (int)$sValue;
if ($iValue > 100)
{
$iValue = 100;
}
else
{
if ($iValue < 0)
{
$iValue = 0;
}
}
if ($iValue > 90)
{
$sColor = "#cc3300";
}
else
{
if ($iValue > 50)
{
$sColor = "#cccc00";
}
else
{
$sColor = "#33cc00";
}
}
$iPercentWidth = ($iWidth * $iValue) / 100;
return "
$sValue %";
}
}
/**
* Map a decimal value column (suitable for financial computations) to an attribute
* internally in PHP such numbers are represented as string. Should you want to perform
* a calculation on them, it is recommended to use the BC Math functions in order to
* retain the precision
*
* @package iTopORM
*/
class AttributeDecimal extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('digits', 'decimals' /* including precision */));
}
public function GetEditClass()
{
return "String";
}
protected function GetSQLCol($bFullSpec = false)
{
return "DECIMAL(".$this->Get('digits').",".$this->Get('decimals').")".($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetValidationPattern()
{
$iNbDigits = $this->Get('digits');
$iPrecision = $this->Get('decimals');
$iNbIntegerDigits = $iNbDigits - $iPrecision - 1; // -1 because the first digit is treated separately in the pattern below
return "^[-+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$";
}
public function GetBasicFilterOperators()
{
return array(
"!=" => "differs from",
"=" => "equals",
">" => "greater (strict) than",
">=" => "greater than",
"<" => "less (strict) than",
"<=" => "less than",
"in" => "in"
);
}
public function GetBasicFilterLooseOperator()
{
// Unless we implement an "equals approximately..." or "same order of magnitude"
return "=";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode)
{
case '!=':
return $this->GetSQLExpr()." != $sQValue";
break;
case '>':
return $this->GetSQLExpr()." > $sQValue";
break;
case '>=':
return $this->GetSQLExpr()." >= $sQValue";
break;
case '<':
return $this->GetSQLExpr()." < $sQValue";
break;
case '<=':
return $this->GetSQLExpr()." <= $sQValue";
break;
case 'in':
if (!is_array($value))
{
throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
}
return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')";
break;
case '=':
default:
return $this->GetSQLExpr()." = \"$value\"";
}
}
public function GetNullValue()
{
return null;
}
public function IsNull($proposedValue)
{
return is_null($proposedValue);
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue))
{
return null;
}
if ($proposedValue === '')
{
return null;
}
return $this->ScalarToSQL($proposedValue);
}
public function ScalarToSQL($value)
{
assert(is_null($value) || preg_match('/'.$this->GetValidationPattern().'/', $value));
if (!is_null($value) && ($value !== ''))
{
$value = sprintf("%01.".$this->Get('decimals')."F", $value);
}
return $value; // null or string
}
}
/**
* Map a boolean column to an attribute
*
* @package iTopORM
*/
class AttributeBoolean extends AttributeInteger
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "Integer";
}
protected function GetSQLCol($bFullSpec = false)
{
return "TINYINT(1)".($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue))
{
return null;
}
if ($proposedValue === '')
{
return null;
}
if ((int)$proposedValue)
{
return true;
}
return false;
}
public function ScalarToSQL($value)
{
if ($value)
{
return 1;
}
return 0;
}
public function GetValueLabel($bValue)
{
if (is_null($bValue))
{
$sLabel = Dict::S('Core:'.get_class($this).'/Value:null');
}
else
{
$sValue = $bValue ? 'yes' : 'no';
$sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue);
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, true /*user lang*/);
}
return $sLabel;
}
public function GetValueDescription($bValue)
{
if (is_null($bValue))
{
$sDescription = Dict::S('Core:'.get_class($this).'/Value:null+');
}
else
{
$sValue = $bValue ? 'yes' : 'no';
$sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue.'+');
$sDescription = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue.'+', $sDefault,
true /*user lang*/);
}
return $sDescription;
}
public function GetAsHTML($bValue, $oHostObject = null, $bLocalize = true)
{
if (is_null($bValue))
{
$sRes = '';
}
elseif ($bLocalize)
{
$sLabel = $this->GetValueLabel($bValue);
$sDescription = $this->GetValueDescription($bValue);
// later, we could imagine a detailed description in the title
$sRes = "".parent::GetAsHtml($sLabel)."";
}
else
{
$sRes = $bValue ? 'yes' : 'no';
}
return $sRes;
}
public function GetAsXML($bValue, $oHostObject = null, $bLocalize = true)
{
if (is_null($bValue))
{
$sFinalValue = '';
}
elseif ($bLocalize)
{
$sFinalValue = $this->GetValueLabel($bValue);
}
else
{
$sFinalValue = $bValue ? 'yes' : 'no';
}
$sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize);
return $sRes;
}
public function GetAsCSV(
$bValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
) {
if (is_null($bValue))
{
$sFinalValue = '';
}
elseif ($bLocalize)
{
$sFinalValue = $this->GetValueLabel($bValue);
}
else
{
$sFinalValue = $bValue ? 'yes' : 'no';
}
$sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize);
return $sRes;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\SelectField';
}
/**
* @param \DBObject $oObject
* @param \Combodo\iTop\Form\Field\SelectField $oFormField
*
* @return \Combodo\iTop\Form\Field\SelectField
* @throws \CoreException
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null)
{
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
$oFormField->SetChoices(array('yes' => $this->GetValueLabel(true), 'no' => $this->GetValueLabel(false)));
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
public function GetEditValue($value, $oHostObj = null)
{
if (is_null($value))
{
return '';
}
else
{
return $this->GetValueLabel($value);
}
}
/**
* Helper to get a value that will be JSON encoded
* The operation is the opposite to FromJSONToValue
*
* @param $value
*
* @return bool
*/
public function GetForJSON($value)
{
return (bool)$value;
}
public function MakeValueFromString(
$sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
$sAttributeQualifier = null
) {
$sInput = strtolower(trim($sProposedValue));
if ($bLocalizedValue)
{
switch ($sInput)
{
case '1': // backward compatibility
case $this->GetValueLabel(true):
$value = true;
break;
case '0': // backward compatibility
case 'no':
case $this->GetValueLabel(false):
$value = false;
break;
default:
$value = null;
}
}
else
{
switch ($sInput)
{
case '1': // backward compatibility
case 'yes':
$value = true;
break;
case '0': // backward compatibility
case 'no':
$value = false;
break;
default:
$value = null;
}
}
return $value;
}
}
/**
* Map a varchar column (size < ?) to an attribute
*
* @package iTopORM
*/
class AttributeString extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "String";
}
protected function GetSQLCol($bFullSpec = false)
{
return 'VARCHAR(255)'
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetValidationPattern()
{
$sPattern = $this->GetOptional('validation_pattern', '');
if (empty($sPattern))
{
return parent::GetValidationPattern();
}
else
{
return $sPattern;
}
}
public function CheckFormat($value)
{
$sRegExp = $this->GetValidationPattern();
if (empty($sRegExp))
{
return true;
}
else
{
$sRegExp = str_replace('/', '\\/', $sRegExp);
return preg_match("/$sRegExp/", $value);
}
}
public function GetMaxSize()
{
return 255;
}
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)
{
return ($proposedValue == '');
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue))
{
return '';
}
return (string)$proposedValue;
}
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->GetHostClass(),
'attribute' => $this->GetCode()
));
}
return $value;
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
) {
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
return $sTextQualifier.$sEscaped.$sTextQualifier;
}
public function GetDisplayStyle()
{
return $this->GetOptional('display_style', 'select');
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\StringField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null)
{
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
}
/**
* An attribute that matches an object class
*
* @package iTopORM
*/
class AttributeClass extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM;
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("class_category", "more_values"));
}
public function __construct($sCode, $aParams)
{
$this->m_sCode = $sCode;
$aParams["allowed_values"] = new ValueSetEnumClasses($aParams['class_category'], $aParams['more_values']);
parent::__construct($sCode, $aParams);
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
$sDefault = parent::GetDefaultValue($oHostObject);
if (!$this->IsNullAllowed() && $this->IsNull($sDefault))
{
// For this kind of attribute specifying null as default value
// is authorized even if null is not allowed
// Pick the first one...
$aClasses = $this->GetAllowedValues();
$sDefault = key($aClasses);
}
return $sDefault;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (empty($sValue))
{
return '';
}
return MetaModel::GetName($sValue);
}
public function RequiresIndex()
{
return true;
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
}
/**
* An attribute that matches a class state
*
* @package iTopORM
*/
class AttributeClassState extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('class_field'));
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
if (isset($aArgs['this']))
{
$oHostObj = $aArgs['this'];
$sTargetClass = $this->Get('class_field');
$sClass = $oHostObj->Get($sTargetClass);
$aAllowedStates = array();
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass)
{
$aValues = MetaModel::EnumStates($sChildClass);
foreach (array_keys($aValues) as $sState)
{
$aAllowedStates[$sState] = $sState.' ('.MetaModel::GetStateLabel($sChildClass, $sState).')';
}
}
return $aAllowedStates;
}
return null;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (empty($sValue))
{
return '';
}
if (!empty($oHostObject))
{
$sTargetClass = $this->Get('class_field');
$sClass = $oHostObject->Get($sTargetClass);
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass)
{
$aValues = MetaModel::EnumStates($sChildClass);
if (in_array($sValue, $aValues))
{
$sHTML = ''.$sValue.'';
return $sHTML;
}
}
}
return $sValue;
}
}
/**
* An attibute that matches one of the language codes availables in the dictionnary
*
* @package iTopORM
*/
class AttributeApplicationLanguage extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
}
public function __construct($sCode, $aParams)
{
$this->m_sCode = $sCode;
$aAvailableLanguages = Dict::GetLanguages();
$aLanguageCodes = array();
foreach($aAvailableLanguages as $sLangCode => $aInfo)
{
$aLanguageCodes[$sLangCode] = $aInfo['description'].' ('.$aInfo['localized_description'].')';
}
$aParams["allowed_values"] = new ValueSetEnum($aLanguageCodes);
parent::__construct($sCode, $aParams);
}
public function RequiresIndex()
{
return true;
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
}
/**
* The attribute dedicated to the finalclass automatic attribute
*
* @package iTopORM
*/
class AttributeFinalClass extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
public $m_sValue;
public function __construct($sCode, $aParams)
{
$this->m_sCode = $sCode;
$aParams["allowed_values"] = null;
parent::__construct($sCode, $aParams);
$this->m_sValue = $this->Get("default_value");
}
public function IsWritable()
{
return false;
}
public function IsMagic()
{
return true;
}
public function RequiresIndex()
{
return true;
}
public function SetFixedValue($sValue)
{
$this->m_sValue = $sValue;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->m_sValue;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (empty($sValue))
{
return '';
}
if ($bLocalize)
{
return MetaModel::GetName($sValue);
}
else
{
return $sValue;
}
}
/**
* An enum can be localized
*
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return mixed|null|string
* @throws \CoreException
* @throws \OQLException
*/
public function MakeValueFromString(
$sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
$sAttributeQualifier = null
) {
if ($bLocalizedValue)
{
// Lookup for the value matching the input
//
$sFoundValue = null;
$aRawValues = self::GetAllowedValues();
if (!is_null($aRawValues))
{
foreach($aRawValues as $sKey => $sValue)
{
if ($sProposedValue == $sValue)
{
$sFoundValue = $sKey;
break;
}
}
}
if (is_null($sFoundValue))
{
return null;
}
return $this->MakeRealValue($sFoundValue, null);
}
else
{
return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue,
$sAttributeQualifier);
}
}
// Because this is sometimes used to get a localized/string version of an attribute...
public function GetEditValue($sValue, $oHostObj = null)
{
if (empty($sValue))
{
return '';
}
return MetaModel::GetName($sValue);
}
/**
* Helper to get a value that will be JSON encoded
* The operation is the opposite to FromJSONToValue
*
* @param $value
*
* @return string
*/
public function GetForJSON($value)
{
// JSON values are NOT localized
return $value;
}
/**
* @param $value
* @param string $sSeparator
* @param string $sTextQualifier
* @param \DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return string
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function GetAsCSV(
$value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
) {
if ($bLocalize && $value != '')
{
$sRawValue = MetaModel::GetName($value);
}
else
{
$sRawValue = $value;
}
return parent::GetAsCSV($sRawValue, $sSeparator, $sTextQualifier, null, false, $bConvertToPlainText);
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
if (empty($value))
{
return '';
}
if ($bLocalize)
{
$sRawValue = MetaModel::GetName($value);
}
else
{
$sRawValue = $value;
}
return Str::pure2xml($sRawValue);
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
public function GetValueLabel($sValue)
{
if (empty($sValue))
{
return '';
}
return MetaModel::GetName($sValue);
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$aRawValues = MetaModel::EnumChildClasses($this->GetHostClass(), ENUM_CHILD_CLASSES_ALL);
$aLocalizedValues = array();
foreach($aRawValues as $sClass)
{
$aLocalizedValues[$sClass] = MetaModel::GetName($sClass);
}
return $aLocalizedValues;
}
/**
* @return bool
* @since 2.7
*/
public function CopyOnAllTables()
{
$sClass = self::GetHostClass();
if (MetaModel::IsLeafClass($sClass))
{
// Leaf class, no finalclass
return false;
}
return true;
}
}
/**
* Map a varchar column (size < ?) to an attribute that must never be shown to the user
*
* @package iTopORM
*/
class AttributePassword extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
*
* @param string $sCode
* @param array $aParams
*
* @throws \Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "Password";
}
protected function GetSQLCol($bFullSpec = false)
{
return "VARCHAR(64)"
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetMaxSize()
{
return 64;
}
public function GetFilterDefinitions()
{
// Note: due to this, you will get an error if a password is being declared as a search criteria (see ZLists)
// not allowed to search on passwords!
return array();
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (strlen($sValue) == 0)
{
return '';
}
else
{
return '******';
}
}
public function IsPartOfFingerprint()
{
return false;
} // Cannot reliably compare two encrypted passwords since the same password will be encrypted in diffferent manners depending on the random 'salt'
}
/**
* Map a text column (size < 255) to an attribute that is encrypted in the database
* The encryption is based on a key set per iTop instance. Thus if you export your
* database (in SQL) to someone else without providing the key at the same time
* the encrypted fields will remain encrypted
*
* @package iTopORM
*/
class AttributeEncryptedString extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
static $sKey = null; // Encryption key used for all encrypted fields
static $sLibrary = null; // Encryption library used for all encrypted fields
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
if (self::$sKey == null)
{
self::$sKey = MetaModel::GetConfig()->GetEncryptionKey();
}
if (self::$sLibrary == null)
{
self::$sLibrary = MetaModel::GetConfig()->GetEncryptionLibrary();
}
}
/**
* When the attribute definitions are stored in APC cache:
* 1) The static class variable $sKey is NOT serialized
* 2) The object's constructor is NOT called upon wakeup
* 3) mcrypt may crash the server if passed an empty key !!
*
* So let's restore the key (if needed) when waking up
**/
public function __wakeup()
{
if (self::$sKey == null)
{
self::$sKey = MetaModel::GetConfig()->GetEncryptionKey();
}
if (self::$sLibrary == null)
{
self::$sLibrary = MetaModel::GetConfig()->GetEncryptionLibrary();
}
}
protected function GetSQLCol($bFullSpec = false)
{
return "TINYBLOB";
}
public function GetMaxSize()
{
return 255;
}
public function GetFilterDefinitions()
{
// Note: due to this, you will get an error if a an encrypted field is declared as a search criteria (see ZLists)
// not allowed to search on encrypted fields !
return array();
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue))
{
return null;
}
return (string)$proposedValue;
}
/**
* Decrypt the value when reading from the database
*
* @param array $aCols
* @param string $sPrefix
*
* @return string
* @throws \Exception
*/
public function FromSQLToValue($aCols, $sPrefix = '')
{
$oSimpleCrypt = new SimpleCrypt(self::$sLibrary);
$sValue = $oSimpleCrypt->Decrypt(self::$sKey, $aCols[$sPrefix]);
return $sValue;
}
/**
* Encrypt the value before storing it in the database
*
* @param $value
*
* @return array
* @throws \Exception
*/
public function GetSQLValues($value)
{
$oSimpleCrypt = new SimpleCrypt(self::$sLibrary);
$encryptedValue = $oSimpleCrypt->Encrypt(self::$sKey, $value);
$aValues = array();
$aValues[$this->Get("sql")] = $encryptedValue;
return $aValues;
}
}
/**
* Wiki formatting - experimental
*
* [[:|