Optimized Bulk actions

- Updated SQL schema for every created_at column so that it has a default value
- Updated unit tests based on recent changes (new methods in SubscriberSegment model)
- Added check for HelpScout initialization code so that it doesn't throw errors
This commit is contained in:
Jonathan Labreuille
2016-05-27 10:49:34 +02:00
parent 4a4c4e093a
commit 3c46a5b434
10 changed files with 288 additions and 244 deletions

View File

@@ -63,9 +63,9 @@ class Migrator {
'name varchar(90) NOT NULL,', 'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL DEFAULT "default",', 'type varchar(90) NOT NULL DEFAULT "default",',
'description varchar(250) NOT NULL DEFAULT "",', 'description varchar(250) NOT NULL DEFAULT "",',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,', 'deleted_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY name (name)' 'UNIQUE KEY name (name)'
); );
@@ -77,7 +77,7 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,', 'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,', 'name varchar(20) NOT NULL,',
'value longtext,', 'value longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY name (name)' 'UNIQUE KEY name (name)'
@@ -91,7 +91,7 @@ class Migrator {
'name varchar(90) NOT NULL,', 'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,', 'type varchar(90) NOT NULL,',
'params longtext NOT NULL,', 'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY name (name)' 'UNIQUE KEY name (name)'
@@ -114,8 +114,8 @@ class Migrator {
'count_failed mediumint(9) NOT NULL DEFAULT 0,', 'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NULL,', 'scheduled_at TIMESTAMP NULL,',
'processed_at TIMESTAMP NULL,', 'processed_at TIMESTAMP NULL,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,', 'deleted_at TIMESTAMP NULL,',
'PRIMARY KEY (id)', 'PRIMARY KEY (id)',
); );
@@ -130,8 +130,8 @@ class Migrator {
'last_name tinytext NOT NULL DEFAULT "",', 'last_name tinytext NOT NULL DEFAULT "",',
'email varchar(150) NOT NULL,', 'email varchar(150) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "' . Subscriber::STATUS_UNCONFIRMED . '",', 'status varchar(12) NOT NULL DEFAULT "' . Subscriber::STATUS_UNCONFIRMED . '",',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,', 'deleted_at TIMESTAMP NULL,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY email (email)' 'UNIQUE KEY email (email)'
@@ -145,8 +145,8 @@ class Migrator {
'subscriber_id mediumint(9) NOT NULL,', 'subscriber_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,', 'segment_id mediumint(9) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "' . Subscriber::STATUS_SUBSCRIBED . '",', 'status varchar(12) NOT NULL DEFAULT "' . Subscriber::STATUS_SUBSCRIBED . '",',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)' 'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
); );
@@ -159,8 +159,8 @@ class Migrator {
'subscriber_id mediumint(9) NOT NULL,', 'subscriber_id mediumint(9) NOT NULL,',
'custom_field_id mediumint(9) NOT NULL,', 'custom_field_id mediumint(9) NOT NULL,',
'value varchar(255) NOT NULL DEFAULT "",', 'value varchar(255) NOT NULL DEFAULT "",',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)' 'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
); );
@@ -178,8 +178,8 @@ class Migrator {
'reply_to_name varchar(150) NOT NULL DEFAULT "",', 'reply_to_name varchar(150) NOT NULL DEFAULT "",',
'preheader varchar(250) NOT NULL DEFAULT "",', 'preheader varchar(250) NOT NULL DEFAULT "",',
'body longtext,', 'body longtext,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,', 'deleted_at TIMESTAMP NULL,',
'PRIMARY KEY (id)' 'PRIMARY KEY (id)'
); );
@@ -194,8 +194,8 @@ class Migrator {
'body LONGTEXT,', 'body LONGTEXT,',
'thumbnail LONGTEXT,', 'thumbnail LONGTEXT,',
'readonly TINYINT(1) DEFAULT 0,', 'readonly TINYINT(1) DEFAULT 0,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)' 'PRIMARY KEY (id)'
); );
return $this->sqlify(__FUNCTION__, $attributes); return $this->sqlify(__FUNCTION__, $attributes);
@@ -206,8 +206,8 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,', 'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,', 'name varchar(90) NOT NULL,',
'newsletter_type varchar(90) NOT NULL,', 'newsletter_type varchar(90) NOT NULL,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY name_newsletter_type (newsletter_type,name)' 'UNIQUE KEY name_newsletter_type (newsletter_type,name)'
); );
@@ -220,8 +220,8 @@ class Migrator {
'newsletter_id mediumint(9) NOT NULL,', 'newsletter_id mediumint(9) NOT NULL,',
'option_field_id mediumint(9) NOT NULL,', 'option_field_id mediumint(9) NOT NULL,',
'value varchar(255) NOT NULL DEFAULT "",', 'value varchar(255) NOT NULL DEFAULT "",',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_id_option_field_id (newsletter_id,option_field_id)' 'UNIQUE KEY newsletter_id_option_field_id (newsletter_id,option_field_id)'
); );
@@ -233,8 +233,8 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,', 'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,', 'newsletter_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,', 'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)' 'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
); );
@@ -248,8 +248,8 @@ class Migrator {
'queue_id mediumint(9) NOT NULL,', 'queue_id mediumint(9) NOT NULL,',
'url varchar(255) NOT NULL,', 'url varchar(255) NOT NULL,',
'hash varchar(20) NOT NULL,', 'hash varchar(20) NOT NULL,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)', 'PRIMARY KEY (id)',
); );
return $this->sqlify(__FUNCTION__, $attributes); return $this->sqlify(__FUNCTION__, $attributes);
@@ -260,7 +260,7 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,', 'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,', 'newsletter_id mediumint(9) NOT NULL,',
'post_id mediumint(9) NOT NULL,', 'post_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)', 'PRIMARY KEY (id)',
); );
@@ -274,8 +274,8 @@ class Migrator {
'body longtext,', 'body longtext,',
'settings longtext,', 'settings longtext,',
'styles longtext,', 'styles longtext,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,', 'deleted_at TIMESTAMP NULL,',
'PRIMARY KEY (id)' 'PRIMARY KEY (id)'
); );
@@ -302,8 +302,8 @@ class Migrator {
'queue_id mediumint(9) NOT NULL,', 'queue_id mediumint(9) NOT NULL,',
'link_id mediumint(9) NOT NULL,', 'link_id mediumint(9) NOT NULL,',
'count mediumint(9) NOT NULL,', 'count mediumint(9) NOT NULL,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)', 'PRIMARY KEY (id)',
); );
return $this->sqlify(__FUNCTION__, $attributes); return $this->sqlify(__FUNCTION__, $attributes);
@@ -315,7 +315,7 @@ class Migrator {
'newsletter_id mediumint(9) NOT NULL,', 'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,', 'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,', 'queue_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)', 'PRIMARY KEY (id)',
); );
return $this->sqlify(__FUNCTION__, $attributes); return $this->sqlify(__FUNCTION__, $attributes);
@@ -327,7 +327,7 @@ class Migrator {
'newsletter_id mediumint(9) NOT NULL,', 'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,', 'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,', 'queue_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)', 'PRIMARY KEY (id)',
); );
return $this->sqlify(__FUNCTION__, $attributes); return $this->sqlify(__FUNCTION__, $attributes);
@@ -338,7 +338,7 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,', 'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'form_id mediumint(9) NOT NULL,', 'form_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,', 'subscriber_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id),',
'UNIQUE KEY form_subscriber (form_id,subscriber_id)' 'UNIQUE KEY form_subscriber (form_id,subscriber_id)'
); );

