~ubuntu-branches/ubuntu/wily/phabricator/wily

« back to all changes in this revision

Viewing changes to phabricator/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php

  • Committer: Package Import Robot
  • Author(s): Richard Sellam
  • Date: 2015-06-13 10:52:10 UTC
  • mfrom: (0.30.1) (0.29.1) (0.17.4) (2.1.9 sid)
  • Revision ID: package-import@ubuntu.com-20150613105210-5uirr7tvnk0n6e6y
Tags: 0~git20150613-1
* New snapshot release (closes: #787805)
* fixed typo in logrotate script (closes: #787645)

Show diffs side-by-side

added added

removed removed

Lines of Context:
47
47
 
48
48
  abstract public function validateMailReceiver($mail_receiver);
49
49
  abstract public function getPrivateReplyHandlerEmailAddress(
50
 
    PhabricatorObjectHandle $handle);
 
50
    PhabricatorUser $user);
51
51
 
52
52
  public function getReplyHandlerDomain() {
53
53
    return PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain');
117
117
    return null;
118
118
  }
119
119
 
120
 
  final public function getRecipientsSummary(
121
 
    array $to_handles,
122
 
    array $cc_handles) {
123
 
    assert_instances_of($to_handles, 'PhabricatorObjectHandle');
124
 
    assert_instances_of($cc_handles, 'PhabricatorObjectHandle');
125
 
 
126
 
    $body = '';
127
 
 
128
 
    if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) {
129
 
      if ($to_handles) {
130
 
        $body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n";
131
 
      }
132
 
      if ($cc_handles) {
133
 
        $body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n";
134
 
      }
135
 
    }
136
 
 
137
 
    return $body;
138
 
  }
139
 
 
140
 
  final public function getRecipientsSummaryHTML(
141
 
    array $to_handles,
142
 
    array $cc_handles) {
143
 
    assert_instances_of($to_handles, 'PhabricatorObjectHandle');
144
 
    assert_instances_of($cc_handles, 'PhabricatorObjectHandle');
145
 
 
146
 
    if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) {
147
 
      $body = array();
148
 
      if ($to_handles) {
149
 
        $body[] = phutil_tag('strong', array(), 'To: ');
150
 
        $body[] = phutil_implode_html(', ', mpull($to_handles, 'getName'));
151
 
        $body[] = phutil_tag('br');
152
 
      }
153
 
      if ($cc_handles) {
154
 
        $body[] = phutil_tag('strong', array(), 'Cc: ');
155
 
        $body[] = phutil_implode_html(', ', mpull($cc_handles, 'getName'));
156
 
        $body[] = phutil_tag('br');
157
 
      }
158
 
      return phutil_tag('div', array(), $body);
159
 
    } else {
160
 
      return '';
161
 
    }
162
 
 
163
 
  }
164
 
 
165
 
  final public function multiplexMail(
166
 
    PhabricatorMetaMTAMail $mail_template,
167
 
    array $to_handles,
168
 
    array $cc_handles) {
169
 
    assert_instances_of($to_handles, 'PhabricatorObjectHandle');
170
 
    assert_instances_of($cc_handles, 'PhabricatorObjectHandle');
171
 
 
172
 
    $result = array();
173
 
 
174
 
    // If MetaMTA is configured to always multiplex, skip the single-email
175
 
    // case.
176
 
    if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
177
 
      // If private replies are not supported, simply send one email to all
178
 
      // recipients and CCs. This covers cases where we have no reply handler,
179
 
      // or we have a public reply handler.
180
 
      if (!$this->supportsPrivateReplies()) {
181
 
        $mail = clone $mail_template;
182
 
        $mail->addTos(mpull($to_handles, 'getPHID'));
183
 
        $mail->addCCs(mpull($cc_handles, 'getPHID'));
184
 
 
185
 
        if ($this->supportsPublicReplies()) {
186
 
          $reply_to = $this->getPublicReplyHandlerEmailAddress();
187
 
          $mail->setReplyTo($reply_to);
188
 
        }
189
 
 
190
 
        $result[] = $mail;
191
 
 
192
 
        return $result;
193
 
      }
194
 
    }
195
 
 
196
 
    // TODO: This is pretty messy. We should really be doing all of this
