- Implements link extraction, processing, replacement and saving

This commit is contained in:
Vlad
2016-04-13 20:07:01 -04:00
parent 809be415c5
commit f1b955d74a
8 changed files with 304 additions and 128 deletions

View File

@ -68,35 +68,37 @@ class Initializer {
'SET TIME_ZONE = "' . Env::$db_timezone_offset. '"'
));
$subscribers = Env::$db_prefix . 'subscribers';
$settings = Env::$db_prefix . 'settings';
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$segments = Env::$db_prefix . 'segments';
$forms = Env::$db_prefix . 'forms';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$custom_fields = Env::$db_prefix . 'custom_fields';
$subscribers = Env::$db_prefix . 'subscribers';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$sending_queues = Env::$db_prefix . 'sending_queues';
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields';
$newsletter_option = Env::$db_prefix . 'newsletter_option';
$sending_queues = Env::$db_prefix . 'sending_queues';
$newsletter_statistics = Env::$db_prefix . 'newsletter_statistics';
$newsletter_links = Env::$db_prefix . 'newsletter_links';
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SETTINGS_TABLE', $settings);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_SEGMENTS_TABLE', $segments);
define('MP_FORMS_TABLE', $forms);
define('MP_CUSTOM_FIELDS_TABLE', $custom_fields);
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
define('MP_CUSTOM_FIELDS_TABLE', $custom_fields);
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
define('MP_NEWSLETTER_LINKS_TABLE', $newsletter_links);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
}
function runMigrator() {

View File

@ -10,19 +10,20 @@ class Migrator {
$this->prefix = Env::$db_prefix;
$this->charset = Env::$db_charset;
$this->models = array(
'subscribers',
'segments',
'settings',
'custom_fields',
'sending_queues',
'subscribers',
'subscriber_segment',
'subscriber_custom_field',
'newsletters',
'newsletter_templates',
'segments',
'subscriber_segment',
'newsletter_segment',
'custom_fields',
'subscriber_custom_field',
'newsletter_option_fields',
'newsletter_option',
'sending_queues',
'newsletter_segment',
'newsletter_statistics',
'newsletter_links',
'forms'
);
}
@ -49,6 +50,72 @@ class Migrator {
array_map($drop_table, $this->models);
}
function segments() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL DEFAULT "default",',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function settings() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,',
'value longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function custom_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,',
'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function sending_queues() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_body_hash varchar(250) NULL DEFAULT NULL,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,',
'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscribers() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
@ -66,15 +133,30 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function settings() {
function subscriber_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,',
'value longtext,',
'subscriber_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "subscribed",',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_custom_field() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'custom_field_id mediumint(9) NOT NULL,',
'value varchar(255) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -113,76 +195,6 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function segments() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL DEFAULT "default",',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "subscribed",',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function custom_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,',
'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_custom_field() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'custom_field_id mediumint(9) NOT NULL,',
'value varchar(255) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_option_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
@ -210,26 +222,15 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function sending_queues() {
function newsletter_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_body_hash varchar(250) NULL DEFAULT NULL,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,',
'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'PRIMARY KEY (id)',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -246,6 +247,20 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_links() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'url varchar(255) NOT NULL,',
'hash varchar(20) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function forms() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',

View File

@ -4,9 +4,11 @@ namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Util\Helpers;
@ -17,6 +19,7 @@ class SendingQueue {
public $mta_config;
public $mta_log;
public $processing_method;
public $divider = '***MailPoet***';
private $timer;
const batch_size = 50;
@ -38,7 +41,7 @@ class SendingQueue {
continue;
}
$newsletter = $newsletter->asArray();
$newsletter['body'] = $this->getNewsletterBodyAndSubject($queue, $newsletter);
$newsletter['body'] = $this->getOrRenderNewsletterBody($queue, $newsletter);
$queue->subscribers = (object) unserialize($queue->subscribers);
if(!isset($queue->subscribers->processed)) {
$queue->subscribers->processed = array();
@ -67,11 +70,20 @@ class SendingQueue {
}
}
function getNewsletterBodyAndSubject($queue, $newsletter) {
function getOrRenderNewsletterBody($queue, $newsletter) {
// check if newsletter has been rendered, in which case return its contents
// or render & and for future use
// or render and save for future reuse
if($queue->newsletter_rendered_body === null) {
$newsletter['body'] = $this->renderNewsletter($newsletter);
// render newsletter
$rendered_newsletter = $this->renderNewsletter($newsletter);
// extract and replace links
$processed_newsletter = $this->processLinks(
$this->joinObject($rendered_newsletter),
$newsletter['id'],
$queue->id
);
list($newsletter['body']['html'], $newsletter['body']['text']) =
$this->splitObject($processed_newsletter);
$queue->newsletter_rendered_body = json_encode($newsletter['body']);
$queue->newsletter_rendered_body_hash = md5($newsletter['body']['text']);
$queue->save();
@ -84,7 +96,7 @@ class SendingQueue {
function processBulkSubscribers($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$processed_newsletters[] =
$this->processNewsletter($newsletter, $subscriber);
$this->processNewsletter($newsletter, $subscriber, $queue);
$transformed_subscribers[] =
$mailer->transformSubscriber($subscriber);
}
@ -125,8 +137,8 @@ class SendingQueue {
function processIndividualSubscriber($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$this->checkSendingLimit();
$processed_newsletter = $this->processNewsletter($newsletter, $subscriber);
if (!$queue->newsletter_rendered_subject) {
$processed_newsletter = $this->processNewsletter($newsletter, $subscriber, $queue);
if(!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletter['subject'];
}
$transformed_subscriber = $mailer->transformSubscriber($subscriber);
@ -162,22 +174,61 @@ class SendingQueue {
return $renderer->render();
}
function processNewsletter($newsletter, $subscriber = false) {
$divider = '***MailPoet***';
$data_for_shortcodes =
array_merge(array($newsletter['subject']), $newsletter['body']);
$body = implode($divider, $data_for_shortcodes);
$shortcodes = new Shortcodes(
function processLinks($text, $newsletter_id, $queue_id) {
$links = new Links();
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 processNewsletter($newsletter, $subscriber = false, $queue) {
$data_for_shortcodes = array(
$newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
);
$processed_newsletter = $this->replaceShortcodes(
$newsletter,
$subscriber
$subscriber,
$this->joinObject($data_for_shortcodes)
);
$processed_newsletter = $this->replaceLinks(
$newsletter['id'],
$subscriber['id'],
$queue->id,
$processed_newsletter
);
list($newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
) = explode($divider, $shortcodes->replace($body));
) = $this->splitObject($processed_newsletter);
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) {
$shortcodes = new Shortcodes(
$newsletter,
$subscriber
);
return $shortcodes->replace($body);
}
function sendNewsletter($mailer, $newsletter, $subscriber) {
return $mailer->mailer_instance->send(
$newsletter,
@ -284,4 +335,12 @@ class SendingQueue {
}
return;
}
private function joinObject($object = array()) {
return implode($this->divider, $object);
}
private function splitObject($object = array()) {
return explode($this->divider, $object);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class NewsletterLink extends Model {
public static $_table = MP_NEWSLETTER_LINKS_TABLE;
function __construct() {
parent::__construct();
}
}

View File

@ -3,8 +3,8 @@ namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class NewsletterStatistics extends Model {
public static $_table = MP_NEWSLETTER_STATISTICS_TABLE;
class StatisticsNewsletters extends Model {
public static $_table = MP_STATISTICS_NEWSLETTERS_TABLE;
function __construct() {
parent::__construct();
@ -12,7 +12,7 @@ class NewsletterStatistics extends Model {
static function createMultiple($data) {
return self::rawExecute(
'INSERT INTO `' . NewsletterStatistics::$_table . '` ' .
'INSERT INTO `' . self::$_table . '` ' .
'(newsletter_id, subscriber_id, queue_id) ' .
'VALUES ' . rtrim(
str_repeat('(?,?,?), ', count($data)/3),

View File

@ -0,0 +1,24 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class StatisticsNewsletters extends Model {
public static $_table = MP_STATISTICS_NEWSLETTERS_TABLE;
function __construct() {
parent::__construct();
}
static function createMultiple($data) {
return self::rawExecute(
'INSERT INTO `' . self::$_table . '` ' .
'(newsletter_id, subscriber_id, queue_id) ' .
'VALUES ' . rtrim(
str_repeat('(?,?,?), ', count($data)/3),
', '
),
$data
);
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace MailPoet\Newsletter\Links;
use MailPoet\Util\Security;
class Links {
public $newsletter_id;
public $queue_id;
public $subscriber_id;
function __construct(
$newsletter_id = false,
$subscriber_id = false,
$queue_id = false) {
$this->newsletter_id = $newsletter_id;
$this->queue_id = $queue_id;
$this->subscriber_id = $subscriber_id;
}
function extract($text) {
// adopted from WP's wp_extract_urls() function & modified to work on hrefs
$regex = '#(?:href.*?=.*?)(["\']?)('
. '(?:([\w-]+:)?//?)'
. '[^\s()<>]+'
. '[.]'
. '(?:'
. '\([\w\d]+\)|'
. '(?:'
. '[^`!()\[\]{};:\'".,<>«»“”‘’\s]|'
. '(?:[:]\d+)?/?'
. ')+'
. ')'
. ')\\1#';
preg_match_all($regex, $text, $links);
preg_match_all('/\[\w+:\w+\]/', $text, $shortcodes);
return array_merge(
array_unique($links[2]),
array_unique($shortcodes[0])
);
}
function replace($text, $links = false) {
$links = ($links) ? $links : $this->extract($text);
$processed_links = array();
foreach($links as $link) {
$hash = Security::generateRandomString(5);
$processed_links[] = array(
'hash' => $hash,
'url' => $link
);
$encoded_link = sprintf(
'%s/?mailpoet&endpoint=track&data=%s',
home_url(),
'[mailpoet_data]-'.$hash
);
$link_regex = '/' . preg_quote($link, '/') . '/';
$text = preg_replace($link_regex, $encoded_link, $text);
}
return array($text, $processed_links);
}
}

View File

@ -26,9 +26,12 @@ class Shortcodes {
$shortcode,
$shortcode_details
);
$shortcode_class =
__NAMESPACE__ . '\\Categories\\' . ucfirst($shortcode_details['type']);
$shortcode_type = ucfirst($shortcode_details['type']);
$shortcode_action = $shortcode_details['action'];
// do not process subscription management links
if ($shortcode_type === 'Subscription') return $shortcode;
$shortcode_class =
__NAMESPACE__ . '\\Categories\\' . $shortcode_type;
$shortcode_default_value = isset($shortcode_details['default'])
? $shortcode_details['default'] : false;
if(!class_exists($shortcode_class)) return false;