setSubject('[Phabricator] Password Reset');
- $mail->setFrom($target_user->getPHID());
$mail->addTos(
array(
$target_user->getPHID(),
));
$mail->setBody($body);
$mail->saveAndSend();
$view = new AphrontRequestFailureView();
$view->setHeader('Check Your Email');
$view->appendChild(
'An email has been sent with a link you can use to login.
');
return $this->buildStandardPageResponse(
$view,
array(
'title' => 'Email Sent',
));
}
}
}
$email_auth = new AphrontFormView();
$email_auth
->setAction('/login/email/')
->setUser($request->getUser())
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Email')
->setName('email')
->setValue($request->getStr('email'))
->setError($e_email))
->appendChild(
id(new AphrontFormRecaptchaControl())
->setLabel('Captcha')
->setError($e_captcha))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Send Email'));
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Login Error');
$error_view->setErrors($errors);
}
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild('Forgot Password / Email Login
');
$panel->appendChild($email_auth);
return $this->buildStandardPageResponse(
array(
$error_view,
$panel,
),
array(
'title' => 'Create New Account',
));
}
}
diff --git a/src/applications/metamta/storage/mail/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/mail/PhabricatorMetaMTAMail.php
index 4e81fc3692..aa2bec6211 100644
--- a/src/applications/metamta/storage/mail/PhabricatorMetaMTAMail.php
+++ b/src/applications/metamta/storage/mail/PhabricatorMetaMTAMail.php
@@ -1,473 +1,544 @@
status = self::STATUS_QUEUE;
$this->retryCount = 0;
$this->nextRetry = time();
$this->parameters = array();
parent::__construct();
}
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'parameters' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
protected function setParam($param, $value) {
$this->parameters[$param] = $value;
return $this;
}
protected function getParam($param) {
return idx($this->parameters, $param);
}
/**
* In Gmail, conversations will be broken if you reply to a thread and the
* server sends back a response without referencing your Message-ID, even if
* it references a Message-ID earlier in the thread. To avoid this, use the
* parent email's message ID explicitly if it's available. This overwrites the
* "In-Reply-To" and "References" headers we would otherwise generate. This
* needs to be set whenever an action is triggered by an email message. See
* T251 for more details.
*
* @param string The "Message-ID" of the email which precedes this one.
* @return this
*/
public function setParentMessageID($id) {
$this->setParam('parent-message-id', $id);
return $this;
}
public function getParentMessageID() {
return $this->getParam('parent-message-id');
}
public function getSubject() {
return $this->getParam('subject');
}
public function addTos(array $phids) {
$phids = array_unique($phids);
$this->setParam('to', $phids);
return $this;
}
public function addCCs(array $phids) {
$phids = array_unique($phids);
$this->setParam('cc', $phids);
return $this;
}
public function addHeader($name, $value) {
$this->parameters['headers'][$name] = $value;
return $this;
}
public function addAttachment(PhabricatorMetaMTAAttachment $attachment) {
$this->parameters['attachments'][] = $attachment;
return $this;
}
public function getAttachments() {
return $this->getParam('attachments');
}
public function setAttachments(array $attachments) {
$this->setParam('attachments', $attachments);
return $this;
}
public function setFrom($from) {
$this->setParam('from', $from);
return $this;
}
public function setReplyTo($reply_to) {
$this->setParam('reply-to', $reply_to);
return $this;
}
public function setSubject($subject) {
$this->setParam('subject', $subject);
return $this;
}
public function setBody($body) {
$this->setParam('body', $body);
return $this;
}
public function getBody() {
return $this->getParam('body');
}
public function setIsHTML($html) {
$this->setParam('is-html', $html);
return $this;
}
public function getSimulatedFailureCount() {
return nonempty($this->getParam('simulated-failures'), 0);
}
public function setSimulatedFailureCount($count) {
$this->setParam('simulated-failures', $count);
return $this;
}
/**
* Flag that this is an auto-generated bulk message and should have bulk
* headers added to it if appropriate. Broadly, this means some flavor of
* "Precedence: bulk" or similar, but is implementation and configuration
* dependent.
*
* @param bool True if the mail is automated bulk mail.
* @return this
*/
public function setIsBulk($is_bulk) {
$this->setParam('is-bulk', $is_bulk);
return $this;
}
/**
* Use this method to set an ID used for message threading. MetaMTA will
* set appropriate headers (Message-ID, In-Reply-To, References and
* Thread-Index) based on the capabilities of the underlying mailer.
*
* @param string Unique identifier, appropriate for use in a Message-ID,
* In-Reply-To or References headers.
* @param bool If true, indicates this is the first message in the thread.
* @return this
*/
public function setThreadID($thread_id, $is_first_message = false) {
$this->setParam('thread-id', $thread_id);
$this->setParam('is-first-message', $is_first_message);
return $this;
}
/**
* Save a newly created mail to the database and attempt to send it
* immediately if the server is configured for immediate sends. When
* applications generate new mail they should generally use this method to
* deliver it. If the server doesn't use immediate sends, this has the same
* effect as calling save(): the mail will eventually be delivered by the
* MetaMTA daemon.
*
* @return this
*/
public function saveAndSend() {
$ret = null;
if (PhabricatorEnv::getEnvConfig('metamta.send-immediately')) {
$ret = $this->sendNow();
} else {
$ret = $this->save();
}
return $ret;
}
public function buildDefaultMailer() {
$class_name = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
PhutilSymbolLoader::loadClass($class_name);
return newv($class_name, array());
}
/**
* Attempt to deliver an email immediately, in this process.
*
* @param bool Try to deliver this email even if it has already been
* delivered or is in backoff after a failed delivery attempt.
* @param PhabricatorMailImplementationAdapter Use a specific mail adapter,
* instead of the default.
*
* @return void
*/
public function sendNow(
$force_send = false,
PhabricatorMailImplementationAdapter $mailer = null) {
if ($mailer === null) {
$mailer = $this->buildDefaultMailer();
}
if (!$force_send) {
if ($this->getStatus() != self::STATUS_QUEUE) {
throw new Exception("Trying to send an already-sent mail!");
}
if (time() < $this->getNextRetry()) {
throw new Exception("Trying to send an email before next retry!");
}
}
try {
$parameters = $this->parameters;
$phids = array();
foreach ($parameters as $key => $value) {
switch ($key) {
case 'from':
case 'to':
case 'cc':
if (!is_array($value)) {
$value = array($value);
}
foreach (array_filter($value) as $phid) {
$phids[] = $phid;
}
break;
}
}
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
+ $exclude = array();
+
$params = $this->parameters;
$default = PhabricatorEnv::getEnvConfig('metamta.default-address');
if (empty($params['from'])) {
$mailer->setFrom($default);
- } else if (!PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) {
+ } else {
$from = $params['from'];
- $handle = $handles[$from];
- if (empty($params['reply-to'])) {
- $params['reply-to'] = $handle->getEmail();
- $params['reply-to-name'] = $handle->getFullName();
+
+ // If the user has set their preferences to not send them email about
+ // things they do, exclude them from being on To or Cc.
+ $from_user = id(new PhabricatorUser())->loadOneWhere(
+ 'phid = %s',
+ $from);
+ if ($from_user) {
+ $pref_key = PhabricatorUserPreferences::PREFERENCE_NO_SELF_MAIL;
+ $exclude_self = $from_user
+ ->loadPreferences()
+ ->getPreference($pref_key);
+ if ($exclude_self) {
+ $exclude[$from] = true;
+ }
+ }
+
+ if (!PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) {
+ $handle = $handles[$from];
+ if (empty($params['reply-to'])) {
+ $params['reply-to'] = $handle->getEmail();
+ $params['reply-to-name'] = $handle->getFullName();
+ }
+ $mailer->setFrom(
+ $default,
+ $handle->getFullName());
+ unset($params['from']);
}
- $mailer->setFrom(
- $default,
- $handle->getFullName());
- unset($params['from']);
}
- $is_first = !empty($params['is-first-message']);
+ $is_first = idx($params, 'is-first-message');
unset($params['is-first-message']);
+ $is_threaded = (bool)idx($params, 'thread-id');
+
$reply_to_name = idx($params, 'reply-to-name', '');
unset($params['reply-to-name']);
+ $add_cc = null;
+ $add_to = null;
+
foreach ($params as $key => $value) {
switch ($key) {
case 'from':
$mailer->setFrom($handles[$value]->getEmail());
break;
case 'reply-to':
$mailer->addReplyTo($value, $reply_to_name);
break;
case 'to':
- $emails = $this->getDeliverableEmailsFromHandles($value, $handles);
+ $emails = $this->getDeliverableEmailsFromHandles(
+ $value,
+ $handles,
+ $exclude);
if ($emails) {
- $mailer->addTos($emails);
- } else {
- if ($value) {
- throw new Exception(
- "All 'To' objects are undeliverable (e.g., disabled users).");
- } else {
- throw new Exception("No 'To' specified!");
- }
+ $add_to = $emails;
}
break;
case 'cc':
- $emails = $this->getDeliverableEmailsFromHandles($value, $handles);
+ $emails = $this->getDeliverableEmailsFromHandles(
+ $value,
+ $handles,
+ $exclude);
if ($emails) {
- $mailer->addCCs($emails);
+ $add_cc = $emails;
}
break;
case 'headers':
foreach ($value as $header_key => $header_value) {
// NOTE: If we have \n in a header, SES rejects the email.
$header_value = str_replace("\n", " ", $header_value);
$mailer->addHeader($header_key, $header_value);
}
break;
case 'attachments':
foreach ($value as $attachment) {
$mailer->addAttachment(
$attachment->getData(),
$attachment->getFilename(),
$attachment->getMimeType()
);
}
break;
case 'body':
$mailer->setBody($value);
break;
case 'subject':
+ if ($is_threaded) {
+ $add_re = PhabricatorEnv::getEnvConfig('metamta.re-prefix');
+
+ // If this message has a single recipient, respect their "Re:"
+ // preference. Otherwise, use the global setting.
+
+ $to = idx($params, 'to', array());
+ $cc = idx($params, 'cc', array());
+ if (count($to) == 1 && count($cc) == 0) {
+ $user = id(new PhabricatorUser())->loadOneWhere(
+ 'phid = %s',
+ head($to));
+ if ($user) {
+ $prefs = $user->loadPreferences();
+ $pref_key = PhabricatorUserPreferences::PREFERENCE_RE_PREFIX;
+ $add_re = $prefs->getPreference($pref_key, $add_re);
+ }
+ }
+
+ if ($add_re) {
+ $value = 'Re: '.$value;
+ }
+ }
+
$mailer->setSubject($value);
break;
case 'is-html':
if ($value) {
$mailer->setIsHTML(true);
}
break;
case 'is-bulk':
if ($value) {
if (PhabricatorEnv::getEnvConfig('metamta.precedence-bulk')) {
$mailer->addHeader('Precedence', 'bulk');
}
}
break;
case 'thread-id':
if ($is_first && $mailer->supportsMessageIDHeader()) {
$mailer->addHeader('Message-ID', $value);
} else {
$in_reply_to = $value;
$references = array($value);
$parent_id = $this->getParentMessageID();
if ($parent_id) {
$in_reply_to = $parent_id;
// By RFC 2822, the most immediate parent should appear last
// in the "References" header, so this order is intentional.
$references[] = $parent_id;
}
$references = implode(' ', $references);
$mailer->addHeader('In-Reply-To', $in_reply_to);
$mailer->addHeader('References', $references);
}
$thread_index = $this->generateThreadIndex($value, $is_first);
$mailer->addHeader('Thread-Index', $thread_index);
break;
default:
// Just discard.
}
}
$mailer->addHeader('X-Mail-Transport-Agent', 'MetaMTA');
+ if ($add_to) {
+ $mailer->addTos($add_to);
+ if ($add_cc) {
+ $mailer->addCCs($add_cc);
+ }
+ } else if ($add_cc) {
+ // If we have CC addresses but no "to" address, promote the CCs to
+ // "to".
+ $mailer->addTos($add_cc);
+ } else {
+ $this->setStatus(self::STATUS_VOID);
+ $this->setMessage(
+ "Message has no valid recipients: all To/CC are disabled or ".
+ "configured not to receive this mail.");
+ return $this->save();
+ }
+
} catch (Exception $ex) {
$this->setStatus(self::STATUS_FAIL);
$this->setMessage($ex->getMessage());
return $this->save();
}
if ($this->getRetryCount() < $this->getSimulatedFailureCount()) {
$ok = false;
$error = 'Simulated failure.';
} else {
try {
$ok = $mailer->send();
$error = null;
} catch (Exception $ex) {
$ok = false;
$error = $ex->getMessage()."\n".$ex->getTraceAsString();
}
}
if (!$ok) {
$this->setMessage($error);
if ($this->getRetryCount() > self::MAX_RETRIES) {
$this->setStatus(self::STATUS_FAIL);
} else {
$this->setRetryCount($this->getRetryCount() + 1);
$next_retry = time() + ($this->getRetryCount() * self::RETRY_DELAY);
$this->setNextRetry($next_retry);
}
} else {
$this->setStatus(self::STATUS_SENT);
}
return $this->save();
}
public static function getReadableStatus($status_code) {
static $readable = array(
self::STATUS_QUEUE => "Queued for Delivery",
self::STATUS_FAIL => "Delivery Failed",
self::STATUS_SENT => "Sent",
+ self::STATUS_VOID => "Void",
);
$status_code = coalesce($status_code, '?');
return idx($readable, $status_code, $status_code);
}
private function generateThreadIndex($seed, $is_first_mail) {
// When threading, Outlook ignores the 'References' and 'In-Reply-To'
// headers that most clients use. Instead, it uses a custom 'Thread-Index'
// header. The format of this header is something like this (from
// camel-exchange-folder.c in Evolution Exchange):
/* A new post to a folder gets a 27-byte-long thread index. (The value
* is apparently unique but meaningless.) Each reply to a post gets a
* 32-byte-long thread index whose first 27 bytes are the same as the
* parent's thread index. Each reply to any of those gets a
* 37-byte-long thread index, etc. The Thread-Index header contains a
* base64 representation of this value.
*/
// The specific implementation uses a 27-byte header for the first email
// a recipient receives, and a random 5-byte suffix (32 bytes total)
// thereafter. This means that all the replies are (incorrectly) siblings,
// but it would be very difficult to keep track of the entire tree and this
// gets us reasonable client behavior.
$base = substr(md5($seed), 0, 27);
if (!$is_first_mail) {
// Not totally sure, but it seems like outlook orders replies by
// thread-index rather than timestamp, so to get these to show up in the
// right order we use the time as the last 4 bytes.
$base .= ' '.pack('N', time());
}
return base64_encode($base);
}
private function getDeliverableEmailsFromHandles(
array $phids,
- array $handles) {
+ array $handles,
+ array $exclude) {
$emails = array();
foreach ($phids as $phid) {
if ($handles[$phid]->isDisabled()) {
continue;
}
if (!$handles[$phid]->isComplete()) {
continue;
}
+ if (isset($exclude[$phid])) {
+ continue;
+ }
$emails[] = $handles[$phid]->getEmail();
}
return $emails;
}
}
diff --git a/src/applications/metamta/storage/mail/__init__.php b/src/applications/metamta/storage/mail/__init__.php
index a61fe4f106..16046a8a08 100644
--- a/src/applications/metamta/storage/mail/__init__.php
+++ b/src/applications/metamta/storage/mail/__init__.php
@@ -1,18 +1,20 @@
page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
- $this->pages = array(
- 'account' => 'Account',
- 'profile' => 'Profile',
- 'email' => 'Email',
- 'password' => 'Password',
- 'preferences' => 'Preferences',
- 'conduit' => 'Conduit Certificate',
- );
-
- if (!PhabricatorEnv::getEnvConfig('account.editable') ||
- !PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) {
- unset($this->pages['password']);
- }
-
- if (PhabricatorUserSSHKeysSettingsPanelController::isEnabled()) {
- $this->pages['sshkeys'] = 'SSH Public Keys';
- }
-
$oauth_providers = PhabricatorOAuthProvider::getAllProviders();
- foreach ($oauth_providers as $provider) {
- if (!$provider->isProviderEnabled()) {
- continue;
- }
- $key = $provider->getProviderKey();
- $name = $provider->getProviderName();
- $this->pages[$key] = $name.' Account';
- }
-
- if (empty($this->pages[$this->page])) {
- $this->page = key($this->pages);
- }
+ $sidenav = $this->renderSideNav($oauth_providers);
+ $this->page = $sidenav->selectFilter($this->page, 'account');
switch ($this->page) {
case 'account':
$delegate = new PhabricatorUserAccountSettingsPanelController($request);
break;
case 'profile':
$delegate = new PhabricatorUserProfileSettingsPanelController($request);
break;
case 'email':
$delegate = new PhabricatorUserEmailSettingsPanelController($request);
break;
+ case 'emailpref':
+ $delegate = new PhabricatorUserEmailPreferenceSettingsPanelController(
+ $request);
+ break;
case 'password':
$delegate = new PhabricatorUserPasswordSettingsPanelController(
$request);
break;
case 'conduit':
$delegate = new PhabricatorUserConduitSettingsPanelController($request);
break;
case 'sshkeys':
$delegate = new PhabricatorUserSSHKeysSettingsPanelController($request);
break;
case 'preferences':
$delegate = new PhabricatorUserPreferenceSettingsPanelController(
$request);
break;
default:
- if (empty($this->pages[$this->page])) {
- return new Aphront404Response();
- }
$delegate = new PhabricatorUserOAuthSettingsPanelController($request);
$delegate->setOAuthProvider($oauth_providers[$this->page]);
+ break;
}
$response = $this->delegateToController($delegate);
if ($response instanceof AphrontView) {
- $sidenav = $this->renderSideNav();
$sidenav->appendChild($response);
return $this->buildStandardPageResponse(
$sidenav,
array(
'title' => 'Account Settings',
));
} else {
return $response;
}
}
- private function renderSideNav() {
- $sidenav = new AphrontSideNavView();
- foreach ($this->pages as $page => $name) {
- $sidenav->addNavItem(
- phutil_render_tag(
- 'a',
- array(
- 'href' => '/settings/page/'.$page.'/',
- 'class' => ($page == $this->page)
- ? 'aphront-side-nav-selected'
- : null,
- ),
- phutil_escape_html($name)));
+ private function renderSideNav($oauth_providers) {
+ $sidenav = new AphrontSideNavFilterView();
+ $sidenav
+ ->setBaseURI(new PhutilURI('/settings/page/'))
+ ->addLabel('Account Information')
+ ->addFilter('account', 'Account')
+ ->addFilter('profile', 'Profile')
+ ->addSpacer()
+ ->addLabel('Email')
+ ->addFilter('email', 'Email Address')
+ ->addFilter('emailpref', 'Email Preferences')
+ ->addSpacer()
+ ->addLabel('Authentication');
+
+ if (PhabricatorEnv::getEnvConfig('account.editable') &&
+ PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) {
+ $sidenav->addFilter('password', 'Password');
}
+
+ $sidenav->addFilter('conduit', 'Conduit Certificate');
+
+ if (PhabricatorUserSSHKeysSettingsPanelController::isEnabled()) {
+ $sidenav->addFilter('sshkeys', 'SSH Public Keys');
+ }
+
+ $sidenav->addSpacer();
+ $sidenav->addLabel('Application Settings');
+ $sidenav->addFilter('preferences', 'Display Preferences');
+
+ $items = array();
+ foreach ($oauth_providers as $provider) {
+ if (!$provider->isProviderEnabled()) {
+ continue;
+ }
+ $key = $provider->getProviderKey();
+ $name = $provider->getProviderName();
+ $items[$key] = $name.' Account';
+ }
+
+ if ($items) {
+ $sidenav->addSpacer();
+ $sidenav->addLabel('Linked Accounts');
+ foreach ($items as $key => $name) {
+ $sidenav->addFilter($key, $name);
+ }
+ }
+
return $sidenav;
}
}
diff --git a/src/applications/people/controller/settings/__init__.php b/src/applications/people/controller/settings/__init__.php
index 08c21ebe1e..82e0c1d610 100644
--- a/src/applications/people/controller/settings/__init__.php
+++ b/src/applications/people/controller/settings/__init__.php
@@ -1,27 +1,27 @@
getRequest();
+ $user = $request->getUser();
+
+ $preferences = $user->loadPreferences();
+
+ $pref_re_prefix = PhabricatorUserPreferences::PREFERENCE_RE_PREFIX;
+ $pref_no_self_mail = PhabricatorUserPreferences::PREFERENCE_NO_SELF_MAIL;
+
+ $errors = array();
+ if ($request->isFormPost()) {
+
+ if ($request->getStr($pref_re_prefix) == 'default') {
+ $preferences->unsetPreference($pref_re_prefix);
+ } else {
+ $preferences->setPreference(
+ $pref_re_prefix,
+ $request->getBool($pref_re_prefix));
+ }
+
+ $preferences->setPreference(
+ $pref_no_self_mail,
+ $request->getStr($pref_no_self_mail));
+
+ $preferences->save();
+
+ return id(new AphrontRedirectResponse())
+ ->setURI('/settings/page/emailpref/?saved=true');
+ }
+
+ $notice = null;
+ if (!$errors) {
+ if ($request->getStr('saved')) {
+ $notice = new AphrontErrorView();
+ $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
+ $notice->setTitle('Changes Saved');
+ $notice->appendChild('Your changes have been saved.
');
+ }
+ } else {
+ $notice = new AphrontErrorView();
+ $notice->setTitle('Form Errors');
+ $notice->setErrors($errors);
+ }
+
+ $re_prefix_default = PhabricatorEnv::getEnvConfig('metamta.re-prefix')
+ ? 'Enabled'
+ : 'Disabled';
+
+ $re_prefix_value = $preferences->getPreference($pref_re_prefix);
+ if ($re_prefix_value === null) {
+ $re_prefix_value = 'defualt';
+ } else {
+ $re_prefix_value = $re_prefix_value
+ ? 'true'
+ : 'false';
+ }
+
+ $form = new AphrontFormView();
+ $form
+ ->setUser($user)
+ ->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setLabel('Self Actions')
+ ->setName($pref_no_self_mail)
+ ->setOptions(
+ array(
+ '0' => 'Send me an email when I take an action',
+ '1' => 'Do not send me an email when I take an action',
+ ))
+ ->setCaption('You can disable email about your own actions.')
+ ->setValue($preferences->getPreference($pref_no_self_mail, 0)))
+ ->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setLabel('Add "Re:" Prefix')
+ ->setName($pref_re_prefix)
+ ->setCaption(
+ 'Enable this option to fix threading in Mail.app on OS X Lion, '.
+ 'or if you like "Re:" in your email subjects.')
+ ->setOptions(
+ array(
+ 'default' => 'Use Server Default ('.$re_prefix_default.')',
+ 'true' => 'Enable "Re:" prefix',
+ 'false' => 'Disable "Re:" prefix',
+ ))
+ ->setValue($re_prefix_value));
+
+ $form
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue('Save'));
+
+ $panel = new AphrontPanelView();
+ $panel->setHeader('Email Preferences');
+ $panel->setWidth(AphrontPanelView::WIDTH_FORM);
+ $panel->appendChild($form);
+
+ return id(new AphrontNullView())
+ ->appendChild(
+ array(
+ $notice,
+ $panel,
+ ));
+ }
+}
diff --git a/src/applications/people/controller/settings/panels/oauth/__init__.php b/src/applications/people/controller/settings/panels/emailpref/__init__.php
similarity index 60%
copy from src/applications/people/controller/settings/panels/oauth/__init__.php
copy to src/applications/people/controller/settings/panels/emailpref/__init__.php
index 173c57fcb7..8a7376d88d 100644
--- a/src/applications/people/controller/settings/panels/oauth/__init__.php
+++ b/src/applications/people/controller/settings/panels/emailpref/__init__.php
@@ -1,22 +1,23 @@
provider = $oauth_provider;
return $this;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$provider = $this->provider;
$notice = null;
$provider_name = $provider->getProviderName();
$provider_key = $provider->getProviderKey();
$oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere(
'userID = %d AND oauthProvider = %s',
$user->getID(),
$provider->getProviderKey());
$form = new AphrontFormView();
$form
->setUser($user);
$forms = array();
$forms[] = $form;
if (!$oauth_info) {
$form
->appendChild(
'There is currently no '.
$provider_name.' account linked to your Phabricator account. You '.
'can link an account, which will allow you to use it to log into '.
'Phabricator.
');
- switch ($provider_key) {
- case PhabricatorOAuthProvider::PROVIDER_GITHUB:
- $form->appendChild(
- 'Additionally, you must '.
- 'link your Github account before Phabricator can access any '.
- 'information about hosted repositories.
');
- break;
- }
-
$auth_uri = $provider->getAuthURI();
$client_id = $provider->getClientID();
$redirect_uri = $provider->getRedirectURI();
$minimum_scope = $provider->getMinimumScope();
$form
->setAction($auth_uri)
->setMethod('GET')
->addHiddenInput('redirect_uri', $redirect_uri)
->addHiddenInput('client_id', $client_id)
->addHiddenInput('scope', $minimum_scope);
foreach ($provider->getExtraAuthParameters() as $key => $value) {
$form->addHiddenInput($key, $value);
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Link '.$provider_name." Account \xC2\xBB"));
} else {
$form
->appendChild(
'Your account is linked with '.
'a '.$provider_name.' account. You may use your '.$provider_name.' '.
'credentials to log into Phabricator.
')
->appendChild(
id(new AphrontFormStaticControl())
->setLabel($provider_name.' ID')
->setValue($oauth_info->getOAuthUID()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel($provider_name.' Name')
->setValue($oauth_info->getAccountName()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel($provider_name.' URI')
->setValue($oauth_info->getAccountURI()));
if (!$provider->isProviderLinkPermanent()) {
$unlink = 'Unlink '.$provider_name.' Account';
$unlink_form = new AphrontFormView();
$unlink_form
->setUser($user)
->appendChild(
'You may unlink this account '.
'from your '.$provider_name.' account. This will prevent you from '.
'logging in with your '.$provider_name.' credentials.
')
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink));
$forms['Unlink Account'] = $unlink_form;
}
$expires = $oauth_info->getTokenExpires();
if ($expires) {
if ($expires <= time()) {
$expires = "Expired";
} else {
$expires = phabricator_datetime($expires, $user);
}
} else {
$expires = 'No Information Available';
}
$scope = $oauth_info->getTokenScope();
if (!$scope) {
$scope = 'No Information Available';
}
$status = $oauth_info->getTokenStatus();
$status = PhabricatorUserOAuthInfo::getReadableTokenStatus($status);
$token_form = new AphrontFormView();
$token_form
->setUser($user)
->appendChild(
'insert rap about tokens
')
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Token Status')
->setValue($status))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Expires')
->setValue($expires))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Scope')
->setValue($scope));
$forms['Account Token Information'] = $token_form;
}
$panel = new AphrontPanelView();
$panel->setHeader($provider_name.' Account Settings');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
foreach ($forms as $name => $form) {
if ($name) {
$panel->appendChild('
'.$name.'
');
}
$panel->appendChild($form);
}
return id(new AphrontNullView())
->appendChild(
array(
$notice,
$panel,
));
}
}
diff --git a/src/applications/people/controller/settings/panels/oauth/__init__.php b/src/applications/people/controller/settings/panels/oauth/__init__.php
index 173c57fcb7..ff727a2068 100644
--- a/src/applications/people/controller/settings/panels/oauth/__init__.php
+++ b/src/applications/people/controller/settings/panels/oauth/__init__.php
@@ -1,22 +1,21 @@
getRequest();
$user = $request->getUser();
$preferences = $user->loadPreferences();
+ $pref_monospaced = PhabricatorUserPreferences::PREFERENCE_MONOSPACED;
+ $pref_titles = PhabricatorUserPreferences::PREFERENCE_TITLES;
+
if ($request->isFormPost()) {
- $monospaced = $request->getStr(
- PhabricatorUserPreferences::PREFERENCE_MONOSPACED);
+ $monospaced = $request->getStr($pref_monospaced);
// Prevent the user from doing stupid things.
$monospaced = preg_replace('/[^a-z0-9 ,"]+/i', '', $monospaced);
- $pref_dict = array(
- PhabricatorUserPreferences::PREFERENCE_TITLES =>
- $request->getStr(PhabricatorUserPreferences::PREFERENCE_TITLES),
- PhabricatorUserPreferences::PREFERENCE_MONOSPACED =>
- $monospaced);
+ $preferences->setPreference($pref_titles, $request->getStr($pref_titles));
+ $preferences->setPreference($pref_monospaced, $monospaced);
- $preferences->setPreferences($pref_dict);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI('/settings/page/preferences/?saved=true');
}
$example_string = <<setUser($user)
->setAction('/settings/page/preferences/')
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Page Titles')
- ->setName(PhabricatorUserPreferences::PREFERENCE_TITLES)
- ->setValue($preferences->getPreference(
- PhabricatorUserPreferences::PREFERENCE_TITLES))
+ ->setName($pref_titles)
+ ->setValue($preferences->getPreference($pref_titles))
->setOptions(
array(
'glyph' =>
"In page titles, show Tool names as unicode glyphs: \xE2\x9A\x99",
'text' =>
'In page titles, show Tool names as plain text: [Differential]',
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Monospaced Font')
- ->setName(PhabricatorUserPreferences::PREFERENCE_MONOSPACED)
+ ->setName($pref_monospaced)
->setCaption(
'Overrides default fonts in tools like Differential. '.
'(Default: 10px "Menlo", "Consolas", "Monaco", '.
'monospace)')
- ->setValue($preferences->getPreference(
- PhabricatorUserPreferences::PREFERENCE_MONOSPACED)))
+ ->setValue($preferences->getPreference($pref_monospaced)))
->appendChild(
id(new AphrontFormMarkupControl())
->setValue(
''.
phutil_escape_html($example_string).
''))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save Preferences'));
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
- $panel->setHeader('Phabricator Preferences');
+ $panel->setHeader('Display Preferences');
$panel->appendChild($form);
$error_view = null;
if ($request->getStr('saved') === 'true') {
$error_view = id(new AphrontErrorView())
->setTitle('Preferences Saved')
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setErrors(array('Your preferences have been saved.'));
}
return id(new AphrontNullView())
->appendChild(
array(
$error_view,
$panel,
));
}
}
diff --git a/src/applications/people/storage/preferences/PhabricatorUserPreferences.php b/src/applications/people/storage/preferences/PhabricatorUserPreferences.php
index f84b87c0d4..06c47de689 100644
--- a/src/applications/people/storage/preferences/PhabricatorUserPreferences.php
+++ b/src/applications/people/storage/preferences/PhabricatorUserPreferences.php
@@ -1,39 +1,53 @@
array(
'preferences' => self::SERIALIZATION_JSON,
),
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
- public function getPreference($key) {
- return idx($this->getPreferences(), $key);
+ public function getPreference($key, $default = null) {
+ return idx($this->preferences, $key, $default);
+ }
+
+ public function setPreference($key, $value) {
+ $this->preferences[$key] = $value;
+ return $this;
}
+
+ public function unsetPreference($key) {
+ unset($this->preferences[$key]);
+ return $this;
+ }
+
}