diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -934,6 +934,7 @@ 'HeraldCommitAdapter' => 'applications/herald/adapter/HeraldCommitAdapter.php', 'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php', + 'HeraldContentSourceField' => 'applications/herald/field/HeraldContentSourceField.php', 'HeraldController' => 'applications/herald/controller/HeraldController.php', 'HeraldCustomAction' => 'applications/herald/extension/HeraldCustomAction.php', 'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php', @@ -943,6 +944,7 @@ 'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php', 'HeraldEffect' => 'applications/herald/engine/HeraldEffect.php', 'HeraldEngine' => 'applications/herald/engine/HeraldEngine.php', + 'HeraldField' => 'applications/herald/field/HeraldField.php', 'HeraldInvalidActionException' => 'applications/herald/engine/exception/HeraldInvalidActionException.php', 'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php', 'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php', @@ -4429,6 +4431,7 @@ 'HeraldCommitAdapter' => 'HeraldAdapter', 'HeraldCondition' => 'HeraldDAO', 'HeraldConditionTranscript' => 'Phobject', + 'HeraldContentSourceField' => 'HeraldField', 'HeraldController' => 'PhabricatorController', 'HeraldCustomAction' => 'Phobject', 'HeraldDAO' => 'PhabricatorLiskDAO', @@ -4438,6 +4441,7 @@ 'HeraldDisableController' => 'HeraldController', 'HeraldEffect' => 'Phobject', 'HeraldEngine' => 'Phobject', + 'HeraldField' => 'Phobject', 'HeraldInvalidActionException' => 'Exception', 'HeraldInvalidConditionException' => 'Exception', 'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability', diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -24,7 +24,6 @@ const FIELD_RULE = 'rule'; const FIELD_AFFECTED_PACKAGE = 'affected-package'; const FIELD_AFFECTED_PACKAGE_OWNER = 'affected-package-owner'; - const FIELD_CONTENT_SOURCE = 'contentsource'; const FIELD_ALWAYS = 'always'; const FIELD_AUTHOR_PROJECTS = 'authorprojects'; const FIELD_PROJECTS = 'projects'; @@ -113,6 +112,7 @@ private $emailPHIDs = array(); private $forcedEmailPHIDs = array(); private $unsubscribedPHIDs; + private $fieldMap; public function getEmailPHIDs() { return array_values($this->emailPHIDs); @@ -190,11 +190,14 @@ abstract public function getHeraldName(); public function getHeraldField($field_name) { + $impl = $this->getFieldImplementation($field_name); + if ($impl) { + return $impl->getHeraldFieldValue($this->getObject()); + } + switch ($field_name) { case self::FIELD_RULE: return null; - case self::FIELD_CONTENT_SOURCE: - return $this->getContentSource()->getSource(); case self::FIELD_ALWAYS: return true; case self::FIELD_IS_NEW_OBJECT: @@ -354,9 +357,52 @@ /* -( Fields )------------------------------------------------------------- */ + private function getFieldImplementationMap() { + if ($this->fieldMap === null) { + // We can't use PhutilClassMapQuery here because field expansion + // depends on the adapter and object. + + $object = $this->getObject(); + + $map = array(); + $all = HeraldField::getAllFields(); + foreach ($all as $key => $field) { + if (!$field->supportsObject($object)) { + continue; + } + $subfields = $field->getFieldsForObject($object); + foreach ($subfields as $subkey => $subfield) { + if (isset($map[$subkey])) { + throw new Exception( + pht( + 'Two HeraldFields (of classes "%s" and "%s") have the same '. + 'field key ("%s") after expansion for an object of class '. + '"%s" inside adapter "%s". Each field must have a unique '. + 'field key.', + get_class($subfield), + get_class($map[$subkey]), + $subkey, + get_class($object), + get_class($this))); + } + + $subfield = id(clone $subfield)->setAdapter($this); + + $map[$subkey] = $subfield; + } + } + $this->fieldMap = $map; + } + + return $this->fieldMap; + } + + private function getFieldImplementation($key) { + return idx($this->getFieldImplementationMap(), $key); + } public function getFields() { - $fields = array(); + $fields = array_keys($this->getFieldImplementationMap()); $fields[] = self::FIELD_ALWAYS; $fields[] = self::FIELD_RULE; @@ -373,7 +419,9 @@ } public function getFieldNameMap() { - return array( + $map = mpull($this->getFieldImplementationMap(), 'getHeraldFieldName'); + + return $map + array( self::FIELD_TITLE => pht('Title'), self::FIELD_BODY => pht('Body'), self::FIELD_AUTHOR => pht('Author'), @@ -394,7 +442,6 @@ self::FIELD_AFFECTED_PACKAGE => pht('Any affected package'), self::FIELD_AFFECTED_PACKAGE_OWNER => pht("Any affected package's owner"), - self::FIELD_CONTENT_SOURCE => pht('Content Source'), self::FIELD_ALWAYS => pht('Always'), self::FIELD_AUTHOR_PROJECTS => pht("Author's projects"), self::FIELD_PROJECTS => pht('Projects'), @@ -452,6 +499,11 @@ } public function getConditionsForField($field) { + $impl = $this->getFieldImplementation($field); + if ($impl) { + return $impl->getHeraldFieldConditions(); + } + switch ($field) { case self::FIELD_TITLE: case self::FIELD_BODY: @@ -526,11 +578,6 @@ self::CONDITION_RULE, self::CONDITION_NOT_RULE, ); - case self::FIELD_CONTENT_SOURCE: - return array( - self::CONDITION_IS, - self::CONDITION_IS_NOT, - ); case self::FIELD_ALWAYS: return array( self::CONDITION_UNCONDITIONALLY, @@ -940,6 +987,10 @@ public function getValueTypeForFieldAndCondition($field, $condition) { + $impl = $this->getFieldImplementation($field); + if ($impl) { + return $impl->getHeraldFieldValueType($condition); + } if ($this->isHeraldCustomKey($field)) { $value_type = $this->getCustomFieldValueTypeForFieldAndCondition( @@ -958,13 +1009,7 @@ return self::VALUE_TEXT; case self::CONDITION_IS: case self::CONDITION_IS_NOT: - switch ($field) { - case self::FIELD_CONTENT_SOURCE: - return self::VALUE_CONTENT_SOURCE; - default: - return self::VALUE_TEXT; - } - break; + return self::VALUE_TEXT; case self::CONDITION_IS_ANY: case self::CONDITION_IS_NOT_ANY: switch ($field) { diff --git a/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php b/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php --- a/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php +++ b/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php @@ -67,7 +67,6 @@ self::FIELD_AUTHOR, self::FIELD_ASSIGNEE, self::FIELD_CC, - self::FIELD_CONTENT_SOURCE, self::FIELD_PROJECTS, self::FIELD_TASK_PRIORITY, self::FIELD_TASK_STATUS, diff --git a/src/applications/herald/field/HeraldContentSourceField.php b/src/applications/herald/field/HeraldContentSourceField.php new file mode 100644 --- /dev/null +++ b/src/applications/herald/field/HeraldContentSourceField.php @@ -0,0 +1,30 @@ +getAdapter()->getContentSource()->getSource(); + } + + public function getHeraldFieldConditions() { + return array( + HeraldAdapter::CONDITION_IS, + HeraldAdapter::CONDITION_IS_NOT, + ); + } + + public function getHeraldFieldValueType($condition) { + return HeraldAdapter::VALUE_CONTENT_SOURCE; + } + + public function supportsObject($object) { + return true; + } + +} diff --git a/src/applications/herald/field/HeraldField.php b/src/applications/herald/field/HeraldField.php new file mode 100644 --- /dev/null +++ b/src/applications/herald/field/HeraldField.php @@ -0,0 +1,60 @@ +getFieldConstant() => $this); + } + + final public function setAdapter(HeraldAdapter $adapter) { + $this->adapter = $adapter; + return $this; + } + + final public function getAdapter() { + return $this->adapter; + } + + final public function getFieldConstant() { + $class = new ReflectionClass($this); + + $const = $class->getConstant('FIELDCONST'); + if ($const === false) { + throw new Exception( + pht( + '"%s" class "%s" must define a "%s" property.', + __CLASS__, + get_class($this), + 'FIELDCONST')); + } + + if (!is_string($const) || (strlen($const) > 32)) { + throw new Exception( + pht( + '"%s" class "%s" has an invalid "%s" property. Field constants '. + 'must be strings and no more than 32 bytes in length.', + __CLASS__, + get_class($this), + 'FIELDCONST')); + } + + return $const; + } + + final public static function getAllFields() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getFieldConstant') + ->execute(); + } + +}