mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 14:00:56 +01:00
Implement Asana and JIRA external links via HyperlinkEngineExtension, not separate Remarkup rules
Summary: Depends on D20527. Ref T13291. Now that we have more flexible support for URI rewriting, use it for Doorkeeper URIs. These are used when you set up Asana or JIRA and include the URI to an Asana task or a JIRA issue in a comment. Test Plan: - Linked up to Asana and JIRA. - Put Asana and JIRA URIs in comments. - Saw the UI update to pull task titles from Asana / JIRA using my OAuth credentials. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13291 Differential Revision: https://secure.phabricator.com/D20528
This commit is contained in:
parent
33688c8a41
commit
f420159380
10 changed files with 260 additions and 199 deletions
|
@ -1072,7 +1072,6 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'DivinerWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'DoorkeeperAsanaFeedWorker' => 'DoorkeeperFeedWorker',
|
||||
'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule',
|
||||
'DoorkeeperBridge' => 'Phobject',
|
||||
'DoorkeeperBridgeAsana' => 'DoorkeeperBridge',
|
||||
'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge',
|
||||
|
@ -6779,15 +6778,15 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationsController' => 'PhabricatorController',
|
||||
'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController',
|
||||
'PhabricatorApplyEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorAsanaAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
||||
'PhabricatorAsanaAuthProvider' => array(
|
||||
'PhabricatorOAuth2AuthProvider',
|
||||
'DoorkeeperRemarkupURIInterface',
|
||||
),
|
||||
'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorAsanaTaskHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
|
@ -9569,7 +9571,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher',
|
||||
'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource',
|
||||
'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider',
|
||||
'PhabricatorJIRAAuthProvider' => array(
|
||||
'PhabricatorOAuth1AuthProvider',
|
||||
'DoorkeeperRemarkupURIInterface',
|
||||
),
|
||||
'PhabricatorJSONConfigType' => 'PhabricatorTextConfigType',
|
||||
'PhabricatorJSONDocumentEngine' => 'PhabricatorTextDocumentEngine',
|
||||
'PhabricatorJSONExportFormat' => 'PhabricatorExportFormat',
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAsanaAuthProvider extends PhabricatorOAuth2AuthProvider {
|
||||
final class PhabricatorAsanaAuthProvider
|
||||
extends PhabricatorOAuth2AuthProvider
|
||||
implements DoorkeeperRemarkupURIInterface {
|
||||
|
||||
public function getProviderName() {
|
||||
return pht('Asana');
|
||||
|
@ -46,4 +48,26 @@ final class PhabricatorAsanaAuthProvider extends PhabricatorOAuth2AuthProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
/* -( DoorkeeperRemarkupURIInterface )------------------------------------- */
|
||||
|
||||
public function getDoorkeeperURIRef(PhutilURI $uri) {
|
||||
$uri_string = phutil_string_cast($uri);
|
||||
|
||||
$pattern = '(https://app\\.asana\\.com/0/(\\d+)/(\\d+))';
|
||||
$matches = null;
|
||||
if (!preg_match($pattern, $uri_string, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$context_id = $matches[1];
|
||||
$task_id = $matches[2];
|
||||
|
||||
return id(new DoorkeeperURIRef())
|
||||
->setURI($uri)
|
||||
->setApplicationType(DoorkeeperBridgeAsana::APPTYPE_ASANA)
|
||||
->setApplicationDomain(DoorkeeperBridgeAsana::APPDOMAIN_ASANA)
|
||||
->setObjectType(DoorkeeperBridgeAsana::OBJTYPE_TASK)
|
||||
->setObjectID($task_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorJIRAAuthProvider extends PhabricatorOAuth1AuthProvider {
|
||||
|
||||
public function getJIRABaseURI() {
|
||||
return $this->getProviderConfig()->getProperty(self::PROPERTY_JIRA_URI);
|
||||
}
|
||||
final class PhabricatorJIRAAuthProvider
|
||||
extends PhabricatorOAuth1AuthProvider
|
||||
implements DoorkeeperRemarkupURIInterface {
|
||||
|
||||
public function getProviderName() {
|
||||
return pht('JIRA');
|
||||
|
@ -332,4 +330,33 @@ final class PhabricatorJIRAAuthProvider extends PhabricatorOAuth1AuthProvider {
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,13 +22,6 @@ final class PhabricatorDoorkeeperApplication extends PhabricatorApplication {
|
|||
return pht('Connect to Other Software');
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new DoorkeeperAsanaRemarkupRule(),
|
||||
new DoorkeeperJIRARemarkupRule(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/doorkeeper/' => array(
|
||||
|
|
91
src/applications/doorkeeper/engine/DoorkeeperURIRef.php
Normal file
91
src/applications/doorkeeper/engine/DoorkeeperURIRef.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
final class DoorkeeperURIRef extends Phobject {
|
||||
|
||||
private $uri;
|
||||
private $applicationType;
|
||||
private $applicationDomain;
|
||||
private $objectType;
|
||||
private $objectID;
|
||||
private $text;
|
||||
private $displayMode = self::DISPLAY_FULL;
|
||||
|
||||
const DISPLAY_FULL = 'full';
|
||||
const DISPLAY_SHORT = 'short';
|
||||
|
||||
public function setURI(PhutilURI $uri) {
|
||||
$this->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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class DoorkeeperHyperlinkEngineExtension
|
||||
extends PhutilRemarkupHyperlinkEngineExtension {
|
||||
|
||||
const LINKENGINEKEY = 'doorkeeper';
|
||||
|
||||
public function processHyperlinks(array $hyperlinks) {
|
||||
$engine = $this->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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
interface DoorkeeperRemarkupURIInterface {
|
||||
|
||||
public function getDoorkeeperURIRef(PhutilURI $uri);
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class DoorkeeperAsanaRemarkupRule
|
||||
extends DoorkeeperRemarkupRule {
|
||||
|
||||
public function apply($text) {
|
||||
return preg_replace_callback(
|
||||
'@https://app\\.asana\\.com/0/(\\d+)/(\\d+)@',
|
||||
array($this, 'markupAsanaLink'),
|
||||
$text);
|
||||
}
|
||||
|
||||
public function markupAsanaLink($matches) {
|
||||
return $this->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],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class DoorkeeperJIRARemarkupRule
|
||||
extends DoorkeeperRemarkupRule {
|
||||
|
||||
public function apply($text) {
|
||||
return preg_replace_callback(
|
||||
'@(https?://\S+?)/browse/([A-Z]+-[1-9]\d*)@',
|
||||
array($this, 'markupJIRALink'),
|
||||
$text);
|
||||
}
|
||||
|
||||
public function markupJIRALink($matches) {
|
||||
$match_domain = $matches[1];
|
||||
$match_issue = $matches[2];
|
||||
|
||||
// TODO: When we support multiple instances, deal with them here.
|
||||
$provider = PhabricatorJIRAAuthProvider::getJIRAProvider();
|
||||
if (!$provider) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
|
||||
$jira_base = $provider->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,
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
|
||||
abstract class DoorkeeperRemarkupRule extends PhutilRemarkupRule {
|
||||
|
||||
const KEY_TAGS = 'doorkeeper.tags';
|
||||
|
||||
const VIEW_FULL = 'full';
|
||||
const VIEW_SHORT = 'short';
|
||||
|
||||
public function getPriority() {
|
||||
return 350.0;
|
||||
}
|
||||
|
||||
protected function addDoorkeeperTag(array $spec) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$spec,
|
||||
array(
|
||||
'href' => 'string',
|
||||
'tag' => 'map<string, wild>',
|
||||
|
||||
'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());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue