@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.

Use ApplicationSearch to search for Legalpad signatures

Summary:
Ref T3116. Currently, document signatures are just in a big list that you can't search through.

- Make it easier to check if a specific user has signed.
- Restrict this UI to users who have edit permission on the document (roughly, you need to be a document manager to see the full signature list).

(It's currently possible to generate a Dashboard panel using this query, but it will just throw an exception. I'm going to leave it like that for now, we might reasonably expose some "view signatures across doucments" UI later so someone can quickly check if a user has signed 5 documents or something.)

Test Plan: {F171576}

Reviewers: chad

Reviewed By: chad

Subscribers: epriestley

Maniphest Tasks: T3116

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

+204 -90
+2
src/__phutil_library_map__.php
··· 873 873 'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php', 874 874 'LegalpadDocumentSignatureListController' => 'applications/legalpad/controller/LegalpadDocumentSignatureListController.php', 875 875 'LegalpadDocumentSignatureQuery' => 'applications/legalpad/query/LegalpadDocumentSignatureQuery.php', 876 + 'LegalpadDocumentSignatureSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php', 876 877 'LegalpadDocumentSignatureVerificationController' => 'applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php', 877 878 'LegalpadMockMailReceiver' => 'applications/legalpad/mail/LegalpadMockMailReceiver.php', 878 879 'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php', ··· 3633 3634 ), 3634 3635 'LegalpadDocumentSignatureListController' => 'LegalpadController', 3635 3636 'LegalpadDocumentSignatureQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3637 + 'LegalpadDocumentSignatureSearchEngine' => 'PhabricatorApplicationSearchEngine', 3636 3638 'LegalpadDocumentSignatureVerificationController' => 'LegalpadController', 3637 3639 'LegalpadMockMailReceiver' => 'PhabricatorObjectMailReceiver', 3638 3640 'LegalpadReplyHandler' => 'PhabricatorMailReplyHandler',
+4 -3
src/applications/legalpad/application/PhabricatorApplicationLegalpad.php
··· 41 41 '/L(?P<id>\d+)' => 'LegalpadDocumentSignController', 42 42 '/legalpad/' => array( 43 43 '' => 'LegalpadDocumentListController', 44 - '(query/(?P<queryKey>[^/]+)/)?' => 'LegalpadDocumentListController', 44 + '(?:query/(?P<queryKey>[^/]+)/)?' => 'LegalpadDocumentListController', 45 45 'create/' => 'LegalpadDocumentEditController', 46 46 'edit/(?P<id>\d+)/' => 'LegalpadDocumentEditController', 47 47 'comment/(?P<id>\d+)/' => 'LegalpadDocumentCommentController', 48 48 'view/(?P<id>\d+)/' => 'LegalpadDocumentManageController', 49 49 'done/' => 'LegalpadDocumentDoneController', 50 50 'verify/(?P<code>[^/]+)/' => 51 - 'LegalpadDocumentSignatureVerificationController', 52 - 'signatures/(?P<id>\d+)/' => 'LegalpadDocumentSignatureListController', 51 + 'LegalpadDocumentSignatureVerificationController', 52 + 'signatures/(?P<id>\d+)/(?:query/(?P<queryKey>[^/]+)/)?' 53 + => 'LegalpadDocumentSignatureListController', 53 54 'document/' => array( 54 55 'preview/' => 'PhabricatorMarkupPreviewController'), 55 56 ));
+34 -87
src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php
··· 2 2 3 3 final class LegalpadDocumentSignatureListController extends LegalpadController { 4 4 5 - private $documentId; 5 + private $documentID; 6 + private $queryKey; 7 + private $document; 6 8 7 9 public function willProcessRequest(array $data) { 8 - $this->documentId = $data['id']; 10 + $this->documentID = $data['id']; 11 + $this->queryKey = idx($data, 'queryKey'); 9 12 } 10 13 11 14 public function processRequest() { ··· 14 17 15 18 $document = id(new LegalpadDocumentQuery()) 16 19 ->setViewer($user) 17 - ->withIDs(array($this->documentId)) 20 + ->withIDs(array($this->documentID)) 21 + ->requireCapabilities( 22 + array( 23 + PhabricatorPolicyCapability::CAN_VIEW, 24 + PhabricatorPolicyCapability::CAN_EDIT, 25 + )) 18 26 ->executeOne(); 19 - 20 27 if (!$document) { 21 28 return new Aphront404Response(); 22 29 } 23 30 24 - $title = pht('Signatures for %s', $document->getMonogram()); 31 + $this->document = $document; 25 32 26 - $pager = id(new AphrontCursorPagerView()) 27 - ->readFromRequest($request); 28 - $signatures = id(new LegalpadDocumentSignatureQuery()) 29 - ->setViewer($user) 30 - ->withDocumentPHIDs(array($document->getPHID())) 31 - ->executeWithCursorPager($pager); 33 + $engine = id(new LegalpadDocumentSignatureSearchEngine()) 34 + ->setDocument($document); 32 35 33 - $crumbs = $this->buildApplicationCrumbs($this->buildSideNav()); 34 - $crumbs->addTextCrumb( 35 - $document->getMonogram(), 36 - $this->getApplicationURI('view/'.$document->getID())); 36 + $controller = id(new PhabricatorApplicationSearchController($request)) 37 + ->setQueryKey($this->queryKey) 38 + ->setSearchEngine($engine) 39 + ->setNavigation($this->buildSideNav()); 37 40 38 - $crumbs->addTextCrumb( 39 - pht('Signatures')); 40 - $list = $this->renderResultsList($document, $signatures); 41 - $list->setPager($pager); 42 - 43 - return $this->buildApplicationPage( 44 - array( 45 - $crumbs, 46 - $list, 47 - ), 48 - array( 49 - 'title' => $title, 50 - )); 41 + return $this->delegateToController($controller); 51 42 } 52 43 53 - private function renderResultsList( 54 - LegalpadDocument $document, 55 - array $signatures) { 56 - assert_instances_of($signatures, 'LegalpadDocumentSignature'); 57 - 44 + public function buildSideNav($for_app = false) { 58 45 $user = $this->getRequest()->getUser(); 59 46 60 - $list = new PHUIObjectItemListView(); 61 - $list->setUser($user); 47 + $nav = new AphrontSideNavFilterView(); 48 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 62 49 63 - foreach ($signatures as $signature) { 64 - $created = phabricator_date($signature->getDateCreated(), $user); 50 + id(new LegalpadDocumentSignatureSearchEngine()) 51 + ->setViewer($user) 52 + ->setDocument($this->document) 53 + ->addNavigationItems($nav->getMenu()); 65 54 66 - $data = $signature->getSignatureData(); 67 - 68 - $sig_data = phutil_tag( 69 - 'div', 70 - array(), 71 - array( 72 - phutil_tag( 73 - 'div', 74 - array(), 75 - phutil_tag( 76 - 'a', 77 - array( 78 - 'href' => 'mailto:'.$data['email'], 79 - ), 80 - $data['email'])), 81 - phutil_tag( 82 - 'div', 83 - array(), 84 - $data['address_1']), 85 - phutil_tag( 86 - 'div', 87 - array(), 88 - $data['address_2']), 89 - phutil_tag( 90 - 'div', 91 - array(), 92 - $data['phone']) 93 - )); 94 - 95 - $item = id(new PHUIObjectItemView()) 96 - ->setObject($signature) 97 - ->setHeader($data['name']) 98 - ->setSubhead($sig_data) 99 - ->addIcon('none', pht('Signed %s', $created)); 100 - 101 - $good_sig = true; 102 - if (!$signature->isVerified()) { 103 - $item->addFootIcon('disable', 'Unverified Email'); 104 - $good_sig = false; 105 - } 106 - if ($signature->getDocumentVersion() != $document->getVersions()) { 107 - $item->addFootIcon('delete', 'Stale Signature'); 108 - $good_sig = false; 109 - } 55 + return $nav; 56 + } 110 57 111 - if ($good_sig) { 112 - $item->setBarColor('green'); 113 - } 58 + public function buildApplicationCrumbs() { 59 + $crumbs = parent::buildApplicationCrumbs(); 114 60 115 - $list->addItem($item); 116 - } 61 + $crumbs->addTextCrumb( 62 + $this->document->getMonogram(), 63 + '/'.$this->document->getMonogram()); 117 64 118 - return $list; 65 + return $crumbs; 119 66 } 120 67 121 68 }
+164
src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php
··· 1 + <?php 2 + 3 + final class LegalpadDocumentSignatureSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + private $document; 7 + 8 + public function getResultTypeDescription() { 9 + return pht('Legalpad Signatures'); 10 + } 11 + 12 + public function getApplicationClassName() { 13 + return 'PhabricatorApplicationLegalpad'; 14 + } 15 + 16 + public function setDocument(LegalpadDocument $document) { 17 + $this->document = $document; 18 + return $this; 19 + } 20 + 21 + public function buildSavedQueryFromRequest(AphrontRequest $request) { 22 + $saved = new PhabricatorSavedQuery(); 23 + 24 + $saved->setParameter( 25 + 'signerPHIDs', 26 + $this->readUsersFromRequest($request, 'signers')); 27 + 28 + return $saved; 29 + } 30 + 31 + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 32 + $query = id(new LegalpadDocumentSignatureQuery()); 33 + 34 + $signer_phids = $saved->getParameter('signerPHIDs', array()); 35 + if ($signer_phids) { 36 + $query->withSignerPHIDs($signer_phids); 37 + } 38 + 39 + if ($this->document) { 40 + $query->withDocumentPHIDs(array($this->document->getPHID())); 41 + } 42 + 43 + return $query; 44 + } 45 + 46 + public function buildSearchForm( 47 + AphrontFormView $form, 48 + PhabricatorSavedQuery $saved_query) { 49 + 50 + $signer_phids = $saved_query->getParameter('signerPHIDs', array()); 51 + 52 + $phids = array_merge($signer_phids); 53 + $handles = id(new PhabricatorHandleQuery()) 54 + ->setViewer($this->requireViewer()) 55 + ->withPHIDs($phids) 56 + ->execute(); 57 + 58 + $form 59 + ->appendChild( 60 + id(new AphrontFormTokenizerControl()) 61 + ->setDatasource('/typeahead/common/users/') 62 + ->setName('signers') 63 + ->setLabel(pht('Signers')) 64 + ->setValue(array_select_keys($handles, $signer_phids))); 65 + } 66 + 67 + protected function getURI($path) { 68 + if ($this->document) { 69 + return '/legalpad/signatures/'.$this->document->getID().'/'.$path; 70 + } else { 71 + throw new Exception( 72 + pht( 73 + 'Searching for signatures outside of a document context is not '. 74 + 'currently supported.')); 75 + } 76 + } 77 + 78 + public function getBuiltinQueryNames() { 79 + $names = array( 80 + 'all' => pht('All Signatures'), 81 + ); 82 + 83 + return $names; 84 + } 85 + 86 + public function buildSavedQueryFromBuiltin($query_key) { 87 + 88 + $query = $this->newSavedQuery(); 89 + $query->setQueryKey($query_key); 90 + 91 + switch ($query_key) { 92 + case 'all': 93 + return $query; 94 + } 95 + 96 + return parent::buildSavedQueryFromBuiltin($query_key); 97 + } 98 + 99 + protected function getRequiredHandlePHIDsForResultList( 100 + array $documents, 101 + PhabricatorSavedQuery $query) { 102 + return mpull($documents, 'getSignerPHID'); 103 + } 104 + 105 + protected function renderResultList( 106 + array $signatures, 107 + PhabricatorSavedQuery $query, 108 + array $handles) { 109 + assert_instances_of($signatures, 'LegalpadDocumentSignature'); 110 + 111 + $viewer = $this->requireViewer(); 112 + 113 + $list = new PHUIObjectItemListView(); 114 + $list->setUser($viewer); 115 + 116 + foreach ($signatures as $signature) { 117 + $created = phabricator_date($signature->getDateCreated(), $viewer); 118 + 119 + $data = $signature->getSignatureData(); 120 + 121 + $sig_data = phutil_tag( 122 + 'div', 123 + array(), 124 + array( 125 + phutil_tag( 126 + 'div', 127 + array(), 128 + phutil_tag( 129 + 'a', 130 + array( 131 + 'href' => 'mailto:'.$data['email'], 132 + ), 133 + $data['email'])), 134 + )); 135 + 136 + $item = id(new PHUIObjectItemView()) 137 + ->setObject($signature) 138 + ->setHeader($data['name']) 139 + ->setSubhead($sig_data) 140 + ->addIcon('none', pht('Signed %s', $created)); 141 + 142 + $good_sig = true; 143 + if (!$signature->isVerified()) { 144 + $item->addFootIcon('disable', 'Unverified Email'); 145 + $good_sig = false; 146 + } 147 + 148 + $document = $signature->getDocument(); 149 + if ($signature->getDocumentVersion() != $document->getVersions()) { 150 + $item->addFootIcon('delete', 'Stale Signature'); 151 + $good_sig = false; 152 + } 153 + 154 + if ($good_sig) { 155 + $item->setBarColor('green'); 156 + } 157 + 158 + $list->addItem($item); 159 + } 160 + 161 + return $list; 162 + } 163 + 164 + }