diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 98e6c322fa..fcc2c3cb1b 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -4,17 +4,6 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
-use Combodo\iTop\Application\UI\Base\Component\FieldBadge\FieldBadgeUIBlockFactory;
-use Combodo\iTop\Application\UI\Links\Set\BlockLinkSetDisplayAsProperty;
-use Combodo\iTop\Application\WebPage\WebPage;
-use Combodo\iTop\Form\Field\LabelField;
-use Combodo\iTop\Form\Field\TextAreaField;
-use Combodo\iTop\Form\Form;
-use Combodo\iTop\Form\Validator\CustomRegexpValidator;
-use Combodo\iTop\Renderer\BlockRenderer;
-use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
-use Combodo\iTop\Service\Links\LinkSetModel;
-
require_once('MyHelpers.class.inc.php');
require_once('ormdocument.class.inc.php');
require_once('ormstopwatch.class.inc.php');
@@ -27,13782 +16,3 @@
require_once('customfieldshandler.class.inc.php');
require_once('ormcustomfieldsvalue.class.inc.php');
require_once('datetimeformat.class.inc.php');
-
-/**
- * MissingColumnException - sent if an attribute is being created but the column is missing in the row
- *
- * @package iTopORM
- */
-class MissingColumnException extends Exception
-{
-}
-
-/**
- * add some description here...
- *
- * @package iTopORM
- */
-define('EXTKEY_RELATIVE', 1);
-
-/**
- * add some description here...
- *
- * @package iTopORM
- */
-define('EXTKEY_ABSOLUTE', 2);
-
-/**
- * Propagation of the deletion through an external key - ask the user to delete the referencing object
- *
- * @package iTopORM
- */
-define('DEL_MANUAL', 1);
-
-/**
- * Propagation of the deletion through an external key - remove linked objects if ext key has is_null_allowed=false
- *
- * @package iTopORM
- */
-define('DEL_AUTO', 2);
-/**
- * Fully silent delete... not yet implemented
- */
-define('DEL_SILENT', 2);
-/**
- * For HierarchicalKeys only: move all the children up one level automatically
- */
-define('DEL_MOVEUP', 3);
-
-/**
- * Do nothing at least automatically
- */
-define('DEL_NONE', 4);
-
-
-/**
- * For Link sets: tracking_level
- *
- * @package iTopORM
- */
-define('ATTRIBUTE_TRACKING_NONE', 0); // Do not track changes of the attribute
-define('ATTRIBUTE_TRACKING_ALL', 3); // Do track all changes of the attribute
-define('LINKSET_TRACKING_NONE', 0); // Do not track changes in the link set
-define('LINKSET_TRACKING_LIST', 1); // Do track added/removed items
-define('LINKSET_TRACKING_DETAILS', 2); // Do track modified items
-define('LINKSET_TRACKING_ALL', 3); // Do track added/removed/modified items
-
-define('LINKSET_EDITMODE_NONE', 0); // The linkset cannot be edited at all from inside this object
-define('LINKSET_EDITMODE_ADDONLY', 1); // The only possible action is to open a new window to create a new object
-define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
-define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
-define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
-
-define('LINKSET_EDITWHEN_NEVER', 0); // The linkset cannot be edited at all from inside this object
-define('LINKSET_EDITWHEN_ON_HOST_EDITION', 1); // The only possible action is to open a new window to create a new object
-define('LINKSET_EDITWHEN_ON_HOST_DISPLAY', 2); // Show the usual 'Actions' popup menu
-define('LINKSET_EDITWHEN_ALWAYS', 3); // Show the usual 'Actions' popup menu
-
-
-define('LINKSET_DISPLAY_STYLE_PROPERTY', 'property');
-define('LINKSET_DISPLAY_STYLE_TAB', 'tab');
-
-/**
- * Attributes implementing this interface won't be accepted as `group by` field
- *
- * @since 2.7.4 N°3473
- */
-interface iAttributeNoGroupBy
-{
- //no method, just a contract on implement
-}
-
-/**
- * Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.)
- *
- * @package iTopORM
- */
-abstract class AttributeDefinition
-{
- const SEARCH_WIDGET_TYPE_RAW = 'raw';
- const SEARCH_WIDGET_TYPE_STRING = 'string';
- const SEARCH_WIDGET_TYPE_NUMERIC = 'numeric';
- const SEARCH_WIDGET_TYPE_ENUM = 'enum';
- const SEARCH_WIDGET_TYPE_EXTERNAL_KEY = 'external_key';
- const SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY = 'hierarchical_key';
- const SEARCH_WIDGET_TYPE_EXTERNAL_FIELD = 'external_field';
- const SEARCH_WIDGET_TYPE_DATE_TIME = 'date_time';
- const SEARCH_WIDGET_TYPE_DATE = 'date';
- const SEARCH_WIDGET_TYPE_SET = 'set';
- const SEARCH_WIDGET_TYPE_TAG_SET = 'tag_set';
-
-
- const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
-
- const INDEX_LENGTH = 95;
-
- protected $aCSSClasses;
-
- public function GetType()
- {
- return Dict::S('Core:'.get_class($this));
- }
-
- public function GetTypeDesc()
- {
- return Dict::S('Core:'.get_class($this).'+');
- }
-
- abstract public function GetEditClass();
-
- /**
- * @return array Css classes
- * @since 3.1.0 N°3190
- */
- public function GetCssClasses(): array
- {
- return $this->aCSSClasses;
- }
-
- /**
- * Return the search widget type corresponding to this attribute
- *
- * @return string
- */
- public function GetSearchType()
- {
- return static::SEARCH_WIDGET_TYPE;
- }
-
- /**
- * @return bool
- */
- public function IsSearchable()
- {
- return $this->GetSearchType() != static::SEARCH_WIDGET_TYPE_RAW;
- }
-
- /** @var string */
- protected $m_sCode;
- /** @var array */
- protected $m_aParams;
- /** @var string */
- protected $m_sHostClass = '!undefined!';
-
- public function Get($sParamName)
- {
- return $this->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|string true for no errors, false or error message otherwise
- */
- public function CheckValue(DBObject $oHostObject, $value)
- {
- // later: factorize here the cases implemented into DBObject
- return true;
- }
-
- // table, key field, name field
-
- 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 can be used in bulk modify.
- *
- * @return bool
- * @since 3.1.0 N°3190
- *
- */
- public static function IsBulkModifyCompatible(): bool
- {
- return static::IsScalar();
- }
-
- /**
- * 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 LoadFromClassTables()
- {
- return true;
- }
-
- /**
- * Write attribute values outside the current class tables
- *
- * @param \DBObject $oHostObject
- *
- * @return void
- * @since 3.1.0 Method creation, to offer a generic method for all attributes - before we were calling directly \AttributeCustomFields::WriteValue
- *
- * @used-by \DBObject::WriteExternalAttributes()
- */
- public function WriteExternalValues(DBObject $oHostObject): void
- {
- }
-
- /**
- * Read the data from where it has been stored (outside the current class tables).
- * This verb must be implemented as soon as LoadFromClassTables returns false and LoadInObject returns true
- *
- * @param DBObject $oHostObject
- *
- * @return mixed|null
- * @since 3.1.0
- */
- public function ReadExternalValues(DBObject $oHostObject)
- {
- return null;
- }
-
- /**
- * Cleanup data upon object deletion (outside the current class tables)
- * object id still available here
- *
- * @param \DBObject $oHostObject
- *
- * @since 3.1.0
- */
- public function DeleteExternalValues(DBObject $oHostObject): void
- {
- }
-
- /**
- * @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;
- }
-
- /**
- * @return bool True if the attribute has a description {@see \AttributeDefinition::GetDescription()}
- * @throws \Exception
- * @since 3.1.0
- */
- public function HasDescription(): bool
- {
- return utils::IsNotNullOrEmptyString($this->GetDescription());
- }
-
- /**
- * @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);
- }
-
- /**
- * @param mixed $proposedValue
- *
- * @return bool True if $proposedValue is an actual value set in the attribute, false is the attribute remains "empty"
- * @since 3.0.3, 3.1.0 N°5784
- */
- public function HasAValue($proposedValue): bool
- {
- // Default implementation, we don't really know what type $proposedValue will be
- return !(is_null($proposedValue));
- }
-
- /**
- * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!
- *
- * @param mixed $proposedValue
- * @param \DBObject $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;
- }
-
- 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 GetMagicFields()
- {
- return [];
- }
-
- 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
- *
- * @see FromJSONToValue for the reverse operation
- *
- * @param mixed $value field value
- *
- * @return string|array PHP struct that can be properly encoded
- *
- */
- 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. This way the attribute itself handles the transformation from the JSON structure to the expected data (the one that
- * needs to be used in the {@see \DBObject::Set()} method).
- *
- * Note that for CSV and XML this isn't done yet (no delegation to the attribute but switch/case inside controllers) :/
- *
- * @see GetForJSON for the reverse operation
- *
- * @param string $json JSON encoded value
- *
- * @return mixed JSON decoded data, depending on the attribute type
- *
- */
- public function FromJSONToValue($json)
- {
- // Pass-through 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|null $oFormField
- *
- * @return \Combodo\iTop\Form\Field\Field
- * @throws \CoreException
- * @throws \Exception
- *
- * @noinspection PhpMissingReturnTypeInspection
- * @noinspection PhpMissingParamTypeInspection
- * @noinspection ReturnTypeCanBeDeclaredInspection
- */
- 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 CustomRegexpValidator($this->GetValidationPattern()));
- }
-
- // Description
- $sAttDescription = $this->GetDescription();
- if (!empty($sAttDescription)) {
- $oFormField->SetDescription($this->GetDescription());
- }
-
- // Metadata
- $oFormField->AddMetadata('attribute-code', $this->GetCode());
- $oFormField->AddMetadata('attribute-type', get_class($this));
- $oFormField->AddMetadata('attribute-label', $this->GetLabel());
- // - Attribute flags
- $aPossibleAttFlags = MetaModel::EnumPossibleAttributeFlags();
- foreach ($aPossibleAttFlags as $sFlagCode => $iFlagValue) {
- // Note: Skip normal flag as we don't need it.
- if ($sFlagCode === 'normal') {
- continue;
- }
- $sFormattedFlagCode = str_ireplace('_', '-', $sFlagCode);
- $sFormattedFlagValue = (($iFlags & $iFlagValue) === $iFlagValue) ? 'true' : 'false';
- $oFormField->AddMetadata('attribute-flag-'.$sFormattedFlagCode, $sFormattedFlagValue);
- }
- // - Value raw
- if ($this::IsScalar()) {
- $oFormField->AddMetadata('value-raw', (string)$oObject->Get($this->GetCode()));
- }
-
- // We don't want to invalidate field because of old untouched values that are no longer valid
- $aModifiedAttCodes = $oObject->ListChanges();
- $bAttributeHasBeenModified = array_key_exists($this->GetCode(), $aModifiedAttCodes);
- if (false === $bAttributeHasBeenModified) {
- $oFormField->SetValidationDisabled(true);
- }
-
- 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);
- }
-
- /**
- * GetAllowedValuesForSelect is the same as GetAllowedValues except for field with obsolescence flag
- * @param array $aArgs
- * @param string $sContains
- *
- * @return array|null
- * @throws \CoreException
- * @throws \OQLException
- */
- public function GetAllowedValuesForSelect($aArgs = array(), $sContains = '')
- {
- return $this->GetAllowedValues($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;
- }
-
- /**
- * @param \DBObject $oObject
- * @param mixed $original
- * @param mixed $value
- *
- * @throws \ArchivedObjectException
- * @throws \CoreCannotSaveObjectException
- * @throws \CoreException if cannot create object
- * @throws \CoreUnexpectedValue
- * @throws \CoreWarning
- * @throws \MySQLException
- * @throws \OQLException
- *
- * @uses GetChangeRecordAdditionalData
- * @uses GetChangeRecordClassName
- *
- * @since 3.1.0 N°6042
- */
- public function RecordAttChange(DBObject $oObject, $original, $value): void
- {
- /** @var CMDBChangeOp $oMyChangeOp */
- $oMyChangeOp = MetaModel::NewObject($this->GetChangeRecordClassName());
- $oMyChangeOp->Set("objclass", get_class($oObject));
- $oMyChangeOp->Set("objkey", $oObject->GetKey());
- $oMyChangeOp->Set("attcode", $this->GetCode());
-
- $this->GetChangeRecordAdditionalData($oMyChangeOp, $oObject, $original, $value);
-
- $oMyChangeOp->DBInsertNoReload();
- }
-
- /**
- * Add attribute specific information in the {@link \CMDBChangeOp} instance
- *
- * @param \CMDBChangeOp $oMyChangeOp
- * @param \DBObject $oObject
- * @param $original
- * @param $value
- *
- * @return void
- * @used-by RecordAttChange
- */
- protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
- {
- $oMyChangeOp->Set("oldvalue", $original);
- $oMyChangeOp->Set("newvalue", $value);
- }
-
- /**
- * @return string name of the children of {@link \CMDBChangeOp} class to use for the history record
- * @used-by RecordAttChange
- */
- protected function GetChangeRecordClassName(): string
- {
- return CMDBChangeOpSetAttributeScalar::class;
- }
-
- /**
- * 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;
- }
-
- /*
- * return string
- */
- public function GetRenderForDataTable(string $sClassAlias) :string
- {
- $sRenderFunction = "return data;";
- return $sRenderFunction;
- }
-}
-
-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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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 '';
- }
-
- /**
- * @inheritDoc
- */
- public function HasAValue($proposedValue): bool
- {
- // Always return false for now, we don't consider a custom version of a dashboard
- return false;
- }
-}
-
-/**
- * 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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @param string $sCode
- * @param array $aParams
- *
- * @throws \Exception
- * @noinspection SenselessProxyMethodInspection
- */
- public function __construct($sCode, $aParams)
- {
- parent::__construct($sCode, $aParams);
- $this->aCSSClasses[] = 'attribute-set';
- }
-
- 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";
- }
-
- /** @inheritDoc */
- public static function IsBulkModifyCompatible(): bool
- {
- return false;
- }
-
- public function IsWritable()
- {
- return true;
- }
-
- public static function IsLinkSet()
- {
- return true;
- }
-
- public function IsIndirect()
- {
- return false;
- }
-
- public function GetValuesDef()
- {
- $oValSetDef = $this->Get("allowed_values");
- if (!$oValSetDef) {
- // Let's propose every existing value
- $oValSetDef = new ValueSetObjects('SELECT '.LinkSetModel::GetTargetClass($this));
- }
-
- return $oValSetDef;
- }
-
- public function GetEditValue($value, $oHostObj = null)
- {
- /** @var ormLinkSet $value * */
- if ($value->Count() === 0) {
- return '';
- }
-
- /** Return linked objects key as string **/
- $aValues = $value->GetValues();
-
- return implode(' ', $aValues);
- }
-
- 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'));
- }
-
- /**
- * @return string see LINKSET_EDITMODE_* constants
- */
- public function GetEditMode()
- {
- return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
- }
-
- /**
- * @return int see LINKSET_EDITWHEN_* constants
- * @since 3.1.1 3.2.0 N°6385
- */
- public function GetEditWhen(): int
- {
- return $this->GetOptional('edit_when', LINKSET_EDITWHEN_ALWAYS);
- }
-
- /**
- * @return string see LINKSET_DISPLAY_STYLE_* constants
- * @since 3.1.0 N°3190
- */
- public function GetDisplayStyle()
- {
- $sDisplayStyle = $this->GetOptional('display_style', LINKSET_DISPLAY_STYLE_TAB);
- if ($sDisplayStyle === '') {
- $sDisplayStyle = LINKSET_DISPLAY_STYLE_TAB;
- }
-
- return $sDisplayStyle;
- }
-
- /**
- * Indicates if the current Attribute has constraints (php constraints or datamodel constraints)
- * @return bool true if Attribute has constraints
- * @since 3.1.0 N°6228
- */
- public function HasPHPConstraint(): bool
- {
- return $this->GetOptional('with_php_constraint', false);
- }
-
- /**
- * @return bool true if Attribute has computation (DB_LINKS_CHANGED event propagation, `with_php_computation` attribute xml property), false otherwise
- * @since 3.1.1 3.2.0 N°6228
- */
- public function HasPHPComputation(): bool
- {
- return $this->GetOptional('with_php_computation', false);
- }
-
- 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 '';
- }
-
- /** @inheritDoc * */
- public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
- {
- if($this->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_TAB){
- return $this->GetAsHTMLForTab($sValue, $oHostObject, $bLocalize);
- }
- else{
- return $this->GetAsHTMLForProperty($sValue, $oHostObject, $bLocalize);
- }
- }
-
- public function GetAsHTMLForTab($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;
- }
-
- public function GetAsHTMLForProperty($sValue, $oHostObject = null, $bLocalize = true): string
- {
- try {
-
- /** @var ormLinkSet $sValue */
- if (is_null($sValue) || $sValue->Count() === 0) {
- return '';
- }
-
- $oLinkSetBlock = new BlockLinkSetDisplayAsProperty($this->GetCode(), $this, $sValue);
-
- return ConsoleBlockRenderer::RenderBlockTemplates($oLinkSetBlock);
- }
- catch (Exception $e) {
- $sMessage = "Error while displaying attribute {$this->GetCode()}";
- IssueLog::Error($sMessage, IssueLog::CHANNEL_DEFAULT, [
- 'host_object_class' => $this->GetHostClass(),
- 'host_object_key' => $oHostObject->GetKey(),
- 'attribute' => $this->GetCode(),
- ]);
-
- return $sMessage;
- }
- }
-
- /**
- * @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 '
';
-
- 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()] = 'MEDIUMTEXT'.CMDBSource::GetSqlStringColumnDefinition();
-
- 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;
- }
-
- /**
- * @inheritDoc
- *
- * @param \ormLinkSet $value
- */
- 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;
- }
-
- /**
- * @inheritDoc
- *
- * @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->SetLinkedClass($this->GetLinkedClass());
- $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 properties
- $aAttributesToDisplay = array();
- foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) {
- $oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay);
- $aAttributesToDisplay[$sAttCodeToDisplay] = [
- 'att_code' => $sAttCodeToDisplay,
- 'label' => $oAttDefToDisplay->GetLabel(),
- ];
- }
- $oFormField->SetAttributesToDisplay($aAttributesToDisplay);
-
- // Append lnk attributes (filtered from zlist)
- if ($this->IsIndirect()) {
- $aLnkAttDefToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($this->m_sHostClass, $this->m_sCode);
- $aLnkAttributesToDisplay = array();
- foreach ($aLnkAttDefToDisplay as $oLnkAttDefToDisplay) {
- $aLnkAttributesToDisplay[$oLnkAttDefToDisplay->GetCode()] = [
- 'att_code' => $oLnkAttDefToDisplay->GetCode(),
- 'label' => $oLnkAttDefToDisplay->GetLabel(),
- 'mandatory' => !$oLnkAttDefToDisplay->IsNullAllowed(),
- ];
- }
- $oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay);
- }
-
- parent::MakeFormField($oObject, $oFormField);
-
- return $oFormField;
- }
-
- public function IsPartOfFingerprint()
- {
- return false;
- }
-
- /**
- * @inheritDoc
- * @param \ormLinkSet $proposedValue
- */
- public function HasAValue($proposedValue): bool
- {
- // Protection against wrong value type
- if (false === ($proposedValue instanceof ormLinkSet)) {
- return parent::HasAValue($proposedValue);
- }
-
- // We test if there is at least 1 item in the linkset (new or existing), not if an item is being added to it.
- return $proposedValue->Count() > 0;
- }
-
- /**
- * SearchSpecificLabel.
- *
- * @param string $sDictEntrySuffix
- * @param string $sDefault
- * @param bool $bUserLanguageOnly
- * @param ...$aArgs
- * @return string
- * @since 3.1.0
- */
- public function SearchSpecificLabel(string $sDictEntrySuffix, string $sDefault, bool $bUserLanguageOnly, ...$aArgs): string
- {
- try {
- $sNextClass = $this->m_sHostClass;
-
- do {
- $sKey = "Class:{$sNextClass}/Attribute:{$this->m_sCode}/{$sDictEntrySuffix}";
- if (Dict::S($sKey, null, $bUserLanguageOnly) !== $sKey) {
- return Dict::Format($sKey, ...$aArgs);
- }
- $sNextClass = MetaModel::GetParentClass($sNextClass);
- } while ($sNextClass !== null);
-
- if (Dict::S($sDictEntrySuffix, null, $bUserLanguageOnly) !== $sKey) {
- return Dict::Format($sDictEntrySuffix, ...$aArgs);
- } else {
- return $sDefault;
- }
- } catch (Exception $e) {
- ExceptionLog::LogException($e);
- return $sDefault;
- }
- }
-}
-
-/**
- * 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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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;
- }
-
- /** @inheritDoc */
- public static function IsBulkModifyCompatible(): bool
- {
- return true;
- }
-
-}
-
-/**
- * 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 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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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);
- }
-
- /**
- * @inheritDoc
- */
- public function HasAValue($proposedValue): bool
- {
- return utils::IsNotNullOrEmptyString($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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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);
- }
-
- /**
- * @inheritDoc
- */
- public function HasAValue($proposedValue): bool
- {
- return ((int) $proposedValue) !== 0;
- }
-
- /**
- * @inheritDoc
- *
- * @param int|DBObject $proposedValue Object key or valid ({@see MetaModel::IsValidObject()}) datamodel object
- */
- public function MakeRealValue($proposedValue, $oHostObj)
- {
- if (is_null($proposedValue))
- {
- return 0;
- }
- if ($proposedValue === '')
- {
- return 0;
- }
- if (MetaModel::IsValidObject($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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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;
-
- return "^[\-\+]?\d{1,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$";
- }
-
- /**
- * @inheritDoc
- * @since 3.2.0
- */
- public function CheckFormat($value)
- {
- $sRegExp = $this->GetValidationPattern();
- return preg_match("/$sRegExp/", $value);
- }
-
- 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);
- }
-
- /**
- * @inheritDoc
- */
- public function HasAValue($proposedValue): bool
- {
- return utils::IsNotNullOrEmptyString($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("%1.".$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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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);
- }
- }
-
- public function GetForJSON($value)
- {
- return (bool)$value;
- }
-
- public function MakeValueFromString(
- $sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
- $sAttributeQualifier = null
- ) {
- $sInput = mb_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;
- }
-
- public function RecordAttChange(DBObject $oObject, $original, $value): void
- {
- parent::RecordAttChange($oObject, $original ? 1 : 0, $value ? 1 : 0);
- }
-
- protected function GetChangeRecordClassName(): string
- {
- return CMDBChangeOpSetAttributeScalar::class;
- }
-
- public function GetAllowedValues($aArgs = array(), $sContains = '') : array
- {
- return [
- 0 => $this->GetValueLabel(false),
- 1 => $this->GetValueLabel(true)
- ];
- }
-
- public function GetDisplayStyle()
- {
- return $this->GetOptional('display_style', 'select');
- }
-}
-
-/**
- * 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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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 == '');
- }
-
- /**
- * @inheritDoc
- */
- public function HasAValue($proposedValue): bool
- {
- return utils::IsNotNullOrEmptyString($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;
- }
-
- /**
- * @param array $aArgs
- * @param string $sContains
- *
- * @return array|null
- * @throws \CoreException
- */
- public function GetAllowedValues($aArgs = array(), $sContains = '')
- {
- $oValSetDef = $this->GetValuesDef();
- if (!$oValSetDef) {
- return null;
- }
-
- $aListClass = $oValSetDef->GetValues($aArgs, $sContains);
- /* @since 3.3.0 remove elements in class_exclusion_list*/
- $sClassExclusionList = $this->GetOptional('class_exclusion_list',null);
- if (!empty($sClassExclusionList)) {
- foreach (explode(',', $sClassExclusionList) as $sClassName) {
- unset($aListClass[trim($sClassName)]);
- }
- }
-
- return $aListClass;
- }
-
- 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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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))
- {
- $sLabelForHtmlAttribute = utils::EscapeHtml($sValue.' ('.MetaModel::GetStateLabel($sChildClass, $sValue).')');
- $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'].')';
- }
-
- // N°6462 This should be sorted directly in \Dict during the compilation but we can't for 2 reasons:
- // - Additional languages can be added on the fly even though it is not recommended
- // - Formatting is done at run time (just above)
- natcasesort($aLanguageCodes);
-
- $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);
- }
-
- 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.0 N°2272 OQL perf finalclass in all intermediary tables
- */
- 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 implements iAttributeNoGroupBy
-{
- 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
- * @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
- *
- * @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 GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
- {
- if (utils::IsNullOrEmptyString($sValue))
- {
- 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 implements iAttributeNoGroupBy
-{
- const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
-
- protected function GetSQLCol($bFullSpec = false)
- {
- return "TINYBLOB";
- }
-
- public function GetMaxSize()
- {
- return 255;
- }
-
- 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(MetaModel::GetConfig()->GetEncryptionLibrary());
- $sValue = $oSimpleCrypt->Decrypt(MetaModel::GetConfig()->GetEncryptionKey(), $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(MetaModel::GetConfig()->GetEncryptionLibrary());
- $encryptedValue = $oSimpleCrypt->Encrypt(MetaModel::GetConfig()->GetEncryptionKey(), $value);
-
- $aValues = array();
- $aValues[$this->Get("sql")] = $encryptedValue;
-
- return $aValues;
- }
-
- protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
- {
- if (is_null($original)) {
- $original = '';
- }
- $oMyChangeOp->Set("prevstring", $original);
- }
-
- protected function GetChangeRecordClassName(): string
- {
- return CMDBChangeOpSetAttributeEncrypted::class;
- }
-
-
-}
-
-
-/**
- * Wiki formatting - experimental
- *
- * [[:|