PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Core / AcymPlugin.php

AcymPlugin.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.1.0, at back/Core/AcymPlugin.php

1,930 lines 69.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Core;
4
5 use AcyMailing\Classes\CampaignClass;
6 use AcyMailing\Classes\PluginClass;
7 use AcyMailing\Helpers\PluginHelper;
8 use Joomla\CMS\HTML\HTMLHelper;
9
10 class AcymPlugin extends AcymObject
11 {
12 // The row placeholders are replaced by the JS when a row is added to the form
13 const CONDITION_FIELD_NAME = 'acym_condition[conditions][__numor__][__numand__]';
14 const FILTER_FIELD_NAME = 'acym_action[filters][__numor__][__numand__]';
15 const MAILBOX_ACTION_FIELD_NAME = 'acym_action[__num__]';
16
17 public string $cms = 'all';
18 public string $name = '';
19 public bool $installed = true;
20
21 protected array $addonDefinition;
22 protected PluginHelper $pluginHelper;
23
24 protected int $rootCategoryId = 1;
25 protected array $categories;
26 protected array $catvalues = [];
27 private array $cats = [];
28 private array $subCategories = [];
29
30 protected array $tagvalues = [];
31
32 protected array $tags = [];
33 protected object $pageInfo;
34 protected array $searchFields = [];
35 protected string $querySelect = '';
36 protected string $query = '';
37 protected array $filters = [];
38 protected string $elementIdTable = '';
39 protected string $elementIdColumn = '';
40
41 public object $pluginDescription;
42 public object $generateCampaignResult;
43
44 protected ?object $defaultValues;
45 protected string $emailLanguage = '';
46 private int $campaignId;
47 private string $campaignType;
48
49 protected array $displayOptions = [];
50 protected array $replaceOptions = [];
51 protected array $elementOptions = [];
52 protected array $customOptions = [];
53
54 private array $sendingPlugins = [
55 'wp_mail_smtp' => 'WP Mail SMTP',
56 ];
57
58 public array $errors = [];
59 public string $logFilename = '';
60 protected int $responseCode;
61
62 private bool $active;
63 public array $settings = [];
64 private array $savedSettings = [];
65
66 public array $automationTriggers = [];
67 public array $automationActions = [];
68 public array $automationConditions = [];
69 public array $automationFilters = [];
70 public array $mailboxActions = [];
71
72 public function __construct()
73 {
74 parent::__construct();
75
76 $this->elementOptions = ['wrappedText' => [acym_translation('ACYM_WRAPPED_TEXT')]];
77
78 //TODO only load this helper once even if 50 plugins are loaded
79 $this->pluginHelper = new PluginHelper();
80 $this->pageInfo = new \stdClass();
81
82 $this->pluginDescription = new \stdClass();
83 $this->pluginDescription->plugin = get_class($this);
84
85 $this->name = strtolower(substr($this->pluginDescription->plugin, 7));
86
87 $this->generateCampaignResult = new \stdClass();
88 $this->generateCampaignResult->status = true;
89 $this->generateCampaignResult->message = '';
90
91 $this->campaignId = acym_getVar('int', 'campaignId', 0);
92 $this->campaignType = acym_getVar('string', 'campaign_type', '');
93
94 $pluginClass = new PluginClass();
95 $currentAddon = $pluginClass->getOnePluginByFolderName($this->name);
96 $this->active = empty($currentAddon) || $currentAddon->active;
97
98 if (!empty($currentAddon->settings)) {
99 if (is_string($currentAddon->settings)) {
100 $this->savedSettings = json_decode($currentAddon->settings, true);
101 } else {
102 $this->savedSettings = $currentAddon->settings;
103 }
104 }
105
106 $this->logFilename = acym_getErrorLogFilename(empty($this->name) ? get_class($this) : $this->name);
107 }
108
109 protected function displaySelectionZone(string $zoneContent): void
110 {
111 ?>
112 <p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">
113 <?php echo esc_html(acym_translation('ACYM_CONTENT_TO_INSERT')); ?><i class="acymicon-keyboard-arrow-up"></i>
114 </p>
115 <div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container">
116 <?php echo $zoneContent; ?>
117 </div>
118 <?php
119 }
120
121 /**
122 * Called using ajax
123 */
124 public function displayListing(): void
125 {
126 echo $this->prepareListing();
127 }
128
129 public function prepareListing()
130 {
131 $this->pageInfo->limit = 10;
132 $this->pageInfo->page = acym_getVar('int', 'pagination_page_ajax', 1);
133 $this->pageInfo->start = ($this->pageInfo->page - 1) * $this->pageInfo->limit;
134 $this->pageInfo->search = acym_getVar('string', 'plugin_search', '');
135 $this->pageInfo->filter_cat = acym_getVar('int', 'plugin_category', 0);
136 $this->pageInfo->orderdir = 'DESC';
137 $this->pageInfo->loadMore = acym_getVar('boolean', 'loadMore', false);
138
139 if (!empty($this->pageInfo->search) && !empty($this->searchFields)) {
140 $searchVal = '%'.acym_getEscaped($this->pageInfo->search, true).'%';
141 $this->filters[] = implode(' LIKE '.acym_escapeDB($searchVal).' OR ', $this->searchFields).' LIKE '.acym_escapeDB($searchVal);
142 }
143
144 return '';
145 }
146
147 public function getElements(): array
148 {
149 $conditions = '';
150 if (!empty($this->filters)) {
151 $conditions = ' WHERE ('.implode(') AND (', $this->filters).')';
152 }
153
154 $ordering = '';
155 if (!empty($this->pageInfo->order)) {
156 $ordering = ' ORDER BY '.acym_secureDBColumn($this->pageInfo->order).' '.acym_secureDBColumn($this->pageInfo->orderdir);
157 }
158
159 $rows = acym_loadObjectList($this->querySelect.$this->query.$conditions.$ordering, '', $this->pageInfo->start, $this->pageInfo->limit);
160 $this->pageInfo->total = acym_loadResult('SELECT COUNT(*) '.$this->query.$conditions.$ordering);
161
162 if (!empty($this->defaultValues->id) && $this->defaultValues->defaultPluginTab === $this->name) {
163 $found = false;
164 foreach ($rows as $oneRow) {
165 if (intval($oneRow->{$this->elementIdColumn}) === intval($this->defaultValues->id)) {
166 $found = true;
167 }
168 }
169
170 if (!$found) {
171 $conditions = ' WHERE '.$this->elementIdTable.'.'.$this->elementIdColumn.' = '.intval($this->defaultValues->id);
172 $row = acym_loadObject($this->querySelect.$this->query.$conditions);
173 if (!empty($row)) {
174 $rows[] = $row;
175 }
176 }
177 }
178
179 return $rows;
180 }
181
182 protected function getFilteringZone(bool $categoryFilter = true): string
183 {
184 $result = '<div class="grid-x" id="plugin_listing_filters">
185 <div class="cell medium-6">
186 <input type="text" name="plugin_search" placeholder="'.esc_attr(acym_translation('ACYM_SEARCH')).'"/>
187 </div>
188 <div class="cell medium-6 grid-x">
189 <div class="cell hide-for-small-only medium-auto"></div>
190 <div class="cell medium-shrink">';
191
192 if ($categoryFilter) {
193 $result .= $this->getCategoryFilter();
194 }
195
196 $result .= '</div>
197 </div>
198 </div>';
199
200 return $result;
201 }
202
203 public function prepareWPCategories(string $type): void
204 {
205 $this->categories = acym_loadObjectList(
206 'SELECT cat.term_taxonomy_id AS id, parent.term_taxonomy_id AS parent_id, catdetails.name AS title
207 FROM `#__term_taxonomy` AS cat
208 JOIN `#__terms` AS catdetails ON cat.term_id = catdetails.term_id
209 LEFT JOIN `#__term_taxonomy` AS parent ON cat.parent = parent.term_id
210 WHERE cat.taxonomy = '.acym_escapeDB($type)
211 );
212 foreach ($this->categories as $i => $oneCat) {
213 if (empty($oneCat->parent_id)) {
214 $this->categories[$i]->parent_id = $this->rootCategoryId;
215 }
216 }
217 }
218
219 protected function getCategoryFilter()
220 {
221 $filter_cat = acym_getVar('int', 'plugin_category', 0);
222 $this->catvalues = [];
223 $this->catvalues[] = acym_selectOption(0, 'ACYM_ALL');
224
225 $this->cats = [];
226 $this->subCategories = [];
227 if (!empty($this->categories)) {
228 foreach ($this->categories as $oneCat) {
229 $this->cats[$oneCat->parent_id][] = $oneCat;
230 }
231 $this->handleChildrenCategories($this->rootCategoryId);
232 foreach ($this->categories as $oneCat) {
233 $this->subCategories[$oneCat->id] = $this->getSubCats($oneCat->id);
234 }
235 }
236
237 return acym_select(
238 $this->catvalues,
239 'plugin_category',
240 intval($filter_cat),
241 ['class' => 'plugin_category_select']
242 );
243 }
244
245 private function getSubCats(int $categoryId): array
246 {
247 $result = [$categoryId];
248 if (empty($this->cats[$categoryId])) {
249 return $result;
250 }
251
252 foreach ($this->cats[$categoryId] as $oneSubCategory) {
253 $result = array_merge($result, $this->getSubCats($oneSubCategory->id));
254 }
255
256 return $result;
257 }
258
259 protected function handleChildrenCategories(int $parent_id, int $level = 0): void
260 {
261 if (empty($this->cats[$parent_id])) return;
262
263 foreach ($this->cats[$parent_id] as $cat) {
264 $this->catvalues[] = acym_selectOption($cat->id, str_repeat(' - - ', $level).$cat->title);
265 $this->handleChildrenCategories($cat->id, $level + 1);
266 }
267 }
268
269 protected function getSubCategories(int $categoryId): array
270 {
271 $this->getCategoryFilter();
272
273 return $this->subCategories[$categoryId];
274 }
275
276 protected function autoContentOptions(array &$options, ?string $type = null): void
277 {
278 if ($type === 'event') {
279 $options[] = [
280 'title' => 'ACYM_FROM',
281 'type' => 'date',
282 'name' => 'from',
283 'default' => time(),
284 'relativeDate' => '+',
285 ];
286
287 $options[] = [
288 'title' => 'ACYM_TO',
289 'type' => 'date',
290 'name' => 'to',
291 'default' => '',
292 'relativeDate' => '+',
293 ];
294 } elseif ($type !== 'simple') {
295 $options[] = [
296 'title' => 'ACYM_MIN_PUBLISH_DATE',
297 'tooltip' => 'ACYM_MIN_PUBLISH_DATE_DESC',
298 'type' => 'date',
299 'name' => 'min_publish',
300 'default' => '',
301 'relativeDate' => '-',
302 ];
303 }
304
305 $options[] = [
306 'title' => 'ACYM_COLUMNS',
307 'type' => 'number',
308 'name' => 'cols',
309 'default' => 1,
310 ];
311
312 $options[] = [
313 'title' => 'ACYM_COLUMN_HORIZONTAL_PADDING',
314 'type' => 'number',
315 'name' => 'hpadding',
316 'default' => 10,
317 ];
318
319 $options[] = [
320 'title' => 'ACYM_COLUMN_VERTICAL_PADDING',
321 'type' => 'number',
322 'name' => 'vpadding',
323 'default' => 10,
324 ];
325
326 $options[] = [
327 'title' => 'ACYM_MAX_NB_ELEMENTS',
328 'type' => 'number',
329 'name' => 'max',
330 'default' => 20,
331 ];
332 }
333
334 protected function autoCampaignOptions(array &$options, bool $modified = false): void
335 {
336 if (empty($this->campaignId) && empty($this->campaignType)) {
337 return;
338 } elseif (!empty($this->campaignId) && (empty($this->campaignType) || $this->campaignType !== 'auto')) {
339 $campaignClass = new CampaignClass();
340 $campaign = $campaignClass->getOneById($this->campaignId);
341 if ($campaign->sending_type !== 'auto') return;
342 } elseif (empty($this->campaignId) && !empty($this->campaignType) && $this->campaignType !== 'auto') {
343 return;
344 }
345
346 $options[] = [
347 'title' => 'ACYM_DOCUMENTATION',
348 'type' => 'custom',
349 'name' => 'documentation',
350 'output' => static function (): void {
351 echo '<a target="_blank" href="'.esc_url(
352 ACYM_DOCUMENTATION.'main-pages/campaigns/automatic-campaigns#dont-send-twice-the-same-content'
353 ).'"><i class="acymicon-book"></i></a>';
354 },
355 'js' => '',
356 'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
357 ];
358
359 if ($modified) {
360 $options[] = [
361 'title' => 'ACYM_DATE',
362 'type' => 'select',
363 'name' => 'datefilter',
364 'default' => 'onlynew',
365 'tooltip' => 'ACYM_ONLY_NEWLY_CREATED_DESC',
366 'options' => [
367 '' => 'ACYM_NO_FILTER',
368 'onlynew' => 'ACYM_ONLY_NEWLY_CREATED',
369 'onlymodified' => 'ACYM_ONLY_NEWLY_MODIFIED',
370 ],
371 'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
372 ];
373 } else {
374 $options[] = [
375 'title' => 'ACYM_ONLY_NEWLY_CREATED',
376 'type' => 'boolean',
377 'name' => 'onlynew',
378 'default' => true,
379 'tooltip' => 'ACYM_ONLY_NEWLY_CREATED_DESC',
380 'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
381 ];
382 }
383
384 $options[] = [
385 'title' => 'ACYM_MIN_NB_ELEMENTS',
386 'type' => 'number',
387 'name' => 'min',
388 'default' => 0,
389 'tooltip' => 'ACYM_MIN_NB_ELEMENTS_DESC',
390 'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
391 ];
392 }
393
394 protected function getElementsListing(array $options): string
395 {
396 if ($this->pageInfo->loadMore) {
397 ob_start();
398 $this->displayInnerListing($options);
399
400 return ob_get_clean();
401 }
402 $listing = '<div id="plugin_listing" class="acym__popup__listing">';
403 $listing .= '<input type="hidden" name="plugin" value="'.esc_attr(get_class($this)).'" />';
404
405 // Column names
406 $listing .= '<div class="cell grid-x hide-for-small-only plugin_listing_headers">';
407 foreach ($options['header'] as $oneColumn) {
408 $class = empty($oneColumn['class']) ? '' : ' '.$oneColumn['class'];
409 $listing .= '<div class="cell plugin_listing_headers__title medium-'.$oneColumn['size'].$class.'">'.acym_translation($oneColumn['label']).'</div>';
410 }
411 $listing .= '</div>';
412
413 ob_start();
414 $this->displayInnerListing($options);
415 $listing .= ob_get_clean();
416
417 $listing .= '<input type="hidden" value="1" id="acym_pagination__ajax__load-more" name="acym_pagination__ajax__load-more">';
418 $listing .= '</div>';
419
420 return $listing;
421 }
422
423 private function displayInnerListing(array $options): void
424 {
425 if (empty($options['rows'])) {
426 if ($this->pageInfo->loadMore) {
427 echo '<h3 class="cell acym__listing__empty__load-more text-center">'.esc_html(acym_translation('ACYM_NO_MORE_RESULTS')).'</h3>';
428 } else {
429 echo '<h1 class="cell acym__listing__empty__search__modal text-center">'.esc_html(acym_translation('ACYM_NO_RESULTS_FOUND')).'</h1>';
430 }
431
432 return;
433 }
434
435 $selected = explode(',', acym_getVar('string', 'selected', ''));
436 if (!empty($this->defaultValues->id)) {
437 $selected = [$this->defaultValues->id];
438 }
439
440 foreach ($options['rows'] as $row) {
441 $class = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
442 if (in_array($row->{$options['id']}, $selected)) {
443 $class .= ' selected_row';
444 }
445
446 echo '<div
447 class="'.esc_attr($class).'"
448 data-id="'.esc_attr($row->{$options['id']}).'"
449 onclick="applyContent'.esc_attr($this->name).'(\''.esc_attr($row->{$options['id']}).'\', this);">';
450
451 foreach ($options['header'] as $column => $oneColumn) {
452 $value = $row->$column;
453 $class = empty($oneColumn['class']) ? '' : ' '.$oneColumn['class'];
454
455 echo '<div class="cell medium-'.esc_attr($oneColumn['size'].$class).'">';
456
457 if (!empty($oneColumn['type'])) {
458 if ($oneColumn['type'] === 'date') {
459 if (empty($value)) {
460 echo '-';
461 } else {
462 if (!is_numeric($value) && $value != '0000-00-00 00:00:00') {
463 $value = strtotime($value);
464 }
465 $tooltip = acym_date($value, acym_translation('ACYM_DATE_FORMAT_LC2'));
466 acym_tooltip(
467 [
468 'hoveredText' => acym_date($value, acym_translation('ACYM_DATE_FORMAT_LC5')),
469 'textShownInTooltip' => $tooltip,
470 ]
471 );
472 }
473 } elseif ($oneColumn['type'] === 'int') {
474 echo intval($value);
475 } else {
476 echo esc_html($value);
477 }
478 } else {
479 echo esc_html($value);
480 }
481
482 echo '</div>';
483 }
484
485 echo '</div>';
486 }
487 }
488
489 protected function getCategoryListing(): string
490 {
491 $listing = '';
492 if (empty($this->catvalues)) {
493 $listing .= '<h1 class="cell acym__listing__empty__search__modal text-center">'.acym_translation('ACYM_NO_RESULTS_FOUND').'</h1>';
494
495 return $listing;
496 }
497
498 $listing .= '<div class="acym__popup__listing padding-0">';
499 $selected = [];
500 if (!empty($this->defaultValues->id) && strpos($this->defaultValues->id, '-')) {
501 $selected = explode('-', $this->defaultValues->id);
502 }
503 foreach ($this->catvalues as $oneCat) {
504 if (empty($oneCat->value)) continue;
505
506 $classes = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
507 if (in_array($oneCat->value, $selected)) {
508 $classes .= ' selected_row';
509 }
510 $listing .= '<div class="'.$classes.'" data-id="'.intval($oneCat->value).'" onclick="applyContentauto'.esc_attr($this->name).'('.intval($oneCat->value).', this);">
511 <div class="cell medium-5">'.esc_html($oneCat->text).'</div>
512 </div>';
513 }
514 $listing .= '</div>';
515
516 return $listing;
517 }
518
519 protected function getTagListing(): string
520 {
521 $listing = '';
522 if (empty($this->tagvalues)) {
523 $listing .= '<h1 class="cell acym__listing__empty__search__modal text-center">'.acym_translation('ACYM_NO_RESULTS_FOUND').'</h1>';
524
525 return $listing;
526 }
527
528 $listing .= '<div class="acym__popup__listing padding-0">';
529 $selected = [];
530 if (!empty($this->defaultValues->id) && strpos($this->defaultValues->id, '-')) {
531 $selected = explode('-', $this->defaultValues->id);
532 }
533 $termIdName = 'term_id';
534 if ('wordpress' === ACYM_CMS) {
535 $termIdName = 'term_taxonomy_id';
536 }
537 foreach ($this->tagvalues as $oneTag) {
538 if (empty($oneTag->$termIdName)) continue;
539
540 $class = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
541 if (in_array($oneTag->$termIdName, $selected)) {
542 $class .= ' selected_row';
543 }
544 $listing .= '<div class="'.esc_attr($class).'"
545 data-id="'.intval($oneTag->$termIdName).'"
546 onclick="applyContent'.esc_attr($this->name).'_tags('.intval($oneTag->$termIdName).', this);">
547 <div class="cell medium-5">'.esc_html($oneTag->name).'</div>
548 </div>';
549 }
550 $listing .= '</div>';
551
552 return $listing;
553 }
554
555 protected function replaceMultiple(object &$email)
556 {
557 $this->generateByCategory($email);
558 if (empty($this->tags)) return;
559
560 $this->pluginHelper->replaceTags($email, $this->tags, true);
561 }
562
563 protected function handleOrderBy(string &$query, object $parameter, ?string $table = null)
564 {
565 if (empty($parameter->order)) return;
566
567 $ordering = explode(',', $parameter->order);
568 if ($ordering[0] === 'rand') {
569 $query .= ' ORDER BY rand()';
570 } else {
571 $table = null === $table ? '' : $table.'.';
572 $column = $ordering[0];
573
574 if (strpos($column, '.') !== false) {
575 $parts = explode('.', $column, 2);
576 $table = acym_secureDBColumn($parts[0]).'.';
577 $column = $parts[1];
578 }
579
580 $query .= ' ORDER BY '.$table.'`'.acym_secureDBColumn(trim($column)).'` '.acym_secureDBColumn(trim($ordering[1]));
581 }
582 }
583
584 protected function handleMax(string &$query, object $parameter): void
585 {
586 if (empty($parameter->max)) {
587 $parameter->max = 20;
588 }
589 $query .= ' LIMIT '.intval($parameter->max);
590 }
591
592 protected function handleMin(array $elements, object $parameter): void
593 {
594 if (!empty($parameter->min) && count($elements) < $parameter->min) {
595 $this->generateCampaignResult->status = false;
596 $this->generateCampaignResult->message = acym_translationSprintf(
597 'ACYM_GENERATE_CAMPAIGN_NOT_ENOUGH_CONTENT',
598 $this->pluginDescription->name,
599 count($elements),
600 $parameter->min
601 );
602 }
603 }
604
605 protected function getLastGenerated(int $mailId): int
606 {
607 $campaignClass = new CampaignClass();
608
609 return $campaignClass->getLastGenerated($mailId);
610 }
611
612 /**
613 * Returns the individual elements tags based on a query result
614 */
615 protected function finalizeCategoryFormat(string $query, object $parameter, ?string $table = null): string
616 {
617 $this->handleOrderBy($query, $parameter, $table);
618 $this->handleMax($query, $parameter);
619
620 $elements = acym_loadResultArray($query);
621
622 return $this->formatIndividualTags($elements, $parameter);
623 }
624
625 protected function formatIndividualTags(array $elements, object $parameter): string
626 {
627 $this->handleMin($elements, $parameter);
628
629 if (empty($elements)) return '';
630
631 $elements = $this->groupByCategory($elements);
632
633 $customLayout = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'_auto.php';
634 if (file_exists($customLayout)) {
635 ob_start();
636 require $customLayout;
637
638 return ob_get_clean();
639 }
640
641 $arrayElements = $this->buildIndividualTags($elements, $parameter);
642
643 return $this->pluginHelper->getFormattedResult($arrayElements, $parameter);
644 }
645
646 protected function buildIndividualTags(array $elements, object $parameter): array
647 {
648 $arrayElements = [];
649 unset($parameter->id);
650
651 $i = 0;
652 foreach ($elements as $oneElementId) {
653 $args = [];
654 $args[] = $this->name.':'.$oneElementId;
655
656 foreach ($parameter as $oneParam => $val) {
657 if (is_bool($val)) {
658 $args[] = $oneParam;
659 } else {
660 $args[] = $oneParam.':'.$val;
661 }
662 }
663
664 if ($i % 2 === 1 && !empty($parameter->alternate)) {
665 $args[] = 'invert';
666 }
667
668 $arrayElements[] = '{'.implode('| ', $args).'}';
669
670 $i++;
671 }
672
673 return $arrayElements;
674 }
675
676 /**
677 * This method can be overridden in the add-on when a group by category option is added
678 */
679 protected function groupByCategory(array $elements)
680 {
681 return $elements;
682 }
683
684 protected function getSelectedArea(object $parameter): array
685 {
686 $allcats = explode('-', $parameter->id);
687 $selectedArea = [];
688 foreach ($allcats as $oneCat) {
689 if (empty($oneCat)) continue;
690 $selectedArea[] = intval($oneCat);
691 }
692
693 return $selectedArea;
694 }
695
696 protected function replaceOne(object &$email): void
697 {
698 $tags = $this->pluginHelper->extractTags($email, $this->name);
699 if (empty($tags)) return;
700
701 $newConfiguration = [
702 'dcontent_default_'.get_class($this) => json_encode(end($tags)),
703 ];
704 $this->config->saveConfig($newConfiguration);
705
706 if (method_exists($this, 'loadLibraries') && !$this->loadLibraries($email)) {
707 return;
708 }
709
710 $this->emailLanguage = $email->links_language;
711 $translationTool = $this->config->get('translate_content', 'no');
712
713 $tagsReplaced = [];
714 foreach ($tags as $i => $oneTag) {
715 if (isset($tagsReplaced[$i])) continue;
716
717 if (!empty($this->emailLanguage) && $translationTool !== 'no' && acym_isMultilingual()) {
718 $oneTag->id = $this->getTranslationId($oneTag->id, $translationTool);
719 }
720 $tagsReplaced[$i] = $this->replaceIndividualContent($oneTag);
721 }
722
723 $email->custom_view = file_exists(ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.html');
724
725 $this->pluginHelper->replaceTags($email, $tagsReplaced, true);
726 }
727
728 protected function initIndividualContent(object &$tag, string $query)
729 {
730 $element = acym_loadObject($query);
731
732 if (empty($element)) {
733 if (acym_isAdmin()) {
734 acym_enqueueMessage(acym_translationSprintf('ACYM_CONTENT_NOT_FOUND', $tag->id), 'notice');
735 }
736
737 return null;
738 }
739
740 if (empty($tag->display)) {
741 $tag->display = [];
742 } else {
743 $tag->display = explode(',', $tag->display);
744 }
745
746 return $element;
747 }
748
749 protected function getCustomLayoutVars(object $element): array
750 {
751 $varFields = [];
752 $varFields['{picthtml}'] = '';
753 foreach ($element as $fieldName => $oneField) {
754 if (is_object($oneField) || is_array($oneField)) {
755 $varFields['{'.$fieldName.'}'] = json_encode($oneField);
756 } else {
757 $varFields['{'.$fieldName.'}'] = $oneField;
758 }
759 }
760
761 return $varFields;
762 }
763
764 /**
765 * Handles the custom layouts and the pictures management
766 *
767 * @param string $htmlResult What will be inserted in the email
768 * @param object $insertionOptions Selected options when inserting dcontent
769 * @param array $dataShortcodes Data used as shortcodes in custom layouts
770 */
771 protected function finalizeElementFormat(string $htmlResult, object $insertionOptions, array $dataShortcodes): string
772 {
773 // Hidden feature, users can do a PHP custom view
774 $fullCustomLayoutPath = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.php';
775 if ($this->config->get('php_overrides', 0) == 1 && file_exists($fullCustomLayoutPath)) {
776 ob_start();
777 require $fullCustomLayoutPath;
778 $viewContent = ob_get_clean();
779 $htmlResult = str_replace(array_keys($dataShortcodes), $dataShortcodes, $viewContent);
780 } else {
781 // Default custom view feature with only HTML for security reasons
782 $customLayoutPath = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.html';
783 if (file_exists($customLayoutPath)) {
784 $dataShortcodes['{wrappedText}'] = $this->pluginHelper->wrappedText;
785 $viewContent = acym_fileGetContent($customLayoutPath);
786 $viewContentReplace = str_replace(array_keys($dataShortcodes), $dataShortcodes, $viewContent);
787 if ($viewContent !== $viewContentReplace) {
788 $htmlResult = $viewContentReplace;
789 }
790 }
791 }
792
793 $allShortcodesList = '<table><tr><th>Shortcode</th><th>Value</th></tr>';
794 foreach ($dataShortcodes as $name => $value) {
795 if (!is_string($value)) {
796 continue;
797 }
798
799 $allShortcodesList .= '<tr><td>'.trim($name, '{}').'</td><td>'.$value.'</td></tr>';
800 }
801 $allShortcodesList .= '</table>';
802 $htmlResult = str_replace('{allshortcodes}', $allShortcodesList, $htmlResult);
803
804 // Resize/remove pictures if needed
805 return $this->pluginHelper->managePicts($insertionOptions, $htmlResult);
806 }
807
808 /**
809 * Keep this method untyped until v12
810 */
811 public function onAcymDeclareTriggers(&$triggers, &$defaultValues)
812 {
813 if (empty($this->automationTriggers) && method_exists($this, 'initAutomationTriggers')) {
814 $this->initAutomationTriggers();
815 }
816
817 foreach ($this->automationTriggers as $group => $groupTriggers) {
818 foreach ($groupTriggers as $key => $name) {
819 $triggers[$group][$key] = $this->declareTrigger($key, $name, $defaultValues, $group);
820 }
821 }
822 }
823
824 /**
825 * Keep this method untyped until v12
826 */
827 public function onAcymDeclareTriggersScenario(&$triggers, &$defaultValues)
828 {
829 $this->onAcymDeclareTriggers($triggers, $defaultValues);
830 }
831
832 /**
833 * Declares name and options callbacks for automation/scenario triggers
834 */
835 protected function declareTrigger(string $key, string $name, $defaultValues = [], string $group = 'user'): object
836 {
837 $trigger = new \stdClass();
838 $trigger->name = $name;
839
840 if (!is_array($defaultValues)) {
841 $defaultValues = [];
842 }
843
844 $method = 'displayTriggerOptions_'.$key;
845 if (!method_exists($this, $method)) {
846 $method = 'displayTriggerOptions';
847 }
848
849 if (method_exists($this, $method)) {
850 $automationFieldName = '[triggers]['.$group.']['.$key.']';
851 $trigger->displayOptions = function (?string $fieldName = null) use ($method, $defaultValues, $key, $automationFieldName): void {
852 $this->$method($fieldName === null ? $automationFieldName : $fieldName, $defaultValues, $key);
853 };
854 }
855
856 return $trigger;
857 }
858
859 public function onAcymDeclareActions(array &$actions): void
860 {
861 if (empty($this->automationActions) && method_exists($this, 'initAutomationActions')) {
862 $this->initAutomationActions();
863 }
864
865 foreach ($this->automationActions as $key => $name) {
866 $actions[$key] = $this->declareAction($key, $name);
867 }
868 }
869
870 public function onAcymDeclareActionsScenario(array &$actions): void
871 {
872 $this->onAcymDeclareActions($actions);
873 }
874
875 /**
876 * Declares name and options callbacks for an automation/scenario action
877 */
878 protected function declareAction(string $key, string $name): object
879 {
880 $action = new \stdClass();
881 $action->name = $name;
882
883 $method = 'displayActionOptions_'.$key;
884 if (!method_exists($this, $method)) {
885 $method = 'displayActionOptions';
886 }
887
888 if (method_exists($this, $method)) {
889 $action->displayOptions = function () use ($method, $key): void {
890 $this->$method($key);
891 };
892 }
893
894 return $action;
895 }
896
897 public function onAcymDeclareConditions(array &$conditions): void
898 {
899 if (empty($this->automationConditions) && method_exists($this, 'initAutomationConditions')) {
900 $this->initAutomationConditions();
901 }
902
903 foreach ($this->automationConditions as $group => $groupConditions) {
904 foreach ($groupConditions as $key => $name) {
905 $conditions[$group][$key] = $this->declareCondition($key, $name);
906 }
907 }
908 }
909
910 public function onAcymDeclareConditionsScenario(array &$conditions): void
911 {
912 $this->onAcymDeclareConditions($conditions);
913 }
914
915 public function onAcymDeclareFilters(array &$filters): void
916 {
917 if (empty($this->automationFilters) && method_exists($this, 'initAutomationFilters')) {
918 $this->initAutomationFilters();
919 }
920
921 foreach ($this->automationFilters as $key => $name) {
922 $filters[$key] = $this->declareFilter($key, $name);
923 }
924 }
925
926 /**
927 * Returns a field name without its acym_condition / acym_action prefix.
928 */
929 protected function getFieldNameWithoutPrefix(string $fieldName): string
930 {
931 return str_replace(['acym_condition', 'acym_action'], '', $fieldName);
932 }
933
934 protected function declareCondition(string $key, string $name): object
935 {
936 $condition = new \stdClass();
937 $condition->name = $name;
938
939 $renderer = $this->getOptionsRenderer('displayConditionOptions', $key, self::CONDITION_FIELD_NAME.'['.$key.']');
940 if ($renderer !== null) {
941 $condition->displayOptions = $renderer;
942 }
943
944 return $condition;
945 }
946
947 public function onAcymMailboxActionDefine(&$actions)
948 {
949 if (empty($this->mailboxActions) && method_exists($this, 'initMailboxActions')) {
950 $this->initMailboxActions();
951 }
952
953 foreach ($this->mailboxActions as $key => $name) {
954 $actions[$key] = $this->declareMailboxAction($key, $name);
955 }
956 }
957
958 /**
959 * A follow-up condition row: a translated sentence holding the selectors the add-on built.
960 */
961 protected function followupConditionRow(string $translationKey, string ...$fields): callable
962 {
963 return static function () use ($translationKey, $fields): void {
964 echo acym_translationSprintf($translationKey, ...$fields);
965 };
966 }
967
968 protected function declareMailboxAction(string $key, string $name): object
969 {
970 $action = new \stdClass();
971 $action->name = $name;
972
973 $renderer = $this->getOptionsRenderer('displayMailboxActionOptions', $key, self::MAILBOX_ACTION_FIELD_NAME.'['.$key.']');
974 if ($renderer !== null) {
975 $action->displayOptions = $renderer;
976 }
977
978 return $action;
979 }
980
981 protected function declareFilter(string $key, string $name): object
982 {
983 $filter = new \stdClass();
984 $filter->name = $name;
985
986 $renderer = $this->getOptionsRenderer('displayFilterOptions', $key, self::FILTER_FIELD_NAME.'['.$key.']');
987 if ($renderer !== null) {
988 $filter->displayOptions = $renderer;
989 }
990
991 return $filter;
992 }
993
994 /**
995 * Builds the callback that displays the option fields of a declaration.
996 */
997 private function getOptionsRenderer(string $methodPrefix, string $key, string $defaultFieldName): ?callable
998 {
999 $method = $methodPrefix.'_'.$key;
1000 if (!method_exists($this, $method)) {
1001 $method = $methodPrefix;
1002 }
1003
1004 if (!method_exists($this, $method)) {
1005 return null;
1006 }
1007
1008 return function (?string $fieldName = null) use ($method, $key, $defaultFieldName): void {
1009 $this->$method($fieldName === null ? $defaultFieldName : $fieldName, $key);
1010 };
1011 }
1012
1013 /**
1014 * Most add-ons offer the same choices as conditions and as filters, the only difference being the name the fields are nested under
1015 */
1016 protected function filtersFromConditions(array &$filters): void
1017 {
1018 $conditions = [];
1019 $this->onAcymDeclareConditions($conditions);
1020
1021 foreach ($conditions as $oneGroup) {
1022 foreach ($oneGroup as $oneFilterName => $oneFilter) {
1023 $renderer = $this->getOptionsRenderer('displayConditionOptions', $oneFilterName, self::FILTER_FIELD_NAME.'['.$oneFilterName.']');
1024 if ($renderer !== null) {
1025 $oneFilter->displayOptions = $renderer;
1026 } elseif (!empty($oneFilter->option)) {
1027 // TODO: remove this on version 12, only there for retro-compat of add-ons/plugins not updated along with AcyMailing
1028 $oneFilter->option = str_replace(['acym_condition', '[conditions]'], ['acym_action', '[filters]'], $oneFilter->option);
1029 }
1030
1031 $filters[$oneFilterName] = $oneFilter;
1032 }
1033 }
1034 }
1035
1036 protected function getElementTags(string $type, int $id): array
1037 {
1038 $tags = acym_loadObjectList(
1039 'SELECT tags.`title`, tags.`alias`, tags.`id`
1040 FROM #__tags AS tags
1041 JOIN #__contentitem_tag_map AS map ON tags.`id` = map.`tag_id`
1042 WHERE map.`type_alias` = '.acym_escapeDB($type).'
1043 AND map.`content_item_id` = '.intval($id)
1044 );
1045
1046 if (empty($tags)) return [];
1047
1048 $displayTags = [];
1049 foreach ($tags as $oneTag) {
1050 $displayTags[] = '<a href="index.php?option=com_tags&view=tag&id='.$oneTag->id.':'.$oneTag->alias.'" target="_blank">'.$oneTag->title.'</a>';
1051 }
1052
1053 return $displayTags;
1054 }
1055
1056 /**
1057 * @return mixed
1058 */
1059 protected function getFormattedValue(array $fieldValues)
1060 {
1061 $field = $fieldValues[0];
1062
1063 if (!empty($field->fieldparams) && !is_array($field->fieldparams)) {
1064 $field->fieldparams = json_decode($field->fieldparams, true);
1065 }
1066
1067 switch ($field->type) {
1068 case 'calendar':
1069 $format = acym_translation($field->fieldparams['showtime'] == '1' ? 'ACYM_DATE_FORMAT_LC2' : 'ACYM_DATE_FORMAT_LC1');
1070 $field->value = HTMLHelper::_('date', $field->value, $format);
1071 break;
1072
1073 case 'checkboxes':
1074 case 'list':
1075 case 'radio':
1076 $values = [];
1077 foreach ($fieldValues as $oneValue) {
1078 foreach ($field->fieldparams['options'] as $oneOPT) {
1079 if ($oneOPT['value'] == $oneValue->value) {
1080 $values[] = $oneOPT['name'];
1081 break;
1082 }
1083 }
1084 }
1085
1086 $field->value = implode(', ', $values);
1087 break;
1088
1089 case 'dpcalendar':
1090 if (empty($field->value)) {
1091 $field->value = '';
1092 break;
1093 }
1094
1095 $title = acym_loadResult('SELECT `title` FROM #__categories WHERE `id` = '.intval($field->value));
1096 if (empty($title)) {
1097 $field->value = '';
1098 } else {
1099 $date = '#year='.acym_date('now', 'Y').'&month='.acym_date('now', 'm').'&day='.acym_date('now', 'd').'&view=month';
1100 $field->value = '<a target="_blank" href="index.php?option=com_dpcalendar&view=calendar&id='.intval($field->value).$date.'">'.$title.'</a>';
1101 }
1102 break;
1103
1104 case 'imagelist':
1105 if (!empty($field->fieldparams['directory']) && strlen($field->fieldparams['directory']) > 1) {
1106 $field->value = '/'.$field->value;
1107 } else {
1108 $field->fieldparams['directory'] = '';
1109 }
1110 $field->value = '<img src="images/'.$field->fieldparams['directory'].$field->value.'" />';
1111 break;
1112
1113 case 'media':
1114 $value = '';
1115 if (!empty($field->value)) {
1116 if (substr($field->value, 0, 1) === '{') {
1117 $value = json_decode($field->value, true);
1118 if (!empty($value['imagefile'])) {
1119 $alt = empty($value['alt_text']) ? '' : ' alt="'.esc_attr($value['alt_text']).'"';
1120 $value = '<img src="'.$value['imagefile'].'"'.$alt.' />';
1121 }
1122 } else {
1123 $value = '<img src="'.$field->value.'" alt="" />';
1124 }
1125 }
1126
1127 $field->value = $value;
1128 break;
1129
1130 case 'repeatable':
1131 if (!empty($field->value)) {
1132 $values = is_string($field->value) ? json_decode($field->value) : $field->value;
1133 $formattedValues = [];
1134 foreach ($values as $oneSetOfValues) {
1135 $formattedSet = [];
1136 foreach ($oneSetOfValues as $oneLabel => $oneValue) {
1137 if (empty($oneValue)) continue;
1138
1139 foreach ($field->fieldparams['fields'] as $oneField) {
1140 if ($oneField['fieldname'] !== $oneLabel) continue;
1141
1142 if ($oneField['fieldtype'] === 'media') {
1143 $oneValue = '<img src="'.$oneValue.'" alt="" />';
1144 }
1145
1146 $formattedSet[] = $oneValue;
1147 break;
1148 }
1149 }
1150
1151 if (empty($formattedSet)) continue;
1152 $formattedValues[] = implode(', ', $formattedSet);
1153 }
1154
1155 $field->value = empty($formattedSet) ? '' : '<ul><li>'.implode('</li><li>', $formattedValues).'</li></ul>';
1156 }
1157 break;
1158
1159 case 'sql':
1160 if (empty($field->options)) {
1161 $field->options = acym_loadObjectList($field->fieldparams['query'], 'value');
1162 }
1163
1164 $field->value = $field->options[$field->value]->text;
1165 break;
1166
1167 case 'url':
1168 $field->value = '<a target="_blank" href="'.$field->value.'">'.$field->value.'</a>';
1169 break;
1170
1171 case 'user':
1172 $field->value = acym_currentUserName($field->value);
1173 break;
1174
1175 case 'usergrouplist':
1176 if (empty($field->usergroups)) {
1177 $field->usergroups = acym_loadObjectList('SELECT id, title FROM #__usergroups', 'id');
1178 }
1179
1180 if (empty($field->usergroups[$field->value])) {
1181 $field->value = '';
1182 break;
1183 }
1184 $field->value = $field->usergroups[$field->value]->title;
1185 break;
1186 case 'subform':
1187 if (!empty($field->value)) {
1188 $rows = is_string($field->value) ? json_decode($field->value, true) : $field->value;
1189
1190 $formattedValues = [];
1191 foreach ($rows as $values) {
1192 $fieldValues = [];
1193 foreach ($values as $fieldIdentifier => $fieldValue) {
1194 $fieldId = str_replace('field', '', $fieldIdentifier);
1195 $oneFieldDefinition = acym_loadObject(
1196 'SELECT `field`.*
1197 FROM #__fields AS `field`
1198 WHERE `field`.`id` = '.intval($fieldId)
1199 );
1200
1201 if (empty($oneFieldDefinition)) {
1202 break 3;
1203 }
1204
1205 $multiCompatibleValues = [];
1206 if (is_array($fieldValue)) {
1207 foreach ($fieldValue as $oneFieldValue) {
1208 $oneValue = clone $oneFieldDefinition;
1209 $oneValue->value = $oneFieldValue;
1210 $multiCompatibleValues[] = $oneValue;
1211 }
1212 } else {
1213 $oneFieldDefinition->value = $fieldValue;
1214 $multiCompatibleValues[] = $oneFieldDefinition;
1215 }
1216
1217 $fieldValues[] = $oneFieldDefinition->label.': '.$this->getFormattedValue($multiCompatibleValues);
1218 }
1219 $formattedValues[] = implode(', ', $fieldValues);
1220 }
1221
1222 $field->value = empty($formattedValues) ? '' : '<ul><li>'.implode('</li><li>', $formattedValues).'</li></ul>';
1223 }
1224 }
1225
1226 return $field->value;
1227 }
1228
1229 protected function getLanguage(?string $elementLanguage = null, bool $onlyValue = false): string
1230 {
1231 $value = $this->emailLanguage;
1232 if (!empty($elementLanguage) && $elementLanguage !== '*') {
1233 $value = $elementLanguage;
1234 }
1235
1236 if (empty($value)) {
1237 return '';
1238 }
1239
1240 if (ACYM_CMS === 'joomla' && acym_isPluginActive('languagefilter')) {
1241 $languages = acym_loadObjectList('SELECT * FROM #__languages', 'lang_code');
1242 if (isset($languages[$value])) {
1243 $value = $languages[$value]->sef;
1244 } else {
1245 $value = substr($value, 0, strpos($value, '-'));
1246 }
1247
1248 if ($onlyValue) {
1249 return $value;
1250 }
1251
1252 return '&lang='.$value;
1253 } else {
1254 if ($onlyValue) {
1255 return $value;
1256 }
1257
1258 return '&language='.$value;
1259 }
1260 }
1261
1262 // Don't add types for $tag and $sef, for retro-compatibility
1263 protected function finalizeLink(string $link, $tag = null, $sef = true): string
1264 {
1265 if (acym_isPluginActive('languagefilter')) {
1266 if (strpos($link, 'lang=') === false && !empty($this->emailLanguage)) {
1267 $link .= strpos($link, '?') === false ? '?' : '&';
1268 $link .= 'lang='.substr($this->emailLanguage, 0, strpos($this->emailLanguage, '-'));
1269 }
1270 } else {
1271 if (strpos($link, 'language=') === false && !empty($this->emailLanguage)) {
1272 $link .= strpos($link, '?') === false ? '?' : '&';
1273 $link .= 'language='.$this->emailLanguage;
1274 }
1275 }
1276
1277 if (!empty($tag->autologin)) {
1278 $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:autologin_token|urlencode}';
1279 }
1280
1281 // Retro-compatibility for add-on versions before the v8.6.0
1282 if (is_bool($tag)) {
1283 $sef = $tag;
1284 }
1285
1286 return acym_frontendLink($link, false, $sef);
1287 }
1288
1289 protected function handleCustomFields(object $tag, array &$customFields): void
1290 {
1291 if (empty($tag->custom)) {
1292 return;
1293 }
1294
1295 $tag->custom = explode(',', $tag->custom);
1296 acym_arrayToInteger($tag->custom);
1297
1298 $rawfields = acym_loadObjectList(
1299 'SELECT `field`.*, `values`.`value`
1300 FROM #__fields_values AS `values`
1301 JOIN #__fields AS `field` ON `values`.`field_id` = `field`.`id`
1302 WHERE `values`.`item_id` = '.intval($tag->id).' AND `field`.`id` IN ('.implode(', ', $tag->custom).')'
1303 );
1304
1305 $fields = [];
1306 foreach ($rawfields as $field) {
1307 $fields[$field->id][] = $field;
1308 }
1309
1310 usort($fields, function ($a, $b) {
1311 return $a[0]->ordering <=> $b[0]->ordering;
1312 });
1313
1314 foreach ($fields as $fieldValues) {
1315 $value = $this->getFormattedValue($fieldValues);
1316
1317 if (empty($value)) continue;
1318
1319 $customFields[] = [
1320 $value,
1321 $fieldValues[0]->label,
1322 ];
1323 }
1324 }
1325
1326 protected function getIntro(string $text): string
1327 {
1328 $pageBreak = null;
1329 $possibleBreaks = [
1330 '<!--more-->',
1331 '<hr id="system-readmore" />',
1332 '<!--nextpage-->',
1333 ];
1334
1335 foreach ($possibleBreaks as $oneBreak) {
1336 if (strpos($text, $oneBreak) !== false) {
1337 $pageBreak = $oneBreak;
1338 break;
1339 }
1340 }
1341
1342 if (empty($pageBreak)) {
1343 return $text;
1344 }
1345
1346 $split = explode($pageBreak, $text, 2);
1347
1348 return array_shift($split);
1349 }
1350
1351 public function displayCustomViewEditor(): void
1352 {
1353 if (empty($this->settings['custom_view']) || !acym_isAdmin()) {
1354 return;
1355 }
1356
1357 echo '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">';
1358 echo esc_html(acym_translation('ACYM_ADDON_SETTINGS'));
1359 echo '<i class="acymicon-keyboard-arrow-up"></i></p>';
1360 echo '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container">';
1361 $this->displayCustomView($this->settings['custom_view']);
1362 echo '</div>';
1363 }
1364
1365 public function generateSettings(object $plugin): bool
1366 {
1367 if (empty($plugin->settings)) {
1368 return false;
1369 }
1370
1371 if (array_key_exists('not_installed', $plugin->settings)) {
1372 $plugin->settings = 'not_installed';
1373
1374 return true;
1375 }
1376
1377 foreach ($plugin->settings as $key => $field) {
1378 $text = '';
1379 if (empty($field['type'])) {
1380 $plugin->settings[$key] = $text;
1381 continue;
1382 }
1383
1384 $id = $plugin->folder_name.'_'.$key;
1385 $name = $plugin->folder_name.'['.$key.']';
1386 if (!empty($field['label'])) {
1387 $field['label'] = acym_translation($field['label']);
1388 }
1389
1390 if (!empty($field['info'])) {
1391 ob_start();
1392 acym_info(
1393 [
1394 'textShownInTooltip' => acym_translation($field['info']),
1395 'classText' => 'wysid_tooltip',
1396 'isWarning' => !empty($field['info_warning']),
1397 ]
1398 );
1399 $field['label'] .= ob_get_clean();
1400 }
1401
1402 if ($field['type'] === 'checkbox') {
1403 $classLabel = 'shrink';
1404 $text .= '<label for="'.esc_attr($id).'" class="cell '.esc_attr($classLabel).'">'.esc_html($field['label']).'</label>';
1405 $text .= '<input id="'.esc_attr($id).'" class="cell shrink" type="checkbox" name="'.esc_attr($name).'" '.checked(
1406 !empty($field['value']),
1407 true,
1408 false
1409 ).'>';
1410 } elseif ($field['type'] === 'switch') {
1411 ob_start();
1412 acym_switch([
1413 'name' => $name,
1414 'value' => $field['value'],
1415 'label' => $field['label'],
1416 'labelClass' => 'large-7',
1417 ]);
1418 $text .= ob_get_clean();
1419 } elseif ($field['type'] === 'select') {
1420 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1421 $text .= acym_select(
1422 $field['data'],
1423 $name,
1424 $field['value'],
1425 ['class' => 'acym__select'],
1426 'value',
1427 'text',
1428 false,
1429 true
1430 );
1431 } elseif ($field['type'] === 'multiple_select') {
1432 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1433 $text .= acym_selectMultiple(
1434 $field['data'],
1435 $name,
1436 empty($field['value']) ? [] : $field['value'],
1437 ['class' => 'acym__select']
1438 );
1439 } elseif ($field['type'] === 'text') {
1440 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1441 $text .= '<input type="text" name="'.$name.'" value="'.esc_attr($field['value']).'" class="cell shrink">';
1442 } elseif ($field['type'] === 'number') {
1443 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1444 $text .= '<input type="number" name="'.$name.'" value="'.esc_attr($field['value']).'" class="cell large-2 medium-5">';
1445 if (!empty($field['post_text'])) $text .= '<span class="cell shrink">'.strtolower($field['post_text']).'</span>';
1446 } elseif ($field['type'] === 'radio') {
1447 $text .= '<p class="cell">'.$field['label'].'</p>';
1448 ob_start();
1449 acym_radio(
1450 $field['data'],
1451 $name,
1452 $field['value']
1453 );
1454 $text .= ob_get_clean();
1455 } elseif ($field['type'] === 'date') {
1456 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1457 $text .= acym_dateField(
1458 $name,
1459 $field['value'],
1460 'cell shrink'
1461 );
1462 } elseif ($field['type'] === 'custom_view') {
1463 ob_start();
1464 $this->displayCustomView($field);
1465 $text .= ob_get_clean();
1466 } elseif ($field['type'] === 'multikeyvalue') {
1467 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1468
1469 $text .= '<div class="multikeyvalue_container grid-x">';
1470
1471 if (!empty($field['value'])) {
1472 $headers = json_decode($field['value'], true);
1473 foreach ($headers as $headerKey => $headerValue) {
1474 $text .= '<input type="text" class="cell" placeholder="'.esc_attr(acym_translation('ACYM_DKIM_KEY')).'" value="'.esc_attr($headerKey).'"/>';
1475 $text .= '<input type="text" class="cell" placeholder="'.esc_attr(acym_translation('ACYM_VALUE')).'" value="'.esc_attr($headerValue).'" />';
1476 $text .= '<div class="multikeyvalue_container_separator cell small-6"></div>';
1477 }
1478 }
1479
1480 $text .= '<input type="text" class="cell" placeholder="'.esc_attr(acym_translation('ACYM_DKIM_KEY')).'" value=""/>';
1481 $text .= '<input type="text" class="cell" placeholder="'.esc_attr(acym_translation('ACYM_VALUE')).'" value="" />';
1482
1483 $text .= '<button class="button multikeyvalue_container_new">'.esc_html(acym_translation('ACYM_ADD_NEW')).'</button>';
1484
1485 $text .= '<input type="hidden" name="'.esc_attr($name).'" value="" />';
1486 $text .= '</div>';
1487 } elseif ($field['type'] === 'custom') {
1488 $text .= $field['content'];
1489 }
1490 $plugin->settings[$key] = $text;
1491 }
1492
1493 return true;
1494 }
1495
1496 protected function displayCustomView(array $field): void
1497 {
1498 if (!acym_isAdmin()) {
1499 return;
1500 }
1501
1502 $idCustomView = 'acym__plugins__installed__custom-view__'.$this->name;
1503 $ctrl = acym_getVar('string', 'ctrl', '');
1504 $classTooltip = $ctrl === 'dynamics' ? '' : 'wysid_tooltip';
1505
1506 echo '<label class="cell">';
1507 echo esc_html(acym_translation('ACYM_CUSTOM_VIEW'));
1508 acym_info(
1509 [
1510 'textShownInTooltip' => 'ACYM_CUSTOM_VIEW_DESC',
1511 'classText' => $classTooltip,
1512 ]
1513 );
1514 echo '</label>';
1515
1516 if (empty($field['tags'])) {
1517 $field['tags'] = [];
1518 }
1519
1520 ob_start();
1521 acym_info(
1522 [
1523 'textShownInTooltip' => acym_translation('ACYM_DYNAMIC_CONTENT_DESC'),
1524 ]
1525 );
1526 $tooltip = ob_get_clean();
1527
1528 $modalContent = '<div id="'.esc_attr($idCustomView).'" class="cell grid-x acym__plugins__installed__custom-view" acym-data-tags="'.esc_attr(
1529 json_encode($field['tags'])
1530 ).'">
1531 <h2 class="cell text-center acym__title__primary__color">'.esc_html(acym_translationSprintf('ACYM_CUSTOM_VIEW_FOR_X', $this->pluginDescription->name)).'</h2>
1532 <div class="cell grid-x acym__plugins__installed__custom-view__edit-container">
1533 <div class="acym__plugins__installed__custom-view__editor-loader grid-x cell align-center acym_vcenter" v-if="loading">
1534 <div class="cell shrink acym_loader_logo">
1535 '.acym_fileGetContent(ACYM_IMAGES.'logos/logo_grey.svg').'
1536 </div>
1537 </div>
1538 <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>
1539 <div class="cell grid-x medium-3 margin-left-1 acym__plugins__installed__custom-view__tags">
1540 <h3 class="acym__title acym__title__secondary cell text-center">'.esc_html(acym_translation('ACYM_DYNAMIC_CONTENT')).$tooltip.'</h3>
1541 <div class="cell acym__plugins__installed__custom-view__tag" v-for="(trad, tag) in tags" :key="tag" @click.prevent="insertTag(tag)">{{ trad }}</div>
1542 </div>
1543 </div>
1544 <div class="cell grid-x acym__plugins__installed__custom-view__actions acym_vcenter margin-top-1 padding-bottom-1">
1545 <div class="cell auto grid-x">
1546 <button type="button" class="cell shrink button-secondary button" @click="resetView">'.esc_html(acym_translation('ACYM_RESET_VIEW')).'</button>
1547 <div class="cell shrink margin-right-2 acym_vcenter margin-left-1">
1548 <i v-if="deleting" class="acymicon-spin acymicon-circle-o-notch" style="margin-bottom: 0; line-height: 26px;"></i>
1549 <span v-if="deleted">{{ messageDeleted }}</span>
1550 </div>
1551 </div>
1552 <div class="cell auto align-right grid-x">
1553 <div class="cell shrink margin-right-2 acym_vcenter">
1554 <i v-if="saving" class="acymicon-spin acymicon-circle-o-notch" style="margin-bottom: 0; line-height: 26px;"></i>
1555 <span v-if="saved">{{ messageSaved }}</span>
1556 </div>
1557 <button @click="save()" class="cell shrink button" type="button">'.esc_html(acym_translation('ACYM_SAVE_NEW_CUSTOM_VIEW_VERSION')).'</button>
1558 </div>
1559 </div>
1560 </div>';
1561
1562 acym_modal(
1563 acym_translation('ACYM_EDIT_CUSTOM_VIEW'),
1564 $modalContent,
1565 null,
1566 [
1567 'acym-data-plugins-id' => $idCustomView,
1568 'acym-data-plugin-class' => get_class($this),
1569 'acym-data-plugin-folder' => $this->name,
1570 ],
1571 ['class' => 'cell button']
1572 );
1573 }
1574
1575 /**
1576 * Called using ajax
1577 *
1578 * @param string $css This attribute is the name of the file in the folder css of the plugin OR it can be raw CSS
1579 */
1580 public function loadCSS(string $css, bool $raw = false, ?string $path = null): void
1581 {
1582 if (!$raw) {
1583 if (empty($path)) $path = ACYM_DYNAMICS_URL.$this->name;
1584 $css = $path.'/css/'.$css.'.css';
1585 }
1586 acym_addStyle($raw, $css);
1587 }
1588
1589 /**
1590 * @param string $js This attribute is the name of the file in the folder js of the plugin OR it can be raw Javascript
1591 */
1592 public function loadJavascript(string $js, bool $raw = false, ?string $path = null): void
1593 {
1594 if (!$raw) {
1595 if (empty($path)) $path = ACYM_DYNAMICS_URL.$this->name;
1596 $js = $path.'/js/'.$js.'.js';
1597 }
1598 acym_addScript($raw, $js);
1599 }
1600
1601 public function includeView(string $view, array $data = [], ?string $path = null): string
1602 {
1603 if (empty($path)) {
1604 $path = ACYM_ADDONS_FOLDER_PATH.$this->name.DS.'views'.DS.$view.'.php';
1605 } else {
1606 $path = $path.DS.'views'.DS.$view.'.php';
1607 }
1608
1609 if (!file_exists($path)) {
1610 throw new \Exception(esc_attr(acym_translation('ACYM_NON_EXISTING_PAGE')));
1611 }
1612
1613 ob_start();
1614 include $path;
1615
1616 return ob_get_clean();
1617 }
1618
1619 /**
1620 * This function will parse and show all settings
1621 */
1622 public function onAcymAddSettings(array &$plugins): void
1623 {
1624 if (method_exists($this, 'initSettings')) {
1625 $this->initSettings();
1626 }
1627
1628 foreach ($plugins as $key => $plugin) {
1629 if ($plugin->folder_name === $this->name) {
1630 if (!empty($plugin->settings)) {
1631 foreach ($plugin->settings as $keySettings => $value) {
1632 if (isset($this->settings[$keySettings]['value'])) {
1633 $this->settings[$keySettings]['value'] = $plugin->settings[$keySettings]['value'];
1634 }
1635 }
1636 }
1637 $plugins[$key]->settings = $this->settings;
1638
1639 $this->generateSettings($plugins[$key]);
1640 break;
1641 }
1642 }
1643 }
1644
1645 /**
1646 * @param mixed $default
1647 *
1648 * @return mixed
1649 */
1650 protected function getParam(string $name, $default = '')
1651 {
1652 return $this->savedSettings[$name]['value'] ?? $default;
1653 }
1654
1655 protected function getTranslationId(int $elementId, string $translationTool, bool $defaultLanguage = false)
1656 {
1657 return $elementId;
1658 }
1659
1660 public function filterSpecialMailsDailySend(array &$specialMails, int $time, string $mailType): void
1661 {
1662 // Only once a day
1663 $dailyHour = $this->config->get('daily_hour', '12');
1664 $dailyMinute = $this->config->get('daily_minute', '00');
1665 // The day it is currently based on the timezone specified in the CMS configuration
1666 $dayBasedOnCMSTimezone = acym_date('now', 'Y-m-d');
1667 // The UTC timestamp of the current day based on the CMS timezone, at the specified hour
1668 $dayBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$dailyHour.':'.$dailyMinute);
1669
1670 $campaignClass = new CampaignClass();
1671
1672 $tmpMails = [];
1673 foreach ($specialMails as $oneMail) {
1674 if ($oneMail->sending_type === $mailType) {
1675 $noNextTrigger = empty($oneMail->next_trigger);
1676 $nextTriggerIsNow = gmdate('m-d', $oneMail->next_trigger) == gmdate('m-d', $dayBasedOnCMSTimezoneAtSpecifiedHour);
1677 $nextTriggerIsInPast = $oneMail->next_trigger < $time;
1678 if ($time >= $dayBasedOnCMSTimezoneAtSpecifiedHour && ($noNextTrigger || $nextTriggerIsNow || $nextTriggerIsInPast)) {
1679 $oneMail->next_trigger = acym_getTime('tomorrow '.$dailyHour.':'.$dailyMinute);
1680 $oneMail->last_generated = $time;
1681 $campaignClass->save($oneMail);
1682 $tmpMails[] = $oneMail;
1683 }
1684 } else {
1685 $tmpMails[] = $oneMail;
1686 }
1687 }
1688 $specialMails = $tmpMails;
1689 }
1690
1691 protected function getIdsSelectAjax(): array
1692 {
1693 $ids = acym_getVar('string', 'id');
1694 if (empty($ids)) {
1695 return [];
1696 }
1697
1698 $ids = explode(',', $ids);
1699 acym_arrayToInteger($ids);
1700
1701 return $ids;
1702 }
1703
1704 protected function cleanExtensionContent(string $text)
1705 {
1706 if (ACYM_CMS === 'wordpress') {
1707 if (!acym_isExtensionActive('classic-editor/classic-editor.php') || strpos($text, '<!-- wp:') !== false) {
1708 return $text;
1709 }
1710
1711 return nl2br($text);
1712 } else {
1713 return preg_replace('#\{igallery[^}]+\}#Ui', '', $text);
1714 }
1715 }
1716
1717 protected function callApiSendingMethod(
1718 string $url,
1719 array $data = [],
1720 array $headers = [],
1721 string $type = 'GET',
1722 array $authentication = [],
1723 bool $dataDecoded = false,
1724 array $files = []
1725 ): array {
1726 $assocHeaders = [];
1727 foreach ($headers as $key => $value) {
1728 if (is_int($key)) {
1729 $parts = explode(':', $value, 2);
1730 if (count($parts) === 2) {
1731 $assocHeaders[trim($parts[0])] = trim($parts[1]);
1732 }
1733 } else {
1734 $assocHeaders[$key] = $value;
1735 }
1736 }
1737
1738 if (!empty($authentication)) {
1739 $assocHeaders['Authorization'] = 'Basic '.base64_encode($authentication['name'].':'.$authentication['pwd']);
1740 }
1741
1742 $result = acym_makeCurlCall(
1743 $url,
1744 [
1745 'method' => strtoupper($type),
1746 'headers' => $assocHeaders,
1747 'data' => $data,
1748 'files' => $files,
1749 // Some APIs (Mailgun) expect the data as a raw multipart body, not encoded
1750 'multipart' => $dataDecoded === true,
1751 'verifySsl' => false,
1752 ]
1753 );
1754
1755 $this->responseCode = (int)($result['status_code'] ?? 0);
1756 unset($result['status_code']);
1757
1758 if (isset($result['error'])) {
1759 return ['error_curl' => $result['error']];
1760 }
1761
1762 return $result;
1763 }
1764
1765 protected function getTestCredentialsSendingMethodButton(string $sendingMethodId): void
1766 {
1767 echo '<div class="cell grid-x margin-top-1 acym__sending__methods__credentials__test">
1768 <button type="button" sending-method-id="'.esc_attr($sendingMethodId).'" class="acym__configuration__sending__method-test cell shrink button button-secondary">
1769 '.esc_html(acym_translation('ACYM_TEST_CREDENTIALS')).'
1770 </button>
1771 <span class="acym__configuration__sending__method-icon cell shrink margin-left-1 acym_vcenter"></span>
1772 <span class="acym__configuration__sending__method-test__message cell shrink margin-left-1 acym_vcenter"></span>
1773 </div>';
1774 }
1775
1776 public function getCopySettingsButton(array $data, string $sendingMethodId, string $fromPlugin, bool $withContainer = true): void
1777 {
1778 if (empty($data[$fromPlugin.'_installed'])) {
1779 return;
1780 }
1781
1782 if ($withContainer) {
1783 echo '<div class="cell grid-x margin-top-1">';
1784 }
1785
1786 echo '<button
1787 type="button"
1788 class="cell shrink button button-secondary acym__configuration__copy__mail__settings"
1789 acym-data-plugin="'.esc_attr($fromPlugin).'"
1790 acym-data-method="'.esc_attr($sendingMethodId).'">
1791 '.esc_html(acym_translationSprintf('ACYM_COPY_SETTINGS_FROM', $this->sendingPlugins[$fromPlugin])).'
1792 </button>
1793 <span class="acym__configuration__sending__method-icon cell shrink margin-left-1 acym_vcenter"></span>';
1794
1795 if ($withContainer) {
1796 echo '</div>';
1797 }
1798 }
1799
1800 protected function getLinks(string $account = '', string $pricing = ''): void
1801 {
1802 if (empty($account) && empty($pricing)) {
1803 return;
1804 }
1805
1806 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>';
1807 if (!empty($account)) {
1808 echo '<a target="_blank" class="cell shrink" href="'.esc_url($account).'">'.esc_html(acym_translation('ACYM_CREATE_ONE')).'</a>';
1809 }
1810 if (!empty($account) && !empty($pricing)) {
1811 echo '<p class="cell shrink">'.esc_html(strtolower(acym_translation('ACYM_OR'))).'</p>';
1812 }
1813 if (!empty($pricing)) {
1814 echo '<a target="_blank" class="cell shrink" href="'.esc_url($pricing).'">'.esc_html(acym_translation('ACYM_CHECK_THEIR_PRICING')).'</a>';
1815 }
1816 echo '</div>';
1817 }
1818
1819 public function onAcymGetSendingMethodsSelected(array &$data): void
1820 {
1821 if (ACYM_CMS === 'wordpress') {
1822 $this->config->load();
1823 }
1824
1825 $mailerMethod = $this->config->get('mailer_method', 'phpmail');
1826 foreach ($data['sendingMethods'] as $key => $sendingMethod) {
1827 $data['sendingMethods'][$key]['selected'] = $key == $mailerMethod;
1828 }
1829 }
1830
1831 public function errorCallback(): void
1832 {
1833 foreach ($this->errors as $error) {
1834 acym_logError($error, empty($this->name) ? get_class($this) : $this->name);
1835 }
1836
1837 $this->errors = [];
1838 }
1839
1840 public function isLogFileEmpty(): bool
1841 {
1842 return !acym_isLogFileErrorExist(empty($this->name) ? get_class($this) : $this->name);
1843 }
1844
1845 public function initCustomView(bool $customFields = false): void
1846 {
1847 $page = acym_getVar('cmd', 'page');
1848 if (is_array($page)) return;
1849 if (!empty($page) && is_string($page)) {
1850 $page = str_replace(ACYM_COMPONENT.'_', '', $page);
1851 }
1852 $ctrl = acym_getVar('cmd', 'ctrl', $page);
1853 if (!in_array($ctrl, ['plugins', 'dynamics'])) return;
1854
1855 $task = acym_getVar('cmd', 'task', 'installed');
1856
1857 // Installed add-ons page or installed + editor
1858 if (($ctrl === 'plugins' && $task === 'installed') || ($this->active && $ctrl === 'dynamics' && $task === 'trigger')) {
1859 $this->initElementOptionsCustomView();
1860 $this->initReplaceOptionsCustomView();
1861 if ($customFields) $this->initCustomOptionsCustomView();
1862 }
1863 }
1864
1865 public function processDateToCheck(array $options): \DateTime
1866 {
1867 $dateNowWithTimeZone = acym_date('now', 'Y-m-d h:i:s');
1868 $dateToCheck = new \DateTime($dateNowWithTimeZone);
1869 $interval = new \DateInterval('P'.intval($options['days']).'D');
1870 if ($options['relative'] === 'before') {
1871 $dateToCheck->add($interval);
1872 } else {
1873 $dateToCheck->sub($interval);
1874 }
1875
1876 return $dateToCheck;
1877 }
1878
1879 public function onAcymCheckInstalled(bool &$installed): void
1880 {
1881 $installed = $this->installed;
1882 }
1883
1884 protected function getLinkTranslated(string $link): string
1885 {
1886 if (empty($this->emailLanguage)) return $link;
1887 if (!acym_isMultilingual()) return $link;
1888
1889 $translationTool = $this->config->get('translate_content', 'no');
1890 if ($translationTool !== 'wpml') return $link;
1891
1892 if (!acym_isExtensionActive('sitepress-multilingual-cms/sitepress.php')) return $link;
1893
1894 $languageCode = substr($this->emailLanguage, 0, 2);
1895
1896 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML hook for integration.
1897 return apply_filters('wpml_permalink', $link, $languageCode);
1898 }
1899
1900 protected function replaceShortcode(string $content): string
1901 {
1902 $content = do_shortcode($content);
1903
1904 return preg_replace('#<!-- wp:shortcode -->(.*)<!-- /wp:shortcode -->#Uis', '$1', $content);
1905 }
1906
1907 protected function fixDivStructure(string $text): string
1908 {
1909 $nbOpeningDivs = substr_count($text, '<div');
1910 $nbClosingDivs = substr_count($text, '</div>');
1911
1912 if ($nbOpeningDivs > $nbClosingDivs) {
1913 $text .= str_repeat('</div>', $nbOpeningDivs - $nbClosingDivs);
1914 } elseif ($nbOpeningDivs < $nbClosingDivs) {
1915 $text = str_repeat('<div>', $nbClosingDivs - $nbOpeningDivs).$text;
1916 }
1917
1918 return $text;
1919 }
1920
1921 protected function getBounceAddress(object $mailerHelper): string
1922 {
1923 if (method_exists($mailerHelper, 'getSendSettings')) {
1924 return $mailerHelper->getSendSettings('bounce_email');
1925 } else {
1926 return $this->config->get('bounce_email');
1927 }
1928 }
1929 }
1930