Compare commits

...

29 Commits

Author SHA1 Message Date
Tautvidas Sipavičius
2f25dc6a20 Bump up release version to 0.0.27 2016-05-06 14:46:12 +03:00
Tautvidas Sipavičius
fc38ee2f08 Merge pull request #466 from mailpoet/rendering_engine_update
Fixes empty paragraph spacing
2016-05-05 13:04:26 +03:00
Vlad
33bebc6629 - Replaces empty paragraphs with single space according to specific
condition
2016-05-04 19:57:10 -04:00
Tautvidas Sipavičius
14a8e02f99 Merge pull request #465 from mailpoet/browser_preview
Newsletter preview
2016-05-04 17:58:41 +03:00
Vlad
0bf6c87ec7 - Fixes shortcode category name for view in browser url
- Updates shortcode regex
2016-05-04 10:45:30 -04:00
Vlad
422fba2835 - Updates based on code review comments 2016-05-03 18:41:57 -04:00
Vlad
f36dbb78e6 - Updates unit test 2016-05-02 13:44:36 -04:00
Vlad
3213dd0d08 - Removes unused exception handlers 2016-05-02 13:19:10 -04:00
Vlad
3f2199fd63 - Updates link tracking to use the same Public API data format as other
endpoints
2016-05-02 13:15:48 -04:00
Vlad
a4477a9bd6 - Updates CONST values 2016-05-02 12:27:00 -04:00
Vlad
52790d7bd6 - Updates code comment 2016-05-02 12:24:56 -04:00
Vlad
0b9812210f - Updates CONST values 2016-05-02 11:30:37 -04:00
Vlad
756dbb4641 - Updates daemon status to CONSTs
- Uncomments temporary commented out code
- Removes unnecessary exception handles in Public API
2016-05-02 10:32:34 -04:00
Vlad
b38742ddc0 - Changes divider to CONST 2016-05-02 10:02:22 -04:00
Vlad
49e38549ec - Fixes URLs not being properly generated when tracking is enabled 2016-05-02 10:00:35 -04:00
Vlad
afcb0a0d7f - Upperases all constants 2016-05-02 09:47:54 -04:00
Vlad
d18f0e50b5 - Removes debug backtrace
- Removes uncommented queue save
2016-05-02 09:23:37 -04:00
Vlad
6868a07ead - Moves link saving logic into Links class 2016-05-01 21:55:24 -04:00
Vlad
cca76d0d97 - Adds custom shortcode processing logic
- Updates shortcode categories code
- Completely rewrites shortcodes unit tess
2016-05-01 14:07:06 -04:00
Vlad
70fa77d333 - Creates temporary folder if it doesn't exist 2016-04-30 22:38:55 -04:00
Vlad
412201d965 - Updates unit test 2016-04-30 22:38:39 -04:00
Vlad
045a92c7d6 - Standardizes variable names 2016-04-30 22:21:03 -04:00
Vlad
2ba2e3eca5 - Refactors sending queue worker 2016-04-30 22:19:59 -04:00
Vlad
90c294f60e - Updates editor/router to use the new browser preview class 2016-04-30 22:19:59 -04:00
Vlad
57b953dd14 - Rewrites shortcode processing class to work with other changes 2016-04-30 22:19:59 -04:00
Vlad
0f81a8db60 - Updates method to utilize constant value 2016-04-30 22:19:59 -04:00
Vlad
2d6971f8df - Refactors link tracking class 2016-04-30 22:19:59 -04:00
Vlad
0abe8b5371 - Implements view in browser 2016-04-30 22:19:59 -04:00
Vlad
5bad682879 - Merges all shortcode links under one category
- Adds filter hooks for custom links
- Renames old shortcodes in the editor and elsewhere
2016-04-30 22:19:59 -04:00
32 changed files with 805 additions and 388 deletions

View File

