PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/bin/CiviMailProcessor.php

https://github.com/ksecor/civicrm
PHP | 218 lines | 125 code | 30 blank | 63 comment | 21 complexity | c5626aa464fd2a832542d45d9d4de22c MD5 | raw file
  1. <?php
  2. /*
  3. +--------------------------------------------------------------------+
  4. | CiviCRM version 3.1 |
  5. +--------------------------------------------------------------------+
  6. | Copyright CiviCRM LLC (c) 2004-2009 |
  7. +--------------------------------------------------------------------+
  8. | This file is a part of CiviCRM. |
  9. | |
  10. | CiviCRM is free software; you can copy, modify, and distribute it |
  11. | under the terms of the GNU Affero General Public License |
  12. | Version 3, 19 November 2007. |
  13. | |
  14. | CiviCRM is distributed in the hope that it will be useful, but |
  15. | WITHOUT ANY WARRANTY; without even the implied warranty of |
  16. | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
  17. | See the GNU Affero General Public License for more details. |
  18. | |
  19. | You should have received a copy of the GNU Affero General Public |
  20. | License along with this program; if not, contact CiviCRM LLC |
  21. | at info[AT]civicrm[DOT]org. If you have questions about the |
  22. | GNU Affero General Public License or the licensing of CiviCRM, |
  23. | see the CiviCRM license FAQ at http://civicrm.org/licensing |
  24. +--------------------------------------------------------------------+
  25. */
  26. /**
  27. *
  28. * @package CRM
  29. * @copyright CiviCRM LLC (c) 2004-2009
  30. * $Id$
  31. *
  32. */
  33. class CiviMailProcessor {
  34. /**
  35. * Delete old files from a given directory (recursively)
  36. *
  37. * @param string $dir directory to cleanup
  38. * @param int $age files older than this many seconds will be deleted (default: 60 days)
  39. * @return void
  40. */
  41. static function cleanupDir($dir, $age = 5184000)
  42. {
  43. // return early if we can’t read/write the dir
  44. if (!is_writable($dir) or !is_readable($dir) or !is_dir($dir)) return;
  45. foreach (scandir($dir) as $file) {
  46. // don’t go up the directory stack and skip new files/dirs
  47. if ($file == '.' or $file == '..') continue;
  48. if (filemtime("$dir/$file") > time() - $age) continue;
  49. // it’s an old file/dir, so delete/recurse
  50. is_dir("$dir/$file") ? self::cleanupDir("$dir/$file", $age) : unlink("$dir/$file");
  51. }
  52. }
  53. /**
  54. * Process the mailbox defined by the named set of settings from civicrm_mail_settings
  55. *
  56. * @param string $name name of the set of settings from civicrm_mail_settings (null for default set)
  57. * @return void
  58. */
  59. static function process($name = null) {
  60. require_once 'CRM/Core/DAO/MailSettings.php';
  61. $dao = new CRM_Core_DAO_MailSettings;
  62. $dao->domain_id = CRM_Core_Config::domainID( );
  63. $name ? $dao->name = $name : $dao->is_default = 1;
  64. if ( ! $dao->find(true) ) {
  65. throw new Exception("Could not find entry named $name in civicrm_mail_settings");
  66. }
  67. $config =& CRM_Core_Config::singleton();
  68. $verpSeperator = preg_quote( $config->verpSeparator );
  69. $twoDigitStringMin = $verpSeperator . '(\d+)' . $verpSeperator . '(\d+)';
  70. $twoDigitString = $twoDigitStringMin . $verpSeperator;
  71. $threeDigitString = $twoDigitString . '(\d+)' . $verpSeperator;
  72. // FIXME: legacy regexen to handle CiviCRM 2.1 address patterns, with domain id and possible VERP part
  73. $commonRegex = '/^' . preg_quote($dao->localpart) . '(b|bounce|c|confirm|o|optOut|r|reply|re|e|resubscribe|u|unsubscribe)' . $threeDigitString . '([0-9a-f]{16})(-.*)?@' . preg_quote($dao->domain) . '$/';
  74. $subscrRegex = '/^' . preg_quote($dao->localpart) . '(s|subscribe)' . $twoDigitStringMin . '@' . preg_quote($dao->domain) . '$/';
  75. // a common-for-all-actions regex to handle CiviCRM 2.2 address patterns
  76. $regex = '/^' . preg_quote($dao->localpart) . '(b|c|e|o|r|u)' . $twoDigitString . '([0-9a-f]{16})@' . preg_quote($dao->domain) . '$/';
  77. // retrieve the emails
  78. require_once 'CRM/Mailing/MailStore.php';
  79. $store = CRM_Mailing_MailStore::getStore($name);
  80. require_once 'api/Mailer.php';
  81. // process fifty at a time, CRM-4002
  82. while ($mails = $store->fetchNext(50)) {
  83. foreach ($mails as $key => $mail) {
  84. // for every addressee: match address elements if it's to CiviMail
  85. $matches = array();
  86. foreach ($mail->to as $address) {
  87. if (preg_match($regex, $address->email, $matches)) {
  88. list($match, $action, $job, $queue, $hash) = $matches;
  89. break;
  90. // FIXME: the below elseifs should be dropped when we drop legacy support
  91. } elseif (preg_match($commonRegex, $address->email, $matches)) {
  92. list($match, $action, $_, $job, $queue, $hash) = $matches;
  93. break;
  94. } elseif (preg_match($subscrRegex, $address->email, $matches)) {
  95. list($match, $action, $_, $job) = $matches;
  96. break;
  97. }
  98. }
  99. // if $matches is empty, this email is not CiviMail-bound
  100. if (!$matches) {
  101. $store->markIgnored($key);
  102. continue;
  103. }
  104. // get $replyTo from either the Reply-To header or from From
  105. // FIXME: make sure it works with Reply-Tos containing non-email stuff
  106. $replyTo = $mail->getHeader('Reply-To') ? $mail->getHeader('Reply-To') : $mail->from->email;
  107. // handle the action by passing it to the proper API call
  108. // FIXME: leave only one-letter cases when dropping legacy support
  109. switch ($action) {
  110. case 'b':
  111. case 'bounce':
  112. $text = '';
  113. if ($mail->body instanceof ezcMailText) {
  114. $text = $mail->body->text;
  115. } elseif ($mail->body instanceof ezcMailMultipart) {
  116. if ($mail->body instanceof ezcMailMultipartRelated) {
  117. foreach ($mail->body->getRelatedParts() as $part) {
  118. if (isset($part->subType) and $part->subType == 'plain') {
  119. $text = $part->text;
  120. break;
  121. }
  122. }
  123. } else {
  124. foreach ($mail->body->getParts() as $part) {
  125. if (isset($part->subType) and $part->subType == 'plain') {
  126. $text = $part->text;
  127. break;
  128. }
  129. }
  130. }
  131. }
  132. crm_mailer_event_bounce($job, $queue, $hash, $text);
  133. break;
  134. case 'c':
  135. case 'confirm':
  136. crm_mailer_event_confirm($job, $queue, $hash);
  137. break;
  138. case 'o':
  139. case 'optOut':
  140. crm_mailer_event_domain_unsubscribe($job, $queue, $hash);
  141. break;
  142. case 'r':
  143. case 'reply':
  144. // instead of text and HTML parts (4th and 6th params) send the whole email as the last param
  145. crm_mailer_event_reply($job, $queue, $hash, null, $replyTo, null, $mail->generate());
  146. break;
  147. case 'e':
  148. case 're':
  149. case 'resubscribe':
  150. crm_mailer_event_resubscribe($job, $queue, $hash);
  151. break;
  152. case 's':
  153. case 'subscribe':
  154. crm_mailer_event_subscribe($mail->from->email, $job);
  155. break;
  156. case 'u':
  157. case 'unsubscribe':
  158. crm_mailer_event_unsubscribe($job, $queue, $hash);
  159. break;
  160. }
  161. $store->markProcessed($key);
  162. }
  163. }
  164. }
  165. }
  166. // bootstrap the environment and run the processor
  167. session_start();
  168. require_once '../civicrm.config.php';
  169. require_once 'CRM/Core/Config.php';
  170. $config =& CRM_Core_Config::singleton();
  171. CRM_Utils_System::authenticateScript(true);
  172. require_once 'CRM/Core/Lock.php';
  173. $lock = new CRM_Core_Lock('CiviMailProcessor');
  174. if ($lock->isAcquired()) {
  175. // try to unset any time limits
  176. if (!ini_get('safe_mode')) set_time_limit(0);
  177. // cleanup directories with old mail files (if they exist): CRM-4452
  178. CiviMailProcessor::cleanupDir($config->customFileUploadDir . DIRECTORY_SEPARATOR . 'CiviMail.ignored');
  179. CiviMailProcessor::cleanupDir($config->customFileUploadDir . DIRECTORY_SEPARATOR . 'CiviMail.processed');
  180. // if there are named sets of settings, use them - otherwise use the default (null)
  181. $names = isset($_REQUEST['names']) && is_array($_REQUEST['names']) ? $_REQUEST['names'] : array( null );
  182. foreach ($names as $name) {
  183. CiviMailProcessor::process($name);
  184. }
  185. } else {
  186. throw new Exception('Could not acquire lock, another CiviMailProcessor process is running');
  187. }
  188. $lock->release();