Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / people / storage / PhabricatorUserEmail.php
blob4e43b2fb41bf8e1dd34ec7533b0d1aa30ce49908
1 <?php
3 /**
4 * @task restrictions Domain Restrictions
5 * @task email Email About Email
6 */
7 final class PhabricatorUserEmail
8 extends PhabricatorUserDAO
9 implements
10 PhabricatorDestructibleInterface,
11 PhabricatorPolicyInterface {
13 protected $userPHID;
14 protected $address;
15 protected $isVerified;
16 protected $isPrimary;
17 protected $verificationCode;
19 private $user = self::ATTACHABLE;
21 const MAX_ADDRESS_LENGTH = 128;
23 protected function getConfiguration() {
24 return array(
25 self::CONFIG_AUX_PHID => true,
26 self::CONFIG_COLUMN_SCHEMA => array(
27 'address' => 'sort128',
28 'isVerified' => 'bool',
29 'isPrimary' => 'bool',
30 'verificationCode' => 'text64?',
32 self::CONFIG_KEY_SCHEMA => array(
33 'address' => array(
34 'columns' => array('address'),
35 'unique' => true,
37 'userPHID' => array(
38 'columns' => array('userPHID', 'isPrimary'),
41 ) + parent::getConfiguration();
44 public function getPHIDType() {
45 return PhabricatorPeopleUserEmailPHIDType::TYPECONST;
48 public function getVerificationURI() {
49 return '/emailverify/'.$this->getVerificationCode().'/';
52 public function save() {
53 if (!$this->verificationCode) {
54 $this->setVerificationCode(Filesystem::readRandomCharacters(24));
56 return parent::save();
59 public function attachUser(PhabricatorUser $user) {
60 $this->user = $user;
61 return $this;
64 public function getUser() {
65 return $this->assertAttached($this->user);
69 /* -( Domain Restrictions )------------------------------------------------ */
72 /**
73 * @task restrictions
75 public static function isValidAddress($address) {
76 if (strlen($address) > self::MAX_ADDRESS_LENGTH) {
77 return false;
80 // Very roughly validate that this address isn't so mangled that a
81 // reasonable piece of code might completely misparse it. In particular,
82 // the major risks are:
84 // - `PhutilEmailAddress` needs to be able to extract the domain portion
85 // from it.
86 // - Reasonable mail adapters should be hard-pressed to interpret one
87 // address as several addresses.
89 // To this end, we're roughly verifying that there's some normal text, an
90 // "@" symbol, and then some more normal text.
92 $email_regex = '(^[a-z0-9_+.!-]+@[a-z0-9_+:.-]+\z)i';
93 if (!preg_match($email_regex, $address)) {
94 return false;
97 return true;
102 * @task restrictions
104 public static function describeValidAddresses() {
105 return pht(
106 'Email addresses should be in the form "user@domain.com". The maximum '.
107 'length of an email address is %s characters.',
108 new PhutilNumber(self::MAX_ADDRESS_LENGTH));
113 * @task restrictions
115 public static function isAllowedAddress($address) {
116 if (!self::isValidAddress($address)) {
117 return false;
120 $allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
121 if (!$allowed_domains) {
122 return true;
125 $addr_obj = new PhutilEmailAddress($address);
127 $domain = $addr_obj->getDomainName();
128 if (!$domain) {
129 return false;
132 $lower_domain = phutil_utf8_strtolower($domain);
133 foreach ($allowed_domains as $allowed_domain) {
134 $lower_allowed = phutil_utf8_strtolower($allowed_domain);
135 if ($lower_allowed === $lower_domain) {
136 return true;
140 return false;
145 * @task restrictions
147 public static function describeAllowedAddresses() {
148 $domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
149 if (!$domains) {
150 return null;
153 if (count($domains) == 1) {
154 return pht('Email address must be @%s', head($domains));
155 } else {
156 return pht(
157 'Email address must be at one of: %s',
158 implode(', ', $domains));
164 * Check if this install requires email verification.
166 * @return bool True if email addresses must be verified.
168 * @task restrictions
170 public static function isEmailVerificationRequired() {
171 // NOTE: Configuring required email domains implies required verification.
172 return PhabricatorEnv::getEnvConfig('auth.require-email-verification') ||
173 PhabricatorEnv::getEnvConfig('auth.email-domains');
177 /* -( Email About Email )-------------------------------------------------- */
181 * Send a verification email from $user to this address.
183 * @param PhabricatorUser The user sending the verification.
184 * @return this
185 * @task email
187 public function sendVerificationEmail(PhabricatorUser $user) {
188 $username = $user->getUsername();
190 $address = $this->getAddress();
191 $link = PhabricatorEnv::getProductionURI($this->getVerificationURI());
194 $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
196 $signature = null;
197 if (!$is_serious) {
198 $signature = pht("Get Well Soon,\nPhabricator");
201 $body = sprintf(
202 "%s\n\n%s\n\n %s\n\n%s",
203 pht('Hi %s', $username),
204 pht(
205 'Please verify that you own this email address (%s) by '.
206 'clicking this link:',
207 $address),
208 $link,
209 $signature);
211 id(new PhabricatorMetaMTAMail())
212 ->addRawTos(array($address))
213 ->setForceDelivery(true)
214 ->setSubject(pht('[Phabricator] Email Verification'))
215 ->setBody($body)
216 ->setRelatedPHID($user->getPHID())
217 ->saveAndSend();
219 return $this;
224 * Send a notification email from $user to this address, informing the
225 * recipient that this is no longer their account's primary address.
227 * @param PhabricatorUser The user sending the notification.
228 * @param PhabricatorUserEmail New primary email address.
229 * @return this
230 * @task email
232 public function sendOldPrimaryEmail(
233 PhabricatorUser $user,
234 PhabricatorUserEmail $new) {
235 $username = $user->getUsername();
237 $old_address = $this->getAddress();
238 $new_address = $new->getAddress();
240 $body = sprintf(
241 "%s\n\n%s\n",
242 pht('Hi %s', $username),
243 pht(
244 'This email address (%s) is no longer your primary email address. '.
245 'Going forward, Phabricator will send all email to your new primary '.
246 'email address (%s).',
247 $old_address,
248 $new_address));
250 id(new PhabricatorMetaMTAMail())
251 ->addRawTos(array($old_address))
252 ->setForceDelivery(true)
253 ->setSubject(pht('[Phabricator] Primary Address Changed'))
254 ->setBody($body)
255 ->setFrom($user->getPHID())
256 ->setRelatedPHID($user->getPHID())
257 ->saveAndSend();
262 * Send a notification email from $user to this address, informing the
263 * recipient that this is now their account's new primary email address.
265 * @param PhabricatorUser The user sending the verification.
266 * @return this
267 * @task email
269 public function sendNewPrimaryEmail(PhabricatorUser $user) {
270 $username = $user->getUsername();
272 $new_address = $this->getAddress();
274 $body = sprintf(
275 "%s\n\n%s\n",
276 pht('Hi %s', $username),
277 pht(
278 'This is now your primary email address (%s). Going forward, '.
279 'Phabricator will send all email here.',
280 $new_address));
282 id(new PhabricatorMetaMTAMail())
283 ->addRawTos(array($new_address))
284 ->setForceDelivery(true)
285 ->setSubject(pht('[Phabricator] Primary Address Changed'))
286 ->setBody($body)
287 ->setFrom($user->getPHID())
288 ->setRelatedPHID($user->getPHID())
289 ->saveAndSend();
291 return $this;
295 /* -( PhabricatorDestructibleInterface )----------------------------------- */
298 public function destroyObjectPermanently(
299 PhabricatorDestructionEngine $engine) {
300 $this->delete();
304 /* -( PhabricatorPolicyInterface )----------------------------------------- */
306 public function getCapabilities() {
307 return array(
308 PhabricatorPolicyCapability::CAN_VIEW,
309 PhabricatorPolicyCapability::CAN_EDIT,
313 public function getPolicy($capability) {
314 $user = $this->getUser();
316 if ($this->getIsSystemAgent() || $this->getIsMailingList()) {
317 return PhabricatorPolicies::POLICY_ADMIN;
320 return $user->getPHID();
323 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
324 return false;