@@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'footer',
text: '<a href="[subscription:unsubscribe_url]">Unsubscribe</a> | <a href="[subscription:manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
styles: {
block: {
backgroundColor: 'transparent',

View File

@@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'header',
text: 'Display problems? <a href="[newsletter:view_in_browser_url]">View it in your browser</a>',
text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>',
styles: {
block: {
backgroundColor: 'transparent',

View File

@@ -284,7 +284,7 @@ define([
}
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
JSON.stringify(jsonObject).indexOf("[subscription:unsubscribe_url]") < 0) {
JSON.stringify(jsonObject).indexOf("[link:subscription_unsubscribe_url]") < 0) {
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
return;
}

View File

@@ -245,14 +245,20 @@ define([
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'render',
action: 'showPreview',
data: json,
}).done(function(response){
MailPoet.Modal.loading(false);
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
if (response.result === true) {
window.open(response.data.url, '_blank')
}
MailPoet.Notice.error(response.errors);
}).fail(function(error) {
MailPoet.Modal.loading(false);
alert('Something went wrong, check console');
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewFailed')
);
});
},
sendPreview: function() {

View File

@@ -39,6 +39,9 @@ class Env {
self::$assets_url = plugins_url('/assets', $file);
$wp_upload_dir = wp_upload_dir();
self::$temp_path = $wp_upload_dir['basedir'].'/'.self::$plugin_name;
if (!is_dir(self::$temp_path)) {
mkdir(self::$temp_path);
}
self::$temp_url = $wp_upload_dir['baseurl'].'/'.self::$plugin_name;
self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib';

View File

@@ -142,7 +142,7 @@ class BlankTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => "<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
"text" => "<a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a> | <a href=\"[link:subscription_manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@@ -46,7 +46,7 @@ class FranksRoastHouseTemplate {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
"text" => __("Display problems?&nbsp;<a href=\"[link:newsletter_view_in_browser_url]\">View it in your browser</a>"),
"styles" => array(
"block" => array(
"backgroundColor" => "#ccc6c6"
@@ -280,7 +280,7 @@ class FranksRoastHouseTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<p><a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
"text" => __("<p><a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a> | <a href=\"[link:subscription_manage_url]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
"styles" => array(
"block" => array(
"backgroundColor" => "#a9a7a7"

View File

@@ -242,7 +242,7 @@ class PostNotificationsBlankTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"text" => __("<a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a> | <a href=\"[link:subscription_manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@@ -46,7 +46,7 @@ class WelcomeTemplate {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
"text" => __("Display problems?&nbsp;<a href=\"[link:newsletter_view_in_browser_url]\">View it in your browser</a>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"
@@ -224,7 +224,7 @@ class WelcomeTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"text" => __("<a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a> | <a href=\"[link:subscription_manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@@ -2,9 +2,10 @@
namespace MailPoet\Config;
use MailPoet\Cron\Daemon;
use MailPoet\Newsletter\Viewer\ViewInBrowser;
use MailPoet\Statistics\Track\Clicks;
use MailPoet\Statistics\Track\Opens;
use MailPoet\Subscription;
use MailPoet\Statistics\Track\Clicks;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
@@ -25,7 +26,7 @@ class PublicAPI {
Helpers::underscoreToCamelCase($_GET['action']) :
false;
$this->data = isset($_GET['data']) ?
$_GET['data'] :
unserialize(base64_decode($_GET['data'])) :
false;
}
@@ -35,33 +36,29 @@ class PublicAPI {
}
function queue() {
try {
$queue = new Daemon($this->_decodeData());
$this->_checkAndCallMethod($queue, $this->action);
} catch(\Exception $e) {
}
$queue = new Daemon($this->data);
$this->_checkAndCallMethod($queue, $this->action);
}
function subscription() {
try {
$subscription = new Subscription\Pages($this->action, $this->_decodeData());
$this->_checkAndCallMethod($subscription, $this->action);
} catch(\Exception $e) {
}
$subscription = new Subscription\Pages($this->action, $this->data);
$this->_checkAndCallMethod($subscription, $this->action);
}
function track() {
try {
if($this->action === 'click') {
$track_class = new Clicks($this->data);
}
if($this->action === 'open') {
$track_class = new Opens($this->data);
}
if(!isset($track_class)) return;
$track_class->track();
} catch(\Exception $e) {
if($this->action === 'click') {
$track_class = new Clicks($this->data);
}
if($this->action === 'open') {
$track_class = new Opens($this->data);
}
if(!isset($track_class)) return;
$track_class->track();
}
function viewInBrowser() {
$viewer = new ViewInBrowser($this->data);
$viewer->view();
}
private function _checkAndCallMethod($class, $method, $terminate_request = false) {
@@ -77,12 +74,4 @@ class PublicAPI {
)
);
}
private function _decodeData() {
if($this->data !== false) {
return unserialize(base64_decode($this->data));
} else {
return array();
}
}
}

View File

@@ -7,13 +7,13 @@ use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class CronHelper {
const daemon_execution_limit = 20;
const daemon_execution_timeout = 35;
const daemon_request_timeout = 2;
const DAEMON_EXECUTION_LIMIT = 20;
const DAEMON_EXECUTION_TIMEOUT = 35;
const DAEMON_REQUEST_TIMEOUT = 2;
static function createDaemon($token) {
$daemon = array(
'status' => 'starting',
'status' => Daemon::STATUS_STARTING,
'counter' => 0,
'token' => $token
);
@@ -37,7 +37,7 @@ class CronHelper {
return Security::generateRandomString();
}
static function accessDaemon($token, $timeout = self::daemon_request_timeout) {
static function accessDaemon($token, $timeout = self::DAEMON_REQUEST_TIMEOUT) {
$data = serialize(array('token' => $token));
$url = '/?mailpoet&endpoint=queue&action=run&data=' .
base64_encode($data);
@@ -71,7 +71,7 @@ class CronHelper {
static function checkExecutionTimer($timer) {
$elapsed_time = microtime(true) - $timer;
if($elapsed_time >= self::daemon_execution_limit) {
if($elapsed_time >= self::DAEMON_EXECUTION_LIMIT) {
throw new \Exception(__('Maximum execution time reached.'));
}
}

View File

@@ -12,12 +12,15 @@ class Daemon {
public $daemon;
public $data;
public $refreshed_token;
const daemon_request_timeout = 5;
const STATUS_STOPPED = 'stopped';
const STATUS_STOPPING = 'stopping';
const STATUS_STARTED = 'started';
const STATUS_STARTING = 'starting';
const REQUEST_TIMEOUT = 5;
private $timer;
function __construct($data) {
if(empty($data)) $this->abortWithError(__('Invalid or missing cron data.'));
set_time_limit(0);
ignore_user_abort();
$this->daemon = CronHelper::getDaemon();
$this->token = CronHelper::createToken();
@@ -27,6 +30,7 @@ class Daemon {
function run() {
$daemon = $this->daemon;
set_time_limit(0);
if(!$daemon) {
$this->abortWithError(__('Daemon does not exist.'));
}
@@ -42,10 +46,11 @@ class Daemon {
$queue = new SendingQueue();
$queue->process($this->timer);
} catch(\Exception $e) {
// continue processing, no need to catch errors
}
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time < CronHelper::daemon_execution_limit) {
sleep(CronHelper::daemon_execution_limit - $elapsed_time);
if($elapsed_time < CronHelper::DAEMON_EXECUTION_LIMIT) {
sleep(CronHelper::DAEMON_EXECUTION_LIMIT - $elapsed_time);
}
// after each execution, re-read daemon data in case it was deleted or
// its status has changed
@@ -55,8 +60,8 @@ class Daemon {
}
$daemon['counter']++;
$this->abortIfStopped($daemon);
if($daemon['status'] === 'starting') {
$daemon['status'] = 'started';
if($daemon['status'] === self::STATUS_STARTING) {
$daemon['status'] = self::STATUS_STARTED;
}
$daemon['token'] = $this->token;
CronHelper::saveDaemon($daemon);
@@ -64,9 +69,9 @@ class Daemon {
}
function abortIfStopped($daemon) {
if($daemon['status'] === 'stopped') exit;
if($daemon['status'] === 'stopping') {
$daemon['status'] = 'stopped';
if($daemon['status'] === self::STATUS_STOPPED) exit;
if($daemon['status'] === self::STATUS_STOPPING) {
$daemon['status'] = self::STATUS_STOPPING;
CronHelper::saveDaemon($daemon);
exit;
}
@@ -77,7 +82,7 @@ class Daemon {
}
function callSelf() {
CronHelper::accessDaemon($this->token, self::daemon_request_timeout);
CronHelper::accessDaemon($this->token, self::REQUEST_TIMEOUT);
exit;
}
}

View File

@@ -23,7 +23,7 @@ class Supervisor {
// if the daemon is stopped, return its status and do nothing
if(!$this->force_run &&
isset($daemon['status']) &&
$daemon['status'] === 'stopped'
$daemon['status'] === Daemon::STATUS_STOPPED
) {
return $this->formatDaemonStatusMessage($daemon['status']);
@@ -31,17 +31,17 @@ class Supervisor {
$elapsed_time = time() - (int) $daemon['updated_at'];
// if it's been less than 40 seconds since last execution and we're not
// force-running the daemon, return its status and do nothing
if($elapsed_time < CronHelper::daemon_execution_timeout && !$this->force_run) {
if($elapsed_time < CronHelper::DAEMON_EXECUTION_TIMEOUT && !$this->force_run) {
return $this->formatDaemonStatusMessage($daemon['status']);
}
// if it's been less than 40 seconds since last execution, we are
// force-running the daemon and it's either being started or stopped,
// return its status and do nothing
elseif($elapsed_time < CronHelper::daemon_execution_timeout &&
elseif($elapsed_time < CronHelper::DAEMON_EXECUTION_TIMEOUT &&
$this->force_run &&
in_array($daemon['status'], array(
'stopping',
'starting'
Daemon::STATUS_STOPPING,
Daemon::STATUS_STARTING
))
) {
return $this->formatDaemonStatusMessage($daemon['status']);

View File

@@ -4,7 +4,6 @@ namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\Setting;
use MailPoet\Models\StatisticsNewsletters;
use MailPoet\Models\Subscriber;
@@ -20,9 +19,9 @@ class SendingQueue {
public $mta_config;
public $mta_log;
public $processing_method;
public $divider = '***MailPoet***';
private $timer;
const batch_size = 50;
const BATCH_SIZE = 50;
const DIVIDER = '***MailPoet***';
function __construct($timer = false) {
$this->mta_config = $this->getMailerConfig();
@@ -51,7 +50,7 @@ class SendingQueue {
$queue->subscribers->failed = array();
}
$mailer = $this->configureMailer($newsletter);
foreach(array_chunk($queue->subscribers->to_process, self::batch_size) as
foreach(array_chunk($queue->subscribers->to_process, self::BATCH_SIZE) as
$subscribers_ids) {
$subscribers = Subscriber::whereIn('id', $subscribers_ids)
->findArray();
@@ -94,8 +93,8 @@ class SendingQueue {
// render newsletter
list($rendered_newsletter, $queue->newsletter_rendered_body_hash) =
$this->renderNewsletter($newsletter);
// extract and replace links
$processed_newsletter = $this->processLinks(
// process link shortcodes, extract and save links in the database
$processed_newsletter = $this->processLinksAndShortcodes(
$this->joinObject($rendered_newsletter),
$newsletter['id'],
$queue->id
@@ -119,7 +118,7 @@ class SendingQueue {
function processBulkSubscribers($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$processed_newsletters[] =
$this->processNewsletter($newsletter, $subscriber, $queue);
$this->processNewsletterBeforeSending($newsletter, $subscriber, $queue);
if(!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletters[0]['subject'];
}
@@ -163,7 +162,7 @@ class SendingQueue {
function processIndividualSubscriber($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$this->checkSendingLimit();
$processed_newsletter = $this->processNewsletter($newsletter, $subscriber, $queue);
$processed_newsletter = $this->processNewsletterBeforeSending($newsletter, $subscriber, $queue);
if(!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletter['subject'];
}
@@ -202,21 +201,26 @@ class SendingQueue {
return array($rendered_newsletter, $rendered_newsletter_hash);
}
function processLinks($text, $newsletter_id, $queue_id) {
list($text, $processed_links) = Links::replace($text);
foreach($processed_links as $link) {
// save extracted and processed links
$newsletter_link = NewsletterLink::create();
$newsletter_link->newsletter_id = $newsletter_id;
$newsletter_link->queue_id = $queue_id;
$newsletter_link->hash = $link['hash'];
$newsletter_link->url = $link['url'];
$newsletter_link->save();
}
return $text;
function processLinksAndShortcodes($content, $newsletter_id, $queue_id) {
// process only link shortcodes
$shortcodes = new Shortcodes($newsletter = false, $subscriber = false, $queue_id);
$content = $shortcodes->replace(
$content,
$categories = array('link')
);
// extract and save links and link shortcodes
list($content, $processed_links) =
Links::process(
$content,
$links = false,
$process_link_shortcodes = true,
$queue_id
);
Links::save($processed_links, $newsletter_id, $queue_id);
return $content;
}
function processNewsletter($newsletter, $subscriber = false, $queue) {
function processNewsletterBeforeSending($newsletter, $subscriber = false, $queue) {
$data_for_shortcodes = array(
$newsletter['subject'],
$newsletter['body']['html'],
@@ -225,10 +229,11 @@ class SendingQueue {
$processed_newsletter = $this->replaceShortcodes(
$newsletter,
$subscriber,
$queue,
$this->joinObject($data_for_shortcodes)
);
if((boolean) Setting::getValue('tracking.enabled')) {
$processed_newsletter = $this->replaceLinks(
$processed_newsletter = Links::replaceSubscriberData(
$newsletter['id'],
$subscriber['id'],
$queue->id,
@@ -242,18 +247,11 @@ class SendingQueue {
return $newsletter;
}
function replaceLinks($newsletter_id, $subscriber_id, $queue_id, $body) {
return str_replace(
'[mailpoet_data]',
sprintf('%s-%s-%s', $newsletter_id, $subscriber_id, $queue_id),
$body
);
}
function replaceShortcodes($newsletter, $subscriber, $body) {
function replaceShortcodes($newsletter, $subscriber, $queue, $body) {
$shortcodes = new Shortcodes(
$newsletter,
$subscriber
$subscriber,
$queue
);
return $shortcodes->replace($body);
}
@@ -372,10 +370,10 @@ class SendingQueue {
}
private function joinObject($object = array()) {
return implode($this->divider, $object);
return implode(self::DIVIDER, $object);
}
private function splitObject($object = array()) {
return explode($this->divider, $object);
return explode(self::DIVIDER, $object);
}
}

View File

@@ -124,6 +124,10 @@ class Subscriber extends Model {
return false;
}
static function verifyToken($email, $token) {
return (self::generateToken($email) === $token);
}
static function subscribe($subscriber_data = array(), $segment_ids = array()) {
if(empty($subscriber_data) or empty($segment_ids)) {
return false;

View File

@@ -1,13 +1,16 @@
<?php
namespace MailPoet\Newsletter\Links;
use MailPoet\Models\NewsletterLink;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Util\Security;
class Links {
static function extract($text) {
const DATA_TAG = '[mailpoet_data]';
static function extract($content) {
// adopted from WP's wp_extract_urls() function & modified to work on hrefs
# match href=' or href="
# match href=' or href="
$regex = '#(?:href.*?=.*?)(["\']?)('
# match http://
. '(?:([\w-]+:)?//?)'
@@ -18,22 +21,24 @@ class Links {
. '(?:'
. '\([\w\d]+\)|'
. '(?:'
. '[^`!()\[\]{};:\'".,<>«»“”‘’\s]|'
. '[^`!()\[\]{}:;\'".,<>«»“”‘’\s]|'
. '(?:[:]\d+)?/?'
. ')+'
. ')'
. ')\\1#';
preg_match_all($regex, $text, $links);
$shortcodes = new Shortcodes();;
$shortcodes = $shortcodes->extract($text, $limit = array('subscription'));
// extract shortcodes with [link:*] format
$shortcodes = new Shortcodes();
$shortcodes = $shortcodes->extract($content, $categories = array('link'));
// extract links
preg_match_all($regex, $content, $links);
return array_merge(
array_unique($links[2]),
$shortcodes
);
}
static function replace($text, $links = false) {
$links = ($links) ? $links : self::extract($text);
static function process($content) {
$links = self::extract($content);
$processed_links = array();
foreach($links as $link) {
$hash = Security::generateRandomString(5);
@@ -42,13 +47,53 @@ class Links {
'url' => $link
);
$encoded_link = sprintf(
'%s/?mailpoet&endpoint=track&action=click&data=%s',
'%s/?mailpoet&endpoint=track&action=click&data=%s-%s',
home_url(),
'[mailpoet_data]-'.$hash
self::DATA_TAG,
$hash
);
$link_regex = '/' . preg_quote($link, '/') . '/';
$text = preg_replace($link_regex, $encoded_link, $text);
$content = preg_replace($link_regex, $encoded_link, $content);
}
return array(
$content,
$processed_links
);
}
static function replaceSubscriberData(
$newsletter_id,
$subscriber_id,
$queue_id,
$content
) {
$regex = sprintf('/data=(%s(?:-\w+)?)/', preg_quote(self::DATA_TAG));
preg_match_all($regex, $content, $links);
foreach($links[1] as $link) {
$hash = null;
if(preg_match('/-/', $link)) {
list(, $hash) = explode('-', $link);
}
$data = array(
'newsletter' => $newsletter_id,
'subscriber' => $subscriber_id,
'queue' => $queue_id,
'hash' => $hash
);
$data = rtrim(base64_encode(serialize($data)), '=');
$content = str_replace($link, $data, $content);
}
return $content;
}
static function save($links, $newsletter_id, $queue_id) {
foreach($links as $link) {
$newsletter_link = NewsletterLink::create();
$newsletter_link->newsletter_id = $newsletter_id;
$newsletter_link->queue_id = $queue_id;
$newsletter_link->hash = $link['hash'];
$newsletter_link->url = $link['url'];
$newsletter_link->save();
}
return array($text, $processed_links);
}
}

View File

@@ -57,14 +57,7 @@ class Text {
</tr>
</tbody>'
);
$blockquote->parent->insertChild(
array(
'tag_name' => 'br',
'self_close' => true,
'attributes' => array()
),
$blockquote->index() + 1
);
$blockquote = self::insertLineBreak($blockquote);
}
return $DOM->__toString();
}
@@ -75,8 +68,25 @@ class Text {
$paragraphs = $DOM->query('p');
if(!$paragraphs->count()) return $html;
foreach($paragraphs as $paragraph) {
// remove empty paragraphs
// process empty paragraphs
if(!trim($paragraph->html())) {
$next_element = ($paragraph->getNextSibling()) ?
trim($paragraph->getNextSibling()->text()) :
false;
$previous_element = ($paragraph->getPreviousSibling()) ?
trim($paragraph->getPreviousSibling()->text()) :
false;
$previous_element_tag = ($previous_element) ?
$paragraph->getPreviousSibling()->tag :
false;
// if previous or next paragraphs are empty OR previous paragraph
// is a heading, insert a break line
if (!$next_element ||
!$previous_element ||
(preg_match('/h\d+/', $previous_element_tag))
) {
$paragraph = self::insertLineBreak($paragraph);
}
$paragraph->remove();
continue;
}
@@ -91,9 +101,13 @@ class Text {
$paragraph->cellpadding = 0;
$next_element = $paragraph->getNextSibling();
// unless this is the last element in column, add double line breaks
$line_breaks = ($next_element && !$next_element->getInnerText()) ? '<br /><br />' : '';
$line_breaks = ($next_element && !trim($next_element->text())) ?
'<br /><br />' :
'';
// if this element is followed by a list, add single line break
$line_breaks = ($next_element && preg_match('/<li>/i', $next_element->getInnerText())) ? '<br />' : $line_breaks;
$line_breaks = ($next_element && preg_match('/<li>/i', $next_element->text())) ?
'<br />' :
$line_breaks;
$paragraph->html('
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;' . $style . '">
@@ -139,4 +153,16 @@ class Text {
static function removeLastLineBreak($html) {
return preg_replace('/(^)?(<br.*?\/?>)+$/i', '', $html);
}
static function insertLineBreak($element) {
$element->parent->insertChild(
array(
'tag_name' => 'br',
'self_close' => true,
'attributes' => array()
),
$element->index() + 1
);
return $element;
}
}

View File

@@ -1,6 +1,8 @@
<?php
namespace MailPoet\Newsletter\Renderer\PostProcess;
use MailPoet\Newsletter\Links\Links;
class OpenTracking {
static function process($template) {
$DOM = new \pQuery();
@@ -9,7 +11,7 @@ class OpenTracking {
$open_tracking_link = sprintf(
'<img alt="" class="" src="%s/%s"/>',
home_url(),
htmlentities('?mailpoet&endpoint=track&action=open&data=[mailpoet_data]')
esc_attr('?mailpoet&endpoint=track&action=open&data=' . Links::DATA_TAG)
);
$template->html($template->html() . $open_tracking_link);
return $DOM->__toString();

View File

@@ -0,0 +1,171 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Statistics\Track\Unsubscribes;
use MailPoet\Subscription\Url as SubscriptionUrl;
class Link {
/*
{
text: '<%= __('Unsubscribe') %>',-
shortcode: 'subscription:unsubscribe',
},
{
text: '<%= __('Manage subscription') %>',
shortcode: 'subscription:manage',
},
{
text: '<%= __('View in browser link') %>',
shortcode: 'newsletter:view_in_browser',
}
*/
static function process($action,
$default_value = false,
$newsletter,
$subscriber,
$queue = false
) {
switch($action) {
case 'subscription_unsubscribe':
$action = 'subscription_unsubscribe_url';
$url = self::processUrl(
$action,
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber)),
$queue
);
return sprintf(
'<a target="_blank" href="%s">%s</a>',
$url,
__('Unsubscribe')
);
break;
case 'subscription_unsubscribe_url':
return self::processUrl(
$action,
SubscriptionUrl::getUnsubscribeUrl($subscriber),
$queue
);
break;
case 'subscription_manage':
$url = self::processUrl(
$action = 'subscription_manage_url',
esc_attr(SubscriptionUrl::getManageUrl($subscriber)),
$queue
);
return sprintf(
'<a target="_blank" href="%s">%s</a>',
$url,
__('Manage subscription')
);
break;
case 'subscription_manage_url':
return self::processUrl(
$action,
SubscriptionUrl::getManageUrl($subscriber),
$queue
);
break;
case 'newsletter_view_in_browser':
$action = 'view_in_browser_url';
$url = esc_attr(self::getViewInBrowserUrl($newsletter, $subscriber, $queue));
$url = self::processUrl($action, $url, $queue);
return sprintf(
'<a target="_blank" href="%s">%s</a>',
$url,
__('View in your browser')
);
break;
case 'newsletter_view_in_browser_url':
$url = self::getViewInBrowserUrl($newsletter, $subscriber, $queue);
return self::processUrl($action, $url, $queue);
break;
default:
$shortcode = self::getShortcode($action);
$url = apply_filters(
'mailpoet_newsletter_shortcode_link',
$shortcode,
$newsletter,
$subscriber,
$queue
);
return ($url !== $shortcode) ?
self::processUrl($action, $url, $queue) :
false;
break;
}
}
static function getViewInBrowserUrl(
$newsletter,
$subscriber = false,
$queue = false
) {
$data = array(
'newsletter' => (isset($newsletter['id'])) ?
$newsletter['id'] :
$newsletter,
'subscriber' => (isset($subscriber['id'])) ?
$subscriber['id'] :
$subscriber,
'subscriber_token' => (isset($subscriber['id'])) ?
Subscriber::generateToken($subscriber['email']) :
false,
'queue' => (isset($queue['id'])) ?
$queue['id'] :
$queue
);
$data = rtrim(base64_encode(serialize($data)), '=');
return home_url() . '/?mailpoet&endpoint=view_in_browser&data=' . $data;
}
static function processUrl($action, $url, $queue) {
return ($queue !== false && (boolean) Setting::getValue('tracking.enabled')) ?
self::getShortcode($action) :
$url;
}
static function processShortcodeAction(
$shortcode_action, $newsletter, $subscriber, $queue
) {
switch($shortcode_action) {
case 'subscription_unsubscribe_url':
// track unsubscribe event
if((boolean) Setting::getValue('tracking.enabled')) {
$unsubscribe = new Unsubscribes();
$unsubscribe->track($subscriber['id'], $queue['id'], $newsletter['id']);
}
$url = SubscriptionUrl::getUnsubscribeUrl($subscriber);
break;
case 'subscription_manage_url':
$url = SubscriptionUrl::getManageUrl($subscriber);
break;
case 'newsletter_view_in_browser_url':
$url = Link::getViewInBrowserUrl($newsletter, $subscriber, $queue);
break;
default:
$shortcode = self::getShortcode($shortcode_action);
$url = apply_filters(
'mailpoet_newsletter_shortcode_link',
$shortcode,
$newsletter,
$subscriber,
$queue
);
$url = ($url !== $shortcode_action) ? $url : false;
break;
}
return $url;
}
private static function getShortcode($action) {
return sprintf('[link:%s]', $action);
}
}

View File

@@ -2,6 +2,7 @@
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\SendingQueue;
use MailPoet\Newsletter\Shortcodes\ShortcodesHelper;
require_once( ABSPATH . "wp-includes/pluggable.php" );
@@ -26,29 +27,26 @@ class Newsletter {
{
text: '<%= __('Issue number') %>',
shortcode: 'newsletter:number',
},
{
text: '<%= __('View in browser link') %>',
shortcode: 'newsletter:view_in_browser',
}
*/
static function process($action,
$default_value = false,
$newsletter, $subscriber = false, $text) {
if(is_object($newsletter)) {
$newsletter = $newsletter->asArray();
}
$newsletter,
$subscriber,
$queue = false,
$content
) {
switch($action) {
case 'subject':
return ($newsletter) ? $newsletter['subject'] : false;
break;
case 'total':
return substr_count($text, 'data-post-id');
return substr_count($content, 'data-post-id');
break;
case 'post_title':
preg_match_all('/data-post-id="(\w+)"/ism', $text, $posts);
preg_match_all('/data-post-id="(\d+)"/ism', $content, $posts);
$post_ids = array_unique($posts[1]);
$latest_post = self::getLatestWPPost($post_ids);
return ($latest_post) ? $latest_post['post_title'] : false;
@@ -63,14 +61,6 @@ class Newsletter {
return ++$sent_newsletters;
break;
case 'view_in_browser':
return '<a href="#TODO">'.__('View in your browser').'</a>';
break;
case 'view_in_browser_url':
return '#TODO';
break;
default:
return false;
break;

View File

@@ -1,69 +0,0 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\Setting;
use MailPoet\Subscription\Url as SubscriptionUrl;
class Subscription {
/*
{
text: '<%= __('Unsubscribe') %>',-
shortcode: 'subscription:unsubscribe',
},
{
text: '<%= __('Manage subscriptions') %>',
shortcode: 'subscription:manage',
},
*/
static function process(
$action,
$default_value = false,
$newsletter = false,
$subscriber = false,
$text = false,
$shortcode
) {
switch($action) {
case 'unsubscribe':
return '<a target="_blank" href="'.
self::getShortcodeUrl(
$shortcode,
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber))
)
.'">'.__('Unsubscribe').'</a>';
break;
case 'unsubscribe_url':
return self::getShortcodeUrl(
$shortcode,
SubscriptionUrl::getUnsubscribeUrl($subscriber)
);
break;
case 'manage':
return '<a target="_blank" href="'.
self::getShortcodeUrl(
$shortcode,
esc_attr(SubscriptionUrl::getManageUrl($subscriber))
)
.'">'.__('Manage subscription').'</a>';
break;
case 'manage_url':
return self::getShortcodeUrl(
$shortcode,
SubscriptionUrl::getManageUrl($subscriber)
);
break;
default:
return false;
break;
}
}
static function getShortcodeUrl($shortcode, $url) {
return ((boolean) Setting::getValue('tracking.enabled')) ?
$shortcode :
$url;
}
}

View File

@@ -28,10 +28,12 @@ class User {
shortcode: 'user:count',
}
*/
static function process($action, $default_value, $newsletter = false, $subscriber) {
if(is_object($subscriber)) {
$subscriber = $subscriber->asArray();
}
static function process(
$action,
$default_value,
$newsletter = false,
$subscriber
) {
switch($action) {
case 'firstname':
return ($subscriber) ? $subscriber['first_name'] : $default_value;

View File

@@ -4,61 +4,86 @@ namespace MailPoet\Newsletter\Shortcodes;
class Shortcodes {
public $newsletter;
public $subscriber;
public $queue;
function __construct(
$newsletter = false,
$subscriber = false
$subscriber = false,
$queue = false
) {
$this->newsletter = $newsletter;
$this->subscriber = $subscriber;
$this->newsletter = (is_object($newsletter)) ?
$newsletter->asArray() :
$newsletter;
$this->subscriber = (is_object($subscriber)) ?
$subscriber->asArray() :
$subscriber;
$this->queue = (is_object($queue)) ?
$queue->asArray() :
$queue;
}
function extract($text, $limit = false) {
$limit = (is_array($limit)) ? implode('|', $limit) : false;
function extract($content, $categories= false) {
$categories = (is_array($categories)) ? implode('|', $categories) : false;
$regex = sprintf(
'/\[%s:.*?\]/ism',
($limit) ? '(?:' . $limit . ')' : '(?:\w+)'
($categories) ? '(?:' . $categories . ')' : '(?:\w+)'
);
preg_match_all($regex, $text, $shortcodes);
preg_match_all($regex, $content, $shortcodes);
return array_unique($shortcodes[0]);
}
function match($shortcode) {
preg_match(
'/\[(?P<type>\w+):(?P<action>\w+)(?:.*?default:(?P<default>.*?))?\]/',
'/\[(?P<category>\w+)?:(?P<action>\w+)(?:.*?\|.*?default:(?P<default>.*?))?\]/',
$shortcode,
$match
);
return $match;
}
function process($shortcodes, $text) {
function process($shortcodes, $content = false) {
$processed_shortcodes = array_map(
function($shortcode) use($text) {
function($shortcode) use($content) {
$shortcode_details = $this->match($shortcode);
$shortcode_type = ucfirst($shortcode_details['type']);
$shortcode_action = $shortcode_details['action'];
$shortcode_category = isset($shortcode_details['category']) ?
ucfirst($shortcode_details['category']) :
false;
$shortcode_action = isset($shortcode_details['action']) ?
$shortcode_details['action'] :
false;
$shortcode_class =
__NAMESPACE__ . '\\Categories\\' . $shortcode_type;
__NAMESPACE__ . '\\Categories\\' . $shortcode_category;
$shortcode_default_value = isset($shortcode_details['default'])
? $shortcode_details['default'] : false;
if(!class_exists($shortcode_class)) return false;
if(!class_exists($shortcode_class)) {
$custom_shortcode = apply_filters(
'mailpoet_newsletter_shortcode',
$shortcode,
$this->newsletter,
$this->subscriber,
$this->queue,
$content
);
return ($custom_shortcode === $shortcode) ?
false :
$custom_shortcode;
}
return $shortcode_class::process(
$shortcode_action,
$shortcode_default_value,
$this->newsletter,
$this->subscriber,
$text,
$shortcode
$this->queue,
$content
);
}, $shortcodes);
return $processed_shortcodes;
}
function replace($text) {
$shortcodes = $this->extract($text);
$processed_shortcodes = $this->process($shortcodes, $text);
function replace($content, $categories = false) {
$shortcodes = $this->extract($content, $categories);
$processed_shortcodes = $this->process($shortcodes, $content);
$shortcodes = array_intersect_key($shortcodes, $processed_shortcodes);
return str_replace($shortcodes, $processed_shortcodes, $text);
return str_replace($shortcodes, $processed_shortcodes, $content);
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace MailPoet\Newsletter\Viewer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
class ViewInBrowser {
public $data;
function __construct($data) {
$this->data = $data;
}
function view($data = false) {
$data = ($data) ? $data : $this->data;
$newsletter = ($data['newsletter'] !== false) ?
Newsletter::findOne($data['newsletter']) :
false;
if(!$newsletter) $this->abort();
$subscriber = ($data['subscriber'] !== false) ?
$this->verifySubscriber($data['subscriber'], $data['subscriber_token']) :
false;
$queue = ($data['queue'] !== false) ?
SendingQueue::findOne($data['queue']) :
false;
$rendered_newsletter =
$this->getAndRenderNewsletter($newsletter, $subscriber, $queue);
header('Content-Type: text/html; charset=utf-8');
echo $rendered_newsletter;
exit;
}
function verifySubscriber($subscriber_id, $subscriber_token) {
$subscriber = Subscriber::findOne($subscriber_id);
if(!$subscriber ||
!Subscriber::verifyToken($subscriber->email, $subscriber_token)
) {
return false;
}
return $subscriber;
}
function getAndRenderNewsletter($newsletter, $subscriber, $queue) {
if($queue) {
$newsletter_body = json_decode($queue->newsletter_rendered_body, true);
} else {
$renderer = new Renderer($newsletter->asArray());
$newsletter_body = $renderer->render();
}
$shortcodes = new Shortcodes(
$newsletter,
$subscriber,
$queue
);
$rendered_newsletter = $shortcodes->replace($newsletter_body['html']);
if($queue && (boolean) Setting::getValue('tracking.enabled')) {
$rendered_newsletter = Links::replaceSubscriberData(
$newsletter->id,
$subscriber->id,
$queue->id,
$rendered_newsletter
);
}
return $rendered_newsletter;
}
private function abort() {
header('HTTP/1.0 404 Not Found');
exit;
}
}

View File

@@ -1,24 +1,23 @@
<?php
namespace MailPoet\Router;
use MailPoet\Config\Shortcodes;
use MailPoet\Listing;
use MailPoet\Mailer\API\MailPoet;
use MailPoet\Models\Newsletter;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Models\NewsletterTemplate;
use MailPoet\Models\NewsletterSegment;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Models\SendingQueue;
use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Newsletter\Shortcodes\Categories\Link;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Newsletters {
function __construct() {
}
@@ -134,15 +133,32 @@ class Newsletters {
return false;
}
function render($data = array()) {
function showPreview($data = array()) {
if(!isset($data['body'])) {
return false;
return array(
'result' => false,
'errors' => array(__('Newsletter data is missing.'))
);
}
$renderer = new Renderer($data);
$rendered_newsletter = $renderer->render();
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes($data);
$rendered_newsletter = $shortcodes->replace($rendered_newsletter['html']);
return array('rendered_body' => $rendered_newsletter);
$newsletter_id = (isset($data['id'])) ? (int) $data['id'] : 0;
$newsletter = Newsletter::findOne($newsletter_id);
if (!$newsletter) {
return array(
'result' => false,
'errors' => array(__('Newsletter could not be read.'))
);
}
$newsletter->body = $data['body'];
$newsletter->save();
$wp_user =wp_get_current_user();
$subscriber = Subscriber::where('email', $wp_user->data->user_email)
->findOne();
$subscriber = ($subscriber) ? $subscriber->asArray() : $subscriber;
$preview_url = Link::getViewInBrowserUrl($data, $subscriber);
return array(
'result' => true,
'data' => array('url' => $preview_url)
);
}
function sendPreview($data = array()) {

View File

@@ -1,67 +1,87 @@
<?php
namespace MailPoet\Statistics\Track;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\StatisticsClicks;
use MailPoet\Models\Subscriber;
use MailPoet\Subscription\Url as SubscriptionUrl;
use MailPoet\Newsletter\Shortcodes\Categories\Link;
if(!defined('ABSPATH')) exit;
class Clicks {
public $url;
public $data;
function __construct($url) {
$this->url = $url;
function __construct($data) {
$this->data = $data;
}
function track($url = false) {
$url = ($url) ? $url : $this->url;
if(!preg_match('/\d+-\d+-\d+-[a-zA-Z0-9]/', $url)) $this->abort();
list ($newsletter_id, $subscriber_id, $queue_id, $hash) = explode('-', $url);
$subscriber = Subscriber::findOne($subscriber_id);
$link = NewsletterLink::where('hash', $hash)
->findOne();
if(!$subscriber) return;
if(!$link) $this->abort();
$statistics = StatisticsClicks::where('link_id', $link->id)
->where('subscriber_id', $subscriber_id)
->where('newsletter_id', $newsletter_id)
->where('queue_id', $queue_id)
function track($data = false) {
$data = ($data) ? $data : $this->data;
$newsletter = $this->getNewsletter($data['newsletter']);
$subscriber = $this->getSubscriber($data['subscriber']);
$queue = $this->getQueue($data['queue']);
$link = $this->getLink($data['hash']);
if(!$subscriber || !$newsletter || !$link || !$queue) {
$this->abort();
}
$statistics = StatisticsClicks::where('link_id', $link['id'])
->where('subscriber_id', $subscriber['id'])
->where('newsletter_id', $newsletter['id'])
->where('queue_id', $queue['id'])
->findOne();
if(!$statistics) {
// track open action in case it did not register
$opens = new Opens($url, $display_image = false);
$opens->track();
// track open event in case it did not register
$open = new Opens($data, $display_image = false);
$open->track();
$statistics = StatisticsClicks::create();
$statistics->newsletter_id = $newsletter_id;
$statistics->link_id = $link->id;
$statistics->subscriber_id = $subscriber_id;
$statistics->queue_id = $queue_id;
$statistics->newsletter_id = $newsletter['id'];
$statistics->link_id = $link['id'];
$statistics->subscriber_id = $subscriber['id'];
$statistics->queue_id = $queue['id'];
$statistics->count = 1;
$statistics->save();
} else {
$statistics->count++;
$statistics->save();
}
$url = (preg_match('/\[subscription:.*?\]/', $link->url)) ?
$this->processSubscriptionUrl($link->url, $subscriber, $queue_id, $newsletter_id) :
$link->url;
$url = $this->processUrl($link['url'], $newsletter, $subscriber, $queue);
header('Location: ' . $url, true, 302);
exit;
}
function processSubscriptionUrl($url, $subscriber, $queue_id, $newsletter_id) {
preg_match('/\[subscription:(.*?)\]/', $url, $match);
$action = $match[1];
if(preg_match('/unsubscribe/', $action)) {
$url = SubscriptionUrl::getUnsubscribeUrl($subscriber);
// track unsubscribe action
$unsubscribes = new Unsubscribes();
$unsubscribes->track($subscriber->id, $queue_id, $newsletter_id);
}
if(preg_match('/manage/', $action)) {
$url = SubscriptionUrl::getManageUrl($subscriber);
function getNewsletter($newsletter_id) {
$newsletter = Newsletter::findOne($newsletter_id);
return ($newsletter) ? $newsletter->asArray() : $newsletter;
}
function getSubscriber($subscriber_id) {
$subscriber = Subscriber::findOne($subscriber_id);
return ($subscriber) ? $subscriber->asArray() : $subscriber;
}
function getQueue($queue_id) {
$queue = SendingQueue::findOne($queue_id);
return ($queue) ? $queue->asArray() : $queue;
}
function getLink($hash) {
$link = NewsletterLink::where('hash', $hash)
->findOne();
return ($link) ? $link->asArray() : $link;
}
function processUrl($url, $newsletter, $subscriber, $queue) {
if(preg_match('/\[link:(?P<action>.*?)\]/', $url, $shortcode)) {
if(!$shortcode['action']) $this->abort();
$url = Link::processShortcodeAction(
$shortcode['action'],
$newsletter,
$subscriber,
$queue
);
if(!$url) $this->abort();
}
return $url;
}
@@ -70,4 +90,4 @@ class Clicks {
header('HTTP/1.0 404 Not Found');
exit;
}
}
}

View File

@@ -17,22 +17,20 @@ class Opens {
function track($data = false) {
$data = ($data) ? $data : $this->data;
if(!preg_match('/\d+-\d+-\d+/', $data)) $this->abort();
list ($newsletter_id, $subscriber_id, $queue_id) = explode('-', $data);
$subscriber = Subscriber::findOne($subscriber_id);
$subscriber = Subscriber::findOne($data['subscriber']);
if(!$subscriber) return;
$statistics = StatisticsOpens::where('subscriber_id', $subscriber_id)
->where('newsletter_id', $newsletter_id)
->where('queue_id', $queue_id)
$statistics = StatisticsOpens::where('subscriber_id', $subscriber->id)
->where('newsletter_id', $data['newsletter'])
->where('queue_id', $data['queue'])
->findOne();
if(!$statistics) {
$statistics = StatisticsOpens::create();
$statistics->newsletter_id = $newsletter_id;
$statistics->subscriber_id = $subscriber_id;
$statistics->queue_id = $queue_id;
$statistics->newsletter_id = $data['newsletter'];
$statistics->subscriber_id = $data['subscriber'];
$statistics->queue_id = $data['queue'];
$statistics->save();
}
if ($this->display_image) {
if($this->display_image) {
// return 1x1 pixel transparent gif image
header('Content-Type: image/gif');
echo "\x47\x49\x46\x38\x37\x61\x1\x0\x1\x0\x80\x0\x0\xfc\x6a\x6c\x0\x0\x0\x2c\x0\x0\x0\x0\x1\x0\x1\x0\x0\x2\x2\x44\x1\x0\x3b";

View File

@@ -45,7 +45,7 @@ class Url {
$params = array(
'endpoint=subscription',
'action='.$action,
'data='.base64_encode(serialize($data))
'data='.rtrim(base64_encode(serialize($data)), '=')
);
// add parameters

View File

@@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
use \MailPoet\Config\Initializer;
/*
* Plugin Name: MailPoet
* Version: 0.0.26
* Version: 0.0.27
* Plugin URI: http://www.mailpoet.com
* Description: MailPoet Newsletters.
* Author: MailPoet
@@ -22,7 +22,7 @@ use \MailPoet\Config\Initializer;
require 'vendor/autoload.php';
define('MAILPOET_VERSION', '0.0.26');
define('MAILPOET_VERSION', '0.0.27');
$initializer = new Initializer(array(
'file' => __FILE__,

View File

@@ -22,7 +22,7 @@
"blocks": [
{
"type": "header",
"text": "Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>",
"text": "Display problems?&nbsp;<a href=\"[link:newsletter_view_in_browser_url]\">View it in your browser</a>",
"styles": {
"block": {
"backgroundColor": "#ffffff"
@@ -1154,7 +1154,7 @@
"blocks": [
{
"type": "footer",
"text": "<p>You are receiving this email because you opted in on our website. <a href=\"[subscription:manage_url]\">Update your preferences</a> or <a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a><a href=\"[subscription:manage_url]\"> </a><br />123 Maple Avenue<br />93102<br />Oakland, California </p>",
"text": "<p>You are receiving this email because you opted in on our website. <a href=\"[link:subscription_manage_url]\">Update your preferences</a> or <a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a><a href=\"[link:subscription_manage_url]\"> </a><br />123 Maple Avenue<br />93102<br />Oakland, California </p>",
"styles": {
"block": {
"backgroundColor": "transparent"

View File

@@ -1,11 +1,13 @@
<?php
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Subscriber;
use MailPoet\Config\Populator;
use MailPoet\Subscription\Url as SubscriptionUrl;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Shortcodes\Categories\Date;
require_once(ABSPATH . 'wp-includes/pluggable.php');
require_once(ABSPATH . 'wp-admin/includes/user.php');
class ShortcodesTest extends MailPoetTest {
public $rendered_newsletter;
@@ -15,109 +17,206 @@ class ShortcodesTest extends MailPoetTest {
function _before() {
$populator = new Populator();
$populator->up();
$this->wp_user = $this->_createWPUser();
$this->WP_user = $this->_createWPUser();
$this->WP_post = $this->_createWPPost();
$this->subscriber = $this->_createSubscriber();
$this->newsletter = array(
'subject' => 'some subject',
'type' => 'notification',
'id' => 2
);
$this->post_data = array(
'post_title' => 'Sample Post',
'post_content' => 'contents',
'post_status' => 'publish',
);
$this->post_id = wp_insert_post($this->post_data);
$this->rendered_newsletter = "
Hello [user:displayname | default:member].
Your first name is [user:firstname | default:First Name].
Your last name is [user:lastname | default:Last Name].
Thank you for subscribing with [user:email].
We already have [user:count] users.
<h1 data-post-id=\"1\">some post</h1>
<h1 data-post-id=\"{$this->post_id}\">another post</h1>
There are [newsletter:total] posts in this newsletter.
You are reading [newsletter:subject].
The latest post in this newsletter is called [newsletter:post_title].
The issue number of this newsletter is [newsletter:number].
Date: [date:d].
Ordinal date: [date:dordinal].
Date text: [date:dtext].
Month: [date:m].
Month text: [date:mtext].
Year: [date:y]
You can unsubscribe here: [subscription:unsubscribe_url].
Manage your subscription here: [subscription:manage_url].
View this newsletter in browser: [newsletter:view_in_browser_url].";
$this->shortcodes_object = new MailPoet\Newsletter\Shortcodes\Shortcodes(
$this->newsletter,
$this->subscriber
);
Setting::setValue('tracking.enabled', false);
}
function testItCanExtractShortcodes() {
$shortcodes = $this->shortcodes_object->extract($this->rendered_newsletter);
expect(count($shortcodes))->equals(18);
$content = '[category:action] [notshortcode]';
$shortcodes = $this->shortcodes_object->extract($content);
expect(count($shortcodes))->equals(1);
}
function testItCanProcessShortcodes() {
$wp_user = get_userdata($this->wp_user);
function testItCanExtractOnlySelectShortcodes() {
$content = '[link:action] [newsletter:action]';
$limit = array('link');
$shortcodes = $this->shortcodes_object->extract($content, $limit);
expect(count($shortcodes))->equals(1);
expect(preg_match('/link/', $shortcodes[0]))->equals(1);
}
$queue = SendingQueue::create();
$queue->newsletter_id = $this->newsletter['id'];
$queue->save();
$issue_number = 1;
function testItCanMatchShortcodeDetails() {
$shortcodes_object = $this->shortcodes_object;
$content = '[category:action]';
$details = $shortcodes_object->match($content);
expect($details['category'])->equals('category');
expect($details['action'])->equals('action');
$content = '[category:action|default:default_value]';
$details = $shortcodes_object->match($content);
expect($details['category'])->equals('category');
expect($details['action'])->equals('action');
expect($details['default'])->equals('default_value');
$content = '[category:action|default]';
$details = $shortcodes_object->match($content);
expect($details)->isEmpty();
$content = '[category|default:default_value]';
$details = $shortcodes_object->match($content);
expect($details)->isEmpty();
}
$number_of_posts = 2;
function testItCanProcessCustomShortcodes() {
$shortcodes_object = $this->shortcodes_object;
$shortcode = array('[some:shortcode]');
$result = $shortcodes_object->process($shortcode);
expect($result[0])->false();
add_filter('mailpoet_newsletter_shortcode', function (
$shortcode, $newsletter, $subscriber, $queue, $content) {
if($shortcode === '[some:shortcode]') return 'success';
}, 10, 5);
$result = $shortcodes_object->process($shortcode);
expect($result[0])->equals('success');
}
function testItCanProcessDateShortcodes() {
$date = new \DateTime('now');
$subscriber_count = Subscriber::count();
$newsletter_with_replaced_shortcodes = $this->shortcodes_object->replace(
$this->rendered_newsletter
expect(Date::process('d'))->equals($date->format('d'));
expect(Date::process('dordinal'))->equals($date->format('dS'));
expect(Date::process('dtext'))->equals($date->format('D'));
expect(Date::process('m'))->equals($date->format('m'));
expect(Date::process('mtext'))->equals($date->format('F'));
expect(Date::process('y'))->equals($date->format('Y'));
}
function testItCanProcessNewsletterShortcodes() {
$shortcodes_object = $this->shortcodes_object;
$content =
'<a data-post-id="' . $this->WP_post . '" href="#">latest post</a>' .
'<a data-post-id="10" href="#">another post</a>' .
'<a href="#">not post</a>';
$result =
$shortcodes_object->process(array('[newsletter:subject]'));
expect($result[0])->equals($this->newsletter['subject']);
$result =
$shortcodes_object->process(array('[newsletter:total]'), $content);
expect($result[0])->equals(2);
$result =
$shortcodes_object->process(array('[newsletter:post_title]'));
$wp_post = get_post($this->WP_post);
expect($result['0'])->equals($wp_post->post_title);
$result =
$shortcodes_object->process(array('[newsletter:number]'));
expect($result['0'])->equals(1);
$queue = $this->_createQueue();
$result =
$shortcodes_object->process(array('[newsletter:number]'));
expect($result['0'])->equals(2);
}
function testItCanProcessUserShortcodes() {
$shortcodes_object = $this->shortcodes_object;
$result =
$shortcodes_object->process(array('[user:firstname]'));
expect($result[0])->equals($this->subscriber->first_name);
$result =
$shortcodes_object->process(array('[user:lastname]'));
expect($result[0])->equals($this->subscriber->last_name);
$result =
$shortcodes_object->process(array('[user:displayname]'));
expect($result[0])->equals($this->WP_user->user_login);
$subscribers = Subscriber::where('status', 'subscribed')
->findMany();
$subscriber_count = count($subscribers);
$result =
$shortcodes_object->process(array('[user:count]'));
expect($result[0])->equals($subscriber_count);
$this->subscriber->status = 'unsubscribed';
$this->subscriber->save();
$result =
$shortcodes_object->process(array('[user:count]'));
expect($result[0])->equals(--$subscriber_count);
}
function testItCanProcessLinkShortcodes() {
$shortcodes_object = $this->shortcodes_object;
$result =
$shortcodes_object->process(array('[link:subscription_unsubscribe]'));
expect(preg_match('/^<a.*?\/a>$/', $result['0']))->equals(1);
expect(preg_match('/action=unsubscribe/', $result['0']))->equals(1);
$result =
$shortcodes_object->process(array('[link:subscription_unsubscribe_url]'));
expect(preg_match('/^http.*?action=unsubscribe/', $result['0']))->equals(1);
$result =
$shortcodes_object->process(array('[link:subscription_manage]'));
expect(preg_match('/^<a.*?\/a>$/', $result['0']))->equals(1);
expect(preg_match('/action=manage/', $result['0']))->equals(1);
$result =
$shortcodes_object->process(array('[link:subscription_manage_url]'));
expect(preg_match('/^http.*?action=manage/', $result['0']))->equals(1);
$result =
$shortcodes_object->process(array('[link:newsletter_view_in_browser]'));
expect(preg_match('/^<a.*?\/a>$/', $result['0']))->equals(1);
expect(preg_match('/endpoint=view_in_browser/', $result['0']))->equals(1);
$result =
$shortcodes_object->process(array('[link:newsletter_view_in_browser_url]'));
expect(preg_match('/^http.*?endpoint=view_in_browser/', $result['0']))->equals(1);
}
function testItReturnsShortcodeWhenTrackingEnabled() {
$shortcodes_object = $this->shortcodes_object;
$shortcode = '[link:subscription_unsubscribe_url]';
$result =
$shortcodes_object->process(array($shortcode));
expect(preg_match('/^http.*?action=unsubscribe/', $result['0']))->equals(1);
Setting::setValue('tracking.enabled', true);
$shortcodes = array(
'[link:subscription_unsubscribe]',
'[link:subscription_unsubscribe_url]',
'[link:subscription_manage]',
'[link:subscription_manage_url]',
'[link:newsletter_view_in_browser]',
'[link:newsletter_view_in_browser_url]'
);
// tracking function only works during sending, so queue object must not be false
$shortcodes_object->queue = true;
$result =
$shortcodes_object->process($shortcodes);
// all returned shortcodes must end with url
$result = join(',', $result);
expect(substr_count($result, '_url'))->equals(count($shortcodes));
}
$unsubscribe_url = SubscriptionUrl::getUnsubscribeUrl($this->subscriber);
$manage_url = SubscriptionUrl::getManageUrl($this->subscriber);
$view_in_browser_url = '#TODO';
function testItCanProcessCustomLinkShortcodes() {
$shortcodes_object = $this->shortcodes_object;
$shortcode = '[link:shortcode]';
$result = $shortcodes_object->process(array($shortcode));
expect($result[0])->false();
add_filter('mailpoet_newsletter_shortcode_link', function (
$shortcode, $newsletter, $subscriber, $queue) {
if($shortcode === '[link:shortcode]') return 'success';
}, 10, 4);
$result = $shortcodes_object->process(array($shortcode));
expect($result[0])->equals('success');
Setting::setValue('tracking.enabled', true);
// tracking function only works during sending, so queue object must not be false
$shortcodes_object->queue = true;
$result = $shortcodes_object->process(array($shortcode));
expect($result[0])->equals($shortcode);
}
expect($newsletter_with_replaced_shortcodes)->equals("
Hello {$wp_user->user_login}.
Your first name is {$this->subscriber->first_name}.
Your last name is {$this->subscriber->last_name}.
Thank you for subscribing with {$this->subscriber->email}.
We already have {$subscriber_count} users.
<h1 data-post-id=\"1\">some post</h1>
<h1 data-post-id=\"{$this->post_id}\">another post</h1>
There are {$number_of_posts} posts in this newsletter.
You are reading {$this->newsletter['subject']}.
The latest post in this newsletter is called {$this->post_data['post_title']}.
The issue number of this newsletter is {$issue_number}.
Date: {$date->format('d')}.
Ordinal date: {$date->format('dS')}.
Date text: {$date->format('D')}.
Month: {$date->format('m')}.
Month text: {$date->format('F')}.
Year: {$date->format('Y')}
You can unsubscribe here: {$unsubscribe_url}.
Manage your subscription here: {$manage_url}.
View this newsletter in browser: {$view_in_browser_url}.");
}
function _createWPPost() {
$data = array(
'post_title' => 'Sample Post',
'post_content' => 'contents',
'post_status' => 'publish',
);
return wp_insert_post($data);
}
function _createWPUser() {
$wp_user = wp_create_user('phoenix_test_user', 'pass', 'phoenix@test.com');
if(is_wp_error($wp_user)) {
$wp_user = get_user_by('login', 'phoenix_test_user');
$wp_user = $wp_user->ID;
}
return $wp_user;
$WP_user = wp_create_user('phoenix_test_user', 'pass', 'phoenix@test.com');
$WP_user = get_user_by('login', 'phoenix_test_user');
return $WP_user;
}
function _createSubscriber() {
@@ -128,15 +227,25 @@ class ShortcodesTest extends MailPoetTest {
'last_name' => 'Trump',
'email' => 'mister@trump.com',
'status' => Subscriber::STATUS_SUBSCRIBED,
'wp_user_id' => $this->wp_user
'WP_user_id' => $this->WP_user->ID
)
);
$subscriber->save();
return Subscriber::findOne($subscriber->id);
}
function _createQueue() {
$queue = SendingQueue::create();
$queue->newsletter_id = $this->newsletter['id'];
$queue->status = 'completed';
$queue->save();
return $queue;
}
function _after() {
Subscriber::deleteMany();
wp_delete_post($this->post_id, true);
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
wp_delete_post($this->WP_post, true);
wp_delete_user($this->WP_user->ID);
}
}

View File

@@ -320,6 +320,7 @@
'customFieldsWindowTitle': __('Select a shortcode'),
'unsubscribeLinkMissing': __('All newsletters must include an "unsubscribe" link. Add a footer widget to your newsletter to continue.'),
'newsletterPreviewEmailMissing': __('Please enter an email where newsletter preview should be sent to.'),
'newsletterPreviewFailed': __('Preview failed. Pleae check console log.'),
'newsletterPreviewSent': __('Newsletter preview email has been successfully sent!'),
'newsletterPreviewFailedToSend': __('Attempt to send a newsletter preview email failed. Please verify that your sending method is configured correctly try again.'),
'templateNameMissing': __('Please add a template name'),
@@ -1024,7 +1025,7 @@
},
},
footer: {
text: '<a href="[subscription:unsubscribe_url]"><%= __('Unsubscribe') %></a> | <a href="[subscription:manage_url]"><%= __('Manage subscription') %></a><br /><b><%= __('Add your postal address here!') %></b>',
text: '<a href="[link:subscription_unsubscribe_url]"><%= __('Unsubscribe') %></a> | <a href="[link:subscription_manage_url]"><%= __('Manage subscription') %></a><br /><b><%= __('Add your postal address here!') %></b>',
styles: {
block: {
backgroundColor: 'transparent',
@@ -1149,7 +1150,7 @@
},
header: {
text: '<%= __('Display problems?') %>&nbsp;'+
'<a href="[newsletter:view_in_browser_url]"><%= __('View it in your browser') %></a>',
'<a href="[link:newsletter_view_in_browser_url]"><%= __('View it in your browser') %></a>',
styles: {
block: {
backgroundColor: 'transparent',
@@ -1239,15 +1240,15 @@
'<%= __('Links') %>': [
{
text: '<%= __('Unsubscribe link') %>',
shortcode: 'subscription:unsubscribe',
shortcode: 'link:subscription_unsubscribe',
},
{
text: '<%= __('Edit subscription page link') %>',
shortcode: 'subscription:manage',
shortcode: 'link:subscription_manage',
},
{
text: '<%= __('View in browser link') %>',
shortcode: 'newsletter:view_in_browser',
shortcode: 'link:newsletter_view_in_browser',
}
],
<% if customFields %>