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 @@ -2583,6 +2583,7 @@ 'PhabricatorCustomFieldEditEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldEditEngineExtension.php', 'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php', 'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php', + 'PhabricatorCustomFieldExportEngineExtension' => 'infrastructure/export/PhabricatorCustomFieldExportEngineExtension.php', 'PhabricatorCustomFieldFulltextEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php', 'PhabricatorCustomFieldHeraldAction' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php', 'PhabricatorCustomFieldHeraldActionGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php', @@ -2847,6 +2848,7 @@ 'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php', 'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php', 'PhabricatorExecFutureFileUploadSource' => 'applications/files/uploadsource/PhabricatorExecFutureFileUploadSource.php', + 'PhabricatorExportEngineExtension' => 'infrastructure/export/PhabricatorExportEngineExtension.php', 'PhabricatorExportField' => 'infrastructure/export/PhabricatorExportField.php', 'PhabricatorExportFormat' => 'infrastructure/export/PhabricatorExportFormat.php', 'PhabricatorExtendedPolicyInterface' => 'applications/policy/interface/PhabricatorExtendedPolicyInterface.php', @@ -7991,6 +7993,7 @@ 'PhabricatorCustomFieldEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorCustomFieldEditField' => 'PhabricatorEditField', 'PhabricatorCustomFieldEditType' => 'PhabricatorEditType', + 'PhabricatorCustomFieldExportEngineExtension' => 'PhabricatorExportEngineExtension', 'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorCustomFieldHeraldAction' => 'HeraldAction', 'PhabricatorCustomFieldHeraldActionGroup' => 'HeraldActionGroup', @@ -8280,6 +8283,7 @@ 'PhabricatorEventType' => 'PhutilEventType', 'PhabricatorExampleEventListener' => 'PhabricatorEventListener', 'PhabricatorExecFutureFileUploadSource' => 'PhabricatorFileUploadSource', + 'PhabricatorExportEngineExtension' => 'Phobject', 'PhabricatorExportField' => 'Phobject', 'PhabricatorExportFormat' => 'Phobject', 'PhabricatorExtendingPhabricatorConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1483,6 +1483,26 @@ $fields[$key] = $export_field; } + $extensions = $this->newExportExtensions(); + foreach ($extensions as $extension) { + $extension_fields = $extension->newExportFields(); + foreach ($extension_fields as $extension_field) { + $key = $extension_field->getKey(); + + if (isset($fields[$key])) { + throw new Exception( + pht( + 'Export engine extension ("%s") defines an export field with '. + 'a key ("%s") that collides with another field. Each field '. + 'must have a unique key.', + get_class($extension_field), + $key)); + } + + $fields[$key] = $extension_field; + } + } + return $fields; } @@ -1514,6 +1534,25 @@ $maps[$ii] += $export_data[$ii]; } + $extensions = $this->newExportExtensions(); + foreach ($extensions as $extension) { + $extension_data = $extension->newExportData($objects); + $extension_data = array_values($extension_data); + if (count($export_data) !== count($objects)) { + throw new Exception( + pht( + 'Export engine extension ("%s") exported the wrong number of '. + 'objects, expected %s but got %s.', + get_class($extension), + phutil_count($objects), + phutil_count($export_data))); + } + + for ($ii = 0; $ii < $n; $ii++) { + $maps[$ii] += $extension_data[$ii]; + } + } + return $maps; } @@ -1525,4 +1564,23 @@ throw new PhutilMethodNotImplementedException(); } + private function newExportExtensions() { + $object = $this->newResultObject(); + $viewer = $this->requireViewer(); + + $extensions = PhabricatorExportEngineExtension::getAllExtensions(); + + $supported = array(); + foreach ($extensions as $extension) { + $extension = clone $extension; + $extension->setViewer($viewer); + + if ($extension->supportsObject($object)) { + $supported[] = $extension; + } + } + + return $supported; + } + } diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -35,6 +35,7 @@ const ROLE_HERALD = 'herald'; const ROLE_EDITENGINE = 'EditEngine'; const ROLE_HERALDACTION = 'herald.action'; + const ROLE_EXPORT = 'export'; /* -( Building Applications with Custom Fields )--------------------------- */ @@ -299,6 +300,8 @@ case self::ROLE_EDITENGINE: return $this->shouldAppearInEditView() || $this->shouldAppearInEditEngine(); + case self::ROLE_EXPORT: + return $this->shouldAppearInDataExport(); case self::ROLE_DEFAULT: return true; default: @@ -1362,6 +1365,46 @@ } +/* -( Data Export )-------------------------------------------------------- */ + + + public function shouldAppearInDataExport() { + if ($this->proxy) { + return $this->proxy->shouldAppearInDataExport(); + } + + try { + $this->newExportFieldType(); + return true; + } catch (PhabricatorCustomFieldImplementationIncompleteException $ex) { + return false; + } + } + + public function newExportField() { + if ($this->proxy) { + return $this->proxy->newExportField(); + } + + return $this->newExportFieldType() + ->setLabel($this->getFieldName()); + } + + public function newExportData() { + if ($this->proxy) { + return $this->proxy->newExportData(); + } + throw new PhabricatorCustomFieldImplementationIncompleteException($this); + } + + protected function newExportFieldType() { + if ($this->proxy) { + return $this->proxy->newExportFieldType(); + } + throw new PhabricatorCustomFieldImplementationIncompleteException($this); + } + + /* -( Conduit )------------------------------------------------------------ */ diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php @@ -496,5 +496,8 @@ return $this->getFieldValue(); } + public function newExportData() { + return $this->getFieldValue(); + } } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php @@ -124,4 +124,8 @@ return new ConduitIntParameterType(); } + protected function newExportFieldType() { + return new PhabricatorIntExportField(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php @@ -76,4 +76,8 @@ return new ConduitStringParameterType(); } + protected function newExportFieldType() { + return new PhabricatorStringExportField(); + } + } diff --git a/src/infrastructure/export/PhabricatorCustomFieldExportEngineExtension.php b/src/infrastructure/export/PhabricatorCustomFieldExportEngineExtension.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/export/PhabricatorCustomFieldExportEngineExtension.php @@ -0,0 +1,86 @@ +object = $object; + return ($object instanceof PhabricatorCustomFieldInterface); + } + + public function newExportFields() { + $prototype = $this->object; + + $fields = $this->newCustomFields($prototype); + + $results = array(); + foreach ($fields as $field) { + $field_key = $field->getModernFieldKey(); + + $results[] = $field->newExportField() + ->setKey($field_key); + } + + return $results; + } + + public function newExportData(array $objects) { + $viewer = $this->getViewer(); + + $field_map = array(); + foreach ($objects as $object) { + $object_phid = $object->getPHID(); + + $fields = PhabricatorCustomField::getObjectFields( + $object, + PhabricatorCustomField::ROLE_EXPORT); + + $fields + ->setViewer($viewer) + ->readFieldsFromObject($object); + + $field_map[$object_phid] = $fields; + } + + $all_fields = array(); + foreach ($field_map as $field_list) { + foreach ($field_list->getFields() as $field) { + $all_fields[] = $field; + } + } + + id(new PhabricatorCustomFieldStorageQuery()) + ->addFields($all_fields) + ->execute(); + + $results = array(); + foreach ($objects as $object) { + $object_phid = $object->getPHID(); + $object_fields = $field_map[$object_phid]; + + $map = array(); + foreach ($object_fields->getFields() as $field) { + $key = $field->getModernFieldKey(); + $map[$key] = $field->newExportData(); + } + + $results[] = $map; + } + + return $results; + } + + private function newCustomFields($object) { + $fields = PhabricatorCustomField::getObjectFields( + $object, + PhabricatorCustomField::ROLE_EXPORT); + $fields->setViewer($this->getViewer()); + + return $fields->getFields(); + } + +} diff --git a/src/infrastructure/export/PhabricatorExportEngineExtension.php b/src/infrastructure/export/PhabricatorExportEngineExtension.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/export/PhabricatorExportEngineExtension.php @@ -0,0 +1,31 @@ +getPhobjectClassConstant('EXTENSIONKEY'); + } + + final public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + abstract public function supportsObject($object); + abstract public function newExportFields(); + abstract public function newExportData(array $objects); + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + +}