[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class LegalpadDocumentSignController extends LegalpadController { 4 5 private $id; 6 7 public function shouldAllowPublic() { 8 return true; 9 } 10 11 public function willProcessRequest(array $data) { 12 $this->id = $data['id']; 13 } 14 15 public function processRequest() { 16 $request = $this->getRequest(); 17 $viewer = $request->getUser(); 18 19 $document = id(new LegalpadDocumentQuery()) 20 ->setViewer($viewer) 21 ->withIDs(array($this->id)) 22 ->needDocumentBodies(true) 23 ->executeOne(); 24 if (!$document) { 25 return new Aphront404Response(); 26 } 27 28 list($signer_phid, $signature_data) = $this->readSignerInformation( 29 $document, 30 $request); 31 32 $signature = null; 33 34 $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; 35 $is_individual = ($document->getSignatureType() == $type_individual); 36 if ($is_individual) { 37 if ($signer_phid) { 38 // TODO: This is odd and should probably be adjusted after grey/external 39 // accounts work better, but use the omnipotent viewer to check for a 40 // signature so we can pick up anonymous/grey signatures. 41 42 $signature = id(new LegalpadDocumentSignatureQuery()) 43 ->setViewer(PhabricatorUser::getOmnipotentUser()) 44 ->withDocumentPHIDs(array($document->getPHID())) 45 ->withSignerPHIDs(array($signer_phid)) 46 ->executeOne(); 47 48 if ($signature && !$viewer->isLoggedIn()) { 49 return $this->newDialog() 50 ->setTitle(pht('Already Signed')) 51 ->appendParagraph(pht('You have already signed this document!')) 52 ->addCancelButton('/'.$document->getMonogram(), pht('Okay')); 53 } 54 } 55 56 $signed_status = null; 57 if (!$signature) { 58 $has_signed = false; 59 $signature = id(new LegalpadDocumentSignature()) 60 ->setSignerPHID($signer_phid) 61 ->setDocumentPHID($document->getPHID()) 62 ->setDocumentVersion($document->getVersions()); 63 64 // If the user is logged in, show a notice that they haven't signed. 65 // If they aren't logged in, we can't be as sure, so don't show 66 // anything. 67 if ($viewer->isLoggedIn()) { 68 $signed_status = id(new AphrontErrorView()) 69 ->setSeverity(AphrontErrorView::SEVERITY_WARNING) 70 ->setErrors( 71 array( 72 pht('You have not signed this document yet.'), 73 )); 74 } 75 } else { 76 $has_signed = true; 77 $signature_data = $signature->getSignatureData(); 78 79 // In this case, we know they've signed. 80 $signed_at = $signature->getDateCreated(); 81 82 if ($signature->getIsExemption()) { 83 $exemption_phid = $signature->getExemptionPHID(); 84 $handles = $this->loadViewerHandles(array($exemption_phid)); 85 $exemption_handle = $handles[$exemption_phid]; 86 87 $signed_text = pht( 88 'You do not need to sign this document. '. 89 '%s added a signature exemption for you on %s.', 90 $exemption_handle->renderLink(), 91 phabricator_datetime($signed_at, $viewer)); 92 } else { 93 $signed_text = pht( 94 'You signed this document on %s.', 95 phabricator_datetime($signed_at, $viewer)); 96 } 97 98 $signed_status = id(new AphrontErrorView()) 99 ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) 100 ->setErrors(array($signed_text)); 101 } 102 103 $field_errors = array( 104 'name' => true, 105 'email' => true, 106 'agree' => true, 107 ); 108 } else { 109 $signature = id(new LegalpadDocumentSignature()) 110 ->setDocumentPHID($document->getPHID()) 111 ->setDocumentVersion($document->getVersions()); 112 113 if ($viewer->isLoggedIn()) { 114 $has_signed = false; 115 116 $signed_status = null; 117 } else { 118 // This just hides the form. 119 $has_signed = true; 120 121 $login_text = pht( 122 'This document requires a corporate signatory. You must log in to '. 123 'accept this document on behalf of a company you represent.'); 124 $signed_status = id(new AphrontErrorView()) 125 ->setSeverity(AphrontErrorView::SEVERITY_WARNING) 126 ->setErrors(array($login_text)); 127 } 128 129 $field_errors = array( 130 'name' => true, 131 'address' => true, 132 'contact.name' => true, 133 'email' => true, 134 ); 135 } 136 137 $signature->setSignatureData($signature_data); 138 139 $errors = array(); 140 if ($request->isFormOrHisecPost() && !$has_signed) { 141 142 // Require two-factor auth to sign legal documents. 143 if ($viewer->isLoggedIn()) { 144 $engine = new PhabricatorAuthSessionEngine(); 145 $engine->requireHighSecuritySession( 146 $viewer, 147 $request, 148 '/'.$document->getMonogram()); 149 } 150 151 list($form_data, $errors, $field_errors) = $this->readSignatureForm( 152 $document, 153 $request); 154 155 $signature_data = $form_data + $signature_data; 156 157 $signature->setSignatureData($signature_data); 158 $signature->setSignatureType($document->getSignatureType()); 159 $signature->setSignerName((string)idx($signature_data, 'name')); 160 $signature->setSignerEmail((string)idx($signature_data, 'email')); 161 162 $agree = $request->getExists('agree'); 163 if (!$agree) { 164 $errors[] = pht( 165 'You must check "I agree to the terms laid forth above."'); 166 $field_errors['agree'] = pht('Required'); 167 } 168 169 if ($viewer->isLoggedIn() && $is_individual) { 170 $verified = LegalpadDocumentSignature::VERIFIED; 171 } else { 172 $verified = LegalpadDocumentSignature::UNVERIFIED; 173 } 174 $signature->setVerified($verified); 175 176 if (!$errors) { 177 $signature->save(); 178 179 // If the viewer is logged in, send them to the document page, which 180 // will show that they have signed the document. Otherwise, send them 181 // to a completion page. 182 if ($viewer->isLoggedIn() && $is_individual) { 183 $next_uri = '/'.$document->getMonogram(); 184 } else { 185 $this->sendVerifySignatureEmail( 186 $document, 187 $signature); 188 189 $next_uri = $this->getApplicationURI('done/'); 190 } 191 192 return id(new AphrontRedirectResponse())->setURI($next_uri); 193 } 194 } 195 196 $document_body = $document->getDocumentBody(); 197 $engine = id(new PhabricatorMarkupEngine()) 198 ->setViewer($viewer); 199 $engine->addObject( 200 $document_body, 201 LegalpadDocumentBody::MARKUP_FIELD_TEXT); 202 $engine->process(); 203 204 $document_markup = $engine->getOutput( 205 $document_body, 206 LegalpadDocumentBody::MARKUP_FIELD_TEXT); 207 208 $title = $document_body->getTitle(); 209 210 $manage_uri = $this->getApplicationURI('view/'.$document->getID().'/'); 211 212 $can_edit = PhabricatorPolicyFilter::hasCapability( 213 $viewer, 214 $document, 215 PhabricatorPolicyCapability::CAN_EDIT); 216 217 $header = id(new PHUIHeaderView()) 218 ->setHeader($title) 219 ->addActionLink( 220 id(new PHUIButtonView()) 221 ->setTag('a') 222 ->setIcon( 223 id(new PHUIIconView()) 224 ->setIconFont('fa-pencil')) 225 ->setText(pht('Manage Document')) 226 ->setHref($manage_uri) 227 ->setDisabled(!$can_edit) 228 ->setWorkflow(!$can_edit)); 229 230 $preamble = null; 231 if (strlen($document->getPreamble())) { 232 $preamble_text = PhabricatorMarkupEngine::renderOneObject( 233 id(new PhabricatorMarkupOneOff())->setContent( 234 $document->getPreamble()), 235 'default', 236 $viewer); 237 238 $preamble = id(new PHUIPropertyListView()) 239 ->addSectionHeader(pht('Preamble')) 240 ->addTextContent($preamble_text); 241 } 242 243 $content = id(new PHUIDocumentView()) 244 ->addClass('legalpad') 245 ->setHeader($header) 246 ->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS) 247 ->appendChild( 248 array( 249 $signed_status, 250 $preamble, 251 $document_markup, 252 )); 253 254 if (!$has_signed) { 255 $error_view = null; 256 if ($errors) { 257 $error_view = id(new AphrontErrorView()) 258 ->setErrors($errors); 259 } 260 261 $signature_form = $this->buildSignatureForm( 262 $document, 263 $signature, 264 $field_errors); 265 266 $subheader = id(new PHUIHeaderView()) 267 ->setHeader(pht('Agree and Sign Document')) 268 ->setBleedHeader(true); 269 270 $content->appendChild( 271 array( 272 $subheader, 273 $error_view, 274 $signature_form, 275 )); 276 } 277 278 $crumbs = $this->buildApplicationCrumbs(); 279 $crumbs->addTextCrumb($document->getMonogram()); 280 281 return $this->buildApplicationPage( 282 array( 283 $crumbs, 284 $content, 285 ), 286 array( 287 'title' => $title, 288 'pageObjects' => array($document->getPHID()), 289 )); 290 } 291 292 private function readSignerInformation( 293 LegalpadDocument $document, 294 AphrontRequest $request) { 295 296 $viewer = $request->getUser(); 297 $signer_phid = null; 298 $signature_data = array(); 299 300 switch ($document->getSignatureType()) { 301 case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: 302 if ($viewer->isLoggedIn()) { 303 $signer_phid = $viewer->getPHID(); 304 $signature_data = array( 305 'name' => $viewer->getRealName(), 306 'email' => $viewer->loadPrimaryEmailAddress(), 307 ); 308 } else if ($request->isFormPost()) { 309 $email = new PhutilEmailAddress($request->getStr('email')); 310 if (strlen($email->getDomainName())) { 311 $email_obj = id(new PhabricatorUserEmail()) 312 ->loadOneWhere('address = %s', $email->getAddress()); 313 if ($email_obj) { 314 return $this->signInResponse(); 315 } 316 $external_account = id(new PhabricatorExternalAccountQuery()) 317 ->setViewer($viewer) 318 ->withAccountTypes(array('email')) 319 ->withAccountDomains(array($email->getDomainName())) 320 ->withAccountIDs(array($email->getAddress())) 321 ->loadOneOrCreate(); 322 if ($external_account->getUserPHID()) { 323 return $this->signInResponse(); 324 } 325 $signer_phid = $external_account->getPHID(); 326 } 327 } 328 break; 329 case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: 330 $signer_phid = $viewer->getPHID(); 331 if ($signer_phid) { 332 $signature_data = array( 333 'contact.name' => $viewer->getRealName(), 334 'email' => $viewer->loadPrimaryEmailAddress(), 335 'actorPHID' => $viewer->getPHID(), 336 ); 337 } 338 break; 339 } 340 341 return array($signer_phid, $signature_data); 342 } 343 344 private function buildSignatureForm( 345 LegalpadDocument $document, 346 LegalpadDocumentSignature $signature, 347 array $errors) { 348 349 $viewer = $this->getRequest()->getUser(); 350 $data = $signature->getSignatureData(); 351 352 $form = id(new AphrontFormView()) 353 ->setUser($viewer); 354 355 $signature_type = $document->getSignatureType(); 356 switch ($signature_type) { 357 case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: 358 $this->buildIndividualSignatureForm( 359 $form, 360 $document, 361 $signature, 362 $errors); 363 break; 364 case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: 365 $this->buildCorporateSignatureForm( 366 $form, 367 $document, 368 $signature, 369 $errors); 370 break; 371 default: 372 throw new Exception( 373 pht( 374 'This document has an unknown signature type ("%s").', 375 $signature_type)); 376 } 377 378 $form 379 ->appendChild( 380 id(new AphrontFormCheckboxControl()) 381 ->setError(idx($errors, 'agree', null)) 382 ->addCheckbox( 383 'agree', 384 'agree', 385 pht('I agree to the terms laid forth above.'), 386 false)) 387 ->appendChild( 388 id(new AphrontFormSubmitControl()) 389 ->setValue(pht('Sign Document')) 390 ->addCancelButton($this->getApplicationURI())); 391 392 return $form; 393 } 394 395 private function buildIndividualSignatureForm( 396 AphrontFormView $form, 397 LegalpadDocument $document, 398 LegalpadDocumentSignature $signature, 399 array $errors) { 400 401 $data = $signature->getSignatureData(); 402 403 $form 404 ->appendChild( 405 id(new AphrontFormTextControl()) 406 ->setLabel(pht('Name')) 407 ->setValue(idx($data, 'name', '')) 408 ->setName('name') 409 ->setError(idx($errors, 'name', null))); 410 411 $viewer = $this->getRequest()->getUser(); 412 if (!$viewer->isLoggedIn()) { 413 $form->appendChild( 414 id(new AphrontFormTextControl()) 415 ->setLabel(pht('Email')) 416 ->setValue(idx($data, 'email', '')) 417 ->setName('email') 418 ->setError(idx($errors, 'email', null))); 419 } 420 421 return $form; 422 } 423 424 private function buildCorporateSignatureForm( 425 AphrontFormView $form, 426 LegalpadDocument $document, 427 LegalpadDocumentSignature $signature, 428 array $errors) { 429 430 $data = $signature->getSignatureData(); 431 432 $form 433 ->appendChild( 434 id(new AphrontFormTextControl()) 435 ->setLabel(pht('Company Name')) 436 ->setValue(idx($data, 'name', '')) 437 ->setName('name') 438 ->setError(idx($errors, 'name', null))) 439 ->appendChild( 440 id(new AphrontFormTextAreaControl()) 441 ->setLabel(pht('Company Address')) 442 ->setValue(idx($data, 'address', '')) 443 ->setName('address') 444 ->setError(idx($errors, 'address', null))) 445 ->appendChild( 446 id(new AphrontFormTextControl()) 447 ->setLabel(pht('Contact Name')) 448 ->setValue(idx($data, 'contact.name', '')) 449 ->setName('contact.name') 450 ->setError(idx($errors, 'contact.name', null))) 451 ->appendChild( 452 id(new AphrontFormTextControl()) 453 ->setLabel(pht('Contact Email')) 454 ->setValue(idx($data, 'email', '')) 455 ->setName('email') 456 ->setError(idx($errors, 'email', null))); 457 458 return $form; 459 } 460 461 private function readSignatureForm( 462 LegalpadDocument $document, 463 AphrontRequest $request) { 464 465 $signature_type = $document->getSignatureType(); 466 switch ($signature_type) { 467 case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: 468 $result = $this->readIndividualSignatureForm( 469 $document, 470 $request); 471 break; 472 case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: 473 $result = $this->readCorporateSignatureForm( 474 $document, 475 $request); 476 break; 477 default: 478 throw new Exception( 479 pht( 480 'This document has an unknown signature type ("%s").', 481 $signature_type)); 482 } 483 484 return $result; 485 } 486 487 private function readIndividualSignatureForm( 488 LegalpadDocument $document, 489 AphrontRequest $request) { 490 491 $signature_data = array(); 492 $errors = array(); 493 $field_errors = array(); 494 495 496 $name = $request->getStr('name'); 497 498 if (!strlen($name)) { 499 $field_errors['name'] = pht('Required'); 500 $errors[] = pht('Name field is required.'); 501 } else { 502 $field_errors['name'] = null; 503 } 504 $signature_data['name'] = $name; 505 506 $viewer = $request->getUser(); 507 if ($viewer->isLoggedIn()) { 508 $email = $viewer->loadPrimaryEmailAddress(); 509 } else { 510 $email = $request->getStr('email'); 511 512 $addr_obj = null; 513 if (!strlen($email)) { 514 $field_errors['email'] = pht('Required'); 515 $errors[] = pht('Email field is required.'); 516 } else { 517 $addr_obj = new PhutilEmailAddress($email); 518 $domain = $addr_obj->getDomainName(); 519 if (!$domain) { 520 $field_errors['email'] = pht('Invalid'); 521 $errors[] = pht('A valid email is required.'); 522 } else { 523 $field_errors['email'] = null; 524 } 525 } 526 } 527 $signature_data['email'] = $email; 528 529 return array($signature_data, $errors, $field_errors); 530 } 531 532 private function readCorporateSignatureForm( 533 LegalpadDocument $document, 534 AphrontRequest $request) { 535 536 $viewer = $request->getUser(); 537 if (!$viewer->isLoggedIn()) { 538 throw new Exception( 539 pht( 540 'You can not sign a document on behalf of a corporation unless '. 541 'you are logged in.')); 542 } 543 544 $signature_data = array(); 545 $errors = array(); 546 $field_errors = array(); 547 548 $name = $request->getStr('name'); 549 550 if (!strlen($name)) { 551 $field_errors['name'] = pht('Required'); 552 $errors[] = pht('Company name is required.'); 553 } else { 554 $field_errors['name'] = null; 555 } 556 $signature_data['name'] = $name; 557 558 $address = $request->getStr('address'); 559 if (!strlen($address)) { 560 $field_errors['address'] = pht('Required'); 561 $errors[] = pht('Company address is required.'); 562 } else { 563 $field_errors['address'] = null; 564 } 565 $signature_data['address'] = $address; 566 567 $contact_name = $request->getStr('contact.name'); 568 if (!strlen($contact_name)) { 569 $field_errors['contact.name'] = pht('Required'); 570 $errors[] = pht('Contact name is required.'); 571 } else { 572 $field_errors['contact.name'] = null; 573 } 574 $signature_data['contact.name'] = $contact_name; 575 576 $email = $request->getStr('email'); 577 $addr_obj = null; 578 if (!strlen($email)) { 579 $field_errors['email'] = pht('Required'); 580 $errors[] = pht('Contact email is required.'); 581 } else { 582 $addr_obj = new PhutilEmailAddress($email); 583 $domain = $addr_obj->getDomainName(); 584 if (!$domain) { 585 $field_errors['email'] = pht('Invalid'); 586 $errors[] = pht('A valid email is required.'); 587 } else { 588 $field_errors['email'] = null; 589 } 590 } 591 $signature_data['email'] = $email; 592 593 return array($signature_data, $errors, $field_errors); 594 } 595 596 private function sendVerifySignatureEmail( 597 LegalpadDocument $doc, 598 LegalpadDocumentSignature $signature) { 599 600 $signature_data = $signature->getSignatureData(); 601 $email = new PhutilEmailAddress($signature_data['email']); 602 $doc_name = $doc->getTitle(); 603 $doc_link = PhabricatorEnv::getProductionURI('/'.$doc->getMonogram()); 604 $path = $this->getApplicationURI(sprintf( 605 '/verify/%s/', 606 $signature->getSecretKey())); 607 $link = PhabricatorEnv::getProductionURI($path); 608 609 $name = idx($signature_data, 'name'); 610 611 $body = <<<EOBODY 612 {$name}: 613 614 This email address was used to sign a Legalpad document in Phabricator: 615 616 {$doc_name} 617 618 Please verify you own this email address and accept the agreement by clicking 619 this link: 620 621 {$link} 622 623 Your signature is not valid until you complete this verification step. 624 625 You can review the document here: 626 627 {$doc_link} 628 629 EOBODY; 630 631 id(new PhabricatorMetaMTAMail()) 632 ->addRawTos(array($email->getAddress())) 633 ->setSubject(pht('[Legalpad] Signature Verification')) 634 ->setForceDelivery(true) 635 ->setBody($body) 636 ->setRelatedPHID($signature->getDocumentPHID()) 637 ->saveAndSend(); 638 } 639 640 private function signInResponse() { 641 return id(new Aphront403Response()) 642 ->setForbiddenText(pht( 643 'The email address specified is associated with an account. '. 644 'Please login to that account and sign this document again.')); 645 } 646 647 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |