# acymailing/11.1.0/back/Core/AcymPlugin.php

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

- Page: https://pluginprobe.com/plugins/acymailing/11.1.0/code/back/Core/AcymPlugin.php
- Raw: https://pluginprobe.com/plugins/acymailing/11.1.0/raw/back/Core/AcymPlugin.php
- Modified: 2026-09-24T14:59:06+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/Core/AcymPlugin.php#L10-L20`.

```php
<?php

namespace AcyMailing\Core;

use AcyMailing\Classes\CampaignClass;
use AcyMailing\Classes\PluginClass;
use AcyMailing\Helpers\PluginHelper;
use Joomla\CMS\HTML\HTMLHelper;

class AcymPlugin extends AcymObject
{
    // The row placeholders are replaced by the JS when a row is added to the form
    const CONDITION_FIELD_NAME = 'acym_condition[conditions][__numor__][__numand__]';
    const FILTER_FIELD_NAME = 'acym_action[filters][__numor__][__numand__]';
    const MAILBOX_ACTION_FIELD_NAME = 'acym_action[__num__]';

    public string $cms = 'all';
    public string $name = '';
    public bool $installed = true;

    protected array $addonDefinition;
    protected PluginHelper $pluginHelper;

    protected int $rootCategoryId = 1;
    protected array $categories;
    protected array $catvalues = [];
    private array $cats = [];
    private array $subCategories = [];

    protected array $tagvalues = [];

    protected array $tags = [];
    protected object $pageInfo;
    protected array $searchFields = [];
    protected string $querySelect = '';
    protected string $query = '';
    protected array $filters = [];
    protected string $elementIdTable = '';
    protected string $elementIdColumn = '';

    public object $pluginDescription;
    public object $generateCampaignResult;

    protected ?object $defaultValues;
    protected string $emailLanguage = '';
    private int $campaignId;
    private string $campaignType;

    protected array $displayOptions = [];
    protected array $replaceOptions = [];
    protected array $elementOptions = [];
    protected array $customOptions = [];

    private array $sendingPlugins = [
        'wp_mail_smtp' => 'WP Mail SMTP',
    ];

    public array $errors = [];
    public string $logFilename = '';
    protected int $responseCode;

    private bool $active;
    public array $settings = [];
    private array $savedSettings = [];

    public array $automationTriggers = [];
    public array $automationActions = [];
    public array $automationConditions = [];
    public array $automationFilters = [];
    public array $mailboxActions = [];

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

        $this->elementOptions = ['wrappedText' => [acym_translation('ACYM_WRAPPED_TEXT')]];

        //TODO only load this helper once even if 50 plugins are loaded
        $this->pluginHelper = new PluginHelper();
        $this->pageInfo = new \stdClass();

        $this->pluginDescription = new \stdClass();
        $this->pluginDescription->plugin = get_class($this);

        $this->name = strtolower(substr($this->pluginDescription->plugin, 7));

        $this->generateCampaignResult = new \stdClass();
        $this->generateCampaignResult->status = true;
        $this->generateCampaignResult->message = '';

        $this->campaignId = acym_getVar('int', 'campaignId', 0);
        $this->campaignType = acym_getVar('string', 'campaign_type', '');

        $pluginClass = new PluginClass();
        $currentAddon = $pluginClass->getOnePluginByFolderName($this->name);
        $this->active = empty($currentAddon) || $currentAddon->active;

        if (!empty($currentAddon->settings)) {
            if (is_string($currentAddon->settings)) {
                $this->savedSettings = json_decode($currentAddon->settings, true);
            } else {
                $this->savedSettings = $currentAddon->settings;
            }
        }

        $this->logFilename = acym_getErrorLogFilename(empty($this->name) ? get_class($this) : $this->name);
    }

    protected function displaySelectionZone(string $zoneContent): void
    {
        ?>
		<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">
            <?php echo esc_html(acym_translation('ACYM_CONTENT_TO_INSERT')); ?><i class="acymicon-keyboard-arrow-up"></i>
		</p>
		<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container">
            <?php echo $zoneContent; ?>
		</div>
        <?php
    }

    /**
     * Called using ajax
     */
    public function displayListing(): void
    {
        echo $this->prepareListing();
    }

    public function prepareListing()
    {
        $this->pageInfo->limit = 10;
        $this->pageInfo->page = acym_getVar('int', 'pagination_page_ajax', 1);
        $this->pageInfo->start = ($this->pageInfo->page - 1) * $this->pageInfo->limit;
        $this->pageInfo->search = acym_getVar('string', 'plugin_search', '');
        $this->pageInfo->filter_cat = acym_getVar('int', 'plugin_category', 0);
        $this->pageInfo->orderdir = 'DESC';
        $this->pageInfo->loadMore = acym_getVar('boolean', 'loadMore', false);

        if (!empty($this->pageInfo->search) && !empty($this->searchFields)) {
            $searchVal = '%'.acym_getEscaped($this->pageInfo->search, true).'%';
            $this->filters[] = implode(' LIKE '.acym_escapeDB($searchVal).' OR ', $this->searchFields).' LIKE '.acym_escapeDB($searchVal);
        }

        return '';
    }

    public function getElements(): array
    {
        $conditions = '';
        if (!empty($this->filters)) {
            $conditions = ' WHERE ('.implode(') AND (', $this->filters).')';
        }

        $ordering = '';
        if (!empty($this->pageInfo->order)) {
            $ordering = ' ORDER BY '.acym_secureDBColumn($this->pageInfo->order).' '.acym_secureDBColumn($this->pageInfo->orderdir);
        }

        $rows = acym_loadObjectList($this->querySelect.$this->query.$conditions.$ordering, '', $this->pageInfo->start, $this->pageInfo->limit);
        $this->pageInfo->total = acym_loadResult('SELECT COUNT(*) '.$this->query.$conditions.$ordering);

        if (!empty($this->defaultValues->id) && $this->defaultValues->defaultPluginTab === $this->name) {
            $found = false;
            foreach ($rows as $oneRow) {
                if (intval($oneRow->{$this->elementIdColumn}) === intval($this->defaultValues->id)) {
                    $found = true;
                }
            }

            if (!$found) {
                $conditions = ' WHERE '.$this->elementIdTable.'.'.$this->elementIdColumn.' = '.intval($this->defaultValues->id);
                $row = acym_loadObject($this->querySelect.$this->query.$conditions);
                if (!empty($row)) {
                    $rows[] = $row;
                }
            }
        }

        return $rows;
    }

    protected function getFilteringZone(bool $categoryFilter = true): string
    {
        $result = '<div class="grid-x" id="plugin_listing_filters">
                    <div class="cell medium-6">
                        <input type="text" name="plugin_search" placeholder="'.esc_attr(acym_translation('ACYM_SEARCH')).'"/>
                    </div>
                    <div class="cell medium-6 grid-x">
                        <div class="cell hide-for-small-only medium-auto"></div>
                        <div class="cell medium-shrink">';

        if ($categoryFilter) {
            $result .= $this->getCategoryFilter();
        }

        $result .= '</div>
                    </div>
                </div>';

        return $result;
    }

    public function prepareWPCategories(string $type): void
    {
        $this->categories = acym_loadObjectList(
            'SELECT cat.term_taxonomy_id AS id, parent.term_taxonomy_id AS parent_id, catdetails.name AS title 
            FROM `#__term_taxonomy` AS cat 
            JOIN `#__terms` AS catdetails ON cat.term_id = catdetails.term_id 
            LEFT JOIN `#__term_taxonomy` AS parent ON cat.parent = parent.term_id 
            WHERE cat.taxonomy = '.acym_escapeDB($type)
        );
        foreach ($this->categories as $i => $oneCat) {
            if (empty($oneCat->parent_id)) {
                $this->categories[$i]->parent_id = $this->rootCategoryId;
            }
        }
    }

    protected function getCategoryFilter()
    {
        $filter_cat = acym_getVar('int', 'plugin_category', 0);
        $this->catvalues = [];
        $this->catvalues[] = acym_selectOption(0, 'ACYM_ALL');

        $this->cats = [];
        $this->subCategories = [];
        if (!empty($this->categories)) {
            foreach ($this->categories as $oneCat) {
                $this->cats[$oneCat->parent_id][] = $oneCat;
            }
            $this->handleChildrenCategories($this->rootCategoryId);
            foreach ($this->categories as $oneCat) {
                $this->subCategories[$oneCat->id] = $this->getSubCats($oneCat->id);
            }
        }

        return acym_select(
            $this->catvalues,
            'plugin_category',
            intval($filter_cat),
            ['class' => 'plugin_category_select']
        );
    }

    private function getSubCats(int $categoryId): array
    {
        $result = [$categoryId];
        if (empty($this->cats[$categoryId])) {
            return $result;
        }

        foreach ($this->cats[$categoryId] as $oneSubCategory) {
            $result = array_merge($result, $this->getSubCats($oneSubCategory->id));
        }

        return $result;
    }

    protected function handleChildrenCategories(int $parent_id, int $level = 0): void
    {
        if (empty($this->cats[$parent_id])) return;

        foreach ($this->cats[$parent_id] as $cat) {
            $this->catvalues[] = acym_selectOption($cat->id, str_repeat(' - - ', $level).$cat->title);
            $this->handleChildrenCategories($cat->id, $level + 1);
        }
    }

    protected function getSubCategories(int $categoryId): array
    {
        $this->getCategoryFilter();

        return $this->subCategories[$categoryId];
    }

    protected function autoContentOptions(array &$options, ?string $type = null): void
    {
        if ($type === 'event') {
            $options[] = [
                'title' => 'ACYM_FROM',
                'type' => 'date',
                'name' => 'from',
                'default' => time(),
                'relativeDate' => '+',
            ];

            $options[] = [
                'title' => 'ACYM_TO',
                'type' => 'date',
                'name' => 'to',
                'default' => '',
                'relativeDate' => '+',
            ];
        } elseif ($type !== 'simple') {
            $options[] = [
                'title' => 'ACYM_MIN_PUBLISH_DATE',
                'tooltip' => 'ACYM_MIN_PUBLISH_DATE_DESC',
                'type' => 'date',
                'name' => 'min_publish',
                'default' => '',
                'relativeDate' => '-',
            ];
        }

        $options[] = [
            'title' => 'ACYM_COLUMNS',
            'type' => 'number',
            'name' => 'cols',
            'default' => 1,
        ];

        $options[] = [
            'title' => 'ACYM_COLUMN_HORIZONTAL_PADDING',
            'type' => 'number',
            'name' => 'hpadding',
            'default' => 10,
        ];

        $options[] = [
            'title' => 'ACYM_COLUMN_VERTICAL_PADDING',
            'type' => 'number',
            'name' => 'vpadding',
            'default' => 10,
        ];

        $options[] = [
            'title' => 'ACYM_MAX_NB_ELEMENTS',
            'type' => 'number',
            'name' => 'max',
            'default' => 20,
        ];
    }

    protected function autoCampaignOptions(array &$options, bool $modified = false): void
    {
        if (empty($this->campaignId) && empty($this->campaignType)) {
            return;
        } elseif (!empty($this->campaignId) && (empty($this->campaignType) || $this->campaignType !== 'auto')) {
            $campaignClass = new CampaignClass();
            $campaign = $campaignClass->getOneById($this->campaignId);
            if ($campaign->sending_type !== 'auto') return;
        } elseif (empty($this->campaignId) && !empty($this->campaignType) && $this->campaignType !== 'auto') {
            return;
        }

        $options[] = [
            'title' => 'ACYM_DOCUMENTATION',
            'type' => 'custom',
            'name' => 'documentation',
            'output' => static function (): void {
                echo '<a target="_blank" href="'.esc_url(
                        ACYM_DOCUMENTATION.'main-pages/campaigns/automatic-campaigns#dont-send-twice-the-same-content'
                    ).'"><i class="acymicon-book"></i></a>';
            },
            'js' => '',
            'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
        ];

        if ($modified) {
            $options[] = [
                'title' => 'ACYM_DATE',
                'type' => 'select',
                'name' => 'datefilter',
                'default' => 'onlynew',
                'tooltip' => 'ACYM_ONLY_NEWLY_CREATED_DESC',
                'options' => [
                    '' => 'ACYM_NO_FILTER',
                    'onlynew' => 'ACYM_ONLY_NEWLY_CREATED',
                    'onlymodified' => 'ACYM_ONLY_NEWLY_MODIFIED',
                ],
                'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
            ];
        } else {
            $options[] = [
                'title' => 'ACYM_ONLY_NEWLY_CREATED',
                'type' => 'boolean',
                'name' => 'onlynew',
                'default' => true,
                'tooltip' => 'ACYM_ONLY_NEWLY_CREATED_DESC',
                'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
            ];
        }

        $options[] = [
            'title' => 'ACYM_MIN_NB_ELEMENTS',
            'type' => 'number',
            'name' => 'min',
            'default' => 0,
            'tooltip' => 'ACYM_MIN_NB_ELEMENTS_DESC',
            'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
        ];
    }

    protected function getElementsListing(array $options): string
    {
        if ($this->pageInfo->loadMore) {
            ob_start();
            $this->displayInnerListing($options);

            return ob_get_clean();
        }
        $listing = '<div id="plugin_listing" class="acym__popup__listing">';
        $listing .= '<input type="hidden" name="plugin" value="'.esc_attr(get_class($this)).'" />';

        // Column names
        $listing .= '<div class="cell grid-x hide-for-small-only plugin_listing_headers">';
        foreach ($options['header'] as $oneColumn) {
            $class = empty($oneColumn['class']) ? '' : ' '.$oneColumn['class'];
            $listing .= '<div class="cell plugin_listing_headers__title medium-'.$oneColumn['size'].$class.'">'.acym_translation($oneColumn['label']).'</div>';
        }
        $listing .= '</div>';

        ob_start();
        $this->displayInnerListing($options);
        $listing .= ob_get_clean();

        $listing .= '<input type="hidden" value="1" id="acym_pagination__ajax__load-more" name="acym_pagination__ajax__load-more">';
        $listing .= '</div>';

        return $listing;
    }

    private function displayInnerListing(array $options): void
    {
        if (empty($options['rows'])) {
            if ($this->pageInfo->loadMore) {
                echo '<h3 class="cell acym__listing__empty__load-more text-center">'.esc_html(acym_translation('ACYM_NO_MORE_RESULTS')).'</h3>';
            } else {
                echo '<h1 class="cell acym__listing__empty__search__modal text-center">'.esc_html(acym_translation('ACYM_NO_RESULTS_FOUND')).'</h1>';
            }

            return;
        }

        $selected = explode(',', acym_getVar('string', 'selected', ''));
        if (!empty($this->defaultValues->id)) {
            $selected = [$this->defaultValues->id];
        }

        foreach ($options['rows'] as $row) {
            $class = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
            if (in_array($row->{$options['id']}, $selected)) {
                $class .= ' selected_row';
            }

            echo '<div 
                    class="'.esc_attr($class).'" 
                    data-id="'.esc_attr($row->{$options['id']}).'" 
                    onclick="applyContent'.esc_attr($this->name).'(\''.esc_attr($row->{$options['id']}).'\', this);">';

            foreach ($options['header'] as $column => $oneColumn) {
                $value = $row->$column;
                $class = empty($oneColumn['class']) ? '' : ' '.$oneColumn['class'];

                echo '<div class="cell medium-'.esc_attr($oneColumn['size'].$class).'">';

                if (!empty($oneColumn['type'])) {
                    if ($oneColumn['type'] === 'date') {
                        if (empty($value)) {
                            echo '-';
                        } else {
                            if (!is_numeric($value) && $value != '0000-00-00 00:00:00') {
                                $value = strtotime($value);
                            }
                            $tooltip = acym_date($value, acym_translation('ACYM_DATE_FORMAT_LC2'));
                            acym_tooltip(
                                [
                                    'hoveredText' => acym_date($value, acym_translation('ACYM_DATE_FORMAT_LC5')),
                                    'textShownInTooltip' => $tooltip,
                                ]
                            );
                        }
                    } elseif ($oneColumn['type'] === 'int') {
                        echo intval($value);
                    } else {
                        echo esc_html($value);
                    }
                } else {
                    echo esc_html($value);
                }

                echo '</div>';
            }

            echo '</div>';
        }
    }

    protected function getCategoryListing(): string
    {
        $listing = '';
        if (empty($this->catvalues)) {
            $listing .= '<h1 class="cell acym__listing__empty__search__modal text-center">'.acym_translation('ACYM_NO_RESULTS_FOUND').'</h1>';

            return $listing;
        }

        $listing .= '<div class="acym__popup__listing padding-0">';
        $selected = [];
        if (!empty($this->defaultValues->id) && strpos($this->defaultValues->id, '-')) {
            $selected = explode('-', $this->defaultValues->id);
        }
        foreach ($this->catvalues as $oneCat) {
            if (empty($oneCat->value)) continue;

            $classes = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
            if (in_array($oneCat->value, $selected)) {
                $classes .= ' selected_row';
            }
            $listing .= '<div class="'.$classes.'" data-id="'.intval($oneCat->value).'" onclick="applyContentauto'.esc_attr($this->name).'('.intval($oneCat->value).', this);">
                    <div class="cell medium-5">'.esc_html($oneCat->text).'</div>
                </div>';
        }
        $listing .= '</div>';

        return $listing;
    }

    protected function getTagListing(): string
    {
        $listing = '';
        if (empty($this->tagvalues)) {
            $listing .= '<h1 class="cell acym__listing__empty__search__modal text-center">'.acym_translation('ACYM_NO_RESULTS_FOUND').'</h1>';

            return $listing;
        }

        $listing .= '<div class="acym__popup__listing padding-0">';
        $selected = [];
        if (!empty($this->defaultValues->id) && strpos($this->defaultValues->id, '-')) {
            $selected = explode('-', $this->defaultValues->id);
        }
        $termIdName = 'term_id';
        if ('wordpress' === ACYM_CMS) {
            $termIdName = 'term_taxonomy_id';
        }
        foreach ($this->tagvalues as $oneTag) {
            if (empty($oneTag->$termIdName)) continue;

            $class = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
            if (in_array($oneTag->$termIdName, $selected)) {
                $class .= ' selected_row';
            }
            $listing .= '<div class="'.esc_attr($class).'" 
							data-id="'.intval($oneTag->$termIdName).'" 
							onclick="applyContent'.esc_attr($this->name).'_tags('.intval($oneTag->$termIdName).', this);">
                        <div class="cell medium-5">'.esc_html($oneTag->name).'</div>
                    </div>';
        }
        $listing .= '</div>';

        return $listing;
    }

    protected function replaceMultiple(object &$email)
    {
        $this->generateByCategory($email);
        if (empty($this->tags)) return;

        $this->pluginHelper->replaceTags($email, $this->tags, true);
    }

    protected function handleOrderBy(string &$query, object $parameter, ?string $table = null)
    {
        if (empty($parameter->order)) return;

        $ordering = explode(',', $parameter->order);
        if ($ordering[0] === 'rand') {
            $query .= ' ORDER BY rand()';
        } else {
            $table = null === $table ? '' : $table.'.';
            $column = $ordering[0];

            if (strpos($column, '.') !== false) {
                $parts = explode('.', $column, 2);
                $table = acym_secureDBColumn($parts[0]).'.';
                $column = $parts[1];
            }

            $query .= ' ORDER BY '.$table.'`'.acym_secureDBColumn(trim($column)).'` '.acym_secureDBColumn(trim($ordering[1]));
        }
    }

    protected function handleMax(string &$query, object $parameter): void
    {
        if (empty($parameter->max)) {
            $parameter->max = 20;
        }
        $query .= ' LIMIT '.intval($parameter->max);
    }

    protected function handleMin(array $elements, object $parameter): void
    {
        if (!empty($parameter->min) && count($elements) < $parameter->min) {
            $this->generateCampaignResult->status = false;
            $this->generateCampaignResult->message = acym_translationSprintf(
                'ACYM_GENERATE_CAMPAIGN_NOT_ENOUGH_CONTENT',
                $this->pluginDescription->name,
                count($elements),
                $parameter->min
            );
        }
    }

    protected function getLastGenerated(int $mailId): int
    {
        $campaignClass = new CampaignClass();

        return $campaignClass->getLastGenerated($mailId);
    }

    /**
     * Returns the individual elements tags based on a query result
     */
    protected function finalizeCategoryFormat(string $query, object $parameter, ?string $table = null): string
    {
        $this->handleOrderBy($query, $parameter, $table);
        $this->handleMax($query, $parameter);

        $elements = acym_loadResultArray($query);

        return $this->formatIndividualTags($elements, $parameter);
    }

    protected function formatIndividualTags(array $elements, object $parameter): string
    {
        $this->handleMin($elements, $parameter);

        if (empty($elements)) return '';

        $elements = $this->groupByCategory($elements);

        $customLayout = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'_auto.php';
        if (file_exists($customLayout)) {
            ob_start();
            require $customLayout;

            return ob_get_clean();
        }

        $arrayElements = $this->buildIndividualTags($elements, $parameter);

        return $this->pluginHelper->getFormattedResult($arrayElements, $parameter);
    }

    protected function buildIndividualTags(array $elements, object $parameter): array
    {
        $arrayElements = [];
        unset($parameter->id);

        $i = 0;
        foreach ($elements as $oneElementId) {
            $args = [];
            $args[] = $this->name.':'.$oneElementId;

            foreach ($parameter as $oneParam => $val) {
                if (is_bool($val)) {
                    $args[] = $oneParam;
                } else {
                    $args[] = $oneParam.':'.$val;
                }
            }

            if ($i % 2 === 1 && !empty($parameter->alternate)) {
                $args[] = 'invert';
            }

            $arrayElements[] = '{'.implode('| ', $args).'}';

            $i++;
        }

        return $arrayElements;
    }

    /**
     * This method can be overridden in the add-on when a group by category option is added
     */
    protected function groupByCategory(array $elements)
    {
        return $elements;
    }

    protected function getSelectedArea(object $parameter): array
    {
        $allcats = explode('-', $parameter->id);
        $selectedArea = [];
        foreach ($allcats as $oneCat) {
            if (empty($oneCat)) continue;
            $selectedArea[] = intval($oneCat);
        }

        return $selectedArea;
    }

    protected function replaceOne(object &$email): void
    {
        $tags = $this->pluginHelper->extractTags($email, $this->name);
        if (empty($tags)) return;

        $newConfiguration = [
            'dcontent_default_'.get_class($this) => json_encode(end($tags)),
        ];
        $this->config->saveConfig($newConfiguration);

        if (method_exists($this, 'loadLibraries') && !$this->loadLibraries($email)) {
            return;
        }

        $this->emailLanguage = $email->links_language;
        $translationTool = $this->config->get('translate_content', 'no');

        $tagsReplaced = [];
        foreach ($tags as $i => $oneTag) {
            if (isset($tagsReplaced[$i])) continue;

            if (!empty($this->emailLanguage) && $translationTool !== 'no' && acym_isMultilingual()) {
                $oneTag->id = $this->getTranslationId($oneTag->id, $translationTool);
            }
            $tagsReplaced[$i] = $this->replaceIndividualContent($oneTag);
        }

        $email->custom_view = file_exists(ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.html');

        $this->pluginHelper->replaceTags($email, $tagsReplaced, true);
    }

    protected function initIndividualContent(object &$tag, string $query)
    {
        $element = acym_loadObject($query);

        if (empty($element)) {
            if (acym_isAdmin()) {
                acym_enqueueMessage(acym_translationSprintf('ACYM_CONTENT_NOT_FOUND', $tag->id), 'notice');
            }

            return null;
        }

        if (empty($tag->display)) {
            $tag->display = [];
        } else {
            $tag->display = explode(',', $tag->display);
        }

        return $element;
    }

    protected function getCustomLayoutVars(object $element): array
    {
        $varFields = [];
        $varFields['{picthtml}'] = '';
        foreach ($element as $fieldName => $oneField) {
            if (is_object($oneField) || is_array($oneField)) {
                $varFields['{'.$fieldName.'}'] = json_encode($oneField);
            } else {
                $varFields['{'.$fieldName.'}'] = $oneField;
            }
        }

        return $varFields;
    }

    /**
     * Handles the custom layouts and the pictures management
     *
     * @param string $htmlResult What will be inserted in the email
     * @param object $insertionOptions Selected options when inserting dcontent
     * @param array  $dataShortcodes Data used as shortcodes in custom layouts
     */
    protected function finalizeElementFormat(string $htmlResult, object $insertionOptions, array $dataShortcodes): string
    {
        // Hidden feature, users can do a PHP custom view
        $fullCustomLayoutPath = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.php';
        if ($this->config->get('php_overrides', 0) == 1 && file_exists($fullCustomLayoutPath)) {
            ob_start();
            require $fullCustomLayoutPath;
            $viewContent = ob_get_clean();
            $htmlResult = str_replace(array_keys($dataShortcodes), $dataShortcodes, $viewContent);
        } else {
            // Default custom view feature with only HTML for security reasons
            $customLayoutPath = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.html';
            if (file_exists($customLayoutPath)) {
                $dataShortcodes['{wrappedText}'] = $this->pluginHelper->wrappedText;
                $viewContent = acym_fileGetContent($customLayoutPath);
                $viewContentReplace = str_replace(array_keys($dataShortcodes), $dataShortcodes, $viewContent);
                if ($viewContent !== $viewContentReplace) {
                    $htmlResult = $viewContentReplace;
                }
            }
        }

        $allShortcodesList = '<table><tr><th>Shortcode</th><th>Value</th></tr>';
        foreach ($dataShortcodes as $name => $value) {
            if (!is_string($value)) {
                continue;
            }

            $allShortcodesList .= '<tr><td>'.trim($name, '{}').'</td><td>'.$value.'</td></tr>';
        }
        $allShortcodesList .= '</table>';
        $htmlResult = str_replace('{allshortcodes}', $allShortcodesList, $htmlResult);

        // Resize/remove pictures if needed
        return $this->pluginHelper->managePicts($insertionOptions, $htmlResult);
    }

    /**
     * Keep this method untyped until v12
     */
    public function onAcymDeclareTriggers(&$triggers, &$defaultValues)
    {
        if (empty($this->automationTriggers) && method_exists($this, 'initAutomationTriggers')) {
            $this->initAutomationTriggers();
        }

        foreach ($this->automationTriggers as $group => $groupTriggers) {
            foreach ($groupTriggers as $key => $name) {
                $triggers[$group][$key] = $this->declareTrigger($key, $name, $defaultValues, $group);
            }
        }
    }

    /**
     * Keep this method untyped until v12
     */
    public function onAcymDeclareTriggersScenario(&$triggers, &$defaultValues)
    {
        $this->onAcymDeclareTriggers($triggers, $defaultValues);
    }

    /**
     * Declares name and options callbacks for automation/scenario triggers
     */
    protected function declareTrigger(string $key, string $name, $defaultValues = [], string $group = 'user'): object
    {
        $trigger = new \stdClass();
        $trigger->name = $name;

        if (!is_array($defaultValues)) {
            $defaultValues = [];
        }

        $method = 'displayTriggerOptions_'.$key;
        if (!method_exists($this, $method)) {
            $method = 'displayTriggerOptions';
        }

        if (method_exists($this, $method)) {
            $automationFieldName = '[triggers]['.$group.']['.$key.']';
            $trigger->displayOptions = function (?string $fieldName = null) use ($method, $defaultValues, $key, $automationFieldName): void {
                $this->$method($fieldName === null ? $automationFieldName : $fieldName, $defaultValues, $key);
            };
        }

        return $trigger;
    }

    public function onAcymDeclareActions(array &$actions): void
    {
        if (empty($this->automationActions) && method_exists($this, 'initAutomationActions')) {
            $this->initAutomationActions();
        }

        foreach ($this->automationActions as $key => $name) {
            $actions[$key] = $this->declareAction($key, $name);
        }
    }

    public function onAcymDeclareActionsScenario(array &$actions): void
    {
        $this->onAcymDeclareActions($actions);
    }

    /**
     * Declares name and options callbacks for an automation/scenario action
     */
    protected function declareAction(string $key, string $name): object
    {
        $action = new \stdClass();
        $action->name = $name;

        $method = 'displayActionOptions_'.$key;
        if (!method_exists($this, $method)) {
            $method = 'displayActionOptions';
        }

        if (method_exists($this, $method)) {
            $action->displayOptions = function () use ($method, $key): void {
                $this->$method($key);
            };
        }

        return $action;
    }

    public function onAcymDeclareConditions(array &$conditions): void
    {
        if (empty($this->automationConditions) && method_exists($this, 'initAutomationConditions')) {
            $this->initAutomationConditions();
        }

        foreach ($this->automationConditions as $group => $groupConditions) {
            foreach ($groupConditions as $key => $name) {
                $conditions[$group][$key] = $this->declareCondition($key, $name);
            }
        }
    }

    public function onAcymDeclareConditionsScenario(array &$conditions): void
    {
        $this->onAcymDeclareConditions($conditions);
    }

    public function onAcymDeclareFilters(array &$filters): void
    {
        if (empty($this->automationFilters) && method_exists($this, 'initAutomationFilters')) {
            $this->initAutomationFilters();
        }

        foreach ($this->automationFilters as $key => $name) {
            $filters[$key] = $this->declareFilter($key, $name);
        }
    }

    /**
     * Returns a field name without its acym_condition / acym_action prefix.
     */
    protected function getFieldNameWithoutPrefix(string $fieldName): string
    {
        return str_replace(['acym_condition', 'acym_action'], '', $fieldName);
    }

    protected function declareCondition(string $key, string $name): object
    {
        $condition = new \stdClass();
        $condition->name = $name;

        $renderer = $this->getOptionsRenderer('displayConditionOptions', $key, self::CONDITION_FIELD_NAME.'['.$key.']');
        if ($renderer !== null) {
            $condition->displayOptions = $renderer;
        }

        return $condition;
    }

    public function onAcymMailboxActionDefine(&$actions)
    {
        if (empty($this->mailboxActions) && method_exists($this, 'initMailboxActions')) {
            $this->initMailboxActions();
        }

        foreach ($this->mailboxActions as $key => $name) {
            $actions[$key] = $this->declareMailboxAction($key, $name);
        }
    }

    /**
     * A follow-up condition row: a translated sentence holding the selectors the add-on built.
     */
    protected function followupConditionRow(string $translationKey, string ...$fields): callable
    {
        return static function () use ($translationKey, $fields): void {
            echo acym_translationSprintf($translationKey, ...$fields);
        };
    }

    protected function declareMailboxAction(string $key, string $name): object
    {
        $action = new \stdClass();
        $action->name = $name;

        $renderer = $this->getOptionsRenderer('displayMailboxActionOptions', $key, self::MAILBOX_ACTION_FIELD_NAME.'['.$key.']');
        if ($renderer !== null) {
            $action->displayOptions = $renderer;
        }

        return $action;
    }

    protected function declareFilter(string $key, string $name): object
    {
        $filter = new \stdClass();
        $filter->name = $name;

        $renderer = $this->getOptionsRenderer('displayFilterOptions', $key, self::FILTER_FIELD_NAME.'['.$key.']');
        if ($renderer !== null) {
            $filter->displayOptions = $renderer;
        }

        return $filter;
    }

    /**
     * Builds the callback that displays the option fields of a declaration.
     */
    private function getOptionsRenderer(string $methodPrefix, string $key, string $defaultFieldName): ?callable
    {
        $method = $methodPrefix.'_'.$key;
        if (!method_exists($this, $method)) {
            $method = $methodPrefix;
        }

        if (!method_exists($this, $method)) {
            return null;
        }

        return function (?string $fieldName = null) use ($method, $key, $defaultFieldName): void {
            $this->$method($fieldName === null ? $defaultFieldName : $fieldName, $key);
        };
    }

    /**
     * Most add-ons offer the same choices as conditions and as filters, the only difference being the name the fields are nested under
     */
    protected function filtersFromConditions(array &$filters): void
    {
        $conditions = [];
        $this->onAcymDeclareConditions($conditions);

        foreach ($conditions as $oneGroup) {
            foreach ($oneGroup as $oneFilterName => $oneFilter) {
                $renderer = $this->getOptionsRenderer('displayConditionOptions', $oneFilterName, self::FILTER_FIELD_NAME.'['.$oneFilterName.']');
                if ($renderer !== null) {
                    $oneFilter->displayOptions = $renderer;
                } elseif (!empty($oneFilter->option)) {
                    // TODO: remove this on version 12, only there for retro-compat of add-ons/plugins not updated along with AcyMailing
                    $oneFilter->option = str_replace(['acym_condition', '[conditions]'], ['acym_action', '[filters]'], $oneFilter->option);
                }

                $filters[$oneFilterName] = $oneFilter;
            }
        }
    }

    protected function getElementTags(string $type, int $id): array
    {
        $tags = acym_loadObjectList(
            'SELECT tags.`title`, tags.`alias`, tags.`id` 
                FROM #__tags AS tags 
                JOIN #__contentitem_tag_map AS map ON tags.`id` = map.`tag_id` 
                WHERE map.`type_alias` = '.acym_escapeDB($type).'
                    AND map.`content_item_id` = '.intval($id)
        );

        if (empty($tags)) return [];

        $displayTags = [];
        foreach ($tags as $oneTag) {
            $displayTags[] = '<a href="index.php?option=com_tags&view=tag&id='.$oneTag->id.':'.$oneTag->alias.'" target="_blank">'.$oneTag->title.'</a>';
        }

        return $displayTags;
    }

    /**
     * @return mixed
     */
    protected function getFormattedValue(array $fieldValues)
    {
        $field = $fieldValues[0];

        if (!empty($field->fieldparams) && !is_array($field->fieldparams)) {
            $field->fieldparams = json_decode($field->fieldparams, true);
        }

        switch ($field->type) {
            case 'calendar':
                $format = acym_translation($field->fieldparams['showtime'] == '1' ? 'ACYM_DATE_FORMAT_LC2' : 'ACYM_DATE_FORMAT_LC1');
                $field->value = HTMLHelper::_('date', $field->value, $format);
                break;

            case 'checkboxes':
            case 'list':
            case 'radio':
                $values = [];
                foreach ($fieldValues as $oneValue) {
                    foreach ($field->fieldparams['options'] as $oneOPT) {
                        if ($oneOPT['value'] == $oneValue->value) {
                            $values[] = $oneOPT['name'];
                            break;
                        }
                    }
                }

                $field->value = implode(', ', $values);
                break;

            case 'dpcalendar':
                if (empty($field->value)) {
                    $field->value = '';
                    break;
                }

                $title = acym_loadResult('SELECT `title` FROM #__categories WHERE `id` = '.intval($field->value));
                if (empty($title)) {
                    $field->value = '';
                } else {
                    $date = '#year='.acym_date('now', 'Y').'&month='.acym_date('now', 'm').'&day='.acym_date('now', 'd').'&view=month';
                    $field->value = '<a target="_blank" href="index.php?option=com_dpcalendar&view=calendar&id='.intval($field->value).$date.'">'.$title.'</a>';
                }
                break;

            case 'imagelist':
                if (!empty($field->fieldparams['directory']) && strlen($field->fieldparams['directory']) > 1) {
                    $field->value = '/'.$field->value;
                } else {
                    $field->fieldparams['directory'] = '';
                }
                $field->value = '<img src="images/'.$field->fieldparams['directory'].$field->value.'" />';
                break;

            case 'media':
                $value = '';
                if (!empty($field->value)) {
                    if (substr($field->value, 0, 1) === '{') {
                        $value = json_decode($field->value, true);
                        if (!empty($value['imagefile'])) {
                            $alt = empty($value['alt_text']) ? '' : ' alt="'.esc_attr($value['alt_text']).'"';
                            $value = '<img src="'.$value['imagefile'].'"'.$alt.' />';
                        }
                    } else {
                        $value = '<img src="'.$field->value.'" alt="" />';
                    }
                }

                $field->value = $value;
                break;

            case 'repeatable':
                if (!empty($field->value)) {
                    $values = is_string($field->value) ? json_decode($field->value) : $field->value;
                    $formattedValues = [];
                    foreach ($values as $oneSetOfValues) {
                        $formattedSet = [];
                        foreach ($oneSetOfValues as $oneLabel => $oneValue) {
                            if (empty($oneValue)) continue;

                            foreach ($field->fieldparams['fields'] as $oneField) {
                                if ($oneField['fieldname'] !== $oneLabel) continue;

                                if ($oneField['fieldtype'] === 'media') {
                                    $oneValue = '<img src="'.$oneValue.'" alt="" />';
                                }

                                $formattedSet[] = $oneValue;
                                break;
                            }
                        }

                        if (empty($formattedSet)) continue;
                        $formattedValues[] = implode(', ', $formattedSet);
                    }

                    $field->value = empty($formattedSet) ? '' : '<ul><li>'.implode('</li><li>', $formattedValues).'</li></ul>';
                }
                break;

            case 'sql':
                if (empty($field->options)) {
                    $field->options = acym_loadObjectList($field->fieldparams['query'], 'value');
                }

                $field->value = $field->options[$field->value]->text;
                break;

            case 'url':
                $field->value = '<a target="_blank" href="'.$field->value.'">'.$field->value.'</a>';
                break;

            case 'user':
                $field->value = acym_currentUserName($field->value);
                break;

            case 'usergrouplist':
                if (empty($field->usergroups)) {
                    $field->usergroups = acym_loadObjectList('SELECT id, title FROM #__usergroups', 'id');
                }

                if (empty($field->usergroups[$field->value])) {
                    $field->value = '';
                    break;
                }
                $field->value = $field->usergroups[$field->value]->title;
                break;
            case 'subform':
                if (!empty($field->value)) {
                    $rows = is_string($field->value) ? json_decode($field->value, true) : $field->value;

                    $formattedValues = [];
                    foreach ($rows as $values) {
                        $fieldValues = [];
                        foreach ($values as $fieldIdentifier => $fieldValue) {
                            $fieldId = str_replace('field', '', $fieldIdentifier);
                            $oneFieldDefinition = acym_loadObject(
                                'SELECT `field`.*
                                FROM #__fields AS `field`
                                WHERE `field`.`id` = '.intval($fieldId)
                            );

                            if (empty($oneFieldDefinition)) {
                                break 3;
                            }

                            $multiCompatibleValues = [];
                            if (is_array($fieldValue)) {
                                foreach ($fieldValue as $oneFieldValue) {
                                    $oneValue = clone $oneFieldDefinition;
                                    $oneValue->value = $oneFieldValue;
                                    $multiCompatibleValues[] = $oneValue;
                                }
                            } else {
                                $oneFieldDefinition->value = $fieldValue;
                                $multiCompatibleValues[] = $oneFieldDefinition;
                            }

                            $fieldValues[] = $oneFieldDefinition->label.': '.$this->getFormattedValue($multiCompatibleValues);
                        }
                        $formattedValues[] = implode(', ', $fieldValues);
                    }

                    $field->value = empty($formattedValues) ? '' : '<ul><li>'.implode('</li><li>', $formattedValues).'</li></ul>';
                }
        }

        return $field->value;
    }

    protected function getLanguage(?string $elementLanguage = null, bool $onlyValue = false): string
    {
        $value = $this->emailLanguage;
        if (!empty($elementLanguage) && $elementLanguage !== '*') {
            $value = $elementLanguage;
        }

        if (empty($value)) {
            return '';
        }

        if (ACYM_CMS === 'joomla' && acym_isPluginActive('languagefilter')) {
            $languages = acym_loadObjectList('SELECT * FROM #__languages', 'lang_code');
            if (isset($languages[$value])) {
                $value = $languages[$value]->sef;
            } else {
                $value = substr($value, 0, strpos($value, '-'));
            }

            if ($onlyValue) {
                return $value;
            }

            return '&lang='.$value;
        } else {
            if ($onlyValue) {
                return $value;
            }

            return '&language='.$value;
        }
    }

    // Don't add types for $tag and $sef, for retro-compatibility
    protected function finalizeLink(string $link, $tag = null, $sef = true): string
    {
        if (acym_isPluginActive('languagefilter')) {
            if (strpos($link, 'lang=') === false && !empty($this->emailLanguage)) {
                $link .= strpos($link, '?') === false ? '?' : '&';
                $link .= 'lang='.substr($this->emailLanguage, 0, strpos($this->emailLanguage, '-'));
            }
        } else {
            if (strpos($link, 'language=') === false && !empty($this->emailLanguage)) {
                $link .= strpos($link, '?') === false ? '?' : '&';
                $link .= 'language='.$this->emailLanguage;
            }
        }

        if (!empty($tag->autologin)) {
            $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:autologin_token|urlencode}';
        }

        // Retro-compatibility for add-on versions before the v8.6.0
        if (is_bool($tag)) {
            $sef = $tag;
        }

        return acym_frontendLink($link, false, $sef);
    }

    protected function handleCustomFields(object $tag, array &$customFields): void
    {
        if (empty($tag->custom)) {
            return;
        }

        $tag->custom = explode(',', $tag->custom);
        acym_arrayToInteger($tag->custom);

        $rawfields = acym_loadObjectList(
            'SELECT `field`.*, `values`.`value` 
                FROM #__fields_values AS `values` 
                JOIN #__fields AS `field` ON `values`.`field_id` = `field`.`id` 
                WHERE `values`.`item_id` = '.intval($tag->id).' AND `field`.`id` IN ('.implode(', ', $tag->custom).')'
        );

        $fields = [];
        foreach ($rawfields as $field) {
            $fields[$field->id][] = $field;
        }

        usort($fields, function ($a, $b) {
            return $a[0]->ordering <=> $b[0]->ordering;
        });

        foreach ($fields as $fieldValues) {
            $value = $this->getFormattedValue($fieldValues);

            if (empty($value)) continue;

            $customFields[] = [
                $value,
                $fieldValues[0]->label,
            ];
        }
    }

    protected function getIntro(string $text): string
    {
        $pageBreak = null;
        $possibleBreaks = [
            '<!--more-->',
            '<hr id="system-readmore" />',
            '<!--nextpage-->',
        ];

        foreach ($possibleBreaks as $oneBreak) {
            if (strpos($text, $oneBreak) !== false) {
                $pageBreak = $oneBreak;
                break;
            }
        }

        if (empty($pageBreak)) {
            return $text;
        }

        $split = explode($pageBreak, $text, 2);

        return array_shift($split);
    }

    public function displayCustomViewEditor(): void
    {
        if (empty($this->settings['custom_view']) || !acym_isAdmin()) {
            return;
        }

        echo '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">';
        echo esc_html(acym_translation('ACYM_ADDON_SETTINGS'));
        echo '<i class="acymicon-keyboard-arrow-up"></i></p>';
        echo '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container">';
        $this->displayCustomView($this->settings['custom_view']);
        echo '</div>';
    }

    public function generateSettings(object $plugin): bool
    {
        if (empty($plugin->settings)) {
            return false;
        }

        if (array_key_exists('not_installed', $plugin->settings)) {
            $plugin->settings = 'not_installed';

            return true;
        }

        foreach ($plugin->settings as $key => $field) {
            $text = '';
            if (empty($field['type'])) {
                $plugin->settings[$key] = $text;
                continue;
            }

            $id = $plugin->folder_name.'_'.$key;
            $name = $plugin->folder_name.'['.$key.']';
            if (!empty($field['label'])) {
                $field['label'] = acym_translation($field['label']);
            }

            if (!empty($field['info'])) {
                ob_start();
                acym_info(
                    [
                        'textShownInTooltip' => acym_translation($field['info']),
                        'classText' => 'wysid_tooltip',
                        'isWarning' => !empty($field['info_warning']),
                    ]
                );
                $field['label'] .= ob_get_clean();
            }

            if ($field['type'] === 'checkbox') {
                $classLabel = 'shrink';
                $text .= '<label for="'.esc_attr($id).'" class="cell '.esc_attr($classLabel).'">'.esc_html($field['label']).'</label>';
                $text .= '<input id="'.esc_attr($id).'" class="cell shrink" type="checkbox" name="'.esc_attr($name).'" '.checked(
                        !empty($field['value']),
                        true,
                        false
                    ).'>';
            } elseif ($field['type'] === 'switch') {
                ob_start();
                acym_switch([
                    'name' => $name,
                    'value' => $field['value'],
                    'label' => $field['label'],
                    'labelClass' => 'large-7',
                ]);
                $text .= ob_get_clean();
            } elseif ($field['type'] === 'select') {
                $text .= '<label class="cell shrink">'.$field['label'].'</label>';
                $text .= acym_select(
                    $field['data'],
                    $name,
                    $field['value'],
                    ['class' => 'acym__select'],
                    'value',
                    'text',
                    false,
                    true
                );
            } elseif ($field['type'] === 'multiple_select') {
                $text .= '<label class="cell shrink">'.$field['label'].'</label>';
                $text .= acym_selectMultiple(
                    $field['data'],
                    $name,
                    empty($field['value']) ? [] : $field['value'],
                    ['class' => 'acym__select']
                );
            } elseif ($field['type'] === 'text') {
                $text .= '<label class="cell shrink">'.$field['label'].'</label>';
                $text .= '<input type="text" name="'.$name.'" value="'.esc_attr($field['value']).'" class="cell shrink">';
            } elseif ($field['type'] === 'number') {
                $text .= '<label class="cell shrink">'.$field['label'].'</label>';
                $text .= '<input type="number" name="'.$name.'" value="'.esc_attr($field['value']).'" class="cell large-2 medium-5">';
                if (!empty($field['post_text'])) $text .= '<span class="cell shrink">'.strtolower($field['post_text']).'</span>';
            } elseif ($field['type'] === 'radio') {
                $text .= '<p class="cell">'.$field['label'].'</p>';
                ob_start();
                acym_radio(
                    $field['data'],
                    $name,
                    $field['value']
                );
                $text .= ob_get_clean();
            } elseif ($field['type'] === 'date') {
                $text .= '<label class="cell shrink">'.$field['label'].'</label>';
                $text .= acym_dateField(
                    $name,
                    $field['value'],
                    'cell shrink'
                );
            } elseif ($field['type'] === 'custom_view') {
                ob_start();
                $this->displayCustomView($field);
                $text .= ob_get_clean();
            } elseif ($field['type'] === 'multikeyvalue') {
                $text .= '<label class="cell shrink">'.$field['label'].'</label>';

                $text .= '<div class="multikeyvalue_container grid-x">';

                if (!empty($field['value'])) {
                    $headers = json_decode($field['value'], true);
                    foreach ($headers as $headerKey => $headerValue) {
                        $text .= '<input type="text" class="cell" placeholder="'.esc_attr(acym_translation('ACYM_DKIM_KEY')).'" value="'.esc_attr($headerKey).'"/>';
                        $text .= '<input type="text" class="cell" placeholder="'.esc_attr(acym_translation('ACYM_VALUE')).'" value="'.esc_attr($headerValue).'" />';
                        $text .= '<div class="multikeyvalue_container_separator cell small-6"></div>';
                    }
                }

                $text .= '<input type="text" class="cell" placeholder="'.esc_attr(acym_translation('ACYM_DKIM_KEY')).'" value=""/>';
                $text .= '<input type="text" class="cell" placeholder="'.esc_attr(acym_translation('ACYM_VALUE')).'" value="" />';

                $text .= '<button class="button multikeyvalue_container_new">'.esc_html(acym_translation('ACYM_ADD_NEW')).'</button>';

                $text .= '<input type="hidden" name="'.esc_attr($name).'" value="" />';
                $text .= '</div>';
            } elseif ($field['type'] === 'custom') {
                $text .= $field['content'];
            }
            $plugin->settings[$key] = $text;
        }

        return true;
    }

    protected function displayCustomView(array $field): void
    {
        if (!acym_isAdmin()) {
            return;
        }

        $idCustomView = 'acym__plugins__installed__custom-view__'.$this->name;
        $ctrl = acym_getVar('string', 'ctrl', '');
        $classTooltip = $ctrl === 'dynamics' ? '' : 'wysid_tooltip';

        echo '<label class="cell">';
        echo esc_html(acym_translation('ACYM_CUSTOM_VIEW'));
        acym_info(
            [
                'textShownInTooltip' => 'ACYM_CUSTOM_VIEW_DESC',
                'classText' => $classTooltip,
            ]
        );
        echo '</label>';

        if (empty($field['tags'])) {
            $field['tags'] = [];
        }

        ob_start();
        acym_info(
            [
                'textShownInTooltip' => acym_translation('ACYM_DYNAMIC_CONTENT_DESC'),
            ]
        );
        $tooltip = ob_get_clean();

        $modalContent = '<div id="'.esc_attr($idCustomView).'" class="cell grid-x acym__plugins__installed__custom-view" acym-data-tags="'.esc_attr(
                json_encode($field['tags'])
            ).'">
				<h2 class="cell text-center acym__title__primary__color">'.esc_html(acym_translationSprintf('ACYM_CUSTOM_VIEW_FOR_X', $this->pluginDescription->name)).'</h2>
				<div class="cell grid-x acym__plugins__installed__custom-view__edit-container">
					<div class="acym__plugins__installed__custom-view__editor-loader grid-x cell align-center acym_vcenter" v-if="loading">
						<div class="cell shrink acym_loader_logo">
							'.acym_fileGetContent(ACYM_IMAGES.'logos/logo_grey.svg').'
						</div>
					</div>
					<vue-prism-editor :emitEvents="true" class="cell acym__plugins__installed__custom-view__code cell auto" v-model="code" :language="language" lineNumbers="true"></vue-prism-editor>
					<div class="cell grid-x medium-3 margin-left-1 acym__plugins__installed__custom-view__tags">
						<h3 class="acym__title acym__title__secondary cell text-center">'.esc_html(acym_translation('ACYM_DYNAMIC_CONTENT')).$tooltip.'</h3>
						<div class="cell acym__plugins__installed__custom-view__tag" v-for="(trad, tag) in tags" :key="tag" @click.prevent="insertTag(tag)">{{ trad }}</div>
					</div>
				</div>
				<div class="cell grid-x acym__plugins__installed__custom-view__actions acym_vcenter margin-top-1 padding-bottom-1">
					<div class="cell auto grid-x">
						<button type="button" class="cell shrink button-secondary button" @click="resetView">'.esc_html(acym_translation('ACYM_RESET_VIEW')).'</button>
						<div class="cell shrink margin-right-2 acym_vcenter margin-left-1">
							<i v-if="deleting" class="acymicon-spin acymicon-circle-o-notch" style="margin-bottom: 0; line-height: 26px;"></i>
							<span v-if="deleted">{{ messageDeleted }}</span>
						</div>
					</div>
					<div class="cell auto align-right grid-x">
						<div class="cell shrink margin-right-2 acym_vcenter">
							<i v-if="saving" class="acymicon-spin acymicon-circle-o-notch" style="margin-bottom: 0; line-height: 26px;"></i>
							<span v-if="saved">{{ messageSaved }}</span>
						</div>
						<button @click="save()" class="cell shrink button" type="button">'.esc_html(acym_translation('ACYM_SAVE_NEW_CUSTOM_VIEW_VERSION')).'</button>
					</div>
				</div>
			</div>';

        acym_modal(
            acym_translation('ACYM_EDIT_CUSTOM_VIEW'),
            $modalContent,
            null,
            [
                'acym-data-plugins-id' => $idCustomView,
                'acym-data-plugin-class' => get_class($this),
                'acym-data-plugin-folder' => $this->name,
            ],
            ['class' => 'cell button']
        );
    }

    /**
     * Called using ajax
     *
     * @param string $css This attribute is the name of the file in the folder css of the plugin OR it can be raw CSS
     */
    public function loadCSS(string $css, bool $raw = false, ?string $path = null): void
    {
        if (!$raw) {
            if (empty($path)) $path = ACYM_DYNAMICS_URL.$this->name;
            $css = $path.'/css/'.$css.'.css';
        }
        acym_addStyle($raw, $css);
    }

    /**
     * @param string $js This attribute is the name of the file in the folder js of the plugin OR it can be raw Javascript
     */
    public function loadJavascript(string $js, bool $raw = false, ?string $path = null): void
    {
        if (!$raw) {
            if (empty($path)) $path = ACYM_DYNAMICS_URL.$this->name;
            $js = $path.'/js/'.$js.'.js';
        }
        acym_addScript($raw, $js);
    }

    public function includeView(string $view, array $data = [], ?string $path = null): string
    {
        if (empty($path)) {
            $path = ACYM_ADDONS_FOLDER_PATH.$this->name.DS.'views'.DS.$view.'.php';
        } else {
            $path = $path.DS.'views'.DS.$view.'.php';
        }

        if (!file_exists($path)) {
            throw new \Exception(esc_attr(acym_translation('ACYM_NON_EXISTING_PAGE')));
        }

        ob_start();
        include $path;

        return ob_get_clean();
    }

    /**
     * This function will parse and show all settings
     */
    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)) {
                    foreach ($plugin->settings as $keySettings => $value) {
                        if (isset($this->settings[$keySettings]['value'])) {
                            $this->settings[$keySettings]['value'] = $plugin->settings[$keySettings]['value'];
                        }
                    }
                }
                $plugins[$key]->settings = $this->settings;

                $this->generateSettings($plugins[$key]);
                break;
            }
        }
    }

    /**
     * @param mixed $default
     *
     * @return mixed
     */
    protected function getParam(string $name, $default = '')
    {
        return $this->savedSettings[$name]['value'] ?? $default;
    }

    protected function getTranslationId(int $elementId, string $translationTool, bool $defaultLanguage = false)
    {
        return $elementId;
    }

    public function filterSpecialMailsDailySend(array &$specialMails, int $time, string $mailType): void
    {
        // Only once a day
        $dailyHour = $this->config->get('daily_hour', '12');
        $dailyMinute = $this->config->get('daily_minute', '00');
        // The day it is currently based on the timezone specified in the CMS configuration
        $dayBasedOnCMSTimezone = acym_date('now', 'Y-m-d');
        // The UTC timestamp of the current day based on the CMS timezone, at the specified hour
        $dayBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$dailyHour.':'.$dailyMinute);

        $campaignClass = new CampaignClass();

        $tmpMails = [];
        foreach ($specialMails as $oneMail) {
            if ($oneMail->sending_type === $mailType) {
                $noNextTrigger = empty($oneMail->next_trigger);
                $nextTriggerIsNow = gmdate('m-d', $oneMail->next_trigger) == gmdate('m-d', $dayBasedOnCMSTimezoneAtSpecifiedHour);
                $nextTriggerIsInPast = $oneMail->next_trigger < $time;
                if ($time >= $dayBasedOnCMSTimezoneAtSpecifiedHour && ($noNextTrigger || $nextTriggerIsNow || $nextTriggerIsInPast)) {
                    $oneMail->next_trigger = acym_getTime('tomorrow '.$dailyHour.':'.$dailyMinute);
                    $oneMail->last_generated = $time;
                    $campaignClass->save($oneMail);
                    $tmpMails[] = $oneMail;
                }
            } else {
                $tmpMails[] = $oneMail;
            }
        }
        $specialMails = $tmpMails;
    }

    protected function getIdsSelectAjax(): array
    {
        $ids = acym_getVar('string', 'id');
        if (empty($ids)) {
            return [];
        }

        $ids = explode(',', $ids);
        acym_arrayToInteger($ids);

        return $ids;
    }

    protected function cleanExtensionContent(string $text)
    {
        if (ACYM_CMS === 'wordpress') {
            if (!acym_isExtensionActive('classic-editor/classic-editor.php') || strpos($text, '<!-- wp:') !== false) {
                return $text;
            }

            return nl2br($text);
        } else {
            return preg_replace('#\{igallery[^}]+\}#Ui', '', $text);
        }
    }

    protected function callApiSendingMethod(
        string $url,
        array  $data = [],
        array  $headers = [],
        string $type = 'GET',
        array  $authentication = [],
        bool   $dataDecoded = false,
        array  $files = []
    ): array {
        $assocHeaders = [];
        foreach ($headers as $key => $value) {
            if (is_int($key)) {
                $parts = explode(':', $value, 2);
                if (count($parts) === 2) {
                    $assocHeaders[trim($parts[0])] = trim($parts[1]);
                }
            } else {
                $assocHeaders[$key] = $value;
            }
        }

        if (!empty($authentication)) {
            $assocHeaders['Authorization'] = 'Basic '.base64_encode($authentication['name'].':'.$authentication['pwd']);
        }

        $result = acym_makeCurlCall(
            $url,
            [
                'method' => strtoupper($type),
                'headers' => $assocHeaders,
                'data' => $data,
                'files' => $files,
                // Some APIs (Mailgun) expect the data as a raw multipart body, not encoded
                'multipart' => $dataDecoded === true,
                'verifySsl' => false,
            ]
        );

        $this->responseCode = (int)($result['status_code'] ?? 0);
        unset($result['status_code']);

        if (isset($result['error'])) {
            return ['error_curl' => $result['error']];
        }

        return $result;
    }

    protected function getTestCredentialsSendingMethodButton(string $sendingMethodId): void
    {
        echo '<div class="cell grid-x margin-top-1 acym__sending__methods__credentials__test">
                    <button type="button" sending-method-id="'.esc_attr($sendingMethodId).'" class="acym__configuration__sending__method-test cell shrink button button-secondary">
                    '.esc_html(acym_translation('ACYM_TEST_CREDENTIALS')).'
                    </button>
                    <span class="acym__configuration__sending__method-icon cell shrink margin-left-1 acym_vcenter"></span>
                    <span class="acym__configuration__sending__method-test__message cell shrink margin-left-1 acym_vcenter"></span>
                </div>';
    }

    public function getCopySettingsButton(array $data, string $sendingMethodId, string $fromPlugin, bool $withContainer = true): void
    {
        if (empty($data[$fromPlugin.'_installed'])) {
            return;
        }

        if ($withContainer) {
            echo '<div class="cell grid-x margin-top-1">';
        }

        echo '<button
				type="button"
				class="cell shrink button button-secondary acym__configuration__copy__mail__settings"
				acym-data-plugin="'.esc_attr($fromPlugin).'"
				acym-data-method="'.esc_attr($sendingMethodId).'">
				'.esc_html(acym_translationSprintf('ACYM_COPY_SETTINGS_FROM', $this->sendingPlugins[$fromPlugin])).'
			</button>
			<span class="acym__configuration__sending__method-icon cell shrink margin-left-1 acym_vcenter"></span>';

        if ($withContainer) {
            echo '</div>';
        }
    }

    protected function getLinks(string $account = '', string $pricing = ''): void
    {
        if (empty($account) && empty($pricing)) {
            return;
        }

        echo '<div class="cell grid-x acym-grid-margin-x shrink acym_vcenter"><p class="cell shrink">'.esc_html(acym_translation('ACYM_DONT_HAVE_ACCOUNT')).'</p>';
        if (!empty($account)) {
            echo '<a target="_blank" class="cell shrink" href="'.esc_url($account).'">'.esc_html(acym_translation('ACYM_CREATE_ONE')).'</a>';
        }
        if (!empty($account) && !empty($pricing)) {
            echo '<p class="cell shrink">'.esc_html(strtolower(acym_translation('ACYM_OR'))).'</p>';
        }
        if (!empty($pricing)) {
            echo '<a target="_blank" class="cell shrink" href="'.esc_url($pricing).'">'.esc_html(acym_translation('ACYM_CHECK_THEIR_PRICING')).'</a>';
        }
        echo '</div>';
    }

    public function onAcymGetSendingMethodsSelected(array &$data): void
    {
        if (ACYM_CMS === 'wordpress') {
            $this->config->load();
        }

        $mailerMethod = $this->config->get('mailer_method', 'phpmail');
        foreach ($data['sendingMethods'] as $key => $sendingMethod) {
            $data['sendingMethods'][$key]['selected'] = $key == $mailerMethod;
        }
    }

    public function errorCallback(): void
    {
        foreach ($this->errors as $error) {
            acym_logError($error, empty($this->name) ? get_class($this) : $this->name);
        }

        $this->errors = [];
    }

    public function isLogFileEmpty(): bool
    {
        return !acym_isLogFileErrorExist(empty($this->name) ? get_class($this) : $this->name);
    }

    public function initCustomView(bool $customFields = false): void
    {
        $page = acym_getVar('cmd', 'page');
        if (is_array($page)) return;
        if (!empty($page) && is_string($page)) {
            $page = str_replace(ACYM_COMPONENT.'_', '', $page);
        }
        $ctrl = acym_getVar('cmd', 'ctrl', $page);
        if (!in_array($ctrl, ['plugins', 'dynamics'])) return;

        $task = acym_getVar('cmd', 'task', 'installed');

        // Installed add-ons page or installed + editor
        if (($ctrl === 'plugins' && $task === 'installed') || ($this->active && $ctrl === 'dynamics' && $task === 'trigger')) {
            $this->initElementOptionsCustomView();
            $this->initReplaceOptionsCustomView();
            if ($customFields) $this->initCustomOptionsCustomView();
        }
    }

    public function processDateToCheck(array $options): \DateTime
    {
        $dateNowWithTimeZone = acym_date('now', 'Y-m-d h:i:s');
        $dateToCheck = new \DateTime($dateNowWithTimeZone);
        $interval = new \DateInterval('P'.intval($options['days']).'D');
        if ($options['relative'] === 'before') {
            $dateToCheck->add($interval);
        } else {
            $dateToCheck->sub($interval);
        }

        return $dateToCheck;
    }

    public function onAcymCheckInstalled(bool &$installed): void
    {
        $installed = $this->installed;
    }

    protected function getLinkTranslated(string $link): string
    {
        if (empty($this->emailLanguage)) return $link;
        if (!acym_isMultilingual()) return $link;

        $translationTool = $this->config->get('translate_content', 'no');
        if ($translationTool !== 'wpml') return $link;

        if (!acym_isExtensionActive('sitepress-multilingual-cms/sitepress.php')) return $link;

        $languageCode = substr($this->emailLanguage, 0, 2);

        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML hook for integration.
        return apply_filters('wpml_permalink', $link, $languageCode);
    }

    protected function replaceShortcode(string $content): string
    {
        $content = do_shortcode($content);

        return preg_replace('#<!-- wp:shortcode -->(.*)<!-- /wp:shortcode -->#Uis', '$1', $content);
    }

    protected function fixDivStructure(string $text): string
    {
        $nbOpeningDivs = substr_count($text, '<div');
        $nbClosingDivs = substr_count($text, '</div>');

        if ($nbOpeningDivs > $nbClosingDivs) {
            $text .= str_repeat('</div>', $nbOpeningDivs - $nbClosingDivs);
        } elseif ($nbOpeningDivs < $nbClosingDivs) {
            $text = str_repeat('<div>', $nbClosingDivs - $nbOpeningDivs).$text;
        }

        return $text;
    }

    protected function getBounceAddress(object $mailerHelper): string
    {
        if (method_exists($mailerHelper, 'getSendSettings')) {
            return $mailerHelper->getSendSettings('bounce_email');
        } else {
            return $this->config->get('bounce_email');
        }
    }
}

```
