PageRenderTime 27ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/system_remove/modules/avisota/AvisotaNewsletterTransport.php

https://github.com/avisota/contao-core
PHP | 597 lines | 410 code | 95 blank | 92 comment | 66 complexity | d2290bebd7ecec8f065fbdfc123234cb MD5 | raw file
  1. <?php if (defined('TL_ROOT')) {
  2. die('You can not access this file via contao!');
  3. }
  4. /**
  5. * Avisota newsletter and mailing system
  6. * Copyright © 2016 Sven Baumann
  7. *
  8. * PHP version 5
  9. *
  10. * @copyright way.vision 2015
  11. * @author Sven Baumann <baumann.sv@gmail.com>
  12. * @package avisota/contao-core
  13. * @license LGPL-3.0+
  14. * @filesource
  15. */
  16. // disable contao 2.10 token check
  17. define('BYPASS_TOKEN_CHECK', true);
  18. // run in FE mode
  19. define('TL_MODE', 'FE');
  20. // Define the static URL constants
  21. define('TL_FILES_URL', '');
  22. define('TL_SCRIPT_URL', '');
  23. define('TL_PLUGINS_URL', '');
  24. // initialize contao
  25. include('../../initialize.php');
  26. // disable error reporting
  27. #error_reporting(0);
  28. /**
  29. * Class AvisotaNewsletterTransport
  30. *
  31. * @copyright way.vision 2015
  32. * @author Sven Baumann <baumann.sv@gmail.com>
  33. * @package avisota/contao-core
  34. */
  35. class AvisotaNewsletterTransport extends Backend
  36. {
  37. protected $newsletter;
  38. protected $category;
  39. protected $attachments;
  40. public function __construct()
  41. {
  42. $this->import('BackendUser', 'User');
  43. parent::__construct();
  44. $this->import('AvisotaBase', 'Base');
  45. $this->import('AvisotaNewsletterContent', 'Content');
  46. $this->import('AvisotaStatic', 'Static');
  47. $this->import('Database');
  48. // force all URLs absolute
  49. $GLOBALS['TL_CONFIG']['forceAbsoluteDomainLink'] = true;
  50. // load default translations
  51. $this->loadLanguageFile('default');
  52. // HOTFIX Remove isotope frontend hook
  53. if (isset($GLOBALS['TL_HOOKS']['parseTemplate']) && is_array($GLOBALS['TL_HOOKS']['parseTemplate'])) {
  54. foreach ($GLOBALS['TL_HOOKS']['parseTemplate'] as $k => $v) {
  55. if ($v[0] == 'IsotopeFrontend') {
  56. unset($GLOBALS['TL_HOOKS']['parseTemplate'][$k]);
  57. }
  58. }
  59. }
  60. // HOTFIX Remove catalog frontend hook
  61. if (isset($GLOBALS['TL_HOOKS']['parseFrontendTemplate']) && is_array(
  62. $GLOBALS['TL_HOOKS']['parseFrontendTemplate']
  63. )
  64. ) {
  65. foreach ($GLOBALS['TL_HOOKS']['parseFrontendTemplate'] as $k => $v) {
  66. if ($v[0] == 'CatalogExt') {
  67. unset($GLOBALS['TL_HOOKS']['parseFrontendTemplate'][$k]);
  68. }
  69. }
  70. }
  71. }
  72. public function run()
  73. {
  74. // user have to be authenticated
  75. $this->User->authenticate();
  76. // load language files
  77. $this->loadLanguageFile('orm_avisota_message');
  78. // get the current action
  79. $action = $this->Input->post('action');
  80. // preview a newsletter
  81. if ($action == 'preview') {
  82. $this->preview();
  83. }
  84. // user have to be the right to transport from this point
  85. if (!$this->User->isAdmin && !$this->User->hasAccess('send', 'avisota_newsletter_permissions')) {
  86. $this->log('Not enough permissions to send avisota newsletter', 'AvisotaNewsletterTransport', TL_ERROR);
  87. $this->redirect('contao/main.php?act=error');
  88. }
  89. // schedule a newsletter
  90. if ($action == 'schedule') {
  91. $this->schedule();
  92. }
  93. // send a newsletter
  94. if ($action == 'send') {
  95. $this->send();
  96. }
  97. // no valid action to do
  98. $this->log('No action given.', 'AvisotaNewsletterTransport', TL_ERROR);
  99. $this->redirect('contao/main.php?act=error');
  100. }
  101. /**
  102. *
  103. */
  104. protected function findNewsletter($id)
  105. {
  106. $this->newsletter = \Database::getInstance()
  107. ->prepare("SELECT * FROM orm_avisota_message WHERE id=?")
  108. ->execute($id);
  109. if (!$this->newsletter->next()) {
  110. $this->log('Could not find newsletter ID ' . $id, 'AvisotaNewsletterTransport', TL_ERROR);
  111. $this->redirect('contao/main.php?act=error');
  112. }
  113. // find the newsletter category
  114. $this->category = \Database::getInstance()
  115. ->prepare("SELECT * FROM orm_avisota_message_category WHERE id=?")
  116. ->execute($this->newsletter->pid);
  117. if (!$this->category->next()) {
  118. $this->log(
  119. 'Could not find newsletter category ID ' . $this->newsletter->pid,
  120. 'AvisotaNewsletterTransport',
  121. TL_ERROR
  122. );
  123. $this->redirect('contao/main.php?act=error');
  124. }
  125. // set static data
  126. $this->Static->setNewsletter($this->newsletter);
  127. $this->Static->setCategory($this->category);
  128. // Overwrite the SMTP configuration
  129. if ($this->category->useSMTP) {
  130. $GLOBALS['TL_CONFIG']['useSMTP'] = true;
  131. $GLOBALS['TL_CONFIG']['smtpHost'] = $this->category->smtpHost;
  132. $GLOBALS['TL_CONFIG']['smtpUser'] = $this->category->smtpUser;
  133. $GLOBALS['TL_CONFIG']['smtpPass'] = $this->category->smtpPass;
  134. $GLOBALS['TL_CONFIG']['smtpEnc'] = $this->category->smtpEnc;
  135. $GLOBALS['TL_CONFIG']['smtpPort'] = $this->category->smtpPort;
  136. }
  137. // Add default sender address
  138. if (!strlen($this->category->sender)) {
  139. list($this->category->senderName, $this->category->sender) = $this->splitFriendlyName(
  140. $GLOBALS['TL_CONFIG']['adminEmail']
  141. );
  142. }
  143. $this->attachments = array();
  144. // Add attachments
  145. if ($this->newsletter->addFile) {
  146. $files = deserialize($this->newsletter->files);
  147. if (is_array($files) && count($files) > 0) {
  148. foreach ($files as $file) {
  149. if (is_file(TL_ROOT . '/' . $file)) {
  150. $this->attachments[] = $file;
  151. }
  152. }
  153. }
  154. }
  155. }
  156. /**
  157. * Send a newsletter preview.
  158. */
  159. protected function preview()
  160. {
  161. $this->findNewsletter($this->Input->post('id'));
  162. // get the email address
  163. $recipientEmail = false;
  164. if ($this->Input->post('recipient_email')) {
  165. if ($this->User->isAdmin || $this->User->hasAccess('send', 'avisota_newsletter_permissions')) {
  166. $recipientEmail = urldecode($this->Input->post('recipient_email', true));
  167. }
  168. }
  169. else {
  170. $user = \Database::getInstance()
  171. ->prepare("SELECT * FROM tl_user WHERE id=?")
  172. ->execute($this->Input->post('recipient_user'));
  173. if ($user->next()) {
  174. $recipient = $user->row();
  175. $recipientEmail = $user->email;
  176. }
  177. }
  178. // validate the email address
  179. if (!$recipientEmail || !$this->isValidEmailAddress($recipientEmail)) {
  180. $_SESSION['TL_PREVIEW_ERROR'] = true;
  181. return;
  182. }
  183. // read session data
  184. $session = $this->Session->get('AVISOTA_PREVIEW');
  185. // create the recipient object
  186. if (empty($recipient)) {
  187. $recipient = $this->Base->getPreviewRecipient($session['personalized']);
  188. $recipient['email'] = $recipientEmail;
  189. }
  190. $personalized = $this->Base->finalizeRecipientArray($recipient);
  191. // register static data
  192. $this->Static->set($this->category, $this->newsletter, $recipient);
  193. // create the contents
  194. $plain = $this->Content->generatePlain($this->newsletter, $this->category, $personalized);
  195. $html = $this->Content->generateHtml($this->newsletter, $this->category, $personalized);
  196. // prepare content for sending, e.a. replace specific insert tags
  197. $plain = $this->Content->prepareBeforeSending($plain);
  198. $html = $this->Content->prepareBeforeSending($html);
  199. // replace insert tags
  200. $plain = $this->replaceInsertTags($plain);
  201. $html = $this->replaceInsertTags($html);
  202. // Send
  203. $email = $this->generateEmailObject();
  204. $this->sendNewsletter($email, $plain, $html, $recipient, $personalized);
  205. // Redirect
  206. $_SESSION['TL_CONFIRM'][] = sprintf($GLOBALS['TL_LANG']['orm_avisota_message']['confirmPreview'], $recipientEmail);
  207. $this->redirect(
  208. 'contao/main.php?do=avisota_newsletter&table=orm_avisota_message&key=send&id=' . $this->newsletter->id
  209. );
  210. }
  211. /**
  212. * Put a newsletter into the outbox.
  213. */
  214. protected function schedule()
  215. {
  216. $this->findNewsletter($this->Input->post('id'));
  217. $time = time();
  218. $outboxId = \Database::getInstance()
  219. ->prepare("INSERT INTO orm_avisota_message_outbox %s")
  220. ->set(array('pid' => $this->newsletter->id, 'tstamp' => $time))
  221. ->execute()
  222. ->insertId;
  223. // Insert list of recipients into outbox
  224. $recipients = unserialize($this->newsletter->recipients);
  225. foreach ($recipients as $recipientEmail) {
  226. if (preg_match('#^(list|mgroup)\-(\d+)$#', $recipientEmail, $matches)) {
  227. switch ($matches[1]) {
  228. case 'list':
  229. $idTemp = $matches[2];
  230. \Database::getInstance()
  231. ->prepare(
  232. "
  233. INSERT INTO
  234. orm_avisota_message_outbox_recipient
  235. (pid, tstamp, email, domain, recipientID, source, sourceID)
  236. SELECT
  237. ?,
  238. ?,
  239. r.email,
  240. SUBSTRING(r.email, LOCATE('@', r.email)+1),
  241. r.id,
  242. 'list',
  243. r.pid
  244. FROM
  245. orm_avisota_recipient r
  246. WHERE
  247. r.email NOT IN (SELECT email FROM orm_avisota_message_outbox_recipient WHERE pid=?)
  248. AND r.pid=?
  249. AND r.confirmed='1'"
  250. )
  251. ->execute($outboxId, $time, $outboxId, $idTemp);
  252. break;
  253. case 'mgroup':
  254. $idTemp = $matches[2];
  255. \Database::getInstance()
  256. ->prepare(
  257. "
  258. INSERT INTO
  259. orm_avisota_message_outbox_recipient
  260. (pid, tstamp, email, domain, recipientID, source, sourceID)
  261. SELECT
  262. ?,
  263. ?,
  264. m.email,
  265. SUBSTRING(m.email, LOCATE('@', m.email)+1),
  266. m.id,
  267. 'mgroup',
  268. g.group_id
  269. FROM
  270. tl_member m
  271. LEFT JOIN
  272. tl_member_to_group g
  273. ON
  274. m.id=g.member_id
  275. WHERE
  276. m.email NOT IN (SELECT email FROM orm_avisota_message_outbox_recipient WHERE pid=?)
  277. AND g.group_id=?
  278. AND m.disable=''
  279. AND m.email!=''"
  280. )
  281. ->execute($outboxId, $time, $outboxId, $idTemp);
  282. break;
  283. }
  284. }
  285. }
  286. $this->redirect('contao/main.php?do=avisota_outbox&id=' . $outboxId);
  287. }
  288. /**
  289. * Send a newsletter from the outbox.
  290. */
  291. protected function send()
  292. {
  293. $outbox = \Database::getInstance()
  294. ->prepare("SELECT * FROM orm_avisota_message_outbox WHERE id=?")
  295. ->execute($this->Input->post('id'));
  296. if (!$outbox->next()) {
  297. $this->log('Could not find outbox ID ' . $this->Input->get('id'), 'AvisotaNewsletterTransport', TL_ERROR);
  298. $this->redirect('contao/main.php?act=error');
  299. }
  300. $this->findNewsletter($outbox->pid);
  301. ob_start();
  302. // set timeout and count
  303. $timeout = $GLOBALS['TL_CONFIG']['avisota_max_send_timeout'];
  304. $count = $GLOBALS['TL_CONFIG']['avisota_max_send_count'];
  305. // set counters
  306. $successes = array();
  307. $fails = array();
  308. $startTime = time();
  309. // get recipients
  310. $recipient = \Database::getInstance()
  311. ->prepare(
  312. "SELECT
  313. *
  314. FROM
  315. orm_avisota_message_outbox_recipient
  316. WHERE
  317. pid=?
  318. AND send=0
  319. GROUP BY
  320. domain"
  321. )
  322. ->limit($count)
  323. ->execute($outbox->id);
  324. // Send newsletter
  325. if ($recipient->numRows > 0) {
  326. if (!$this->newsletter->sendOn) {
  327. \Database::getInstance()
  328. ->prepare(
  329. "
  330. UPDATE
  331. orm_avisota_message
  332. SET
  333. sendOn=?
  334. WHERE
  335. id=?"
  336. )
  337. ->execute(time(), $this->newsletter->id);
  338. }
  339. $endExecutionTime = $_SERVER['REQUEST_TIME'] + $GLOBALS['TL_CONFIG']['avisota_max_send_time'];
  340. while ($endExecutionTime > time() && $recipient->next()) {
  341. $recipientData = $recipient->row();
  342. // add recipient details
  343. if ($recipient->source == 'list') {
  344. $data = \Database::getInstance()
  345. ->prepare("SELECT * FROM orm_avisota_recipient WHERE id=?")
  346. ->execute($recipient->recipientID);
  347. if ($data->next()) {
  348. $this->Base->extendArray($data->row(), $recipientData);
  349. }
  350. }
  351. // add member details
  352. if ($recipient->source == 'mgroup') {
  353. $data = \Database::getInstance()
  354. ->prepare("SELECT * FROM tl_member WHERE id=?")
  355. ->execute($recipient->recipientID);
  356. if ($data->next()) {
  357. $this->Base->extendArray($data->row(), $recipientData);
  358. }
  359. }
  360. // merge member details
  361. else if ($GLOBALS['TL_CONFIG']['avisota_merge_member_details']) {
  362. $data = \Database::getInstance()
  363. ->prepare("SELECT * FROM tl_member WHERE email=?")
  364. ->execute($recipient->email);
  365. if ($data->next()) {
  366. $this->Base->extendArray($data->row(), $recipientData);
  367. }
  368. }
  369. $personalized = $this->Base->finalizeRecipientArray($recipientData);
  370. $this->Static->set($this->category, $this->newsletter, $recipientData);
  371. // create the contents
  372. $plain = $this->Content->generatePlain($this->newsletter, $this->category, $personalized);
  373. $html = $this->Content->generateHtml($this->newsletter, $this->category, $personalized);
  374. // prepare content for sending, e.a. replace specific insert tags
  375. $plain = $this->Content->prepareBeforeSending($plain);
  376. $html = $this->Content->prepareBeforeSending($html);
  377. // replace insert tags
  378. $plain = $this->replaceInsertTags($plain);
  379. $html = $this->replaceInsertTags($html);
  380. // Send
  381. $email = $this->generateEmailObject();
  382. if ($this->sendNewsletter(
  383. $email,
  384. $plain,
  385. $html,
  386. $recipientData,
  387. $personalized
  388. )
  389. ) {
  390. $successes[] = $recipient->row();
  391. }
  392. else {
  393. $fails[] = $recipient->row();
  394. \Database::getInstance()
  395. ->prepare("UPDATE orm_avisota_message_outbox_recipient SET failed='1' WHERE id=?")
  396. ->execute($recipient->id);
  397. // disable recipient from list
  398. if ($recipient->source == 'list') {
  399. if (!$GLOBALS['TL_CONFIG']['avisota_dont_disable_recipient_on_failure']) {
  400. \Database::getInstance()
  401. ->prepare("UPDATE orm_avisota_recipient SET confirmed='' WHERE id=?")
  402. ->execute($recipient->recipientID);
  403. $this->log(
  404. 'Recipient address "' . $recipient->email . '" was rejected and has been deactivated',
  405. 'AvisotaNewsletterTransport',
  406. TL_ERROR
  407. );
  408. }
  409. }
  410. // disable member
  411. else if ($recipient->source == 'mgroup') {
  412. if (!$GLOBALS['TL_CONFIG']['avisota_dont_disable_member_on_failure']) {
  413. \Database::getInstance()
  414. ->prepare("UPDATE tl_member SET disable='1' WHERE id=?")
  415. ->execute($recipient->recipientID);
  416. $this->log(
  417. 'Member address "' . $recipient->email . '" was rejected and has been disabled',
  418. 'AvisotaNewsletterTransport',
  419. TL_ERROR
  420. );
  421. }
  422. }
  423. }
  424. \Database::getInstance()
  425. ->prepare("UPDATE orm_avisota_message_outbox_recipient SET send=? WHERE id=?")
  426. ->execute(time(), $recipient->id);
  427. }
  428. }
  429. $error = '';
  430. do {
  431. $buffer = ob_get_contents();
  432. if ($buffer) {
  433. $error .= $buffer . "\n";
  434. }
  435. } while (ob_end_clean());
  436. header('Content-Type: application/json');
  437. echo json_encode(
  438. array(
  439. 'success' => $successes,
  440. 'failed' => $fails,
  441. 'time' => time() - $startTime,
  442. 'error' => $error
  443. )
  444. );
  445. exit;
  446. }
  447. /**
  448. * Generate the e-mail object and return it
  449. *
  450. * @return object
  451. */
  452. protected function generateEmailObject()
  453. {
  454. $email = new BasicEmail();
  455. $email->from = $this->category->sender;
  456. $email->subject = $this->newsletter->subject;
  457. // Add sender name
  458. if (strlen($this->category->senderName)) {
  459. $email->fromName = $this->category->senderName;
  460. }
  461. $email->logFile = 'newsletter_' . $this->newsletter->id . '.log';
  462. // Attachments
  463. if (is_array($this->attachments) && count($this->attachments) > 0) {
  464. foreach ($this->attachments as $attachmentPathname) {
  465. $email->attachFile(TL_ROOT . '/' . $attachmentPathname);
  466. }
  467. }
  468. return $email;
  469. }
  470. /**
  471. * Send a newsletter.
  472. */
  473. protected function sendNewsletter(Email $email, $plain, $html, $recipientData, $personalized)
  474. {
  475. // set text content
  476. $email->text = $plain;
  477. // Prepare html content
  478. $email->html = $html;
  479. $email->imageDir = TL_ROOT . '/';
  480. $failed = false;
  481. // Deactivate invalid addresses
  482. try {
  483. if ($GLOBALS['TL_CONFIG']['avisota_developer_mode']) {
  484. $email->sendTo($GLOBALS['TL_CONFIG']['avisota_developer_email']);
  485. }
  486. else {
  487. $email->sendTo($recipientData['email']);
  488. }
  489. }
  490. catch (Swift_RfcComplianceException $e) {
  491. $failed = true;
  492. }
  493. // Rejected recipients
  494. if (count($email->failures)) {
  495. $failed = true;
  496. }
  497. $this->Static->resetRecipient();
  498. return !$failed;
  499. }
  500. }
  501. $avisotaNewsletterTransport = new AvisotaNewsletterTransport();
  502. $avisotaNewsletterTransport->run();