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 @@ -1072,7 +1072,6 @@ 'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php', 'DivinerWorkflow' => 'applications/diviner/workflow/DivinerWorkflow.php', 'DoorkeeperAsanaFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php', - 'DoorkeeperAsanaRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php', 'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php', 'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php', 'DoorkeeperBridgeGitHub' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php', @@ -1088,15 +1087,16 @@ 'DoorkeeperExternalObjectQuery' => 'applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php', 'DoorkeeperFeedStoryPublisher' => 'applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php', 'DoorkeeperFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperFeedWorker.php', + 'DoorkeeperHyperlinkEngineExtension' => 'applications/doorkeeper/engineextension/DoorkeeperHyperlinkEngineExtension.php', 'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php', 'DoorkeeperJIRAFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperJIRAFeedWorker.php', - 'DoorkeeperJIRARemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperJIRARemarkupRule.php', 'DoorkeeperMissingLinkException' => 'applications/doorkeeper/exception/DoorkeeperMissingLinkException.php', 'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php', - 'DoorkeeperRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperRemarkupRule.php', + 'DoorkeeperRemarkupURIInterface' => 'applications/doorkeeper/interface/DoorkeeperRemarkupURIInterface.php', 'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php', 'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php', 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', + 'DoorkeeperURIRef' => 'applications/doorkeeper/engine/DoorkeeperURIRef.php', 'DrydockAcquiredBrokenResourceException' => 'applications/drydock/exception/DrydockAcquiredBrokenResourceException.php', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php', 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', @@ -6761,7 +6761,6 @@ 'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule', 'DivinerWorkflow' => 'PhabricatorManagementWorkflow', 'DoorkeeperAsanaFeedWorker' => 'DoorkeeperFeedWorker', - 'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule', 'DoorkeeperBridge' => 'Phobject', 'DoorkeeperBridgeAsana' => 'DoorkeeperBridge', 'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge', @@ -6779,15 +6778,15 @@ 'DoorkeeperExternalObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DoorkeeperFeedStoryPublisher' => 'Phobject', 'DoorkeeperFeedWorker' => 'FeedPushWorker', + 'DoorkeeperHyperlinkEngineExtension' => 'PhutilRemarkupHyperlinkEngineExtension', 'DoorkeeperImportEngine' => 'Phobject', 'DoorkeeperJIRAFeedWorker' => 'DoorkeeperFeedWorker', - 'DoorkeeperJIRARemarkupRule' => 'DoorkeeperRemarkupRule', 'DoorkeeperMissingLinkException' => 'Exception', 'DoorkeeperObjectRef' => 'Phobject', - 'DoorkeeperRemarkupRule' => 'PhutilRemarkupRule', 'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DoorkeeperTagView' => 'AphrontView', 'DoorkeeperTagsController' => 'PhabricatorController', + 'DoorkeeperURIRef' => 'Phobject', 'DrydockAcquiredBrokenResourceException' => 'Exception', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', @@ -8090,7 +8089,10 @@ 'PhabricatorApplicationsController' => 'PhabricatorController', 'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController', 'PhabricatorApplyEditField' => 'PhabricatorEditField', - 'PhabricatorAsanaAuthProvider' => 'PhabricatorOAuth2AuthProvider', + 'PhabricatorAsanaAuthProvider' => array( + 'PhabricatorOAuth2AuthProvider', + 'DoorkeeperRemarkupURIInterface', + ), 'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorAsanaTaskHasObjectEdgeType' => 'PhabricatorEdgeType', @@ -9569,7 +9571,10 @@ 'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase', 'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource', - 'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider', + 'PhabricatorJIRAAuthProvider' => array( + 'PhabricatorOAuth1AuthProvider', + 'DoorkeeperRemarkupURIInterface', + ), 'PhabricatorJSONConfigType' => 'PhabricatorTextConfigType', 'PhabricatorJSONDocumentEngine' => 'PhabricatorTextDocumentEngine', 'PhabricatorJSONExportFormat' => 'PhabricatorExportFormat', diff --git a/src/applications/auth/provider/PhabricatorAsanaAuthProvider.php b/src/applications/auth/provider/PhabricatorAsanaAuthProvider.php --- a/src/applications/auth/provider/PhabricatorAsanaAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAsanaAuthProvider.php @@ -1,6 +1,8 @@ setURI($uri) + ->setApplicationType(DoorkeeperBridgeAsana::APPTYPE_ASANA) + ->setApplicationDomain(DoorkeeperBridgeAsana::APPDOMAIN_ASANA) + ->setObjectType(DoorkeeperBridgeAsana::OBJTYPE_TASK) + ->setObjectID($task_id); + } + } diff --git a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php --- a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php @@ -1,10 +1,8 @@ getProviderConfig()->getProperty(self::PROPERTY_JIRA_URI); - } +final class PhabricatorJIRAAuthProvider + extends PhabricatorOAuth1AuthProvider + implements DoorkeeperRemarkupURIInterface { public function getProviderName() { return pht('JIRA'); @@ -332,4 +330,33 @@ return $config->getProperty(self::PROPERTY_REPORT_COMMENT, true); } +/* -( DoorkeeperRemarkupURIInterface )------------------------------------- */ + + public function getDoorkeeperURIRef(PhutilURI $uri) { + $uri_string = phutil_string_cast($uri); + + $pattern = '((https?://\S+?)/browse/([A-Z]+-[1-9]\d*))'; + $matches = null; + if (!preg_match($pattern, $uri_string, $matches)) { + return null; + } + + $domain = $matches[1]; + $issue = $matches[2]; + + $config = $this->getProviderConfig(); + $base_uri = $config->getProperty(self::PROPERTY_JIRA_URI); + + if ($domain !== rtrim($base_uri, '/')) { + return null; + } + + return id(new DoorkeeperURIRef()) + ->setURI($uri) + ->setApplicationType(DoorkeeperBridgeJIRA::APPTYPE_JIRA) + ->setApplicationDomain($this->getProviderDomain()) + ->setObjectType(DoorkeeperBridgeJIRA::OBJTYPE_ISSUE) + ->setObjectID($issue); + } + } diff --git a/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php b/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php --- a/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php +++ b/src/applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php @@ -22,13 +22,6 @@ return pht('Connect to Other Software'); } - public function getRemarkupRules() { - return array( - new DoorkeeperAsanaRemarkupRule(), - new DoorkeeperJIRARemarkupRule(), - ); - } - public function getRoutes() { return array( '/doorkeeper/' => array( diff --git a/src/applications/doorkeeper/engine/DoorkeeperURIRef.php b/src/applications/doorkeeper/engine/DoorkeeperURIRef.php new file mode 100644 --- /dev/null +++ b/src/applications/doorkeeper/engine/DoorkeeperURIRef.php @@ -0,0 +1,91 @@ +uri = $uri; + return $this; + } + + public function getURI() { + return $this->uri; + } + + public function setApplicationType($application_type) { + $this->applicationType = $application_type; + return $this; + } + + public function getApplicationType() { + return $this->applicationType; + } + + public function setApplicationDomain($application_domain) { + $this->applicationDomain = $application_domain; + return $this; + } + + public function getApplicationDomain() { + return $this->applicationDomain; + } + + public function setObjectType($object_type) { + $this->objectType = $object_type; + return $this; + } + + public function getObjectType() { + return $this->objectType; + } + + public function setObjectID($object_id) { + $this->objectID = $object_id; + return $this; + } + + public function getObjectID() { + return $this->objectID; + } + + public function setText($text) { + $this->text = $text; + return $this; + } + + public function getText() { + return $this->text; + } + + public function setDisplayMode($display_mode) { + $options = array( + self::DISPLAY_FULL => true, + self::DISPLAY_SHORT => true, + ); + + if (!isset($options[$display_mode])) { + throw new Exception( + pht( + 'DoorkeeperURIRef display mode "%s" is unknown.', + $display_mode)); + } + + $this->displayMode = $display_mode; + return $this; + } + + public function getDisplayMode() { + return $this->displayMode; + } + +} diff --git a/src/applications/doorkeeper/engineextension/DoorkeeperHyperlinkEngineExtension.php b/src/applications/doorkeeper/engineextension/DoorkeeperHyperlinkEngineExtension.php new file mode 100644 --- /dev/null +++ b/src/applications/doorkeeper/engineextension/DoorkeeperHyperlinkEngineExtension.php @@ -0,0 +1,92 @@ +getEngine(); + $viewer = $engine->getConfig('viewer'); + + if (!$viewer) { + return; + } + + $configs = id(new PhabricatorAuthProviderConfigQuery()) + ->setViewer($viewer) + ->withIsEnabled(true) + ->execute(); + + $providers = array(); + foreach ($configs as $key => $config) { + $provider = $config->getProvider(); + if (($provider instanceof DoorkeeperRemarkupURIInterface)) { + $providers[] = $provider; + } + } + + if (!$providers) { + return; + } + + $refs = array(); + foreach ($hyperlinks as $hyperlink) { + $uri = $hyperlink->getURI(); + $uri = new PhutilURI($uri); + + foreach ($providers as $provider) { + $ref = $provider->getDoorkeeperURIRef($uri); + + if (($ref !== null) && !($ref instanceof DoorkeeperURIRef)) { + throw new Exception( + pht( + 'Expected "getDoorkeeperURIRef()" to return "null" or an '. + 'object of type "DoorkeeperURIRef", but got %s from provider '. + '"%s".', + phutil_describe_type($ref), + get_class($provider))); + } + + if ($ref === null) { + continue; + } + + $tag_id = celerity_generate_unique_node_id(); + $href = phutil_string_cast($ref->getURI()); + + $refs[] = array( + 'id' => $tag_id, + 'href' => $href, + 'ref' => array( + $ref->getApplicationType(), + $ref->getApplicationDomain(), + $ref->getObjectType(), + $ref->getObjectID(), + ), + 'view' => $ref->getDisplayMode(), + ); + + $text = $ref->getText(); + if ($text === null) { + $text = $href; + } + + $view = id(new PHUITagView()) + ->setID($tag_id) + ->setName($text) + ->setHref($href) + ->setType(PHUITagView::TYPE_OBJECT) + ->setExternal(true); + + $hyperlink->setResult($view); + break; + } + } + + if ($refs) { + Javelin::initBehavior('doorkeeper-tag', array('tags' => $refs)); + } + } + +} diff --git a/src/applications/doorkeeper/interface/DoorkeeperRemarkupURIInterface.php b/src/applications/doorkeeper/interface/DoorkeeperRemarkupURIInterface.php new file mode 100644 --- /dev/null +++ b/src/applications/doorkeeper/interface/DoorkeeperRemarkupURIInterface.php @@ -0,0 +1,7 @@ +addDoorkeeperTag( - array( - 'href' => $matches[0], - 'tag' => array( - 'ref' => array( - DoorkeeperBridgeAsana::APPTYPE_ASANA, - DoorkeeperBridgeAsana::APPDOMAIN_ASANA, - DoorkeeperBridgeAsana::OBJTYPE_TASK, - $matches[2], - ), - 'extra' => array( - 'asana.context' => $matches[1], - ), - ), - )); - } - -} diff --git a/src/applications/doorkeeper/remarkup/DoorkeeperJIRARemarkupRule.php b/src/applications/doorkeeper/remarkup/DoorkeeperJIRARemarkupRule.php deleted file mode 100644 --- a/src/applications/doorkeeper/remarkup/DoorkeeperJIRARemarkupRule.php +++ /dev/null @@ -1,44 +0,0 @@ -getJIRABaseURI(); - if ($match_domain != rtrim($jira_base, '/')) { - return $matches[0]; - } - - return $this->addDoorkeeperTag( - array( - 'href' => $matches[0], - 'tag' => array( - 'ref' => array( - DoorkeeperBridgeJIRA::APPTYPE_JIRA, - $provider->getProviderDomain(), - DoorkeeperBridgeJIRA::OBJTYPE_ISSUE, - $match_issue, - ), - ), - )); - } - - -} diff --git a/src/applications/doorkeeper/remarkup/DoorkeeperRemarkupRule.php b/src/applications/doorkeeper/remarkup/DoorkeeperRemarkupRule.php deleted file mode 100644 --- a/src/applications/doorkeeper/remarkup/DoorkeeperRemarkupRule.php +++ /dev/null @@ -1,103 +0,0 @@ - 'string', - 'tag' => 'map', - - 'name' => 'optional string', - 'view' => 'optional string', - )); - - $spec = $spec + array( - 'view' => self::VIEW_FULL, - ); - - $views = array( - self::VIEW_FULL, - self::VIEW_SHORT, - ); - $views = array_fuse($views); - if (!isset($views[$spec['view']])) { - throw new Exception( - pht( - 'Unsupported Doorkeeper tag view mode "%s". Supported modes are: %s.', - $spec['view'], - implode(', ', $views))); - } - - $key = self::KEY_TAGS; - $engine = $this->getEngine(); - $token = $engine->storeText(get_class($this)); - - $tags = $engine->getTextMetadata($key, array()); - - $tags[] = array( - 'token' => $token, - ) + $spec + array( - 'extra' => array(), - ); - - $engine->setTextMetadata($key, $tags); - return $token; - } - - public function didMarkupText() { - $key = self::KEY_TAGS; - $engine = $this->getEngine(); - $tags = $engine->getTextMetadata($key, array()); - - if (!$tags) { - return; - } - - $refs = array(); - foreach ($tags as $spec) { - $href = $spec['href']; - $name = idx($spec, 'name', $href); - - $this->assertFlatText($href); - $this->assertFlatText($name); - - if ($this->getEngine()->isTextMode()) { - $view = "{$name} <{$href}>"; - } else { - $tag_id = celerity_generate_unique_node_id(); - - $refs[] = array( - 'id' => $tag_id, - 'view' => $spec['view'], - ) + $spec['tag']; - - $view = id(new PHUITagView()) - ->setID($tag_id) - ->setName($name) - ->setHref($href) - ->setType(PHUITagView::TYPE_OBJECT) - ->setExternal(true); - } - - $engine->overwriteStoredText($spec['token'], $view); - } - - if ($refs) { - Javelin::initBehavior('doorkeeper-tag', array('tags' => $refs)); - } - - $engine->setTextMetadata($key, array()); - } - -}