diff --git a/core/valuesetdef/ValueSetDefinition.php b/core/valuesetdef/ValueSetDefinition.php new file mode 100644 index 000000000..bcc117c97 --- /dev/null +++ b/core/valuesetdef/ValueSetDefinition.php @@ -0,0 +1,69 @@ +GetValues(array(), ''); + $aDisplayedValues = array(); + foreach ($aValues as $key => $value) { + $aDisplayedValues[] = "$key => $value"; + } + $sAllowedValues = implode(', ', $aDisplayedValues); + return $sAllowedValues; + } + + /** + * @param array $aArgs + * @param string $sContains + * @param string $sOperation for the values {@see static::LoadValues()} + * + * @return array hash array of keys => values + */ + public function GetValues($aArgs, $sContains = '', $sOperation = 'contains') + { + if (!$this->m_bIsLoaded) { + $this->LoadValues($aArgs); + $this->m_bIsLoaded = true; + } + if (strlen($sContains) == 0) { + // No filtering + $aRet = $this->m_aValues; + } else { + // Filter on results containing the needle + $aRet = array(); + foreach ($this->m_aValues as $sKey => $sValue) { + if (stripos($sValue, $sContains) !== false) { + $aRet[$sKey] = $sValue; + } + } + } + $this->SortValues($aRet); + return $aRet; + } + + /** + * @param array $aValues Values to sort in the form keys => values + * + * @return void + * @since 3.1.0 N°1646 Create method + */ + public function SortValues(array &$aValues): void + { + // Sort alphabetically on values + natcasesort($aValues); + } + + abstract protected function LoadValues($aArgs); +} \ No newline at end of file diff --git a/core/valuesetdef/ValueSetEnum.php b/core/valuesetdef/ValueSetEnum.php new file mode 100644 index 000000000..574a9cfc0 --- /dev/null +++ b/core/valuesetdef/ValueSetEnum.php @@ -0,0 +1,94 @@ +m_values = $Values; + $this->bSortByValues = $bSortByValues; + } + + /** + * @return bool + * @see \ValueSetEnum::$bSortByValues + * @since 3.1.0 N°1646 + */ + public function IsSortedByValues(): bool + { + return $this->bSortByValues; + } + + // Helper to export the data model + public function GetValueList() + { + $this->LoadValues(null); + return $this->m_aValues; + } + + /** + * @inheritDoc + * @since 3.1.0 N°1646 Overload method + */ + public function SortValues(array &$aValues): void + { + // Force sort by values only if necessary + if ($this->bSortByValues) { + natcasesort($aValues); + return; + } + + // Don't sort values as we rely on the order defined during compilation + return; + } + + /** + * @param array|string $aArgs + * + * @return true + */ + protected function LoadValues($aArgs) + { + $aValues = []; + if (is_array($this->m_values)) { + foreach ($this->m_values as $key => $value) { + // Handle backed-enum case + if (is_object($value) && enum_exists(get_class($value))) { + $aValues[$value->value] = $value->value; + continue; + } + + $aValues[$key] = $value; + } + } elseif (is_string($this->m_values) && strlen($this->m_values) > 0) { + foreach (explode(",", $this->m_values) as $sVal) { + $sVal = trim($sVal); + $sKey = $sVal; + $aValues[$sKey] = $sVal; + } + } else { + $aValues = []; + } + $this->m_aValues = $aValues; + return true; + } +} \ No newline at end of file diff --git a/core/valuesetdef/ValueSetEnumClasses.php b/core/valuesetdef/ValueSetEnumClasses.php new file mode 100644 index 000000000..b2788f738 --- /dev/null +++ b/core/valuesetdef/ValueSetEnumClasses.php @@ -0,0 +1,43 @@ +m_sCategories = $sCategories; + parent::__construct($sAdditionalValues, true /* Classes are always sorted alphabetically */); + } + + protected function LoadValues($aArgs) + { + // Call the parent to parse the additional values... + parent::LoadValues($aArgs); + + // Translate the labels of the additional values + foreach ($this->m_aValues as $sClass => $void) { + if (MetaModel::IsValidClass($sClass)) { + $this->m_aValues[$sClass] = MetaModel::GetName($sClass); + } else { + unset($this->m_aValues[$sClass]); + } + } + + // Then, add the classes from the category definition + foreach (MetaModel::GetClasses($this->m_sCategories) as $sClass) { + if (MetaModel::IsValidClass($sClass)) { + $this->m_aValues[$sClass] = MetaModel::GetName($sClass); + } else { + unset($this->m_aValues[$sClass]); + } + } + + return true; + } +} \ No newline at end of file diff --git a/core/valuesetdef/ValueSetEnumPadded.php b/core/valuesetdef/ValueSetEnumPadded.php new file mode 100644 index 000000000..2f6f9e075 --- /dev/null +++ b/core/valuesetdef/ValueSetEnumPadded.php @@ -0,0 +1,25 @@ +LoadValues(null); + } else { + $this->m_aValues = $Values; + } + $aPaddedValues = array(); + foreach ($this->m_aValues as $sKey => $sVal) { + // Pad keys to the min. length required by the \AttributeSet + $sKey = str_pad($sKey, 3, '_', STR_PAD_LEFT); + $aPaddedValues[$sKey] = $sVal; + } + $this->m_values = $aPaddedValues; + } +} \ No newline at end of file diff --git a/core/valuesetdef/ValueSetObjects.php b/core/valuesetdef/ValueSetObjects.php new file mode 100644 index 000000000..6576d74fe --- /dev/null +++ b/core/valuesetdef/ValueSetObjects.php @@ -0,0 +1,345 @@ +.]attcode' => bAscending + */ + public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array()) + { + $this->m_sContains = ''; + $this->m_sOperation = ''; + $this->m_sFilterExpr = $sFilterExp; + $this->m_sValueAttCode = $sValueAttCode; + $this->m_aOrderBy = $aOrderBy; + $this->m_bAllowAllData = $bAllowAllData; + $this->m_aModifierProperties = $aModifierProperties; + $this->m_oExtraCondition = null; + $this->m_bSort = true; + $this->m_iLimit = 0; + } + + public function SetModifierProperty($sPluginClass, $sProperty, $value) + { + $this->m_aModifierProperties[$sPluginClass][$sProperty] = $value; + $this->m_bIsLoaded = false; + } + + /** + * @param \DBSearch $oFilter + * @deprecated use SetCondition instead + * + */ + public function AddCondition(DBSearch $oFilter) + { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use SetCondition instead'); + $this->SetCondition($oFilter); + } + + public function SetCondition(DBSearch $oFilter) + { + $this->m_oExtraCondition = $oFilter; + $this->m_bIsLoaded = false; + } + + public function SetOrderBy(array $aOrderBy) + { + $this->m_aOrderBy = $aOrderBy; + } + + public function ToObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null) + { + if ($this->m_bAllowAllData) { + $oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr); + } else { + $oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr); + } + if (!is_null($this->m_oExtraCondition)) { + $oFilter = $oFilter->Intersect($this->m_oExtraCondition); + } + foreach ($this->m_aModifierProperties as $sPluginClass => $aProperties) { + foreach ($aProperties as $sProperty => $value) { + $oFilter->SetModifierProperty($sPluginClass, $sProperty, $value); + } + } + if ($iAdditionalValue > 0) { + $oSearchAdditionalValue = new DBObjectSearch($oFilter->GetClass()); + $oSearchAdditionalValue->AddConditionExpression(new BinaryExpression( + new FieldExpression('id', $oSearchAdditionalValue->GetClassAlias()), + '=', + new VariableExpression('current_extkey_id')) + ); + $oSearchAdditionalValue->AllowAllData(); + $oSearchAdditionalValue->SetArchiveMode(true); + $oSearchAdditionalValue->SetInternalParams(array('current_extkey_id' => $iAdditionalValue)); + + $oFilter = new DBUnionSearch(array($oFilter, $oSearchAdditionalValue)); + } + + return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs); + } + + /** + * @inheritDoc + * @throws CoreException + * @throws OQLException + */ + public function GetValues($aArgs, $sContains = '', $sOperation = 'contains') + { + if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation)) { + $this->LoadValues($aArgs, $sContains, $sOperation); + $this->m_bIsLoaded = true; + } + // The results are already filtered and sorted (on friendly name) + $aRet = $this->m_aValues; + return $aRet; + } + + /** + * @param $aArgs + * @param string $sContains + * @param string $sOperation 'contains' or 'equals_start_with' + * + * @return bool + * @throws \CoreException + * @throws \OQLException + */ + protected function LoadValues($aArgs, $sContains = '', $sOperation = 'contains') + { + $this->m_sContains = $sContains; + $this->m_sOperation = $sOperation; + + $this->m_aValues = array(); + + $oFilter = $this->GetFilter($sOperation, $sContains); + + $oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort); + if (empty($this->m_sValueAttCode)) { + $aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname')); + } else { + $aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode)); + } + $oObjects->OptimizeColumnLoad($aAttToLoad); + while ($oObject = $oObjects->Fetch()) { + if (empty($this->m_sValueAttCode)) { + $this->m_aValues[$oObject->GetKey()] = $oObject->GetName(); + } else { + $this->m_aValues[$oObject->GetKey()] = $oObject->Get($this->m_sValueAttCode); + } + } + + return true; + } + + + /** + * Get filter for functions LoadValues and LoadValuesForAutocomplete + * + * @param $sOperation + * @param $sContains + * + * @return \DBObjectSearch|\DBSearch|\DBUnionSearch|false|mixed + * @throws \CoreException + * @throws \OQLException + * @since 3.0.3 3.1.0 + */ + protected function GetFilter($sOperation, $sContains) + { + $this->m_sContains = $sContains; + $this->m_sOperation = $sOperation; + + if ($this->m_bAllowAllData) { + $oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr); + } else { + $oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr); + $oFilter->SetShowObsoleteData(utils::ShowObsoleteData()); + } + if (!$oFilter) { + return false; + } + if (!is_null($this->m_oExtraCondition)) { + $oFilter = $oFilter->Intersect($this->m_oExtraCondition); + } + foreach ($this->m_aModifierProperties as $sPluginClass => $aProperties) { + foreach ($aProperties as $sProperty => $value) { + $oFilter->SetModifierProperty($sPluginClass, $sProperty, $value); + } + } + + $sClass = $oFilter->GetClass(); + + switch ($this->m_sOperation) { + case 'equals': + case 'start_with': + if ($this->m_sOperation === 'start_with') { + $this->m_sContains .= '%'; + $sOperator = 'LIKE'; + } else { + $sOperator = '='; + } + + $aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass); + if (count($aAttributes) > 0) { + $sClassAlias = $oFilter->GetClassAlias(); + $aFilters = array(); + $oValueExpr = new ScalarExpression($this->m_sContains); + foreach ($aAttributes as $sAttribute) { + $oNewFilter = $oFilter->DeepClone(); + $oNameExpr = new FieldExpression($sAttribute, $sClassAlias); + $oCondition = new BinaryExpression($oNameExpr, $sOperator, $oValueExpr); + $oNewFilter->AddConditionExpression($oCondition); + $aFilters[] = $oNewFilter; + } + // Unions are much faster than OR conditions + $oFilter = new DBUnionSearch($aFilters); + } else { + $oValueExpr = new ScalarExpression($this->m_sContains); + $oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias()); + $oNewCondition = new BinaryExpression($oNameExpr, $sOperator, $oValueExpr); + $oFilter->AddConditionExpression($oNewCondition); + } + break; + + default: + $oValueExpr = new ScalarExpression('%' . $this->m_sContains . '%'); + $oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias()); + $oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr); + $oFilter->AddConditionExpression($oNewCondition); + break; + } + + return $oFilter; + } + + public function GetValuesDescription() + { + return 'Filter: ' . $this->m_sFilterExpr; + } + + public function GetFilterExpression() + { + return $this->m_sFilterExpr; + } + + /** + * @param $iLimit + */ + public function SetLimit($iLimit) + { + $this->m_iLimit = $iLimit; + } + + /** + * @param $bSort + */ + public function SetSort($bSort) + { + $this->m_bSort = $bSort; + } + + public function GetValuesForAutocomplete($aArgs, $sContains = '', $sOperation = 'contains') + { + if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation)) { + $this->LoadValuesForAutocomplete($aArgs, $sContains, $sOperation); + $this->m_bIsLoaded = true; + } + // The results are already filtered and sorted (on friendly name) + $aRet = $this->m_aValues; + return $aRet; + } + + /** + * @param $aArgs + * @param string $sContains + * @param string $sOperation 'contains' or 'equals_start_with' + * + * @return bool + * @throws \CoreException + * @throws \OQLException + */ + protected function LoadValuesForAutocomplete($aArgs, $sContains = '', $sOperation = 'contains') + { + $this->m_aValues = array(); + + $oFilter = $this->GetFilter($sOperation, $sContains); + $sClass = $oFilter->GetClass(); + $sClassAlias = $oFilter->GetClassAlias(); + + $oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort); + if (empty($this->m_sValueAttCode)) { + $aAttToLoad = ['friendlyname']; + } else { + $aAttToLoad = [$this->m_sValueAttCode]; + } + + $sImageAttr = MetaModel::GetImageAttributeCode($sClass); + if (!empty($sImageAttr)) { + $aAttToLoad [] = $sImageAttr; + } + + $aComplementAttributeSpec = MetaModel::GetNameSpec($sClass, \Combodo\iTop\Core\MetaModel\FriendlyNameType::COMPLEMENTARY); + $sFormatAdditionalField = $aComplementAttributeSpec[0]; + $aAdditionalField = $aComplementAttributeSpec[1]; + + if (count($aAdditionalField) > 0) { + if (is_array($aAdditionalField)) { + $aAttToLoad = array_merge($aAttToLoad, $aAdditionalField); + } else { + $aAttToLoad [] = $aAdditionalField; + } + } + + $oObjects->OptimizeColumnLoad([$sClassAlias => $aAttToLoad]); + while ($oObject = $oObjects->Fetch()) { + $aData = []; + if (empty($this->m_sValueAttCode)) { + $aData['label'] = $oObject->GetName(); + } else { + $aData['label'] = $oObject->Get($this->m_sValueAttCode); + } + if ($oObject->IsObsolete()) { + $aData['obsolescence_flag'] = '1'; + } else { + $aData['obsolescence_flag'] = '0'; + } + if (count($aAdditionalField) > 0) { + $aArguments = []; + foreach ($aAdditionalField as $sAdditionalField) { + array_push($aArguments, $oObject->Get($sAdditionalField)); + } + $aData['additional_field'] = utils::VSprintf($sFormatAdditionalField, $aArguments); + } else { + $aData['additional_field'] = ''; + } + if (!empty($sImageAttr)) { + /** @var \ormDocument $oImage */ + $oImage = $oObject->Get($sImageAttr); + if (!$oImage->IsEmpty()) { + $aData['picture_url'] = $oImage->GetDisplayURL($sClass, $oObject->GetKey(), $sImageAttr); + $aData['initials'] = ''; + } else { + $aData['initials'] = utils::ToAcronym($aData['label']); + } + } + $this->m_aValues[$oObject->GetKey()] = $aData; + } + return true; + } +} \ No newline at end of file diff --git a/core/valuesetdef/ValueSetRange.php b/core/valuesetdef/ValueSetRange.php new file mode 100644 index 000000000..f0353251b --- /dev/null +++ b/core/valuesetdef/ValueSetRange.php @@ -0,0 +1,28 @@ +m_iStart = $iStart; + $this->m_iEnd = $iEnd; + $this->m_iStep = $iStep; + } + + protected function LoadValues($aArgs) + { + $iValue = $this->m_iStart; + for ($iValue = $this->m_iStart; $iValue <= $this->m_iEnd; $iValue += $this->m_iStep) { + $this->m_aValues[$iValue] = $iValue; + } + return true; + } +} \ No newline at end of file