public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $this->requireApplicationCapability(PeopleCreateUsersCapability::CAPABILITY); $is_confirm = false; $errors = array(); $confirm_errors = array(); $e_emails = true; $message = $request->getStr('message'); $emails = $request->getStr('emails'); $severity = PHUIInfoView::SEVERITY_ERROR; if ($request->isFormPost()) { // NOTE: We aren't using spaces as a delimiter here because email // addresses with names often include spaces. $email_list = preg_split('/[,;\\n]+/', $emails); foreach ($email_list as $key => $email) { if (!strlen(trim($email))) { unset($email_list[$key]); } } if ($email_list) { $e_emails = null; } else { $e_emails = pht('Required'); $errors[] = pht('To send invites, you must enter at least one email address.'); } if (!$errors) { $is_confirm = true; $actions = PhabricatorAuthInviteAction::newActionListFromAddresses($viewer, $email_list); $any_valid = false; $all_valid = true; foreach ($actions as $action) { if ($action->willSend()) { $any_valid = true; } else { $all_valid = false; } } if (!$any_valid) { $confirm_errors[] = pht('None of the provided addresses are valid invite recipients. ' . 'Review the table below for details. Revise the address list ' . 'to continue.'); } else { if ($all_valid) { $confirm_errors[] = pht('All of the addresses appear to be valid invite recipients. ' . 'Confirm the actions below to continue.'); $severity = PHUIInfoView::SEVERITY_NOTICE; } else { $confirm_errors[] = pht('Some of the addresses you entered do not appear to be ' . 'valid recipients. Review the table below. You can revise ' . 'the address list, or ignore these errors and continue.'); $severity = PHUIInfoView::SEVERITY_WARNING; } } if ($any_valid && $request->getBool('confirm')) { // TODO: The copywriting on this mail could probably be more // engaging and we could have a fancy HTML version. $template = array(); $template[] = pht('%s has invited you to join Phabricator.', $viewer->getFullName()); if (strlen(trim($message))) { $template[] = $message; } $template[] = pht('To register an account and get started, follow this link:'); // This isn't a variable; it will be replaced later on in the // daemons once they generate the URI. $template[] = '{$INVITE_URI}'; $template[] = pht('If you already have an account, you can follow the link to ' . 'quickly verify this email address.'); $template = implode("\n\n", $template); foreach ($actions as $action) { if ($action->willSend()) { $action->sendInvite($viewer, $template); } } // TODO: This is a bit anticlimactic. We don't really have anything // to show the user because the action is happening in the background // and the invites won't exist yet. After T5166 we can show a // better progress bar. return id(new AphrontRedirectResponse())->setURI($this->getApplicationURI()); } } } if ($is_confirm) { $title = pht('Confirm Invites'); } else { $title = pht('Invite Users'); } $crumbs = $this->buildApplicationCrumbs(); if ($is_confirm) { $crumbs->addTextCrumb(pht('Confirm')); } else { $crumbs->addTextCrumb(pht('Invite Users')); } $crumbs->setBorder(true); $confirm_box = null; $info_view = null; if ($is_confirm) { $handles = array(); if ($actions) { $handles = $this->loadViewerHandles(mpull($actions, 'getUserPHID')); } $invite_table = id(new PhabricatorAuthInviteActionTableView())->setUser($viewer)->setInviteActions($actions)->setHandles($handles); $confirm_form = null; if ($any_valid) { $confirm_form = id(new AphrontFormView())->setUser($viewer)->addHiddenInput('message', $message)->addHiddenInput('emails', $emails)->addHiddenInput('confirm', true)->appendRemarkupInstructions(pht('If everything looks good, click **Send Invitations** to ' . 'deliver email invitations these users. Otherwise, edit the ' . 'email list or personal message at the bottom of the page to ' . 'revise the invitations.'))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Send Invitations'))); } $info_view = id(new PHUIInfoView())->setErrors($confirm_errors)->setSeverity($severity); $confirm_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Confirm Invites'))->setTable($invite_table)->appendChild($confirm_form)->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); } $form = id(new AphrontFormView())->setUser($viewer)->appendRemarkupInstructions(pht('To invite users to Phabricator, enter their email addresses below. ' . 'Separate addresses with commas or newlines.'))->appendChild(id(new AphrontFormTextAreaControl())->setLabel(pht('Email Addresses'))->setName(pht('emails'))->setValue($emails)->setError($e_emails)->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL))->appendRemarkupInstructions(pht('You can optionally include a heartfelt personal message in ' . 'the email.'))->appendChild(id(new AphrontFormTextAreaControl())->setLabel(pht('Message'))->setName(pht('message'))->setValue($message))->appendChild(id(new AphrontFormSubmitControl())->setValue($is_confirm ? pht('Update Preview') : pht('Continue'))->addCancelButton($this->getApplicationURI('invite/'))); $header = id(new PHUIHeaderView())->setHeader($title)->setHeaderIcon('fa-group'); $box = id(new PHUIObjectBoxView())->setHeaderText($is_confirm ? pht('Revise Invites') : pht('Invite Users'))->setFormErrors($errors)->setForm($form)->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $view = id(new PHUITwoColumnView())->setHeader($header)->setFooter(array($info_view, $confirm_box, $box)); return $this->newPage()->setTitle($title)->setCrumbs($crumbs)->appendChild($view); }
public static function newActionListFromAddresses(PhabricatorUser $viewer, array $addresses) { $results = array(); foreach ($addresses as $address) { $result = new PhabricatorAuthInviteAction(); $result->rawInput = $address; $email = new PhutilEmailAddress($address); $result->emailAddress = phutil_utf8_strtolower($email->getAddress()); if (!preg_match('/^\\S+@\\S+\\.\\S+\\z/', $result->emailAddress)) { $result->issues[] = self::ISSUE_PARSE; } $results[] = $result; } // Identify duplicates. $address_groups = mgroup($results, 'getEmailAddress'); foreach ($address_groups as $address => $group) { if (count($group) > 1) { foreach ($group as $action) { $action->issues[] = self::ISSUE_DUPLICATE; } } } // Identify addresses which are already in the system. $addresses = mpull($results, 'getEmailAddress'); $email_objects = id(new PhabricatorUserEmail())->loadAllWhere('address IN (%Ls)', $addresses); $email_map = array(); foreach ($email_objects as $email_object) { $address_key = phutil_utf8_strtolower($email_object->getAddress()); $email_map[$address_key] = $email_object; } // Identify outstanding invites. $invites = id(new PhabricatorAuthInviteQuery())->setViewer($viewer)->withEmailAddresses($addresses)->execute(); $invite_map = mpull($invites, null, 'getEmailAddress'); foreach ($results as $action) { $email = idx($email_map, $action->getEmailAddress()); if ($email) { if ($email->getUserPHID()) { $action->userPHID = $email->getUserPHID(); if ($email->getIsVerified()) { $action->issues[] = self::ISSUE_VERIFIED; } else { $action->issues[] = self::ISSUE_UNVERIFIED; } } } $invite = idx($invite_map, $action->getEmailAddress()); if ($invite) { if ($invite->getAcceptedByPHID()) { $action->issues[] = self::ISSUE_ACCEPTED; if (!$action->userPHID) { // This could be different from the user who is currently attached // to the email address if the address was removed or added to a // different account later. Only show it if the address was // removed, since the current status is more up-to-date otherwise. $action->userPHID = $invite->getAcceptedByPHID(); } } else { $action->issues[] = self::ISSUE_INVITED; } } } foreach ($results as $result) { foreach ($result->getIssues() as $issue) { switch ($issue) { case self::ISSUE_PARSE: $result->action = self::ACTION_ERROR; break; case self::ISSUE_ACCEPTED: case self::ISSUE_VERIFIED: $result->action = self::ACTION_IGNORE; break; } } if (!$result->action) { $result->action = self::ACTION_SEND; } } return $results; }