PageRenderTime 59ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/nette_example/libs/nSMTPMailer/nSMTPMailer/Commands/RcptCommand.php

https://github.com/peci1/nSMTPMailer
PHP | 285 lines | 106 code | 37 blank | 142 comment | 11 complexity | 5aceb2733d6244cb7f285e2db4dee7b1 MD5 | raw file
  1. <?php
  2. namespace Nette\Mail\SMTP;
  3. use \Nette\InvalidStateException;
  4. use \Nette\IOException;
  5. /**
  6. * The command that adds a recipient to the mail message
  7. *
  8. * RFC DESCRIPTION:
  9. *
  10. * This command is used to identify an individual recipient of the mail
  11. * data; multiple recipients are specified by multiple use of this
  12. * command. The argument field contains a forward-path and may contain
  13. * optional parameters.
  14. *
  15. * The forward-path normally consists of the required destination
  16. * mailbox. Sending systems SHOULD not generate the optional list of
  17. * hosts known as a source route.
  18. *
  19. * 250 Requested mail action okay, completed
  20. * 251 User not local; will forward to <forward-path>
  21. *
  22. * 450 Requested mail action not taken: mailbox unavailable
  23. * (e.g., mailbox busy)
  24. * 451 Requested action aborted: local error in processing
  25. * 452 Requested action not taken: insufficient system storage
  26. *
  27. * 550 Requested action not taken: mailbox unavailable
  28. * (e.g., mailbox not found, no access, or command rejected
  29. * for policy reasons)
  30. * 551 User not local; please try <forward-path>
  31. * 552 Requested mail action aborted: exceeded storage allocation
  32. * 553 Requested action not taken: mailbox name not allowed
  33. * (e.g., mailbox syntax incorrect)
  34. * Copyright (c) 2013, Martin Pecka (peci1@seznam.cz)
  35. * All rights reserved.
  36. *
  37. * Redistribution and use in source and binary forms, with or without
  38. * modification, are permitted provided that the following conditions are met:
  39. * * Redistributions of source code must retain the above copyright
  40. * notice, this list of conditions and the following disclaimer.
  41. * * Redistributions in binary form must reproduce the above copyright
  42. * notice, this list of conditions and the following disclaimer in the
  43. * documentation and/or other materials provided with the distribution.
  44. * * Neither the name Martin Pecka nor the
  45. * names of contributors may be used to endorse or promote products
  46. * derived from this software without specific prior written permission.
  47. *
  48. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  49. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  50. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  51. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  52. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  53. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  54. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  55. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  56. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  57. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  58. * @package nSMTPMailer
  59. * @version 2.0.0
  60. * @copyright (c) 2013 Martin Pecka
  61. * @author Martin Pecka <peci1@seznam.cz>
  62. * @license See license.txt
  63. */
  64. class RcptCommand extends BaseCommand
  65. {
  66. /** @var array of string|int Array of response code masks of codes that
  67. * should force the application to immediately close the connection
  68. * and quit
  69. * */
  70. protected $failResponses =
  71. array('450', '451', '452', '550', '551', '552', '553');
  72. protected $successResponses = array('250', '251');
  73. /** @var string Textual name of the command */
  74. protected $name = 'RCPT TO';
  75. /** @var string The command to send - if it is NULL, no command is sent
  76. * and the communicator just reads the server response */
  77. protected $command = 'RCPT TO: <%s>';
  78. /** @var array of array The recipients' email addresses
  79. * Structure of the array:
  80. * 'email' => The email address
  81. * 'retries' => The remaining # of retries to send
  82. * 'original_email' => The original email, if this is a forwarded
  83. * one */
  84. protected $recipients = array();
  85. /**
  86. * Execute the command, handle error states and throw InvalidStateException
  87. * if an unrecoverable error occurs. Should be overridden, but always call
  88. * parent::execute() !!!
  89. *
  90. * @return array of key=>val:
  91. * undelivered => Array of addresses the mail couldn't be delivered to
  92. * toDeliver => Array of internal format of the addresses not yet
  93. * tried to send the email to. If set, causes the client to repeat
  94. * the message sending procedure with these recipients (possible
  95. * infinte loop - be sure this array always gets smaller than
  96. * before)
  97. * recipientsCount => Number of the recipients the message will really
  98. * be sent to
  99. *
  100. * @throws InvalidStateException If an unrecoverable error occurs
  101. */
  102. public function execute()
  103. {
  104. $undeliveredRecipients = array();
  105. $recipientsCount = 0;
  106. while (count($this->recipients) >= 1) {
  107. //send the command and read the response
  108. parent::execute();
  109. $recipient = array_pop($this->recipients);
  110. switch ($this->response->getCode()) {
  111. case '250':
  112. case '251':
  113. if (isset($recipient['original_email'])) {
  114. //we finally delivered mail to a user that provided a
  115. //forward-address
  116. $key = array_search($recipient['original_email'],
  117. $undeliveredRecipients);
  118. if ($key !== FALSE)
  119. unset($undeliveredRecipients[$key]);
  120. }
  121. $recipientsCount++;
  122. break;
  123. case '450':
  124. case '451':
  125. //add the recipient to the begginning of the queue to try again
  126. //later
  127. if ($recipient['retries'] > 0) {
  128. $recipient['retries']--;
  129. array_unshift($this->recipients, $recipient);
  130. }
  131. break;
  132. /* Handle too much recipients by splitting the message to chunks
  133. *
  134. * RFC 821 [30] incorrectly listed the error where an SMTP server
  135. * exhausts its implementation limit on the number of RCPT commands
  136. * ("too many recipients") as having reply code 552. The correct
  137. * reply code for this condition is 452. Clients SHOULD treat a
  138. * 552 code in this case as a temporary, rather than permanent,
  139. * failure so the logic below works.
  140. */
  141. case '452':
  142. case '552':
  143. array_push($this->recipients, $recipient);
  144. $result['undelivered'] = $undeliveredRecipients;
  145. //this causes the client to append a new mail sending sequence
  146. //for the rest of recipients
  147. $result['toDeliver'] = $this->recipients;
  148. $result['recipientsCount'] = $recipientsCount;
  149. return $result;
  150. break; //unreachable
  151. case '550':
  152. case '553':
  153. $undeliveredRecipients[] = $recipient['email'];
  154. break;
  155. case '551':
  156. if (preg_match('/<\([^>]*\)>/', $this->response, $matches)) {
  157. $forward = array();
  158. $forward['email'] = $matches[1];
  159. $forward['retries'] = 2;
  160. if (isset($recipient['original_email'])) {
  161. $forward['original_email'] =
  162. $recipient['original_email'];
  163. } else {
  164. $forward['original_email'] = $recipient['email'];
  165. $undeliveredRecipients[] = $recipient['email'];
  166. }
  167. $this->recipients[] = $forward;
  168. }
  169. break;
  170. }
  171. }
  172. return array(
  173. 'undelivered' => $undeliveredRecipients,
  174. 'recipientsCount' => $recipientsCount,
  175. );
  176. }
  177. /**
  178. * Add a recipient's address
  179. *
  180. * @param string $recipient The recipient's address
  181. * @return void
  182. *
  183. * @throws InvalidArgumentException If the email address is invalid
  184. */
  185. public function addRecipient($recipient)
  186. {
  187. if (!$this->validateEmail($recipient))
  188. throw new InvalidArgumentException(
  189. 'The email address provided to RCPT TO was invalid.' .
  190. 'The address was: ' . $recipient);
  191. $this->recipients[] = array('email' => $recipient, 'retries' => 2);
  192. }
  193. /**
  194. * Add some recipients' addresses
  195. *
  196. * @param array of string $recipients The recipients' addresses
  197. *
  198. * @return array of string The recipients that have invalid address format
  199. */
  200. public function addRecipients(array $recipients)
  201. {
  202. $bad = array();
  203. if (!empty($recipients)) {
  204. foreach ($recipients as $recipient) {
  205. try {
  206. $this->addRecipient($recipient);
  207. } catch (InvalidArgumentException $e) {
  208. $bad[] = $recipient;
  209. }
  210. }
  211. }
  212. return $bad;
  213. }
  214. /**
  215. * Set the internal recipients array to the one given
  216. *
  217. * @remarks Used only for sending message chunks, do not use for
  218. * another purposes. The array must have the internal
  219. * structure.
  220. *
  221. * @param array of array $recipients
  222. * @return void
  223. */
  224. public function setRecipients(array $recipients)
  225. {
  226. $this->recipients = $recipients;
  227. }
  228. /**
  229. * Build the command string and return it (can be overriden)
  230. *
  231. * @return string The command to send to the server
  232. *
  233. * @throws InvalidStateException If no recipients are set
  234. */
  235. protected function buildCommand()
  236. {
  237. if (count($this->recipients) < 1)
  238. throw new InvalidStateException(
  239. 'Tried to send mail, but no recipients were set!');
  240. return sprintf($this->command,
  241. $this->recipients[count($this->recipients) - 1]['email']);
  242. }
  243. }
  244. /* ?> omitted intentionally */