# acymailing/11.1.0/back/Classes/FollowupClass.php

AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress, version 11.1.0. 732 lines.

- Page: https://pluginprobe.com/plugins/acymailing/11.1.0/code/back/Classes/FollowupClass.php
- Raw: https://pluginprobe.com/plugins/acymailing/11.1.0/raw/back/Classes/FollowupClass.php
- Modified: 2026-09-03T09:36:36+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/acymailing/11.1.0/code/back/Classes/FollowupClass.php#L10-L20`.

```php
<?php

namespace AcyMailing\Classes;

use AcyMailing\Helpers\PaginationHelper;
use AcyMailing\Core\AcymClass;

class FollowupClass extends AcymClass
{
    const DEFAULT_DELAY_UNIT = 86400;
    const DELAY_UNIT = [
        60 => 'ACYM_MINUTES',
        3600 => 'ACYM_HOURS',
        86400 => 'ACYM_DAYS',
        604800 => 'ACYM_WEEKS',
        2628000 => 'ACYM_MONTHS',
    ];

    public function __construct()
    {
        parent::__construct();

        $this->table = 'followup';
        $this->pkey = 'id';
    }

    public function getDelayUnits(): array
    {
        $return = self::DELAY_UNIT;
        foreach ($return as $key => $value) {
            $return[$key] = acym_translation($value);
        }

        return $return;
    }

    public function getMatchingElements(array $settings = []): array
    {
        $query = 'SELECT `followup`.*, COUNT(`mails`.`mail_id`) AS nbEmails 
                FROM #__acym_followup AS followup 
                LEFT JOIN #__acym_followup_has_mail AS mails 
                    ON followup.id = mails.followup_id';

        $queryCount = 'SELECT COUNT(*) AS total FROM #__acym_followup AS followup ';

        $filters = [];

        if (!empty($settings['search'])) {
            $filters[] = 'followup.name LIKE '.acym_escapeDB('%'.$settings['search'].'%');
        }

        if (!empty($filters)) {
            $query .= ' WHERE ('.implode(') AND (', $filters).')';
            $queryCount .= ' WHERE ('.implode(') AND (', $filters).')';
        }

        $query .= ' GROUP BY `followup`.`id` ';

        if (empty($settings['ordering'])) $settings['ordering'] = $this->pkey;
        $query .= ' ORDER BY `'.acym_secureDBColumn($settings['ordering']).'`';
        if (!empty($settings['ordering_sort_order'])) $query .= ' '.acym_secureDBColumn(strtoupper($settings['ordering_sort_order']));

        if (empty($settings['offset']) || $settings['offset'] < 0) {
            $settings['offset'] = 0;
        }

        if (empty($settings['elementsPerPage']) || $settings['elementsPerPage'] < 1) {
            $pagination = new PaginationHelper();
            $settings['elementsPerPage'] = $pagination->getListLimit();
        }

        $results = [];
        $results['elements'] = acym_loadObjectList($query, '', $settings['offset'], $settings['elementsPerPage']);
        $urlClickClass = new UrlClickClass();
        foreach ($results['elements'] as $key => $oneFollowup) {
            $results['elements'][$key]->subscribers = $this->getNumberSubscribersByListId($oneFollowup->list_id);
            $this->getGlobalStats($results['elements'][$key], $urlClickClass);
        }

        $results['total'] = acym_loadObject($queryCount);

        return $results;
    }

    public function getNumberSubscribersByListId(int $listId = 0, bool $onlySubscribed = false): int
    {
        if (empty($listId)) return 0;

        $query = 'SELECT COUNT(*) 
                FROM #__acym_user_has_list 
                WHERE `list_id` = '.intval($listId);

        if ($onlySubscribed) {
            $query .= ' AND `status` = 1';
        }

        return (int)acym_loadResult($query);
    }

    private function getGlobalStats(object $element, UrlClickClass $urlClickClass): void
    {
        $mailsStats = acym_loadObjectList(
            'SELECT mail_stat.* 
            FROM #__acym_mail_stat AS mail_stat 
            JOIN #__acym_followup_has_mail AS fhm ON fhm.mail_id = mail_stat.mail_id 
            WHERE fhm.followup_id = '.intval($element->id)
        );

        $element->open = 0;
        $element->click = 0;
        $element->income = 0;
        $numberMailSent = 0;
        if (empty($mailsStats)) return;

        foreach ($mailsStats as $key => $mailsStat) {
            $element->open += $mailsStat->open_unique;
            $element->click += $urlClickClass->getNumberUsersClicked([$mailsStat->mail_id]);
            $numberMailSent += $mailsStat->sent;
        }

        if (!empty($numberMailSent)) {
            $element->open = number_format($element->open / $numberMailSent * 100, 2);
            $element->click = number_format($element->click / $numberMailSent * 100, 2);
        }

        //Tracking sales
        if (!acym_isTrackingSalesActive()) return;

        $trackingSales = acym_loadObject(
            'SELECT SUM(user_stat.tracking_sale) AS sale, user_stat.currency 
            FROM #__acym_user_stat AS user_stat 
            JOIN #__acym_followup_has_mail AS fhm ON fhm.mail_id = user_stat.mail_id
            WHERE fhm.followup_id = '.intval($element->id)
        );

        $element->sale = $trackingSales->sale;
        if (empty($element->currency)) {
            $element->currency = '';
        }

        acym_trigger('getCurrency', [&$element->currency]);
    }

    public function delete(array $elements): int
    {
        acym_arrayToInteger($elements);

        if (empty($elements)) return 0;

        $emailIds = $this->getEmailsByIds($elements);
        if (!empty($emailIds)) {
            $mailClass = new MailClass();
            $mailClass->delete($emailIds);
        }

        $listIds = $this->getListsByIds($elements);

        $result = parent::delete($elements);

        $listClass = new ListClass();
        $listClass->delete($listIds);

        return $result;
    }

    public function getEmailsByIds(array $followupIds): array
    {
        acym_arrayToInteger($followupIds);

        if (empty($followupIds)) {
            return [];
        }

        return acym_loadResultArray(
            'SELECT `mail_id` 
            FROM #__acym_followup_has_mail 
            WHERE `followup_id` IN ('.implode(', ', $followupIds).')
            ORDER BY `delay`*`delay_unit` ASC'
        );
    }

    public function getListsByIds(array $followupIds): array
    {
        acym_arrayToInteger($followupIds);

        if (empty($followupIds)) {
            return [];
        }

        return acym_loadResultArray(
            'SELECT `list_id` 
            FROM #__acym_followup
            WHERE `id` IN ('.implode(', ', $followupIds).')'
        );
    }

    public function getOneById(int $id): ?object
    {
        $followup = parent::getOneById($id);

        if (empty($followup)) return null;

        if (!empty($followup->condition)) {
            $followup->condition = json_decode($followup->condition, true);
        }

        if (!empty($followup->loop_mail_skip)) {
            $followup->loop_mail_skip = json_decode($followup->loop_mail_skip, true);
        }

        return $followup;
    }

    public function getOneByListId(int $listId): ?object
    {
        $followup = acym_loadObject('SELECT * FROM `#__acym_followup` WHERE `list_id` = '.intval($listId));

        if (empty($followup)) {
            return null;
        }

        if (!empty($followup->condition)) {
            $followup->condition = json_decode($followup->condition, true);
        }

        return $followup;
    }

    public function save(object $element): ?int
    {
        if (!empty($element->condition) && is_array($element->condition)) {
            $element->condition = json_encode($element->condition);
        }

        if (!empty($element->loop_mail_skip) && is_array($element->loop_mail_skip)) {
            $element->loop_mail_skip = json_encode($element->loop_mail_skip);
        }

        $this->updateListFollowup($element);

        return parent::save($element);
    }

    private function updateListFollowup(object $element): void
    {
        $listClass = new ListClass();
        if (empty($element->list_id)) {
            $list = new \stdClass();
            $list->name = $element->display_name;
            $list->description = '';
            $list->color = '#'.implode('', [
                    ListClass::COLOR_PARTS[acym_rand(0, 15)],
                    ListClass::COLOR_PARTS[acym_rand(0, 15)],
                    ListClass::COLOR_PARTS[acym_rand(0, 15)],
                    ListClass::COLOR_PARTS[acym_rand(0, 15)],
                    ListClass::COLOR_PARTS[acym_rand(0, 15)],
                    ListClass::COLOR_PARTS[acym_rand(0, 15)],
                ]);
            $list->access = '';
            $list->type = ListClass::LIST_TYPE_FOLLOWUP;
        } else {
            $list = $listClass->getOneById($element->list_id);
            $list->name = $element->display_name;
        }

        $element->list_id = $listClass->save($list);
    }

    public function getConditionSummary(object $followup): array
    {
        $condition = $followup->condition;
        $statusArray = [
            'is' => acym_strtolower(acym_translation('ACYM_IS')),
            'is_not' => acym_strtolower(acym_translation('ACYM_IS_NOT')),
        ];
        $listClass = new ListClass();
        $segmentClass = new SegmentClass();

        $return = [];
        if (empty($condition['lists_status']) || empty($condition['lists'])) {
            $return[] = acym_translation('ACYM_NO_CONDITION_USER_SUBSCRIPTION');
        } else {
            $lists = $listClass->getListsByIds($condition['lists']);
            $listsToDisplay = [];
            foreach ($lists as $list) {
                $listsToDisplay[] = $list->name;
            }

            $translationKey = $followup->trigger === 'user_subscribe' ? 'ACYM_X_SUBSCRIBING_X_LIST' : 'ACYM_X_SUBSCRIBED_X_LIST';
            $return[] = acym_translationSprintf($translationKey, acym_strtolower($statusArray[$condition['lists_status']]), implode(',', $listsToDisplay));
        }


        if (empty($condition['segments_status']) || empty($condition['segments'])) {
            $return[] = acym_translation('ACYM_NO_CONDITION_SEGMENT');
        } else {
            $segments = $segmentClass->getByIds($condition['segments']);
            $segmentsToDisplay = [];
            foreach ($segments as $segment) {
                $segmentsToDisplay[] = $segment->name;
            }
            $return[] = acym_translationSprintf('ACYM_X_PART_X_SEGMENT', acym_strtolower($statusArray[$condition['segments_status']]), implode(',', $segmentsToDisplay));
        }

        acym_trigger('getFollowupConditionSummary', [&$return, $condition, $followup->trigger, $statusArray]);

        return $return;
    }

    private function getKeyMailArray(int $key, array $mailsKey): int
    {
        if (!empty($mailsKey) && in_array($key, $mailsKey)) {
            return $this->getKeyMailArray(++$key, $mailsKey);
        }

        return $key;
    }

    public function getOneByIdWithMails(int $id): ?object
    {
        $followup = $this->getOneById($id);

        if (empty($followup)) {
            return null;
        }

        $mailClass = new MailClass();
        $mails = $mailClass->decode(
            acym_loadObjectList(
                'SELECT mail.subject, mail.id, followup_mail.delay, followup_mail.delay_unit 
                FROM #__acym_mail AS mail 
                JOIN #__acym_followup_has_mail AS followup_mail ON mail.id = followup_mail.mail_id AND followup_mail.followup_id = '.intval($id),
                'id'
            )
        );

        $return = [];
        foreach ($mails as $key => $mail) {
            $mail->delay_display = $this->getDelayDisplay($mail->delay, $mail->delay_unit);
            $mail->edit_link = acym_completeLink('mails&task=edit&step=editEmail&type=followup&id='.$key.'&followup_id='.$id.'&return='.urlencode(acym_currentURL()), false, true);
            $finalKey = $this->getKeyMailArray(intval($mail->delay) * intval($mail->delay_unit), array_keys($return));
            $return[$finalKey] = $mail;
        }

        ksort($return);

        $followup->mails = empty($return) ? [] : $return;

        return $followup;
    }

    private function getDelayDisplay(int $delay, int $delayUnit): string
    {
        $delayUnits = $this->getDelayUnits();

        return acym_translationSprintf('ACYM_X_PLUS_X_FOLLOW_UP', $delayUnits[$delayUnit], $delay);
    }

    public function getDelaySettingToMail(object $mail, int $followupId): bool
    {
        $settings = acym_loadObject('SELECT delay, delay_unit FROM #__acym_followup_has_mail WHERE followup_id = '.intval($followupId).' AND mail_id = '.intval($mail->id));

        if (empty($settings)) {
            return false;
        }

        $mail->delay = $settings->delay;
        $mail->delay_unit = $settings->delay_unit;

        return true;
    }

    public function saveDelaySettings(array $followupData, int $mailId): bool
    {
        if (empty($followupData['id']) || empty($followupData['delay_unit']) || empty($mailId)) {
            return false;
        }

        acym_arrayToInteger($followupData);

        $affectedRow = acym_query(
            'INSERT INTO #__acym_followup_has_mail (`mail_id`, `followup_id`, `delay`, `delay_unit`) VALUE ('.intval(
                $mailId
            ).', '.$followupData['id'].', '.$followupData['delay'].', '.$followupData['delay_unit'].') ON DUPLICATE KEY UPDATE delay = '.$followupData['delay'].', delay_unit = '.$followupData['delay_unit'].''
        );

        return $affectedRow !== false;
    }

    public function duplicateMail(int $mailId, int $id): bool
    {
        if (empty($mailId) || empty($id)) {
            return false;
        }

        $mailClass = new MailClass();
        $mail = $mailClass->getOneById($mailId);

        if (empty($mail)) {
            return false;
        }

        $delaySettings = new \stdClass();
        $delaySettings->id = $mail->id;
        $this->getDelaySettingToMail($delaySettings, $id);

        unset($mail->id);
        unset($mail->creator_id);
        $mail->name .= '_copy';

        $mail->id = $mailClass->save($mail);
        if (empty($mail->id)) {
            return false;
        }

        $affectedRow = acym_query(
            'INSERT INTO #__acym_followup_has_mail (`mail_id`, `followup_id`, `delay`, `delay_unit`) VALUE ('.intval($mail->id).', '.$id.', '.intval(
                $delaySettings->delay
            ).', '.intval($delaySettings->delay_unit).')'
        );

        return !empty($affectedRow);
    }

    public function deleteMail(int $mailId): bool
    {
        if (empty($mailId)) {
            return false;
        }

        $mailClass = new MailClass();

        return (bool)$mailClass->delete([$mailId]);
    }

    public function getFollowupsWithMailsInfoByIds(array $followupIds): array
    {
        acym_arrayToInteger($followupIds);

        $mailsInfo = acym_loadObjectList(
            'SELECT followup_mail.*, followup.send_once
                  FROM #__acym_followup_has_mail AS followup_mail
                  JOIN #__acym_followup AS followup ON followup.id = followup_mail.followup_id AND followup_mail.followup_id IN ('.implode(',', $followupIds).')'
        );

        if (empty($mailsInfo)) {
            return [];
        }

        $return = [];

        foreach ($mailsInfo as $mailInfo) {
            if (!isset($return[$mailInfo->followup_id])) $return[$mailInfo->followup_id] = [];
            $return[$mailInfo->followup_id][] = $mailInfo;
        }

        return $return;
    }

    public function subscribeUserToFollowupList(array $followupIds, $userId): array
    {
        acym_arrayToInteger($followupIds);

        $followupLists = acym_loadObjectList(
            'SELECT followup.list_id, IF(user_list.status IS NULL, "", status) AS status, followup.id, followup.send_once FROM #__acym_followup AS followup
             LEFT JOIN #__acym_user_has_list AS user_list ON followup.list_id = user_list.list_id AND (user_list.user_id = '.intval($userId).' OR user_list.user_id IS NULL)
             WHERE followup.id IN ('.implode(',', $followupIds).')',
            'list_id'
        );

        if (empty($followupLists)) {
            return [];
        }

        $followupListIds = [];
        foreach ($followupLists as $listId => $followupList) {
            // The user unsubscribed, don't re-subscribe them
            if (in_array($followupList->status, [0, '0'], true)) {
                continue;
            }

            if ($followupList->status === '' || $followupList->send_once == 0) {
                $followupListIds[$listId] = $followupList->id;
            }
        }

        if (empty($followupListIds)) {
            return [];
        }

        $userClass = new UserClass();
        $userClass->subscribe([$userId], array_keys($followupListIds), false);

        return $followupListIds;
    }

    public function addFollowupEmailsQueue(string $followupTrigger, int $userId, array $params = []): bool
    {
        if (empty($followupTrigger) || empty($userId)) {
            return false;
        }

        $followupToTrigger = acym_loadObjectList('SELECT * FROM #__acym_followup WHERE active = 1 AND `trigger` = '.acym_escapeDB($followupTrigger), 'id');

        if (empty($followupToTrigger)) {
            return false;
        }

        foreach ($followupToTrigger as $key => $followup) {
            if (!empty($followup->condition)) {
                $followupToTrigger[$key]->condition = json_decode($followup->condition, true);
            }
        }

        acym_trigger('matchFollowupsConditions', [&$followupToTrigger, $userId, $params]);

        if (empty($followupToTrigger)) {
            return false;
        }

        $followupIds = $this->subscribeUserToFollowupList(array_keys($followupToTrigger), $userId);

        if (empty($followupIds)) {
            return false;
        }

        $followups = $this->getFollowupsWithMailsInfoByIds($followupIds);

        if (empty($followups)) {
            return false;
        }

        $priority = $this->config->get('followup_max_priority', 0) == 1 ? 1 : 2;

        $valuesToInsert = [];

        foreach ($followups as $followupId => $mailsInfo) {
            foreach ($mailsInfo as $mailInfo) {
                $sendDate = time() + (intval($mailInfo->delay) * intval($mailInfo->delay_unit));
                $sendDate = acym_date($sendDate, 'Y-m-d H:i:s', false);
                $valuesToInsert[] = ' ('.intval($mailInfo->mail_id).', '.intval($userId).', '.acym_escapeDB($sendDate).', '.intval($priority).', '.acym_escapeDB(
                        json_encode($params)
                    ).') ';
                $this->addMailStat($mailInfo->mail_id);
            }
            $followupToTrigger[$followupId]->last_trigger = time();
            $this->save($followupToTrigger[$followupId]);
        }

        $query = 'INSERT IGNORE INTO #__acym_queue (`mail_id`, `user_id`, `sending_date`, `priority`, `params`) VALUES '.implode(',', $valuesToInsert);

        $affectedRows = acym_query($query);

        return !empty($affectedRows);
    }

    private function addMailStat(int $mailId): void
    {
        $mailStatClass = new MailStatClass();
        $mailStat = $mailStatClass->getOneRowByMailId($mailId);
        $newMailStat = new \stdClass();
        $newMailStat->mail_id = intval($mailId);
        $newMailStat->total_subscribers = 1;

        if (empty($mailStat)) {
            $newMailStat->send_date = acym_date('now', 'Y-m-d H:i:s', false);
        }

        $mailStatClass->save($newMailStat);
    }

    public function getFollowupDailyBases(): array
    {
        $triggers = [];
        acym_trigger('onAcymGetFollowupDailyBases', [&$triggers]);

        if (empty($triggers)) {
            return [];
        }

        $safeTriggers = array_map('acym_escapeDB', $triggers);

        return acym_loadObjectList('SELECT * FROM #__acym_followup WHERE `trigger` IN ('.implode(',', $safeTriggers).') AND active  = 1');
    }

    public function queueForSubscribers(int $emailId): ?int
    {
        if (empty($emailId)) {
            return null;
        }

        $mailInfo = acym_loadObject(
            'SELECT map.*, followup.list_id 
            FROM #__acym_followup_has_mail AS map 
            JOIN #__acym_followup AS followup 
                ON followup.id = map.followup_id 
            WHERE map.mail_id = '.intval($emailId)
        );
        if (empty($mailInfo)) return null;

        $this->addMailStat($mailInfo->mail_id);

        $delay = intval($mailInfo->delay) * intval($mailInfo->delay_unit);
        $query = 'INSERT IGNORE INTO #__acym_queue (`mail_id`, `user_id`, `sending_date`, `priority`) 
                SELECT '.intval($mailInfo->mail_id).', user_id, TIMESTAMPADD(SECOND, '.$delay.', subscription_date), 2
                FROM #__acym_user_has_list 
                WHERE status = 1 
                    AND list_id = '.intval($mailInfo->list_id);

        $addedEmails = acym_query($query);

        return $addedEmails === false || $addedEmails === null ? null : intval($addedEmails);
    }

    public function getXFollowups(array $options): array
    {
        $limit = $options['limit'] ?? 10;
        $offset = $options['offset'] ?? 0;
        $filters = $options['filters'] ?? [];

        $conditions = [];
        foreach ($filters as $column => $filter) {
            switch ($column) {
                case 'id':
                case 'active':
                case 'send_one':
                    $conditions[] = acym_secureDBColumn($column).' = '.intval($filter);
                    break;
                case 'name':
                case 'trigger':
                case 'display_name':
                    $conditions[] = '`'.acym_secureDBColumn($column).'` LIKE '.acym_escapeDB('%'.$filter.'%');
                    break;
                default:
                    $conditions[] = acym_secureDBColumn($column).' = '.acym_escapeDB('%'.$filter.'%');
            }
        }

        $query = 'SELECT * FROM #__acym_followup ';

        if (!empty($conditions)) {
            $query .= ' WHERE '.implode(' AND ', $conditions);
        }

        $followUps = acym_loadObjectList($query, $this->pkey, $offset, $limit);

        if (empty($followUps)) {
            return [];
        }

        foreach ($followUps as &$followUp) {
            if (!empty($followUp->condition)) {
                $followUp->condition = json_decode($followUp->condition, true);
            }
        }

        return $followUps;
    }

    /**
     * @param string $mailId
     *
     * @return object|null
     */
    public function getOneByMailId(int $mailId): ?object
    {
        return acym_loadObject(
            'SELECT followup.* 
            FROM #__acym_followup AS followup
            JOIN #__acym_followup_has_mail AS map
                 ON followup.id = map.followup_id
            WHERE mail_id = '.intval($mailId)
        );
    }

    /**
     * @param $followupId
     *
     * @return object
     */
    public function getLastEmail(int $followupId): object
    {
        return acym_loadObject(
            'SELECT mail_id as id, delay * delay_unit as totalDelay FROM #__acym_followup_has_mail WHERE followup_id = '.intval($followupId).' ORDER BY totalDelay DESC'
        );
    }

    /**
     * @param int   $followUpId
     * @param int   $userId
     * @param int   $additionalDelay
     * @param array $mailIdToSkip
     *
     * @return void
     */
    public function triggerFollowUp(int $followUpId, int $userId, int $additionalDelay = 0, array $mailIdToSkip = []): void
    {
        $followUp = $this->getOneById($followUpId);

        if (empty($followUp)) {
            return;
        }

        $values = intval($userId).', '.intval($followUp->list_id).', 1, '.acym_escapeDB(acym_date(time(), 'Y-m-d H:i:s'));

        acym_query('INSERT IGNORE INTO #__acym_user_has_list (`user_id`, `list_id`, `status`, `subscription_date`) VALUES ('.$values.') ON DUPLICATE KEY UPDATE status = 1');

        $followups = $this->getFollowupsWithMailsInfoByIds([$followUpId]);
        $allValues = [];
        foreach ($followups as $mails) {
            foreach ($mails as $mail) {
                if (in_array($mail->mail_id, $mailIdToSkip)) {
                    continue;
                }
                $sendDate = time() + (intval($mail->delay) * intval($mail->delay_unit)) + $additionalDelay;
                $sendDate = acym_escapeDB(acym_date($sendDate, 'Y-m-d H:i:s', false));
                $allValues[] = '('.intval($mail->mail_id).', '.intval($userId).', '.$sendDate.', '.$this->config->get('priority_newsletter', 3).', 0'.')';
            }
        }

        if (empty($allValues)) {
            return;
        }

        $queryToProcess = 'INSERT IGNORE INTO #__acym_queue (`mail_id`, `user_id`, `sending_date`, `priority`, `try`) VALUES '.implode(', ', $allValues);

        if (empty(acym_query($queryToProcess))) {
            acym_logError("Error adding follow up email to queue \n Values: ".implode(', ', $allValues)." \n Error: ".acym_getDBError());
        }
    }
}

```
