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 +550 -229 10.11.1 → 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
@@ -57,8 +62,14 @@
57 62 private bool $active;
58 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();
@@ -97,9 +109,9 @@
97 109 protected function displaySelectionZone(string $zoneContent): void
98 110 {
99 111 ?>
100 112 <p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">
101 - <?php echo acym_escape(acym_translation('ACYM_CONTENT_TO_INSERT')); ?><i class="acymicon-keyboard-arrow-up"></i>
113 + <?php echo esc_html(acym_translation('ACYM_CONTENT_TO_INSERT')); ?><i class="acymicon-keyboard-arrow-up"></i>
102 114 </p>
103 115 <div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container">
104 116 <?php echo $zoneContent; ?>
105 117 </div>
@@ -105,8 +117,11 @@
105 117 </div>
106 118 <?php
107 119 }
108 120
121 + /**
122 + * Called using ajax
123 + */
109 124 public function displayListing(): void
110 125 {
111 126 echo $this->prepareListing();
112 127 }
@@ -167,9 +182,9 @@
167 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">';
@@ -331,9 +346,13 @@
331 346 $options[] = [
332 347 'title' => 'ACYM_DOCUMENTATION',
333 348 'type' => 'custom',
334 349 'name' => 'documentation',
335 - '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 + },
336 355 'js' => '',
337 356 'section' => 'ACYM_AUTO_CAMPAIGNS_OPTIONS',
338 357 ];
339 358
@@ -374,13 +393,17 @@
374 393
375 394 protected function getElementsListing(array $options): string
376 395 {
377 396 if ($this->pageInfo->loadMore) {
378 - return $this->getInnerListing($options);
397 + ob_start();
398 + $this->displayInnerListing($options);
399 +
400 + return ob_get_clean();
379 401 }
380 402 $listing = '<div id="plugin_listing" class="acym__popup__listing">';
381 - $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)).'" />';
382 404
405 + // Column names
383 406 $listing .= '<div class="cell grid-x hide-for-small-only plugin_listing_headers">';
384 407 foreach ($options['header'] as $oneColumn) {
385 408 $class = empty($oneColumn['class']) ? '' : ' '.$oneColumn['class'];
386 409 $listing .= '<div class="cell plugin_listing_headers__title medium-'.$oneColumn['size'].$class.'">'.acym_translation($oneColumn['label']).'</div>';
@@ -386,9 +409,11 @@
386 409 $listing .= '<div class="cell plugin_listing_headers__title medium-'.$oneColumn['size'].$class.'">'.acym_translation($oneColumn['label']).'</div>';
387 410 }
388 411 $listing .= '</div>';
389 412
390 - $listing .= $this->getInnerListing($options);
413 + ob_start();
414 + $this->displayInnerListing($options);
415 + $listing .= ob_get_clean();
391 416
392 417 $listing .= '<input type="hidden" value="1" id="acym_pagination__ajax__load-more" name="acym_pagination__ajax__load-more">';
393 418 $listing .= '</div>';
394 419
@@ -394,65 +419,72 @@
394 419
395 420 return $listing;
396 421 }
397 422
398 - private function getInnerListing(array $options): string
423 + private function displayInnerListing(array $options): void
399 424 {
400 - if (empty($options['rows']) && $this->pageInfo->loadMore) {
401 - $listing = '<h3 class="cell acym__listing__empty__load-more text-center">'.acym_translation('ACYM_NO_MORE_RESULTS').'</h3>';
402 - } elseif (empty($options['rows'])) {
403 - $listing = '<h1 class="cell acym__listing__empty__search__modal text-center">'.acym_translation('ACYM_NO_RESULTS_FOUND').'</h1>';
404 - } else {
405 - $selected = explode(',', acym_getVar('string', 'selected', ''));
406 - if (!empty($this->defaultValues->id)) {
407 - $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>';
408 430 }
409 431
410 - $listing = '';
411 - foreach ($options['rows'] as $row) {
412 - $class = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
413 - if (in_array($row->{$options['id']}, $selected)) {
414 - $class .= ' selected_row';
415 - }
432 + return;
433 + }
416 434
417 - $listing .= '<div
418 - class="'.$class.'"
419 - data-id="'.acym_escape($row->{$options['id']}).'"
420 - 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 + }
421 439
422 - foreach ($options['header'] as $column => $oneColumn) {
423 - $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 + }
424 445
425 - if (!empty($oneColumn['type'])) {
426 - if ($oneColumn['type'] === 'date') {
427 - if (empty($value)) {
428 - $value = '-';
429 - } else {
430 - if (!is_numeric($value) && $value != '0000-00-00 00:00:00') {
431 - $value = strtotime($value);
432 - }
433 - $tooltip = acym_date($value, acym_translation('ACYM_DATE_FORMAT_LC2'));
434 - $value = acym_tooltip(
435 - [
436 - 'hoveredText' => acym_date($value, acym_translation('ACYM_DATE_FORMAT_LC5')),
437 - 'textShownInTooltip' => $tooltip,
438 - ]
439 - );
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);
440 464 }
441 - } elseif ($oneColumn['type'] === 'int') {
442 - $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 + );
443 472 }
473 + } elseif ($oneColumn['type'] === 'int') {
474 + echo intval($value);
475 + } else {
476 + echo esc_html($value);
444 477 }
445 -
446 - $class = empty($oneColumn['class']) ? '' : ' '.$oneColumn['class'];
447 - $listing .= '<div class="cell medium-'.$oneColumn['size'].$class.'">'.$value.'</div>';
478 + } else {
479 + echo esc_html($value);
448 480 }
449 481
450 - $listing .= '</div>';
482 + echo '</div>';
451 483 }
484 +
485 + echo '</div>';
452 486 }
453 -
454 - return $listing;
455 487 }
456 488
457 489 protected function getCategoryListing(): string
458 490 {
@@ -474,10 +506,10 @@
474 506 $classes = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
475 507 if (in_array($oneCat->value, $selected)) {
476 508 $classes .= ' selected_row';
477 509 }
478 - $listing .= '<div class="'.$classes.'" data-id="'.intval($oneCat->value).'" onclick="applyContentauto'.acym_escape($this->name).'('.intval($oneCat->value).', this);">
479 - <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>
480 512 </div>';
481 513 }
482 514 $listing .= '</div>';
483 515
@@ -505,11 +537,15 @@
505 537 foreach ($this->tagvalues as $oneTag) {
506 538 if (empty($oneTag->$termIdName)) continue;
507 539
508 540 $class = 'cell grid-x acym__row__no-listing acym__listing__row__popup';
509 - if (in_array($oneTag->$termIdName, $selected)) $class .= ' selected_row';
510 - $listing .= '<div class="'.$class.'" data-id="'.intval($oneTag->$termIdName).'" onclick="applyContent'.acym_escape($this->name).'_tags('.intval($oneTag->$termIdName).', this);">
511 - <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>
512 548 </div>';
513 549 }
514 550 $listing .= '</div>';
515 551
@@ -572,8 +608,11 @@
572 608
573 609 return $campaignClass->getLastGenerated($mailId);
574 610 }
575 611
612 + /**
613 + * Returns the individual elements tags based on a query result
614 + */
576 615 protected function finalizeCategoryFormat(string $query, object $parameter, ?string $table = null): string
577 616 {
578 617 $this->handleOrderBy($query, $parameter, $table);
579 618 $this->handleMax($query, $parameter);
@@ -633,8 +672,11 @@
633 672
634 673 return $arrayElements;
635 674 }
636 675
676 + /**
677 + * This method can be overridden in the add-on when a group by category option is added
678 + */
637 679 protected function groupByCategory(array $elements)
638 680 {
639 681 return $elements;
640 682 }
@@ -718,10 +760,18 @@
718 760
719 761 return $varFields;
720 762 }
721 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 + */
722 771 protected function finalizeElementFormat(string $htmlResult, object $insertionOptions, array $dataShortcodes): string
723 772 {
773 + // Hidden feature, users can do a PHP custom view
724 774 $fullCustomLayoutPath = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.php';
725 775 if ($this->config->get('php_overrides', 0) == 1 && file_exists($fullCustomLayoutPath)) {
726 776 ob_start();
727 777 require $fullCustomLayoutPath;
@@ -727,8 +777,9 @@
727 777 require $fullCustomLayoutPath;
728 778 $viewContent = ob_get_clean();
729 779 $htmlResult = str_replace(array_keys($dataShortcodes), $dataShortcodes, $viewContent);
730 780 } else {
781 + // Default custom view feature with only HTML for security reasons
731 782 $customLayoutPath = ACYM_CUSTOM_PLUGIN_LAYOUT.$this->name.'.html';
732 783 if (file_exists($customLayoutPath)) {
733 784 $dataShortcodes['{wrappedText}'] = $this->pluginHelper->wrappedText;
734 785 $viewContent = acym_fileGetContent($customLayoutPath);
@@ -749,19 +800,235 @@
749 800 }
750 801 $allShortcodesList .= '</table>';
751 802 $htmlResult = str_replace('{allshortcodes}', $allShortcodesList, $htmlResult);
752 803
804 + // Resize/remove pictures if needed
753 805 return $this->pluginHelper->managePicts($insertionOptions, $htmlResult);
754 806 }
755 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 + */
756 1016 protected function filtersFromConditions(array &$filters): void
757 1017 {
758 - $newFilters = [];
1018 + $conditions = [];
1019 + $this->onAcymDeclareConditions($conditions);
759 1020
760 - $this->onAcymDeclareConditions($newFilters);
761 - foreach ($newFilters as $oneType) {
762 - foreach ($oneType as $oneFilterName => $oneFilter) {
763 - if (!empty($oneFilter->option)) $oneFilter->option = str_replace(['acym_condition', '[conditions]'], ['acym_action', '[filters]'], $oneFilter->option);
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 +
764 1031 $filters[$oneFilterName] = $oneFilter;
765 1032 }
766 1033 }
767 1034 }
@@ -785,8 +1052,11 @@
785 1052
786 1053 return $displayTags;
787 1054 }
788 1055
1056 + /**
1057 + * @return mixed
1058 + */
789 1059 protected function getFormattedValue(array $fieldValues)
790 1060 {
791 1061 $field = $fieldValues[0];
792 1062
@@ -845,9 +1115,9 @@
845 1115 if (!empty($field->value)) {
846 1116 if (substr($field->value, 0, 1) === '{') {
847 1117 $value = json_decode($field->value, true);
848 1118 if (!empty($value['imagefile'])) {
849 - $alt = empty($value['alt_text']) ? '' : ' alt="'.acym_escape($value['alt_text']).'"';
1119 + $alt = empty($value['alt_text']) ? '' : ' alt="'.esc_attr($value['alt_text']).'"';
850 1120 $value = '<img src="'.$value['imagefile'].'"'.$alt.' />';
851 1121 }
852 1122 } else {
853 1123 $value = '<img src="'.$field->value.'" alt="" />';
@@ -988,8 +1258,9 @@
988 1258 return '&language='.$value;
989 1259 }
990 1260 }
991 1261
1262 + // Don't add types for $tag and $sef, for retro-compatibility
992 1263 protected function finalizeLink(string $link, $tag = null, $sef = true): string
993 1264 {
994 1265 if (acym_isPluginActive('languagefilter')) {
995 1266 if (strpos($link, 'lang=') === false && !empty($this->emailLanguage)) {
@@ -1006,8 +1277,9 @@
1006 1277 if (!empty($tag->autologin)) {
1007 1278 $link .= (strpos($link, '?') ? '&' : '?').'autoSubId={subscriber:id}&subKey={subscriber:autologin_token|urlencode}';
1008 1279 }
1009 1280
1281 + // Retro-compatibility for add-on versions before the v8.6.0
1010 1282 if (is_bool($tag)) {
1011 1283 $sef = $tag;
1012 1284 }
1013 1285
@@ -1075,30 +1347,27 @@
1075 1347
1076 1348 return array_shift($split);
1077 1349 }
1078 1350
1079 - public function displayCustomViewEditor(string &$output): void
1351 + public function displayCustomViewEditor(): void
1080 1352 {
1081 - $plugin = new \stdClass();
1082 - $plugin->folder_name = $this->name;
1083 - $plugin->settings = $this->settings;
1084 - $this->generateSettings($plugin);
1085 -
1086 - if (empty($plugin->settings['custom_view'])) {
1353 + if (empty($this->settings['custom_view']) || !acym_isAdmin()) {
1087 1354 return;
1088 1355 }
1089 1356
1090 - $output .= '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">';
1091 - $output .= acym_translation('ACYM_ADDON_SETTINGS');
1092 - $output .= '<i class="acymicon-keyboard-arrow-up"></i></p>';
1093 - $output .= '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container">';
1094 - $output .= $plugin->settings['custom_view'];
1095 - $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>';
1096 1363 }
1097 1364
1098 1365 public function generateSettings(object $plugin): bool
1099 1366 {
1100 - if (empty($plugin->settings)) return false;
1367 + if (empty($plugin->settings)) {
1368 + return false;
1369 + }
1101 1370
1102 1371 if (array_key_exists('not_installed', $plugin->settings)) {
1103 1372 $plugin->settings = 'not_installed';
1104 1373
@@ -1118,9 +1387,10 @@
1118 1387 $field['label'] = acym_translation($field['label']);
1119 1388 }
1120 1389
1121 1390 if (!empty($field['info'])) {
1122 - $field['label'] .= acym_info(
1391 + ob_start();
1392 + acym_info(
1123 1393 [
1124 1394 'textShownInTooltip' => acym_translation($field['info']),
1125 1395 'classText' => 'wysid_tooltip',
1126 1396 'isWarning' => !empty($field['info_warning']),
@@ -1125,22 +1395,28 @@
1125 1395 'classText' => 'wysid_tooltip',
1126 1396 'isWarning' => !empty($field['info_warning']),
1127 1397 ]
1128 1398 );
1399 + $field['label'] .= ob_get_clean();
1129 1400 }
1130 1401
1131 1402 if ($field['type'] === 'checkbox') {
1132 1403 $classLabel = 'shrink';
1133 - $text .= '<label for="'.acym_escape($id).'" class="cell '.acym_escape($classLabel).'">'.acym_escape($field['label']).'</label>';
1134 - $text .= '<input id="'.acym_escape($id).'" class="cell shrink" type="checkbox" name="'.acym_escape($name).'" '.(empty($field['value']) ? '' : 'checked').'>';
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 + ).'>';
1135 1410 } elseif ($field['type'] === 'switch') {
1136 - $text .= acym_switch(
1137 - $name,
1138 - $field['value'],
1139 - $field['label'],
1140 - [],
1141 - 'large-7'
1142 - );
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();
1143 1419 } elseif ($field['type'] === 'select') {
1144 1420 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1145 1421 $text .= acym_select(
1146 1422 $field['data'],
@@ -1161,20 +1437,22 @@
1161 1437 ['class' => 'acym__select']
1162 1438 );
1163 1439 } elseif ($field['type'] === 'text') {
1164 1440 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1165 - $text .= '<input type="text" name="'.$name.'" value="'.acym_escape($field['value']).'" class="cell shrink">';
1441 + $text .= '<input type="text" name="'.$name.'" value="'.esc_attr($field['value']).'" class="cell shrink">';
1166 1442 } elseif ($field['type'] === 'number') {
1167 1443 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1168 - $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">';
1169 1445 if (!empty($field['post_text'])) $text .= '<span class="cell shrink">'.strtolower($field['post_text']).'</span>';
1170 1446 } elseif ($field['type'] === 'radio') {
1171 1447 $text .= '<p class="cell">'.$field['label'].'</p>';
1172 - $text .= acym_radio(
1448 + ob_start();
1449 + acym_radio(
1173 1450 $field['data'],
1174 1451 $name,
1175 1452 $field['value']
1176 1453 );
1454 + $text .= ob_get_clean();
1177 1455 } elseif ($field['type'] === 'date') {
1178 1456 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1179 1457 $text .= acym_dateField(
1180 1458 $name,
@@ -1180,71 +1458,13 @@
1180 1458 $name,
1181 1459 $field['value'],
1182 1460 'cell shrink'
1183 1461 );
1184 - } elseif ($field['type'] === 'custom_view' && acym_isAdmin()) {
1185 - $idCustomView = 'acym__plugins__installed__custom-view__'.$this->name;
1186 - $ctrl = acym_getVar('string', 'ctrl', '');
1187 - $classTooltip = $ctrl === 'dynamics' ? '' : 'wysid_tooltip';
1188 - $text .= '<label class="cell">';
1189 - $text .= acym_translation('ACYM_CUSTOM_VIEW');
1190 - $text .= acym_info(
1191 - [
1192 - 'textShownInTooltip' => 'ACYM_CUSTOM_VIEW_DESC',
1193 - 'classText' => $classTooltip,
1194 - ]
1195 - );
1196 - $text .= '</label>';
1197 -
1198 - if (empty($field['tags'])) {
1199 - $field['tags'] = [];
1200 - }
1201 -
1202 - $modalContent = '<div id="'.acym_escape($idCustomView).'" class="cell grid-x acym__plugins__installed__custom-view" acym-data-tags="'.acym_escape(
1203 - json_encode($field['tags'])
1204 - ).'">
1205 - <h2 class="cell text-center acym__title__primary__color">'.acym_translationSprintf('ACYM_CUSTOM_VIEW_FOR_X', $this->pluginDescription->name).'</h2>
1206 - <div class="cell grid-x acym__plugins__installed__custom-view__edit-container">
1207 - <div class="acym__plugins__installed__custom-view__editor-loader grid-x cell align-center acym_vcenter" v-if="loading">'.acym_loaderLogo().'</div>
1208 - <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>
1209 - <div class="cell grid-x medium-3 margin-left-1 acym__plugins__installed__custom-view__tags">
1210 - <h3 class="acym__title acym__title__secondary cell text-center">'.acym_translation('ACYM_DYNAMIC_CONTENT').acym_info(
1211 - [
1212 - 'textShownInTooltip' => acym_translation('ACYM_DYNAMIC_CONTENT_DESC'),
1213 - ]
1214 - ).'</h3>
1215 - <div class="cell acym__plugins__installed__custom-view__tag" v-for="(trad, tag) in tags" :key="tag" @click.prevent="insertTag(tag)">{{ trad }}</div>
1216 - </div>
1217 - </div>
1218 - <div class="cell grid-x acym__plugins__installed__custom-view__actions acym_vcenter margin-top-1 padding-bottom-1">
1219 - <div class="cell auto grid-x">
1220 - <button type="button" class="cell shrink button-secondary button" @click="resetView">'.acym_translation('ACYM_RESET_VIEW').'</button>
1221 - <div class="cell shrink margin-right-2 acym_vcenter margin-left-1">
1222 - <i v-if="deleting" class="acymicon-spin acymicon-circle-o-notch" style="margin-bottom: 0; line-height: 26px;"></i>
1223 - <span v-if="deleted">{{ messageDeleted }}</span>
1224 - </div>
1225 - </div>
1226 - <div class="cell auto align-right grid-x">
1227 - <div class="cell shrink margin-right-2 acym_vcenter">
1228 - <i v-if="saving" class="acymicon-spin acymicon-circle-o-notch" style="margin-bottom: 0; line-height: 26px;"></i>
1229 - <span v-if="saved">{{ messageSaved }}</span>
1230 - </div>
1231 - <button @click="save()" class="cell shrink button" type="button">'.acym_translation('ACYM_SAVE_NEW_CUSTOM_VIEW_VERSION').'</button>
1232 - </div>
1233 - </div>
1234 - </div>';
1235 - $text .= acym_modal(
1236 - acym_translation('ACYM_EDIT_CUSTOM_VIEW'),
1237 - $modalContent,
1238 - null,
1239 - [
1240 - 'acym-data-plugins-id' => $idCustomView,
1241 - 'acym-data-plugin-class' => get_class($this),
1242 - 'acym-data-plugin-folder' => $this->name,
1243 - ],
1244 - ['class' => 'cell button']
1245 - );
1246 - } 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') {
1247 1467 $text .= '<label class="cell shrink">'.$field['label'].'</label>';
1248 1468
1249 1469 $text .= '<div class="multikeyvalue_container grid-x">';
1250 1470
@@ -1250,22 +1470,22 @@
1250 1470
1251 1471 if (!empty($field['value'])) {
1252 1472 $headers = json_decode($field['value'], true);
1253 1473 foreach ($headers as $headerKey => $headerValue) {
1254 - $text .= '<input type="text" class="cell" placeholder="'.acym_translation('ACYM_DKIM_KEY', true).'" value="'.acym_escape($headerKey).'"/>';
1255 - $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).'" />';
1256 1476 $text .= '<div class="multikeyvalue_container_separator cell small-6"></div>';
1257 1477 }
1258 1478 }
1259 1479
1260 - $text .= '<input type="text" class="cell" placeholder="'.acym_translation('ACYM_DKIM_KEY', true).'" value=""/>';
1261 - $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="" />';
1262 1482
1263 - $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>';
1264 1484
1265 - $text .= '<input type="hidden" name="'.$name.'" value="" />';
1485 + $text .= '<input type="hidden" name="'.esc_attr($name).'" value="" />';
1266 1486 $text .= '</div>';
1267 - } elseif ($field['type'] == 'custom') {
1487 + } elseif ($field['type'] === 'custom') {
1268 1488 $text .= $field['content'];
1269 1489 }
1270 1490 $plugin->settings[$key] = $text;
1271 1491 }
@@ -1272,8 +1492,92 @@
1272 1492
1273 1493 return true;
1274 1494 }
1275 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 + */
1276 1580 public function loadCSS(string $css, bool $raw = false, ?string $path = null): void
1277 1581 {
1278 1582 if (!$raw) {
1279 1583 if (empty($path)) $path = ACYM_DYNAMICS_URL.$this->name;
@@ -1281,8 +1585,11 @@
1281 1585 }
1282 1586 acym_addStyle($raw, $css);
1283 1587 }
1284 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 + */
1285 1592 public function loadJavascript(string $js, bool $raw = false, ?string $path = null): void
1286 1593 {
1287 1594 if (!$raw) {
1288 1595 if (empty($path)) $path = ACYM_DYNAMICS_URL.$this->name;
@@ -1299,9 +1606,9 @@
1299 1606 $path = $path.DS.'views'.DS.$view.'.php';
1300 1607 }
1301 1608
1302 1609 if (!file_exists($path)) {
1303 - throw new \Exception(acym_translation('ACYM_NON_EXISTING_PAGE'));
1610 + throw new \Exception(esc_attr(acym_translation('ACYM_NON_EXISTING_PAGE')));
1304 1611 }
1305 1612
1306 1613 ob_start();
1307 1614 include $path;
@@ -1308,8 +1615,11 @@
1308 1615
1309 1616 return ob_get_clean();
1310 1617 }
1311 1618
1619 + /**
1620 + * This function will parse and show all settings
1621 + */
1312 1622 public function onAcymAddSettings(array &$plugins): void
1313 1623 {
1314 1624 if (method_exists($this, 'initSettings')) {
1315 1625 $this->initSettings();
@@ -1331,8 +1641,13 @@
1331 1641 }
1332 1642 }
1333 1643 }
1334 1644
1645 + /**
1646 + * @param mixed $default
1647 + *
1648 + * @return mixed
1649 + */
1335 1650 protected function getParam(string $name, $default = '')
1336 1651 {
1337 1652 return $this->savedSettings[$name]['value'] ?? $default;
1338 1653 }
@@ -1343,11 +1658,14 @@
1343 1658 }
1344 1659
1345 1660 public function filterSpecialMailsDailySend(array &$specialMails, int $time, string $mailType): void
1346 1661 {
1662 + // Only once a day
1347 1663 $dailyHour = $this->config->get('daily_hour', '12');
1348 1664 $dailyMinute = $this->config->get('daily_minute', '00');
1665 + // The day it is currently based on the timezone specified in the CMS configuration
1349 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
1350 1668 $dayBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$dailyHour.':'.$dailyMinute);
1351 1669
1352 1670 $campaignClass = new CampaignClass();
1353 1671
@@ -1354,9 +1672,9 @@
1354 1672 $tmpMails = [];
1355 1673 foreach ($specialMails as $oneMail) {
1356 1674 if ($oneMail->sending_type === $mailType) {
1357 1675 $noNextTrigger = empty($oneMail->next_trigger);
1358 - $nextTriggerIsNow = date('m-d', $oneMail->next_trigger) == date('m-d', $dayBasedOnCMSTimezoneAtSpecifiedHour);
1676 + $nextTriggerIsNow = gmdate('m-d', $oneMail->next_trigger) == gmdate('m-d', $dayBasedOnCMSTimezoneAtSpecifiedHour);
1359 1677 $nextTriggerIsInPast = $oneMail->next_trigger < $time;
1360 1678 if ($time >= $dayBasedOnCMSTimezoneAtSpecifiedHour && ($noNextTrigger || $nextTriggerIsNow || $nextTriggerIsInPast)) {
1361 1679 $oneMail->next_trigger = acym_getTime('tomorrow '.$dailyHour.':'.$dailyMinute);
1362 1680 $oneMail->last_generated = $time;
@@ -1395,68 +1713,61 @@
1395 1713 return preg_replace('#\{igallery[^}]+\}#Ui', '', $text);
1396 1714 }
1397 1715 }
1398 1716
1399 - protected function callApiSendingMethod(string $url, array $data = [], array $headers = [], string $type = 'GET', array $authentication = [], bool $dataDecoded = false): array
1400 - {
1401 - if (!empty($headers) && empty($headers[0])) {
1402 - $newHeaders = [];
1403 - foreach ($headers as $key => $value) {
1404 - $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;
1405 1735 }
1406 - $headers = $newHeaders;
1407 1736 }
1408 1737
1409 - $curl = curl_init();
1410 -
1411 - $optionsArray = [
1412 - CURLOPT_URL => $url,
1413 - CURLOPT_RETURNTRANSFER => true,
1414 - CURLOPT_ENCODING => '',
1415 - CURLOPT_MAXREDIRS => 10,
1416 - CURLOPT_TIMEOUT => 30,
1417 - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
1418 - CURLOPT_CUSTOMREQUEST => $type,
1419 - CURLOPT_HTTPHEADER => $headers,
1420 - CURLOPT_SSL_VERIFYHOST => 0,
1421 - CURLOPT_SSL_VERIFYPEER => 0,
1422 - ];
1423 -
1424 - if (!empty($data)) {
1425 - if (empty($dataDecoded)) {
1426 - $optionsArray[CURLOPT_POSTFIELDS] = json_encode($data);
1427 - } elseif ($dataDecoded === true) {
1428 - $optionsArray[CURLOPT_POSTFIELDS] = $data;
1429 - }
1430 - }
1431 -
1432 1738 if (!empty($authentication)) {
1433 - $optionsArray[CURLOPT_USERPWD] = $authentication['name'].':'.$authentication['pwd'];
1739 + $assocHeaders['Authorization'] = 'Basic '.base64_encode($authentication['name'].':'.$authentication['pwd']);
1434 1740 }
1435 1741
1436 - curl_setopt_array(
1437 - $curl,
1438 - $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 + ]
1439 1753 );
1440 1754
1441 - $response = curl_exec($curl);
1442 - $error = curl_error($curl);
1443 - $this->responseCode = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
1755 + $this->responseCode = (int)($result['status_code'] ?? 0);
1756 + unset($result['status_code']);
1444 1757
1445 - if ($error) {
1446 - return ['error_curl' => $error];
1447 - } else {
1448 - $response = json_decode($response, true);
1758 + if (isset($result['error'])) {
1759 + return ['error_curl' => $result['error']];
1760 + }
1449 1761
1450 - return $response === null ? ['error_curl' => 'Malformed response'] : $response;
1451 - }
1762 + return $result;
1452 1763 }
1453 1764
1454 - protected function getTestCredentialsSendingMethodButton(string $sendingMethodId): string
1765 + protected function getTestCredentialsSendingMethodButton(string $sendingMethodId): void
1455 1766 {
1456 - return '<div class="cell grid-x margin-top-1 acym__sending__methods__credentials__test">
1457 - <button type="button" sending-method-id="'.acym_escape($sendingMethodId).'" class="acym__configuration__sending__method-test cell shrink button button-secondary">
1458 - '.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')).'
1459 1770 </button>
1460 1771 <span class="acym__configuration__sending__method-icon cell shrink margin-left-1 acym_vcenter"></span>
1461 1772 <span class="acym__configuration__sending__method-test__message cell shrink margin-left-1 acym_vcenter"></span>
1462 1773 </div>';
@@ -1461,41 +1772,49 @@
1461 1772 <span class="acym__configuration__sending__method-test__message cell shrink margin-left-1 acym_vcenter"></span>
1462 1773 </div>';
1463 1774 }
1464 1775
1465 - 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
1466 1777 {
1467 1778 if (empty($data[$fromPlugin.'_installed'])) {
1468 - return '';
1779 + return;
1469 1780 }
1470 1781
1471 - $button = '<button
1472 - type="button"
1473 - class="cell shrink button button-secondary acym__configuration__copy__mail__settings"
1474 - acym-data-plugin="'.acym_escape($fromPlugin).'"
1475 - acym-data-method="'.acym_escape($sendingMethodId).'">
1476 - '.acym_translationSprintf('ACYM_COPY_SETTINGS_FROM', $this->sendingPlugins[$fromPlugin]).'
1477 - </button>
1478 - <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 + }
1479 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 +
1480 1795 if ($withContainer) {
1481 - $button = '<div class="cell grid-x margin-top-1">'.$button.'</div>';
1796 + echo '</div>';
1482 1797 }
1483 -
1484 - return $button;
1485 1798 }
1486 1799
1487 - protected function getLinks(string $account = '', string $pricing = ''): string
1800 + protected function getLinks(string $account = '', string $pricing = ''): void
1488 1801 {
1489 - if (empty($account) && empty($pricing)) return '';
1802 + if (empty($account) && empty($pricing)) {
1803 + return;
1804 + }
1490 1805
1491 - $html = '<div class="cell grid-x acym-grid-margin-x shrink acym_vcenter"><p class="cell shrink">'.acym_translation('ACYM_DONT_HAVE_ACCOUNT').'</p>';
1492 - if (!empty($account)) $html .= '<a target="_blank" class="cell shrink" href="'.$account.'">'.acym_translation('ACYM_CREATE_ONE').'</a>';
1493 - if (!empty($account) && !empty($pricing)) $html .= '<p class="cell shrink">'.strtolower(acym_translation('ACYM_OR')).'</p>';
1494 - if (!empty($pricing)) $html .= '<a target="_blank" class="cell shrink" href="'.$pricing.'">'.acym_translation('ACYM_CHECK_THEIR_PRICING').'</a>';
1495 - $html .= '</div>';
1496 -
1497 - 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>';
1498 1817 }
1499 1818
1500 1819 public function onAcymGetSendingMethodsSelected(array &$data): void
1501 1820 {
@@ -1534,8 +1853,9 @@
1534 1853 if (!in_array($ctrl, ['plugins', 'dynamics'])) return;
1535 1854
1536 1855 $task = acym_getVar('cmd', 'task', 'installed');
1537 1856
1857 + // Installed add-ons page or installed + editor
1538 1858 if (($ctrl === 'plugins' && $task === 'installed') || ($this->active && $ctrl === 'dynamics' && $task === 'trigger')) {
1539 1859 $this->initElementOptionsCustomView();
1540 1860 $this->initReplaceOptionsCustomView();
1541 1861 if ($customFields) $this->initCustomOptionsCustomView();
@@ -1572,8 +1892,9 @@
1572 1892 if (!acym_isExtensionActive('sitepress-multilingual-cms/sitepress.php')) return $link;
1573 1893
1574 1894 $languageCode = substr($this->emailLanguage, 0, 2);
1575 1895
1896 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML hook for integration.
1576 1897 return apply_filters('wpml_permalink', $link, $languageCode);
1577 1898 }
1578 1899
1579 1900 protected function replaceShortcode(string $content): string