1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 12:52:42 +01:00

Allow Legalpad documents to designate corporate signers

Summary:
Ref T5532. This adds:

  - Documents can designate that they should be signed by "Corporations" or "Individuals".
  - Corporate documents get different fields and a different exemption process.
  - Basically everything works the same but this is like a zillion lines of form code.

Test Plan: See screenshots.

Reviewers: btrahan, chad

Reviewed By: chad

Subscribers: epriestley

Maniphest Tasks: T5532

Differential Revision: https://secure.phabricator.com/D9812
This commit is contained in:
epriestley 2014-07-04 08:04:28 -07:00
parent 04d5402e2f
commit e8d217b8bd
17 changed files with 728 additions and 292 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_legalpad.legalpad_document
ADD signatureType VARCHAR(4) NOT NULL COLLATE utf8_bin;

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_legalpad.legalpad_document
SET signatureType = 'user' WHERE signatureType = '';

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature
ADD signatureType VARCHAR(4) NOT NULL COLLATE utf8_bin AFTER documentVersion;

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_legalpad.legalpad_documentsignature
SET signatureType = 'user' WHERE signatureType = '';

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature
CHANGE signerPHID signerPHID VARCHAR(64) COLLATE utf8_bin;

View file

@ -1,11 +1,9 @@
<?php
/**
* @group legalpad
*/
final class LegalpadTransactionType extends LegalpadConstants {
const TYPE_TITLE = 'title';
const TYPE_TEXT = 'text';
const TYPE_SIGNATURE_TYPE = 'legalpad:signature-type';
}

View file

@ -1,8 +1,5 @@
<?php
/**
* @group legalpad
*/
final class LegalpadDocumentEditController extends LegalpadController {
private $id;
@ -26,8 +23,6 @@ final class LegalpadDocumentEditController extends LegalpadController {
->setCreatorPHID($user->getPHID());
$document->attachDocumentBody($body);
$document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID);
$title = null;
$text = null;
} else {
$is_create = false;
@ -44,12 +39,15 @@ final class LegalpadDocumentEditController extends LegalpadController {
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;
@ -86,6 +84,13 @@ final class LegalpadDocumentEditController extends LegalpadController {
->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)
@ -113,7 +118,23 @@ final class LegalpadDocumentEditController extends LegalpadController {
->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')

View file

@ -173,6 +173,10 @@ final class LegalpadDocumentManageController extends LegalpadController {
->setObject($document)
->setActionList($actions);
$properties->addProperty(
pht('Signature Type'),
$document->getSignatureTypeName());
$properties->addProperty(
pht('Last Updated'),
phabricator_datetime($document->getDateModified(), $user));

View file

@ -25,107 +25,116 @@ final class LegalpadDocumentSignController extends LegalpadController {
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();
}
$signer_phid = $external_account->getPHID();
}
}
list($signer_phid, $signature_data) = $this->readSignerInformation(
$document,
$request);
$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();
$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.
if ($signature && !$viewer->isLoggedIn()) {
return $this->newDialog()
->setTitle(pht('Already Signed'))
->appendParagraph(pht('You have already signed this document!'))
->addCancelButton('/'.$document->getMonogram(), pht('Okay'));
$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;
$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));
}
$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) {
@ -139,50 +148,25 @@ final class LegalpadDocumentSignController extends LegalpadController {
'/'.$document->getMonogram());
}
$name = $request->getStr('name');
$agree = $request->getExists('agree');
list($form_data, $errors, $field_errors) = $this->readSignatureForm(
$document,
$request);
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');
$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;
@ -195,9 +179,13 @@ final class LegalpadDocumentSignController extends LegalpadController {
// 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/');
}
@ -257,11 +245,9 @@ final class LegalpadDocumentSignController extends LegalpadController {
}
$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'))
@ -289,38 +275,96 @@ final class LegalpadDocumentSignController extends LegalpadController {
));
}
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)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setValue(idx($data, 'name', ''))
->setName('name')
->setError($e_name));
->setUser($viewer);
if (!$viewer->isLoggedIn()) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setValue(idx($data, 'email', ''))
->setName('email')
->setError($e_email));
$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($e_agree)
->setError(idx($errors, 'agree', null))
->addCheckbox(
'agree',
'agree',
@ -334,27 +378,240 @@ final class LegalpadDocumentSignController extends LegalpadController {
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(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(idx($errors, 'email', null)));
}
return $form;
}
private function buildCorporateSignatureForm(
AphrontFormView $form,
LegalpadDocument $document,
LegalpadDocumentSignature $signature,
array $errors) {
$data = $signature->getSignatureData();
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Company Name'))
->setValue(idx($data, 'name', ''))
->setName('name')
->setError(idx($errors, 'name', null)))
->appendChild(
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);
$body = <<<EOBODY
Hi {$signature_data['name']},
$name = idx($signature_data, 'name');
This email address was used to sign a Legalpad document ({$doc_link}).
Please verify you own this email address by clicking this link:
$body = <<<EOBODY
{$name}:
This email address was used to sign a Legalpad document in Phabricator:
{$doc_name}
Please verify you own this email address and accept the agreement by clicking
this link:
{$link}
Your signature is invalid until you verify you own the email.
Your signature is not valid until you complete this verification step.
You can review the document here:
{$doc_link}
EOBODY;
id(new PhabricatorMetaMTAMail())

