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
← All changes | back/Core/AcymPlugin.php +658 -303 10.2.2 → 11.1.0 View file →
@@ -8,8 +8,13 @@
8 8 use Joomla\CMS\HTML\HTMLHelper;
9 9
10 10 class AcymPlugin extends AcymObject
11 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 +
12 17 public string $cms = 'all';
13 18 public string $name = '';
14 19 public bool $installed = true;
15 20
@@ -53,12 +58,18 @@
53 58 public array $errors = [];
54 59 public string $logFilename = '';
55 60 protected int $responseCode;
56 61
57 - private $active;
58 - public $settings;
62 + private bool $active;
63 + public array $settings = [];
59 64 private array $savedSettings = [];
60 65
66 + public array $automationTriggers = [];
67 + public array $automationActions = [];
68 + public array $automationConditions = [];
69 + public array $automationFilters = [];
70 + public array $mailboxActions = [];
71 +
61 72 public function __construct()
62 73 {
63 74 parent::__construct();
64 75
@@ -63,8 +74,9 @@
63 74 parent::__construct();
64 75
65 76 $this->elementOptions = ['wrappedText' => [acym_translation('ACYM_WRAPPED_TEXT')]];
66 77
78 + //TODO only load this helper once even if 50 plugins are loaded
67 79 $this->pluginHelper = new PluginHelper();
68 80 $this->pageInfo = new \stdClass();
69 81
70 82 $this->pluginDescription = new \stdClass();
@@ -93,21 +105,24 @@
93 105
94 106 $this->logFilename = acym_getErrorLogFilename(empty($this->name) ? get_class($this) : $this->name);
95 107 }
96 108
97 - protected function displaySelectionZone($zoneContent)
109 + protected function displaySelectionZone(string $zoneContent): void
98 110 {
99 - $output = '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">';
100 - $output .= acym_translation('ACYM_CONTENT_TO_INSERT').'<i class="acymicon-keyboard-arrow-up"></i>';
101 - $output .= '</p>';
102 - $output .= '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container">';
103 - $output .= $zoneContent;
104 - $output .= '</div>';
105 -
106 - return $output;
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
107 119 }
108 120
109 - public function displayListing()
121 + /**
122 + * Called using ajax
123 + */
124 + public function displayListing(): void
110 125 {
111 126 echo $this->prepareListing();
112 127 }
113 128
@@ -128,9 +143,9 @@
128 143
129 144 return '';
130 145 }
131 146
132 - public function getElements()
147 + public function getElements(): array
133 148 {
134 149 $conditions = '';
135 150 if (!empty($this->filters)) {
136 151 $conditions = ' WHERE ('.implode(') AND (', $this->filters).')';
@@ -163,13 +178,13 @@
163 178
164 179 return $rows;
165 180 }
166 181
167 - protected function getFilteringZone($categoryFilter = true): string
182 + protected function getFilteringZone(bool $categoryFilter = true): string
168 183 {
169 184 $result = '<div class="grid-x" id="plugin_listing_filters">
170 185 <div class="cell medium-6">
171 - <input type="text" name="plugin_search" placeholder="'.acym_escape(acym_translation('ACYM_SEARCH')).'"/>
186 + <input type="text" name="plugin_search" placeholder="'.esc_attr(acym_translation('ACYM_SEARCH')).'"/>
172 187 </div>
173 188 <div class="cell medium-6 grid-x">
174 189 <div class="cell hide-for-small-only medium-auto"></div>
175 190 <div class="cell medium-shrink">';
@@ -184,9 +199,9 @@
184 199
185 200 return $result;
186 201 }
187 202
188 - public function prepareWPCategories($type)
203 + public function prepareWPCategories(string $type): void
189 204 {
190 205 $this->categories = acym_loadObjectList(
191 206 'SELECT cat.term_taxonomy_id AS id, parent.term_taxonomy_id AS parent_id, catdetails.name AS title
192 207 FROM `#__term_taxonomy` AS cat
@@ -226,12 +241,14 @@
226 241 ['class' => 'plugin_category_select']
227 242 );
228 243 }
229 244
230 - private function getSubCats($categoryId)
245 + private function getSubCats(int $categoryId): array
231 246 {
232 247 $result = [$categoryId];
233 - if (empty($this->cats[$categoryId])) return $result;
248 + if (empty($this->cats[$categoryId])) {
249 + return $result;
250 + }
234 251
235 252 foreach ($this->cats[$categoryId] as $oneSubCategory) {
236 253 $result = array_merge($result, $this->getSubCats($oneSubCategory->id));
237 254 }
@@ -238,9 +255,9 @@
238 255
239 256 return $result;
240 257 }
241 258
242 - protected function handleChildrenCategories($parent_id, $level = 0)
259 + protected function handleChildrenCategories(int $parent_id, int $level = 0): void
243 260 {
244 261 if (empty($this->cats[$parent_id])) return;
245 262
246 263 foreach ($this->cats[$parent_id] as $cat) {
@@ -248,9 +265,9 @@
248 265 $this->handleChildrenCategories($cat->id, $level + 1);
249 266 }
250 267 }
251 268
252 - protected function getSubCategories($categoryId)
269 + protected function getSubCategories(int $categoryId): array
253 270 {
254 271 $this->getCategoryFilter();
255 272
256 273 return $this->subCategories[$categoryId];
@@ -255,9 +272,9 @@
255 272
256 273 return $this->subCategories[$categoryId];
257 274 }
258 275
259 - protected function autoContentOptions(&$options, $type = null)
276 + protected function autoContentOptions(array &$options, ?string $type = null): void
260 277 {
261 278 if ($type === 'event') {
262 279 $options[] = [
263 280 'title' => 'ACYM_FROM',
@@ -313,9 +330,9 @@
313 330 'default' => 20,
314 331 ];
315 332 }
316 333
317 - protected function autoCampaignOptions(&$options, $modified = false)
334 + protected function autoCampaignOptions(array &$options, bool $modified = false): void
318 335 {
319 336 if (empty($this->campaignId) && empty($this->campaignType)) {
320 337 return;
321 338 } elseif (!empty($this->campaignId) && (empty($this->campaignType) || $this->campaignType !== 'auto')) {
@@ -329,9 +346,13 @@
329 346 $options[] = [
330 347 'title' => 'ACYM_DOCUMENTATION',
331 348 'type' => 'custom',
332 349 'name' => 'documentation',
333 - 'output' => '<a target="_blank" href="'.ACYM_DOCUMENTATION.'main-pages/campaigns/automatic-campaigns#dont-send-twice-the-same-content"><i class="acymicon-book"></i></a>',
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 + },
334 355 'js' => '',
335 356 'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
336 357 ];
337 358
@@ -369,16 +390,20 @@
369 390 'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
370 391 ];
371 392 }
372 393
373 - protected function getElementsListing($options): string
394 + protected function getElementsListing(array $options): string
374 395 {
375 396 if ($this->pageInfo->loadMore) {
376 - return $this->getInnerListing($options);
397 + ob_start();
398 + $this->displayInnerListing($options);
399 +
400 + return ob_get_clean();
377 401 }
378 402 $listing = '<div id="plugin_listing" class="acym__popup__listing">';
379 - $listing .= '<input type="hidden" name="plugin" value="'.acym_escape(get_class($this)).'" />';
403 + $listing .= '<input type="hidden" name="plugin" value="'.esc_attr(get_class($this)).'" />';
380 404
405 + // Column names
381 406 $listing .= '<div class="cell grid-x hide-for-small-only plugin_listing_headers">';
382 407 foreach ($options['header'] as $oneColumn) {
383 408 $class = empty($oneColumn['class']) ? '' : ' '.$oneColumn['class'];
384 409 $listing .= '<div class="cell plugin_listing_headers__title medium-'.$oneColumn['size'].$class.'">'.acym_translation($oneColumn['label']).'</div>';
@@ -384,9 +409,11 @@
384 409 $listing .= '<div class="cell plugin_listing_headers__title medium-'.$oneColumn['size'].$class.'">'.acym_translation($oneColumn['label']).'</div>';
385 410 }
386 411 $listing .= '</div>';
387 412
388 - $listing .= $this->getInnerListing($options);
413 + ob_start();
414 + $this->displayInnerListing($options);
415 + $listing .= ob_get_clean();
389 416
390 417 $listing .= '<input type="hidden" value="1" id="acym_pagination__ajax__load-more" name="acym_pagination__ajax__load-more">';
391 418 $listing .= '</div>';
392 419
@@ -392,65 +419,72 @@
392 419
393 420 return $listing;
394 421 }
395 422
396 - private function getInnerListing($options): string
423 + private function displayInnerListing(array $options): void
397 424 {
398 - if (empty($options['rows']) && $this->pageInfo->loadMore) {
399 - $listing = '<h3 class="cell acym__listing__empty__load-more text-center">'.acym_translation('ACYM_NO_MORE_RESULTS').'</h3>';
400 - } elseif (empty($options['rows'])) {
401 - $listing = '<h1 class="cell acym__listing__empty__search__modal text-center">'.acym_translation('ACYM_NO_RESULTS_FOUND').'</h1>';
402 - } else {
403 - $selected = explode(',', acym_getVar('string', 'selected', ''));
404 - if (!empty($this->defaultValues->id)) {
405 - $selected = [$this->defaultValues->id];
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>';
406 430 }
407 431
408 - $listing = '';
409 - foreach ($options['rows'] as $row) {
410 - $class = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
411 - if (in_array($row->{$options['id']}, $selected)) {
412 - $class .= ' selected_row';
413 - }
432 + return;
433 + }
414 434
415 - $listing .= '<div
416 - class="'.$class.'"
417 - data-id="'.acym_escape($row->{$options['id']}).'"
418 - onclick="applyContent'.acym_escape($this->name).'(\''.acym_escape($row->{$options['id']}).'\', this);">';
435 + $selected = explode(',', acym_getVar('string', 'selected', ''));
436 + if (!empty($this->defaultValues->id)) {
437 + $selected = [$this->defaultValues->id];
438 + }
419 439
420 - foreach ($options['header'] as $column => $oneColumn) {
421 - $value = $row->$column;
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 + }
422 445
423 - if (!empty($oneColumn['type'])) {
424 - if ($oneColumn['type'] === 'date') {
425 - if (empty($value)) {
426 - $value = '-';
427 - } else {
428 - if (!is_numeric($value) && $value != '0000-00-00 00:00:00') {
429 - $value = strtotime($value);
430 - }
431 - $tooltip = acym_date($value, acym_translation('ACYM_DATE_FORMAT_LC2'));
432 - $value = acym_tooltip(
433 - [
434 - 'hoveredText' => acym_date($value, acym_translation('ACYM_DATE_FORMAT_LC5')),
435 - 'textShownInTooltip' => $tooltip,
436 - ]
437 - );
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);
438 464 }
439 - } elseif ($oneColumn['type'] === 'int') {
440 - $value = intval($value);
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 + );
441 472 }
473 + } elseif ($oneColumn['type'] === 'int') {
474 + echo intval($value);
475 + } else {
476 + echo esc_html($value);
442 477 }
443 -
444 - $class = empty($oneColumn['class']) ? '' : ' '.$oneColumn['class'];
445 - $listing .= '<div class="cell medium-'.$oneColumn['size'].$class.'">'.$value.'</div>';
478 + } else {
479 + echo esc_html($value);
446 480 }
447 481
448 - $listing .= '</div>';
482 + echo '</div>';
449 483 }
484 +
485 + echo '</div>';
450 486 }
451 -
452 - return $listing;
453 487 }
454 488
455 489 protected function getCategoryListing(): string
456 490 {
@@ -472,10 +506,10 @@
472 506 $classes = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
473 507 if (in_array($oneCat->value, $selected)) {
474 508 $classes .= ' selected_row';
475 509 }
476 - $listing .= '<div class="'.$classes.'" data-id="'.intval($oneCat->value).'" onclick="applyContentauto'.acym_escape($this->name).'('.intval($oneCat->value).', this);">
477 - <div class="cell medium-5">'.acym_escape($oneCat->text).'</div>
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>
478 512 </div>';
479 513 }
480 514 $listing .= '</div>';
481 515
@@ -503,11 +537,15 @@
503 537 foreach ($this->tagvalues as $oneTag) {
504 538 if (empty($oneTag->$termIdName)) continue;
505 539
506 540 $class = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
507 - if (in_array($oneTag->$termIdName, $selected)) $class .= ' selected_row';
508 - $listing .= '<div class="'.$class.'" data-id="'.intval($oneTag->$termIdName).'" onclick="applyContent'.acym_escape($this->name).'_tags('.intval($oneTag->$termIdName).', this);">
509 - <div class="cell medium-5">'.acym_escape($oneTag->name).'</div>
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>
510 548 </div>';
511 549 }
512 550 $listing .= '</div>';
513 551
@@ -513,9 +551,9 @@
513 551
514 552 return $listing;
515 553 }
516 554
517 - protected function replaceMultiple(&$email)
555 + protected function replaceMultiple(object &$email)
518 556 {
519 557 $this->generateByCategory($email);
520 558 if (empty($this->tags)) return;
521 559
@@ -521,9 +559,9 @@
521 559
522 560 $this->pluginHelper->replaceTags($email, $this->tags, true);
523 561 }
524 562
525 - protected function handleOrderBy(&$query, $parameter, $table = null)
563 + protected function handleOrderBy(string &$query, object $parameter, ?string $table = null)
526 564 {
527 565 if (empty($parameter->order)) return;
528 566
529 567 $ordering = explode(',', $parameter->order);
@@ -542,15 +580,17 @@
542 580 $query .= ' ORDER BY '.$table.'`'.acym_secureDBColumn(trim($column)).'` '.acym_secureDBColumn(trim($ordering[1]));
543 581 }
544 582 }
545 583
546 - protected function handleMax(&$query, $parameter)
584 + protected function handleMax(string &$query, object $parameter): void
547 585 {
548 - if (empty($parameter->max)) $parameter->max = 20;
586 + if (empty($parameter->max)) {
587 + $parameter->max = 20;
588 + }
549 589 $query .= ' LIMIT '.intval($parameter->max);
550 590 }
551 591
552 - protected function handleMin($elements, $parameter)
592 + protected function handleMin(array $elements, object $parameter): void
553 593 {
554 594 if (!empty($parameter->min) && count($elements) < $parameter->min) {
555 595 $this->generateCampaignResult->status = false;
556 596 $this->generateCampaignResult->message = acym_translationSprintf(
@@ -561,9 +601,9 @@
561 601 );
562 602 }
563 603 }
564 604
565 - protected function getLastGenerated($mailId)
605 + protected function getLastGenerated(int $mailId): int
566 606 {
567 607 $campaignClass = new CampaignClass();
568 608
569 609 return $campaignClass->getLastGenerated($mailId);
@@ -568,9 +608,12 @@
568 608
569 609 return $campaignClass->getLastGenerated($mailId);
570 610 }
571 611
572 - protected function finalizeCategoryFormat($query, $parameter, $table = null)
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
573 616 {
574 617 $this->handleOrderBy($query, $parameter, $table);
575 618 $this->handleMax($query, $parameter);
576 619
@@ -578,9 +621,9 @@
578 621
579 622 return $this->formatIndividualTags($elements, $parameter);
580 623 }
581 624
582 - protected function formatIndividualTags($elements, $parameter)
625 + protected function formatIndividualTags(array $elements, object $parameter): string
583 626 {
584 627 $this->handleMin($elements, $parameter);
585 628
586 629 if (empty($elements)) return '';
@@ -599,9 +642,9 @@
599 642
600 643 return $this->pluginHelper->getFormattedResult($arrayElements, $parameter);
601 644 }
602 645
603 - protected function buildIndividualTags($elements, $parameter): array
646 + protected function buildIndividualTags(array $elements, object $parameter): array
604 647 {
605 648 $arrayElements = [];
606 649 unset($parameter->id);
607 650
@@ -629,14 +672,17 @@
629 672
630 673 return $arrayElements;
631 674 }
632 675
633 - protected function groupByCategory($elements)
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)
634 680 {
635 681 return $elements;
636 682 }
637 683
638 - protected function getSelectedArea($parameter)
684 + protected function getSelectedArea(object $parameter): array
639 685 {
640 686 $allcats = explode('-', $parameter->id);
641 687 $selectedArea = [];
642 688 foreach ($allcats as $oneCat) {
@@ -646,9 +692,9 @@
646 692
647 693 return $selectedArea;
648 694 }
649 695
650 - protected function replaceOne(&$email)
696 + protected function replaceOne(object &$email): void
651 697 {
652 698 $tags = $this->pluginHelper->extractTags($email, $this->name);
653 699 if (empty($tags)) return;
654 700
@@ -654,11 +700,14 @@
654 700
655 701 $newConfiguration = [
656 702 'dcontent_default_'.get_class($this) => json_encode(end($tags)),
657 703 ];
658 - $this->config->save($newConfiguration);
704 + $this->config->saveConfig($newConfiguration);
659 705
660 - if (false === $this->loadLibraries($email)) return;
706 + if (method_exists($this, 'loadLibraries') && !$this->loadLibraries($email)) {
707 + return;
708 + }
709 +
661 710 $this->emailLanguage = $email->links_language;
662 711 $translationTool = $this->config->get('translate_content', 'no');
663 712
664 713 $tagsReplaced = [];
@@ -667,9 +716,9 @@
667 716
668 717 if (!empty($this->emailLanguage) && $translationTool !== 'no' && acym_isMultilingual()) {
669 718 $oneTag->id = $this->getTranslationId($oneTag->id, $translationTool);
670 719 }
671 - $tagsReplaced[$i] = $this->replaceIndividualContent($oneTag, $email);
720 + $tagsReplaced[$i] = $this->replaceIndividualContent($oneTag);
672 721 }
673 722
674 723 $email->custom_view = file_exists(ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.html');
675 724
@@ -675,15 +724,10 @@
675 724
676 725 $this->pluginHelper->replaceTags($email, $tagsReplaced, true);
677 726 }
678 727
679 - protected function loadLibraries($email)
728 + protected function initIndividualContent(object &$tag, string $query)
680 729 {
681 - return true;
682 - }
683 -
684 - protected function initIndividualContent(&$tag, $query)
685 - {
686 730 $element = acym_loadObject($query);
687 731
688 732 if (empty($element)) {
689 733 if (acym_isAdmin()) {
@@ -689,9 +733,9 @@
689 733 if (acym_isAdmin()) {
690 734 acym_enqueueMessage(acym_translationSprintf('ACYM_CONTENT_NOT_FOUND', $tag->id), 'notice');
691 735 }
692 736
693 - return false;
737 + return null;
694 738 }
695 739
696 740 if (empty($tag->display)) {
697 741 $tag->display = [];
@@ -701,9 +745,9 @@
701 745
702 746 return $element;
703 747 }
704 748
705 - protected function getCustomLayoutVars($element)
749 + protected function getCustomLayoutVars(object $element): array
706 750 {
707 751 $varFields = [];
708 752 $varFields['{picthtml}'] = '';
709 753 foreach ($element as $fieldName => $oneField) {
@@ -716,10 +760,18 @@
716 760
717 761 return $varFields;
718 762 }
719 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 + */
720 771 protected function finalizeElementFormat(string $htmlResult, object $insertionOptions, array $dataShortcodes): string
721 772 {
773 + // Hidden feature, users can do a PHP custom view
722 774 $fullCustomLayoutPath = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.php';
723 775 if ($this->config->get('php_overrides', 0) == 1 && file_exists($fullCustomLayoutPath)) {
724 776 ob_start();
725 777 require $fullCustomLayoutPath;
@@ -725,8 +777,9 @@
725 777 require $fullCustomLayoutPath;
726 778 $viewContent = ob_get_clean();
727 779 $htmlResult = str_replace(array_keys($dataShortcodes), $dataShortcodes, $viewContent);
728 780 } else {
781 + // Default custom view feature with only HTML for security reasons
729 782 $customLayoutPath = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.html';
730 783 if (file_exists($customLayoutPath)) {
731 784 $dataShortcodes['{wrappedText}'] = $this->pluginHelper->wrappedText;
732 785 $viewContent = acym_fileGetContent($customLayoutPath);
@@ -736,25 +789,252 @@
736 789 }
737 790 }
738 791 }
739 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
740 805 return $this->pluginHelper->managePicts($insertionOptions, $htmlResult);
741 806 }
742 807
743 - protected function filtersFromConditions(&$filters)
808 + /**
809 + * Keep this method untyped until v12
810 + */
811 + public function onAcymDeclareTriggers(&$triggers, &$defaultValues)
744 812 {
745 - $newFilters = [];
813 + if (empty($this->automationTriggers) && method_exists($this, 'initAutomationTriggers')) {
814 + $this->initAutomationTriggers();
815 + }
746 816
747 - $this->onAcymDeclareConditions($newFilters);
748 - foreach ($newFilters as $oneType) {
749 - foreach ($oneType as $oneFilterName => $oneFilter) {
750 - if (!empty($oneFilter->option)) $oneFilter->option = str_replace(['acym_condition', '[conditions]'], ['acym_action', '[filters]'], $oneFilter->option);
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 +
751 1031 $filters[$oneFilterName] = $oneFilter;
752 1032 }
753 1033 }
754 1034 }
755 1035
756 - protected function getElementTags($type, $id)
1036 + protected function getElementTags(string $type, int $id): array
757 1037 {
758 1038 $tags = acym_loadObjectList(
759 1039 'SELECT tags.`title`, tags.`alias`, tags.`id`
760 1040 FROM #__tags AS tags
@@ -772,9 +1052,12 @@
772 1052
773 1053 return $displayTags;
774 1054 }
775 1055
776 - protected function getFormattedValue($fieldValues)
1056 + /**
1057 + * @return mixed
1058 + */
1059 + protected function getFormattedValue(array $fieldValues)
777 1060 {
778 1061 $field = $fieldValues[0];
779 1062
780 1063 if (!empty($field->fieldparams) && !is_array($field->fieldparams)) {
@@ -832,9 +1115,9 @@
832 1115 if (!empty($field->value)) {
833 1116 if (substr($field->value, 0, 1) === '{') {
834 1117 $value = json_decode($field->value, true);
835 1118 if (!empty($value['imagefile'])) {
836 - $alt = empty($value['alt_text']) ? '' : ' alt="'.acym_escape($value['alt_text']).'"';
1119 + $alt = empty($value['alt_text']) ? '' : ' alt="'.esc_attr($value['alt_text']).'"';
837 1120 $value = '<img src="'.$value['imagefile'].'"'.$alt.' />';
838 1121 }
839 1122 } else {
840 1123 $value = '<img src="'.$field->value.'" alt="" />';
@@ -845,9 +1128,9 @@
845 1128 break;
846 1129
847 1130 case 'repeatable':
848 1131 if (!empty($field->value)) {
849 - $values = json_decode($field->value);
1132 + $values = is_string($field->value) ? json_decode($field->value) : $field->value;
850 1133 $formattedValues = [];
851 1134 foreach ($values as $oneSetOfValues) {
852 1135 $formattedSet = [];
853 1136 foreach ($oneSetOfValues as $oneLabel => $oneValue) {
@@ -901,9 +1184,9 @@
901 1184 $field->value = $field->usergroups[$field->value]->title;
902 1185 break;
903 1186 case 'subform':
904 1187 if (!empty($field->value)) {
905 - $rows = json_decode($field->value, true);
1188 + $rows = is_string($field->value) ? json_decode($field->value, true) : $field->value;
906 1189
907 1190 $formattedValues = [];
908 1191 foreach ($rows as $values) {
909 1192 $fieldValues = [];
@@ -975,8 +1258,9 @@
975 1258 return '&language='.$value;
976 1259 }
977 1260 }
978 1261
1262 + // Don't add types for $tag and $sef, for retro-compatibility
979 1263 protected function finalizeLink(string $link, $tag = null, $sef = true): string
980 1264 {
981 1265 if (acym_isPluginActive('languagefilter')) {
982 1266 if (strpos($link, 'lang=') === false && !empty($this->emailLanguage)) {
@@ -990,11 +1274,12 @@
990 1274 }
991 1275 }
992 1276
993 1277 if (!empty($tag->autologin)) {
994 - $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:key|urlencode}';
1278 + $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:autologin_token|urlencode}';
995 1279 }
996 1280
1281 + // Retro-compatibility for add-on versions before the v8.6.0
997 1282 if (is_bool($tag)) {
998 1283 $sef = $tag;
999 1284 }
1000 1285
@@ -1000,9 +1285,9 @@
1000 1285
1001 1286 return acym_frontendLink($link, false, $sef);
1002 1287 }
1003 1288
1004 - protected function handleCustomFields($tag, &$customFields)
1289 + protected function handleCustomFields(object $tag, array &$customFields): void
1005 1290 {
1006 1291 if (empty($tag->custom)) {
1007 1292 return;
1008 1293 }
@@ -1037,9 +1322,9 @@
1037 1322 ];
1038 1323 }
1039 1324 }
1040 1325
1041 - protected function getIntro($text)
1326 + protected function getIntro(string $text): string
1042 1327 {
1043 1328 $pageBreak = null;
1044 1329 $possibleBreaks = [
1045 1330 '<!--more-->',
@@ -1053,9 +1338,11 @@
1053 1338 break;
1054 1339 }
1055 1340 }
1056 1341
1057 - if (empty($pageBreak)) return $text;
1342 + if (empty($pageBreak)) {
1343 + return $text;
1344 + }
1058 1345
1059 1346 $split = explode($pageBreak, $text, 2);
1060 1347
1061 1348 return array_shift($split);
@@ -1060,30 +1347,27 @@
1060 1347
1061 1348 return array_shift($split);
1062 1349 }
1063 1350
1064 - public function displayCustomViewEditor(&$output)
1351 + public function displayCustomViewEditor(): void
1065 1352 {
1066 - $plugin = new \stdClass();
1067 - $plugin->folder_name = $this->name;
1068 - $plugin->settings = $this->settings;
1069 - $this->generateSettings($plugin);
1070 -
1071 - if (empty($plugin->settings['custom_view'])) {
1353 + if (empty($this->settings['custom_view']) || !acym_isAdmin()) {
1072 1354 return;
1073 1355 }
1074 1356
1075 - $output .= '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">';
1076 - $output .= acym_translation('ACYM_ADDON_SETTINGS');
1077 - $output .= '<i class="acymicon-keyboard-arrow-up"></i></p>';
1078 - $output .= '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container">';
1079 - $output .= $plugin->settings['custom_view'];
1080 - $output .= '</div>';
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>';
1081 1363 }
1082 1364
1083 - public function generateSettings(&$plugin)
1365 + public function generateSettings(object $plugin): bool
1084 1366 {
1085 - if (empty($plugin->settings)) return false;
1367 + if (empty($plugin->settings)) {
1368 + return false;
1369 + }
1086 1370
1087 1371 if (array_key_exists('not_installed', $plugin->settings)) {
1088 1372 $plugin->settings = 'not_installed';
1089 1373
@@ -1103,30 +1387,37 @@
1103 1387 $field['label'] = acym_translation($field['label']);
1104 1388 }
1105 1389
1106 1390 if (!empty($field['info'])) {
1107 - $field['label'] .= acym_info(
1108 - acym_translation($field['info']),
1109 - '',
1110 - '',
1111 - 'wysid_tooltip',
1112 - !empty($field['info_warning'])
1391 + ob_start();
1392 + acym_info(
1393 + [
1394 + 'textShownInTooltip' => acym_translation($field['info']),
1395 + 'classText' => 'wysid_tooltip',
1396 + 'isWarning' => !empty($field['info_warning']),
1397 + ]
1113 1398 );
1399 + $field['label'] .= ob_get_clean();
1114 1400 }
1115 1401
1116 - if ($field['type'] == 'checkbox') {
1402 + if ($field['type'] === 'checkbox') {
1117 1403 $classLabel = 'shrink';
1118 - $text .= '<label for="'.$id.'" class="cell '.$classLabel.'">'.$field['label'].'</label>';
1119 - $text .= '<input id="'.$id.'" class="cell shrink" type="checkbox" name="'.$name.'" '.(empty($field['value']) ? '' : 'checked').'>';
1120 - } elseif ($field['type'] == 'switch') {
1121 - $text .= acym_switch(
1122 - $name,
1123 - $field['value'],
1124 - $field['label'],
1125 - [],
1126 - 'large-7'
1127 - );
1128 - } elseif ($field['type'] == 'select') {
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') {
1129 1420 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1130 1421 $text .= acym_select(
1131 1422 $field['data'],
1132 1423 $name,
@@ -1136,9 +1427,9 @@
1136 1427 'text',
1137 1428 false,
1138 1429 true
1139 1430 );
1140 - } elseif ($field['type'] == 'multiple_select') {
1431 + } elseif ($field['type'] === 'multiple_select') {
1141 1432 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1142 1433 $text .= acym_selectMultiple(
1143 1434 $field['data'],
1144 1435 $name,
@@ -1144,23 +1435,25 @@
1144 1435 $name,
1145 1436 empty($field['value']) ? [] : $field['value'],
1146 1437 ['class' => 'acym__select']
1147 1438 );
1148 - } elseif ($field['type'] == 'text') {
1439 + } elseif ($field['type'] === 'text') {
1149 1440 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1150 - $text .= '<input type="text" name="'.$name.'" value="'.acym_escape($field['value']).'" class="cell shrink">';
1151 - } elseif ($field['type'] == 'number') {
1441 + $text .= '<input type="text" name="'.$name.'" value="'.esc_attr($field['value']).'" class="cell shrink">';
1442 + } elseif ($field['type'] === 'number') {
1152 1443 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1153 - $text .= '<input type="number" name="'.$name.'" value="'.acym_escape($field['value']).'" class="cell large-2 medium-5">';
1444 + $text .= '<input type="number" name="'.$name.'" value="'.esc_attr($field['value']).'" class="cell large-2 medium-5">';
1154 1445 if (!empty($field['post_text'])) $text .= '<span class="cell shrink">'.strtolower($field['post_text']).'</span>';
1155 - } elseif ($field['type'] == 'radio') {
1446 + } elseif ($field['type'] === 'radio') {
1156 1447 $text .= '<p class="cell">'.$field['label'].'</p>';
1157 - $text .= acym_radio(
1448 + ob_start();
1449 + acym_radio(
1158 1450 $field['data'],
1159 1451 $name,
1160 1452 $field['value']
1161 1453 );
1162 - } elseif ($field['type'] == 'date') {
1454 + $text .= ob_get_clean();
1455 + } elseif ($field['type'] === 'date') {
1163 1456 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1164 1457 $text .= acym_dateField(
1165 1458 $name,
1166 1459 $field['value'],
@@ -1165,51 +1458,13 @@
1165 1458 $name,
1166 1459 $field['value'],
1167 1460 'cell shrink'
1168 1461 );
1169 - } elseif ($field['type'] === 'custom_view' && acym_isAdmin()) {
1170 - $idCustomView = 'acym__plugins__installed__custom-view__'.$this->name;
1171 - $ctrl = acym_getVar('string', 'ctrl', '');
1172 - $classTooltip = $ctrl == 'dynamics' ? '' : 'wysid_tooltip';
1173 - $text .= '<label class="cell">'.acym_translation('ACYM_CUSTOM_VIEW').acym_info('ACYM_CUSTOM_VIEW_DESC', '', '', $classTooltip).'</label>';
1174 - if (empty($field['tags'])) $field['tags'] = [];
1175 - $modalContent = '<div id="'.$idCustomView.'" class="cell grid-x acym__plugins__installed__custom-view" acym-data-tags="'.acym_escape(json_encode($field['tags'])).'">
1176 - <h2 class="cell text-center acym__title__primary__color">'.acym_translationSprintf('ACYM_CUSTOM_VIEW_FOR_X', $this->pluginDescription->name).'</h2>
1177 - <div class="cell grid-x acym__plugins__installed__custom-view__edit-container">
1178 - <div class="acym__plugins__installed__custom-view__editor-loader grid-x cell align-center acym_vcenter" v-if="loading">'.acym_loaderLogo().'</div>
1179 - <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>
1180 - <div class="cell grid-x medium-3 margin-left-1 acym__plugins__installed__custom-view__tags">
1181 - <h3 class="acym__title acym__title__secondary cell text-center">'.acym_translation('ACYM_DYNAMIC_CONTENT').acym_info(
1182 - 'ACYM_DYNAMIC_CONTENT_DESC'
1183 - ).'</h3>
1184 - <div class="cell acym__plugins__installed__custom-view__tag" v-for="(trad, tag) in tags" :key="tag" @click.prevent="insertTag(tag)">{{ trad }}</div>
1185 - </div>
1186 - </div>
1187 - <div class="cell grid-x acym__plugins__installed__custom-view__actions acym_vcenter margin-top-1 padding-bottom-1">
1188 - <div class="cell auto grid-x">
1189 - <button type="button" class="cell shrink button-secondary button" @click="resetView">'.acym_translation('ACYM_RESET_VIEW').'</button>
1190 - <div class="cell shrink margin-right-2 acym_vcenter margin-left-1">
1191 - <i v-if="deleting" class="acymicon-spin acymicon-circle-o-notch" style="margin-bottom: 0; line-height: 26px;"></i>
1192 - <span v-if="deleted">{{ messageDeleted }}</span>
1193 - </div>
1194 - </div>
1195 - <div class="cell auto align-right grid-x">
1196 - <div class="cell shrink margin-right-2 acym_vcenter">
1197 - <i v-if="saving" class="acymicon-spin acymicon-circle-o-notch" style="margin-bottom: 0; line-height: 26px;"></i>
1198 - <span v-if="saved">{{ messageSaved }}</span>
1199 - </div>
1200 - <button @click="save()" class="cell shrink button" type="button">'.acym_translation('ACYM_SAVE_NEW_CUSTOM_VIEW_VERSION').'</button>
1201 - </div>
1202 - </div>
1203 - </div>';
1204 - $text .= acym_modal(
1205 - acym_translation('ACYM_EDIT_CUSTOM_VIEW'),
1206 - $modalContent,
1207 - null,
1208 - 'acym-data-plugins-id="'.$idCustomView.'" acym-data-plugin-class="'.get_class($this).'" acym-data-plugin-folder="'.$this->name.'"',
1209 - 'class="cell button"'
1210 - );
1211 - } elseif ($field['type'] == 'multikeyvalue') {
1462 + } elseif ($field['type'] === 'custom_view') {
1463 + ob_start();
1464 + $this->displayCustomView($field);
1465 + $text .= ob_get_clean();
1466 + } elseif ($field['type'] === 'multikeyvalue') {
1212 1467 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1213 1468
1214 1469 $text .= '<div class="multikeyvalue_container grid-x">';
1215 1470
@@ -1215,22 +1470,22 @@
1215 1470
1216 1471 if (!empty($field['value'])) {
1217 1472 $headers = json_decode($field['value'], true);
1218 1473 foreach ($headers as $headerKey => $headerValue) {
1219 - $text .= '<input type="text" class="cell" placeholder="'.acym_translation('ACYM_DKIM_KEY', true).'" value="'.acym_escape($headerKey).'"/>';
1220 - $text .= '<input type="text" class="cell" placeholder="'.acym_translation('ACYM_VALUE', true).'" value="'.acym_escape($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).'" />';
1221 1476 $text .= '<div class="multikeyvalue_container_separator cell small-6"></div>';
1222 1477 }
1223 1478 }
1224 1479
1225 - $text .= '<input type="text" class="cell" placeholder="'.acym_translation('ACYM_DKIM_KEY', true).'" value=""/>';
1226 - $text .= '<input type="text" class="cell" placeholder="'.acym_translation('ACYM_VALUE', true).'" value="" />';
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="" />';
1227 1482
1228 - $text .= '<button class="button multikeyvalue_container_new">'.acym_translation('ACYM_ADD_NEW').'</button>';
1483 + $text .= '<button class="button multikeyvalue_container_new">'.esc_html(acym_translation('ACYM_ADD_NEW')).'</button>';
1229 1484
1230 - $text .= '<input type="hidden" name="'.$name.'" value="" />';
1485 + $text .= '<input type="hidden" name="'.esc_attr($name).'" value="" />';
1231 1486 $text .= '</div>';
1232 - } elseif ($field['type'] == 'custom') {
1487 + } elseif ($field['type'] === 'custom') {
1233 1488 $text .= $field['content'];
1234 1489 }
1235 1490 $plugin->settings[$key] = $text;
1236 1491 }
@@ -1237,10 +1492,94 @@
1237 1492
1238 1493 return true;
1239 1494 }
1240 1495
1241 - public function loadCSS($css, $raw = false, $path = null)
1496 + protected function displayCustomView(array $field): void
1242 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 + {
1243 1582 if (!$raw) {
1244 1583 if (empty($path)) $path = ACYM_DYNAMICS_URL.$this->name;
1245 1584 $css = $path.'/css/'.$css.'.css';
1246 1585 }
@@ -1246,9 +1585,12 @@
1246 1585 }
1247 1586 acym_addStyle($raw, $css);
1248 1587 }
1249 1588
1250 - public function loadJavascript($js, $raw = false, $path = null)
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
1251 1593 {
1252 1594 if (!$raw) {
1253 1595 if (empty($path)) $path = ACYM_DYNAMICS_URL.$this->name;
1254 1596 $js = $path.'/js/'.$js.'.js';
@@ -1255,9 +1597,9 @@
1255 1597 }
1256 1598 acym_addScript($raw, $js);
1257 1599 }
1258 1600
1259 - public function includeView($view, $data = [], $path = null)
1601 + public function includeView(string $view, array $data = [], ?string $path = null): string
1260 1602 {
1261 1603 if (empty($path)) {
1262 1604 $path = ACYM_ADDONS_FOLDER_PATH.$this->name.DS.'views'.DS.$view.'.php';
1263 1605 } else {
@@ -1263,9 +1605,11 @@
1263 1605 } else {
1264 1606 $path = $path.DS.'views'.DS.$view.'.php';
1265 1607 }
1266 1608
1267 - if (!file_exists($path)) return false;
1609 + if (!file_exists($path)) {
1610 + throw new \Exception(esc_attr(acym_translation('ACYM_NON_EXISTING_PAGE')));
1611 + }
1268 1612
1269 1613 ob_start();
1270 1614 include $path;
1271 1615
@@ -1271,10 +1615,17 @@
1271 1615
1272 1616 return ob_get_clean();
1273 1617 }
1274 1618
1275 - public function onAcymAddSettings(&$plugins)
1619 + /**
1620 + * This function will parse and show all settings
1621 + */
1622 + public function onAcymAddSettings(array &$plugins): void
1276 1623 {
1624 + if (method_exists($this, 'initSettings')) {
1625 + $this->initSettings();
1626 + }
1627 +
1277 1628 foreach ($plugins as $key => $plugin) {
1278 1629 if ($plugin->folder_name === $this->name) {
1279 1630 if (!empty($plugin->settings)) {
1280 1631 foreach ($plugin->settings as $keySettings => $value) {
@@ -1290,23 +1641,31 @@
1290 1641 }
1291 1642 }
1292 1643 }
1293 1644
1294 - protected function getParam($name, $default = '')
1645 + /**
1646 + * @param mixed $default
1647 + *
1648 + * @return mixed
1649 + */
1650 + protected function getParam(string $name, $default = '')
1295 1651 {
1296 1652 return $this->savedSettings[$name]['value'] ?? $default;
1297 1653 }
1298 1654
1299 - protected function getTranslationId($elementId, $translationTool, $defaultLanguage = false)
1655 + protected function getTranslationId(int $elementId, string $translationTool, bool $defaultLanguage = false)
1300 1656 {
1301 1657 return $elementId;
1302 1658 }
1303 1659
1304 - public function filterSpecialMailsDailySend(&$specialMails, $time, $mailType)
1660 + public function filterSpecialMailsDailySend(array &$specialMails, int $time, string $mailType): void
1305 1661 {
1662 + // Only once a day
1306 1663 $dailyHour = $this->config->get('daily_hour', '12');
1307 1664 $dailyMinute = $this->config->get('daily_minute', '00');
1665 + // The day it is currently based on the timezone specified in the CMS configuration
1308 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
1309 1668 $dayBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$dailyHour.':'.$dailyMinute);
1310 1669
1311 1670 $campaignClass = new CampaignClass();
1312 1671
@@ -1313,9 +1672,9 @@
1313 1672 $tmpMails = [];
1314 1673 foreach ($specialMails as $oneMail) {
1315 1674 if ($oneMail->sending_type === $mailType) {
1316 1675 $noNextTrigger = empty($oneMail->next_trigger);
1317 - $nextTriggerIsNow = date('m-d', $oneMail->next_trigger) == date('m-d', $dayBasedOnCMSTimezoneAtSpecifiedHour);
1676 + $nextTriggerIsNow = gmdate('m-d', $oneMail->next_trigger) == gmdate('m-d', $dayBasedOnCMSTimezoneAtSpecifiedHour);
1318 1677 $nextTriggerIsInPast = $oneMail->next_trigger < $time;
1319 1678 if ($time >= $dayBasedOnCMSTimezoneAtSpecifiedHour && ($noNextTrigger || $nextTriggerIsNow || $nextTriggerIsInPast)) {
1320 1679 $oneMail->next_trigger = acym_getTime('tomorrow '.$dailyHour.':'.$dailyMinute);
1321 1680 $oneMail->last_generated = $time;
@@ -1328,28 +1687,22 @@
1328 1687 }
1329 1688 $specialMails = $tmpMails;
1330 1689 }
1331 1690
1332 - protected function getIdsSelectAjax()
1691 + protected function getIdsSelectAjax(): array
1333 1692 {
1334 1693 $ids = acym_getVar('string', 'id');
1335 - if (is_null($ids)) {
1336 - return false;
1337 - } elseif (strpos($ids, ',') !== false) {
1338 - $ids = explode(',', $ids);
1339 - } elseif (!empty($ids)) {
1340 - $ids = [$ids];
1341 - } else {
1342 - echo json_encode([]);
1343 - exit;
1694 + if (empty($ids)) {
1695 + return [];
1344 1696 }
1345 1697
1698 + $ids = explode(',', $ids);
1346 1699 acym_arrayToInteger($ids);
1347 1700
1348 1701 return $ids;
1349 1702 }
1350 1703
1351 - protected function cleanExtensionContent($text)
1704 + protected function cleanExtensionContent(string $text)
1352 1705 {
1353 1706 if (ACYM_CMS === 'wordpress') {
1354 1707 if (!acym_isExtensionActive('classic-editor/classic-editor.php') || strpos($text, '<!-- wp:') !== false) {
1355 1708 return $text;
@@ -1360,69 +1713,61 @@
1360 1713 return preg_replace('#\{igallery[^}]+\}#Ui', '', $text);
1361 1714 }
1362 1715 }
1363 1716
1364 - protected function callApiSendingMethod($url, $data = [], $headers = [], $type = 'GET', $authentication = [], $dataDecoded = false)
1365 - {
1366 - if (!empty($headers) && empty($headers[0])) {
1367 - $newHeaders = [];
1368 - foreach ($headers as $key => $value) {
1369 - $newHeaders[] = $key.': '.$value;
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;
1370 1735 }
1371 - $headers = $newHeaders;
1372 1736 }
1373 1737
1374 - $curl = curl_init();
1375 -
1376 - $optionsArray = [
1377 - CURLOPT_URL => $url,
1378 - CURLOPT_RETURNTRANSFER => true,
1379 - CURLOPT_ENCODING => '',
1380 - CURLOPT_MAXREDIRS => 10,
1381 - CURLOPT_TIMEOUT => 30,
1382 - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
1383 - CURLOPT_CUSTOMREQUEST => $type,
1384 - CURLOPT_HTTPHEADER => $headers,
1385 - CURLOPT_SSL_VERIFYHOST => 0,
1386 - CURLOPT_SSL_VERIFYPEER => 0,
1387 - ];
1388 -
1389 - if (!empty($data)) {
1390 - if (empty($dataDecoded)) {
1391 - $optionsArray[CURLOPT_POSTFIELDS] = json_encode($data);
1392 - } elseif ($dataDecoded === true) {
1393 - $optionsArray[CURLOPT_POSTFIELDS] = $data;
1394 - }
1395 - }
1396 -
1397 1738 if (!empty($authentication)) {
1398 - $optionsArray[CURLOPT_USERPWD] = $authentication['name'].':'.$authentication['pwd'];
1739 + $assocHeaders['Authorization'] = 'Basic '.base64_encode($authentication['name'].':'.$authentication['pwd']);
1399 1740 }
1400 1741
1401 - curl_setopt_array(
1402 - $curl,
1403 - $optionsArray
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 + ]
1404 1753 );
1405 1754
1406 - $response = curl_exec($curl);
1407 - $error = curl_error($curl);
1408 - $this->responseCode = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
1409 - curl_close($curl);
1755 + $this->responseCode = (int)($result['status_code'] ?? 0);
1756 + unset($result['status_code']);
1410 1757
1411 - if ($error) {
1412 - return ['error_curl' => $error];
1413 - } else {
1414 - $response = json_decode($response, true);
1758 + if (isset($result['error'])) {
1759 + return ['error_curl' => $result['error']];
1760 + }
1415 1761
1416 - return $response === null ? ['error_curl' => 'Malformed response'] : $response;
1417 - }
1762 + return $result;
1418 1763 }
1419 1764
1420 - protected function getTestCredentialsSendingMethodButton($sendingMethodId)
1765 + protected function getTestCredentialsSendingMethodButton(string $sendingMethodId): void
1421 1766 {
1422 - return '<div class="cell grid-x margin-top-1 acym__sending__methods__credentials__test">
1423 - <button type="button" sending-method-id="'.$sendingMethodId.'" class="acym__configuration__sending__method-test cell shrink button button-secondary">
1424 - '.acym_translation('ACYM_TEST_CREDENTIALS').'
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')).'
1425 1770 </button>
1426 1771 <span class="acym__configuration__sending__method-icon cell shrink margin-left-1 acym_vcenter"></span>
1427 1772 <span class="acym__configuration__sending__method-test__message cell shrink margin-left-1 acym_vcenter"></span>
1428 1773 </div>';
@@ -1427,46 +1772,54 @@
1427 1772 <span class="acym__configuration__sending__method-test__message cell shrink margin-left-1 acym_vcenter"></span>
1428 1773 </div>';
1429 1774 }
1430 1775
1431 - public function getCopySettingsButton(array $data, string $sendingMethodId, string $fromPlugin, bool $withContainer = true): string
1776 + public function getCopySettingsButton(array $data, string $sendingMethodId, string $fromPlugin, bool $withContainer = true): void
1432 1777 {
1433 1778 if (empty($data[$fromPlugin.'_installed'])) {
1434 - return '';
1779 + return;
1435 1780 }
1436 1781
1437 - $button = '<button
1438 - type="button"
1439 - class="cell shrink button button-secondary acym__configuration__copy__mail__settings"
1440 - acym-data-plugin="'.acym_escape($fromPlugin).'"
1441 - acym-data-method="'.acym_escape($sendingMethodId).'">
1442 - '.acym_translationSprintf('ACYM_COPY_SETTINGS_FROM', $this->sendingPlugins[$fromPlugin]).'
1443 - </button>
1444 - <span class="acym__configuration__sending__method-icon cell shrink margin-left-1 acym_vcenter"></span>';
1782 + if ($withContainer) {
1783 + echo '<div class="cell grid-x margin-top-1">';
1784 + }
1445 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 +
1446 1795 if ($withContainer) {
1447 - $button = '<div class="cell grid-x margin-top-1">'.$button.'</div>';
1796 + echo '</div>';
1448 1797 }
1449 -
1450 - return $button;
1451 1798 }
1452 1799
1453 - protected function getLinks($account = '', $pricing = '')
1800 + protected function getLinks(string $account = '', string $pricing = ''): void
1454 1801 {
1455 - if (empty($account) && empty($pricing)) return '';
1802 + if (empty($account) && empty($pricing)) {
1803 + return;
1804 + }
1456 1805
1457 - $html = '<div class="cell grid-x acym-grid-margin-x shrink acym_vcenter"><p class="cell shrink">'.acym_translation('ACYM_DONT_HAVE_ACCOUNT').'</p>';
1458 - if (!empty($account)) $html .= '<a target="_blank" class="cell shrink" href="'.$account.'">'.acym_translation('ACYM_CREATE_ONE').'</a>';
1459 - if (!empty($account) && !empty($pricing)) $html .= '<p class="cell shrink">'.strtolower(acym_translation('ACYM_OR')).'</p>';
1460 - if (!empty($pricing)) $html .= '<a target="_blank" class="cell shrink" href="'.$pricing.'">'.acym_translation('ACYM_CHECK_THEIR_PRICING').'</a>';
1461 - $html .= '</div>';
1462 -
1463 - return $html;
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>';
1464 1817 }
1465 1818
1466 - public function onAcymGetSendingMethodsSelected(&$data)
1819 + public function onAcymGetSendingMethodsSelected(array &$data): void
1467 1820 {
1468 - if (ACYM_CMS == 'wordpress') {
1821 + if (ACYM_CMS === 'wordpress') {
1469 1822 $this->config->load();
1470 1823 }
1471 1824
1472 1825 $mailerMethod = $this->config->get('mailer_method', 'phpmail');
@@ -1474,9 +1827,9 @@
1474 1827 $data['sendingMethods'][$key]['selected'] = $key == $mailerMethod;
1475 1828 }
1476 1829 }
1477 1830
1478 - public function errorCallback()
1831 + public function errorCallback(): void
1479 1832 {
1480 1833 foreach ($this->errors as $error) {
1481 1834 acym_logError($error, empty($this->name) ? get_class($this) : $this->name);
1482 1835 }
@@ -1488,9 +1841,9 @@
1488 1841 {
1489 1842 return !acym_isLogFileErrorExist(empty($this->name) ? get_class($this) : $this->name);
1490 1843 }
1491 1844
1492 - public function initCustomView($customFields = false)
1845 + public function initCustomView(bool $customFields = false): void
1493 1846 {
1494 1847 $page = acym_getVar('cmd', 'page');
1495 1848 if (is_array($page)) return;
1496 1849 if (!empty($page) && is_string($page)) {
@@ -1500,8 +1853,9 @@
1500 1853 if (!in_array($ctrl, ['plugins', 'dynamics'])) return;
1501 1854
1502 1855 $task = acym_getVar('cmd', 'task', 'installed');
1503 1856
1857 + // Installed add-ons page or installed + editor
1504 1858 if (($ctrl === 'plugins' && $task === 'installed') || ($this->active && $ctrl === 'dynamics' && $task === 'trigger')) {
1505 1859 $this->initElementOptionsCustomView();
1506 1860 $this->initReplaceOptionsCustomView();
1507 1861 if ($customFields) $this->initCustomOptionsCustomView();
@@ -1507,9 +1861,9 @@
1507 1861 if ($customFields) $this->initCustomOptionsCustomView();
1508 1862 }
1509 1863 }
1510 1864
1511 - public function processDateToCheck($options)
1865 + public function processDateToCheck(array $options): \DateTime
1512 1866 {
1513 1867 $dateNowWithTimeZone = acym_date('now', 'Y-m-d h:i:s');
1514 1868 $dateToCheck = new \DateTime($dateNowWithTimeZone);
1515 1869 $interval = new \DateInterval('P'.intval($options['days']).'D');
@@ -1521,14 +1875,14 @@
1521 1875
1522 1876 return $dateToCheck;
1523 1877 }
1524 1878
1525 - public function onAcymCheckInstalled(&$installed)
1879 + public function onAcymCheckInstalled(bool &$installed): void
1526 1880 {
1527 1881 $installed = $this->installed;
1528 1882 }
1529 1883
1530 - protected function getLinkTranslated($link)
1884 + protected function getLinkTranslated(string $link): string
1531 1885 {
1532 1886 if (empty($this->emailLanguage)) return $link;
1533 1887 if (!acym_isMultilingual()) return $link;
1534 1888
@@ -1538,12 +1892,13 @@
1538 1892 if (!acym_isExtensionActive('sitepress-multilingual-cms/sitepress.php')) return $link;
1539 1893
1540 1894 $languageCode = substr($this->emailLanguage, 0, 2);
1541 1895
1896 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML hook for integration.
1542 1897 return apply_filters('wpml_permalink', $link, $languageCode);
1543 1898 }
1544 1899
1545 - protected function replaceShortcode($content)
1900 + protected function replaceShortcode(string $content): string
1546 1901 {
1547 1902 $content = do_shortcode($content);
1548 1903
1549 1904 return preg_replace('#<!-- wp:shortcode -->(.*)<!-- /wp:shortcode -->#Uis', '$1', $content);