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\n"; - } - $sRes .= "\n"; - } - $sRes .= "\n"; - } - else - { - $sRes = ''; - } - - return $sRes; - } - - /** - * @param $sValue - * @param string $sSeparator - * @param string $sTextQualifier - * @param \DBObject $oHostObject - * @param bool $bLocalize - * @param bool $bConvertToPlainText - * - * @return mixed|string - * @throws \CoreException - */ - public function GetAsCSV( - $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, - $bConvertToPlainText = false - ) { - $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); - $sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator'); - $sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator'); - $sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier'); - - if (is_object($sValue) && ($sValue instanceof ormLinkSet)) - { - $sValue->Rewind(); - $aItems = array(); - while ($oObj = $sValue->Fetch()) - { - $sObjClass = get_class($oObj); - // Show only relevant information (hide the external key to the current object) - $aAttributes = array(); - foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) - { - if ($sAttCode == 'finalclass') - { - if ($sObjClass == $this->GetLinkedClass()) - { - // Simplify the output if the exact class could be determined implicitely - continue; - } - } - if ($sAttCode == $this->GetExtKeyToMe()) - { - continue; - } - if ($oAttDef->IsExternalField()) - { - continue; - } - if (!$oAttDef->IsBasedOnDBColumns()) - { - continue; - } - if (!$oAttDef->IsScalar()) - { - continue; - } - $sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '', $bLocalize); - if (strlen($sAttValue) > 0) - { - $sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier, - $sAttCode.$sSepValue.$sAttValue); - $aAttributes[] = $sAttributeQualifier.$sAttributeData.$sAttributeQualifier; - } - } - $sAttributes = implode($sSepAttribute, $aAttributes); - $aItems[] = $sAttributes; - } - $sRes = implode($sSepItem, $aItems); - } - else - { - $sRes = ''; - } - $sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes); - $sRes = $sTextQualifier.$sRes.$sTextQualifier; - - return $sRes; - } - - /** - * List the available verbs for 'GetForTemplate' - */ - public function EnumTemplateVerbs() - { - return array( - '' => 'Plain text (unlocalized) representation', - 'html' => 'HTML representation (unordered list)', - ); - } - - /** - * Get various representations of the value, for insertion into a template (e.g. in Notifications) - * - * @param mixed $value The current value of the field - * @param string $sVerb The verb specifying the representation of the value - * @param DBObject $oHostObject The object - * @param bool $bLocalize Whether or not to localize the value - * - * @return string - * @throws \Exception - */ - public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) - { - $sRemoteName = $this->IsIndirect() ? - /** @var \AttributeLinkedSetIndirect $this */ - $this->GetExtKeyToRemote().'_friendlyname' : 'friendlyname'; - - $oLinkSet = clone $value; // Workaround/Safety net for Trac #887 - $iLimit = MetaModel::GetConfig()->Get('max_linkset_output'); - $iCount = 0; - $aNames = array(); - foreach($oLinkSet as $oItem) - { - if (($iLimit > 0) && ($iCount == $iLimit)) - { - $iTotal = $oLinkSet->Count(); - $aNames[] = '... '.Dict::Format('UI:TruncatedResults', $iCount, $iTotal); - break; - } - $aNames[] = $oItem->Get($sRemoteName); - $iCount++; - } - - switch ($sVerb) - { - case '': - return implode("\n", $aNames); - - case 'html': - return ''; - - default: - throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); - } - } - - public function DuplicatesAllowed() - { - return false; - } // No duplicates for 1:n links, never - - public function GetImportColumns() - { - $aColumns = array(); - $aColumns[$this->GetCode()] = '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 - * - * [[:|