N°1646 - Add possibility to sort Attribute[Meta]Enum either by code (default), rank or label

This commit is contained in:
Molkobain
2023-06-07 08:45:55 +02:00
parent f886c78f24
commit bac6cbc1d8
3 changed files with 167 additions and 67 deletions

View File

@@ -5918,6 +5918,14 @@ class AttributeEnum extends AttributeString
$aLocalizedValues[$sKey] = $this->GetValueLabel($sKey);
}
// Sort by label only if necessary
// See N°1646 and {@see \MFCompiler::CompileAttributeEnumValues()} for complete information as for why sort on labels is done at runtime while other sorting are done at compile time
/** @var \ValueSetEnum $oValueSetDef */
$oValueSetDef = $this->GetValuesDef();
if ($oValueSetDef->IsSortedByValues()) {
asort($aLocalizedValues);
}
return $aLocalizedValues;
}

View File

@@ -53,7 +53,13 @@ abstract class ValueSetDefinition
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)
@@ -78,11 +84,22 @@ abstract class ValueSetDefinition
}
}
}
// Sort on the display value
asort($aRet);
$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
asort($aValues);
}
abstract protected function LoadValues($aArgs);
}
@@ -189,11 +206,7 @@ class ValueSetObjects extends ValueSetDefinition
}
/**
* @param $aArgs
* @param string $sContains
* @param string $sOperation for the values @see self::LoadValues()
*
* @return array
* @inheritDoc
* @throws CoreException
* @throws OQLException
*/
@@ -451,10 +464,33 @@ class ValueSetObjects extends ValueSetDefinition
class ValueSetEnum extends ValueSetDefinition
{
protected $m_values;
/**
* @var bool $bSortByValue If true, values will be sorted at runtime, otherwise it is sorted at compile time in a predefined order.
* {@see \MFCompiler::CompileAttributeEnumValues()} for complete reasons.
* @since 3.1.0 N°1646
*/
protected bool $bSortByValue;
public function __construct($Values)
/**
* @param array|string $Values
* @param bool $bLocalizedSort
*
* @since 3.1.0 N°1646 Add $bLocalizedSort parameter
*/
public function __construct($Values, bool $bSortByValue = false)
{
$this->m_values = $Values;
$this->bSortByValue = $bSortByValue;
}
/**
* @see \ValueSetEnum::$bSortByValue
* @return bool
* @since 3.1.0 N°1646
*/
public function IsSortedByValues(): bool
{
return $this->bSortByValue;
}
// Helper to export the data model
@@ -464,6 +500,28 @@ class ValueSetEnum extends ValueSetDefinition
return $this->m_aValues;
}
/**
* @inheritDoc
* @since 3.1.0 N°1646 Overload method
*/
public function SortValues(array &$aValues): void
{
// TODO: Add unit test
// Force sort by values only if necessary
if ($this->bSortByValue) {
asort($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)
{
if (is_array($this->m_values))

View File

@@ -54,6 +54,22 @@ class MFCompiler
{
const DATA_PRECOMPILED_FOLDER = 'data'.DIRECTORY_SEPARATOR.'precompiled_styles'.DIRECTORY_SEPARATOR;
/**
* @var string
* @since 3.1.0
*/
protected const ENUM_ATTRIBUTE_ENUM_SORT_TYPE_CODE = 'code';
/**
* @var string
* @since 3.1.0
*/
protected const ENUM_ATTRIBUTE_ENUM_SORT_TYPE_LABEL = 'label';
/**
* @var string
* @since 3.1.0
*/
protected const ENUM_ATTRIBUTE_ENUM_SORT_TYPE_RANK = 'rank';
/**
* @var string
* @see self::GenerateStyleDataFromNode
@@ -2096,68 +2112,14 @@ EOF
$this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir);
$aParameters['depends_on'] = $sDependencies;
} elseif ($sAttType == 'AttributeEnum') {
$oValues = $oField->GetUniqueElement('values');
$oValueNodes = $oValues->getElementsByTagName('value');
$aValues = [];
$aStyledValues = [];
foreach ($oValueNodes as $oValue) {
// New in 3.0 the format of values changed
$sCode = $this->GetMandatoryPropString($oValue, 'code', false);
$aValues[] = $sCode;
$oStyleNode = $oValue->GetOptionalElement('style');
if ($oStyleNode) {
$aEnumStyleData = $this->GenerateStyleDataFromNode($oStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode, $sCode);
$aStyledValues[] = $aEnumStyleData['orm_style_instantiation'];
$sCss .= $aEnumStyleData['scss'];
}
}
$sValues = '"'.implode(',', $aValues).'"';
$aParameters['allowed_values'] = "new ValueSetEnum($sValues)";
if (count($aStyledValues) > 0) {
$sStyledValues = '['.implode(',', $aStyledValues).']';
$aParameters['styled_values'] = "$sStyledValues";
}
$aParameters['allowed_values'] = "new ValueSetEnum($sValues)";
$oDefaultStyleNode = $oField->GetOptionalElement('default_style');
if ($oDefaultStyleNode) {
$aEnumStyleData = $this->GenerateStyleDataFromNode($oDefaultStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode);
$aParameters['default_style'] = $aEnumStyleData['orm_style_instantiation'];
$sCss .= $aEnumStyleData['scss'];
}
$this->CompileAttributeEnumValues($sModuleRelativeDir, $sClass, $sAttCode, $oField, $aParameters, $sCss);
$this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir, 'list');
$this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir);
$this->CompileCommonProperty('default_value', $oField, $aParameters, $sModuleRelativeDir, '');
$this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false);
$aParameters['depends_on'] = $sDependencies;
} elseif ($sAttType == 'AttributeMetaEnum') {
$oValues = $oField->GetUniqueElement('values');
$oValueNodes = $oValues->getElementsByTagName('value');
$aValues = [];
$aStyledValues = [];
foreach ($oValueNodes as $oValue) {
// New in 3.0 the format of values changed
$sCode = $this->GetMandatoryPropString($oValue, 'code', false);
$aValues[] = $sCode;
$oStyleNode = $oValue->GetOptionalElement('style');
if ($oStyleNode) {
$aEnumStyleData = $this->GenerateStyleDataFromNode($oStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode, $sCode);
$aStyledValues[] = $aEnumStyleData['orm_style_instantiation'];
$sCss .= $aEnumStyleData['scss'];
}
}
$sValues = '"'.implode(',', $aValues).'"';
$aParameters['allowed_values'] = "new ValueSetEnum($sValues)";
if (count($aStyledValues) > 0) {
$sStyledValues = '['.implode(',', $aStyledValues).']';
$aParameters['styled_values'] = "$sStyledValues";
}
$aParameters['allowed_values'] = "new ValueSetEnum($sValues)";
$oDefaultStyleNode = $oField->GetOptionalElement('default_style');
if ($oDefaultStyleNode) {
$aEnumStyleData = $this->GenerateStyleDataFromNode($oDefaultStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode);
$aParameters['default_style'] = $aEnumStyleData['orm_style_instantiation'];
$sCss .= $aEnumStyleData['scss'];
}
$this->CompileAttributeEnumValues($sModuleRelativeDir, $sClass, $sAttCode, $oField, $aParameters, $sCss);
$this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir);
$this->CompileCommonProperty('default_value', $oField, $aParameters, $sModuleRelativeDir, '');
$this->CompileCommonProperty('mappings', $oField, $aParameters, $sModuleRelativeDir);
@@ -2507,6 +2469,78 @@ EOF
$aParameters['depends_on'] = $sDependencies;
}
protected function CompileAttributeEnumValues(string $sModuleRelativeDir, string $sClass, string $sAttCode, DOMElement $oField, array &$aParameters, string &$sCss): void
{
$sSortType = $oField->GetChildText('sort_type');
if (utils::IsNullOrEmptyString($sSortType)) {
$sSortType = static::ENUM_ATTRIBUTE_ENUM_SORT_TYPE_CODE;
}
$oValues = $oField->GetUniqueElement('values');
$oValueNodes = $oValues->getElementsByTagName('value');
$aValues = [];
$aValuesWithRank = [];
$aValuesWithoutRank = [];
$aStyledValues = [];
foreach ($oValueNodes as $oValue) {
// Value's code
$sCode = $this->GetMandatoryPropString($oValue, 'code', false);
$sRankAsString = $this->GetPropNumber($oValue, 'rank');
// Consider value as ranked only if it is the desired sort type, this is to avoid issues if a <rank> node is left when sort type isn't "rank"
if (utils::IsNotNullOrEmptyString($sRankAsString) && ($sSortType === static::ENUM_ATTRIBUTE_ENUM_SORT_TYPE_RANK)){
$aValuesWithRank[$sCode] = (float) $sRankAsString;
} else {
$aValuesWithoutRank[$sCode] = true;
}
// Value's style
$oStyleNode = $oValue->GetOptionalElement('style');
if ($oStyleNode) {
$aEnumStyleData = $this->GenerateStyleDataFromNode($oStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode, $sCode);
$aStyledValues[] = $aEnumStyleData['orm_style_instantiation'];
$sCss .= $aEnumStyleData['scss'];
}
}
// Order values
$aSortedValues = [];
$sLocalizedSortAsPHPParam = '';
switch ($sSortType) {
case static::ENUM_ATTRIBUTE_ENUM_SORT_TYPE_RANK:
// Sort ranked values then append unranked values sorted by their code
asort($aValuesWithRank);
ksort($aValuesWithoutRank);
$aSortedValues = array_merge(array_keys($aValuesWithRank), array_keys($aValuesWithoutRank));
break;
case static::ENUM_ATTRIBUTE_ENUM_SORT_TYPE_LABEL:
// Sort by labels is delegated at runtime for one main reason:
// Default language (fallback -eg. english- if no dict entry for the current language -eg. italian-) can change at anytime in the configuration file -eg. from english to french-
// if that was to happen, users would not understand why they have labels from in english instead of french, which would cause support questions / investigations.
$sLocalizedSortAsPHPParam = ', true';
default:
// Sort values by their code
ksort($aValuesWithoutRank);
$aSortedValues = array_keys($aValuesWithoutRank);
break;
}
$sValuesAsPHPParam = '"'.implode(',', $aSortedValues).'"';
$aParameters['allowed_values'] = "new ValueSetEnum($sValuesAsPHPParam $sLocalizedSortAsPHPParam)";
if (count($aStyledValues) > 0) {
$sStyledValues = '['.implode(',', $aStyledValues).']';
$aParameters['styled_values'] = "$sStyledValues";
}
// Default style for values
$oDefaultStyleNode = $oField->GetOptionalElement('default_style');
if ($oDefaultStyleNode) {
$aEnumStyleData = $this->GenerateStyleDataFromNode($oDefaultStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode);
$aParameters['default_style'] = $aEnumStyleData['orm_style_instantiation'];
$sCss .= $aEnumStyleData['scss'];
}
}
/**
* @internal This method is public in order to be used in the tests
*
@@ -3415,7 +3449,7 @@ EOF;
$aThemes[$sThemeId] = [
'theme_parameters' => $aThemeParameters,
'precompiled_stylesheet' => $oTheme->GetChildText('precompiled_stylesheet', '')
'precompiled_stylesheet' => $oTheme->GetChildText('precompiled_stylesheet', ''),
];
}
@@ -3523,7 +3557,7 @@ EOF;
$aDirToCheck = [
$sSourceDir,
APPROOT . DIRECTORY_SEPARATOR . 'extensions/'
APPROOT . DIRECTORY_SEPARATOR . 'extensions/',
];
$iDataXmlFileLastModified = 0;