197
 
    // multiplexing in the task queue, but that requires significant rewriting
198
 
    // in the general case. ApplicationTransactions can do it fairly easily,
199
 
    // but other mail sites currently can not, so we need to support this
200
 
    // junky version until they catch up and we can swap things over.
201
 
 
202
 
    $to_handles = $this->expandRecipientHandles($to_handles);
203
 
    $cc_handles = $this->expandRecipientHandles($cc_handles);
204
 
 
205
 
    $tos = mpull($to_handles, null, 'getPHID');
206
 
    $ccs = mpull($cc_handles, null, 'getPHID');
207
 
 
208
 
    // Merge all the recipients together. TODO: We could keep the CCs as real
209
 
    // CCs and send to a "noreply@domain.com" type address, but keep it simple
210
 
    // for now.
211
 
    $recipients = $tos + $ccs;
212
 
 
213
 
    // When multiplexing mail, explicitly include To/Cc information in the
214
 
    // message body and headers.
215
 
 
216
 
    $mail_template = clone $mail_template;
217
 
 
218
 
    $mail_template->addPHIDHeaders('X-Phabricator-To', array_keys($tos));
219
 
    $mail_template->addPHIDHeaders('X-Phabricator-Cc', array_keys($ccs));
220
 
 
221
 
    $body = $mail_template->getBody();
222
 
    $body .= "\n";
223
 
    $body .= $this->getRecipientsSummary($to_handles, $cc_handles);
224
 
 
225
 
    $html_body = $mail_template->getHTMLBody();
226
 
    if (strlen($html_body)) {
227
 
      $html_body .= hsprintf('%s',
228
 
        $this->getRecipientsSummaryHTML($to_handles, $cc_handles));
229
 
    }
230
 
 
231
 
    foreach ($recipients as $phid => $recipient) {
232
 
 
233
 
      $mail = clone $mail_template;
234
 
      if (isset($to_handles[$phid])) {
235
 
        $mail->addTos(array($phid));
236
 
      } else if (isset($cc_handles[$phid])) {
237
 
        $mail->addCCs(array($phid));
238
 
      } else {
239
 
        // not good - they should be a to or a cc
240
 
        continue;
241
 
      }
242
 
 
243
 
      $mail->setBody($body);
244
 
      $mail->setHTMLBody($html_body);
245
 
 
246
 
      $reply_to = null;
247
 
      if (!$reply_to && $this->supportsPrivateReplies()) {
248
 
        $reply_to = $this->getPrivateReplyHandlerEmailAddress($recipient);
249
 
      }
250
 
 
251
 
      if (!$reply_to && $this->supportsPublicReplies()) {
252
 
        $reply_to = $this->getPublicReplyHandlerEmailAddress();
253
 
      }
254
 
 
255
 
      if ($reply_to) {
256
 
        $mail->setReplyTo($reply_to);
257
 
      }
258
 
 
259
 
      $result[] = $mail;
260
 
    }
261
 
 
262
 
    return $result;
263
 
  }
264
 
 
265
120
  protected function getDefaultPublicReplyHandlerEmailAddress($prefix) {
266
121
 
267
122
    $receiver = $this->getMailReceiver();
288
143
  }
289
144
 
290
145
  protected function getDefaultPrivateReplyHandlerEmailAddress(
291
 
    PhabricatorObjectHandle $handle,
 
146
    PhabricatorUser $user,
292
147
    $prefix) {
293
148
 
294
 
    if ($handle->getType() != PhabricatorPeopleUserPHIDType::TYPECONST) {
295
 
      // You must be a real user to get a private reply handler address.
296
 
      return null;
297
 
    }
298
 
 
299
 
    $user = id(new PhabricatorPeopleQuery())
300
 
      ->setViewer(PhabricatorUser::getOmnipotentUser())
301
 
      ->withPHIDs(array($handle->getPHID()))
302
 
      ->executeOne();
303
 
 
304
 
    if (!$user) {
305
 
      // This may happen if a user was subscribed to something, and was then
306
 
      // deleted.
307
 
      return null;
308
 
    }
309
 
 
310
149
    $receiver = $this->getMailReceiver();
311
150
    $receiver_id = $receiver->getID();
312
151
    $user_id = $user->getID();
313
152
    $hash = PhabricatorObjectMailReceiver::computeMailHash(
314
153
      $receiver->getMailKey(),
315
 
      $handle->getPHID());
 
154
      $user->getPHID());
