Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php
index 59df22a8aa..cfd0eaee65 100644
--- a/src/applications/base/controller/PhabricatorController.php
+++ b/src/applications/base/controller/PhabricatorController.php
@@ -1,635 +1,636 @@
<?php
abstract class PhabricatorController extends AphrontController {
private $handles;
public function shouldRequireLogin() {
return true;
}
public function shouldRequireAdmin() {
return false;
}
public function shouldRequireEnabledUser() {
return true;
}
public function shouldAllowPublic() {
return false;
}
public function shouldAllowPartialSessions() {
return false;
}
public function shouldRequireEmailVerification() {
return PhabricatorUserEmail::isEmailVerificationRequired();
}
public function shouldAllowRestrictedParameter($parameter_name) {
return false;
}
public function shouldRequireMultiFactorEnrollment() {
if (!$this->shouldRequireLogin()) {
return false;
}
if (!$this->shouldRequireEnabledUser()) {
return false;
}
if ($this->shouldAllowPartialSessions()) {
return false;
}
$user = $this->getRequest()->getUser();
if (!$user->getIsStandardUser()) {
return false;
}
return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth');
}
public function shouldAllowLegallyNonCompliantUsers() {
return false;
}
public function isGlobalDragAndDropUploadEnabled() {
return false;
}
public function willBeginExecution() {
$request = $this->getRequest();
if ($request->getUser()) {
// NOTE: Unit tests can set a user explicitly. Normal requests are not
// permitted to do this.
PhabricatorTestCase::assertExecutingUnitTests();
$user = $request->getUser();
} else {
$user = new PhabricatorUser();
$session_engine = new PhabricatorAuthSessionEngine();
$phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
if (strlen($phsid)) {
$session_user = $session_engine->loadUserForSession(
PhabricatorAuthSession::TYPE_WEB,
$phsid);
if ($session_user) {
$user = $session_user;
}
} else {
// If the client doesn't have a session token, generate an anonymous
// session. This is used to provide CSRF protection to logged-out users.
$phsid = $session_engine->establishSession(
PhabricatorAuthSession::TYPE_WEB,
null,
$partial = false);
// This may be a resource request, in which case we just don't set
// the cookie.
if ($request->canSetCookies()) {
$request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid);
}
}
if (!$user->isLoggedIn()) {
$csrf = PhabricatorHash::digestWithNamedKey($phsid, 'csrf.alternate');
$user->attachAlternateCSRFString($csrf);
}
$request->setUser($user);
}
id(new PhabricatorAuthSessionEngine())
->willServeRequestForUser($user);
if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
$dark_console = PhabricatorDarkConsoleSetting::SETTINGKEY;
if ($user->getUserSetting($dark_console) ||
PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
$console = new DarkConsoleCore();
$request->getApplicationConfiguration()->setConsole($console);
}
}
// NOTE: We want to set up the user first so we can render a real page
// here, but fire this before any real logic.
$restricted = array(
'code',
);
foreach ($restricted as $parameter) {
if ($request->getExists($parameter)) {
if (!$this->shouldAllowRestrictedParameter($parameter)) {
throw new Exception(
pht(
'Request includes restricted parameter "%s", but this '.
'controller ("%s") does not whitelist it. Refusing to '.
'serve this request because it might be part of a redirection '.
'attack.',
$parameter,
get_class($this)));
}
}
}
if ($this->shouldRequireEnabledUser()) {
if ($user->getIsDisabled()) {
$controller = new PhabricatorDisabledUserController();
return $this->delegateToController($controller);
}
}
$auth_class = 'PhabricatorAuthApplication';
$auth_application = PhabricatorApplication::getByClass($auth_class);
// Require partial sessions to finish login before doing anything.
if (!$this->shouldAllowPartialSessions()) {
if ($user->hasSession() &&
$user->getSession()->getIsPartial()) {
$login_controller = new PhabricatorAuthFinishController();
$this->setCurrentApplication($auth_application);
return $this->delegateToController($login_controller);
}
}
// Require users sign Legalpad documents before we check if they have
// MFA. If we don't do this, they can get stuck in a state where they
// can't add MFA until they sign, and can't sign until they add MFA.
// See T13024 and PHI223.
$result = $this->requireLegalpadSignatures();
if ($result !== null) {
return $result;
}
// Check if the user needs to configure MFA.
$need_mfa = $this->shouldRequireMultiFactorEnrollment();
$have_mfa = $user->getIsEnrolledInMultiFactor();
if ($need_mfa && !$have_mfa) {
// Check if the cache is just out of date. Otherwise, roadblock the user
// and require MFA enrollment.
$user->updateMultiFactorEnrollment();
if (!$user->getIsEnrolledInMultiFactor()) {
$mfa_controller = new PhabricatorAuthNeedsMultiFactorController();
$this->setCurrentApplication($auth_application);
return $this->delegateToController($mfa_controller);
}
}
if ($this->shouldRequireLogin()) {
// This actually means we need either:
// - a valid user, or a public controller; and
// - permission to see the application; and
// - permission to see at least one Space if spaces are configured.
$allow_public = $this->shouldAllowPublic() &&
PhabricatorEnv::getEnvConfig('policy.allow-public');
// If this controller isn't public, and the user isn't logged in, require
// login.
if (!$allow_public && !$user->isLoggedIn()) {
$login_controller = new PhabricatorAuthStartController();
$this->setCurrentApplication($auth_application);
return $this->delegateToController($login_controller);
}
if ($user->isLoggedIn()) {
if ($this->shouldRequireEmailVerification()) {
if (!$user->getIsEmailVerified()) {
$controller = new PhabricatorMustVerifyEmailController();
$this->setCurrentApplication($auth_application);
return $this->delegateToController($controller);
}
}
}
// If Spaces are configured, require that the user have access to at
// least one. If we don't do this, they'll get confusing error messages
// later on.
$spaces = PhabricatorSpacesNamespaceQuery::getSpacesExist();
if ($spaces) {
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
$user);
if (!$viewer_spaces) {
$controller = new PhabricatorSpacesNoAccessController();
return $this->delegateToController($controller);
}
}
// If the user doesn't have access to the application, don't let them use
// any of its controllers. We query the application in order to generate
// a policy exception if the viewer doesn't have permission.
$application = $this->getCurrentApplication();
if ($application) {
id(new PhabricatorApplicationQuery())
->setViewer($user)
->withPHIDs(array($application->getPHID()))
->executeOne();
}
// If users need approval, require they wait here. We do this near the
// end so they can take other actions (like verifying email, signing
// documents, and enrolling in MFA) while waiting for an admin to take a
// look at things. See T13024 for more discussion.
if ($this->shouldRequireEnabledUser()) {
if ($user->isLoggedIn() && !$user->getIsApproved()) {
$controller = new PhabricatorAuthNeedsApprovalController();
return $this->delegateToController($controller);
}
}
}
// NOTE: We do this last so that users get a login page instead of a 403
// if they need to login.
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
return new Aphront403Response();
}
}
public function getApplicationURI($path = '') {
if (!$this->getCurrentApplication()) {
throw new Exception(pht('No application!'));
}
return $this->getCurrentApplication()->getApplicationURI($path);
}
public function willSendResponse(AphrontResponse $response) {
$request = $this->getRequest();
if ($response instanceof AphrontDialogResponse) {
if (!$request->isAjax() && !$request->isQuicksand()) {
$dialog = $response->getDialog();
$title = $dialog->getTitle();
$short = $dialog->getShortTitle();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(coalesce($short, $title));
$page_content = array(
$crumbs,
$response->buildResponseString(),
);
$view = id(new PhabricatorStandardPageView())
->setRequest($request)
->setController($this)
->setDeviceReady(true)
->setTitle($title)
->appendChild($page_content);
$response = id(new AphrontWebpageResponse())
->setContent($view->render())
->setHTTPResponseCode($response->getHTTPResponseCode());
} else {
$response->getDialog()->setIsStandalone(true);
return id(new AphrontAjaxResponse())
->setContent(array(
'dialog' => $response->buildResponseString(),
));
}
} else if ($response instanceof AphrontRedirectResponse) {
if ($request->isAjax() || $request->isQuicksand()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'redirect' => $response->getURI(),
'close' => $response->getCloseDialogBeforeRedirect(),
));
}
}
return $response;
}
/**
* WARNING: Do not call this in new code.
*
* @deprecated See "Handles Technical Documentation".
*/
protected function loadViewerHandles(array $phids) {
return id(new PhabricatorHandleQuery())
->setViewer($this->getRequest()->getUser())
->withPHIDs($phids)
->execute();
}
public function buildApplicationMenu() {
return null;
}
protected function buildApplicationCrumbs() {
$crumbs = array();
$application = $this->getCurrentApplication();
if ($application) {
$icon = $application->getIcon();
if (!$icon) {
$icon = 'fa-puzzle';
}
$crumbs[] = id(new PHUICrumbView())
->setHref($this->getApplicationURI())
->setName($application->getName())
->setIcon($icon);
}
$view = new PHUICrumbsView();
foreach ($crumbs as $crumb) {
$view->addCrumb($crumb);
}
return $view;
}
protected function hasApplicationCapability($capability) {
return PhabricatorPolicyFilter::hasCapability(
$this->getRequest()->getUser(),
$this->getCurrentApplication(),
$capability);
}
protected function requireApplicationCapability($capability) {
PhabricatorPolicyFilter::requireCapability(
$this->getRequest()->getUser(),
$this->getCurrentApplication(),
$capability);
}
protected function explainApplicationCapability(
$capability,
$positive_message,
$negative_message) {
$can_act = $this->hasApplicationCapability($capability);
if ($can_act) {
$message = $positive_message;
$icon_name = 'fa-play-circle-o lightgreytext';
} else {
$message = $negative_message;
$icon_name = 'fa-lock';
}
$icon = id(new PHUIIconView())
->setIcon($icon_name);
require_celerity_resource('policy-css');
$phid = $this->getCurrentApplication()->getPHID();
$explain_uri = "/policy/explain/{$phid}/{$capability}/";
$message = phutil_tag(
'div',
array(
'class' => 'policy-capability-explanation',
),
array(
$icon,
javelin_tag(
'a',
array(
'href' => $explain_uri,
'sigil' => 'workflow',
),
$message),
));
return array($can_act, $message);
}
public function getDefaultResourceSource() {
return 'phabricator';
}
/**
* Create a new @{class:AphrontDialogView} with defaults filled in.
*
* @return AphrontDialogView New dialog.
*/
public function newDialog() {
$submit_uri = new PhutilURI($this->getRequest()->getRequestURI());
$submit_uri = $submit_uri->getPath();
return id(new AphrontDialogView())
->setUser($this->getRequest()->getUser())
->setSubmitURI($submit_uri);
}
public function newPage() {
$page = id(new PhabricatorStandardPageView())
->setRequest($this->getRequest())
->setController($this)
->setDeviceReady(true);
$application = $this->getCurrentApplication();
if ($application) {
$page->setApplicationName($application->getName());
if ($application->getTitleGlyph()) {
$page->setGlyph($application->getTitleGlyph());
}
}
$viewer = $this->getRequest()->getUser();
if ($viewer) {
$page->setUser($viewer);
}
return $page;
}
public function newApplicationMenu() {
return id(new PHUIApplicationMenuView())
->setViewer($this->getViewer());
}
public function newCurtainView($object = null) {
$viewer = $this->getViewer();
$action_id = celerity_generate_unique_node_id();
$action_list = id(new PhabricatorActionListView())
->setViewer($viewer)
->setID($action_id);
// NOTE: Applications (objects of class PhabricatorApplication) can't
// currently be set here, although they don't need any of the extensions
// anyway. This should probably work differently than it does, though.
if ($object) {
if ($object instanceof PhabricatorLiskDAO) {
$action_list->setObject($object);
}
}
$curtain = id(new PHUICurtainView())
->setViewer($viewer)
->setActionList($action_list);
if ($object) {
$panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object);
foreach ($panels as $panel) {
$curtain->addPanel($panel);
}
}
return $curtain;
}
protected function buildTransactionTimeline(
PhabricatorApplicationTransactionInterface $object,
PhabricatorApplicationTransactionQuery $query,
PhabricatorMarkupEngine $engine = null,
$view_data = array()) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$xaction = $object->getApplicationTransactionTemplate();
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request)
->setURI(new PhutilURI(
'/transactions/showolder/'.$object->getPHID().'/'));
$xactions = $query
->setViewer($viewer)
->withObjectPHIDs(array($object->getPHID()))
->needComments(true)
->executeWithCursorPager($pager);
$xactions = array_reverse($xactions);
$timeline_engine = PhabricatorTimelineEngine::newForObject($object)
->setViewer($viewer)
->setTransactions($xactions)
->setViewData($view_data);
$view = $timeline_engine->buildTimelineView();
if ($engine) {
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$view->setMarkupEngine($engine);
}
$timeline = $view
->setPager($pager)
->setQuoteTargetID($this->getRequest()->getStr('quoteTargetID'))
->setQuoteRef($this->getRequest()->getStr('quoteRef'));
return $timeline;
}
public function buildApplicationCrumbsForEditEngine() {
// TODO: This is kind of gross, I'm basically just making this public so
// I can use it in EditEngine. We could do this without making it public
// by using controller delegation, or make it properly public.
return $this->buildApplicationCrumbs();
}
private function requireLegalpadSignatures() {
if (!$this->shouldRequireLogin()) {
return null;
}
if ($this->shouldAllowLegallyNonCompliantUsers()) {
return null;
}
$viewer = $this->getViewer();
if (!$viewer->hasSession()) {
return null;
}
$session = $viewer->getSession();
if ($session->getIsPartial()) {
// If the user hasn't made it through MFA yet, require they survive
// MFA first.
return null;
}
if ($session->getSignedLegalpadDocuments()) {
return null;
}
if (!$viewer->isLoggedIn()) {
return null;
}
$must_sign_docs = array();
$sign_docs = array();
$legalpad_class = 'PhabricatorLegalpadApplication';
$legalpad_installed = PhabricatorApplication::isClassInstalledForViewer(
$legalpad_class,
$viewer);
if ($legalpad_installed) {
$sign_docs = id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withSignatureRequired(1)
->needViewerSignatures(true)
->setOrder('oldest')
->execute();
foreach ($sign_docs as $sign_doc) {
if (!$sign_doc->getUserSignature($viewer->getPHID())) {
$must_sign_docs[] = $sign_doc;
}
}
}
if (!$must_sign_docs) {
// If nothing needs to be signed (either because there are no documents
// which require a signature, or because the user has already signed
// all of them) mark the session as good and continue.
$engine = id(new PhabricatorAuthSessionEngine())
->signLegalpadDocuments($viewer, $sign_docs);
return null;
}
$request = $this->getRequest();
$request->setURIMap(
array(
'id' => head($must_sign_docs)->getID(),
));
$application = PhabricatorApplication::getByClass($legalpad_class);
$this->setCurrentApplication($application);
$controller = new LegalpadDocumentSignController();
+ $controller->setIsSessionGate(true);
return $this->delegateToController($controller);
}
/* -( Deprecated )--------------------------------------------------------- */
/**
* DEPRECATED. Use @{method:newPage}.
*/
public function buildStandardPageView() {
return $this->newPage();
}
/**
* DEPRECATED. Use @{method:newPage}.
*/
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->appendChild($view);
return $page->produceAphrontResponse();
}
}
diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php
index f09d95af29..fb15e2af8f 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php
@@ -1,696 +1,714 @@
<?php
final class LegalpadDocumentSignController extends LegalpadController {
+ private $isSessionGate;
+
public function shouldAllowPublic() {
return true;
}
public function shouldAllowLegallyNonCompliantUsers() {
return true;
}
+ public function setIsSessionGate($is_session_gate) {
+ $this->isSessionGate = $is_session_gate;
+ return $this;
+ }
+
+ public function getIsSessionGate() {
+ return $this->isSessionGate;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$document = id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->needDocumentBodies(true)
->executeOne();
if (!$document) {
return new Aphront404Response();
}
$information = $this->readSignerInformation(
$document,
$request);
if ($information instanceof AphrontResponse) {
return $information;
}
list($signer_phid, $signature_data) = $information;
$signature = null;
$type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
$is_individual = ($document->getSignatureType() == $type_individual);
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());
// 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 PHUIInfoView())
->setSeverity(PHUIInfoView::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 PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setErrors(array($signed_text));
}
$field_errors = array(
'name' => true,
'email' => true,
'agree' => true,
);
$signature->setSignatureData($signature_data);
break;
case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
$signature = id(new LegalpadDocumentSignature())
->setDocumentPHID($document->getPHID())
->setDocumentVersion($document->getVersions());
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 PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(array($login_text));
}
$field_errors = array(
'name' => true,
'address' => true,
'contact.name' => true,
'email' => true,
);
$signature->setSignatureData($signature_data);
break;
}
$errors = array();
$hisec_token = null;
if ($request->isFormOrHisecPost() && !$has_signed) {
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) {
// Require MFA to sign legal documents.
if ($viewer->isLoggedIn()) {
$workflow_key = sprintf(
'legalpad.sign(%s)',
$document->getPHID());
$hisec_token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken(
$viewer,
$request,
$document->getURI());
}
$signature->save();
// If the viewer is logged in, signing for themselves, send them to
// the document page, which will show that they have signed the
// document. Unless of course they were required to sign the
// document to use Phabricator; in that case try really hard to
// re-direct them to where they wanted to go.
//
// Otherwise, send them to a completion page.
if ($viewer->isLoggedIn() && $is_individual) {
$next_uri = '/'.$document->getMonogram();
if ($document->getRequireSignature()) {
$request_uri = $request->getRequestURI();
$next_uri = (string)$request_uri;
}
} 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);
// Use the last content update as the modified date. We don't want to
// show that a document like a TOS was "updated" by an incidental change
// to a field like the preamble or privacy settings which does not actually
// affect the content of the agreement.
$content_updated = $document_body->getDateCreated();
// NOTE: We're avoiding `setPolicyObject()` here so we don't pick up
// extra UI elements that are unnecessary and clutter the signature page.
// These details are available on the "Manage" page.
$header = id(new PHUIHeaderView())
->setHeader($title)
->setUser($viewer)
- ->setEpoch($content_updated)
- ->addActionLink(
+ ->setEpoch($content_updated);
+
+ // If we're showing the user this document because it's required to use
+ // Phabricator and they haven't signed it, don't show the "Manage" button,
+ // since it won't work.
+ $is_gate = $this->getIsSessionGate();
+ if (!$is_gate) {
+ $header->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-pencil')
->setText(pht('Manage'))
->setHref($manage_uri)
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
+ }
$preamble_box = null;
if (strlen($document->getPreamble())) {
$preamble_text = new PHUIRemarkupView($viewer, $document->getPreamble());
// NOTE: We're avoiding `setObject()` here so we don't pick up extra UI
// elements like "Subscribers". This information is available on the
// "Manage" page, but just clutters up the "Signature" page.
$preamble = id(new PHUIPropertyListView())
->setUser($viewer)
->addSectionHeader(pht('Preamble'))
->addTextContent($preamble_text);
$preamble_box = new PHUIPropertyGroupView();
$preamble_box->addPropertyList($preamble);
}
$content = id(new PHUIDocumentView())
->addClass('legalpad')
->setHeader($header)
->appendChild(
array(
$signed_status,
$preamble_box,
$document_markup,
));
$signature_box = null;
if (!$has_signed) {
$error_view = null;
if ($errors) {
$error_view = id(new PHUIInfoView())
->setErrors($errors);
}
$signature_form = $this->buildSignatureForm(
$document,
$signature,
$field_errors);
switch ($document->getSignatureType()) {
default:
break;
case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
$box = id(new PHUIObjectBoxView())
->addClass('document-sign-box')
->setHeaderText(pht('Agree and Sign Document'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($signature_form);
if ($error_view) {
$box->setInfoView($error_view);
}
$signature_box = phutil_tag_div(
'phui-document-view-pro-box plt', $box);
break;
}
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
$crumbs->addTextCrumb($document->getMonogram());
$box = id(new PHUITwoColumnView())
->setFooter($signature_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($document->getPHID()))
->appendChild(array(
$content,
$box,
));
}
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();
}
}
}
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));
if ($document->getRequireSignature()) {
$cancel_uri = '/logout/';
$cancel_text = pht('Log Out');
} else {
$cancel_uri = $this->getApplicationURI();
$cancel_text = pht('Cancel');
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Sign Document'))
->addCancelButton($cancel_uri, $cancel_text));
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 = pht(
"%s:\n\n".
"This email address was used to sign a Legalpad document ".
"in Phabricator:\n\n".
" %s\n\n".
"Please verify you own this email address and accept the ".
"agreement by clicking this link:\n\n".
" %s\n\n".
"Your signature is not valid until you complete this ".
"verification step.\n\nYou can review the document here:\n\n".
" %s\n",
$name,
$doc_name,
$link,
$doc_link);
id(new PhabricatorMetaMTAMail())
->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.'));
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Feb 6, 2:34 PM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34031
Default Alt Text
(42 KB)

Event Timeline