diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php index 53bb693757..c4bff97f6f 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php @@ -1,261 +1,263 @@ getUser(); $id = $request->getURIData('id'); if (!$id) { $is_create = true; $this->requireApplicationCapability( LegalpadCreateDocumentsCapability::CAPABILITY); $document = LegalpadDocument::initializeNewDocument($user); $body = id(new LegalpadDocumentBody()) ->setCreatorPHID($user->getPHID()); $document->attachDocumentBody($body); $document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID); } else { $is_create = false; $document = id(new LegalpadDocumentQuery()) ->setViewer($user) ->needDocumentBodies(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($id)) ->executeOne(); if (!$document) { return new Aphront404Response(); } } $e_title = true; $e_text = true; $title = $document->getDocumentBody()->getTitle(); $text = $document->getDocumentBody()->getText(); $v_signature_type = $document->getSignatureType(); $v_preamble = $document->getPreamble(); $v_require_signature = $document->getRequireSignature(); $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); } $v_preamble = $request->getStr('preamble'); $xactions[] = id(new LegalpadTransaction()) ->setTransactionType(LegalpadTransactionType::TYPE_PREAMBLE) ->setNewValue($v_preamble); $v_require_signature = $request->getBool('requireSignature', 0); if ($v_require_signature) { if (!$user->getIsAdmin()) { $errors[] = pht('Only admins may require signature.'); } - $corp = LegalpadDocument::SIGNATURE_TYPE_CORPORATION; - if ($v_signature_type == $corp) { + $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + if ($v_signature_type != $individual) { $errors[] = pht( 'Only documents with signature type "individual" may require '. 'signing to use Phabricator.'); } } if ($user->getIsAdmin()) { $xactions[] = id(new LegalpadTransaction()) ->setTransactionType(LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE) ->setNewValue($v_require_signature); } 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')); if ($is_create) { $form->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Who Should Sign?')) ->setName(pht('signatureType')) ->setValue($v_signature_type) ->setOptions(LegalpadDocument::getSignatureTypeMap())); $show_require = true; + $caption = pht('Applies only to documents individuals sign.'); } else { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Who Should Sign?')) ->setValue($document->getSignatureTypeName())); $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; $show_require = $document->getSignatureType() == $individual; + $caption = null; } if ($show_require) { $form ->appendChild( id(new AphrontFormCheckboxControl()) ->setDisabled(!$user->getIsAdmin()) ->setLabel(pht('Require Signature')) ->addCheckbox( 'requireSignature', 'requireSignature', pht( - 'Should signing this document be required to use Phabricator? '. - 'Applies to invidivuals only.'), - $v_require_signature)); + 'Should signing this document be required to use Phabricator?'), + $v_require_signature) + ->setCaption($caption)); } $form ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($user) ->setID('preamble') ->setLabel(pht('Preamble')) ->setValue($v_preamble) ->setName('preamble') ->setCaption( pht('Optional help text for users signing this document.'))) ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($user) ->setID('document-text') ->setLabel(pht('Document Body')) ->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('Save 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/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php index 0faad2db36..61ea2eb271 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php @@ -1,641 +1,665 @@ getUser(); $document = id(new LegalpadDocumentQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->needDocumentBodies(true) ->executeOne(); if (!$document) { return new Aphront404Response(); } 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()) { + switch ($document->getSignatureType()) { + case LegalpadDocument::SIGNATURE_TYPE_NONE: + // nothing to sign means this should be true + $has_signed = true; + // this is a status UI element + $signed_status = null; + break; + case LegalpadDocument::SIGNATURE_TYPE_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')); + } } - } - $signed_status = null; - if (!$signature) { - $has_signed = false; - $signature = id(new LegalpadDocumentSignature()) - ->setSignerPHID($signer_phid) - ->setDocumentPHID($document->getPHID()) - ->setDocumentVersion($document->getVersions()); + $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 PHUIErrorView()) + ->setSeverity(PHUIErrorView::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)); + } - // 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 PHUIErrorView()) - ->setSeverity(PHUIErrorView::SEVERITY_WARNING) - ->setErrors( - array( - pht('You have not signed this document yet.'), - )); + ->setSeverity(PHUIErrorView::SEVERITY_NOTICE) + ->setErrors(array($signed_text)); } - } 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 PHUIErrorView()) - ->setSeverity(PHUIErrorView::SEVERITY_NOTICE) - ->setErrors(array($signed_text)); - } + $field_errors = array( + 'name' => true, + 'email' => true, + 'agree' => true, + ); + $signature->setSignatureData($signature_data); + break; - $field_errors = array( - 'name' => true, - 'email' => true, - 'agree' => true, - ); - } else { - $signature = id(new LegalpadDocumentSignature()) - ->setDocumentPHID($document->getPHID()) - ->setDocumentVersion($document->getVersions()); + case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: + $signature = id(new LegalpadDocumentSignature()) + ->setDocumentPHID($document->getPHID()) + ->setDocumentVersion($document->getVersions()); - if ($viewer->isLoggedIn()) { - $has_signed = false; + if ($viewer->isLoggedIn()) { + $has_signed = false; - $signed_status = null; - } else { - // This just hides the form. - $has_signed = true; + $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 PHUIErrorView()) - ->setSeverity(PHUIErrorView::SEVERITY_WARNING) - ->setErrors(array($login_text)); - } + $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 PHUIErrorView()) + ->setSeverity(PHUIErrorView::SEVERITY_WARNING) + ->setErrors(array($login_text)); + } - $field_errors = array( - 'name' => true, - 'address' => true, - 'contact.name' => true, - 'email' => true, - ); + $field_errors = array( + 'name' => true, + 'address' => true, + 'contact.name' => true, + 'email' => true, + ); + $signature->setSignatureData($signature_data); + break; } - $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()); } list($form_data, $errors, $field_errors) = $this->readSignatureForm( $document, $request); $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')); $agree = $request->getExists('agree'); if (!$agree) { $errors[] = pht( 'You must check "I agree to the terms laid forth above."'); $field_errors['agree'] = pht('Required'); } 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() && $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)); $preamble = null; if (strlen($document->getPreamble())) { $preamble_text = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent( $document->getPreamble()), 'default', $viewer); $preamble = id(new PHUIPropertyListView()) ->addSectionHeader(pht('Preamble')) ->addTextContent($preamble_text); } $content = id(new PHUIDocumentView()) ->addClass('legalpad') ->setHeader($header) ->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS) ->appendChild( array( $signed_status, $preamble, $document_markup, )); if (!$has_signed) { $error_view = null; if ($errors) { $error_view = id(new PHUIErrorView()) ->setErrors($errors); } $signature_form = $this->buildSignatureForm( $document, $signature, $field_errors); - $subheader = id(new PHUIHeaderView()) - ->setHeader(pht('Agree and Sign Document')) - ->setBleedHeader(true); + switch ($document->getSignatureType()) { + case LegalpadDocument::SIGNATURE_TYPE_NONE: + $subheader = null; + break; + case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: + case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: + $subheader = id(new PHUIHeaderView()) + ->setHeader(pht('Agree and Sign Document')) + ->setBleedHeader(true); + break; + } $content->appendChild( array( $subheader, $error_view, $signature_form, )); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $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_NONE: + break; 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( LegalpadDocument $document, LegalpadDocumentSignature $signature, array $errors) { $viewer = $this->getRequest()->getUser(); $data = $signature->getSignatureData(); $form = id(new AphrontFormView()) ->setUser($viewer); $signature_type = $document->getSignatureType(); switch ($signature_type) { + case LegalpadDocument::SIGNATURE_TYPE_NONE: + // bail out of here quick + return; 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(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_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')) ->setForceDelivery(true) ->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/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php index c06d722fae..d750c3689b 100644 --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -1,256 +1,259 @@ setViewer($actor) ->withClasses(array('PhabricatorLegalpadApplication')) ->executeOne(); $view_policy = $app->getPolicy(LegalpadDefaultViewCapability::CAPABILITY); $edit_policy = $app->getPolicy(LegalpadDefaultEditCapability::CAPABILITY); return id(new LegalpadDocument()) ->setVersions(0) ->setCreatorPHID($actor->getPHID()) ->setContributorCount(0) ->setRecentContributorPHIDs(array()) ->attachSignatures(array()) ->setSignatureType(self::SIGNATURE_TYPE_INDIVIDUAL) ->setPreamble('') ->setRequireSignature(0) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'recentContributorPHIDs' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', 'contributorCount' => 'uint32', 'versions' => 'uint32', 'mailKey' => 'bytes20', 'signatureType' => 'text4', 'preamble' => 'text', 'requireSignature' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_creator' => array( 'columns' => array('creatorPHID', 'dateModified'), ), 'key_required' => array( 'columns' => array('requireSignature', 'dateModified'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorLegalpadDocumentPHIDType::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'), + self::SIGNATURE_TYPE_NONE => pht('No One'), ); } public function getSignatureTypeName() { $type = $this->getSignatureType(); return idx(self::getSignatureTypeMap(), $type, $type); } public function getSignatureTypeIcon() { $type = $this->getSignatureType(); $map = array( + self::SIGNATURE_TYPE_NONE => '', 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(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ 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(); } }