diff --git a/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php b/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php index 9710d6eef..f723f1b08 100644 --- a/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php +++ b/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php @@ -1,68 +1,68 @@ <?php final class PhabricatorApplicationLegalpad extends PhabricatorApplication { public function getBaseURI() { return '/legalpad/'; } public function getShortDescription() { return pht('Agreements and Signatures'); } public function getIconName() { return 'legalpad'; } public function getTitleGlyph() { return "\xC2\xA9"; } public function getFlavorText() { return pht('With advanced signature technology.'); } public function getApplicationGroup() { return self::GROUP_UTILITIES; } public function isBeta() { return true; } public function getRemarkupRules() { return array( new LegalpadDocumentRemarkupRule(), ); } public function getRoutes() { return array( '/L(?P<id>\d+)' => 'LegalpadDocumentSignController', '/legalpad/' => array( '' => 'LegalpadDocumentListController', '(?:query/(?P<queryKey>[^/]+)/)?' => 'LegalpadDocumentListController', 'create/' => 'LegalpadDocumentEditController', 'edit/(?P<id>\d+)/' => 'LegalpadDocumentEditController', 'comment/(?P<id>\d+)/' => 'LegalpadDocumentCommentController', 'view/(?P<id>\d+)/' => 'LegalpadDocumentManageController', 'done/' => 'LegalpadDocumentDoneController', 'verify/(?P<code>[^/]+)/' => 'LegalpadDocumentSignatureVerificationController', - 'signatures/(?P<id>\d+)/(?:query/(?P<queryKey>[^/]+)/)?' - => 'LegalpadDocumentSignatureListController', + 'signatures/(?:(?P<id>\d+)/)?(?:query/(?P<queryKey>[^/]+)/)?' => + 'LegalpadDocumentSignatureListController', 'document/' => array( 'preview/' => 'PhabricatorMarkupPreviewController'), )); } protected function getCustomCapabilities() { return array( LegalpadCapabilityDefaultView::CAPABILITY => array( ), LegalpadCapabilityDefaultEdit::CAPABILITY => array( ), ); } } diff --git a/src/applications/legalpad/controller/LegalpadController.php b/src/applications/legalpad/controller/LegalpadController.php index 1715b4c40..a438bca36 100644 --- a/src/applications/legalpad/controller/LegalpadController.php +++ b/src/applications/legalpad/controller/LegalpadController.php @@ -1,29 +1,32 @@ <?php /** * @group legalpad */ abstract class LegalpadController extends PhabricatorController { public function buildSideNav($for_app = false) { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); if ($for_app) { $nav->addFilter('create/', pht('Create Document')); } id(new LegalpadDocumentSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); + $nav->addLabel(pht('Signatures')); + $nav->addFilter('signatures/', pht('Find Signatures')); + return $nav; } public function buildApplicationMenu() { return $this->buildSideNav(true)->getMenu(); } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php index 0be217813..f7e9b4464 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureListController.php @@ -1,68 +1,83 @@ <?php final class LegalpadDocumentSignatureListController extends LegalpadController { private $documentID; private $queryKey; private $document; public function willProcessRequest(array $data) { - $this->documentID = $data['id']; + $this->documentID = idx($data, 'id'); $this->queryKey = idx($data, 'queryKey'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); - $document = id(new LegalpadDocumentQuery()) - ->setViewer($user) - ->withIDs(array($this->documentID)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$document) { - return new Aphront404Response(); + if ($this->documentID) { + $document = id(new LegalpadDocumentQuery()) + ->setViewer($user) + ->withIDs(array($this->documentID)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$document) { + return new Aphront404Response(); + } + + $this->document = $document; } - $this->document = $document; + $engine = id(new LegalpadDocumentSignatureSearchEngine()); - $engine = id(new LegalpadDocumentSignatureSearchEngine()) - ->setDocument($document); + if ($this->document) { + $engine->setDocument($this->document); + } $controller = id(new PhabricatorApplicationSearchController($request)) ->setQueryKey($this->queryKey) ->setSearchEngine($engine) ->setNavigation($this->buildSideNav()); return $this->delegateToController($controller); } public function buildSideNav($for_app = false) { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - id(new LegalpadDocumentSignatureSearchEngine()) - ->setViewer($user) - ->setDocument($this->document) - ->addNavigationItems($nav->getMenu()); + $engine = id(new LegalpadDocumentSignatureSearchEngine()) + ->setViewer($user); + + if ($this->document) { + $engine->setDocument($this->document); + } + + $engine->addNavigationItems($nav->getMenu()); return $nav; } public function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb( - $this->document->getMonogram(), - '/'.$this->document->getMonogram()); + if ($this->document) { + $crumbs->addTextCrumb( + $this->document->getMonogram(), + '/'.$this->document->getMonogram()); + } else { + $crumbs->addTextCrumb( + pht('Signatures'), + '/legalpad/signatures/'); + } return $crumbs; } } diff --git a/src/applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php b/src/applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php index ad76e1f33..364018140 100644 --- a/src/applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php +++ b/src/applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php @@ -1,74 +1,74 @@ <?php /** * @group legalpad */ final class PhabricatorLegalpadPHIDTypeDocument extends PhabricatorPHIDType { const TYPECONST = 'LEGD'; public function getTypeConstant() { return self::TYPECONST; } public function getTypeName() { return pht('Legalpad Document'); } public function newObject() { return new LegalpadDocument(); } protected function buildQueryForObjects( PhabricatorObjectQuery $query, array $phids) { return id(new LegalpadDocumentQuery()) ->withPHIDs($phids) ->needDocumentBodies(true); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $document = $objects[$phid]; $name = $document->getDocumentBody()->getTitle(); $handle->setName($name); - $handle->setFullName($name); - $handle->setURI('/legalpad/view/'.$document->getID().'/'); + $handle->setFullName($document->getMonogram().' '.$name); + $handle->setURI('/'.$document->getMonogram()); } } public function canLoadNamedObject($name) { return preg_match('/^L\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new LegalpadDocumentQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php index b75fe0c64..83d91e893 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php @@ -1,207 +1,255 @@ <?php final class LegalpadDocumentSignatureSearchEngine extends PhabricatorApplicationSearchEngine { private $document; public function getResultTypeDescription() { return pht('Legalpad Signatures'); } public function getApplicationClassName() { return 'PhabricatorApplicationLegalpad'; } public function setDocument(LegalpadDocument $document) { $this->document = $document; return $this; } public function buildSavedQueryFromRequest(AphrontRequest $request) { $saved = new PhabricatorSavedQuery(); $saved->setParameter( 'signerPHIDs', $this->readUsersFromRequest($request, 'signers')); + $saved->setParameter( + 'documentPHIDs', + $this->readPHIDsFromRequest( + $request, + 'documents', + array( + PhabricatorLegalpadPHIDTypeDocument::TYPECONST, + ))); + return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new LegalpadDocumentSignatureQuery()); $signer_phids = $saved->getParameter('signerPHIDs', array()); if ($signer_phids) { $query->withSignerPHIDs($signer_phids); } if ($this->document) { $query->withDocumentPHIDs(array($this->document->getPHID())); + } else { + $document_phids = $saved->getParameter('documentPHIDs', array()); + if ($document_phids) { + $query->withDocumentPHIDs($document_phids); + } } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { + $document_phids = $saved_query->getParameter('documentPHIDs', array()); $signer_phids = $saved_query->getParameter('signerPHIDs', array()); - $phids = array_merge($signer_phids); + $phids = array_merge($document_phids, $signer_phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); + if (!$this->document) { + $form + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/legalpaddocuments/') + ->setName('documents') + ->setLabel(pht('Documents')) + ->setValue(array_select_keys($handles, $document_phids))); + } + $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('signers') ->setLabel(pht('Signers')) ->setValue(array_select_keys($handles, $signer_phids))); } protected function getURI($path) { if ($this->document) { return '/legalpad/signatures/'.$this->document->getID().'/'.$path; } else { - throw new Exception( - pht( - 'Searching for signatures outside of a document context is not '. - 'currently supported.')); + return '/legalpad/signatures/'.$path; } } public function getBuiltinQueryNames() { $names = array( 'all' => pht('All Signatures'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( - array $documents, + array $signatures, PhabricatorSavedQuery $query) { - return mpull($documents, 'getSignerPHID'); + + return array_merge( + mpull($signatures, 'getSignerPHID'), + mpull($signatures, 'getDocumentPHID')); } protected function renderResultList( array $signatures, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($signatures, 'LegalpadDocumentSignature'); $viewer = $this->requireViewer(); Javelin::initBehavior('phabricator-tooltips'); $sig_good = $this->renderIcon( 'fa-check', null, pht('Verified, Current')); $sig_old = $this->renderIcon( 'fa-clock-o', 'orange', pht('Signed Older Version')); $sig_unverified = $this->renderIcon( 'fa-envelope', 'red', pht('Unverified Email')); id(new PHUIIconView()) ->setIconFont('fa-envelope', 'red') ->addSigil('has-tooltip') ->setMetadata(array('tip' => pht('Unverified Email'))); $rows = array(); foreach ($signatures as $signature) { $data = $signature->getSignatureData(); $name = idx($data, 'name'); $email = idx($data, 'email'); $document = $signature->getDocument(); if (!$signature->isVerified()) { $sig_icon = $sig_unverified; } else if ($signature->getDocumentVersion() != $document->getVersions()) { $sig_icon = $sig_old; } else { $sig_icon = $sig_good; } $rows[] = array( $sig_icon, + $handles[$document->getPHID()]->renderLink(), $handles[$signature->getSignerPHID()]->renderLink(), $name, phutil_tag( 'a', array( 'href' => 'mailto:'.$email, ), $email), phabricator_datetime($signature->getDateCreated(), $viewer), ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( '', + pht('Document'), pht('Account'), pht('Name'), pht('Email'), pht('Signed'), )) + ->setColumnVisibility( + array( + true, + + // Only show the "Document" column if we aren't scoped to a + // particular document. + !$this->document, + )) ->setColumnClasses( array( + '', '', '', '', 'wide', 'right', )); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Signatures')) ->appendChild($table); + if (!$this->document) { + $policy_notice = id(new AphrontErrorView()) + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) + ->setErrors( + array( + pht( + 'NOTE: You can only see your own signatures and signatures on '. + 'documents you have permission to edit.'), + )); + $box->setErrorView($policy_notice); + } + return $box; } private function renderIcon($icon, $color, $title) { Javelin::initBehavior('phabricator-tooltips'); return array( id(new PHUIIconView()) ->setIconFont($icon, $color) ->addSigil('has-tooltip') ->setMetadata(array('tip' => $title)), javelin_tag( 'span', array( 'aural' => true, ), $title), ); } }