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\n"; } $sRes .= "\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 ''; 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 * * [[:|