diff --git a/resources/sql/autopatches/20140703.legalcorp.1.sql b/resources/sql/autopatches/20140703.legalcorp.1.sql new file mode 100644 index 0000000000..abc86c7768 --- /dev/null +++ b/resources/sql/autopatches/20140703.legalcorp.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_legalpad.legalpad_document + ADD signatureType VARCHAR(4) NOT NULL COLLATE utf8_bin; diff --git a/resources/sql/autopatches/20140703.legalcorp.2.sql b/resources/sql/autopatches/20140703.legalcorp.2.sql new file mode 100644 index 0000000000..82fe9c8eac --- /dev/null +++ b/resources/sql/autopatches/20140703.legalcorp.2.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_legalpad.legalpad_document + SET signatureType = 'user' WHERE signatureType = ''; diff --git a/resources/sql/autopatches/20140703.legalcorp.3.sql b/resources/sql/autopatches/20140703.legalcorp.3.sql new file mode 100644 index 0000000000..be18b7f3ec --- /dev/null +++ b/resources/sql/autopatches/20140703.legalcorp.3.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature + ADD signatureType VARCHAR(4) NOT NULL COLLATE utf8_bin AFTER documentVersion; diff --git a/resources/sql/autopatches/20140703.legalcorp.4.sql b/resources/sql/autopatches/20140703.legalcorp.4.sql new file mode 100644 index 0000000000..c05f000ee6 --- /dev/null +++ b/resources/sql/autopatches/20140703.legalcorp.4.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_legalpad.legalpad_documentsignature + SET signatureType = 'user' WHERE signatureType = ''; diff --git a/resources/sql/autopatches/20140703.legalcorp.5.sql b/resources/sql/autopatches/20140703.legalcorp.5.sql new file mode 100644 index 0000000000..b1e9ae76f1 --- /dev/null +++ b/resources/sql/autopatches/20140703.legalcorp.5.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature + CHANGE signerPHID signerPHID VARCHAR(64) COLLATE utf8_bin; diff --git a/src/applications/legalpad/constants/LegalpadTransactionType.php b/src/applications/legalpad/constants/LegalpadTransactionType.php index f49287cc31..92452ba0e1 100644 --- a/src/applications/legalpad/constants/LegalpadTransactionType.php +++ b/src/applications/legalpad/constants/LegalpadTransactionType.php @@ -1,11 +1,9 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if (!$this->id) { $is_create = true; $this->requireApplicationCapability( LegalpadCapabilityCreateDocuments::CAPABILITY); $document = LegalpadDocument::initializeNewDocument($user); $body = id(new LegalpadDocumentBody()) ->setCreatorPHID($user->getPHID()); $document->attachDocumentBody($body); $document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID); - $title = null; - $text = null; } else { $is_create = false; $document = id(new LegalpadDocumentQuery()) ->setViewer($user) ->needDocumentBodies(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($this->id)) ->executeOne(); if (!$document) { return new Aphront404Response(); } - $title = $document->getDocumentBody()->getTitle(); - $text = $document->getDocumentBody()->getText(); } $e_title = true; $e_text = true; + + $title = $document->getDocumentBody()->getTitle(); + $text = $document->getDocumentBody()->getText(); + $v_signature_type = $document->getSignatureType(); + $errors = array(); $can_view = null; $can_edit = null; if ($request->isFormPost()) { $xactions = array(); $title = $request->getStr('title'); if (!strlen($title)) { $e_title = pht('Required'); $errors[] = pht('The document title may not be blank.'); } else { $xactions[] = id(new LegalpadTransaction()) ->setTransactionType(LegalpadTransactionType::TYPE_TITLE) ->setNewValue($title); } $text = $request->getStr('text'); if (!strlen($text)) { $e_text = pht('Required'); $errors[] = pht('The document may not be blank.'); } else { $xactions[] = id(new LegalpadTransaction()) ->setTransactionType(LegalpadTransactionType::TYPE_TEXT) ->setNewValue($text); } $can_view = $request->getStr('can_view'); $xactions[] = id(new LegalpadTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setNewValue($can_view); $can_edit = $request->getStr('can_edit'); $xactions[] = id(new LegalpadTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($can_edit); + if ($is_create) { + $v_signature_type = $request->getStr('signatureType'); + $xactions[] = id(new LegalpadTransaction()) + ->setTransactionType(LegalpadTransactionType::TYPE_SIGNATURE_TYPE) + ->setNewValue($v_signature_type); + } + if (!$errors) { $editor = id(new LegalpadDocumentEditor()) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setActor($user); $xactions = $editor->applyTransactions($document, $xactions); return id(new AphrontRedirectResponse()) ->setURI($this->getApplicationURI('view/'.$document->getID())); } } if ($errors) { // set these to what was specified in the form on post $document->setViewPolicy($can_view); $document->setEditPolicy($can_edit); } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormTextControl()) ->setID('document-title') ->setLabel(pht('Title')) ->setError($e_title) ->setValue($title) - ->setName('title')) + ->setName('title')); + + if ($is_create) { + $form->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Who Should Sign?')) + ->setName(pht('signatureType')) + ->setValue($v_signature_type) + ->setOptions(LegalpadDocument::getSignatureTypeMap())); + } else { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Who Should Sign?')) + ->setValue($document->getSignatureTypeName())); + } + + $form ->appendChild( id(new PhabricatorRemarkupControl()) ->setID('document-text') ->setLabel(pht('Text')) ->setError($e_text) ->setValue($text) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setName('text')); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($user) ->setObject($document) ->execute(); $form ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($document) ->setPolicies($policies) ->setName('can_view')) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($document) ->setPolicies($policies) ->setName('can_edit')); $crumbs = $this->buildApplicationCrumbs($this->buildSideNav()); $submit = new AphrontFormSubmitControl(); if ($is_create) { $submit->setValue(pht('Create Document')); $submit->addCancelButton($this->getApplicationURI()); $title = pht('Create Document'); $short = pht('Create'); } else { $submit->setValue(pht('Edit Document')); $submit->addCancelButton( $this->getApplicationURI('view/'.$document->getID())); $title = pht('Edit Document'); $short = pht('Edit'); $crumbs->addTextCrumb( $document->getMonogram(), $this->getApplicationURI('view/'.$document->getID())); } $form ->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $crumbs->addTextCrumb($short); $preview = id(new PHUIRemarkupPreviewPanel()) ->setHeader(pht('Document Preview')) ->setPreviewURI($this->getApplicationURI('document/preview/')) ->setControlID('document-text') ->setSkin('document'); return $this->buildApplicationPage( array( $crumbs, $form_box, $preview ), array( 'title' => $title, )); } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentManageController.php b/src/applications/legalpad/controller/LegalpadDocumentManageController.php index 4823384059..21ebf1399b 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentManageController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentManageController.php @@ -1,230 +1,234 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); // NOTE: We require CAN_EDIT to view this page. $document = id(new LegalpadDocumentQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->needDocumentBodies(true) ->needContributors(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$document) { return new Aphront404Response(); } $xactions = id(new LegalpadTransactionQuery()) ->setViewer($user) ->withObjectPHIDs(array($document->getPHID())) ->execute(); $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $document->getPHID()); $document_body = $document->getDocumentBody(); $phids = array(); $phids[] = $document_body->getCreatorPHID(); foreach ($subscribers as $subscriber) { $phids[] = $subscriber; } foreach ($document->getContributors() as $contributor) { $phids[] = $contributor; } $this->loadHandles($phids); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($user); $engine->addObject( $document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $title = $document_body->getTitle(); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($user) ->setPolicyObject($document); $actions = $this->buildActionView($document); $properties = $this->buildPropertyView($document, $engine, $actions); $comment_form_id = celerity_generate_unique_node_id(); $xaction_view = id(new LegalpadTransactionView()) ->setUser($this->getRequest()->getUser()) ->setObjectPHID($document->getPHID()) ->setTransactions($xactions) ->setMarkupEngine($engine); $add_comment = $this->buildAddCommentView($document, $comment_form_id); $crumbs = $this->buildApplicationCrumbs($this->buildSideNav()); $crumbs->setActionList($actions); $crumbs->addTextCrumb( $document->getMonogram(), '/'.$document->getMonogram()); $crumbs->addTextCrumb(pht('Manage')); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties) ->addPropertyList($this->buildDocument($engine, $document_body)); $content = array( $crumbs, $object_box, $xaction_view, $add_comment, ); return $this->buildApplicationPage( $content, array( 'title' => $title, 'pageObjects' => array($document->getPHID()), )); } private function buildDocument( PhabricatorMarkupEngine $engine, LegalpadDocumentBody $body) { $view = new PHUIPropertyListView(); $view->addClass('legalpad'); $view->addSectionHeader(pht('Document')); $view->addTextContent( $engine->getOutput($body, LegalpadDocumentBody::MARKUP_FIELD_TEXT)); return $view; } private function buildActionView(LegalpadDocument $document) { $user = $this->getRequest()->getUser(); $actions = id(new PhabricatorActionListView()) ->setUser($user) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($document); $can_edit = PhabricatorPolicyFilter::hasCapability( $user, $document, PhabricatorPolicyCapability::CAN_EDIT); $doc_id = $document->getID(); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil-square') ->setName(pht('View/Sign Document')) ->setHref('/'.$document->getMonogram())); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Document')) ->setHref($this->getApplicationURI('/edit/'.$doc_id.'/')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-terminal') ->setName(pht('View Signatures')) ->setHref($this->getApplicationURI('/signatures/'.$doc_id.'/'))); return $actions; } private function buildPropertyView( LegalpadDocument $document, PhabricatorMarkupEngine $engine, PhabricatorActionListView $actions) { $user = $this->getRequest()->getUser(); $properties = id(new PHUIPropertyListView()) ->setUser($user) ->setObject($document) ->setActionList($actions); + $properties->addProperty( + pht('Signature Type'), + $document->getSignatureTypeName()); + $properties->addProperty( pht('Last Updated'), phabricator_datetime($document->getDateModified(), $user)); $properties->addProperty( pht('Updated By'), $this->getHandle( $document->getDocumentBody()->getCreatorPHID())->renderLink()); $properties->addProperty( pht('Versions'), $document->getVersions()); $contributor_view = array(); foreach ($document->getContributors() as $contributor) { $contributor_view[] = $this->getHandle($contributor)->renderLink(); } $contributor_view = phutil_implode_html(', ', $contributor_view); $properties->addProperty( pht('Contributors'), $contributor_view); $properties->invokeWillRenderEvent(); return $properties; } private function buildAddCommentView( LegalpadDocument $document, $comment_form_id) { $user = $this->getRequest()->getUser(); $draft = PhabricatorDraft::newFromUserAndKey($user, $document->getPHID()); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $title = $is_serious ? pht('Add Comment') : pht('Debate Legislation'); $form = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($user) ->setObjectPHID($document->getPHID()) ->setFormID($comment_form_id) ->setHeaderText($title) ->setDraft($draft) ->setSubmitButtonName(pht('Add Comment')) ->setAction($this->getApplicationURI('/comment/'.$document->getID().'/')) ->setRequestURI($this->getRequest()->getRequestURI()); return $form; } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php index e95863f39a..e2522b52e5 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php @@ -1,375 +1,632 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $document = id(new LegalpadDocumentQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->needDocumentBodies(true) ->executeOne(); if (!$document) { return new Aphront404Response(); } - $signer_phid = null; - $signature_data = array(); - if ($viewer->isLoggedIn()) { - $signer_phid = $viewer->getPHID(); - $signature_data = array( - 'name' => $viewer->getRealName(), - 'email' => $viewer->loadPrimaryEmailAddress(), - ); - } else if ($request->isFormPost()) { - $email = new PhutilEmailAddress($request->getStr('email')); - if (strlen($email->getDomainName())) { - $email_obj = id(new PhabricatorUserEmail()) - ->loadOneWhere('address = %s', $email->getAddress()); - if ($email_obj) { - return $this->signInResponse(); - } - $external_account = id(new PhabricatorExternalAccountQuery()) - ->setViewer($viewer) - ->withAccountTypes(array('email')) - ->withAccountDomains(array($email->getDomainName())) - ->withAccountIDs(array($email->getAddress())) - ->loadOneOrCreate(); - if ($external_account->getUserPHID()) { - return $this->signInResponse(); + list($signer_phid, $signature_data) = $this->readSignerInformation( + $document, + $request); + + $signature = null; + + $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + $is_individual = ($document->getSignatureType() == $type_individual); + if ($is_individual) { + if ($signer_phid) { + // TODO: This is odd and should probably be adjusted after grey/external + // accounts work better, but use the omnipotent viewer to check for a + // signature so we can pick up anonymous/grey signatures. + + $signature = id(new LegalpadDocumentSignatureQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withDocumentPHIDs(array($document->getPHID())) + ->withSignerPHIDs(array($signer_phid)) + ->executeOne(); + + if ($signature && !$viewer->isLoggedIn()) { + return $this->newDialog() + ->setTitle(pht('Already Signed')) + ->appendParagraph(pht('You have already signed this document!')) + ->addCancelButton('/'.$document->getMonogram(), pht('Okay')); } - $signer_phid = $external_account->getPHID(); } - } - $signature = null; - if ($signer_phid) { - // TODO: This is odd and should probably be adjusted after grey/external - // accounts work better, but use the omnipotent viewer to check for a - // signature so we can pick up anonymous/grey signatures. - - $signature = id(new LegalpadDocumentSignatureQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withDocumentPHIDs(array($document->getPHID())) - ->withSignerPHIDs(array($signer_phid)) - ->executeOne(); - - if ($signature && !$viewer->isLoggedIn()) { - return $this->newDialog() - ->setTitle(pht('Already Signed')) - ->appendParagraph(pht('You have already signed this document!')) - ->addCancelButton('/'.$document->getMonogram(), pht('Okay')); + $signed_status = null; + if (!$signature) { + $has_signed = false; + $signature = id(new LegalpadDocumentSignature()) + ->setSignerPHID($signer_phid) + ->setDocumentPHID($document->getPHID()) + ->setDocumentVersion($document->getVersions()); + + // If the user is logged in, show a notice that they haven't signed. + // If they aren't logged in, we can't be as sure, so don't show + // anything. + if ($viewer->isLoggedIn()) { + $signed_status = id(new AphrontErrorView()) + ->setSeverity(AphrontErrorView::SEVERITY_WARNING) + ->setErrors( + array( + pht('You have not signed this document yet.'), + )); + } + } else { + $has_signed = true; + $signature_data = $signature->getSignatureData(); + + // In this case, we know they've signed. + $signed_at = $signature->getDateCreated(); + + if ($signature->getIsExemption()) { + $exemption_phid = $signature->getExemptionPHID(); + $handles = $this->loadViewerHandles(array($exemption_phid)); + $exemption_handle = $handles[$exemption_phid]; + + $signed_text = pht( + 'You do not need to sign this document. '. + '%s added a signature exemption for you on %s.', + $exemption_handle->renderLink(), + phabricator_datetime($signed_at, $viewer)); + } else { + $signed_text = pht( + 'You signed this document on %s.', + phabricator_datetime($signed_at, $viewer)); + } + + $signed_status = id(new AphrontErrorView()) + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) + ->setErrors(array($signed_text)); } - } - $signed_status = null; - if (!$signature) { - $has_signed = false; + $field_errors = array( + 'name' => true, + 'email' => true, + 'agree' => true, + ); + } else { $signature = id(new LegalpadDocumentSignature()) - ->setSignerPHID($signer_phid) ->setDocumentPHID($document->getPHID()) - ->setDocumentVersion($document->getVersions()) - ->setSignerName((string)idx($signature_data, 'name')) - ->setSignerEmail((string)idx($signature_data, 'email')) - ->setSignatureData($signature_data); + ->setDocumentVersion($document->getVersions()); - // If the user is logged in, show a notice that they haven't signed. - // If they aren't logged in, we can't be as sure, so don't show anything. if ($viewer->isLoggedIn()) { + $has_signed = false; + + $signed_status = null; + } else { + // This just hides the form. + $has_signed = true; + + $login_text = pht( + 'This document requires a corporate signatory. You must log in to '. + 'accept this document on behalf of a company you represent.'); $signed_status = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_WARNING) - ->setErrors( - array( - pht('You have not signed this document yet.'), - )); - } - } else { - $has_signed = true; - $signature_data = $signature->getSignatureData(); - - // In this case, we know they've signed. - $signed_at = $signature->getDateCreated(); - - if ($signature->getIsExemption()) { - $exemption_phid = $signature->getExemptionPHID(); - $handles = $this->loadViewerHandles(array($exemption_phid)); - $exemption_handle = $handles[$exemption_phid]; - - $signed_text = pht( - 'You do not need to sign this document. '. - '%s added a signature exemption for you on %s.', - $exemption_handle->renderLink(), - phabricator_datetime($signed_at, $viewer)); - } else { - $signed_text = pht( - 'You signed this document on %s.', - phabricator_datetime($signed_at, $viewer)); + ->setErrors(array($login_text)); } - $signed_status = id(new AphrontErrorView()) - ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) - ->setErrors(array($signed_text)); + $field_errors = array( + 'name' => true, + 'address' => true, + 'contact.name' => true, + 'email' => true, + ); } - $e_name = true; - $e_email = true; - $e_agree = null; + $signature->setSignatureData($signature_data); $errors = array(); if ($request->isFormOrHisecPost() && !$has_signed) { // Require two-factor auth to sign legal documents. if ($viewer->isLoggedIn()) { $engine = new PhabricatorAuthSessionEngine(); $engine->requireHighSecuritySession( $viewer, $request, '/'.$document->getMonogram()); } - $name = $request->getStr('name'); - $agree = $request->getExists('agree'); - - if (!strlen($name)) { - $e_name = pht('Required'); - $errors[] = pht('Name field is required.'); - } else { - $e_name = null; - } - $signature_data['name'] = $name; - - if ($viewer->isLoggedIn()) { - $email = $viewer->loadPrimaryEmailAddress(); - } else { - $email = $request->getStr('email'); + list($form_data, $errors, $field_errors) = $this->readSignatureForm( + $document, + $request); - $addr_obj = null; - if (!strlen($email)) { - $e_email = pht('Required'); - $errors[] = pht('Email field is required.'); - } else { - $addr_obj = new PhutilEmailAddress($email); - $domain = $addr_obj->getDomainName(); - if (!$domain) { - $e_email = pht('Invalid'); - $errors[] = pht('A valid email is required.'); - } else { - $e_email = null; - } - } - } - $signature_data['email'] = $email; + $signature_data = $form_data + $signature_data; + $signature->setSignatureData($signature_data); + $signature->setSignatureType($document->getSignatureType()); $signature->setSignerName((string)idx($signature_data, 'name')); $signature->setSignerEmail((string)idx($signature_data, 'email')); - $signature->setSignatureData($signature_data); + $agree = $request->getExists('agree'); if (!$agree) { $errors[] = pht( 'You must check "I agree to the terms laid forth above."'); - $e_agree = pht('Required'); + $field_errors['agree'] = pht('Required'); } - if ($viewer->isLoggedIn()) { + if ($viewer->isLoggedIn() && $is_individual) { $verified = LegalpadDocumentSignature::VERIFIED; } else { $verified = LegalpadDocumentSignature::UNVERIFIED; } $signature->setVerified($verified); if (!$errors) { $signature->save(); // If the viewer is logged in, send them to the document page, which // will show that they have signed the document. Otherwise, send them // to a completion page. - if ($viewer->isLoggedIn()) { + if ($viewer->isLoggedIn() && $is_individual) { $next_uri = '/'.$document->getMonogram(); } else { + $this->sendVerifySignatureEmail( + $document, + $signature); + $next_uri = $this->getApplicationURI('done/'); } return id(new AphrontRedirectResponse())->setURI($next_uri); } } $document_body = $document->getDocumentBody(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer); $engine->addObject( $document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); $engine->process(); $document_markup = $engine->getOutput( $document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); $title = $document_body->getTitle(); $manage_uri = $this->getApplicationURI('view/'.$document->getID().'/'); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $document, PhabricatorPolicyCapability::CAN_EDIT); $header = id(new PHUIHeaderView()) ->setHeader($title) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setIcon( id(new PHUIIconView()) ->setIconFont('fa-pencil')) ->setText(pht('Manage Document')) ->setHref($manage_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $content = id(new PHUIDocumentView()) ->addClass('legalpad') ->setHeader($header) ->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS) ->appendChild( array( $signed_status, $document_markup, )); if (!$has_signed) { $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setErrors($errors); } $signature_form = $this->buildSignatureForm( - $document_body, + $document, $signature, - $e_name, - $e_email, - $e_agree); + $field_errors); $subheader = id(new PHUIHeaderView()) ->setHeader(pht('Agree and Sign Document')) ->setBleedHeader(true); $content->appendChild( array( $subheader, $error_view, $signature_form, )); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($document->getMonogram()); return $this->buildApplicationPage( array( $crumbs, $content, ), array( 'title' => $title, 'pageObjects' => array($document->getPHID()), )); } + private function readSignerInformation( + LegalpadDocument $document, + AphrontRequest $request) { + + $viewer = $request->getUser(); + $signer_phid = null; + $signature_data = array(); + + switch ($document->getSignatureType()) { + case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: + if ($viewer->isLoggedIn()) { + $signer_phid = $viewer->getPHID(); + $signature_data = array( + 'name' => $viewer->getRealName(), + 'email' => $viewer->loadPrimaryEmailAddress(), + ); + } else if ($request->isFormPost()) { + $email = new PhutilEmailAddress($request->getStr('email')); + if (strlen($email->getDomainName())) { + $email_obj = id(new PhabricatorUserEmail()) + ->loadOneWhere('address = %s', $email->getAddress()); + if ($email_obj) { + return $this->signInResponse(); + } + $external_account = id(new PhabricatorExternalAccountQuery()) + ->setViewer($viewer) + ->withAccountTypes(array('email')) + ->withAccountDomains(array($email->getDomainName())) + ->withAccountIDs(array($email->getAddress())) + ->loadOneOrCreate(); + if ($external_account->getUserPHID()) { + return $this->signInResponse(); + } + $signer_phid = $external_account->getPHID(); + } + } + break; + case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: + $signer_phid = $viewer->getPHID(); + if ($signer_phid) { + $signature_data = array( + 'contact.name' => $viewer->getRealName(), + 'email' => $viewer->loadPrimaryEmailAddress(), + 'actorPHID' => $viewer->getPHID(), + ); + } + break; + } + + return array($signer_phid, $signature_data); + } + private function buildSignatureForm( - LegalpadDocumentBody $body, + LegalpadDocument $document, LegalpadDocumentSignature $signature, - $e_name, - $e_email, - $e_agree) { + array $errors) { $viewer = $this->getRequest()->getUser(); $data = $signature->getSignatureData(); $form = id(new AphrontFormView()) - ->setUser($viewer) + ->setUser($viewer); + + $signature_type = $document->getSignatureType(); + switch ($signature_type) { + case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: + $this->buildIndividualSignatureForm( + $form, + $document, + $signature, + $errors); + break; + case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: + $this->buildCorporateSignatureForm( + $form, + $document, + $signature, + $errors); + break; + default: + throw new Exception( + pht( + 'This document has an unknown signature type ("%s").', + $signature_type)); + } + + $form + ->appendChild( + id(new AphrontFormCheckboxControl()) + ->setError(idx($errors, 'agree', null)) + ->addCheckbox( + 'agree', + 'agree', + pht('I agree to the terms laid forth above.'), + false)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Sign Document')) + ->addCancelButton($this->getApplicationURI())); + + return $form; + } + + private function buildIndividualSignatureForm( + AphrontFormView $form, + LegalpadDocument $document, + LegalpadDocumentSignature $signature, + array $errors) { + + $data = $signature->getSignatureData(); + + $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setValue(idx($data, 'name', '')) ->setName('name') - ->setError($e_name)); + ->setError(idx($errors, 'name', null))); + $viewer = $this->getRequest()->getUser(); if (!$viewer->isLoggedIn()) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Email')) ->setValue(idx($data, 'email', '')) ->setName('email') - ->setError($e_email)); + ->setError(idx($errors, 'email', null))); } + return $form; + } + + private function buildCorporateSignatureForm( + AphrontFormView $form, + LegalpadDocument $document, + LegalpadDocumentSignature $signature, + array $errors) { + + $data = $signature->getSignatureData(); + $form ->appendChild( - id(new AphrontFormCheckboxControl()) - ->setError($e_agree) - ->addCheckbox( - 'agree', - 'agree', - pht('I agree to the terms laid forth above.'), - false)) + id(new AphrontFormTextControl()) + ->setLabel(pht('Company Name')) + ->setValue(idx($data, 'name', '')) + ->setName('name') + ->setError(idx($errors, 'name', null))) ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Sign Document')) - ->addCancelButton($this->getApplicationURI())); + id(new AphrontFormTextAreaControl()) + ->setLabel(pht('Company Address')) + ->setValue(idx($data, 'address', '')) + ->setName('address') + ->setError(idx($errors, 'address', null))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Contact Name')) + ->setValue(idx($data, 'contact.name', '')) + ->setName('contact.name') + ->setError(idx($errors, 'contact.name', null))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Contact Email')) + ->setValue(idx($data, 'email', '')) + ->setName('email') + ->setError(idx($errors, 'email', null))); return $form; } + private function readSignatureForm( + LegalpadDocument $document, + AphrontRequest $request) { + + $signature_type = $document->getSignatureType(); + switch ($signature_type) { + case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: + $result = $this->readIndividualSignatureForm( + $document, + $request); + break; + case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: + $result = $this->readCorporateSignatureForm( + $document, + $request); + break; + default: + throw new Exception( + pht( + 'This document has an unknown signature type ("%s").', + $signature_type)); + } + + return $result; + } + + private function readIndividualSignatureForm( + LegalpadDocument $document, + AphrontRequest $request) { + + $signature_data = array(); + $errors = array(); + $field_errors = array(); + + + $name = $request->getStr('name'); + + if (!strlen($name)) { + $field_errors['name'] = pht('Required'); + $errors[] = pht('Name field is required.'); + } else { + $field_errors['name'] = null; + } + $signature_data['name'] = $name; + + $viewer = $request->getUser(); + if ($viewer->isLoggedIn()) { + $email = $viewer->loadPrimaryEmailAddress(); + } else { + $email = $request->getStr('email'); + + $addr_obj = null; + if (!strlen($email)) { + $field_errors['email'] = pht('Required'); + $errors[] = pht('Email field is required.'); + } else { + $addr_obj = new PhutilEmailAddress($email); + $domain = $addr_obj->getDomainName(); + if (!$domain) { + $field_errors['email'] = pht('Invalid'); + $errors[] = pht('A valid email is required.'); + } else { + $field_errors['email'] = null; + } + } + } + $signature_data['email'] = $email; + + return array($signature_data, $errors, $field_errors); + } + + private function readCorporateSignatureForm( + LegalpadDocument $document, + AphrontRequest $request) { + + $viewer = $request->getUser(); + if (!$viewer->isLoggedIn()) { + throw new Exception( + pht( + 'You can not sign a document on behalf of a corporation unless '. + 'you are logged in.')); + } + + $signature_data = array(); + $errors = array(); + $field_errors = array(); + + $name = $request->getStr('name'); + + if (!strlen($name)) { + $field_errors['name'] = pht('Required'); + $errors[] = pht('Company name is required.'); + } else { + $field_errors['name'] = null; + } + $signature_data['name'] = $name; + + $address = $request->getStr('address'); + if (!strlen($address)) { + $field_errors['address'] = pht('Required'); + $errors[] = pht('Company address is required.'); + } else { + $field_errors['address'] = null; + } + $signature_data['address'] = $address; + + $contact_name = $request->getStr('contact.name'); + if (!strlen($contact_name)) { + $field_errors['contact.name'] = pht('Required'); + $errors[] = pht('Contact name is required.'); + } else { + $field_errors['contact.name'] = null; + } + $signature_data['contact.name'] = $contact_name; + + $email = $request->getStr('email'); + $addr_obj = null; + if (!strlen($email)) { + $field_errors['email'] = pht('Required'); + $errors[] = pht('Contact email is required.'); + } else { + $addr_obj = new PhutilEmailAddress($email); + $domain = $addr_obj->getDomainName(); + if (!$domain) { + $field_errors['email'] = pht('Invalid'); + $errors[] = pht('A valid email is required.'); + } else { + $field_errors['email'] = null; + } + } + $signature_data['email'] = $email; + + return array($signature_data, $errors, $field_errors); + } + private function sendVerifySignatureEmail( LegalpadDocument $doc, LegalpadDocumentSignature $signature) { $signature_data = $signature->getSignatureData(); $email = new PhutilEmailAddress($signature_data['email']); - $doc_link = PhabricatorEnv::getProductionURI($doc->getMonogram()); + $doc_name = $doc->getTitle(); + $doc_link = PhabricatorEnv::getProductionURI('/'.$doc->getMonogram()); $path = $this->getApplicationURI(sprintf( '/verify/%s/', $signature->getSecretKey())); $link = PhabricatorEnv::getProductionURI($path); + $name = idx($signature_data, 'name'); + $body = <<addRawTos(array($email->getAddress())) ->setSubject(pht('[Legalpad] Signature Verification')) ->setBody($body) ->setRelatedPHID($signature->getDocumentPHID()) ->saveAndSend(); } private function signInResponse() { return id(new Aphront403Response()) ->setForbiddenText(pht( 'The email address specified is associated with an account. '. 'Please login to that account and sign this document again.')); } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureAddController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureAddController.php index a9585f4b00..e85e7a70c7 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureAddController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureAddController.php @@ -1,127 +1,168 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $document = id(new LegalpadDocumentQuery()) ->setViewer($viewer) ->needDocumentBodies(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($this->id)) ->executeOne(); if (!$document) { return new Aphront404Response(); } $next_uri = $this->getApplicationURI('signatures/'.$document->getID().'/'); + $e_name = true; $e_user = true; $v_users = array(); $v_notes = ''; + $v_name = ''; $errors = array(); + $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + $is_individual = ($document->getSignatureType() == $type_individual); + if ($request->isFormPost()) { $v_notes = $request->getStr('notes'); $v_users = array_slice($request->getArr('users'), 0, 1); + $v_name = $request->getStr('name'); - $user_phid = head($v_users); - if (!$user_phid) { - $e_user = pht('Required'); - $errors[] = pht('You must choose a user to exempt.'); - } else { - $user = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($user_phid)) - ->executeOne(); - - if (!$user) { - $e_user = pht('Invalid'); - $errors[] = pht('That user does not exist.'); + if ($is_individual) { + $user_phid = head($v_users); + if (!$user_phid) { + $e_user = pht('Required'); + $errors[] = pht('You must choose a user to exempt.'); } else { - $signature = id(new LegalpadDocumentSignatureQuery()) + $user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) - ->withDocumentPHIDs(array($document->getPHID())) - ->withSignerPHIDs(array($user->getPHID())) + ->withPHIDs(array($user_phid)) ->executeOne(); - if ($signature) { - $e_user = pht('Signed'); - $errors[] = pht('That user has already signed this document.'); + + if (!$user) { + $e_user = pht('Invalid'); + $errors[] = pht('That user does not exist.'); } else { - $e_user = null; + $signature = id(new LegalpadDocumentSignatureQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->withSignerPHIDs(array($user->getPHID())) + ->executeOne(); + if ($signature) { + $e_user = pht('Signed'); + $errors[] = pht('That user has already signed this document.'); + } else { + $e_user = null; + } } } + } else { + $company_name = $v_name; + if (!strlen($company_name)) { + $e_name = pht('Required'); + $errors[] = pht('You must choose a company to add an exemption for.'); + } } if (!$errors) { - $name = $user->getRealName(); - $email = $user->loadPrimaryEmailAddress(); + if ($is_individual) { + $name = $user->getRealName(); + $email = $user->loadPrimaryEmailAddress(); + $signer_phid = $user->getPHID(); + $signature_data = array( + 'name' => $name, + 'email' => $email, + 'notes' => $v_notes, + ); + } else { + $name = $company_name; + $email = ''; + $signer_phid = null; + $signature_data = array( + 'name' => $name, + 'email' => null, + 'notes' => $v_notes, + 'actorPHID' => $viewer->getPHID(), + ); + } $signature = id(new LegalpadDocumentSignature()) ->setDocumentPHID($document->getPHID()) ->setDocumentVersion($document->getVersions()) - ->setSignerPHID($user->getPHID()) + ->setSignerPHID($signer_phid) ->setSignerName($name) ->setSignerEmail($email) + ->setSignatureType($document->getSignatureType()) ->setIsExemption(1) ->setExemptionPHID($viewer->getPHID()) ->setVerified(LegalpadDocumentSignature::VERIFIED) - ->setSignatureData( - array( - 'name' => $name, - 'email' => $email, - 'notes' => $v_notes, - )); + ->setSignatureData($signature_data); $signature->save(); return id(new AphrontRedirectResponse())->setURI($next_uri); } } - $user_handles = $this->loadViewerHandles($v_users); - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Exempt User')) - ->setName('users') - ->setLimit(1) - ->setDatasource('/typeahead/common/users/') - ->setValue($user_handles) - ->setError($e_user)) + ->setUser($viewer); + + if ($is_individual) { + $user_handles = $this->loadViewerHandles($v_users); + $form + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setLabel(pht('Exempt User')) + ->setName('users') + ->setLimit(1) + ->setDatasource('/typeahead/common/users/') + ->setValue($user_handles) + ->setError($e_user)); + } else { + $form + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Company Name')) + ->setName('name') + ->setError($e_name) + ->setValue($v_name)); + } + + $form ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Notes')) ->setName('notes') ->setValue($v_notes)); return $this->newDialog() ->setTitle(pht('Add Signature Exemption')) ->setWidth(AphrontDialogView::WIDTH_FORM) ->setErrors($errors) ->appendParagraph( pht( 'You can record a signature exemption if a user has signed an '. 'equivalent document. Other applications will behave as through the '. 'user has signed this document.')) ->appendParagraph(null) ->appendChild($form->buildLayoutView()) ->addSubmitButton(pht('Add Exemption')) ->addCancelButton($next_uri); } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php index 6a61e226ac..7ad5d13946 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php @@ -1,93 +1,99 @@ code = $data['code']; - } - - public function shouldRequireEmailVerification() { - return false; + public function shouldAllowPublic() { + return true; } - public function shouldRequireLogin() { - return false; + public function willProcessRequest(array $data) { + $this->code = $data['code']; } public function processRequest() { $request = $this->getRequest(); - $user = $request->getUser(); - - // this page can be accessed by not logged in users to valid their - // signatures. use the omnipotent user for these cases. - if (!$user->isLoggedIn()) { - $viewer = PhabricatorUser::getOmnipotentUser(); - } else { - $viewer = $user; - } + $viewer = $request->getUser(); + // NOTE: We're using the omnipotent user to handle logged-out signatures + // and corporate signatures. $signature = id(new LegalpadDocumentSignatureQuery()) - ->setViewer($viewer) + ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withSecretKeys(array($this->code)) ->executeOne(); if (!$signature) { - $title = pht('Unable to Verify Signature'); - $content = pht( - 'The verification code you provided is incorrect or the signature '. - 'has been removed. '. - 'Make sure you followed the link in the email correctly.'); - $uri = $this->getApplicationURI(); - $continue = pht('Rats!'); - } else { - $document = id(new LegalpadDocumentQuery()) - ->setViewer($user) - ->withPHIDs(array($signature->getDocumentPHID())) - ->executeOne(); - // the document could be deleted or have its permissions changed - // 4oh4 time - if (!$document) { - return new Aphront404Response(); - } - $uri = '/'.$document->getMonogram(); - if ($signature->isVerified()) { - $title = pht('Signature Already Verified'); - $content = pht( - 'This signature has already been verified.'); - $continue = pht('Continue to Legalpad Document'); - } else { - $guard = AphrontWriteGuard::beginScopedUnguardedWrites(); - $signature - ->setVerified(LegalpadDocumentSignature::VERIFIED) - ->save(); - unset($guard); - $title = pht('Signature Verified'); - $content = pht('The signature is now verified.'); - $continue = pht('Continue to Legalpad Document'); - } + return $this->newDialog() + ->setTitle(pht('Unable to Verify Signature')) + ->appendParagraph( + pht( + 'The signature verification code is incorrect, or the signature '. + 'has been invalidated. Make sure you followed the link in the '. + 'email correctly.')) + ->addCancelButton('/', pht('Rats!')); } - $dialog = id(new AphrontDialogView()) - ->setUser($user) - ->setTitle($title) - ->setMethod('GET') - ->addCancelButton($uri, $continue) - ->appendChild($content); + if ($signature->isVerified()) { + return $this->newDialog() + ->setTitle(pht('Signature Already Verified')) + ->appendParagraph( + pht( + 'This signature has already been verified.')) + ->addCancelButton('/', pht('Okay')); + } - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Verify Signature')); + if ($request->isFormPost()) { + $signature + ->setVerified(LegalpadDocumentSignature::VERIFIED) + ->save(); - return $this->buildApplicationPage( + return $this->newDialog() + ->setTitle(pht('Signature Verified')) + ->appendParagraph(pht('The signature is now verified.')) + ->addCancelButton('/', pht('Okay')); + } + + $document_link = phutil_tag( + 'a', array( - $crumbs, - $dialog, + 'href' => '/'.$signature->getDocument()->getMonogram(), + 'target' => '_blank', ), - array( - 'title' => pht('Verify Signature'), - )); + $signature->getDocument()->getTitle()); + + $signed_at = phabricator_datetime($signature->getDateCreated(), $viewer); + + $name = $signature->getSignerName(); + $email = $signature->getSignerEmail(); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendRemarkupInstructions( + pht('Please verify this document signature.')) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Document')) + ->setValue($document_link)) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Signed At')) + ->setValue($signed_at)) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Name')) + ->setValue($name)) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Email')) + ->setValue($email)); + + return $this->newDialog() + ->setTitle(pht('Verify Signature?')) + ->appendChild($form->buildLayoutView()) + ->addCancelButton('/') + ->addSubmitButton(pht('Verify Signature')); } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureViewController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureViewController.php index a21c1ebed4..6d58d1b693 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureViewController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureViewController.php @@ -1,71 +1,112 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $signature = id(new LegalpadDocumentSignatureQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->executeOne(); if (!$signature) { return new Aphront404Response(); } // NOTE: In order to see signature details (which include the relatively // internal-feeling "notes" field) you must be able to edit the document. // Essentially, this power is for document managers. Notably, this prevents // users from seeing notes about their own exemptions by guessing their // signature ID. This is purely a policy check. $document = id(new LegalpadDocumentQuery()) ->setViewer($viewer) ->withIDs(array($signature->getDocument()->getID())) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$document) { return new Aphront404Response(); } $document_id = $signature->getDocument()->getID(); $next_uri = $this->getApplicationURI('signatures/'.$document_id.'/'); + $data = $signature->getSignatureData(); + $exemption_phid = $signature->getExemptionPHID(); - $handles = $this->loadViewerHandles(array($exemption_phid)); + $actor_phid = idx($data, 'actorPHID'); + $handles = $this->loadViewerHandles( + array( + $exemption_phid, + $actor_phid, + )); $exemptor_handle = $handles[$exemption_phid]; - - $data = $signature->getSignatureData(); + $actor_handle = $handles[$actor_phid]; $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Exemption By')) - ->setValue($exemptor_handle->renderLink())) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Notes')) - ->setValue(idx($data, 'notes'))); + ->setUser($viewer); + + if ($signature->getExemptionPHID()) { + $form + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Exemption By')) + ->setValue($exemptor_handle->renderLink())) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Notes')) + ->setValue(idx($data, 'notes'))); + } + + $type_corporation = LegalpadDocument::SIGNATURE_TYPE_CORPORATION; + if ($signature->getSignatureType() == $type_corporation) { + $form + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Signing User')) + ->setValue($actor_handle->renderLink())) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Company Name')) + ->setValue(idx($data, 'name'))) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Address')) + ->setValue(phutil_escape_html_newlines(idx($data, 'address')))) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Contact Name')) + ->setValue(idx($data, 'contact.name'))) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Contact Email')) + ->setValue( + phutil_tag( + 'a', + array( + 'href' => 'mailto:'.idx($data, 'email'), + ), + idx($data, 'email')))); + } return $this->newDialog() ->setTitle(pht('Signature Details')) ->setWidth(AphrontDialogView::WIDTH_FORM) ->appendChild($form->buildLayoutView()) ->addCancelButton($next_uri, pht('Close')); } } diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php index f24926c086..98205c46b9 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -1,196 +1,206 @@ isContribution = $is_contribution; } + private function isContribution() { return $this->isContribution; } public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = LegalpadTransactionType::TYPE_TITLE; $types[] = LegalpadTransactionType::TYPE_TEXT; + $types[] = LegalpadTransactionType::TYPE_SIGNATURE_TYPE; + return $types; } protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case LegalpadTransactionType::TYPE_TITLE: return $object->getDocumentBody()->getTitle(); case LegalpadTransactionType::TYPE_TEXT: return $object->getDocumentBody()->getText(); + case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: + return $object->getSignatureType(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case LegalpadTransactionType::TYPE_TITLE: case LegalpadTransactionType::TYPE_TEXT: + case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: return $xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case LegalpadTransactionType::TYPE_TITLE: $object->setTitle($xaction->getNewValue()); $body = $object->getDocumentBody(); $body->setTitle($xaction->getNewValue()); $this->setIsContribution(true); break; case LegalpadTransactionType::TYPE_TEXT: $body = $object->getDocumentBody(); $body->setText($xaction->getNewValue()); $this->setIsContribution(true); break; + case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: + $object->setSignatureType($xaction->getNewValue()); + break; } } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return; } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { if ($this->isContribution()) { $object->setVersions($object->getVersions() + 1); $body = $object->getDocumentBody(); $body->setVersion($object->getVersions()); $body->setDocumentPHID($object->getPHID()); $body->save(); $object->setDocumentBodyPHID($body->getPHID()); $actor = $this->getActor(); $type = PhabricatorEdgeConfig::TYPE_CONTRIBUTED_TO_OBJECT; id(new PhabricatorEdgeEditor()) ->addEdge($actor->getPHID(), $type, $object->getPHID()) ->setActor($actor) ->save(); $type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_CONTRIBUTOR; $contributors = PhabricatorEdgeQuery::loadDestinationPHIDs( $object->getPHID(), $type); $object->setRecentContributorPHIDs(array_slice($contributors, 0, 3)); $object->setContributorCount(count($contributors)); $object->save(); } return $xactions; } protected function mergeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { $type = $u->getTransactionType(); switch ($type) { case LegalpadTransactionType::TYPE_TITLE: case LegalpadTransactionType::TYPE_TEXT: + case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: return $v; } return parent::mergeTransactions($u, $v); } /* -( Sending Mail )------------------------------------------------------- */ protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new LegalpadReplyHandler()) ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $phid = $object->getPHID(); $title = $object->getDocumentBody()->getTitle(); return id(new PhabricatorMetaMTAMail()) ->setSubject("L{$id}: {$title}") ->addHeader('Thread-Topic', "L{$id}: {$phid}"); } protected function getMailTo(PhabricatorLiskDAO $object) { return array( $object->getCreatorPHID(), $this->requireActor()->getPHID(), ); } protected function shouldImplyCC( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case LegalpadTransactionType::TYPE_TEXT: case LegalpadTransactionType::TYPE_TITLE: return true; } return parent::shouldImplyCC($object, $xaction); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); $body->addTextSection( pht('DOCUMENT DETAIL'), PhabricatorEnv::getProductionURI('/legalpad/view/'.$object->getID().'/')); return $body; } protected function getMailSubjectPrefix() { return PhabricatorEnv::getEnvConfig('metamta.legalpad.subject-prefix'); } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { return false; } protected function supportsSearch() { return false; } } diff --git a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php index 7d2fe8d96d..9480273c7b 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php @@ -1,210 +1,215 @@ setParameter( 'creatorPHIDs', $this->readUsersFromRequest($request, 'creators')); $saved->setParameter( 'contributorPHIDs', $this->readUsersFromRequest($request, 'contributors')); $saved->setParameter( 'withViewerSignature', $request->getBool('withViewerSignature')); $saved->setParameter('createdStart', $request->getStr('createdStart')); $saved->setParameter('createdEnd', $request->getStr('createdEnd')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new LegalpadDocumentQuery()) ->needViewerSignatures(true); $creator_phids = $saved->getParameter('creatorPHIDs', array()); if ($creator_phids) { $query->withCreatorPHIDs($creator_phids); } $contributor_phids = $saved->getParameter('contributorPHIDs', array()); if ($contributor_phids) { $query->withContributorPHIDs($contributor_phids); } if ($saved->getParameter('withViewerSignature')) { $viewer_phid = $this->requireViewer()->getPHID(); if ($viewer_phid) { $query->withSignerPHIDs(array($viewer_phid)); } } $start = $this->parseDateTime($saved->getParameter('createdStart')); $end = $this->parseDateTime($saved->getParameter('createdEnd')); if ($start) { $query->withDateCreatedAfter($start); } if ($end) { $query->withDateCreatedBefore($end); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $creator_phids = $saved_query->getParameter('creatorPHIDs', array()); $contributor_phids = $saved_query->getParameter( 'contributorPHIDs', array()); $phids = array_merge($creator_phids, $contributor_phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); $viewer_signature = $saved_query->getParameter('withViewerSignature'); if (!$this->requireViewer()->getPHID()) { $viewer_signature = false; } $form ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'withViewerSignature', 1, pht('Show only documents I have signed.'), $viewer_signature) ->setDisabled(!$this->requireViewer()->getPHID())) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('creators') ->setLabel(pht('Creators')) ->setValue(array_select_keys($handles, $creator_phids))) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('contributors') ->setLabel(pht('Contributors')) ->setValue(array_select_keys($handles, $contributor_phids))); $this->buildDateRange( $form, $saved_query, 'createdStart', pht('Created After'), 'createdEnd', pht('Created Before')); } protected function getURI($path) { return '/legalpad/'.$path; } public function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['signed'] = pht('Signed Documents'); } $names['all'] = pht('All Documents'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'signed': return $query ->setParameter('withViewerSignature', true); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( array $documents, PhabricatorSavedQuery $query) { return array(); } protected function renderResultList( array $documents, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($documents, 'LegalpadDocument'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); $list->setUser($viewer); foreach ($documents as $document) { $last_updated = phabricator_date($document->getDateModified(), $viewer); $title = $document->getTitle(); + $type_name = $document->getSignatureTypeName(); + $type_icon = $document->getSignatureTypeIcon(); + $item = id(new PHUIObjectItemView()) ->setObjectName($document->getMonogram()) ->setHeader($title) ->setHref('/'.$document->getMonogram()) ->setObject($document) - ->addIcon('none', pht('Version %d', $document->getVersions())) - ->addIcon('none', pht('Updated %s', $last_updated)); + ->addIcon($type_icon, $type_name) + ->addIcon( + 'fa-pencil grey', + pht('Version %d (%s)', $document->getVersions(), $last_updated)); if ($viewer->getPHID()) { $signature = $document->getUserSignature($viewer->getPHID()); } else { $signature = null; } if ($signature) { $item->addAttribute( array( id(new PHUIIconView())->setIconFont('fa-check-square-o', 'green'), ' ', pht( 'Signed on %s', phabricator_date($signature->getDateCreated(), $viewer)), )); } else { $item->addAttribute( array( id(new PHUIIconView())->setIconFont('fa-square-o', 'grey'), ' ', pht('Not Signed'), )); } $list->addItem($item); } return $list; } } diff --git a/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php index 7a897e7908..2883c6bbef 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php @@ -1,312 +1,327 @@ 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, ))); $saved->setParameter('nameContains', $request->getStr('nameContains')); $saved->setParameter('emailContains', $request->getStr('emailContains')); 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); } } $name_contains = $saved->getParameter('nameContains'); if (strlen($name_contains)) { $query->withNameContains($name_contains); } $email_contains = $saved->getParameter('emailContains'); if (strlen($email_contains)) { $query->withEmailContains($email_contains); } 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($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))); } $name_contains = $saved_query->getParameter('nameContains', ''); $email_contains = $saved_query->getParameter('emailContains', ''); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('signers') ->setLabel(pht('Signers')) ->setValue(array_select_keys($handles, $signer_phids))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name Contains')) ->setName('nameContains') ->setValue($name_contains)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Email Contains')) ->setName('emailContains') ->setValue($email_contains)); } protected function getURI($path) { if ($this->document) { return '/legalpad/signatures/'.$this->document->getID().'/'.$path; } else { 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 $signatures, PhabricatorSavedQuery $query) { 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_corp = $this->renderIcon( + 'fa-building-o', + null, + pht('Verified, Corporate')); + $sig_old = $this->renderIcon( 'fa-clock-o', 'orange', pht('Signed Older Version')); $sig_unverified = $this->renderIcon( 'fa-envelope', 'red', pht('Unverified Email')); $sig_exemption = $this->renderIcon( 'fa-asterisk', 'indigo', pht('Exemption')); id(new PHUIIconView()) ->setIconFont('fa-envelope', 'red') ->addSigil('has-tooltip') ->setMetadata(array('tip' => pht('Unverified Email'))); + $type_corporate = LegalpadDocument::SIGNATURE_TYPE_CORPORATION; + $rows = array(); foreach ($signatures as $signature) { $name = $signature->getSignerName(); $email = $signature->getSignerEmail(); $document = $signature->getDocument(); if ($signature->getIsExemption()) { - $signature_href = $this->getApplicationURI( - 'signature/'.$signature->getID().'/'); - - $sig_icon = javelin_tag( - 'a', - array( - 'href' => $signature_href, - 'sigil' => 'workflow', - ), - $sig_exemption); + $sig_icon = $sig_exemption; } else if (!$signature->isVerified()) { $sig_icon = $sig_unverified; } else if ($signature->getDocumentVersion() != $document->getVersions()) { $sig_icon = $sig_old; + } else if ($signature->getSignatureType() == $type_corporate) { + $sig_icon = $sig_corp; } else { $sig_icon = $sig_good; } + $signature_href = $this->getApplicationURI( + 'signature/'.$signature->getID().'/'); + + $sig_icon = javelin_tag( + 'a', + array( + 'href' => $signature_href, + 'sigil' => 'workflow', + ), + $sig_icon); + + $signer_phid = $signature->getSignerPHID(); + $rows[] = array( $sig_icon, $handles[$document->getPHID()]->renderLink(), - $handles[$signature->getSignerPHID()]->renderLink(), + $signer_phid + ? $handles[$signer_phid]->renderLink() + : null, $name, phutil_tag( 'a', array( 'href' => 'mailto:'.$email, ), $email), phabricator_datetime($signature->getDateCreated(), $viewer), ); } $table = id(new AphrontTableView($rows)) ->setNoDataString(pht('No signatures match the query.')) ->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', )); $header = id(new PHUIHeaderView()) ->setHeader(pht('Signatures')); if ($this->document) { $document_id = $this->document->getID(); $header->addActionLink( id(new PHUIButtonView()) ->setText(pht('Add Signature Exemption')) ->setTag('a') ->setHref($this->getApplicationURI('addsignature/'.$document_id.'/')) ->setWorkflow(true) ->setIcon(id(new PHUIIconView())->setIconFont('fa-pencil'))); } $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->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), ); } } diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php index a29047c4e8..7cc4864438 100644 --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -1,201 +1,228 @@ setViewer($actor) ->withClasses(array('PhabricatorApplicationLegalpad')) ->executeOne(); $view_policy = $app->getPolicy(LegalpadCapabilityDefaultView::CAPABILITY); $edit_policy = $app->getPolicy(LegalpadCapabilityDefaultEdit::CAPABILITY); return id(new LegalpadDocument()) ->setVersions(0) ->setCreatorPHID($actor->getPHID()) ->setContributorCount(0) ->setRecentContributorPHIDs(array()) ->attachSignatures(array()) + ->setSignatureType(self::SIGNATURE_TYPE_INDIVIDUAL) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'recentContributorPHIDs' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorLegalpadPHIDTypeDocument::TYPECONST); } public function getDocumentBody() { return $this->assertAttached($this->documentBody); } public function attachDocumentBody(LegalpadDocumentBody $body) { $this->documentBody = $body; return $this; } public function getContributors() { return $this->assertAttached($this->contributors); } public function attachContributors(array $contributors) { $this->contributors = $contributors; return $this; } public function getSignatures() { return $this->assertAttached($this->signatures); } public function attachSignatures(array $signatures) { $this->signatures = $signatures; return $this; } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function getMonogram() { return 'L'.$this->getID(); } public function getUserSignature($phid) { return $this->assertAttachedKey($this->userSignatures, $phid); } public function attachUserSignature( $user_phid, LegalpadDocumentSignature $signature = null) { $this->userSignatures[$user_phid] = $signature; return $this; } + public static function getSignatureTypeMap() { + return array( + self::SIGNATURE_TYPE_INDIVIDUAL => pht('Individuals'), + self::SIGNATURE_TYPE_CORPORATION => pht('Corporations'), + ); + } + + public function getSignatureTypeName() { + $type = $this->getSignatureType(); + return idx(self::getSignatureTypeMap(), $type, $type); + } + + public function getSignatureTypeIcon() { + $type = $this->getSignatureType(); + $map = array( + self::SIGNATURE_TYPE_INDIVIDUAL => 'fa-user grey', + self::SIGNATURE_TYPE_CORPORATION => 'fa-building-o grey', + ); + + return idx($map, $type, 'fa-user grey'); + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ public function isAutomaticallySubscribed($phid) { return ($this->creatorPHID == $phid); } public function shouldShowSubscribersProperty() { return true; } public function shouldAllowSubscription($phid) { return true; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: $policy = $this->viewPolicy; break; case PhabricatorPolicyCapability::CAN_EDIT: $policy = $this->editPolicy; break; default: $policy = PhabricatorPolicies::POLICY_NOONE; break; } return $policy; } public function hasAutomaticCapability($capability, PhabricatorUser $user) { return ($user->getPHID() == $this->getCreatorPHID()); } public function describeAutomaticCapability($capability) { return pht( 'The author of a document can always view and edit it.'); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new LegalpadDocumentEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new LegalpadTransaction(); } /* -( PhabricatorDestructableInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); $bodies = id(new LegalpadDocumentBody())->loadAllWhere( 'documentPHID = %s', $this->getPHID()); foreach ($bodies as $body) { $body->delete(); } $signatures = id(new LegalpadDocumentSignature())->loadAllWhere( 'documentPHID = %s', $this->getPHID()); foreach ($signatures as $signature) { $signature->delete(); } $this->saveTransaction(); } } diff --git a/src/applications/legalpad/storage/LegalpadDocumentSignature.php b/src/applications/legalpad/storage/LegalpadDocumentSignature.php index 50dfa4cdc7..d56b684eca 100644 --- a/src/applications/legalpad/storage/LegalpadDocumentSignature.php +++ b/src/applications/legalpad/storage/LegalpadDocumentSignature.php @@ -1,77 +1,78 @@ array( 'signatureData' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function save() { if (!$this->getSecretKey()) { $this->setSecretKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function isVerified() { return ($this->getVerified() != self::UNVERIFIED); } public function getDocument() { return $this->assertAttached($this->document); } public function attachDocument(LegalpadDocument $document) { $this->document = $document; return $this; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getDocument()->getPolicy( PhabricatorPolicyCapability::CAN_EDIT); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return ($viewer->getPHID() == $this->getSignerPHID()); } public function describeAutomaticCapability($capability) { return null; } }