View File

@@ -22,8 +22,17 @@ class BulkAction {
} }
function apply() { function apply() {
$bulk_action_method = 'bulk'.ucfirst($this->action);
if(!method_exists($this->model_class, $bulk_action_method)) {
throw new Exception(
$this->model_class. ' has not method "'.$bulk_action_method.'"'
);
return false;
}
return call_user_func_array( return call_user_func_array(
array($this->model_class, 'bulk'.ucfirst($this->action)), array($this->model_class, $bulk_action_method),
array($this->listing->getSelection(), $this->data) array($this->listing->getSelection(), $this->data)
); );
} }

View File

@@ -5,6 +5,7 @@ if(!defined('ABSPATH')) exit;
class Model extends \Sudzy\ValidModel { class Model extends \Sudzy\ValidModel {
protected $_errors; protected $_errors;
const MAX_BATCH_SIZE = 200;
function __construct() { function __construct() {
$this->_errors = array(); $this->_errors = array();
@@ -109,25 +110,50 @@ class Model extends \Sudzy\ValidModel {
static function bulkAction($orm, $callback = false) { static function bulkAction($orm, $callback = false) {
$total = $orm->count(); $total = $orm->count();
if($total > 0) { if($total === 0) return false;
$models = $orm->select(static::$_table.'.id')
$affected_rows = 0;
if($total > self::MAX_BATCH_SIZE) {
for(
$i = 0, $batches = ceil($total / self::MAX_BATCH_SIZE);
$i < $batches;
$i++
) {
$rows = $orm->select('id')
->offset($i * self::MAX_BATCH_SIZE)
->limit(self::MAX_BATCH_SIZE)
->findArray();
$ids = array_map(function($model) {
return (int)$model['id'];
}, $rows);
if($callback !== false) {
$callback($ids);
}
// increment number of affected rows
$affected_rows += $orm->get_last_statement()->rowCount();
}
} else {
$rows = $orm->select(static::$_table.'.id')
->offset(null) ->offset(null)
->limit(null) ->limit(null)
->findArray(); ->findArray();
$ids = array_map(function($model) { $ids = array_map(function($model) {
return (int)$model['id']; return (int)$model['id'];
}, $models); }, $rows);
if(is_callable($callback)) { if($callback !== false) {
$callback($ids); $callback($ids);
} }
$last_statement = $orm->get_last_statement(); // get number of affected rows
return $last_statement->rowCount(); $affected_rows = $orm->get_last_statement()->rowCount();
} }
return $affected_rows;
return false;
} }
function duplicate($data = array()) { function duplicate($data = array()) {

View File

@@ -498,54 +498,72 @@ class Subscriber extends Model {
)); ));
} }
static function bulkAddToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
if($segment === false) return false;
$subscribers_count = parent::bulkAction($orm,
function($subscriber_ids) use($segment) {
SubscriberSegment::subscribeManyToSegments(
$subscriber_ids, array($segment->id)
);
}
);
return array(
'subscribers' => $subscribers_count,
'segment' => $segment->name
);
}
static function bulkMoveToList($orm, $data = array()) { static function bulkMoveToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0); $segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id); $segment = Segment::findOne($segment_id);
if($segment !== false) { if($segment === false) return false;
$subscribers_count = 0;
$subscribers = $orm->findResultSet(); $subscribers_count = parent::bulkAction($orm,
foreach($subscribers as $subscriber) { function($subscriber_ids) use($segment) {
SubscriberSegment::resetSubscriptions($subscriber, array($segment->id)); SubscriberSegment::deleteManySubscriptions($subscriber_ids);
$subscribers_count++; SubscriberSegment::subscribeManyToSegments(
} $subscriber_ids, array($segment->id)
return array( );
'subscribers' => $subscribers_count, });
'segment' => $segment->name
); return array(
} 'subscribers' => $subscribers_count,
return false; 'segment' => $segment->name
);
} }
static function bulkRemoveFromList($orm, $data = array()) { static function bulkRemoveFromList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0); $segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id); $segment = Segment::findOne($segment_id);
if($segment !== false) { if($segment === false) return false;
$subscribers_count = 0;
// delete relations with segment $subscribers_count = parent::bulkAction($orm,
$subscribers = $orm->findResultSet(); function($subscriber_ids) use($segment) {
foreach($subscribers as $subscriber) { SubscriberSegment::deleteManySubscriptions(
SubscriberSegment::unsubscribeFromSegments($subscriber, array($segment->id)); $subscriber_ids, array($segment->id)
$subscribers_count++; );
} }
);
return array( return array(
'subscribers' => $subscribers_count, 'subscribers' => $subscribers_count,
'segment' => $segment->name 'segment' => $segment->name
); );
}
return false;
} }
static function bulkRemoveFromAllLists($orm) { static function bulkRemoveFromAllLists($orm, $data = array()) {
$subscribers = $orm->findResultSet(); $subscribers_count = $orm->count();
$subscribers_count = 0;
foreach($subscribers as $subscriber) { parent::bulkAction($orm, function($subscriber_ids) {
SubscriberSegment::unsubscribeFromSegments($subscriber); SubscriberSegment::deleteManySubscriptions($subscriber_ids);
$subscribers_count++; });
}
return $subscribers_count; return $subscribers_count;
} }
@@ -567,31 +585,9 @@ class Subscriber extends Model {
return false; return false;
} }
static function bulkAddToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
if($segment !== false) {
$subscribers_count = 0;
$subscribers = $orm->findMany();
foreach($subscribers as $subscriber) {
try {
SubscriberSegment::subscribeToSegments($subscriber, array($segment->id));
$subscribers_count++;
} catch(Exception $e) {
}
}
return array(
'subscribers' => $subscribers_count,
'segment' => $segment->name
);
}
return false;
}
static function bulkTrash($orm) { static function bulkTrash($orm) {
return parent::bulkAction($orm, function($subscriber_ids) use($orm) { return parent::bulkAction($orm, function($subscriber_ids) {
parent::rawExecute(join(' ', array( self::rawExecute(join(' ', array(
'UPDATE `'.self::$_table.'`', 'UPDATE `'.self::$_table.'`',
'SET `deleted_at` = NOW()', 'SET `deleted_at` = NOW()',
'WHERE `id` IN ('.rtrim(str_repeat('?,', count($subscriber_ids)), ',').')', 'WHERE `id` IN ('.rtrim(str_repeat('?,', count($subscriber_ids)), ',').')',

View File

@@ -17,72 +17,68 @@ class SubscriberSegment extends Model {
} }
static function unsubscribeFromSegments($subscriber, $segment_ids = array()) { static function unsubscribeFromSegments($subscriber, $segment_ids = array()) {
if($subscriber !== false && $subscriber->id > 0) { if($subscriber === false) return false;
$wp_segment = Segment::getWPSegment(); $wp_segment = Segment::getWPSegment();
if(!empty($segment_ids)) { if(!empty($segment_ids)) {
// unsubscribe from segments // unsubscribe from segments
foreach($segment_ids as $segment_id) { foreach($segment_ids as $segment_id) {
// do not remove subscriptions to the WP Users segment // do not remove subscriptions to the WP Users segment
if( if(
$wp_segment !== false $wp_segment !== false
&& ($wp_segment->id === (int)$segment_id) && ($wp_segment->id === (int)$segment_id)
) { ) {
continue; continue;
}
if((int)$segment_id > 0) {
self::createOrUpdate(array(
'subscriber_id' => $subscriber->id,
'segment_id' => $segment_id,
'status' => Subscriber::STATUS_UNSUBSCRIBED
));
}
}
} else {
// unsubscribe from all segments (except the WP users segment)
$subscriptions = self::where('subscriber_id', $subscriber->id);
if($wp_segment !== false) {
$subscriptions = $subscriptions->whereNotEqual(
'segment_id', $wp_segment->id
);
} }
$subscriptions->findResultSet() if((int)$segment_id > 0) {
->set('status', Subscriber::STATUS_UNSUBSCRIBED) self::createOrUpdate(array(
->save(); 'subscriber_id' => $subscriber->id,
'segment_id' => $segment_id,
'status' => Subscriber::STATUS_UNSUBSCRIBED
));
}
} }
return true; } else {
// unsubscribe from all segments (except the WP users segment)
$subscriptions = self::where('subscriber_id', $subscriber->id);
if($wp_segment !== false) {
$subscriptions = $subscriptions->whereNotEqual(
'segment_id', $wp_segment->id
);
}
$subscriptions->findResultSet()
->set('status', Subscriber::STATUS_UNSUBSCRIBED)
->save();
} }
return false; return true;
} }
static function subscribeToSegments($subscriber, $segment_ids = array()) { static function subscribeToSegments($subscriber, $segment_ids = array()) {
if($subscriber->id > 0) { if($subscriber === false) return false;
if(!empty($segment_ids)) { if(!empty($segment_ids)) {
// subscribe to segments // subscribe to specified segments
foreach($segment_ids as $segment_id) { foreach($segment_ids as $segment_id) {
if((int)$segment_id > 0) { if((int)$segment_id > 0) {
self::createOrUpdate(array( self::createOrUpdate(array(
'subscriber_id' => $subscriber->id, 'subscriber_id' => $subscriber->id,
'segment_id' => $segment_id, 'segment_id' => $segment_id,
'status' => Subscriber::STATUS_SUBSCRIBED 'status' => Subscriber::STATUS_SUBSCRIBED
)); ));
}
} }
return true;
} else {
// subscribe to all segments
return self::where('subscriber_id', $subscriber->id)
->findResultSet()
->set('status', Subscriber::STATUS_SUBSCRIBED)
->save();
} }
return true;
} else {
// (re)subscribe to all segments linked to the subscriber
return self::where('subscriber_id', $subscriber->id)
->findResultSet()
->set('status', Subscriber::STATUS_SUBSCRIBED)
->save();
} }
return false;
} }
static function resetSubscriptions($subscriber, $segment_ids = array()) { static function resetSubscriptions($subscriber, $segment_ids = array()) {
@@ -90,30 +86,59 @@ class SubscriberSegment extends Model {
return self::subscribeToSegments($subscriber, $segment_ids); return self::subscribeToSegments($subscriber, $segment_ids);
} }
static function deleteManySubscriptions($subscriber_ids = array()) { static function subscribeManyToSegments(
if(!empty($subscriber_ids)) { $subscriber_ids = array(),
// delete subscribers' relations to segments (except WP Users' segment) $segment_ids = array()
$subscriptions = SubscriberSegment::whereIn( ) {
'subscriber_id', $subscriber_ids if(empty($subscriber_ids) || empty($segment_ids)) {
); return false;
$wp_segment = Segment::getWPSegment();
if($wp_segment !== false) {
$subscriptions = $subscriptions->whereNotEqual(
'segment_id', $wp_segment->id
);
}
return $subscriptions->deleteMany();
} }
return false;
// create many subscriptions to each segment
foreach($segment_ids as $segment_id) {
$query = array(
'INSERT IGNORE INTO `'.self::$_table.'`',
'(`subscriber_id`, `segment_id`, `status`)',
'VALUES '.rtrim(str_repeat(
"(?, ".(int)$segment_id.", '".Subscriber::STATUS_SUBSCRIBED."'), ",
count($subscriber_ids)
), ', ')
);
self::rawExecute(join(' ', $query), $subscriber_ids);
}
return true;
} }
static function deleteSubscriptions($subscriber) { static function deleteManySubscriptions($subscriber_ids = array(), $segment_ids = array()) {
if($subscriber !== false && $subscriber->id > 0) { if(empty($subscriber_ids)) return false;
// delete all relationships to segments
return self::where('subscriber_id', $subscriber->id)->deleteMany(); // delete subscribers' relations to segments (except WP segment)
$subscriptions = self::whereIn(
'subscriber_id', $subscriber_ids
);
$wp_segment = Segment::getWPSegment();
if($wp_segment !== false) {
$subscriptions = $subscriptions->whereNotEqual(
'segment_id', $wp_segment->id
);
} }
return false; return $subscriptions->deleteMany();
}
static function deleteSubscriptions($subscriber, $segment_ids = array()) {
if($subscriber === false) return false;
$wp_segment = Segment::getWPSegment();
$subscriptions = self::where('subscriber_id', $subscriber->id)
->whereNotEqual('segment_id', $wp_segment->id);
if(!empty($segment_ids)) {
$subscriptions = $subscriptions->whereIn('segment_id', $segment_ids);
}
return $subscriptions->deleteMany();
} }
static function subscribed($orm) { static function subscribed($orm) {
@@ -143,27 +168,4 @@ class SubscriberSegment extends Model {
return $subscription->save(); return $subscription->save();
} }
// TO BE REVIEWED
static function createMultiple($segmnets, $subscribers) {
$values = Helpers::flattenArray(
array_map(function ($segment) use ($subscribers) {
return array_map(function ($subscriber) use ($segment) {
return array(
$segment,
$subscriber
);
}, $subscribers);
}, $segmnets)
);
return self::rawExecute(
'INSERT IGNORE INTO `' . self::$_table . '` ' .
'(segment_id, subscriber_id) ' .
'VALUES ' . rtrim(
str_repeat(
'(?, ?), ', count($subscribers) * count($segmnets)), ', '
),
$values
);
}
} }

View File

@@ -361,9 +361,11 @@ class Import {
return array_walk($wp_users, '\MailPoet\Segments\WP::synchronizeUser'); return array_walk($wp_users, '\MailPoet\Segments\WP::synchronizeUser');
} }
function addSubscribersToSegments($subscribers, $segments) { function addSubscribersToSegments($subscriber_ids, $segment_ids) {
foreach(array_chunk($subscribers, 200) as $data) { foreach(array_chunk($subscriber_ids, 200) as $subscriber_ids_chunk) {
SubscriberSegment::createMultiple($segments, $data); SubscriberSegment::subscribeManyToSegments(
$subscriber_ids_chunk, $segment_ids
);
} }
} }

View File

@@ -223,7 +223,7 @@ class SubscriberSegmentTest extends MailPoetTest {
expect($subscribed_segments[0]['name'])->equals($this->wp_segment->name); expect($subscribed_segments[0]['name'])->equals($this->wp_segment->name);
} }
function testItCanDeleteSubscriptionToWPSegment() { function testItCannotDeleteSubscriptionToWPSegment() {
// subscribe to a segment and the WP segment // subscribe to a segment and the WP segment
$result = SubscriberSegment::subscribeToSegments($this->subscriber, array( $result = SubscriberSegment::subscribeToSegments($this->subscriber, array(
$this->segment_1->id, $this->segment_1->id,
@@ -235,11 +235,10 @@ class SubscriberSegmentTest extends MailPoetTest {
$result = SubscriberSegment::deleteSubscriptions($this->subscriber); $result = SubscriberSegment::deleteSubscriptions($this->subscriber);
expect($result)->true(); expect($result)->true();
// it should have removed all subscriptions (even to the WP segment) // the subscriber should still be subscribed to the WP segment
$subscriptions_count = SubscriberSegment::where( $subscribed_segments = $this->subscriber->segments()->findArray();
'subscriber_id', $this->subscriber->id expect($subscribed_segments)->count(1);
)->count(); expect($subscribed_segments[0]['name'])->equals($this->wp_segment->name);
expect($subscriptions_count)->equals(0);
} }
function _after() { function _after() {

View File

@@ -390,38 +390,45 @@ class SubscriberTest extends MailPoetTest {
} }
function testItCanGetOnlySubscribedSubscribersInSegments() { function testItCanGetOnlySubscribedSubscribersInSegments() {
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table); $subscriber_1 = Subscriber::createOrUpdate(array(
$columns = array( 'first_name' => 'Adam',
'first_name', 'last_name' => 'Smith',
'last_name', 'email' => 'adam@smith.com',
'email', 'status' => Subscriber::STATUS_UNCONFIRMED
'status' ));
$subscriber_2 = Subscriber::createOrUpdate(array(
'first_name' => 'Mary',
'last_name' => 'Jane',
'email' => 'mary@jane.com',
'status' => Subscriber::STATUS_SUBSCRIBED
));
$segment = Segment::createOrUpdate(array(
'name' => 'Only Subscribed Subscribers Segment'
));
//Subscriber::createMultiple($columns, $values);
$result = SubscriberSegment::subscribeManyToSegments(
array($subscriber_1->id, $subscriber_2->id),
array($segment->id)
); );
$values = array( expect($result)->true();
array(
'first_name' => 'Adam', $subscribed_subscribers_in_segment = Subscriber::getSubscribedInSegments(
'last_name' => 'Smith', array($segment->id)
'email' => 'adam@smith.com', )->findArray();
'status' => 'unconfirmed' expect($subscribed_subscribers_in_segment)->count(1);
),
array( // update 1st subscriber's state to subscribed
'first_name' => 'Mary', $subscriber = Subscriber::findOne($subscriber_1->id);
'last_name' => 'Jane', $subscriber->status = Subscriber::STATUS_SUBSCRIBED;
'email' => 'mary@jane.com',
'status' => 'subscribed'
)
);
Subscriber::createMultiple($columns, $values);
SubscriberSegment::createMultiple(array(1), array(1,2));
$subscribed_subscribers_in_segment =
Subscriber::getSubscribedInSegments(array(1))->findArray();
expect(count($subscribed_subscribers_in_segment))->equals(1);
$subscriber = Subscriber::findOne(1);
$subscriber->status = 'subscribed';
$subscriber->save(); $subscriber->save();
$subscribed_subscribers_in_segment =
Subscriber::getSubscribedInSegments(array(1))->findArray(); $subscribed_subscribers_in_segment = Subscriber::getSubscribedInSegments(
expect(count($subscribed_subscribers_in_segment))->equals(2); array($segment->id)
)->findArray();
expect($subscribed_subscribers_in_segment)->count(2);
} }
function testItCannotTrashAWPUser() { function testItCannotTrashAWPUser() {

View File

@@ -1,6 +1,7 @@
<?php <?php
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\Models\Segment;
use MailPoet\Models\SubscriberCustomField; use MailPoet\Models\SubscriberCustomField;
use MailPoet\Models\SubscriberSegment; use MailPoet\Models\SubscriberSegment;
use MailPoet\Subscribers\ImportExport\Import\Import; use MailPoet\Subscribers\ImportExport\Import\Import;
@@ -40,7 +41,9 @@ class ImportTest extends MailPoetTest {
'last_name', 'last_name',
'email' 'email'
); );
$this->segments = range(0, 1); $this->segment_1 = Segment::createOrUpdate(array('name' => 'Segment 1'));
$this->segment_2 = Segment::createOrUpdate(array('name' => 'Segment 2'));
$this->subscriber_custom_fields = array(777); $this->subscriber_custom_fields = array(777);
$this->import = new Import($this->data); $this->import = new Import($this->data);
$this->subscribers_data = $this->import->transformSubscribersData( $this->subscribers_data = $this->import->transformSubscribersData(
@@ -224,7 +227,7 @@ class ImportTest extends MailPoetTest {
expect(count($db_subscribers))->equals(2); expect(count($db_subscribers))->equals(2);
$this->import->addSubscribersToSegments( $this->import->addSubscribersToSegments(
$db_subscribers, $db_subscribers,
$this->segments array($this->segment_1->id, $this->segment_2->id)
); );
$subscribers_segments = SubscriberSegment::findArray(); $subscribers_segments = SubscriberSegment::findArray();
expect(count($subscribers_segments))->equals(4); expect(count($subscribers_segments))->equals(4);
@@ -277,7 +280,7 @@ class ImportTest extends MailPoetTest {
} }
function testItCanaddSubscribersToSegments() { function testItCanAddSubscribersToSegments() {
$subscribers_data = $this->subscribers_data; $subscribers_data = $this->subscribers_data;
$this->import->createOrUpdateSubscribers( $this->import->createOrUpdateSubscribers(
'create', 'create',
@@ -292,7 +295,7 @@ class ImportTest extends MailPoetTest {
); );
$this->import->addSubscribersToSegments( $this->import->addSubscribersToSegments(
$db_subscribers, $db_subscribers,
$this->segments array($this->segment_1->id, $this->segment_2->id)
); );
$subscribers_segments = SubscriberSegment::findArray(); $subscribers_segments = SubscriberSegment::findArray();
// 2 subscribers * 2 segments // 2 subscribers * 2 segments

View File

@@ -52,14 +52,14 @@ jQuery('.toplevel_page_mailpoet.menu-top-last')
<script type="text/javascript"> <script type="text/javascript">
var mailpoet_date_format = "<%= wp_datetime_format() %>"; var mailpoet_date_format = "<%= wp_datetime_format() %>";
</script>
<script> if(window['HS'] !== undefined) {
HS.beacon.config({ HS.beacon.config({
icon: 'message', icon: 'message',
zIndex: 50000, zIndex: 50000,
instructions: '<%= __('Want to give feedback to the MailPoet team? Write them right here with as much information as possible.') %>', instructions: '<%= __('Want to give feedback to the MailPoet team? Write them right here with as much information as possible.') %>',
}); });
}
</script> </script>
<% block after_javascript %><% endblock %> <% block after_javascript %><% endblock %>