public function testCreateAllSpool() { $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/tt_content.xml'); $db = $this->getDatabaseConnection(); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_newsletter', 'begin_time != 0 AND end_time != 0'); $this->assertSame(0, $count); Tools::createAllSpool(); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_newsletter', 'begin_time != 0 AND end_time != 0'); $this->assertSame(1, $count, 'newsletter should be marked as spooled'); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_email', 'newsletter = 20 AND begin_time = 0'); $this->assertSame(2, $count, 'two emails must have been created but not sent yet'); $lastInsertedEmail = $db->exec_SELECTgetSingleRow('*', 'tx_newsletter_domain_model_email', 'newsletter = 20 AND begin_time = 0'); $this->assertNotSame(md5('0' . $lastInsertedEmail['recipient_address']), $lastInsertedEmail['auth_code'], 'the UID used in authCode must never be 0'); $this->assertSame(md5($lastInsertedEmail['uid'] . $lastInsertedEmail['recipient_address']), $lastInsertedEmail['auth_code'], 'the UID used in authCode should be the real value'); // Prepare a mock to always validate content /** @var \Ecodev\Newsletter\Utility\Validator|\PHPUnit_Framework_MockObject_MockObject $mockValidator */ $mockValidator = $this->getMock(\Ecodev\Newsletter\Utility\Validator::class, ['validate'], [], '', false); $mockValidator->method('validate')->will($this->returnValue(['content' => 'some very interesting content <a href="http://example.com/fake-content">link</a>', 'errors' => [], 'warnings' => [], 'infos' => []])); // Force email to NOT be sent global $TYPO3_CONF_VARS; $TYPO3_CONF_VARS['MAIL']['transport'] = 'Swift_NullTransport'; /** @var \Ecodev\Newsletter\Domain\Repository\NewsletterRepository $newsletterRepository */ $newsletterRepository = $this->objectManager->get(\Ecodev\Newsletter\Domain\Repository\NewsletterRepository::class); $newsletter = $newsletterRepository->findByUid(20); $newsletter->setValidator($mockValidator); Tools::runSpool($newsletter); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_email', 'newsletter = 20 AND begin_time != 0 AND end_time != 0 AND recipient_data != ""'); $this->assertSame(2, $count, 'should have sent two emails'); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_link', 'newsletter = 20'); $this->assertSame(1, $count, 'should have on1 new link'); }
public function testCreateAllSpool() { $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/tt_content.xml'); $db = $this->getDatabaseConnection(); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_newsletter', 'begin_time != 0 AND end_time != 0'); $this->assertEquals(0, $count); \Ecodev\Newsletter\Tools::createAllSpool(); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_newsletter', 'begin_time != 0 AND end_time != 0'); $this->assertEquals(1, $count, 'newsletter should be marked as spooled'); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_email', 'newsletter = 20 AND begin_time = 0'); $this->assertEquals(2, $count, 'two emails must have been created but not sent yet'); // Prepare a mock to always validate content $mockValidator = $this->getMock('Ecodev\\Newsletter\\Utility\\Validator', array('validate'), array(), '', false); $mockValidator->method('validate')->will($this->returnValue(array('content' => 'some very interesting content <a href="http://example.com/fake-content">link</a>', 'errors' => array(), 'warnings' => array(), 'infos' => array()))); // Force email to NOT be sent global $TYPO3_CONF_VARS; $TYPO3_CONF_VARS['MAIL']['transport'] = 'Swift_NullTransport'; $newsletterRepository = $this->objectManager->get('Ecodev\\Newsletter\\Domain\\Repository\\NewsletterRepository'); $newsletter = $newsletterRepository->findByUid(20); $newsletter->setValidator($mockValidator); \Ecodev\Newsletter\Tools::runSpool($newsletter); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_email', 'newsletter = 20 AND begin_time != 0 AND end_time != 0 AND recipient_data != ""'); $this->assertEquals(2, $count, 'should have sent two emails'); $count = $db->exec_SELECTcountRows('*', 'tx_newsletter_domain_model_link', 'newsletter = 20'); $this->assertEquals(1, $count, 'should have on1 new link'); }
/** * Creates the remote api based on the module/plugin configuration using the extbase * reflection features. * * @param string $routeUrl * @param string $namespace * @return array */ public function createApi($routeUrl, $namespace) { $api = []; $api['url'] = $routeUrl; $api['type'] = 'remoting'; $api['namespace'] = $namespace; $api['actions'] = []; if (empty($this->frameworkConfiguration['controllerConfiguration'])) { # @todo improve me! Hack for fetching API of newsletter the hard way! # It looks $this->frameworkConfiguration['controllerConfiguration'] is empty as of TYPO3 6.1. Bug or feature? $this->frameworkConfiguration['controllerConfiguration'] = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions']['Newsletter']['modules']['web_NewsletterTxNewsletterM1']['controllers']; } foreach ($this->frameworkConfiguration['controllerConfiguration'] as $controllerName => $allowedControllerActions) { $unstrippedControllerName = $controllerName . 'Controller'; $controllerObjectName = 'Ecodev\\Newsletter\\Controller\\' . $unstrippedControllerName; $controllerActions = []; foreach ($allowedControllerActions['actions'] as $actionName) { $unstrippedActionName = $actionName . 'Action'; try { $actionParameters = $this->reflectionService->getMethodParameters($controllerObjectName, $unstrippedActionName); $controllerActions[] = ['len' => count($actionParameters), 'name' => $unstrippedActionName]; } catch (ReflectionException $re) { if ($unstrippedActionName !== 'extObjAction') { \Ecodev\Newsletter\Tools::getLogger(__CLASS__)->critical('You have a not existing action (' . $controllerObjectName . '::' . $unstrippedActionName . ') in your module/plugin configuration. This action will not be available for Ext.Direct remote execution.'); } } } $api['actions'][$unstrippedControllerName] = $controllerActions; } return $api; }
public function testEncryption() { $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] = 'encryptionKeyValue'; $encrypted = Tools::encrypt('my value'); $this->assertNotSame('my value', $encrypted, 'must be encrypted'); $decrypted = Tools::decrypt($encrypted); $this->assertSame('my value', $decrypted, 'must be original value'); }
/** * @test */ public function getUrlReturnsInitialValueForString() { if (!$this->canRunLynx()) { $this->markTestSkipped('The command "' . Tools::confParam('path_to_lynx') . '" is not available.'); } $html = file_get_contents(__DIR__ . '/input.html'); $expected = file_get_contents(__DIR__ . '/lynx.txt'); $actual = $this->subject->getPlainText($html, 'http://my-domain.com'); $this->assertSame($expected, $actual); }
public function getPlainText($content, $baseUrl) { $tmpFile = tempnam(sys_get_temp_dir(), 'newsletter_'); $contentWithBase = $this->injectBaseUrl($content, $baseUrl); file_put_contents($tmpFile, $contentWithBase); $cmd = escapeshellcmd(Tools::confParam('path_to_lynx')) . ' -force_html -dump ' . escapeshellarg($tmpFile); exec($cmd, $output); unlink($tmpFile); $plainText = implode("\n", $output); return $plainText; }
/** * index action for the module controller * This will render the HTML needed for ExtJS application * * @return void */ public function indexAction() { $pageType = ''; $record = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('doktype', 'pages', 'uid =' . $this->pageId); if (!empty($record['doktype']) && $record['doktype'] == 254) { $pageType = 'folder'; } elseif (!empty($record['doktype'])) { $pageType = 'page'; } $configuration = array('pageId' => $this->pageId, 'pageType' => $pageType, 'emailShowUrl' => Tools::buildFrontendUri('show', array(), 'Email')); $this->view->assign('configuration', $configuration); }
/** * Returns the decrypted field value if set. * @param array $PA Parameter Array * @return string */ public static function getDecryptedFieldValue($field, $value) { $default = @$GLOBALS['TCA']['tx_newsletter_domain_model_bounceaccount']['columns'][$field]['config']['default']; // Set the value if (empty($value)) { if ($default) { $value = $default; } } elseif ($value != $default) { $value = \Ecodev\Newsletter\Tools::decrypt($value); } return $value; }
/** * This method is designed to return some additional information about the task, * that may help to set it apart from other tasks from the same class * This additional information is used - for example - in the Scheduler's BE module * This method should be implemented in most task classes * * @return string Information to display */ public function getAdditionalInformation() { $objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class); $newsletterRepository = $objectManager->get(\Ecodev\Newsletter\Domain\Repository\NewsletterRepository::class); $newslettersToSend = $newsletterRepository->findAllReadyToSend(); $newslettersBeingSent = $newsletterRepository->findAllBeingSent(); $newslettersToSendCount = count($newslettersToSend); $newslettersBeingSentCount = count($newslettersBeingSent); $emailNotSentCount = 0; foreach ($newslettersToSend as $newsletter) { $emailNotSentCount += $newsletter->getEmailNotSentCount(); } foreach ($newslettersBeingSent as $newsletter) { $emailNotSentCount += $newsletter->getEmailNotSentCount(); } $emailsPerRound = Tools::confParam('mails_per_round'); return \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('task_send_emails_additional_information', 'newsletter', [$emailsPerRound, $emailNotSentCount, $newslettersToSendCount, $newslettersBeingSentCount]); }
public function init() { $this->data = array(); $content = \Ecodev\Newsletter\Tools::getUrl($this->getHtmlUrl()); switch ($this->getHtmlFetchType()) { case 'mailto': preg_match_all('|<a[^>]+href="mailto:([^"]+)"[^>]*>(.*)</a>|Ui', $content, $fetched_data); foreach ($fetched_data[1] as $i => $email) { $this->data[] = array('email' => $email, 'name' => $fetched_data[2][$i]); } break; case 'regex': default: preg_match_all("|[\\.a-z0-9!#\$%&'*+-/=?^_`{\\|}]+@[a-z0-9_-][\\.a-z0-9_-]*\\.[a-z]{2,}|i", $content, $fetched_data); foreach ($fetched_data[0] as $address) { $this->data[]['email'] = $address; } } }
/** * Dispatch actions to take according to current bounce level */ public function dispatch() { $this->findEmail(); // If couldn't find the original email we cannot do anything if (!$this->email) { Tools::getLogger(__CLASS__)->warning('Bounced email found but cannot find corresponding record in database. Skipped.'); return; } $bounceLevel = $this->emailParser->getBounceLevel(); if ($bounceLevel != EmailParser::NEWSLETTER_NOT_A_BOUNCE) { if ($this->recipientList) { $this->recipientList->registerBounce($this->email->getRecipientAddress(), $bounceLevel); } $this->email->setBounceTime(new DateTime()); $emailRepository = $this->objectManager->get(\Ecodev\Newsletter\Domain\Repository\EmailRepository::class); $emailRepository->updateNow($this->email); } Tools::getLogger(__CLASS__)->info('Bounced email found with bounce level ' . $bounceLevel); }
/** * Return all markers and their values as associative array * @param Email $email * @return string[] */ private function getMarkers(Email $email) { $markers = $email->getRecipientData(); // Add predefined markers $authCode = $email->getAuthCode(); $markers['newsletter_view_url'] = Tools::buildFrontendUri('show', array('c' => $authCode), 'Email'); $markers['newsletter_unsubscribe_url'] = Tools::buildFrontendUri('unsubscribe', array('c' => $authCode), 'Email'); return $markers; }
/** * Dispatch actions to take according to current bounce level */ public function dispatch() { $this->findEmail(); // If couldn't find the original email we cannot do anything if (!$this->email) { Tools::log("Bounced email found but cannot find corresponding record in database. Skipped.", 1); return; } if ($this->bounceLevel != self::NEWSLETTER_NOT_A_BOUNCE) { if ($this->recipientList) { $this->recipientList->registerBounce($this->email->getRecipientAddress(), $this->bounceLevel); } $this->email->setBounceTime(new DateTime()); $emailRepository = $this->objectManager->get('Ecodev\\Newsletter\\Domain\\Repository\\EmailRepository'); $emailRepository->updateNow($this->email); } Tools::log("Bounced email found with bounce level " . $this->bounceLevel); }
/** * Replace all links in the mail to make spy links. * * @param \Ecodev\Newsletter\Domain\Model\Email $email The email to prepare the newsletter for * @param bool $isPreview whether we are preparing a preview version (if true links will not be stored in database thus no statistics will be available) */ private function injectLinksSpy(Email $email, $isPreview) { /* Exchange all http:// links html */ preg_match_all('|<a [^>]*href="(https?://[^"]*)"|Ui', $this->html, $urls); // No-Track Marker $notrackMarker = Tools::confParam('no-track'); foreach ($urls[1] as $i => $url) { // Check for a no-track marker if (!empty($notrackMarker) && stripos($url, $notrackMarker) != false) { continue; } $newUrl = $this->getLinkAuthCode($email, $url, $isPreview); /* Two step replace to be as precise as possible */ $link = str_replace($url, $newUrl, $urls[0][$i]); $this->html = str_replace($urls[0][$i], $link, $this->html); } }
private function getLinkAuthCode(Email $email, $url, $isPreview, $isPlainText = false) { global $TYPO3_DB; $url = html_entity_decode($url); // First check in our local cache if (isset($this->linksCache[$url])) { $linkId = $this->linksCache[$url]; } elseif ($isPreview) { $linkId = count($this->linksCache); } else { // Look for the link database, it may already exist $res = $TYPO3_DB->sql_query('SELECT uid FROM tx_newsletter_domain_model_link WHERE url = ' . $TYPO3_DB->fullQuoteStr($url, 'tx_newsletter_domain_model_link') . ' AND newsletter = ' . $TYPO3_DB->fullQuoteStr($this->newsletter->getUid(), 'tx_newsletter_domain_model_link') . ' LIMIT 1'); $row = $TYPO3_DB->sql_fetch_row($res); if ($row) { $linkId = $row[0]; } else { $TYPO3_DB->exec_INSERTquery('tx_newsletter_domain_model_link', array('pid' => $this->newsletter->getPid(), 'url' => $url, 'newsletter' => $this->newsletter->getUid())); $linkId = $TYPO3_DB->sql_insert_id(); } } // Store link in cache $this->linksCache[$url] = $linkId; $authCode = md5($email->getAuthCode() . $linkId); $newUrl = Tools::buildFrontendUri('clicked', array(), 'Link') . '&n=' . $this->newsletter->getUid() . '&l=' . $authCode . ($isPlainText ? '&p=1' : ''); return $newUrl; }
/** * Return the content of the given URL * @param string $url * @return string */ protected function getURL($url) { return \Ecodev\Newsletter\Tools::getUrl($url); }
/** * Return HTML code showing an extract of recipients (first X recipients) */ public function getExtract($limit = 30) { if ($this->getError()) { $out = 'Error: ' . $this->getError(); } else { $i = 0; while ($row = $this->getRecipient()) { // Dump formatted table header if ($i == 0) { $out .= '<tr>'; foreach (array_keys($row) as $key) { $out .= '<th style="padding-right: 1em;">' . $this->getFieldTitle($key) . '</th>'; } $out .= '</tr>'; } $out .= '<tr style="border: 1px grey solid; border-collapse: collapse;">'; foreach ($row as $field) { $out .= '<td style="padding-right: 1em;">' . $field . '</td>'; } $out .= '</tr>'; if (++$i == $limit) { break; } } $out = '<table style="border: 1px grey solid; border-collapse: collapse;">' . $out . '</table>'; $authCode = \TYPO3\CMS\Core\Utility\GeneralUtility::stdAuthCode($this->_getCleanProperties()); $uriXml = Tools::buildFrontendUri('export', array('uidRecipientList' => $this->getUid(), 'authCode' => $authCode, 'format' => 'xml'), 'RecipientList'); $uriCsv = Tools::buildFrontendUri('export', array('uidRecipientList' => $this->getUid(), 'authCode' => $authCode, 'format' => 'csv'), 'RecipientList'); $out .= '<p><strong>' . $i . '/' . $this->getCount() . '</strong> recipients (<a href="' . $uriXml . '">export XML</a>, ' . '<a href="' . $uriCsv . '">export CSV</a>' . ')</p>'; } $out = '<h4>' . $this->getTitle() . '</h4>' . $out; return $out; }
/** * Returns the URL of the content of this newsletter * @return string */ public function getContentUrl($language = null) { $append_url = Tools::confParam('append_url'); $baseUrl = $this->getBaseUrl(); if (!is_null($language)) { $language = '&L=' . $language; } return $baseUrl . '/index.php?id=' . $this->getPid() . $language . $append_url; }
<?php return ['ctrl' => ['title' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist', 'label' => 'title', 'tstamp' => 'tstamp', 'crdate' => 'crdate', 'delete' => 'deleted', 'type' => 'type', 'enablecolumns' => ['disabled' => 'hidden'], 'iconfile' => \Ecodev\Newsletter\Tools::getIconfilePrefix() . 'Resources/Public/Icons/tx_newsletter_domain_model_recipientlist.gif', 'type' => 'type'], 'interface' => ['showRecordFieldList' => 'hidden,title'], 'feInterface' => $TCA['tx_newsletter_domain_model_recipientlist']['feInterface'], 'columns' => ['hidden' => ['exclude' => 1, 'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.hidden', 'config' => ['type' => 'check', 'default' => '0']], 'title' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.title', 'config' => ['type' => 'input', 'size' => '30', 'eval' => 'trim,required']], 'plain_only' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.plain_only', 'config' => ['type' => 'check', 'default' => '0']], 'lang' => ['label' => 'LLL:EXT:lang/locallang_tca.php:sys_language', 'config' => ['type' => 'select', 'renderType' => 'selectSingle', 'foreign_table' => 'sys_language', 'foreign_table_where' => 'ORDER BY sys_language.uid', 'minitems' => 0, 'maxitems' => 1, 'items' => ['0' => ['', -1], '1' => ['LLL:EXT:lang/locallang_general.php:LGL.default_value', 0]]]], 'be_users' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.be_users', 'config' => ['type' => 'select', 'renderType' => 'selectMultipleSideBySide', 'foreign_table' => 'be_users', 'foreign_table_where' => 'ORDER BY be_users.uid', 'size' => 5, 'minitems' => 0, 'maxitems' => 100]], 'fe_groups' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.fe_groups', 'config' => ['type' => 'group', 'internal_type' => 'db', 'allowed' => 'fe_groups', 'size' => 5, 'minitems' => 0, 'maxitems' => 100]], 'fe_pages' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.fe_pages', 'config' => ['type' => 'group', 'internal_type' => 'db', 'allowed' => 'pages', 'size' => 5, 'minitems' => 0, 'maxitems' => 100]], 'sql_statement' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.sql_statement', 'config' => ['type' => 'text', 'cols' => '50', 'rows' => '10']], 'sql_register_bounce' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.sql_register_bounce', 'config' => ['type' => 'text', 'cols' => '50', 'rows' => '10']], 'sql_register_open' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.sql_register_open', 'config' => ['type' => 'text', 'cols' => '50', 'rows' => '10']], 'sql_register_click' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.sql_register_click', 'config' => ['type' => 'text', 'cols' => '50', 'rows' => '10']], 'csv_separator' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.csv_separator', 'config' => ['type' => 'input', 'size' => 1]], 'csv_fields' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.csv_fields', 'config' => ['type' => 'input', 'size' => 20]], 'csv_values' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.csv_values', 'config' => ['type' => 'text', 'cols' => 40, 'rows' => 10]], 'csv_filename' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.csv_file', 'config' => ['type' => 'group', 'internal_type' => 'file', 'allowed' => 'csv,txt', 'max_size' => 500, 'uploadfolder' => 'uploads/tx_newsletter', 'size' => 1, 'minitems' => 0, 'maxitems' => 1]], 'csv_url' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.csv_url', 'config' => ['type' => 'input', 'size' => 20]], 'type' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type', 'config' => ['type' => 'select', 'renderType' => 'selectSingle', 'items' => [['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type_be_users', \Ecodev\Newsletter\Domain\Model\RecipientList\BeUsers::class], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type_fe_groups', \Ecodev\Newsletter\Domain\Model\RecipientList\FeGroups::class], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type_fe_pages', \Ecodev\Newsletter\Domain\Model\RecipientList\FePages::class], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type_sql', \Ecodev\Newsletter\Domain\Model\RecipientList\Sql::class], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type_csv_file', \Ecodev\Newsletter\Domain\Model\RecipientList\CsvFile::class], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type_csv_list', \Ecodev\Newsletter\Domain\Model\RecipientList\CsvList::class], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type_csv_url', \Ecodev\Newsletter\Domain\Model\RecipientList\CsvUrl::class], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.type_html', \Ecodev\Newsletter\Domain\Model\RecipientList\Html::class]], 'maxitems' => 1, 'default' => \Ecodev\Newsletter\Domain\Model\RecipientList\Sql::class]], 'html_url' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.html_url', 'config' => ['type' => 'input', 'size' => 20, 'eval' => 'trim,required']], 'html_fetch_type' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.html_fetch_type', 'config' => ['type' => 'select', 'renderType' => 'selectSingle', 'items' => [['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.html_fetch_type_mailto', 'mailto'], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_recipientlist.html_fetch_type_regex', 'regex']], 'size' => 1, 'maxitems' => 1]], 'recipients_preview' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang.xlf:preview', 'config' => ['type' => 'user', 'userFunc' => 'Ecodev\\Newsletter\\Tca\\RecipientListTca->render']]], 'types' => ['0' => ['showitem' => 'hidden;;1, title, type'], \Ecodev\Newsletter\Domain\Model\RecipientList\BeUsers::class => ['showitem' => 'hidden;;1, title, plain_only, lang, type, be_users, recipients_preview'], \Ecodev\Newsletter\Domain\Model\RecipientList\FeGroups::class => ['showitem' => 'hidden;;1, title, plain_only, lang, type, fe_groups, recipients_preview'], \Ecodev\Newsletter\Domain\Model\RecipientList\FePages::class => ['showitem' => 'hidden;;1, title, plain_only, lang, type, fe_pages, recipients_preview'], \Ecodev\Newsletter\Domain\Model\RecipientList\Sql::class => ['showitem' => 'hidden;;1, title, plain_only, type, sql_statement, sql_register_bounce, sql_register_open, sql_register_click, recipients_preview'], \Ecodev\Newsletter\Domain\Model\RecipientList\CsvFile::class => ['showitem' => 'hidden;;1, title, plain_only, type, csv_separator, csv_fields, csv_filename, recipients_preview'], \Ecodev\Newsletter\Domain\Model\RecipientList\CsvList::class => ['showitem' => 'hidden;;1, title, plain_only, type, csv_separator, csv_fields, csv_values, recipients_preview'], \Ecodev\Newsletter\Domain\Model\RecipientList\CsvUrl::class => ['showitem' => 'hidden;;1, title, plain_only, type, csv_separator, csv_fields, csv_url, recipients_preview'], \Ecodev\Newsletter\Domain\Model\RecipientList\Html::class => ['showitem' => 'hidden;;1, title, plain_only, lang, type, html_url, html_fetch_type, recipients_preview']], 'palettes' => ['1' => ['showitem' => '']]];
/** * Find all pairs of newsletter-email UIDs that are should be sent * * @global \TYPO3\CMS\Core\Database\DatabaseConnection $TYPO3_DB * @param Newsletter $newsletter * @return array [[newsletter => 12, email => 5], ...] */ public static function findAllNewsletterAndEmailUidToSend(Newsletter $newsletter = null) { global $TYPO3_DB; // Apply limit of emails per round $mails_per_round = (int) \Ecodev\Newsletter\Tools::confParam('mails_per_round'); if ($mails_per_round) { $limit = ' LIMIT ' . $mails_per_round; } else { $limit = ''; } // Apply newsletter restriction if any if ($newsletter) { $newsletterUid = 'AND tx_newsletter_domain_model_newsletter.uid = ' . $newsletter->getUid(); } else { $newsletterUid = ''; } // Find the uid of emails and newsletters that need to be sent $rs = $TYPO3_DB->sql_query('SELECT tx_newsletter_domain_model_newsletter.uid AS newsletter, tx_newsletter_domain_model_email.uid AS email FROM tx_newsletter_domain_model_email INNER JOIN tx_newsletter_domain_model_newsletter ON (tx_newsletter_domain_model_email.newsletter = tx_newsletter_domain_model_newsletter.uid) WHERE tx_newsletter_domain_model_email.begin_time = 0 ' . $newsletterUid . ' ORDER BY tx_newsletter_domain_model_email.newsletter ' . $limit); $result = array(); while ($record = $TYPO3_DB->sql_fetch_assoc($rs)) { $result[] = $record; } return $result; }
/** * Load data from a CSV file. * @param $filename path to the CSV file may be on disk or remote URL */ protected function loadCsvFromFile($filename) { $csvdata = null; if ($filename) { $csvdata = \Ecodev\Newsletter\Tools::getURL($filename); } $this->loadCsvFromData($csvdata); }
/** * Creates a new Newsletter and forwards to the list action. * * @param \Ecodev\Newsletter\Domain\Model\Newsletter $newNewsletter a fresh Newsletter object which has not yet been added to the repository * @dontverifyrequesthash * @dontvalidate $newNewsletter * @ignorevalidation $newNewsletter */ public function createAction(Newsletter $newNewsletter = null) { $limitTestRecipientCount = 10; // This is a low limit, technically, but it does not make sense to test a newsletter for more people than that anyway $recipientList = $newNewsletter->getRecipientList(); $recipientList->init(); $count = $recipientList->getCount(); $validatedContent = $newNewsletter->getValidatedContent($language); // If we attempt to create a newsletter as a test but it has too many recipient, reject it (we cannot safely send several emails wihtout slowing down respoonse and/or timeout issues) if ($newNewsletter->getIsTest() && $count > $limitTestRecipientCount) { $this->addFlashMessage($this->translate('flashmessage_test_maximum_recipients', [$count, $limitTestRecipientCount]), $this->translate('flashmessage_test_maximum_recipients_title'), \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR); $this->view->assign('success', false); } elseif (count($validatedContent['errors'])) { $this->addFlashMessage('The newsletter HTML content does not validate. See tab "Newsletter > Status" for details.', $this->translate('flashmessage_newsletter_invalid'), \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR); $this->view->assign('success', false); } else { // If it's a test newsletter, it's planned to be sent right now if ($newNewsletter->getIsTest()) { $newNewsletter->setPlannedTime(new DateTime()); } // Save the new newsletter $this->newsletterRepository->add($newNewsletter); $this->persistenceManager->persistAll(); $this->view->assign('success', true); // If it is test newsletter, send it immediately if ($newNewsletter->getIsTest()) { try { // Fill the spool and run the queue Tools::createSpool($newNewsletter); Tools::runSpool($newNewsletter); $this->addFlashMessage($this->translate('flashmessage_test_newsletter_sent'), $this->translate('flashmessage_test_newsletter_sent_title'), \TYPO3\CMS\Core\Messaging\FlashMessage::OK); } catch (\Exception $exception) { $this->addFlashMessage($exception->getMessage(), $this->translate('flashmessage_test_newsletter_error'), \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR); } } else { $this->addFlashMessage($this->translate('flashmessage_newsletter_queued'), $this->translate('flashmessage_newsletter_queued_title'), \TYPO3\CMS\Core\Messaging\FlashMessage::OK); } } $this->view->setVariablesToRender(['data', 'success', 'flashMessages']); $this->view->setConfiguration(['data' => self::resolveJsonViewConfiguration()]); $this->view->assign('data', $newNewsletter); $this->flushFlashMessages(); }
public function getSubstitutedConfig() { $markers = ['###SERVER###', '###PROTOCOL###', '###PORT###', '###USERNAME###', '###PASSWORD###']; $values = []; $values[] = $this->getServer(); $values[] = $this->getProtocol(); $values[] = $this->getPort(); $values[] = $this->getUsername(); $values[] = \Ecodev\Newsletter\Tools::decrypt($this->getPassword()); $config = $this->getConfig(); if (empty($config)) { // Keep the old config to not break old installations $config = 'poll ###SERVER### proto ###PROTOCOL### username "###USERNAME###" password "###PASSWORD###"'; } else { $config = \Ecodev\Newsletter\Tools::decrypt($config); } $result = str_replace($markers, $values, $config); unset($values); // Dont leave unencrypted values in memory around for too long. return $result; }
/** * Returns the URL of the content of this newsletter * @return string */ public function getContentUrl($language = null) { $append_url = Tools::confParam('append_url'); $domain = $this->getDomain(); if (!is_null($language)) { $language = '&L=' . $language; } $protocol = Tools::confParam('protocol'); //stefano: protocol is now set through "basic.protocol" parameter return "{$protocol}{$domain}/index.php?id=" . $this->getPid() . $language . $append_url; }
/** * @test */ public function getSubstitutedConfigCustom() { $this->subject->setServer('pop.example.com'); $this->subject->setProtocol('pop'); $this->subject->setPort(123); $this->subject->setUsername('connor'); $this->subject->setPassword(\Ecodev\Newsletter\Tools::encrypt('skynet')); $config = 'server : ###SERVER### protocol: ###PROTOCOL### port : ###PORT### username: ###USERNAME### password: ###PASSWORD###'; $this->subject->setConfig(\Ecodev\Newsletter\Tools::encrypt($config)); $expected = 'server : pop.example.com protocol: pop port : 123 username: connor password: skynet'; $this->assertSame($expected, $this->subject->getSubstitutedConfig()); }
/** * Encrypt old bounce account passwords and preserve old default config * * @return string[] */ private function getQueriesToEncryptOldBounceAccountPasswords() { // Fetch the old records - they will have a default port and an empty config. $rs = $this->databaseConnection->exec_SELECTquery('uid, password', 'tx_newsletter_domain_model_bounceaccount', 'port = 0 AND config = \'\''); $records = []; while ($record = $this->databaseConnection->sql_fetch_assoc($rs)) { $records[] = $record; } $this->databaseConnection->sql_free_result($rs); if (empty($records)) { return []; } // Keep the old config to not break old installations $config = Tools::encrypt("poll ###SERVER###\nproto ###PROTOCOL### \nusername \"###USERNAME###\"\npassword \"###PASSWORD###\"\n"); $queries = []; foreach ($records as $record) { $queries[] = $this->databaseConnection->UPDATEquery('tx_newsletter_domain_model_bounceaccount', 'uid=' . intval($record['uid']), ['password' => Tools::encrypt($record['password']), 'config' => $config]); } return ['Encrypt bounce account passwords' => $queries]; }
/** * Encrypt old bounce account passwords * * @global \TYPO3\CMS\Core\Database\DatabaseConnection $TYPO3_DB * @return string[] */ private static function getQueriesToEncryptOldBounceAccountPasswords() { // Prepare Queries // Keep the old config to not break old installations $config = Tools::encrypt("poll ###SERVER###\nproto ###PROTOCOL### \nusername \"###USERNAME###\"\npassword \"###PASSWORD###\"\n"); // Fetch and update the old records - they will have a default port and an empty config. global $TYPO3_DB; $rs = $TYPO3_DB->exec_SELECTquery('uid, password', 'tx_newsletter_domain_model_bounceaccount', 'port = 0 AND config = \'\''); while (($records[] = $TYPO3_DB->sql_fetch_assoc($rs)) || array_pop($records)) { } $TYPO3_DB->sql_free_result($rs); // Set Queries $queries = array(); if (!empty($records)) { foreach ($records as $row) { $queries[] = $TYPO3_DB->UPDATEquery('tx_newsletter_domain_model_bounceaccount', 'uid=' . intval($row['uid']), array('password' => Tools::encrypt($row['password']), 'config' => $config)); } } return $queries; }
/** * Sends an email to the address configured in extension settings when a recipient unsubscribe * @param \Ecodev\Newsletter\Domain\Model\Newsletter $newsletter * @param \Ecodev\Newsletter\Domain\Model\RecipientList $recipientList * @param \Ecodev\Newsletter\Domain\Model\Email $email * @return void */ protected function notifyUnsubscribe($newsletter, $recipientList, Email $email) { $notificationEmail = Tools::confParam('notification_email'); // Use the page-owner as user if ($notificationEmail == 'user') { $rs = $GLOBALS['TYPO3_DB']->sql_query("SELECT email\n\t\t\tFROM be_users\n\t\t\tLEFT JOIN pages ON be_users.uid = pages.perms_userid\n\t\t\tWHERE pages.uid = " . $newsletter->getPid()); list($notificationEmail) = $GLOBALS['TYPO3_DB']->sql_fetch_row($rs); } // If cannot find valid email, don't send any notification if (!\TYPO3\CMS\Core\Utility\GeneralUtility::validEmail($notificationEmail)) { return; } // Build email texts $baseUrl = 'http://' . $newsletter->getDomain(); $urlRecipient = $baseUrl . '/typo3/alt_doc.php?&edit[tx_newsletter_domain_model_email][' . $email->getUid() . ']=edit'; $urlRecipientList = $baseUrl . '/typo3/alt_doc.php?&edit[tx_newsletter_domain_model_recipientlist][' . $recipientList->getUid() . ']=edit'; $urlNewsletter = $baseUrl . '/typo3/alt_doc.php?&edit[tx_newsletter_domain_model_newsletter][' . $newsletter->getUid() . ']=edit'; $subject = \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('unsubscribe_notification_subject', 'newsletter'); $body = \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('unsubscribe_notification_body', 'newsletter', array($email->getRecipientAddress(), $urlRecipient, $recipientList->getTitle(), $urlRecipientList, $newsletter->getTitle(), $urlNewsletter)); // Actually sends email $message = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\MailMessage'); $message->setTo($notificationEmail)->setFrom(array($newsletter->getSenderEmail() => $newsletter->getSenderName()))->setSubject($subject)->setBody($body, 'text/html'); $message->send(); }
<?php return ['ctrl' => ['title' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_link', 'label' => 'url', 'iconfile' => \Ecodev\Newsletter\Tools::getIconfilePrefix() . 'Resources/Public/Icons/tx_newsletter_domain_model_link.gif'], 'interface' => ['showRecordFieldList' => 'url,opened_count,newsletter'], 'types' => ['1' => ['showitem' => 'url,opened_count,newsletter']], 'palettes' => ['1' => ['showitem' => '']], 'columns' => ['hidden' => ['exclude' => 1, 'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.hidden', 'config' => ['type' => 'check']], 'url' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_link.url', 'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'readOnly' => true]], 'opened_count' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_link.opened_count', 'config' => ['type' => 'input', 'size' => 4, 'eval' => 'int', 'readOnly' => true]], 'newsletter' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_link.newsletter', 'config' => ['readOnly' => true, 'type' => 'inline', 'foreign_table' => 'tx_newsletter_domain_model_newsletter', 'minitems' => 0, 'maxitems' => 1, 'appearance' => ['collapse' => 0, 'showSynchronizationLink' => 1, 'showPossibleLocalizationRecords' => 1, 'showAllLocalizationLink' => 1]]]]];
<?php // From TYPO3 7.4.0 onward we must use EXT prefix if (version_compare(TYPO3_version, '7.4.0', '>=')) { $wizardIcon = 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_edit.gif'; } else { // But for TYPO3 6.2 family, we still have to use old style $wizardIcon = 'edit2.gif'; } return ['ctrl' => ['title' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter', 'label' => 'planned_time', 'tstamp' => 'tstamp', 'crdate' => 'crdate', 'delete' => 'deleted', 'enablecolumns' => ['disabled' => 'hidden'], 'iconfile' => \Ecodev\Newsletter\Tools::getIconfilePrefix() . 'Resources/Public/Icons/tx_newsletter_domain_model_newsletter.gif'], 'interface' => ['showRecordFieldList' => 'planned_time,begin_time,end_time,repetition,plain_converter,is_test,attachments,sender_name,sender_email,replyto_name,replyto_email,inject_open_spy,inject_links_spy,bounce_account,recipient_list'], 'types' => ['1' => ['showitem' => 'planned_time,begin_time,end_time,repetition,plain_converter,is_test,attachments,sender_name,sender_email,replyto_name,replyto_email,inject_open_spy,inject_links_spy,bounce_account,recipient_list']], 'palettes' => ['1' => ['showitem' => '']], 'columns' => ['hidden' => ['exclude' => 1, 'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.hidden', 'config' => ['type' => 'check']], 'planned_time' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.planned_time', 'config' => ['type' => 'input', 'size' => 12, 'eval' => 'datetime,required']], 'begin_time' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.begin_time', 'config' => ['type' => 'input', 'size' => 12, 'readOnly' => true, 'eval' => 'datetime']], 'end_time' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.end_time', 'config' => ['type' => 'input', 'size' => 12, 'readOnly' => true, 'eval' => 'datetime']], 'repetition' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition', 'config' => ['type' => 'select', 'renderType' => 'selectSingle', 'items' => [['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition_none', '0'], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition_daily', '1'], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition_weekly', '2'], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition_biweekly', '3'], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition_monthly', '4'], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition_quarterly', '5'], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition_semiyearly', '6'], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.repetition_yearly', '7']], 'maxitems' => 1]], 'plain_converter' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.plain_converter', 'config' => ['type' => 'select', 'renderType' => 'selectSingle', 'items' => [['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.plain_converter_builtin', \Ecodev\Newsletter\Domain\Model\PlainConverter\Builtin::class], ['LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.plain_converter_lynx', \Ecodev\Newsletter\Domain\Model\PlainConverter\Lynx::class]], 'maxitems' => 1]], 'is_test' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.is_test', 'config' => ['type' => 'check', 'default' => 0]], 'attachments' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.attachments', 'config' => ['type' => 'group', 'internal_type' => 'file', 'allowed' => '', 'disallowed' => 'php,php3', 'max_size' => 500, 'uploadfolder' => 'uploads/tx_newsletter', 'size' => 3, 'minitems' => 0, 'maxitems' => 10]], 'sender_name' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.sender_name', 'config' => ['type' => 'input', 'size' => 30, 'eval' => 'trim']], 'sender_email' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.sender_email', 'config' => ['type' => 'input', 'size' => 30, 'eval' => 'trim']], 'replyto_name' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.replyto_name', 'config' => ['type' => 'input', 'size' => 30, 'eval' => 'trim']], 'replyto_email' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.replyto_email', 'config' => ['type' => 'input', 'size' => 30, 'eval' => 'trim']], 'inject_open_spy' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.inject_open_spy', 'config' => ['type' => 'check', 'default' => 0]], 'inject_links_spy' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.inject_links_spy', 'config' => ['type' => 'check', 'default' => 0]], 'bounce_account' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.bounce_account', 'config' => ['type' => 'select', 'renderType' => 'selectSingle', 'foreign_table' => 'tx_newsletter_domain_model_bounceaccount', 'items' => [['', 0]], 'maxitems' => 1, 'wizards' => ['edit' => ['type' => 'popup', 'icon' => $wizardIcon, 'module' => ['name' => 'wizard_edit']]]]], 'recipient_list' => ['label' => 'LLL:EXT:newsletter/Resources/Private/Language/locallang_db.xlf:tx_newsletter_domain_model_newsletter.recipient_list', 'config' => ['type' => 'select', 'renderType' => 'selectSingle', 'foreign_table' => 'tx_newsletter_domain_model_recipientlist', 'maxitems' => 1, 'wizards' => ['edit' => ['type' => 'popup', 'icon' => $wizardIcon, 'module' => ['name' => 'wizard_edit']]]]]]];