View file

@ -28,62 +28,90 @@ final class LegalpadDocumentSignatureAddController extends LegalpadController {
$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();
@ -91,18 +119,31 @@ final class LegalpadDocumentSignatureAddController extends LegalpadController {
}
}
$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'))

View file

@ -1,93 +1,99 @@
<?php
final class LegalpadDocumentSignatureVerificationController
extends LegalpadController {
extends LegalpadController {
private $code;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->code = $data['code'];
}
public function shouldRequireEmailVerification() {
return false;
}
public function shouldRequireLogin() {
return false;
}
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'));
}
}

View file

@ -44,22 +44,63 @@ final class LegalpadDocumentSignatureViewController extends LegalpadController {
$document_id = $signature->getDocument()->getID();
$next_uri = $this->getApplicationURI('signatures/'.$document_id.'/');
$exemption_phid = $signature->getExemptionPHID();
$handles = $this->loadViewerHandles(array($exemption_phid));
$exemptor_handle = $handles[$exemption_phid];
$data = $signature->getSignatureData();
$exemption_phid = $signature->getExemptionPHID();
$actor_phid = idx($data, 'actorPHID');
$handles = $this->loadViewerHandles(
array(
$exemption_phid,
$actor_phid,
));
$exemptor_handle = $handles[$exemption_phid];
$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'))

View file

@ -11,6 +11,7 @@ final class LegalpadDocumentEditor
private function setIsContribution($is_contribution) {
$this->isContribution = $is_contribution;
}
private function isContribution() {
return $this->isContribution;
}
@ -24,6 +25,8 @@ final class LegalpadDocumentEditor
$types[] = LegalpadTransactionType::TYPE_TITLE;
$types[] = LegalpadTransactionType::TYPE_TEXT;
$types[] = LegalpadTransactionType::TYPE_SIGNATURE_TYPE;
return $types;
}
@ -36,6 +39,8 @@ final class LegalpadDocumentEditor
return $object->getDocumentBody()->getTitle();
case LegalpadTransactionType::TYPE_TEXT:
return $object->getDocumentBody()->getText();
case LegalpadTransactionType::TYPE_SIGNATURE_TYPE:
return $object->getSignatureType();
}
}
@ -46,6 +51,7 @@ final class LegalpadDocumentEditor
switch ($xaction->getTransactionType()) {
case LegalpadTransactionType::TYPE_TITLE:
case LegalpadTransactionType::TYPE_TEXT:
case LegalpadTransactionType::TYPE_SIGNATURE_TYPE:
return $xaction->getNewValue();
}
}
@ -66,6 +72,9 @@ final class LegalpadDocumentEditor
$body->setText($xaction->getNewValue());
$this->setIsContribution(true);
break;
case LegalpadTransactionType::TYPE_SIGNATURE_TYPE:
$object->setSignatureType($xaction->getNewValue());
break;
}
}
@ -116,6 +125,7 @@ final class LegalpadDocumentEditor
switch ($type) {
case LegalpadTransactionType::TYPE_TITLE:
case LegalpadTransactionType::TYPE_TEXT:
case LegalpadTransactionType::TYPE_SIGNATURE_TYPE:
return $v;
}

View file

@ -169,13 +169,18 @@ final class LegalpadDocumentSearchEngine
$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());

View file

@ -168,6 +168,11 @@ final class LegalpadDocumentSignatureSearchEngine
null,
pht('Verified, Current'));
$sig_corp = $this->renderIcon(
'fa-building-o',
null,
pht('Verified, Corporate'));
$sig_old = $this->renderIcon(
'fa-clock-o',
'orange',
@ -188,6 +193,8 @@ final class LegalpadDocumentSignatureSearchEngine
->addSigil('has-tooltip')
->setMetadata(array('tip' => pht('Unverified Email')));
$type_corporate = LegalpadDocument::SIGNATURE_TYPE_CORPORATION;
$rows = array();
foreach ($signatures as $signature) {
$name = $signature->getSignerName();
@ -196,28 +203,36 @@ final class LegalpadDocumentSignatureSearchEngine
$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',

View file

@ -16,6 +16,10 @@ final class LegalpadDocument extends LegalpadDAO
protected $viewPolicy;
protected $editPolicy;
protected $mailKey;
protected $signatureType;
const SIGNATURE_TYPE_INDIVIDUAL = 'user';
const SIGNATURE_TYPE_CORPORATION = 'corp';
private $documentBody = self::ATTACHABLE;
private $contributors = self::ATTACHABLE;
@ -37,6 +41,7 @@ final class LegalpadDocument extends LegalpadDAO
->setContributorCount(0)
->setRecentContributorPHIDs(array())
->attachSignatures(array())
->setSignatureType(self::SIGNATURE_TYPE_INDIVIDUAL)
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy);
}
@ -104,6 +109,28 @@ final class LegalpadDocument extends LegalpadDAO
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 )----------------------------------- */

View file

@ -9,6 +9,7 @@ final class LegalpadDocumentSignature
protected $documentPHID;
protected $documentVersion;
protected $signatureType;
protected $signerPHID;
protected $signerName;
protected $signerEmail;