mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-19 20:10:55 +01:00
Streamline Legalpad signature workflow
Summary: Generally reduces friction, standardizes, and simplifies this workflow. Particularly, this removes "address" and "phone", which I think we can wait for user demand for. For logged-in users, we just always use their primary email. Test Plan: See screenshots. Reviewers: chad Reviewed By: chad Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D9735
This commit is contained in:
parent
3d1e865804
commit
950d3668f9
7 changed files with 184 additions and 163 deletions
|
@ -7,7 +7,7 @@
|
|||
return array(
|
||||
'names' =>
|
||||
array(
|
||||
'core.pkg.css' => 'e428441c',
|
||||
'core.pkg.css' => '3f0f5da2',
|
||||
'core.pkg.js' => '8c184823',
|
||||
'darkconsole.pkg.js' => 'df001cab',
|
||||
'differential.pkg.css' => '4a93db37',
|
||||
|
@ -20,7 +20,7 @@ return array(
|
|||
'rsrc/css/aphront/context-bar.css' => '1c3b0529',
|
||||
'rsrc/css/aphront/dark-console.css' => '6378ef3d',
|
||||
'rsrc/css/aphront/dialog-view.css' => '4dbbe3bb',
|
||||
'rsrc/css/aphront/error-view.css' => '9f1d5518',
|
||||
'rsrc/css/aphront/error-view.css' => '3462dbee',
|
||||
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
|
||||
'rsrc/css/aphront/list-filter-view.css' => '2ae43867',
|
||||
'rsrc/css/aphront/multi-column.css' => '1b95ab2e',
|
||||
|
@ -499,7 +499,7 @@ return array(
|
|||
'aphront-contextbar-view-css' => '1c3b0529',
|
||||
'aphront-dark-console-css' => '6378ef3d',
|
||||
'aphront-dialog-view-css' => '4dbbe3bb',
|
||||
'aphront-error-view-css' => '9f1d5518',
|
||||
'aphront-error-view-css' => '3462dbee',
|
||||
'aphront-list-filter-view-css' => '2ae43867',
|
||||
'aphront-multi-column-view-css' => '1b95ab2e',
|
||||
'aphront-pager-view-css' => '2e3539af',
|
||||
|
|
|
@ -860,6 +860,7 @@ phutil_register_library_map(array(
|
|||
'LegalpadDocument' => 'applications/legalpad/storage/LegalpadDocument.php',
|
||||
'LegalpadDocumentBody' => 'applications/legalpad/storage/LegalpadDocumentBody.php',
|
||||
'LegalpadDocumentCommentController' => 'applications/legalpad/controller/LegalpadDocumentCommentController.php',
|
||||
'LegalpadDocumentDoneController' => 'applications/legalpad/controller/LegalpadDocumentDoneController.php',
|
||||
'LegalpadDocumentEditController' => 'applications/legalpad/controller/LegalpadDocumentEditController.php',
|
||||
'LegalpadDocumentEditor' => 'applications/legalpad/editor/LegalpadDocumentEditor.php',
|
||||
'LegalpadDocumentListController' => 'applications/legalpad/controller/LegalpadDocumentListController.php',
|
||||
|
@ -3613,6 +3614,7 @@ phutil_register_library_map(array(
|
|||
1 => 'PhabricatorMarkupInterface',
|
||||
),
|
||||
'LegalpadDocumentCommentController' => 'LegalpadController',
|
||||
'LegalpadDocumentDoneController' => 'LegalpadController',
|
||||
'LegalpadDocumentEditController' => 'LegalpadController',
|
||||
'LegalpadDocumentEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'LegalpadDocumentListController' => 'LegalpadController',
|
||||
|
|
|
@ -46,6 +46,7 @@ final class PhabricatorApplicationLegalpad extends PhabricatorApplication {
|
|||
'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+)/' => 'LegalpadDocumentSignatureListController',
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
final class LegalpadDocumentDoneController extends LegalpadController {
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Verify Signature'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Thank you for signing this document. Please check your email '.
|
||||
'to verify your signature and complete the process.'))
|
||||
->addCancelButton('/', pht('Okay'));
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,10 @@ final class LegalpadDocumentListController extends LegalpadController {
|
|||
|
||||
private $queryKey;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->queryKey = idx($data, 'queryKey');
|
||||
}
|
||||
|
|
|
@ -21,145 +21,162 @@ final class LegalpadDocumentSignController extends LegalpadController {
|
|||
->withIDs(array($this->id))
|
||||
->needDocumentBodies(true)
|
||||
->executeOne();
|
||||
|
||||
if (!$document) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$signer_phid = null;
|
||||
$signature = null;
|
||||
$signature_data = array();
|
||||
if ($viewer->isLoggedIn()) {
|
||||
$signer_phid = $viewer->getPHID();
|
||||
$signature_data = array(
|
||||
'email' => $viewer->loadPrimaryEmailAddress());
|
||||
'name' => $viewer->getRealName(),
|
||||
'email' => $viewer->loadPrimaryEmailAddress(),
|
||||
);
|
||||
} else if ($request->isFormPost()) {
|
||||
$email = new PhutilEmailAddress($request->getStr('email'));
|
||||
$email_obj = id(new PhabricatorUserEmail())
|
||||
->loadOneWhere('address = %s', $email->getAddress());
|
||||
if ($email_obj) {
|
||||
return $this->signInResponse();
|
||||
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();
|
||||
}
|
||||
$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();
|
||||
}
|
||||
|
||||
$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($viewer)
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withDocumentPHIDs(array($document->getPHID()))
|
||||
->withSignerPHIDs(array($signer_phid))
|
||||
->withDocumentVersions(array($document->getVersions()))
|
||||
->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;
|
||||
$error_view = null;
|
||||
$signature = id(new LegalpadDocumentSignature())
|
||||
->setSignerPHID($signer_phid)
|
||||
->setDocumentPHID($document->getPHID())
|
||||
->setDocumentVersion($document->getVersions())
|
||||
->setSignatureData($signature_data);
|
||||
|
||||
// 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;
|
||||
if ($signature->isVerified()) {
|
||||
$title = pht('Already Signed');
|
||||
$body = $this->getVerifiedSignatureBlurb();
|
||||
} else {
|
||||
$title = pht('Already Signed but...');
|
||||
$body = $this->getUnverifiedSignatureBlurb();
|
||||
}
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
->setTitle($title)
|
||||
->appendChild($body);
|
||||
$signature_data = $signature->getSignatureData();
|
||||
|
||||
// In this case, we know they've signed.
|
||||
$signed_at = $signature->getDateCreated();
|
||||
$signed_status = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
->setErrors(
|
||||
array(
|
||||
pht(
|
||||
'You signed this document on %s.',
|
||||
phabricator_datetime($signed_at, $viewer)),
|
||||
));
|
||||
}
|
||||
|
||||
$e_name = true;
|
||||
$e_email = true;
|
||||
$e_address_1 = true;
|
||||
$e_agree = null;
|
||||
|
||||
$errors = array();
|
||||
if ($request->isFormPost() && !$has_signed) {
|
||||
$name = $request->getStr('name');
|
||||
$email = $request->getStr('email');
|
||||
$address_1 = $request->getStr('address_1');
|
||||
$address_2 = $request->getStr('address_2');
|
||||
$phone = $request->getStr('phone');
|
||||
$agree = $request->getExists('agree');
|
||||
|
||||
if (!$name) {
|
||||
if (!strlen($name)) {
|
||||
$e_name = pht('Required');
|
||||
$errors[] = pht('Name field is required.');
|
||||
} else {
|
||||
$e_name = null;
|
||||
}
|
||||
$signature_data['name'] = $name;
|
||||
|
||||
$addr_obj = null;
|
||||
if (!$email) {
|
||||
$e_email = pht('Required');
|
||||
$errors[] = pht('Email field is required.');
|
||||
if ($viewer->isLoggedIn()) {
|
||||
$email = $viewer->loadPrimaryEmailAddress();
|
||||
} else {
|
||||
$addr_obj = new PhutilEmailAddress($email);
|
||||
$domain = $addr_obj->getDomainName();
|
||||
if (!$domain) {
|
||||
$e_email = pht('Invalid');
|
||||
$errors[] = pht('A valid email is required.');
|
||||
$email = $request->getStr('email');
|
||||
|
||||
$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;
|
||||
|
||||
if (!$address_1) {
|
||||
$e_address_1 = pht('Required');
|
||||
$errors[] = pht('Address line 1 field is required.');
|
||||
}
|
||||
$signature_data['address_1'] = $address_1;
|
||||
$signature_data['address_2'] = $address_2;
|
||||
$signature_data['phone'] = $phone;
|
||||
$signature->setSignatureData($signature_data);
|
||||
|
||||
if (!$agree) {
|
||||
$errors[] = pht(
|
||||
'You must check "I agree to the terms laid forth above."');
|
||||
$e_agree = pht('Required');
|
||||
}
|
||||
|
||||
$verified = LegalpadDocumentSignature::UNVERIFIED;
|
||||
if ($viewer->isLoggedIn() && $addr_obj) {
|
||||
$email_obj = id(new PhabricatorUserEmail())
|
||||
->loadOneWhere('address = %s', $addr_obj->getAddress());
|
||||
if ($email_obj && $email_obj->getUserPHID() == $viewer->getPHID()) {
|
||||
$verified = LegalpadDocumentSignature::VERIFIED;
|
||||
}
|
||||
if ($viewer->isLoggedIn()) {
|
||||
$verified = LegalpadDocumentSignature::VERIFIED;
|
||||
} else {
|
||||
$verified = LegalpadDocumentSignature::UNVERIFIED;
|
||||
}
|
||||
$signature->setVerified($verified);
|
||||
|
||||
if (!$errors) {
|
||||
$signature->save();
|
||||
$has_signed = true;
|
||||
if ($signature->isVerified()) {
|
||||
$body = $this->getVerifiedSignatureBlurb();
|
||||
|
||||
// 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()) {
|
||||
$next_uri = '/'.$document->getMonogram();
|
||||
} else {
|
||||
$body = $this->getUnverifiedSignatureBlurb();
|
||||
$this->sendVerifySignatureEmail(
|
||||
$document,
|
||||
$signature);
|
||||
$next_uri = $this->getApplicationURI('done/');
|
||||
}
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
->setTitle(pht('Signature Successful'))
|
||||
->appendChild($body);
|
||||
} else {
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setTitle(pht('Error in submission.'))
|
||||
->setErrors($errors);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($next_uri);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,6 +188,10 @@ final class LegalpadDocumentSignController extends LegalpadController {
|
|||
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().'/');
|
||||
|
@ -193,25 +214,36 @@ final class LegalpadDocumentSignController extends LegalpadController {
|
|||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit));
|
||||
|
||||
$signature_form = $this->buildSignatureForm(
|
||||
$document_body,
|
||||
$signature,
|
||||
$has_signed,
|
||||
$e_name,
|
||||
$e_email,
|
||||
$e_address_1);
|
||||
$content = id(new PHUIDocumentView())
|
||||
->addClass('legalpad')
|
||||
->setHeader($header)
|
||||
->appendChild(
|
||||
array(
|
||||
$signed_status,
|
||||
$document_markup,
|
||||
));
|
||||
|
||||
$content = $this->buildDocument(
|
||||
$header,
|
||||
$engine,
|
||||
$document_body);
|
||||
if (!$has_signed) {
|
||||
$error_view = null;
|
||||
if ($errors) {
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setErrors($errors);
|
||||
}
|
||||
|
||||
$content->appendChild(
|
||||
array(
|
||||
id(new PHUIHeaderView())->setHeader(pht('Agree and Sign Document')),
|
||||
$error_view,
|
||||
$signature_form,
|
||||
));
|
||||
$signature_form = $this->buildSignatureForm(
|
||||
$document_body,
|
||||
$signature,
|
||||
$e_name,
|
||||
$e_email,
|
||||
$e_agree);
|
||||
|
||||
$content->appendChild(
|
||||
array(
|
||||
id(new PHUIHeaderView())->setHeader(pht('Agree and Sign Document')),
|
||||
$error_view,
|
||||
$signature_form,
|
||||
));
|
||||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb($document->getMonogram());
|
||||
|
@ -227,35 +259,16 @@ final class LegalpadDocumentSignController extends LegalpadController {
|
|||
));
|
||||
}
|
||||
|
||||
private function buildDocument(
|
||||
PHUIHeaderView $header,
|
||||
PhabricatorMarkupEngine $engine,
|
||||
LegalpadDocumentBody $body) {
|
||||
|
||||
return id(new PHUIDocumentView())
|
||||
->addClass('legalpad')
|
||||
->setHeader($header)
|
||||
->appendChild($engine->getOutput(
|
||||
$body,
|
||||
LegalpadDocumentBody::MARKUP_FIELD_TEXT));
|
||||
}
|
||||
|
||||
private function buildSignatureForm(
|
||||
LegalpadDocumentBody $body,
|
||||
LegalpadDocumentSignature $signature,
|
||||
$has_signed = false,
|
||||
$e_name = true,
|
||||
$e_email = true,
|
||||
$e_address_1 = true) {
|
||||
$e_name,
|
||||
$e_email,
|
||||
$e_agree) {
|
||||
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
if ($has_signed) {
|
||||
$instructions = pht('Thank you for signing and agreeing.');
|
||||
} else {
|
||||
$instructions = pht('Please enter the following information.');
|
||||
}
|
||||
|
||||
$data = $signature->getSignatureData();
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
|
@ -263,61 +276,34 @@ final class LegalpadDocumentSignController extends LegalpadController {
|
|||
->setLabel(pht('Name'))
|
||||
->setValue(idx($data, 'name', ''))
|
||||
->setName('name')
|
||||
->setError($e_name)
|
||||
->setDisabled($has_signed))
|
||||
->appendChild(
|
||||
->setError($e_name));
|
||||
|
||||
if (!$viewer->isLoggedIn()) {
|
||||
$form->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Email'))
|
||||
->setValue(idx($data, 'email', ''))
|
||||
->setName('email')
|
||||
->setError($e_email)
|
||||
->setDisabled($has_signed))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Address line 1'))
|
||||
->setValue(idx($data, 'address_1', ''))
|
||||
->setName('address_1')
|
||||
->setError($e_address_1)
|
||||
->setDisabled($has_signed))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Address line 2'))
|
||||
->setValue(idx($data, 'address_2', ''))
|
||||
->setName('address_2')
|
||||
->setDisabled($has_signed))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Phone'))
|
||||
->setValue(idx($data, 'phone', ''))
|
||||
->setName('phone')
|
||||
->setDisabled($has_signed))
|
||||
->setLabel(pht('Email'))
|
||||
->setValue(idx($data, 'email', ''))
|
||||
->setName('email')
|
||||
->setError($e_email));
|
||||
}
|
||||
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormCheckboxControl())
|
||||
->addCheckbox(
|
||||
'agree',
|
||||
'agree',
|
||||
pht('I agree to the terms laid forth above.'),
|
||||
$has_signed)
|
||||
->setDisabled($has_signed))
|
||||
->setError($e_agree)
|
||||
->addCheckbox(
|
||||
'agree',
|
||||
'agree',
|
||||
pht('I agree to the terms laid forth above.'),
|
||||
false))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Sign and Agree'))
|
||||
->setDisabled($has_signed)
|
||||
->addCancelButton($this->getApplicationURI()));
|
||||
->setValue(pht('Sign Document'))
|
||||
->addCancelButton($this->getApplicationURI()));
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
private function getVerifiedSignatureBlurb() {
|
||||
return pht('Thank you for signing and agreeing.');
|
||||
}
|
||||
|
||||
private function getUnverifiedSignatureBlurb() {
|
||||
return pht('Thank you for signing and agreeing. However, you must '.
|
||||
'verify your email address. Please check your email '.
|
||||
'and follow the instructions.');
|
||||
}
|
||||
|
||||
private function sendVerifySignatureEmail(
|
||||
LegalpadDocument $doc,
|
||||
LegalpadDocumentSignature $signature) {
|
||||
|
|
|
@ -77,6 +77,12 @@ h1.aphront-error-view-head {
|
|||
background-color: #fff;
|
||||
}
|
||||
|
||||
.legalpad .aphront-error-view {
|
||||
margin: 0;
|
||||
border-width: 0 0 1px 0;
|
||||
border-bottom: 1px solid {$lightblueborder};
|
||||
}
|
||||
|
||||
.aphront-dialog-body .aphront-error-view {
|
||||
margin: -16px -16px 16px -16px;
|
||||
border-width: 0 0 1px 0;
|
||||
|
|
Loading…
Reference in a new issue