316
155
    $domain = $this->getReplyHandlerDomain();
317
156
 
318
157
    $address = "{$prefix}{$receiver_id}+{$user_id}+{$hash}@{$domain}";
368
207
    return rtrim($output);
369
208
  }
370
209
 
371
 
  private function expandRecipientHandles(array $handles) {
372
 
    if (!$handles) {
 
210
 
 
211
  /**
 
212
   * Produce a list of mail targets for a given to/cc list.
 
213
   *
 
214
   * Each target should be sent a separate email, and contains the information
 
215
   * required to generate it with appropriate permissions and configuration.
 
216
   *
 
217
   * @param list<phid> List of "To" PHIDs.
 
218
   * @param list<phid> List of "CC" PHIDs.
 
219
   * @return list<PhabricatorMailTarget> List of targets.
 
220
   */
 
221
  final public function getMailTargets(array $raw_to, array $raw_cc) {
 
222
    list($to, $cc) = $this->expandRecipientPHIDs($raw_to, $raw_cc);
 
223
    list($to, $cc) = $this->loadRecipientUsers($to, $cc);
 
224
    list($to, $cc) = $this->filterRecipientUsers($to, $cc);
 
225
 
 
226
    if (!$to && !$cc) {
373
227
      return array();
374
228
    }
375
229
 
376
 
    $phids = mpull($handles, 'getPHID');
377
 
    $results = id(new PhabricatorMetaMTAMemberQuery())
378
 
      ->setViewer(PhabricatorUser::getOmnipotentUser())
379
 
      ->withPHIDs($phids)
380
 
      ->executeExpansion();
381
 
 
382
 
    return id(new PhabricatorHandleQuery())
383
 
      ->setViewer(PhabricatorUser::getOmnipotentUser())
384
 
      ->withPHIDs($results)
385
 
      ->execute();
 
230
    $template = id(new PhabricatorMailTarget())
 
231
      ->setRawToPHIDs($raw_to)
 
232
      ->setRawCCPHIDs($raw_cc);
 
233
 
 
234
    // Set the public reply address as the default, if one exists. We
 
235
    // might replace this with a private address later.
 
236
    if ($this->supportsPublicReplies()) {
 
237
      $reply_to = $this->getPublicReplyHandlerEmailAddress();
 
238
      if ($reply_to) {
 
239
        $template->setReplyTo($reply_to);
 
240
      }
 
241
    }
 
242
 
 
243
    $supports_private_replies = $this->supportsPrivateReplies();
 
244
    $mail_all = !PhabricatorEnv::getEnvConfig('metamta.one-mail-per-recipient');
 
245
    $targets = array();
 
246
    if ($mail_all) {
 
247
      $target = id(clone $template)
 
248
        ->setViewer(PhabricatorUser::getOmnipotentUser())
 
249
        ->setToMap($to)
 
250
        ->setCCMap($cc);
 
251
 
 
252
      $targets[] = $target;
 
253
    } else {
 
254
      $map = $to + $cc;
 
255
 
 
256
      foreach ($map as $phid => $user) {
 
257
        $target = id(clone $template)
 
258
          ->setViewer($user)
 
259
          ->setToMap(array($phid => $user))
 
260
          ->setCCMap(array());
 
261
 
 
262
        if ($supports_private_replies) {
 
263
          $reply_to = $this->getPrivateReplyHandlerEmailAddress($user);
 
264
          if ($reply_to) {
 
265
            $target->setReplyTo($reply_to);
 
266
          }
 
267
        }
 
268
 
 
269
        $targets[] = $target;
 
270
      }
 
271
    }
 
272
 
 
273
    return $targets;
 
274
  }
 
275
 
 
276
 
 
277
  /**
 
278
   * Expand lists of recipient PHIDs.
 
279
   *
 
280
   * This takes any compound recipients (like projects) and looks up all their
 
281
   * members.
 
282
   *
 
283
   * @param list<phid> List of To PHIDs.
 
284
   * @param list<phid> List of CC PHIDs.
 
285
   * @return pair<list<phid>, list<phid>> Expanded PHID lists.
 
286
   */
 
287
  private function expandRecipientPHIDs(array $to, array $cc) {
 
288
    $to_result = array();
 
289
    $cc_result = array();
 
290
 
 
291
    $all_phids = array_merge($to, $cc);
 
292
    if ($all_phids) {
 
293
      $map = id(new PhabricatorMetaMTAMemberQuery())
 
294
        ->setViewer(PhabricatorUser::getOmnipotentUser())
 
295
        ->withPHIDs($all_phids)
 
296
        ->execute();
 
297
      foreach ($to as $phid) {
 
298
        foreach ($map[$phid] as $expanded) {
 
299
          $to_result[$expanded] = $expanded;
 
300
        }
 
301
      }
 
302
      foreach ($cc as $phid) {
 
303
        foreach ($map[$phid] as $expanded) {
 
304
          $cc_result[$expanded] = $expanded;
 
305
        }
 
306
      }
 
307
    }
 
308
 
 
309
    // Remove recipients from "CC" if they're also present in "To".
 
310
    $cc_result = array_diff_key($cc_result, $to_result);
 
311
 
 
312
    return array(array_values($to_result), array_values($cc_result));
 
313
  }
 
314
 
 
315
 
 
316
  /**
 
317
   * Load @{class:PhabricatorUser} objects for each recipient.
 
318
   *
 
319
   * Invalid recipients are dropped from the results.
 
320
   *
 
321
   * @param list<phid> List of To PHIDs.
 
322
   * @param list<phid> List of CC PHIDs.
 
323
   * @return pair<wild, wild> Maps from PHIDs to users.
 
324
   */
 
325
  private function loadRecipientUsers(array $to, array $cc) {
 
326
    $to_result = array();
 
327
    $cc_result = array();
 
328
 
 
329
    $all_phids = array_merge($to, $cc);
 
330
    if ($all_phids) {
 
331
      $users = id(new PhabricatorPeopleQuery())
 
332
        ->setViewer(PhabricatorUser::getOmnipotentUser())
 
333
        ->withPHIDs($all_phids)
 
334
        ->execute();
 
335
      $users = mpull($users, null, 'getPHID');
 
336
 
 
337
      foreach ($to as $phid) {
 
338
        if (isset($users[$phid])) {
 
339
          $to_result[$phid] = $users[$phid];
 
340
        }
 
341
      }
 
342
      foreach ($cc as $phid) {
 
343
        if (isset($users[$phid])) {
 
344
          $cc_result[$phid] = $users[$phid];
 
345
        }
 
346
      }
 
347
    }
 
348
 
 
349
    return array($to_result, $cc_result);
 
350
  }
 
351
 
 
352
 
 
353
  /**
 
354
   * Remove recipients who do not have permission to view the mail receiver.
 
355
   *
 
356
   * @param map<string, PhabricatorUser> Map of "To" users.
 
357
   * @param map<string, PhabricatorUser> Map of "CC" users.
 
358
   * @return pair<wild, wild> Filtered user maps.
 
359
   */
 
360
  private function filterRecipientUsers(array $to, array $cc) {
 
361
    $to_result = array();
 
362
    $cc_result = array();
 
363
 
 
364
    $all_users = $to + $cc;
 
365
    if ($all_users) {
 
366
      $can_see = array();
 
367
      $object = $this->getMailReceiver();
 
368
      foreach ($all_users as $phid => $user) {
 
369
        $visible = PhabricatorPolicyFilter::hasCapability(
 
370
          $user,
 
371
          $object,
 
372
          PhabricatorPolicyCapability::CAN_VIEW);
 
373
        if ($visible) {
 
374
          $can_see[$phid] = true;
 
375
        }
 
376
      }
 
377
 
 
378
      foreach ($to as $phid => $user) {
 
379
        if (!empty($can_see[$phid])) {
 
380
          $to_result[$phid] = $all_users[$phid];
 
381
        }
 
382
      }
 
383
 
 
384
      foreach ($cc as $phid => $user) {
 
385
        if (!empty($can_see[$phid])) {
 
386
          $cc_result[$phid] = $all_users[$phid];
 
387
        }
 
388
      }
 
389
    }
 
390
 
 
391
    return array($to_result, $cc_result);
386
392
  }
387
393
 
388
394
}