- Updates unit tests - Fixes an issue with export UI not displaying subscribers without segment
269 lines
9.0 KiB
PHP
269 lines
9.0 KiB
PHP
<?php
|
|
namespace MailPoet\Subscribers\ImportExport\Export;
|
|
|
|
use MailPoet\Config\Env;
|
|
use MailPoet\Models\CustomField;
|
|
use MailPoet\Models\Segment;
|
|
use MailPoet\Models\Subscriber;
|
|
use MailPoet\Models\SubscriberSegment;
|
|
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
|
|
use MailPoet\Util\Helpers;
|
|
use MailPoet\Util\XLSXWriter;
|
|
|
|
class Export {
|
|
public $export_confirmed_option;
|
|
public $export_format_option;
|
|
public $group_by_segment_option;
|
|
public $segments;
|
|
public $subscribers_without_segment;
|
|
public $subscriber_fields;
|
|
public $subscriber_custom_fields;
|
|
public $formatted_subscriber_fields;
|
|
public $export_path;
|
|
public $export_file;
|
|
public $export_file_URL;
|
|
public $subscriber_batch_size;
|
|
|
|
public function __construct($data) {
|
|
set_time_limit(0);
|
|
$this->export_confirmed_option = $data['export_confirmed_option'];
|
|
$this->export_format_option = $data['export_format_option'];
|
|
$this->group_by_segment_option = $data['group_by_segment_option'];
|
|
$this->segments = $data['segments'];
|
|
$this->subscribers_without_segment = array_search(0, $this->segments);
|
|
$this->subscriber_fields = $data['subscriber_fields'];
|
|
$this->subscriber_custom_fields = $this->getSubscriberCustomFields();
|
|
$this->formatted_subscriber_fields = $this->formatSubscriberFields(
|
|
$this->subscriber_fields,
|
|
$this->subscriber_custom_fields
|
|
);
|
|
$this->export_path = Env::$temp_path;
|
|
$this->export_file = $this->getExportFile($this->export_format_option);
|
|
$this->export_file_URL = $this->getExportFileURL($this->export_file);
|
|
$this->subscriber_batch_size = 15000;
|
|
}
|
|
|
|
function process() {
|
|
try {
|
|
if(is_writable($this->export_path) === false) {
|
|
throw new \Exception(__("Couldn't save export file on the server."));
|
|
}
|
|
$processed_subscribers = call_user_func(
|
|
array(
|
|
$this,
|
|
'generate' . strtoupper($this->export_format_option)
|
|
)
|
|
);
|
|
} catch(\Exception $e) {
|
|
return array(
|
|
'result' => false,
|
|
'errors' => array($e->getMessage())
|
|
);
|
|
}
|
|
return array(
|
|
'result' => true,
|
|
'data' => array(
|
|
'totalExported' => $processed_subscribers,
|
|
'exportFileURL' => $this->export_file_URL
|
|
)
|
|
);
|
|
}
|
|
|
|
function generateCSV() {
|
|
$processed_subscribers = 0;
|
|
$offset = 0;
|
|
$formatted_subscriber_fields = $this->formatted_subscriber_fields;
|
|
$CSV_file = fopen($this->export_file, 'w');
|
|
$format_CSV = function($row) {
|
|
return '"' . str_replace('"', '\"', $row) . '"';
|
|
};
|
|
// add UTF-8 BOM (3 bytes, hex EF BB BF) at the start of the file for
|
|
// Excel to automatically recognize the encoding
|
|
fwrite($CSV_file, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
|
if($this->group_by_segment_option) {
|
|
$formatted_subscriber_fields[] = __('Segment');
|
|
}
|
|
fwrite(
|
|
$CSV_file,
|
|
implode(
|
|
',',
|
|
array_map(
|
|
$format_CSV,
|
|
$formatted_subscriber_fields
|
|
)
|
|
) . PHP_EOL
|
|
);
|
|
do {
|
|
$subscribers = $this->getSubscribers($offset, $this->subscriber_batch_size);
|
|
$processed_subscribers += count($subscribers);
|
|
foreach($subscribers as $subscriber) {
|
|
$row = $this->formatSubscriberData($subscriber);
|
|
if($this->group_by_segment_option) {
|
|
$row[] = ucwords($subscriber['segment_name']);
|
|
}
|
|
fwrite($CSV_file, implode(',', array_map($format_CSV, $row)) . "\n");
|
|
}
|
|
$offset += $this->subscriber_batch_size;
|
|
} while(count($subscribers) === $this->subscriber_batch_size);
|
|
fclose($CSV_file);
|
|
return $processed_subscribers;
|
|
}
|
|
|
|
function generateXLSX() {
|
|
$processed_subscribers = 0;
|
|
$offset = 0;
|
|
$XLSX_writer = new XLSXWriter();
|
|
$XLSX_writer->setAuthor('MailPoet (www.mailpoet.com)');
|
|
$last_segment = false;
|
|
$processed_segments = array();
|
|
do {
|
|
$subscribers = $this->getSubscribers($offset, $this->subscriber_batch_size);
|
|
$processed_subscribers += count($subscribers);
|
|
foreach($subscribers as $i => $subscriber) {
|
|
$current_segment = ucwords($subscriber['segment_name']);
|
|
// Sheet header (1st row) will be written only if:
|
|
// * This is the first time we're processing a segment
|
|
// * "Group by subscriber option" is turned AND the previous subscriber's
|
|
// segment is different from the current subscriber's segment
|
|
// Header will NOT be written if:
|
|
// * We have already processed the segment. Because SQL results are not
|
|
// sorted by segment name (due to slow queries when using ORDER BY and LIMIT),
|
|
// we need to keep track of processed segments so that we do not create header
|
|
// multiple times when switching from one segment to another and back.
|
|
if((!count($processed_segments) ||
|
|
($last_segment !== $current_segment && $this->group_by_segment_option)
|
|
) &&
|
|
(!in_array($last_segment, $processed_segments) ||
|
|
!in_array($current_segment, $processed_segments)
|
|
)
|
|
) {
|
|
$this->writeXLSX(
|
|
$XLSX_writer,
|
|
$subscriber['segment_name'],
|
|
$this->formatted_subscriber_fields
|
|
);
|
|
$processed_segments[] = $current_segment;
|
|
}
|
|
$last_segment = ucwords($subscriber['segment_name']);
|
|
// detect RTL language and set Excel to properly display the sheet
|
|
$RTL_regex = '/\p{Arabic}|\p{Hebrew}/u';
|
|
if(!$XLSX_writer->rtl && (
|
|
preg_grep($RTL_regex, $subscriber) ||
|
|
preg_grep($RTL_regex, $this->formatted_subscriber_fields))
|
|
) {
|
|
$XLSX_writer->rtl = true;
|
|
}
|
|
$this->writeXLSX(
|
|
$XLSX_writer,
|
|
$last_segment,
|
|
$this->formatSubscriberData($subscriber)
|
|
);
|
|
}
|
|
$offset += $this->subscriber_batch_size;
|
|
} while(count($subscribers) === $this->subscriber_batch_size);
|
|
$XLSX_writer->writeToFile($this->export_file);
|
|
return $processed_subscribers;
|
|
}
|
|
|
|
function writeXLSX($XLSX_writer, $segment, $data) {
|
|
return $XLSX_writer->writeSheetRow(
|
|
($this->group_by_segment_option) ?
|
|
ucwords($segment) :
|
|
__('All Segments'),
|
|
$data
|
|
);
|
|
}
|
|
|
|
function getSubscribers($offset, $limit) {
|
|
$subscribers = Subscriber::
|
|
left_outer_join(
|
|
SubscriberSegment::$_table,
|
|
array(
|
|
Subscriber::$_table . '.id',
|
|
'=',
|
|
SubscriberSegment::$_table . '.subscriber_id'
|
|
))
|
|
->left_outer_join(
|
|
Segment::$_table,
|
|
array(
|
|
Segment::$_table . '.id',
|
|
'=',
|
|
SubscriberSegment::$_table . '.segment_id'
|
|
))
|
|
->filter('filterWithCustomFieldsForExport');
|
|
if($this->subscribers_without_segment !== false) {
|
|
$subscribers = $subscribers
|
|
->selectExpr('CASE WHEN ' . Segment::$_table . '.name IS NOT NULL ' .
|
|
'THEN ' . Segment::$_table . '.name ' .
|
|
'ELSE "' . __('Not In Segment') . '" END as segment_name'
|
|
)
|
|
->whereRaw(
|
|
SubscriberSegment::$_table . '.segment_id IN (' .
|
|
rtrim(str_repeat('?,', count($this->segments)), ',') . ') ' .
|
|
'OR ' . SubscriberSegment::$_table . '.segment_id IS NULL ',
|
|
$this->segments
|
|
);
|
|
} else {
|
|
$subscribers = $subscribers
|
|
->select(Segment::$_table . '.name', 'segment_name')
|
|
->whereIn(SubscriberSegment::$_table . '.segment_id', $this->segments);
|
|
}
|
|
if(!$this->group_by_segment_option) {
|
|
$subscribers =
|
|
$subscribers->groupBy(Subscriber::$_table . '.id');
|
|
}
|
|
if($this->export_confirmed_option) {
|
|
$subscribers =
|
|
$subscribers->where(Subscriber::$_table . '.status', 'subscribed');
|
|
}
|
|
$subscribers = $subscribers
|
|
->whereNull(Subscriber::$_table . '.deleted_at')
|
|
->limit(sprintf('%d, %d', $offset, $limit))
|
|
->findArray();
|
|
return $subscribers;
|
|
}
|
|
|
|
function getExportFileURL($file) {
|
|
return sprintf(
|
|
'%s/%s',
|
|
Env::$temp_URL,
|
|
basename($file)
|
|
);
|
|
}
|
|
|
|
function getExportFile($format) {
|
|
return sprintf(
|
|
$this->export_path . '/MailPoet_export_%s.%s',
|
|
substr(md5(time()), 0, 4),
|
|
$format
|
|
);
|
|
}
|
|
|
|
function getSubscriberCustomFields() {
|
|
return Helpers::arrayColumn(
|
|
CustomField::findArray(),
|
|
'name',
|
|
'id'
|
|
);
|
|
}
|
|
|
|
function formatSubscriberFields($subscriber_fields, $subscriber_custom_fields) {
|
|
$bootstrap_menu = new BootStrapMenu();
|
|
$translated_fields = $bootstrap_menu->getSubscriberFields();
|
|
return array_map(function($field) use (
|
|
$translated_fields, $subscriber_custom_fields
|
|
) {
|
|
$field = (isset($translated_fields[$field])) ?
|
|
ucfirst($translated_fields[$field]) :
|
|
ucfirst($field);
|
|
return (isset($subscriber_custom_fields[$field])) ?
|
|
ucfirst($subscriber_custom_fields[$field]) : $field;
|
|
}, $subscriber_fields);
|
|
}
|
|
|
|
function formatSubscriberData($subscriber) {
|
|
return array_map(function($field) use ($subscriber) {
|
|
return $subscriber[$field];
|
|
}, $this->subscriber_fields);
|
|
}
|
|
} |