Files
b2b.redline.com.pl/modules/newsletterpro/classes/NewsletterProTask.php
2025-06-24 14:14:35 +02:00

650 lines
20 KiB
PHP

<?php
/**
* Since 2013 Ovidiu Cimpean.
*
* Ovidiu Cimpean - Newsletter Pro © All rights reserved.
*
* DISCLAIMER
*
* Do not edit, modify or copy this file.
* If you wish to customize it, contact us at addons4prestashop@gmail.com.
*
* @author Ovidiu Cimpean <addons4prestashop@gmail.com>
* @copyright Since 2013 Ovidiu Cimpean
* @license Do not edit, modify or copy this file
*
* @version Release: 4
*/
class NewsletterProTask extends ObjectModel
{
public $id_newsletter_pro_smtp;
public $id_newsletter_pro_tpl_history;
public $date_start;
public $date_modified;
public $active;
public $template;
public $send_method;
public $started;
public $status;
public $sleep;
public $pause;
public $emails_count;
public $emails_error;
public $emails_success;
public $emails_completed;
public $done;
public $error_msg;
/**
* variables.
*/
private $log;
private $num_sent = 0;
/**
* This variable is used into the function send proccess.
*
* @var object
*/
private $task_step;
/**
* 24000.
*/
const MAX_EXECUTION_TIME = 24000;
const STATUS_DEFAULT = 0;
const WRITE_LOG = 0;
const STATUS_IN_PROGRESS = 1;
public static $definition = [
'table' => 'newsletter_pro_task',
'primary' => 'id_newsletter_pro_task',
'fields' => [
'id_newsletter_pro_smtp' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_newsletter_pro_tpl_history' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'date_start' => ['type' => self::TYPE_DATE, 'validate' => 'isDateFormat'],
'date_modified' => ['type' => self::TYPE_DATE, 'validate' => 'isDateFormat'],
'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'template' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true],
'send_method' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true],
'started' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'status' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'sleep' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
'pause' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'emails_count' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
'emails_error' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
'emails_success' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
'emails_completed' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
'done' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'error_msg' => ['type' => self::TYPE_HTML, 'validate' => 'isString'],
],
];
public function __construct($id = null)
{
// set defaults values
$this->active = 1;
$this->error_msg = serialize([]);
$this->log = [];
$this->one_mb = 1048576;
$this->memory_limit = (int) ini_get('memory_limit') * $this->one_mb;
$current_mem = memory_get_usage(true);
if ((int) ini_get('memory_limit') <= 0) {
$this->memory_limit = $current_mem + $this->one_mb * 24 + 1;
}
parent::__construct($id);
if (Validate::isLoadedObject($this)) {
$this->log(sprintf(NewsletterPro::getInstance()->l('Task id : %s'), $this->id));
$this->log(sprintf(NewsletterPro::getInstance()->l('Send method : %s'), $this->send_method));
$this->log(sprintf(NewsletterPro::getInstance()->l('Total emails : %s'), $this->emails_count));
$this->log(sprintf(NewsletterPro::getInstance()->l('Sent success : %s'), $this->emails_success));
$this->log(sprintf(NewsletterPro::getInstance()->l('Sent errors : %s'), $this->emails_error));
$this->log(sprintf(NewsletterPro::getInstance()->l('Sent completed : %s')."\n", $this->emails_completed));
} else {
$this->log(NewsletterPro::getInstance()->l('No task has been loaded.'));
}
}
public static function newInstance($id = null)
{
return new self($id);
}
public function add($autodate = true, $null_values = false)
{
return parent::add($autodate, $null_values);
}
public function update($null_values = false)
{
$this->date_modified = date('Y-m-d H:i:s');
return parent::update($null_values);
}
public function updateFields($fields = [])
{
$fields_default = [
'date_modified' => pSQL(date('Y-m-d H:i:s')),
];
return Db::getInstance()->update('newsletter_pro_task', array_merge($fields_default, $fields), '`id_newsletter_pro_task` = '.(int) $this->id);
}
public function delete()
{
$evaluate = NewsletterProEvaluate::newInstance();
foreach ($this->getStepsIds() as $id) {
$task_step = NewsletterProTaskStep::newInstance($id);
$evaluate->add($task_step->delete());
}
return parent::delete() && $evaluate->success();
}
public function log($value)
{
$this->log[] = $value;
}
public function displayLog($separator = "\n")
{
echo '<pre>';
echo join($separator, $this->log).$separator;
echo '</pre>';
return $this;
}
public function uniqueLog()
{
$this->log = array_unique($this->log);
return $this;
}
public function emptyLog()
{
$this->log = [];
return $this;
}
public function getStepsIds($step_active = false, $limit = 0)
{
$results = Db::getInstance()->executeS('
SELECT `id_newsletter_pro_task_step` FROM `'._DB_PREFIX_.'newsletter_pro_task_step`
WHERE `id_newsletter_pro_task` = '.(int) $this->id.'
'.($step_active ? ' AND `step_active` = 1' : '').'
ORDER BY `id_newsletter_pro_task_step`
'.($limit > 0 ? ' LIMIT '.(int) $limit : '').'
');
return array_map([$this, 'getStepsIdsCallback'], $results);
}
private function getStepsIdsCallback($row)
{
return !empty($row) ? $row['id_newsletter_pro_task_step'] : false;
}
/**
* @param DateTime|string|null $dateTime
*
* @return NewsletterProTask|false
*/
public static function getTask($dateTime = null)
{
if (is_null($dateTime)) {
$dateTime = new DateTime('now');
} else {
if (is_string($dateTime)) {
$dateTime = new DateTime($dateTime);
} elseif (!($dateTime instanceof DateTime)) {
throw new Exception('Invalid task date.');
}
}
$id = (int) Db::getInstance()->getValue('
SELECT `id_newsletter_pro_task` FROM `'._DB_PREFIX_.'newsletter_pro_task`
WHERE `date_start` <= "'.pSQL($dateTime->format('Y-m-d H:i:s')).'"
AND (
DATE(`date_start`) = "'.pSQL($dateTime->format('Y-m-d')).'" OR
`started` = 1
)
AND `active` = 1
AND `done` = 0
ORDER BY `date_start` ASC;
');
$task = new self($id);
if (Validate::isLoadedObject($task)) {
return $task;
}
return false;
}
/**
* Sent the task.
*
* @return int Number of newsletters sent
*/
public function send()
{
if (0 == (int) $this->started) {
$this->started = 1;
$this->update();
}
ignore_user_abort(true);
set_time_limit(0);
@ini_set('max_execution_time', self::MAX_EXECUTION_TIME);
register_shutdown_function([$this, 'sendShutdownFunctionCallback']);
$tlog = _NEWSLETTER_PRO_DIR_.'/logs/task.log';
// update task status
$this->status = self::STATUS_IN_PROGRESS;
$this->update();
$this->task_step = $this->getCurrentStep();
if ($this->task_step) {
$emails_to_send = $this->task_step->getEmailsToSend();
$emails_sent = $this->task_step->getEmailsSent();
foreach ($emails_to_send as $index => $email) {
if ((bool) pqnp_config('TASK_MEMORY_CHECK_ENABLED')) {
$current_mem = memory_get_usage(true);
if ($current_mem + $this->one_mb * 24 >= $this->memory_limit) {
exit('Exit during the memory limt. If the tasks don\'t start sending try to put this command "configuration -set TASK_MEMORY_CHECK_ENABLED 0" into the Terminal tab and press enter.');
exit;
}
}
if (!$this->taskExists()) {
exit;
}
if ($this->isTaskPaused()) {
exit;
}
$template = NewsletterProTemplate::newHistory($this->id_newsletter_pro_tpl_history, $email)->load();
if ($template->user) {
// exclude emails
$exclude_email = false;
$to_info = NewsletterProMail::getEmailInfo($template->user->to());
$to_email = $to_info['email'];
if (NewsletterProEmailExclusion::newInstance()->emailExists($to_email)) {
$exclude_email = true;
}
// build forwarders
$forward = NewsletterProSendManager::buildForward($this->id_newsletter_pro_tpl_history, 'history', $template->user->to());
$message = $template->message();
$title = $message['title'];
$body = $message['body'];
$send_manager = NewsletterProSendManager::getInstance();
$send_manager->setTemplateNameForAttachment($template->name);
try {
$send = $send_manager->sendNewsletter($title, $body, $template->user->to(), [
'user' => $template->user,
'id_smtp' => (int) $this->id_newsletter_pro_smtp,
'send_method' => $this->send_method,
], $forward, false, $exclude_email);
} catch (Exception $e) {
$send = [$e->getMessage()];
// $send = false;
}
if (!is_array($send) && true == $send) {
$emails_sent[] = [
'email' => $template->user->email,
'status' => true,
];
++$this->emails_success;
++$this->num_sent;
$this->log(NewsletterPro::getInstance()->l('Success').' : '.(string) $template->user->email);
if (self::WRITE_LOG) {
$msgSucc = NewsletterPro::getInstance()->l('Success').' : '.(string) $template->user->email;
if ($h = fopen($tlog, 'a+')) {
fwrite($h, date('Y-m-d H:i:s').' '.$msgSucc."\n");
fclose($h);
}
}
} else {
$emails_sent[] = [
'email' => $template->user->email,
'status' => false,
];
++$this->emails_error;
$this->appendError([
'smtp' => join('<br>', $send),
]);
$this->log(NewsletterPro::getInstance()->l('Error').' : '.join("\n", $send).' : '.(string) $template->user->email);
if (self::WRITE_LOG) {
$msgError = NewsletterPro::getInstance()->l('Error').' : '.join("\n", $send).' : '.(string) $template->user->email;
if ($h = fopen($tlog, 'a+')) {
fwrite($h, date('Y-m-d H:i:s').' '.$msgError."\n");
fclose($h);
}
}
}
unset($emails_to_send[$index]);
$this->task_step->setEmailsToSend($emails_to_send);
$this->task_step->setEmailsSent($emails_sent);
$this->task_step->step_active = (count($emails_to_send) > 0 ? 1 : 0);
if (!$exclude_email && (int) $this->sleep > 0) {
sleep((int) $this->sleep);
}
} else {
$this->appendError([
'email' => pSQL(sprintf(NewsletterPro::getInstance()->l('The email %s does not exists in the database.'), $email)),
]);
}
++$this->emails_completed;
if (0 == $this->emails_completed % (int) NewsletterPro::getInstance()->ini_config['task_write_db_limit']) {
$this->task_step->update();
$this->updateFields([
'emails_completed' => (int) $this->emails_completed,
'emails_success' => (int) $this->emails_success,
'emails_error' => (int) $this->emails_error,
]);
}
}
// end the script if it's the case
if ($this->emails_completed > $this->emails_count) {
return $this->endSend();
}
// update when the step is done
$this->task_step->update();
$this->updateFields([
'emails_completed' => (int) $this->emails_completed,
'emails_success' => (int) $this->emails_success,
'emails_error' => (int) $this->emails_error,
]);
// get next step emails
return $this->send();
}
return $this->endSend();
}
private function endSend()
{
$this->done = 1;
$this->update();
return $this->num_sent;
}
public function sendShutdownFunctionCallback()
{
$ob_content = ob_get_contents();
@ob_end_clean();
if (preg_match('/Fatal error/', $ob_content) && preg_match('/Maximum execution time/', $ob_content)) {
$this->displayLog()->emptyLog();
echo '<pre>';
echo "\n";
echo NewsletterPro::getInstance()->l('PHP max execution time exceeded. Access the script again to continue the sending process.');
echo '</pre>';
} else {
echo $ob_content;
}
if (Validate::isLoadedObject($this)) {
$this->status = self::STATUS_DEFAULT;
$this->update();
}
if (isset($this->task_step) && Validate::isLoadedObject($this->task_step)) {
$this->task_step->update();
}
}
public function sendTaskAjax()
{
$errors = [];
if (NewsletterProTask::taskInProgress()) {
$errors[] = NewsletterPro::getInstance()->l('Cannot send multiple tasks in the same time.');
}
@ob_end_clean();
@ob_start();
echo Tools::jsonEncode([
'status' => empty($errors),
'errors' => $errors,
]);
$size = ob_get_length();
header("Content-Length: $size");
header('Connection: close');
@ob_end_flush();
@ob_flush();
flush();
if (session_id()) {
session_write_close();
}
if (empty($errors)) {
// run in background
$this->send();
}
}
public function isInProgress()
{
return $this->status;
}
/**
* The the current task step or the next step if the current step does not have emails addresses.
*
* @return object/boolean
*/
public function getCurrentStep()
{
$steps_id = $this->getStepsIds(true, 1);
if ($steps_id) {
$task_step = NewsletterProTaskStep::newInstance($steps_id[0]);
$emails_to_send = $task_step->getEmailsToSend();
if (empty($emails_to_send)) {
$task_step->step_active = 0;
// if there are no step emails, write step_active = 0
if ($task_step->update()) {
return $this->getCurrentStep();
}
} else {
return $task_step;
}
} else {
// if there are no steps, siable the task
$this->activ = 0;
$this->update();
}
return false;
}
/**
* Task exist.
*
* @param int $id
*
* @return int
*/
public function taskExists()
{
return Db::getInstance()->getValue(
'
SELECT count(*) FROM `'._DB_PREFIX_.'newsletter_pro_task` WHERE `id_newsletter_pro_task`='.(int) $this->id
);
}
/**
* Is task paused.
*
* @param int $id
*
* @return bool
*/
public function isTaskPaused()
{
$this->pause = (int) Db::getInstance()->getValue(
'
SELECT `pause` FROM `'._DB_PREFIX_.'newsletter_pro_task` WHERE `id_newsletter_pro_task`='.(int) $this->id
);
return $this->pause;
}
private function appendError($error_msg)
{
$error_msg_db = NewsletterProTools::unSerialize($this->error_msg);
$error_msg_write = array_merge($error_msg_db, $error_msg);
$this->error_msg = serialize($error_msg_write);
return $this->update();
}
/**
* Task in progress.
*
* @return bool
*/
public static function taskInProgress()
{
return Db::getInstance()->getValue('
SELECT count(*) FROM `'._DB_PREFIX_.'newsletter_pro_task`
WHERE `status` = '.(int) self::STATUS_IN_PROGRESS.'
AND `done` = 0
') ? true : false;
}
public static function getTaskInProgress()
{
$task_id = (int) Db::getInstance()->getValue('
SELECT `id_newsletter_pro_task` FROM `'._DB_PREFIX_.'newsletter_pro_task`
WHERE `status` = '.(int) self::STATUS_IN_PROGRESS.'
AND `done` = 0
');
$task = NewsletterProTask::newInstance($task_id);
if (Validate::isLoadedObject($task)) {
if ($task->getCurrentStep()) {
return $task;
}
}
return false;
}
public function pauseTask()
{
return (int) Db::getInstance()->update('newsletter_pro_task', [
'pause' => 1,
], '`id_newsletter_pro_task` = '.(int) $this->id, 1);
}
public static function exportPrivacy($email)
{
$response = new NewsletterProPrivacyDataResponse(NewsletterProPrivacyDataResponse::TYPE_EXPORT, 'newsletter_pro_task', $email);
try {
// nothing to export
} catch (Exception $e) {
$response->addException($e);
}
return $response;
}
public static function privacySerach($email)
{
$response = new NewsletterProPrivacyDataResponse(NewsletterProPrivacyDataResponse::TYPE_SEARCH, 'newsletter_pro_task', $email);
try {
$count = (int) Db::getInstance()->getValue('
SELECT COUNT(*) FROM `'._DB_PREFIX_.'newsletter_pro_task`
WHERE `error_msg` REGEXP "'.pSQL(preg_quote($email)).'[^A-Za-z0-9]"
');
$response->addToCount($count);
} catch (Exception $e) {
$response->addException($e);
}
return $response;
}
public static function clearPrivacy($email)
{
$response = new NewsletterProPrivacyDataResponse(NewsletterProPrivacyDataResponse::TYPE_CLEAR, 'newsletter_pro_task', $email);
try {
if (Db::getInstance()->update('newsletter_pro_task', [
'error_msg' => serialize([]),
], '`error_msg` REGEXP "'.pSQL(preg_quote($email)).'[^A-Za-z0-9]"')) {
$response->addToCount(Db::getInstance()->Affected_Rows());
}
} catch (Exception $e) {
$response->addException($e);
}
return $response;
}
}