From eb49dbbdc8a73f01b5bf13a7e3cde53d9a4b2d7c Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Tue, 12 Mar 2019 17:50:47 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B01968=20uniqueness=20rules=20:=20fix=20se?= =?UTF-8?q?arch=20for=20classes=20hierarchy,=20disallow=20'attributes'=20p?= =?UTF-8?q?roperty=20overrides?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/dbobject.class.php | 11 +++- core/metamodel.class.php | 121 ++++++++++++++++++++++++++--------- setup/compiler.class.inc.php | 12 +--- 3 files changed, 99 insertions(+), 45 deletions(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 656cc5dee..4ceb6a8ec 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1581,12 +1581,13 @@ abstract class DBObject implements iDisplay */ protected function GetSearchForUniquenessRule($sUniquenessRuleId, $aUniquenessRuleProperties) { - $sCurrentClass = get_class($this); - $sOqlUniquenessQuery = "SELECT $sCurrentClass"; + $sRuleRootClass = $aUniquenessRuleProperties['root_class']; + $sOqlUniquenessQuery = "SELECT $sRuleRootClass"; if (!(empty($sUniquenessFilter = $aUniquenessRuleProperties['filter']))) { $sOqlUniquenessQuery .= ' WHERE '.$sUniquenessFilter; } + /** @var \DBObjectSearch $oUniquenessQuery */ $oUniquenessQuery = DBObjectSearch::FromOQL($sOqlUniquenessQuery); if (!$this->IsNew()) @@ -1600,6 +1601,12 @@ abstract class DBObject implements iDisplay $oUniquenessQuery->AddCondition($sAttributeCode, $attributeValue, '='); } + $aChildClassesWithRuleDisabled = MetaModel::GetChildClassesWithDisabledUniquenessRule($sRuleRootClass, $sUniquenessRuleId); + if (!empty($aChildClassesWithRuleDisabled)) + { + $oUniquenessQuery->AddConditionForInOperatorUsingParam('finalclass', $aChildClassesWithRuleDisabled, false); + } + return $oUniquenessQuery; } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6855e4f60..2700942cd 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -528,13 +528,15 @@ abstract class MetaModel /** * @param string $sClass + * @param bool $bClassDefinitionOnly if true then will only return properties defined in the specified class on not the properties + * from its parent classes * - * @return array + * @return array rule id as key, rule properties as value * @throws \CoreException - * * @since 2.6 N°659 uniqueness constraint + * @see #SetUniquenessRuleRootClass that fixes a specific 'root_class' property to know which class is root per rule */ - final public static function GetUniquenessRules($sClass) + final public static function GetUniquenessRules($sClass, $bClassDefinitionOnly = false) { if (!isset(self::$m_aClassParams[$sClass])) { @@ -548,6 +550,11 @@ abstract class MetaModel $aCurrentUniquenessRules = self::$m_aClassParams[$sClass]['uniqueness_rules']; } + if ($bClassDefinitionOnly) + { + return $aCurrentUniquenessRules; + } + $sParentClass = self::GetParentClass($sClass); if ($sParentClass) { @@ -581,6 +588,22 @@ abstract class MetaModel return $aCurrentUniquenessRules; } + /** + * @param string $sRootClass + * @param string $sRuleId + * + * @throws \CoreException + * @since 2.6.1 N°1918 (sous les pavés, la plage) initialize in 'root_class' property the class that has the first + * definition of the rule in the hierarchy + */ + final private static function SetUniquenessRuleRootClass($sRootClass, $sRuleId) + { + foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL) as $sClass) + { + self::$m_aClassParams[$sClass]['uniqueness_rules'][$sRuleId]['root_class'] = $sClass; + } + } + /** * @param string $sRuleId * @param string $sLeafClassName @@ -608,6 +631,38 @@ abstract class MetaModel return $sFirstClassWithRuleId; } + /** + * @param string $sRootClass + * @param string $sRuleId + * + * @return string[] child classes with the rule disabled + * + * @throws \CoreException + * @since 2.6.1 N°1968 (soyez réalistes, demandez l'impossible) + */ + final public static function GetChildClassesWithDisabledUniquenessRule($sRootClass, $sRuleId) + { + $aClassesWithDisabledRule = array(); + foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_EXCLUDETOP) as $sChildClass) + { + if (!array_key_exists('uniqueness_rules', self::$m_aClassParams[$sChildClass])) + { + continue; + } + if (!array_key_exists($sRuleId, self::$m_aClassParams[$sChildClass]['uniqueness_rules'])) + { + continue; + } + + if (self::$m_aClassParams[$sChildClass]['uniqueness_rules'][$sRuleId]['disabled'] === true) + { + $aClassesWithDisabledRule[] = $sChildClass; + } + } + + return $aClassesWithDisabledRule; + } + /** * @param array $aRuleProperties * @@ -2779,21 +2834,26 @@ abstract class MetaModel } } - $aCurrentClassUniquenessRules = MetaModel::GetUniquenessRules($sPHPClass); + $aCurrentClassUniquenessRules = MetaModel::GetUniquenessRules($sPHPClass, true); if (!empty($aCurrentClassUniquenessRules)) { $aClassFields = self::GetAttributesList($sPHPClass); foreach ($aCurrentClassUniquenessRules as $sUniquenessRuleId => $aUniquenessRuleProperties) { - $bHasSameRuleInParent = self::HasSameUniquenessRuleInParent($sPHPClass, $sUniquenessRuleId); + $bIsRuleOverride = self::HasSameUniquenessRuleInParent($sPHPClass, $sUniquenessRuleId); try { - self::CheckUniquenessRuleValidity($aUniquenessRuleProperties, $bHasSameRuleInParent, $aClassFields); + self::CheckUniquenessRuleValidity($aUniquenessRuleProperties, $bIsRuleOverride, $aClassFields); } catch (CoreUnexpectedValue $e) { throw new Exception("Invalid uniqueness rule declaration : class={$sPHPClass}, rule=$sUniquenessRuleId, reason={$e->getMessage()}"); } + + if (!$bIsRuleOverride) + { + self::SetUniquenessRuleRootClass($sPHPClass, $sUniquenessRuleId); + } } } @@ -3092,71 +3152,68 @@ abstract class MetaModel /** * @param array $aUniquenessRuleProperties * @param bool $bRuleOverride if false then control an original declaration validity, - * otherwise an override validity (can have only the disabled key) + * otherwise an override validity (can only have the 'disabled' key) * @param string[] $aExistingClassFields if non empty, will check that all fields declared in the rules exists in the class * * @throws \CoreUnexpectedValue if the rule is invalid * * @since 2.6 N°659 uniqueness constraint + * @since 2.6.1 N°1968 (joli mois de mai...) disallow overrides of 'attributes' properties */ public static function CheckUniquenessRuleValidity($aUniquenessRuleProperties, $bRuleOverride = true, $aExistingClassFields = array()) { $MANDATORY_ATTRIBUTES = array('attributes'); $UNIQUENESS_MANDATORY_KEYS_NB = count($MANDATORY_ATTRIBUTES); - $bHasDisabledKey = false; + $bHasMissingMandatoryKey = true; $iMissingMandatoryKeysNb = $UNIQUENESS_MANDATORY_KEYS_NB; - $bHasAllMandatoryKeysMissing = false; + /** @var boolean $bHasNonDisabledKeys true if rule contains at least one key that is not 'disabled' */ $bHasNonDisabledKeys = false; foreach ($aUniquenessRuleProperties as $sUniquenessRuleKey => $aUniquenessRuleProperty) { if (($sUniquenessRuleKey === 'disabled') && (!is_null($aUniquenessRuleProperty))) { - $bHasDisabledKey = true; continue; } + if (is_null($aUniquenessRuleProperty)) + { + continue; + } + $bHasNonDisabledKeys = true; if (in_array($sUniquenessRuleKey, $MANDATORY_ATTRIBUTES, true)) { - $bHasMissingMandatoryKey = false; $iMissingMandatoryKeysNb--; } - if (($sUniquenessRuleKey === 'attributes') && (!empty($aExistingClassFields))) + if ($sUniquenessRuleKey === 'attributes') { - foreach ($aUniquenessRuleProperties[$sUniquenessRuleKey] as $sRuleAttribute) + if (!empty($aExistingClassFields)) { - if (!in_array($sRuleAttribute, $aExistingClassFields, true)) + foreach ($aUniquenessRuleProperties[$sUniquenessRuleKey] as $sRuleAttribute) { - throw new CoreUnexpectedValue("Uniqueness rule : non existing field '$sRuleAttribute'"); + if (!in_array($sRuleAttribute, $aExistingClassFields, true)) + { + throw new CoreUnexpectedValue("Uniqueness rule : non existing field '$sRuleAttribute'"); + } } } } } - if ($iMissingMandatoryKeysNb == $UNIQUENESS_MANDATORY_KEYS_NB) + if ($iMissingMandatoryKeysNb === 0) { - $bHasAllMandatoryKeysMissing = true; + $bHasMissingMandatoryKey = false; } - if ($bHasDisabledKey) + if ($bRuleOverride && $bHasNonDisabledKeys) { - if ($bRuleOverride && $bHasAllMandatoryKeysMissing && !$bHasNonDisabledKeys) - { - return; - } - if ($bHasMissingMandatoryKey) - { - throw new CoreUnexpectedValue('Uniqueness rule : missing mandatory properties'); - } - - return; + throw new CoreUnexpectedValue('Uniqueness rule : only the \'disabled\' key can be overridden'); } - - if ($bHasMissingMandatoryKey) + if (!$bRuleOverride && $bHasMissingMandatoryKey) { - throw new CoreUnexpectedValue('Uniqueness rule : missing mandatory properties'); + throw new CoreUnexpectedValue('Uniqueness rule : missing mandatory property'); } } @@ -3810,7 +3867,7 @@ abstract class MetaModel /** * @param string $sClass - * @param int $iOption + * @param int $iOption one of ENUM_CHILD_CLASSES_EXCLUDETOP, ENUM_CHILD_CLASSES_ALL * * @return array * @throws \CoreException diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 13d75c8cb..8215f7dec 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -1152,19 +1152,9 @@ EOF $aUniquenessRules[$sCurrentRuleId]['disabled'] = $this->GetPropBooleanConverted($oUniquenessSingleRule, 'disabled', null); $aUniquenessRules[$sCurrentRuleId]['is_blocking'] = $this->GetPropBooleanConverted($oUniquenessSingleRule, 'is_blocking', null); - - try - { - // we're just checking all mandatory fields are present right now - // we will check for rule overrides validity later (see \MetaModel::InitClasses) - MetaModel::CheckUniquenessRuleValidity($aUniquenessRules[$sCurrentRuleId], true); - } - catch (CoreUnexpectedValue $e) - { - throw(new DOMFormatException("Invalid uniqueness rule declaration : class={$oClass->getAttribute('id')}, rule=$sCurrentRuleId, reason={$e->getMessage()}")); - } } + // we will check for rules validity later as for now we don't have objects hierarchy (see \MetaModel::InitClasses) $aClassParams['uniqueness_rules'] = var_export($aUniquenessRules, true); }