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/Helpers/PluginHelper.php +724 -399 10.11.1 → 11.1.0 View file →
@@ -11,10 +11,16 @@
11 11 public string $name = 'content';
12 12 public string $wrappedText = '';
13 13 public string $contextLanguage;
14 14
15 + /*
16 + * Convert an array of elements into a table with multiple columns based on the $parameter variable
17 + * $parameter->displaytype = table by default
18 + * $parameter->cols = 1 by default
19 + */
15 20 public function getFormattedResult(array $elements, object $parameter): string
16 21 {
22 + //We do not add an extra table or whatever when there is a single element...
17 23 if (count($elements) < 2) {
18 24 return implode('', $elements);
19 25 }
20 26
@@ -109,8 +115,11 @@
109 115
110 116 return str_replace(['{equalwidth}'], [$equalwidth], $string);
111 117 }
112 118
119 + /**
120 + * This function will apply extra parameters such as part:first, ucfirst, strtolower to the $string
121 + */
113 122 public function formatString(&$replaceme, object $mytag): void
114 123 {
115 124 if (!empty($mytag->part)) {
116 125 $parts = explode(' ', $replaceme);
@@ -134,8 +143,9 @@
134 143 } elseif ($mytag->type == 'time') {
135 144 $replaceme = acym_getDate($replaceme, $mytag->format);
136 145 } elseif ($mytag->type == 'diff') {
137 146 try {
147 + //We have a date? Sure?
138 148 $date = $replaceme;
139 149 if (is_numeric($date)) {
140 150 $date = acym_getDate($replaceme, '%Y-%m-%d %H:%M:%S');
141 151 }
@@ -148,8 +158,9 @@
148 158 }
149 159 }
150 160 }
151 161
162 + //Two possibles keywords for that... we used to have "lower" and "upper" only but I added lowercase and uppercase as well on December 2014
152 163 if (!empty($mytag->lower) || !empty($mytag->lowercase)) {
153 164 $replaceme = function_exists('mb_strtolower') ? mb_strtolower($replaceme, 'UTF-8') : strtolower($replaceme);
154 165 }
155 166 if (!empty($mytag->upper) || !empty($mytag->uppercase)) {
@@ -160,8 +171,9 @@
160 171 }
161 172 if (!empty($mytag->ucfirst)) {
162 173 $replaceme = ucfirst($replaceme);
163 174 }
175 + //Remove a character at the end of the string...
164 176 if (isset($mytag->rtrim)) {
165 177 $replaceme = empty($mytag->rtrim) ? rtrim($replaceme) : rtrim($replaceme, $mytag->rtrim);
166 178 }
167 179 if (!empty($mytag->urlencode)) {
@@ -167,8 +179,9 @@
167 179 if (!empty($mytag->urlencode)) {
168 180 $replaceme = urlencode($replaceme);
169 181 }
170 182 if (!empty($mytag->substr)) {
183 + //the parameter |substr:4,6 will select the 6 characters after the 4.
171 184 $args = explode(',', $mytag->substr);
172 185 if (isset($args[1])) {
173 186 $replaceme = substr($replaceme, intval($args[0]), intval($args[1]));
174 187 } else {
@@ -186,8 +199,9 @@
186 199 }
187 200
188 201 public function replaceVideos(string &$text): void
189 202 {
203 + //Youtube videos
190 204 $text = preg_replace(
191 205 '#\[embed=videolink][^}]*youtube[^=]*=([^"/}]*)[^}]*}\[/embed]#i',
192 206 '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>',
193 207 $text
@@ -216,11 +230,13 @@
216 230
217 231 $text = preg_replace('#{vimeo}(https://vimeo.com/[^{]+){/vimeo}#Uis', '<iframe src="$1"></iframe>', $text);
218 232 $text = preg_replace('#{vimeo}([^{]+){/vimeo}#Uis', '<iframe src="https://player.vimeo.com/video/$1"></iframe>', $text);
219 233
234 + // Vimeo iframes
220 235 if (preg_match_all('#<iframe[^>]*src="[^"]*vimeo[^"]*/(\d+)([&/\?][^"]*)?"[^>]*>[^<]*</iframe>#Uis', $text, $matches)) {
221 236 foreach ($matches[1] as $key => $match) {
222 237 $hash = acym_fileGetContent('https://vimeo.com/api/v2/video/'.$match.'.php');
238 + // @ on purpose, the unserialize returns false if $hash isn't a serialized data, but it also throws a notice...
223 239 $hash = @unserialize($hash);
224 240 if (empty($hash)) continue;
225 241
226 242 if (strpos($matches[0][$key], ' width="') !== false) {
@@ -234,18 +250,22 @@
234 250 $thumbnail .= '&src='.urlencode('https://f.vimeocdn.com/p/images/crawler_play.png');
235 251
236 252 $text = str_replace(
237 253 $matches[0][$key],
238 - '<a target="_blank" href="'.acym_escape($hash[0]['url']).'"><img class="donotresize" alt="" src="'.acym_escape($thumbnail).'" /></a>',
254 + '<a target="_blank" href="'.esc_attr($hash[0]['url']).'"><img class="donotresize" alt="" src="'.esc_attr($thumbnail).'" /></a>',
239 255 $text
240 256 );
241 257 }
242 258 }
243 259
260 + //Other videos
244 261 $text = preg_replace('#\[embed=videolink][^}]*video":"([^"]*)[^}]*}\[/embed]#i', '<a target="_blank" href="$1"><img src="'.ACYM_IMAGES.'/video.png"/></a>', $text);
245 262 $text = preg_replace('#<video[^>]*src="([^"]*)"[^>]*>[^>]*</video>#i', '<a target="_blank" href="$1"><img src="'.ACYM_IMAGES.'/video.png"/></a>', $text);
246 263 }
247 264
265 + /**
266 + * Convert pictures base64 code into a real picture
267 + */
248 268 private function convertbase64pictures(string &$html): void
249 269 {
250 270 if (!preg_match_all('#<img[^>]*src=("data:image/([^;]{1,5});base64[^"]*")([^>]*)>#Uis', $html, $resultspictures)) {
251 271 return;
@@ -250,8 +270,9 @@
250 270 if (!preg_match_all('#<img[^>]*src=("data:image/([^;]{1,5});base64[^"]*")([^>]*)>#Uis', $html, $resultspictures)) {
251 271 return;
252 272 }
253 273
274 + //Just in case of... we will need it
254 275
255 276
256 277 $dest = ACYM_MEDIA.'resized'.DS;
257 278 acym_createDir($dest);
@@ -260,8 +281,9 @@
260 281 $picturl = ACYM_LIVE.str_replace(DS, '/', ACYM_MEDIA_FOLDER).'resized/'.$pictname;
261 282 $pictPath = $dest.$pictname;
262 283 $pictCode = trim($resultspictures[1][$i], '"');
263 284 if (file_exists($pictPath)) {
285 + //The picture is already there... lets use it then
264 286 $html = str_replace($pictCode, $picturl, $html);
265 287 continue;
266 288 }
267 289
@@ -282,8 +304,9 @@
282 304 $getfunction = 'ImageCreateFromPNG';
283 305 break;
284 306 }
285 307
308 + //The function does not exists or we didn't find the right function... we just skip that action
286 309 if (empty($getfunction) || !function_exists($getfunction)) {
287 310 continue;
288 311 }
289 312
@@ -296,8 +319,9 @@
296 319 imagealphablending($img, false);
297 320 imagesavealpha($img, false);
298 321 }
299 322
323 + //We display the result and then save it to avoid the issue with FTP configurations
300 324 ob_start();
301 325 switch ($extension) {
302 326 case 'gif':
303 327 $status = imagegif($img);
@@ -312,8 +336,9 @@
312 336 }
313 337 $imageContent = ob_get_clean();
314 338 $status = $status && acym_writeFile($pictPath, $imageContent);
315 339
340 + //we could not save or convert the picture, we skip it
316 341 if (!$status) {
317 342 continue;
318 343 }
319 344 $html = str_replace($pictCode, $picturl, $html);
@@ -319,28 +344,36 @@
319 344 $html = str_replace($pictCode, $picturl, $html);
320 345 }
321 346 }
322 347
348 + //This function will remove or replace code we should not have in an html view.
323 349 public function cleanHtml(string &$html): void
324 350 {
325 351 $this->convertbase64pictures($html);
326 352
353 + //add line-height: 0px; in the TR style when there is only images in the cell, not extra text (new issue with gmail)
354 + //handle a line-height automatically for <tr> <td> <img...></td></tr>
327 355 $pregreplace = [];
328 356 $pregreplace['#<tr([^>"]*>([^<]*<td[^>]*>[ \n\s]*<img[^>]*>[ \n\s]*</ *td[^>]*>[ \n\s]*)*</ *tr)#Uis'] = '<tr style="line-height: 0px;" $1';
329 357 $pregreplace['#<td(((?!style|>).)*>[ \n\s]*(<a[^>]*>)?[ \n\s]*<img[^>]*>[ \n\s]*(</a[^>]*>)?[ \n\s]*</ *td)#Uis'] = '<td style="line-height: 0px;" $1';
330 358
359 + //No number tab system {tab=...}{/tabs} or {tab }{/tabs} and jcomments system
331 360 $pregreplace['#{tab[ =][^}]*}#is'] = '';
332 361 $pregreplace['#{/tabs}#is'] = '';
333 362 $pregreplace['#{jcomments\s+(on|off|lock)}#is'] = '';
334 363
364 + //Remove the JS...
335 365 $pregreplace["#(onmouseout|onmouseover|onclick|onfocus|onload|onblur) *= *\"(?:(?!\").)*\"#Ui"] = '';
336 366 $pregreplace["#< *script(?:(?!< */ *script *>).)*< */ *script *>#Uis"] = '';
337 367 $pregreplace["#< *iframe(?:(?!< */ *iframe *>).)*< */ *iframe *>#Uis"] = '';
338 368
369 + // May God punish every person using Outlook, or imposing this monstrosity for his employees
339 370 $pregreplace['#(<p style=")([^>]*>\s*<img *[^>]*margin-left: auto; margin-right: auto;[^>]*>\s*</p>)#Uis'] = '$1text-align: center;$2';
371 + // Outlook doesn't handle webp images
340 372 $pregreplace['#(<img [^>]*src="[^"]+\.webp"[^>]*>)#Uis'] = '<!--[if !mso]><!-->$1<!--<![endif]-->';
341 373
342 374 $newbody = preg_replace(array_keys($pregreplace), $pregreplace, $html);
375 + //we do it in two steps as this regex can break the page
343 376 if (!empty($newbody)) {
344 377 $html = $newbody;
345 378 }
346 379
@@ -354,8 +387,11 @@
354 387 {
355 388 return "src='".str_replace(' ', '%20', $matches[1])."'";
356 389 }
357 390
391 + /*
392 + * Replace tags in all the email variables where it is possible, in text version or html version
393 + */
358 394 public function replaceTags(object &$email, array $tags, bool $html = false): void
359 395 {
360 396 if (empty($tags)) return;
361 397
@@ -414,8 +450,9 @@
414 450 }
415 451 $safePregValue = str_replace('$', '\$', $value);
416 452
417 453 foreach ($codes as $oneCode) {
454 + // Dtext specific syntax
418 455 $text = preg_replace(
419 456 '#<span[^>]+'.preg_quote($oneCode, '#').'.+</em>[^<]*</span>#Uis',
420 457 $safePregValue,
421 458 $text
@@ -420,8 +457,9 @@
420 457 $safePregValue,
421 458 $text
422 459 );
423 460
461 + // Dcontent specific syntax
424 462 $text = preg_replace(
425 463 '#(<tr[^>]+)data-dynamic="'.preg_quote($oneCode, '#').'"([^>]+>[^<]*<td[^>]*>).+</i>[^<]*</td>[^<]*</tr>#Uis',
426 464 '${1}${2}'.$safePregValue.'</td></tr>',
427 465 $text
@@ -426,8 +464,9 @@
426 464 '${1}${2}'.$safePregValue.'</td></tr>',
427 465 $text
428 466 );
429 467
468 + // If the code was inserted directly in the email (in the subject for example, or a copy-paste)
430 469 $text = str_replace($oneCode, $value, $text);
431 470 }
432 471 }
433 472 }
@@ -434,13 +473,18 @@
434 473
435 474 return $text;
436 475 }
437 476
477 + /**
478 + * This function extracts tags from the mail by checking the subject,body and altbody and returns an array of tag objects
479 + * tagfamily is "vmproduct" for example to handle tags such as {vmproduct:23|file|price}
480 + */
438 481 public function extractTags(object $email, string $tagfamily): array
439 482 {
440 483 $results = [];
441 484
442 485 $match = '#(?:{|%7B)'.$tagfamily.'(?:%3A|\\:)(.*)(?:}|%7D)#Ui';
486 + //If you add a variable there, don't forget to add it in the replaceTags function as well!
443 487 $variables = [
444 488 'subject',
445 489 'AltBody',
446 490 'body',
@@ -476,12 +520,14 @@
476 520 }
477 521 }
478 522 } else {
479 523 $found = preg_match_all($match, $email->$var, $results[$var]) || $found;
524 + //we unset the results so that we won't handle it later... it will save some memory and processing
480 525 if (empty($results[$var][0])) unset($results[$var]);
481 526 }
482 527 }
483 528
529 + //If we didn't find anything...
484 530 if (!$found) {
485 531 return [];
486 532 }
487 533
@@ -487,8 +533,9 @@
487 533
488 534 $tags = [];
489 535 foreach ($results as $var => $allresults) {
490 536 foreach ($allresults[0] as $i => $oneTag) {
537 + //Don't need to process twice a tag we already have!
491 538 if (isset($tags[$oneTag])) {
492 539 continue;
493 540 }
494 541 $tags[$oneTag] = $this->extractTag($allresults[1][$i]);
@@ -497,12 +544,15 @@
497 544
498 545 return $tags;
499 546 }
500 547
548 + /**
549 + * Converts a tag 23|file:myfile|price into an object.
550 + */
501 551 public function extractTag(string $oneTag): \stdClass
502 552 {
503 553 $oneTag = str_replace(['[time]+', '[time]-'], [urlencode('[time]+'), urlencode('[time]-')], $oneTag);
504 - $arguments = explode('|', strip_tags(urldecode($oneTag)));
554 + $arguments = explode('|', acym_stripTags(urldecode($oneTag)));
505 555 $tag = new \stdClass();
506 556 $tag->id = $arguments[0];
507 557 $tag->default = '';
508 558 for ($i = 1, $a = count($arguments); $i < $a; $i++) {
@@ -511,8 +561,9 @@
511 561 if (empty($arg0)) continue;
512 562
513 563 if (isset($args[1])) {
514 564 $tag->$arg0 = $args[1];
565 + //We may have an extra parameter, especially for date format.
515 566 if (isset($args[2])) {
516 567 $tag->{$args[0]} .= ':'.$args[2];
517 568 }
518 569 } else {
@@ -522,8 +573,11 @@
522 573
523 574 return $tag;
524 575 }
525 576
577 + /**
578 + * Wrap the text using the wrapValue
579 + */
526 580 public function wrapText(string $text, object $tag): string
527 581 {
528 582 if (empty($tag->wrap)) {
529 583 return $text;
@@ -532,14 +586,14 @@
532 586 $tag->wrap = intval($tag->wrap);
533 587
534 588 $newText = str_replace(['&lt', '&gt'], ['<', '>'], $text);
535 589
536 - if (mb_strlen(strip_tags($newText)) <= $tag->wrap) {
590 + if (mb_strlen(acym_stripTags($newText)) <= $tag->wrap) {
537 591 return $newText;
538 592 }
539 593
540 594 if (!class_exists('DOMDocument', false)) {
541 - return mb_substr(strip_tags($newText), 0, $tag->wrap).'…';
595 + return mb_substr(acym_stripTags($newText), 0, $tag->wrap).'…';
542 596 }
543 597
544 598 if (function_exists('libxml_use_internal_errors')) {
545 599 libxml_use_internal_errors(true);
@@ -561,8 +615,9 @@
561 615 {
562 616 for ($i = 0; $i < $node->childNodes->length; $i++) {
563 617 $child = $node->childNodes->item($i);
564 618
619 + // Text node
565 620 if ($child->nodeType === XML_TEXT_NODE) {
566 621 $text = $child->nodeValue;
567 622 $len = mb_strlen($text);
568 623
@@ -568,16 +623,20 @@
568 623
569 624 if ($count + $len > $limit) {
570 625 $remaining = $limit - $count;
571 626
627 + // Take the max allowed slice
572 628 $slice = mb_substr($text, 0, $remaining);
573 629
630 + // Try to cut at a word boundary (spaces or punctuation)
574 631 if (preg_match('/^(.+?)([\s\p{P}]+)[^\s\p{P}]*$/u', $slice, $m)) {
575 632 $slice = rtrim($m[1]);
576 633 }
577 634
635 + // Final assignment
578 636 $child->nodeValue = $slice.'…';
579 637
638 + // Remove remaining siblings
580 639 while ($child->nextSibling) {
581 640 $node->removeChild($child->nextSibling);
582 641 }
583 642
@@ -586,8 +645,9 @@
586 645
587 646 $count += $len;
588 647 }
589 648
649 + // Element node → recurse
590 650 if ($child->hasChildNodes() && $this->truncateNode($dom, $child, $limit, $count)) {
591 651 while ($child->nextSibling) {
592 652 $node->removeChild($child->nextSibling);
593 653 }
@@ -598,10 +658,23 @@
598 658
599 659 return false;
600 660 }
601 661
662 + /**
663 + * Returns a formatted version of the passed content
664 + *
665 + * TOP_LEFT : title on top, image with float left and covering description
666 + * TOP_RIGHT : title on top, image with float rigth and covering description
667 + * TITLE_IMG : image on title's left on top, description below
668 + * TITLE_IMG_RIGHT : image on title's right on top, description below
669 + * CENTER_IMG : title on top, image on one line and description below
670 + * TOP_IMG : image on top, then title and description
671 + * COL_LEFT : image on left column, title and description on right column
672 + * COL_RIGHT : image on right column, title and description on left column
673 + */
602 674 public function getStandardDisplay(object $format): string
603 675 {
676 + // By default, put float left on the picture
604 677 if (empty($format->tag->format)) {
605 678 $format->tag->format = 'TOP_LEFT';
606 679 }
607 680 if (!in_array($format->tag->format, ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT'])) {
@@ -619,8 +692,9 @@
619 692 if (!empty($format->tag->invert) && !empty($invertValues[$format->tag->format])) {
620 693 $format->tag->format = $invertValues[$format->tag->format];
621 694 }
622 695
696 + // Get the image with float left/right or no style
623 697 $image = '';
624 698 if (!empty($format->imagePath)) {
625 699 $style = '';
626 700 $linkStyle = '';
@@ -643,15 +717,16 @@
643 717
644 718 preg_match('#src="([^"]+)"#Uis', $format->imagePath, $matches);
645 719 if (!empty($matches[1])) $format->imagePath = $matches[1];
646 720 $altImage = !empty($format->altImage) ? $format->altImage : '';
647 - $image = '<img class="content_main_image" alt="'.acym_escape($altImage).'" src="'.$format->imagePath.'" '.$style.' />';
721 + $image = '<img class="content_main_image" alt="'.esc_attr($altImage).'" src="'.$format->imagePath.'" '.$style.' />';
648 722
649 723 if (!empty($format->imageCaption) && !in_array($format->tag->format, ['TITLE_IMG', 'TITLE_IMG_RIGHT'])) {
650 - $image .= '<p class="content_main_image_caption">'.acym_escape($format->imageCaption).'</p>';
724 + $image .= '<p class="content_main_image_caption">'.esc_html($format->imageCaption).'</p>';
651 725 }
652 726 }
653 727
728 + // If TITLE_IMG, add the image to the title
654 729 $result = '';
655 730 if (in_array($format->tag->format, ['TITLE_IMG', 'TITLE_IMG_RIGHT'])) {
656 731 $format->title = $image.$format->title;
657 732 $image = '';
@@ -660,13 +735,15 @@
660 735 if (!empty($format->link) && !empty($image) && !empty($format->tag->clickableimg)) {
661 736 $image = '<a target="_blank" href="'.$format->link.'" '.$linkStyle.'>'.$image.'</a>';
662 737 }
663 738
739 + // If the image should be displayed before the title, do it
664 740 if ($format->tag->format === 'TOP_IMG' && !empty($image)) {
665 741 $result = $image;
666 742 $image = '';
667 743 }
668 744
745 + // If we want to keep the left/right column for the image, put the whole article in a table
669 746 if (in_array($format->tag->format, ['COL_LEFT', 'COL_RIGHT'])) {
670 747 $maxWidth = empty($format->tag->maxwidth) ? '' : ' width: '.$format->tag->maxwidth.'px;';
671 748 if (empty($image)) {
672 749 $format->tag->format = 'TOP_LEFT';
@@ -678,8 +755,9 @@
678 755 }
679 756 }
680 757 }
681 758
759 + // Display the title
682 760 if (!empty($format->title)) {
683 761 if (!empty($format->link) && !empty($format->tag->clickable)) {
684 762 if (empty($format->tag->type) || $format->tag->type !== 'title') {
685 763 $format->title = '<h2 class="acym_title">'.$format->title.'</h2>';
@@ -711,8 +789,9 @@
711 789
712 790
713 791 $rowText = '<div class="acydescription">';
714 792 $endRow = '</div><br />';
793 + // Display the main content based on the chosen format
715 794 if (in_array($format->tag->format, ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'TOP_IMG'])) {
716 795 if (!empty($image) || !empty($format->description)) {
717 796 $result .= $rowText.$image.$format->description.$endRow;
718 797 }
@@ -734,8 +813,9 @@
734 813 }
735 814 $result .= '</td></tr></table>';
736 815 }
737 816
817 + // Add the custom fields on 1 or 2 columns, with or without the labels
738 818 if (!empty($format->customFields)) {
739 819 $result .= '<table style="width:100%;" class="customfieldsarea"><tr>';
740 820
741 821 $format->cols = empty($format->tag->nbcols) ? 1 : intval($format->tag->nbcols);
@@ -762,8 +842,9 @@
762 842
763 843 $result .= '</tr></table>';
764 844 }
765 845
846 + // Add what should be displayed at the end (share buttons, read more link, etc...)
766 847 if (!empty($format->afterArticle)) {
767 848 $result .= $format->afterArticle;
768 849 }
769 850
@@ -769,8 +850,11 @@
769 850
770 851 return $result;
771 852 }
772 853
854 + /**
855 + * Resizes or removes the pictures according to the parameters
856 + */
773 857 public function managePicts(object $tag, string $result): string
774 858 {
775 859 if (!isset($tag->pict)) {
776 860 return $result;
@@ -793,474 +877,712 @@
793 877 }
794 878
795 879 public function displayOptions(array $options, string $dynamicIdentifier, string $type = 'individual', $defaultValues = null): void
796 880 {
797 - $suffix = preg_replace('[^a-zA-Z0-9]', '_', $dynamicIdentifier);
881 + $suffix = preg_replace('#[^a-zA-Z0-9]#U', '_', $dynamicIdentifier);
798 882 $updateFunction = 'updateDynamic'.$suffix;
799 883
800 - $outputStructure = [
801 - 'topOptions' => [],
802 - 'options' => [],
803 - ];
804 - $jsOptionsMerge = [];
884 + $jsOptions = [];
885 + $topOptions = [];
886 + $sections = [];
805 887
888 + // ---- Phase 1: classify options + collect the JS (in original loop order) ----
806 889 foreach ($options as $option) {
807 - $currentLabel = $option['title'];
808 - $currentOption = '';
809 -
810 890 if (isset($defaultValues->{$option['name']})) {
811 891 $option['default'] = $defaultValues->{$option['name']};
812 892 }
813 893
814 - if ($option['type'] === 'pictures') {
815 - $displayedPictures = $option['default'] ?? 'resized';
816 - if (isset($defaultValues->pict)) $displayedPictures = $defaultValues->pict;
817 - $resizeDisplay = 'resized' === $displayedPictures ? '' : 'style="display: none;"';
818 - $maxWidth = $defaultValues->maxwidth ?? 150;
819 - $maxHeight = $defaultValues->maxheight ?? 150;
894 + // Preserve the early skip: never rendered, no JS collected.
895 + if (
896 + $option['type'] === 'boolean'
897 + && $option['name'] === 'autologin'
898 + && $this->config->get('autologin_urls', 0) != 1
899 + ) {
900 + continue;
901 + }
820 902
821 - $valImages = [];
822 - $valImages[] = acym_selectOption('1', 'ACYM_YES');
823 - $valImages[] = acym_selectOption('resized', 'ACYM_RESIZED');
824 - $valImages[] = acym_selectOption('0', 'ACYM_NO');
825 - $currentOption .= '<div class="cell large-5 acym_plugin_field">'.acym_translation('ACYM_DISPLAY').'</div>';
826 - $currentOption .= '<div class="cell large-7">'.acym_radio(
827 - $valImages,
828 - 'pict'.$suffix,
829 - $displayedPictures,
830 - ['onclick' => $updateFunction.'();'],
831 - ['containerClass' => 'dcontent_pictures'],
832 - !acym_isAdmin()
833 - ).'</div>';
834 - $currentOption .= '<div id="pictsize'.$suffix.'" class="cell grid-x margin-y margin-top-1" '.$resizeDisplay.'>
835 - <div class="cell large-5 acym_plugin_field">'.acym_translation('ACYM_MAX_WIDTH').'</div>
836 - <div class="cell large-7">
837 - <input class="intext_input" name="pictwidth'.$suffix.'" type="number" onchange="'.$updateFunction.'();" value="'.intval($maxWidth).'"/>
838 - </div>
839 - <div class="cell large-5 acym_plugin_field">'.acym_translation('ACYM_MAX_HEIGHT').'</div>
840 - <div class="cell large-7">
841 - <input class="intext_input" name="pictheight'.$suffix.'" type="number" onchange="'.$updateFunction.'();" value="'.intval($maxHeight).'"/>
842 - </div>
843 - </div>';
844 - if (!empty($option['caption'])) {
845 - $currentOption .= '<div class="cell grid-x margin-top-1">';
846 - $currentOption .= '<label class="cell large-5 acym_plugin_field">'.acym_translation('ACYM_CAPTION').'</label>';
847 - $currentOption .= acym_radio(
848 - [
849 - acym_selectOption('1', 'ACYM_YES'),
850 - acym_selectOption('0', 'ACYM_NO'),
851 - ],
852 - 'caption'.$suffix,
853 - $defaultValues->caption ?? '0',
854 - ['onclick' => $updateFunction.'();'],
855 - ['containerClass' => 'cell large-7']
856 - );
857 - $currentOption .= '</div>';
903 + $jsOptions[] = $option;
858 904
859 - $jsOptionsMerge[] = 'otherinfo += "| caption:" + jQuery(\'input[name="caption'.$suffix.'"]:checked\').val();';
905 + $isTop = !empty($option['main']) || in_array($option['type'], ['pictures', 'checkbox'], true);
906 +
907 + if ($isTop) {
908 + $topOptions[$option['title']] = ['kind' => 'field', 'option' => $option];
909 +
910 + // The format block is attached right after the "display" checkbox.
911 + if (
912 + $option['type'] === 'checkbox'
913 + && $option['title'] === 'ACYM_DISPLAY'
914 + && (!isset($option['format']) || $option['format'])
915 + ) {
916 + $topOptions['ACYM_FORMAT'] = ['kind' => 'format', 'option' => $option];
860 917 }
918 + continue;
919 + }
861 920
862 - $jsOptionsMerge[] = '
863 - var _pictVal'.$suffix.' = jQuery(\'input[name="pict'.$suffix.'"]:checked\').val();
864 - otherinfo += "| pict:" + _pictVal'.$suffix.';
865 -
866 - if(_pictVal'.$suffix.' == "resized"){
867 - jQuery("#pictsize'.$suffix.'").show();
868 - otherinfo += "| maxwidth:" + jQuery(\'input[name="pictwidth'.$suffix.'"]\').val();
869 - otherinfo += "| maxheight:" + jQuery(\'input[name="pictheight'.$suffix.'"]\').val();
870 - }else{
871 - jQuery("#pictsize'.$suffix.'").hide();
872 - }';
873 - } elseif ($option['type'] === 'checkbox') {
874 - if (!empty($option['default'])) {
875 - $checkedValues = explode(',', $option['default']);
876 - foreach ($option['options'] as $key => $oneOption) {
877 - $oneOption[1] = in_array($key, $checkedValues);
878 - $option['options'][$key] = $oneOption;
879 - }
880 - }
921 + $section = empty($option['section']) ? 'ACYM_OTHER_OPTIONS' : $option['section'];
922 + $sections[$section][$option['name']] = $option;
923 + }
881 924
882 - $currentOption .= '<div class="cell grid-x">';
883 - foreach ($option['options'] as $value => $title) {
884 - $currentOption .= '<div class="cell medium-6" '.(empty($title[2]) ? '' : $title[2]).'>
885 - <input type="checkbox" name="'.acym_escape($option['name'].$suffix).'" value="'.acym_escape($value).'" id="'.acym_escape(
886 - $value.$suffix
887 - ).'" onclick="'.$updateFunction.'();" '.($title[1] ? 'checked="checked"' : '').'/>
888 - <label style="margin-left:5px" for="'.acym_escape($value.$suffix).'">'.acym_translation($title[0]).'</label>
889 - </div>';
890 - }
891 - $currentOption .= '</div>';
925 + // "Other Options" is always rendered last.
926 + if (isset($sections['ACYM_OTHER_OPTIONS'])) {
927 + $otherOptions = $sections['ACYM_OTHER_OPTIONS'];
928 + unset($sections['ACYM_OTHER_OPTIONS']);
929 + $sections['ACYM_OTHER_OPTIONS'] = $otherOptions;
930 + }
892 931
893 - if (empty($option['separator'])) $option['separator'] = ',';
932 + // ---- Phase 2: render (echo directly, in the original visual order) ----
933 + foreach ($topOptions as $headerLabel => $entry) {
934 + $this->displayPanelHeader($headerLabel);
935 + echo '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container grid-x">';
936 + if ($entry['kind'] === 'format') {
937 + $this->displayFormatPanel($suffix, $updateFunction, $type, $defaultValues);
938 + } else {
939 + $this->displayOptionField($entry['option'], $suffix, $updateFunction, $defaultValues);
940 + }
941 + echo '</div>';
942 + }
894 943
895 - $jsOptionsMerge[] = 'var _checked'.$option['name'].$suffix.' = [];
896 - jQuery("input:checkbox[name='.$option['name'].$suffix.']:checked").each(function(){
897 - _checked'.$option['name'].$suffix.'.push(jQuery(this).val());
898 - });
899 - if(_checked'.$option['name'].$suffix.'.length) otherinfo += "| '.$option['name'].':" + _checked'.$option['name'].$suffix.'.join("'.$option['separator'].'");';
900 - } elseif ($option['type'] === 'boolean') {
901 - if ($option['name'] === 'autologin' && $this->config->get('autologin_urls', 0) != 1) {
902 - continue;
903 - }
944 + foreach ($sections as $sectionName => $sectionOptions) {
945 + $this->displayPanelHeader($sectionName);
946 + echo '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container grid-x">';
947 + foreach ($sectionOptions as $option) {
948 + echo '<div class="cell grid-x margin-bottom-1">';
949 + $this->displayFieldLabel($option, $suffix);
950 + echo '<div class="cell large-7">';
951 + $this->displayOptionField($option, $suffix, $updateFunction, $defaultValues);
952 + echo '</div>';
953 + echo '</div>';
954 + }
955 + echo '</div>';
956 + }
904 957
905 - $currentOption .= acym_boolean(
906 - $option['name'].$suffix,
907 - $option['default'],
908 - $option['name'].$suffix,
909 - ['onclick' => $updateFunction.'();']
910 - );
958 + // ---- Phase 3: the inline script + individual extras ----
959 + $this->displayDynamicScript($jsOptions, $suffix, $updateFunction, $dynamicIdentifier, $type, $defaultValues);
911 960
912 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]:checked\').val();';
913 - } elseif ($option['type'] === 'radio') {
914 - $radioOptions = [];
915 - foreach ($option['options'] as $value => $title) {
916 - $radioOptions[] = acym_selectOption($value, $title);
917 - }
961 + if ($type === 'individual') {
962 + acym_trigger('displayCustomViewEditor', [], 'plgAcym'.ucfirst($dynamicIdentifier));
963 + echo '<input type="hidden" id="acym__dynamic__update__function" value="'.esc_attr($updateFunction).'">';
964 + }
965 + }
918 966
919 - $currentOption .= acym_radio(
920 - $radioOptions,
921 - $option['name'].$suffix,
922 - $option['default'],
923 - ['onclick' => $updateFunction.'();'],
924 - ['pluginMode' => true],
925 - !acym_isAdmin()
926 - );
927 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]:checked\').val();';
928 - } elseif ($option['type'] === 'select') {
929 - $selectOptions = [];
930 - foreach ($option['options'] as $value => $title) {
931 - if (is_object($title)) {
932 - $selectOptions[] = acym_selectOption($title->value, $title->text);
933 - } else {
934 - $selectOptions[] = acym_selectOption($value, $title);
935 - }
936 - }
967 + private function displayPanelHeader(string $labelKey): void
968 + {
969 + echo '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">';
970 + echo esc_html(acym_translation($labelKey));
971 + echo '<i class="acymicon-keyboard-arrow-up"></i>';
972 + echo '</p>';
973 + }
937 974
938 - $default = empty($option['default']) ? null : $option['default'];
939 - if (!empty($default) && strpos($default, ',')) [$default, $defaultOrder] = explode(',', $default);
975 + private function displayFieldLabel(array $option, string $suffix): void
976 + {
977 + echo '<label class="cell large-5 acym_plugin_field acym_plugin_field_'.esc_attr($option['type']).'" for="'.esc_attr($option['name'].$suffix).'">';
978 + echo esc_html(acym_translation($option['title']));
979 + if (!empty($option['tooltip'])) {
980 + echo '&nbsp;';
981 + acym_info(
982 + [
983 + 'textShownInTooltip' => $option['tooltip'],
984 + 'classIcon' => 'acym_plugin_field_'.$option['name'],
985 + ]
986 + );
987 + }
988 + echo '</label>';
989 + }
940 990
941 - $attributes = [
942 - 'onchange' => $updateFunction.'();',
943 - 'id' => $option['name'].$suffix,
944 - ];
945 - if ($option['name'] === 'order') {
946 - $attributes['class'] = 'acym__dynamics__ordering__select';
991 + private function displayOptionField(array $option, string $suffix, string $updateFunction, $defaultValues): void
992 + {
993 + switch ($option['type']) {
994 + case 'pictures':
995 + $this->displayPicturesField($option, $suffix, $updateFunction, $defaultValues);
996 + break;
997 + case 'checkbox':
998 + $this->displayCheckboxField($option, $suffix, $updateFunction);
999 + break;
1000 + case 'boolean':
1001 + acym_boolean($option['name'].$suffix, $option['default'], $option['name'].$suffix, ['onclick' => $updateFunction.'();']);
1002 + break;
1003 + case 'radio':
1004 + $this->displayRadioField($option, $suffix, $updateFunction);
1005 + break;
1006 + case 'select':
1007 + $this->displaySelectField($option, $suffix, $updateFunction);
1008 + break;
1009 + case 'multiselect':
1010 + $this->displayMultiselectField($option, $suffix, $updateFunction);
1011 + break;
1012 + case 'text':
1013 + $default = $option['default'] ?? '';
1014 + $class = empty($option['class']) ? 'acym_plugin_text_field' : $option['class'];
1015 + echo '<input type="text"
1016 + name="'.esc_attr($option['name'].$suffix).'"
1017 + id="'.esc_attr($option['name'].$suffix).'"
1018 + onchange="'.esc_attr($updateFunction).'();"
1019 + value="'.esc_attr($default).'"
1020 + class="'.esc_attr($class).'"';
1021 + if (!empty($option['placeholder'])) {
1022 + echo ' placeholder="'.esc_attr($option['placeholder']).'"';
947 1023 }
948 - $currentOption .= acym_select(
949 - $selectOptions,
950 - $option['name'].$suffix,
951 - $default,
952 - $attributes
1024 + echo '/>';
1025 + break;
1026 + case 'number':
1027 + $class = empty($option['class']) ? 'acym_plugin_text_field' : $option['class'];
1028 + echo '<input type="number"
1029 + min="'.esc_attr(empty($option['min']) ? '0' : $option['min']).'"
1030 + id="'.esc_attr($option['name'].$suffix).'"
1031 + onchange="'.esc_attr($updateFunction).'();"
1032 + value="'.intval($option['default']).'"
1033 + name="'.esc_attr($option['name'].$suffix).'"
1034 + class="'.esc_attr($class).'" ';
1035 + if (!empty($option['max'])) {
1036 + echo ' max="'.esc_attr($option['max']).'"';
1037 + }
1038 + echo ' />';
1039 + break;
1040 + case 'intextfield':
1041 + $inputType = (!empty($option['isNumber']) && $option['isNumber'] === 1) ? 'number' : 'text';
1042 + echo wp_kses(
1043 + acym_translationSprintf(
1044 + $option['text'],
1045 + '<input type="'.esc_attr($inputType).'"
1046 + name="'.esc_attr($option['name'].$suffix).'"
1047 + id="'.esc_attr($option['name'].$suffix).'"
1048 + class="intext_input"
1049 + value="'.esc_attr($option['default']).'"
1050 + onchange="'.esc_attr($updateFunction).'();"/>'
1051 + ),
1052 + [
1053 + 'input' => [
1054 + 'type' => true,
1055 + 'name' => true,
1056 + 'id' => true,
1057 + 'class' => true,
1058 + 'value' => true,
1059 + 'onchange' => true,
1060 + ],
1061 + ]
953 1062 );
1063 + break;
1064 + case 'date':
1065 + $relativeTime = empty($option['relativeDate']) ? '-' : $option['relativeDate'];
1066 + $dateDefault = $option['default'];
1067 + if (!empty($dateDefault) && !is_numeric($dateDefault) && strpos($dateDefault, '[time]') === false) {
1068 + $dateDefault = strtotime($dateDefault);
1069 + }
1070 + echo wp_kses(
1071 + acym_dateField($option['name'].$suffix, $dateDefault, '', ' onchange="'.esc_attr($updateFunction).'();"', $relativeTime),
1072 + SecurityHelper::ALLOWED_HTML_DATE
1073 + );
1074 + break;
1075 + case 'language':
1076 + $this->displayLanguageField($option, $suffix, $updateFunction);
1077 + break;
1078 + case 'custom':
1079 + $this->displayCustomOptionOutput($option);
1080 + break;
1081 + }
1082 + }
954 1083
955 - if ($option['name'] === 'order') {
956 - $dirs = [
957 - 'desc' => acym_translation('ACYM_DESC'),
958 - 'asc' => acym_translation('ACYM_ASC'),
959 - ];
960 - if (empty($defaultOrder)) $defaultOrder = empty($option['defaultdir']) ? null : $option['defaultdir'];
961 - $currentOption .= ' '.acym_select(
962 - $dirs,
963 - 'orderdir'.$suffix,
964 - $defaultOrder,
965 - [
966 - 'onchange' => $updateFunction.'();',
967 - 'style' => 'width: 115px;',
968 - 'class' => 'acym__dynamics__ordering__select',
969 - ]
970 - );
1084 + private function displayCustomOptionOutput(array $option): void
1085 + {
1086 + if (empty($option['output'])) {
1087 + return;
1088 + }
971 1089
972 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'[name="'.$option['name'].$suffix.'"]\').val() + "," + jQuery(\'[name="orderdir'.$suffix.'"]\').val();';
973 - } else {
974 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'[name="'.$option['name'].$suffix.'"]\').val();';
975 - }
976 - } elseif ($option['type'] === 'multiselect') {
977 - $selectOptions = [];
978 - foreach ($option['options'] as $value => $title) {
979 - $selectOptions[] = acym_selectOption($value, $title);
980 - }
1090 + if (!is_string($option['output']) && is_callable($option['output'])) {
1091 + ($option['output'])();
1092 + }
1093 + }
981 1094
1095 + private function displayPicturesField(array $option, string $suffix, string $updateFunction, $defaultValues): void
1096 + {
1097 + $displayedPictures = $option['default'] ?? 'resized';
1098 + if (isset($defaultValues->pict)) {
1099 + $displayedPictures = $defaultValues->pict;
1100 + }
1101 + $maxWidth = $defaultValues->maxwidth ?? 150;
1102 + $maxHeight = $defaultValues->maxheight ?? 150;
982 1103
983 - if (!isset($option['default'])) $option['default'] = [];
984 - if (!is_array($option['default'])) $option['default'] = explode(',', $option['default']);
1104 + $valImages = [
1105 + acym_selectOption('1', 'ACYM_YES'),
1106 + acym_selectOption('resized', 'ACYM_RESIZED'),
1107 + acym_selectOption('0', 'ACYM_NO'),
1108 + ];
985 1109
986 - $currentOption .= acym_selectMultiple(
987 - $selectOptions,
988 - $option['name'].$suffix,
989 - $option['default'],
990 - ['onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix]
991 - );
1110 + echo '<div class="cell large-5 acym_plugin_field">';
1111 + echo esc_html(acym_translation('ACYM_DISPLAY'));
1112 + echo '</div>';
1113 + echo '<div class="cell large-7">';
1114 + acym_radio(
1115 + $valImages,
1116 + 'pict'.$suffix,
1117 + $displayedPictures,
1118 + ['onclick' => $updateFunction.'();'],
1119 + ['containerClass' => 'dcontent_pictures'],
1120 + !acym_isAdmin()
1121 + );
1122 + echo '</div>';
992 1123
993 - $jsOptionsMerge[] = '
994 - var theMultiSelect = document.querySelector(\'[name="'.$option['name'].$suffix.'[]"]\');
995 - var selectedOptions = [];
996 - for(var i = 0 ; i < theMultiSelect.length ; i++){
997 - if(theMultiSelect[i].selected){
998 - selectedOptions.push(theMultiSelect[i].value);
999 - }
1000 - }
1001 - otherinfo += "| '.$option['name'].':" + selectedOptions.join(",");';
1002 - } elseif ($option['type'] === 'text') {
1003 - if (!isset($option['default'])) $option['default'] = '';
1004 - $class = empty($option['class']) ? 'acym_plugin_text_field' : $option['class'];
1005 - $placeholder = empty($option['placeholder']) ? '' : ' placeholder="'.acym_escape($option['placeholder']).'"';
1006 - $currentOption .= '<input
1007 - type="text"
1008 - name="'.$option['name'].$suffix.'"
1009 - id="'.$option['name'].$suffix.'"
1010 - onchange="'.$updateFunction.'();"
1011 - value="'.acym_escape($option['default']).'"
1012 - class="'.acym_escape($class).'" '.$placeholder.'/>';
1013 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]\').val();';
1014 - } elseif ($option['type'] === 'number') {
1015 - $min = empty($option['min']) ? ' min="0"' : ' min="'.$option['min'].'"';
1016 - $max = empty($option['max']) ? '' : ' max="'.$option['max'].'"';
1017 - $class = empty($option['class']) ? 'acym_plugin_text_field' : $option['class'];
1018 - $currentOption .= '<input type="number"'.$min.$max.' name="'.$option['name'].$suffix.'" id="'.$option['name'].$suffix.'" onchange="'.$updateFunction.'();" value="'.intval(
1019 - $option['default']
1020 - ).'" class="'.acym_escape($class).'" />';
1021 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]\').val();';
1022 - } elseif ($option['type'] === 'intextfield') {
1023 - $inputType = 'text';
1024 - if (!empty($option['isNumber']) && $option['isNumber'] === 1) $inputType = 'number';
1025 - $currentOption .= acym_translationSprintf(
1026 - $option['text'],
1027 - '<input type="'.$inputType.'" name="'.$option['name'].$suffix.'" id="'.$option['name'].$suffix.'" class="intext_input" value="'.acym_escape(
1028 - $option['default']
1029 - ).'" onchange="'.$updateFunction.'();"/>'
1030 - );
1031 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]\').val();';
1032 - } elseif ($option['type'] === 'date') {
1033 - $relativeTime = '-';
1034 - if (!empty($option['relativeDate'])) $relativeTime = $option['relativeDate'];
1035 - if (!empty($option['default']) && !is_numeric($option['default']) && false === strpos($option['default'], '[time]')) {
1036 - $option['default'] = strtotime($option['default']);
1037 - }
1038 - $currentOption .= acym_dateField($option['name'].$suffix, $option['default'], '', ' onchange="'.$updateFunction.'();"', $relativeTime);
1039 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]\').val();';
1040 - } elseif ($option['type'] === 'language') {
1041 - $languageOptions = [];
1042 - $languageOptions['any'] = acym_translation('ACYM_ANY');
1124 + echo '<div id="pictsize'.esc_attr($suffix).'" class="cell grid-x margin-y margin-top-1"';
1125 + if ('resized' !== $displayedPictures) {
1126 + echo ' style="display: none;"';
1127 + }
1128 + echo '>';
1129 + echo '<div class="cell large-5 acym_plugin_field">';
1130 + echo esc_html(acym_translation('ACYM_MAX_WIDTH'));
1131 + echo '</div>';
1132 + echo '<div class="cell large-7">';
1133 + echo '<input class="intext_input"
1134 + name="pictwidth'.esc_attr($suffix).'"
1135 + type="number"
1136 + onchange="'.esc_attr($updateFunction).'();"
1137 + value="'.intval($maxWidth).'"/>';
1138 + echo '</div>';
1139 + echo '<div class="cell large-5 acym_plugin_field">';
1140 + echo esc_html(acym_translation('ACYM_MAX_HEIGHT'));
1141 + echo '</div>';
1142 + echo '<div class="cell large-7">';
1143 + echo '<input class="intext_input"
1144 + name="pictheight'.esc_attr($suffix).'"
1145 + type="number"
1146 + onchange="'.esc_attr($updateFunction).'();"
1147 + value="'.intval($maxHeight).'"/>';
1148 + echo '</div>';
1149 + echo '</div>';
1043 1150
1044 - $languages = acym_getLanguages(true);
1045 - foreach ($languages as $language) {
1046 - $languageOptions[$language->language] = $language->name;
1047 - }
1151 + if (!empty($option['caption'])) {
1152 + echo '<div class="cell grid-x margin-top-1">';
1153 + echo '<label class="cell large-5 acym_plugin_field">';
1154 + echo esc_html(acym_translation('ACYM_CAPTION'));
1155 + echo '</label>';
1156 + acym_radio(
1157 + [
1158 + acym_selectOption('1', 'ACYM_YES'),
1159 + acym_selectOption('0', 'ACYM_NO'),
1160 + ],
1161 + 'caption'.$suffix,
1162 + $defaultValues->caption ?? '0',
1163 + ['onclick' => $updateFunction.'();'],
1164 + ['containerClass' => 'cell large-7']
1165 + );
1166 + echo '</div>';
1167 + }
1168 + }
1048 1169
1049 - if (empty($option['default'])) {
1050 - $option['default'] = acym_getVar('string', 'language');
1051 - if (acym_isMultilingual() && (empty($option['default']) || $option['default'] === 'main')) {
1052 - $option['default'] = $this->config->get('multilingual_default');
1053 - }
1170 + private function displayCheckboxField(array $option, string $suffix, string $updateFunction): void
1171 + {
1172 + if (!empty($option['default'])) {
1173 + $checkedValues = explode(',', $option['default']);
1174 + foreach ($option['options'] as $key => $oneOption) {
1175 + $oneOption[1] = in_array($key, $checkedValues);
1176 + $option['options'][$key] = $oneOption;
1177 + }
1178 + }
1179 +
1180 + echo '<div class="cell grid-x">';
1181 + foreach ($option['options'] as $value => $title) {
1182 + echo '<div class="cell medium-6" ';
1183 + if (!empty($title[2]) && is_array($title[2])) {
1184 + foreach ($title[2] as $attribute => $value) {
1185 + echo esc_attr($attribute).'="'.esc_attr($value).'"';
1054 1186 }
1187 + }
1188 + echo '>';
1189 + echo '<input type="checkbox"
1190 + name="'.esc_attr($option['name'].$suffix).'"
1191 + value="'.esc_attr($value).'"
1192 + id="'.esc_attr($value.$suffix).'"
1193 + onclick="'.esc_attr($updateFunction).'();" ';
1194 + checked((bool)$title[1]);
1195 + echo ' />';
1196 + echo '<label style="margin-left:5px" for="'.esc_attr($value.$suffix).'">';
1197 + echo esc_html(acym_translation($title[0]));
1198 + echo '</label>';
1199 + echo '</div>';
1200 + }
1201 + echo '</div>';
1202 + }
1055 1203
1056 - $currentOption .= acym_select(
1057 - $languageOptions,
1058 - $option['name'].$suffix,
1059 - empty($option['default']) ? null : $option['default'],
1060 - [
1061 - 'onchange' => $updateFunction.'();',
1062 - 'id' => $option['name'].$suffix,
1063 - ]
1064 - );
1204 + private function displayRadioField(array $option, string $suffix, string $updateFunction): void
1205 + {
1206 + $radioOptions = [];
1207 + foreach ($option['options'] as $value => $title) {
1208 + $radioOptions[] = acym_selectOption($value, $title);
1209 + }
1210 + acym_radio(
1211 + $radioOptions,
1212 + $option['name'].$suffix,
1213 + $option['default'],
1214 + ['onclick' => $updateFunction.'();'],
1215 + ['pluginMode' => true],
1216 + !acym_isAdmin()
1217 + );
1218 + }
1065 1219
1066 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'[name="'.$option['name'].$suffix.'"]\').val();';
1067 - } elseif ($option['type'] == 'custom') {
1068 - $currentOption .= $option['output'];
1069 - $jsOptionsMerge[] = $option['js'];
1220 + private function displaySelectField(array $option, string $suffix, string $updateFunction): void
1221 + {
1222 + $selectOptions = [];
1223 + foreach ($option['options'] as $value => $title) {
1224 + if (is_object($title)) {
1225 + $selectOptions[] = acym_selectOption($title->value, $title->text);
1226 + } else {
1227 + $selectOptions[] = acym_selectOption($value, $title);
1070 1228 }
1229 + }
1071 1230
1072 - if (!empty($option['main']) || in_array($option['type'], ['pictures', 'checkbox'])) {
1073 - $outputStructure['topOptions'][$currentLabel] = $currentOption;
1231 + $default = empty($option['default']) ? null : $option['default'];
1232 + $defaultOrder = null;
1233 + if (!empty($default) && strpos($default, ',')) {
1234 + [$default, $defaultOrder] = explode(',', $default);
1235 + }
1074 1236
1075 - if ($option['type'] === 'checkbox' && $currentLabel === 'ACYM_DISPLAY' && (!isset($option['format']) || $option['format'])) {
1076 - $formatOption = '<div class="grid-x">';
1077 - $formatOption .= '<div class="cell large-3">'.acym_translation('ACYM_FORMAT').'</div>';
1078 - $formatOption .= '<div class="cell large-9 dcontentFormatContainer">';
1237 + $attributes = [
1238 + 'onchange' => $updateFunction.'();',
1239 + 'id' => $option['name'].$suffix,
1240 + ];
1241 + if ($option['name'] === 'order') {
1242 + $attributes['class'] = 'acym__dynamics__ordering__select';
1243 + }
1079 1244
1080 - $default = empty($defaultValues->format) ? 'TOP_LEFT' : $defaultValues->format;
1081 - $formats = ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT'];
1082 - foreach ($formats as $oneFormat) {
1083 - $class = 'button-radio';
1084 - if ($default === $oneFormat) $class .= ' button-radio-selected';
1245 + acym_select(
1246 + $selectOptions,
1247 + $option['name'].$suffix,
1248 + $default,
1249 + $attributes,
1250 + 'value',
1251 + 'text',
1252 + null,
1253 + false,
1254 + true
1255 + );
1085 1256
1086 - $formatOption .= '<button
1087 - class="'.$class.'"
1088 - acym-button-radio-group="dcontentFormat'.$suffix.'"
1089 - acym-data-type="'.$oneFormat.'"
1090 - acym-callback="'.$updateFunction.'">
1091 - <img alt="'.$oneFormat.'" src="'.ACYM_IMAGES.'editor/dcontent_formats/'.strtolower($oneFormat).'.png"/>
1092 - </button>';
1093 - }
1094 - $formatOption .= '</div>';
1257 + if ($option['name'] === 'order') {
1258 + $dirs = [
1259 + 'desc' => acym_translation('ACYM_DESC'),
1260 + 'asc' => acym_translation('ACYM_ASC'),
1261 + ];
1262 + if (empty($defaultOrder)) {
1263 + $defaultOrder = empty($option['defaultdir']) ? null : $option['defaultdir'];
1264 + }
1265 + echo ' ';
1266 + acym_select(
1267 + $dirs,
1268 + 'orderdir'.$suffix,
1269 + $defaultOrder,
1270 + [
1271 + 'onchange' => $updateFunction.'();',
1272 + 'style' => 'width: 115px;',
1273 + 'class' => 'acym__dynamics__ordering__select',
1274 + ],
1275 + 'value',
1276 + 'text',
1277 + null,
1278 + false,
1279 + true
1280 + );
1281 + }
1282 + }
1095 1283
1096 - if ($type === 'grouped') {
1097 - $formatOption .= '<div class="cell large-3">'.acym_translation('ACYM_ALTERNATE').acym_info(['textShownInTooltip' => 'ACYM_ALTERNATE_DESC']).'</div>';
1098 - $formatOption .= '<div class="cell large-9">';
1099 - $formatOption .= acym_boolean(
1100 - 'alternate'.$suffix,
1101 - !empty($defaultValues->alternate),
1102 - 'alternate'.$suffix,
1103 - ['onclick' => $updateFunction.'();']
1104 - );
1105 - $formatOption .= '</div>';
1284 + private function displayMultiselectField(array $option, string $suffix, string $updateFunction): void
1285 + {
1286 + $selectOptions = [];
1287 + foreach ($option['options'] as $value => $title) {
1288 + $selectOptions[] = acym_selectOption($value, $title);
1289 + }
1106 1290
1107 - $jsOptionsMerge[] = 'var alternate = jQuery(\'input[name="alternate'.$suffix.'"]:checked\').val();';
1108 - $jsOptionsMerge[] = 'if (!acym_helper.empty(alternate)) otherinfo += "| alternate";';
1109 - }
1291 + if (!isset($option['default'])) {
1292 + $option['default'] = [];
1293 + }
1294 + if (!is_array($option['default'])) {
1295 + $option['default'] = explode(',', $option['default']);
1296 + }
1110 1297
1111 - $formatOption .= '</div>';
1298 + acym_selectMultiple(
1299 + $selectOptions,
1300 + $option['name'].$suffix,
1301 + $option['default'],
1302 + ['onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix],
1303 + 'value',
1304 + 'text',
1305 + true
1306 + );
1307 + }
1112 1308
1113 - $jsOptionsMerge[] = 'var selectedFormatOption = jQuery(\'.button-radio-selected[acym-button-radio-group="dcontentFormat'.$suffix.'"]\')';
1114 - $jsOptionsMerge[] = 'if (!acym_helper.empty(selectedFormatOption)) otherinfo += "| format:" + selectedFormatOption.attr("acym-data-type");';
1309 + private function displayLanguageField(array $option, string $suffix, string $updateFunction): void
1310 + {
1311 + $languageOptions = ['any' => acym_translation('ACYM_ANY')];
1312 + foreach (acym_getLanguages(true) as $language) {
1313 + $languageOptions[$language->language] = $language->name;
1314 + }
1115 1315
1116 - $outputStructure['topOptions']['ACYM_FORMAT'] = $formatOption;
1117 - }
1118 - continue;
1316 + if (empty($option['default'])) {
1317 + $option['default'] = acym_getVar('string', 'language');
1318 + if (acym_isMultilingual() && (empty($option['default']) || $option['default'] === 'main')) {
1319 + $option['default'] = $this->config->get('multilingual_default');
1119 1320 }
1321 + }
1120 1322
1121 - if (empty($option['section'])) {
1122 - $option['section'] = 'ACYM_OTHER_OPTIONS';
1123 - }
1323 + acym_select(
1324 + $languageOptions,
1325 + $option['name'].$suffix,
1326 + empty($option['default']) ? null : $option['default'],
1327 + ['onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix],
1328 + 'value',
1329 + 'text',
1330 + null,
1331 + false,
1332 + true
1333 + );
1334 + }
1124 1335
1125 - $currentLabel = acym_translation($currentLabel);
1126 - if (!empty($option['tooltip'])) {
1127 - $currentLabel .= '&nbsp;'.acym_info(['textShownInTooltip' => $option['tooltip'], 'classIcon' => 'acym_plugin_field_'.$option['name']]);
1336 + private function displayFormatPanel(string $suffix, string $updateFunction, string $type, $defaultValues): void
1337 + {
1338 + echo '<div class="grid-x">';
1339 + echo '<div class="cell large-3">';
1340 + echo esc_html(acym_translation('ACYM_FORMAT'));
1341 + echo '</div>';
1342 + echo '<div class="cell large-9 dcontentFormatContainer">';
1343 +
1344 + $default = empty($defaultValues->format) ? 'TOP_LEFT' : $defaultValues->format;
1345 + $formats = ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT'];
1346 + foreach ($formats as $oneFormat) {
1347 + $class = 'button-radio';
1348 + if ($default === $oneFormat) {
1349 + $class .= ' button-radio-selected';
1128 1350 }
1129 - $currentLabel = '<label class="cell large-5 acym_plugin_field acym_plugin_field_'.$option['type'].'" for="'.acym_escape(
1130 - $option['name'].$suffix
1131 - ).'">'.$currentLabel.'</label>';
1351 + echo '<button class="'.esc_attr($class).'"
1352 + acym-button-radio-group="dcontentFormat'.esc_attr($suffix).'"
1353 + acym-data-type="'.esc_attr($oneFormat).'"
1354 + acym-callback="'.esc_attr($updateFunction).'">';
1355 + echo '<img alt="'.esc_attr($oneFormat).'" src="'.esc_url(ACYM_IMAGES.'editor/dcontent_formats/'.strtolower($oneFormat).'.png').'"/>';
1356 + echo '</button>';
1357 + }
1358 + echo '</div>';
1132 1359
1133 - $outputStructure['options'][$option['section']][$currentLabel] = $currentOption;
1360 + if ($type === 'grouped') {
1361 + echo '<div class="cell large-3">';
1362 + echo esc_html(acym_translation('ACYM_ALTERNATE'));
1363 + acym_info(['textShownInTooltip' => 'ACYM_ALTERNATE_DESC']);
1364 + echo '</div>';
1365 + echo '<div class="cell large-9">';
1366 + acym_boolean(
1367 + 'alternate'.$suffix,
1368 + !empty($defaultValues->alternate),
1369 + 'alternate'.$suffix,
1370 + ['onclick' => $updateFunction.'();']
1371 + );
1372 + echo '</div>';
1134 1373 }
1135 1374
1136 - if (!empty($outputStructure['options'])) {
1137 - if (isset($outputStructure['options']['ACYM_OTHER_OPTIONS'])) {
1138 - $otherOptions = $outputStructure['options']['ACYM_OTHER_OPTIONS'];
1139 - unset($outputStructure['options']['ACYM_OTHER_OPTIONS']);
1140 - $outputStructure['options']['ACYM_OTHER_OPTIONS'] = $otherOptions;
1141 - }
1375 + echo '</div>';
1376 + }
1142 1377
1143 - foreach ($outputStructure['options'] as $section => $options) {
1144 - $formattedOptions = '';
1145 - foreach ($options as $label => $option) {
1146 - $formattedOptions .= '<div class="cell grid-x margin-bottom-1">'.$label;
1147 - $formattedOptions .= '<div class="cell large-7">'.$option.'</div>';
1148 - $formattedOptions .= '</div>';
1378 + private function echoOptionJs(array $option, string $suffix, string $type): void
1379 + {
1380 + switch ($option['type']) {
1381 + case 'pictures':
1382 + if (!empty($option['caption'])) {
1383 + echo 'otherinfo += "| caption:" + jQuery(\'input[name="caption'.esc_attr($suffix).'"]:checked\').val();
1384 + ';
1149 1385 }
1150 - $outputStructure['topOptions'][$section] = $formattedOptions;
1151 - }
1386 + echo '
1387 + var _pictVal'.esc_attr($suffix).' = jQuery(\'input[name="pict'.esc_attr($suffix).'"]:checked\').val();
1388 + otherinfo += "| pict:" + _pictVal'.esc_attr($suffix).';
1389 +
1390 + if (_pictVal'.esc_attr($suffix).' == "resized") {
1391 + jQuery("#pictsize'.esc_attr($suffix).'").show();
1392 + otherinfo += "| maxwidth:" + jQuery(\'input[name="pictwidth'.esc_attr($suffix).'"]\').val();
1393 + otherinfo += "| maxheight:" + jQuery(\'input[name="pictheight'.esc_attr($suffix).'"]\').val();
1394 + }else{
1395 + jQuery("#pictsize'.esc_attr($suffix).'").hide();
1396 + }
1397 + ';
1398 +
1399 + break;
1400 +
1401 + case 'checkbox':
1402 + $separator = empty($option['separator']) ? ',' : $option['separator'];
1403 + echo 'var _checked'.esc_attr($option['name'].$suffix).' = [];
1404 + jQuery("input:checkbox[name='.esc_attr($option['name'].$suffix).']:checked").each(function(){
1405 + _checked'.esc_attr($option['name'].$suffix).'.push(jQuery(this).val());
1406 + });
1407 + if(_checked'.esc_attr($option['name'].$suffix).'.length) otherinfo += "| '.esc_attr($option['name']).':" + _checked'.esc_attr(
1408 + $option['name'].$suffix
1409 + ).'.join("'.esc_attr($separator).'");
1410 + ';
1411 +
1412 + if ($option['title'] === 'ACYM_DISPLAY' && (!isset($option['format']) || $option['format'])) {
1413 + if ($type === 'grouped') {
1414 + echo 'var alternate = jQuery(\'input[name="alternate'.esc_attr($suffix).'"]:checked\').val();
1415 + ';
1416 + echo 'if (!acym_helper.empty(alternate)) otherinfo += "| alternate";
1417 + ';
1418 + }
1419 + echo 'var selectedFormatOption = jQuery(\'.button-radio-selected[acym-button-radio-group="dcontentFormat'.esc_attr($suffix).'"]\');
1420 + ';
1421 + echo 'if (!acym_helper.empty(selectedFormatOption)) otherinfo += "| format:" + selectedFormatOption.attr("acym-data-type");
1422 + ';
1423 + }
1424 + break;
1425 +
1426 + case 'boolean':
1427 + case 'radio':
1428 + echo 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'input[name="'.esc_attr($option['name'].$suffix).'"]:checked\').val();
1429 + ';
1430 + break;
1431 +
1432 + case 'select':
1433 + if ($option['name'] === 'order') {
1434 + echo 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr(
1435 + $option['name'].$suffix
1436 + ).'"]\').val() + "," + jQuery(\'[name="orderdir'.esc_attr($suffix).'"]\').val();
1437 + ';
1438 + } else {
1439 + echo 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr($option['name'].$suffix).'"]\').val();
1440 + ';
1441 + }
1442 + break;
1443 +
1444 + case 'language':
1445 + echo 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr($option['name'].$suffix).'"]\').val();
1446 + ';
1447 + break;
1448 +
1449 + case 'multiselect':
1450 + echo '
1451 + var theMultiSelect = document.querySelector(\'[name="'.esc_attr($option['name'].$suffix).'[]"]\');
1452 + var selectedOptions = [];
1453 + for(var i = 0 ; i < theMultiSelect.length ; i++){
1454 + if(theMultiSelect[i].selected){
1455 + selectedOptions.push(theMultiSelect[i].value);
1456 + }
1457 + }
1458 + otherinfo += "| '.esc_attr($option['name']).':" + selectedOptions.join(",");
1459 + ';
1460 + break;
1461 +
1462 + case 'text':
1463 + case 'number':
1464 + case 'intextfield':
1465 + case 'date':
1466 + echo 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'input[name="'.esc_attr($option['name'].$suffix).'"]\').val();
1467 + ';
1468 + break;
1469 +
1470 + case 'custom':
1471 + $this->echoLegacyOptionJs($option);
1472 + break;
1152 1473 }
1474 + }
1153 1475
1154 - $output = '';
1155 - if (!empty($outputStructure['topOptions'])) {
1156 - foreach ($outputStructure['topOptions'] as $label => $oneOption) {
1157 - $output .= '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">';
1158 - $output .= acym_translation($label).'<i class="acymicon-keyboard-arrow-up"></i>';
1159 - $output .= '</p>';
1160 - $output .= '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container grid-x">';
1161 - $output .= $oneOption;
1162 - $output .= '</div>';
1476 + private function echoLegacyOptionJs(array $option): void
1477 + {
1478 + if (empty($option['js'])) {
1479 + return;
1480 + }
1481 +
1482 + $jsonFlags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
1483 +
1484 + foreach (explode(';', $option['js']) as $statement) {
1485 + $statement = trim($statement);
1486 + if ($statement === '') {
1487 + continue;
1163 1488 }
1164 - }
1165 1489
1166 - $storageVar = 'window._additionalInfo'.$suffix;
1167 - $output .= '
1168 - <script type="text/javascript">
1169 - var _selectedRows'.$suffix.' = [];
1170 - var _selectedRows = [];
1171 - '.$storageVar.' = '.$storageVar.' || {};
1490 + // otherinfo += "| key:constant"
1491 + if (preg_match('#^otherinfo\s*\+=\s*"\|\s*([a-zA-Z0-9_-]+):([^"]*)"$#', $statement, $matches)) {
1492 + echo 'otherinfo += '.json_encode('| '.$matches[1].':'.$matches[2], $jsonFlags).';
1172 1493 ';
1173 - if (!empty($defaultValues->id) && (empty($defaultValues->defaultPluginTab) || $dynamicIdentifier === $defaultValues->defaultPluginTab)) {
1174 - $delimiter = strpos($defaultValues->id, '-') ? '-' : ',';
1175 - $selected = explode($delimiter, $defaultValues->id);
1494 + continue;
1495 + }
1176 1496
1177 - foreach ($selected as $value) {
1178 - if (empty($value)) continue;
1179 - $output .= '_selectedRows'.$suffix.'['.intval($value).'] = true;
1497 + // otherinfo += "| key:" + jQuery('selector').val()
1498 + if (preg_match('#^otherinfo\s*\+=\s*"\|\s*([a-zA-Z0-9_-]+):"\s*\+\s*jQuery\(\s*\'(.*)\'\s*\)\.val\(\)$#', $statement, $matches)) {
1499 + echo 'otherinfo += '.json_encode('| '.$matches[1].':', $jsonFlags).' + jQuery('.json_encode($matches[2], $jsonFlags).').val();
1180 1500 ';
1501 + continue;
1181 1502 }
1503 +
1504 + acym_logError(
1505 + 'Unsupported deprecated "js" option declared by an add-on for the option "'.($option['name'] ?? '').'", it was not applied: '.$statement,
1506 + 'plugin'
1507 + );
1182 1508 }
1509 + }
1183 1510
1184 - $output .= '
1185 - function applyContent'.$suffix.'(contentid, row){
1186 - if(_selectedRows'.$suffix.'[contentid]){
1187 - jQuery(row).removeClass("selected_row");
1188 - delete _selectedRows'.$suffix.'[contentid];
1189 - }else{
1190 - ';
1511 + private function displayDynamicScript(array $jsOptions, string $suffix, string $updateFunction, string $dynamicIdentifier, string $type, $defaultValues): void
1512 + {
1513 + $storageVar = 'window._additionalInfo'.$suffix;
1191 1514
1192 - if ('individual' === $type) {
1193 - $output .= '
1194 - for(let elementKey in _selectedRows'.$suffix.') {
1195 - if(!_selectedRows'.$suffix.'.hasOwnProperty(elementKey)) continue;
1196 -
1197 - jQuery(\'[data-id="\' + elementKey + \'"]\').removeClass("selected_row");
1198 - delete _selectedRows'.$suffix.'[elementKey];
1199 - }
1200 - ';
1201 - }
1515 + echo '<script type="text/javascript">';
1516 + echo 'var _selectedRows'.esc_attr($suffix).' = [];';
1517 + echo 'var _selectedRows = [];';
1518 + echo esc_attr($storageVar).' = '.esc_attr($storageVar).' || {};';
1202 1519
1203 - $output .= '
1204 - jQuery(row).addClass("selected_row");
1205 - _selectedRows'.$suffix.'[contentid] = true;
1206 - }
1207 - '.$updateFunction.'();
1208 -
1209 - if(typeof _selectedRows !== "undefined"){
1210 - _selectedRows = _selectedRows'.$suffix.';
1211 - }
1520 + if (!empty($defaultValues->id) && (empty($defaultValues->defaultPluginTab) || $dynamicIdentifier === $defaultValues->defaultPluginTab)) {
1521 + $delimiter = strpos($defaultValues->id, '-') ? '-' : ',';
1522 + foreach (explode($delimiter, $defaultValues->id) as $value) {
1523 + if (empty($value)) {
1524 + continue;
1212 1525 }
1213 -
1214 - function '.$updateFunction.'(){
1215 - var tag = "";
1216 - var otherinfo = "";
1217 -
1218 - '.implode("\r\n\r\n", $jsOptionsMerge).'
1219 -
1220 - for (let [index, info] of Object.entries('.$storageVar.')){
1221 - otherinfo += "| "+index+":"+info;
1222 - }
1223 - ';
1526 + echo '_selectedRows'.esc_attr($suffix).'['.intval($value).'] = true;';
1527 + }
1528 + }
1224 1529
1225 - if ($type == 'individual') {
1226 - $output .= '
1227 - for(var i in _selectedRows'.$suffix.'){
1228 - if(!_selectedRows'.$suffix.'.hasOwnProperty(i)) continue;
1229 -
1230 - tag = tag + "{'.$dynamicIdentifier.':" + i + otherinfo + "}";
1231 - }';
1232 - } elseif ($type == 'grouped') {
1233 - $output .= '
1234 - tag = "{'.$dynamicIdentifier.':";
1235 - for(var icat in _selectedRows'.$suffix.'){
1236 - if(!_selectedRows'.$suffix.'.hasOwnProperty(icat)) continue;
1237 - tag += icat + "-";
1238 - }
1239 - tag += otherinfo + "}";';
1240 - } elseif ($type == 'simple') {
1241 - $output .= '
1242 - tag = "{'.$dynamicIdentifier.':" + otherinfo + "}";';
1530 + echo 'function applyContent'.esc_attr($suffix).'(contentid, row){';
1531 + echo 'if(_selectedRows'.esc_attr($suffix).'[contentid]){';
1532 + echo 'jQuery(row).removeClass("selected_row");';
1533 + echo 'delete _selectedRows'.esc_attr($suffix).'[contentid];';
1534 + echo '}else{';
1535 + if ($type === 'individual') {
1536 + echo 'for(let elementKey in _selectedRows'.esc_attr($suffix).') {';
1537 + echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(elementKey)) continue;';
1538 + echo 'jQuery(\'[data-id="\' + elementKey + \'"]\').removeClass("selected_row");';
1539 + echo 'delete _selectedRows'.esc_attr($suffix).'[elementKey];';
1540 + echo '}';
1243 1541 }
1542 + echo 'jQuery(row).addClass("selected_row");';
1543 + echo '_selectedRows'.esc_attr($suffix).'[contentid] = true;';
1544 + echo '}';
1545 + echo esc_attr($updateFunction).'();';
1546 + echo 'if(typeof _selectedRows !== "undefined"){ _selectedRows = _selectedRows'.esc_attr($suffix).'; }';
1547 + echo '}';
1244 1548
1245 - $output .= '
1246 - acym_editorWysidDynamic.insertDContent(tag);
1247 - }
1248 -
1249 - function addAdditionalInfo'.$suffix.'(index, value){
1250 - '.$storageVar.'[index] = value;
1251 - '.$updateFunction.'();
1252 - }
1253 - </script>';
1549 + echo 'function '.esc_attr($updateFunction).'(){';
1550 + echo 'var tag = ""; var otherinfo = "";';
1551 + foreach ($jsOptions as $option) {
1552 + $this->echoOptionJs($option, $suffix, $type);
1553 + }
1554 + echo 'for (let [index, info] of Object.entries('.esc_attr($storageVar).')){ otherinfo += "| "+index+":"+info; }';
1254 1555
1255 1556 if ($type === 'individual') {
1256 - acym_trigger('displayCustomViewEditor', [&$output], 'plgAcym'.ucfirst($dynamicIdentifier));
1257 - $output .= '<input type="hidden" id="acym__dynamic__update__function" value="'.$updateFunction.'">';
1557 + echo 'for(var i in _selectedRows'.esc_attr($suffix).'){';
1558 + echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(i)) continue;';
1559 + echo 'tag = tag + "{'.esc_attr($dynamicIdentifier).':" + i + otherinfo + "}";';
1560 + echo '}';
1561 + } elseif ($type === 'grouped') {
1562 + echo 'tag = "{'.esc_attr($dynamicIdentifier).':";';
1563 + echo 'for(var icat in _selectedRows'.esc_attr($suffix).'){';
1564 + echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(icat)) continue;';
1565 + echo 'tag += icat + "-";';
1566 + echo '}';
1567 + echo 'tag += otherinfo + "}";';
1568 + } elseif ($type === 'simple') {
1569 + echo 'tag = "{'.esc_attr($dynamicIdentifier).':" + otherinfo + "}";';
1258 1570 }
1259 1571
1260 - echo $output;
1572 + echo 'acym_editorWysidDynamic.insertDContent(tag);';
1573 + echo '}';
1574 +
1575 + echo 'function addAdditionalInfo'.esc_attr($suffix).'(index, value){';
1576 + echo esc_attr($storageVar).'[index] = value;';
1577 + echo esc_attr($updateFunction).'();';
1578 + echo '}';
1579 + echo '</script>';
1261 1580 }
1262 1581
1582 + /**
1583 + * In Joomla, we can translate elements in extensions using the FaLang extension
1584 + */
1263 1585 public function translateItem(object &$item, object &$tag, string $referenceTable, int $referenceId = 0): void
1264 1586 {
1265 1587 if (!acym_isExtensionActive('com_falang')) {
1266 1588 return;
@@ -1333,11 +1655,12 @@
1333 1655
1334 1656 $language = acym_getVar('string', 'language', 'main');
1335 1657 if (!empty($language)) {
1336 1658 if ($language === 'main') {
1337 - $language = $this->config->get('multilingual_default', ACYM_DEFAULT_LANGUAGE);
1659 + $language = acym_getDefaultNewsletterLanguage();
1338 1660 }
1339 1661 $email->links_language = $language;
1662 + $email->language = $language;
1340 1663 }
1341 1664
1342 1665 $email->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
1343 1666 $email->creator_id = acym_currentUserId();
@@ -1347,9 +1670,11 @@
1347 1670 $email->settings = '';
1348 1671 $email->stylesheet = '';
1349 1672 $email->attachments = '';
1350 1673
1674 + // This is only the dynamic text/content code
1351 1675 $email->body = $code;
1676 + // This is the whole editor current content
1352 1677 $email->previewBody = $previewBody;
1353 1678
1354 1679 return $email;
1355 1680 }