@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add DoorkeeperObjectRef, DoorkeeperBridge, DoorkeeperBridgeAsana

Summary:
- `DoorkeeperObjectRef` is a convenience object to keep track of `<applicationType, applicationDomain, objectType, objectID>` tuples.
- `DoorkeeperBridge` provides pull/push between Phabricator and external systems.
- `DoorkeeperBridgeAsana` is a bridge to Asana.

Test Plan:
Ran this snippet and got a task from Asana:

{P871}

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Differential Revision: https://secure.phabricator.com/D6273

+244 -9
+6
src/__phutil_library_map__.php
··· 533 533 'DivinerRenderer' => 'applications/diviner/renderer/DivinerRenderer.php', 534 534 'DivinerStaticPublisher' => 'applications/diviner/publisher/DivinerStaticPublisher.php', 535 535 'DivinerWorkflow' => 'applications/diviner/workflow/DivinerWorkflow.php', 536 + 'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php', 537 + 'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php', 536 538 'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php', 537 539 'DoorkeeperExternalObject' => 'applications/doorkeeper/storage/DoorkeeperExternalObject.php', 540 + 'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php', 538 541 'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php', 539 542 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 540 543 'DrydockBlueprint' => 'applications/drydock/blueprint/DrydockBlueprint.php', ··· 2406 2409 'DivinerRemarkupRuleSymbol' => 'PhutilRemarkupRule', 2407 2410 'DivinerStaticPublisher' => 'DivinerPublisher', 2408 2411 'DivinerWorkflow' => 'PhutilArgumentWorkflow', 2412 + 'DoorkeeperBridge' => 'Phobject', 2413 + 'DoorkeeperBridgeAsana' => 'DoorkeeperBridge', 2409 2414 'DoorkeeperDAO' => 'PhabricatorLiskDAO', 2410 2415 'DoorkeeperExternalObject' => 2411 2416 array( 2412 2417 0 => 'DoorkeeperDAO', 2413 2418 1 => 'PhabricatorPolicyInterface', 2414 2419 ), 2420 + 'DoorkeeperObjectRef' => 'Phobject', 2415 2421 'DrydockAllocatorWorker' => 'PhabricatorWorker', 2416 2422 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 2417 2423 'DrydockCommandInterface' => 'DrydockInterface',
+8
src/applications/auth/provider/PhabricatorAuthProviderOAuth.php
··· 281 281 return parent::renderConfigPropertyTransactionTitle($xaction); 282 282 } 283 283 284 + protected function willSaveAccount(PhabricatorExternalAccount $account) { 285 + parent::willSaveAccount($account); 286 + 287 + $oauth_token = $this->getAdapter()->getAccessToken(); 288 + $account->setProperty('oauth.token', $oauth_token); 289 + 290 + } 291 + 284 292 }
+12
src/applications/auth/provider/PhabricatorAuthProviderOAuthAsana.php
··· 35 35 return 'Asana'; 36 36 } 37 37 38 + public static function getAsanaProvider() { 39 + $providers = self::getAllEnabledProviders(); 40 + 41 + foreach ($providers as $provider) { 42 + if ($provider instanceof PhabricatorAuthProviderOAuthAsana) { 43 + return $provider; 44 + } 45 + } 46 + 47 + return null; 48 + } 49 + 38 50 }
+23
src/applications/doorkeeper/bridge/DoorkeeperBridge.php
··· 1 + <?php 2 + 3 + abstract class DoorkeeperBridge extends Phobject { 4 + 5 + private $viewer; 6 + 7 + final public function setViewer($viewer) { 8 + $this->viewer = $viewer; 9 + return $this; 10 + } 11 + 12 + final public function getViewer() { 13 + return $this->viewer; 14 + } 15 + 16 + public function isEnabled() { 17 + return true; 18 + } 19 + 20 + abstract public function canPullRef(DoorkeeperObjectRef $ref); 21 + abstract public function pullRefs(array $refs); 22 + 23 + }
+79
src/applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php
··· 1 + <?php 2 + 3 + final class DoorkeeperBridgeAsana extends DoorkeeperBridge { 4 + 5 + public function canPullRef(DoorkeeperObjectRef $ref) { 6 + return ($ref->getApplicationType() == 'asana') && 7 + ($ref->getApplicationDomain() == 'asana.com') && 8 + ($ref->getObjectType() == 'asana:task'); 9 + } 10 + 11 + public function pullRefs(array $refs) { 12 + 13 + $id_map = mpull($refs, 'getObjectID', 'getObjectKey'); 14 + $viewer = $this->getViewer(); 15 + 16 + $provider = PhabricatorAuthProviderOAuthAsana::getAsanaProvider(); 17 + if (!$provider) { 18 + return; 19 + } 20 + 21 + $accounts = id(new PhabricatorExternalAccountQuery()) 22 + ->setViewer($viewer) 23 + ->withUserPHIDs(array($viewer->getPHID())) 24 + ->withAccountTypes(array($provider->getProviderType())) 25 + ->withAccountDomains(array($provider->getProviderDomain())) 26 + ->execute(); 27 + 28 + // TODO: If the user has several linked Asana accounts, we just pick the 29 + // first one arbitrarily. We might want to try using all of them or do 30 + // something with more finesse. There's no UI way to link multiple accounts 31 + // right now so this is currently moot. 32 + $account = head($accounts); 33 + 34 + $token = $account->getProperty('oauth.token'); 35 + if (!$token) { 36 + return; 37 + } 38 + 39 + $template = id(new PhutilAsanaFuture()) 40 + ->setAccessToken($token); 41 + 42 + $futures = array(); 43 + foreach ($id_map as $key => $id) { 44 + $futures[$key] = id(clone $template) 45 + ->setRawAsanaQuery("tasks/{$id}"); 46 + } 47 + 48 + $results = array(); 49 + foreach (Futures($futures) as $key => $future) { 50 + $results[$key] = $future->resolve(); 51 + } 52 + 53 + foreach ($refs as $ref) { 54 + $result = idx($results, $ref->getObjectKey()); 55 + if (!$result) { 56 + continue; 57 + } 58 + 59 + $ref->setIsVisible(true); 60 + $ref->setAttribute('asana.data', $result); 61 + $ref->setAttribute('name', $result['name']); 62 + $ref->setAttribute('description', $result['notes']); 63 + 64 + $obj = $ref->getExternalObject(); 65 + if ($obj->getID()) { 66 + continue; 67 + } 68 + 69 + $id = $result['id']; 70 + $uri = "https://app.asana.com/0/{$id}/{$id}"; 71 + $obj->setObjectURI($uri); 72 + 73 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 74 + $obj->save(); 75 + unset($unguarded); 76 + } 77 + } 78 + 79 + }
+109
src/applications/doorkeeper/engine/DoorkeeperObjectRef.php
··· 1 + <?php 2 + 3 + final class DoorkeeperObjectRef extends Phobject { 4 + 5 + private $objectKey; 6 + private $applicationType; 7 + private $applicationDomain; 8 + private $objectType; 9 + private $objectID; 10 + private $attributes = array(); 11 + private $isVisible; 12 + private $externalObject; 13 + 14 + public function newExternalObject() { 15 + return id(new DoorkeeperExternalObject()) 16 + ->setApplicationType($this->getApplicationType()) 17 + ->setApplicationDomain($this->getApplicationDomain()) 18 + ->setObjectType($this->getObjectType()) 19 + ->setObjectID($this->getObjectID()) 20 + ->setViewPolicy(PhabricatorPolicies::POLICY_USER); 21 + } 22 + 23 + public function attachExternalObject( 24 + DoorkeeperExternalObject $external_object) { 25 + $this->externalObject = $external_object; 26 + return $this; 27 + } 28 + 29 + public function getExternalObject() { 30 + if (!$this->externalObject) { 31 + throw new Exception( 32 + "Call attachExternalObject() before getExternalObject()!"); 33 + } 34 + return $this->externalObject; 35 + } 36 + 37 + public function setIsVisible($is_visible) { 38 + $this->isVisible = $is_visible; 39 + return $this; 40 + } 41 + 42 + public function getIsVisible() { 43 + return $this->isVisible; 44 + } 45 + 46 + public function getAttribute($key, $default = null) { 47 + return idx($this->attribute, $key, $default); 48 + } 49 + 50 + public function setAttribute($key, $value) { 51 + $this->attributes[$key] = $value; 52 + return $this; 53 + } 54 + 55 + public function setObjectID($object_id) { 56 + $this->objectID = $object_id; 57 + return $this; 58 + } 59 + 60 + public function getObjectID() { 61 + return $this->objectID; 62 + } 63 + 64 + 65 + public function setObjectType($object_type) { 66 + $this->objectType = $object_type; 67 + return $this; 68 + } 69 + 70 + public function getObjectType() { 71 + return $this->objectType; 72 + } 73 + 74 + 75 + public function setApplicationDomain($application_domain) { 76 + $this->applicationDomain = $application_domain; 77 + return $this; 78 + } 79 + 80 + public function getApplicationDomain() { 81 + return $this->applicationDomain; 82 + } 83 + 84 + 85 + public function setApplicationType($application_type) { 86 + $this->applicationType = $application_type; 87 + return $this; 88 + } 89 + 90 + public function getApplicationType() { 91 + return $this->applicationType; 92 + } 93 + 94 + public function getObjectKey() { 95 + if (!$this->objectKey) { 96 + $this->objectKey = PhabricatorHash::digestForIndex( 97 + implode( 98 + ':', 99 + array( 100 + $this->getApplicationType(), 101 + $this->getApplicationDomain(), 102 + $this->getObjectType(), 103 + $this->getObjectID(), 104 + ))); 105 + } 106 + return $this->objectKey; 107 + } 108 + 109 + }
+7 -9
src/applications/doorkeeper/storage/DoorkeeperExternalObject.php
··· 39 39 public function getObjectKey() { 40 40 $key = parent::getObjectKey(); 41 41 if ($key === null) { 42 - $key = PhabricatorHash::digestForIndex( 43 - implode( 44 - ':', 45 - array( 46 - $this->getApplicationType(), 47 - $this->getApplicationDomain(), 48 - $this->getObjectType(), 49 - $this->getObjectID(), 50 - ))); 42 + $key = id(new DoorkeeperObjectRef()) 43 + ->setApplicationType($this->getApplicationType()) 44 + ->setApplicationDomain($this->getApplicationDomain()) 45 + ->setObjectType($this->getObjectType()) 46 + ->setObjectID($this->getObjectID()) 47 + ->getObjectKey(); 51 48 } 52 49 return $key; 53 50 } ··· 56 53 if (!$this->objectKey) { 57 54 $this->objectKey = $this->getObjectKey(); 58 55 } 56 + 59 57 return parent::save(); 60 58 } 61 59