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 +780 -471 10.1.4 → 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,13 +304,17 @@
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
290 313 $img = $getfunction($pictCode);
314 + if ($img === false) {
315 + continue;
316 + }
291 317
292 318 if (in_array($extension, ['gif', 'png'])) {
293 319 imagealphablending($img, false);
294 320 imagesavealpha($img, false);
@@ -293,8 +319,9 @@
293 319 imagealphablending($img, false);
294 320 imagesavealpha($img, false);
295 321 }
296 322
323 + //We display the result and then save it to avoid the issue with FTP configurations
297 324 ob_start();
298 325 switch ($extension) {
299 326 case 'gif':
300 327 $status = imagegif($img);
@@ -309,8 +336,9 @@
309 336 }
310 337 $imageContent = ob_get_clean();
311 338 $status = $status && acym_writeFile($pictPath, $imageContent);
312 339
340 + //we could not save or convert the picture, we skip it
313 341 if (!$status) {
314 342 continue;
315 343 }
316 344 $html = str_replace($pictCode, $picturl, $html);
@@ -316,28 +344,36 @@
316 344 $html = str_replace($pictCode, $picturl, $html);
317 345 }
318 346 }
319 347
348 + //This function will remove or replace code we should not have in an html view.
320 349 public function cleanHtml(string &$html): void
321 350 {
322 351 $this->convertbase64pictures($html);
323 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>
324 355 $pregreplace = [];
325 356 $pregreplace['#<tr([^>"]*>([^<]*<td[^>]*>[ \n\s]*<img[^>]*>[ \n\s]*</ *td[^>]*>[ \n\s]*)*</ *tr)#Uis'] = '<tr style="line-height: 0px;" $1';
326 357 $pregreplace['#<td(((?!style|>).)*>[ \n\s]*(<a[^>]*>)?[ \n\s]*<img[^>]*>[ \n\s]*(</a[^>]*>)?[ \n\s]*</ *td)#Uis'] = '<td style="line-height: 0px;" $1';
327 358
359 + //No number tab system {tab=...}{/tabs} or {tab }{/tabs} and jcomments system
328 360 $pregreplace['#{tab[ =][^}]*}#is'] = '';
329 361 $pregreplace['#{/tabs}#is'] = '';
330 362 $pregreplace['#{jcomments\s+(on|off|lock)}#is'] = '';
331 363
364 + //Remove the JS...
332 365 $pregreplace["#(onmouseout|onmouseover|onclick|onfocus|onload|onblur) *= *\"(?:(?!\").)*\"#Ui"] = '';
333 366 $pregreplace["#< *script(?:(?!< */ *script *>).)*< */ *script *>#Uis"] = '';
334 367 $pregreplace["#< *iframe(?:(?!< */ *iframe *>).)*< */ *iframe *>#Uis"] = '';
335 368
369 + // May God punish every person using Outlook, or imposing this monstrosity for his employees
336 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
337 372 $pregreplace['#(<img [^>]*src="[^"]+\.webp"[^>]*>)#Uis'] = '<!--[if !mso]><!-->$1<!--<![endif]-->';
338 373
339 374 $newbody = preg_replace(array_keys($pregreplace), $pregreplace, $html);
375 + //we do it in two steps as this regex can break the page
340 376 if (!empty($newbody)) {
341 377 $html = $newbody;
342 378 }
343 379
@@ -351,8 +387,11 @@
351 387 {
352 388 return "src='".str_replace(' ', '%20', $matches[1])."'";
353 389 }
354 390
391 + /*
392 + * Replace tags in all the email variables where it is possible, in text version or html version
393 + */
355 394 public function replaceTags(object &$email, array $tags, bool $html = false): void
356 395 {
357 396 if (empty($tags)) return;
358 397
@@ -411,8 +450,9 @@
411 450 }
412 451 $safePregValue = str_replace('$', '\$', $value);
413 452
414 453 foreach ($codes as $oneCode) {
454 + // Dtext specific syntax
415 455 $text = preg_replace(
416 456 '#<span[^>]+'.preg_quote($oneCode, '#').'.+</em>[^<]*</span>#Uis',
417 457 $safePregValue,
418 458 $text
@@ -417,8 +457,9 @@
417 457 $safePregValue,
418 458 $text
419 459 );
420 460
461 + // Dcontent specific syntax
421 462 $text = preg_replace(
422 463 '#(<tr[^>]+)data-dynamic="'.preg_quote($oneCode, '#').'"([^>]+>[^<]*<td[^>]*>).+</i>[^<]*</td>[^<]*</tr>#Uis',
423 464 '${1}${2}'.$safePregValue.'</td></tr>',
424 465 $text
@@ -423,8 +464,9 @@
423 464 '${1}${2}'.$safePregValue.'</td></tr>',
424 465 $text
425 466 );
426 467
468 + // If the code was inserted directly in the email (in the subject for example, or a copy-paste)
427 469 $text = str_replace($oneCode, $value, $text);
428 470 }
429 471 }
430 472 }
@@ -431,13 +473,18 @@
431 473
432 474 return $text;
433 475 }
434 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 + */
435 481 public function extractTags(object $email, string $tagfamily): array
436 482 {
437 483 $results = [];
438 484
439 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!
440 487 $variables = [
441 488 'subject',
442 489 'AltBody',
443 490 'body',
@@ -473,12 +520,14 @@
473 520 }
474 521 }
475 522 } else {
476 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
477 525 if (empty($results[$var][0])) unset($results[$var]);
478 526 }
479 527 }
480 528
529 + //If we didn't find anything...
481 530 if (!$found) {
482 531 return [];
483 532 }
484 533
@@ -484,8 +533,9 @@
484 533
485 534 $tags = [];
486 535 foreach ($results as $var => $allresults) {
487 536 foreach ($allresults[0] as $i => $oneTag) {
537 + //Don't need to process twice a tag we already have!
488 538 if (isset($tags[$oneTag])) {
489 539 continue;
490 540 }
491 541 $tags[$oneTag] = $this->extractTag($allresults[1][$i]);
@@ -494,16 +544,19 @@
494 544
495 545 return $tags;
496 546 }
497 547
548 + /**
549 + * Converts a tag 23|file:myfile|price into an object.
550 + */
498 551 public function extractTag(string $oneTag): \stdClass
499 552 {
500 553 $oneTag = str_replace(['[time]+', '[time]-'], [urlencode('[time]+'), urlencode('[time]-')], $oneTag);
501 - $arguments = explode('|', strip_tags(urldecode($oneTag)));
554 + $arguments = explode('|', acym_stripTags(urldecode($oneTag)));
502 555 $tag = new \stdClass();
503 556 $tag->id = $arguments[0];
504 557 $tag->default = '';
505 - for ($i = 1, $a = count($arguments) ; $i < $a ; $i++) {
558 + for ($i = 1, $a = count($arguments); $i < $a; $i++) {
506 559 $args = explode(':', $arguments[$i], 2);
507 560 $arg0 = trim($args[0]);
508 561 if (empty($arg0)) continue;
509 562
@@ -508,8 +561,9 @@
508 561 if (empty($arg0)) continue;
509 562
510 563 if (isset($args[1])) {
511 564 $tag->$arg0 = $args[1];
565 + //We may have an extra parameter, especially for date format.
512 566 if (isset($args[2])) {
513 567 $tag->{$args[0]} .= ':'.$args[2];
514 568 }
515 569 } else {
@@ -519,8 +573,11 @@
519 573
520 574 return $tag;
521 575 }
522 576
577 + /**
578 + * Wrap the text using the wrapValue
579 + */
523 580 public function wrapText(string $text, object $tag): string
524 581 {
525 582 if (empty($tag->wrap)) {
526 583 return $text;
@@ -527,103 +584,97 @@
527 584 }
528 585
529 586 $tag->wrap = intval($tag->wrap);
530 587
531 - $allowedTags = [
532 - 'b',
533 - 'strong',
534 - 'i',
535 - 'em',
536 - 'a',
537 - 'p',
538 - 'div',
539 - 'h2',
540 - 'h3',
541 - 'h4',
542 - 'h5',
543 - 'h6',
544 - ];
588 + $newText = str_replace(['&lt', '&gt'], ['<', '>'], $text);
545 589
546 - $aloneAllowedTags = [
547 - 'br',
548 - 'img',
549 - ];
590 + if (mb_strlen(acym_stripTags($newText)) <= $tag->wrap) {
591 + return $newText;
592 + }
550 593
551 - $newText = strip_tags($text, '<'.implode('><', array_merge($allowedTags, $aloneAllowedTags)).'>');
594 + if (!class_exists('DOMDocument', false)) {
595 + return mb_substr(acym_stripTags($newText), 0, $tag->wrap).'…';
596 + }
552 597
553 - $newText = preg_replace('/^(\s|\n|(<br[^>]*>))+/i', '', trim($newText));
554 - $newText = preg_replace('/(\s|\n|(<br[^>]*>))+$/i', '', trim($newText));
598 + if (function_exists('libxml_use_internal_errors')) {
599 + libxml_use_internal_errors(true);
600 + }
555 601
556 - $newText = str_replace(['&lt', '&gt'], ['<', '>'], $newText);
602 + $dom = new \DOMDocument('1.0', 'UTF-8');
603 + $dom->loadHTML(
604 + '<?xml encoding="UTF-8">'.$newText,
605 + LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
606 + );
557 607
558 - $numChar = strlen($newText);
608 + $count = 0;
609 + $this->truncateNode($dom, $dom, $tag->wrap, $count);
559 610
560 - $numCharStrip = strlen(strip_tags($newText));
611 + return $dom->saveHTML();
612 + }
561 613
562 - if ($numCharStrip <= $tag->wrap) {
563 - return $newText;
564 - }
614 + private function truncateNode(\DOMDocument $dom, \DOMNode $node, int $limit, int &$count): bool
615 + {
616 + for ($i = 0; $i < $node->childNodes->length; $i++) {
617 + $child = $node->childNodes->item($i);
565 618
566 - $open = [];
619 + // Text node
620 + if ($child->nodeType === XML_TEXT_NODE) {
621 + $text = $child->nodeValue;
622 + $len = mb_strlen($text);
567 623
568 - $write = true;
624 + if ($count + $len > $limit) {
625 + $remaining = $limit - $count;
569 626
570 - $countStripChar = 0;
627 + // Take the max allowed slice
628 + $slice = mb_substr($text, 0, $remaining);
571 629
572 - for ($i = 0 ; $i < $numChar ; $i++) {
573 - if ($newText[$i] == '<') {
574 - foreach ($allowedTags as $oneAllowedTag) {
575 - if ($numChar >= ($i + strlen($oneAllowedTag) + 1) && substr($newText, $i, strlen($oneAllowedTag) + 1) == '<'.$oneAllowedTag && (in_array(
576 - $newText[$i + strlen($oneAllowedTag) + 1],
577 - [' ', '>']
578 - ))) {
579 - $write = false;
580 - $open[] = '</'.$oneAllowedTag.'>';
630 + // Try to cut at a word boundary (spaces or punctuation)
631 + if (preg_match('/^(.+?)([\s\p{P}]+)[^\s\p{P}]*$/u', $slice, $m)) {
632 + $slice = rtrim($m[1]);
581 633 }
582 634
583 - if ($numChar >= ($i + strlen($oneAllowedTag) + 2) && substr($newText, $i, strlen($oneAllowedTag) + 2) == '</'.$oneAllowedTag) {
584 - if (end($open) == '</'.$oneAllowedTag.'>') {
585 - array_pop($open);
586 - }
635 + // Final assignment
636 + $child->nodeValue = $slice.'…';
637 +
638 + // Remove remaining siblings
639 + while ($child->nextSibling) {
640 + $node->removeChild($child->nextSibling);
587 641 }
588 - }
589 642
590 - foreach ($aloneAllowedTags as $oneAllowedTag) {
591 - if ($numChar >= ($i + strlen($oneAllowedTag) + 1) && substr($newText, $i, strlen($oneAllowedTag) + 1) == '<'.$oneAllowedTag && (in_array(
592 - $newText[$i + strlen($oneAllowedTag) + 1],
593 - [' ', '/', '>']
594 - ))) {
595 - $write = false;
596 - }
643 + return true;
597 644 }
598 - }
599 645
600 - if ($write) {
601 - $countStripChar++;
646 + $count += $len;
602 647 }
603 648
604 - if ($newText[$i] == ">") {
605 - $write = true;
606 - }
649 + // Element node → recurse
650 + if ($child->hasChildNodes() && $this->truncateNode($dom, $child, $limit, $count)) {
651 + while ($child->nextSibling) {
652 + $node->removeChild($child->nextSibling);
653 + }
607 654
608 - if ($newText[$i] == " " && $countStripChar >= $tag->wrap && $write) {
609 - $newText = substr($newText, 0, $i).'...';
610 -
611 - $open = array_reverse($open);
612 - $newText = $newText.implode('', $open);
613 -
614 - break;
655 + return true;
615 656 }
616 657 }
617 658
618 - $newText = preg_replace('/^(\s|\n|(<br[^>]*>))+/i', '', trim($newText));
619 - $newText = preg_replace('/(\s|\n|(<br[^>]*>))+$/i', '', trim($newText));
620 -
621 - return $newText;
659 + return false;
622 660 }
623 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 + */
624 674 public function getStandardDisplay(object $format): string
625 675 {
676 + // By default, put float left on the picture
626 677 if (empty($format->tag->format)) {
627 678 $format->tag->format = 'TOP_LEFT';
628 679 }
629 680 if (!in_array($format->tag->format, ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT'])) {
@@ -641,8 +692,9 @@
641 692 if (!empty($format->tag->invert) && !empty($invertValues[$format->tag->format])) {
642 693 $format->tag->format = $invertValues[$format->tag->format];
643 694 }
644 695
696 + // Get the image with float left/right or no style
645 697 $image = '';
646 698 if (!empty($format->imagePath)) {
647 699 $style = '';
648 700 $linkStyle = '';
@@ -665,15 +717,16 @@
665 717
666 718 preg_match('#src="([^"]+)"#Uis', $format->imagePath, $matches);
667 719 if (!empty($matches[1])) $format->imagePath = $matches[1];
668 720 $altImage = !empty($format->altImage) ? $format->altImage : '';
669 - $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.' />';
670 722
671 723 if (!empty($format->imageCaption) && !in_array($format->tag->format, ['TITLE_IMG', 'TITLE_IMG_RIGHT'])) {
672 - $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>';
673 725 }
674 726 }
675 727
728 + // If TITLE_IMG, add the image to the title
676 729 $result = '';
677 730 if (in_array($format->tag->format, ['TITLE_IMG', 'TITLE_IMG_RIGHT'])) {
678 731 $format->title = $image.$format->title;
679 732 $image = '';
@@ -682,13 +735,15 @@
682 735 if (!empty($format->link) && !empty($image) && !empty($format->tag->clickableimg)) {
683 736 $image = '<a target="_blank" href="'.$format->link.'" '.$linkStyle.'>'.$image.'</a>';
684 737 }
685 738
739 + // If the image should be displayed before the title, do it
686 740 if ($format->tag->format === 'TOP_IMG' && !empty($image)) {
687 741 $result = $image;
688 742 $image = '';
689 743 }
690 744
745 + // If we want to keep the left/right column for the image, put the whole article in a table
691 746 if (in_array($format->tag->format, ['COL_LEFT', 'COL_RIGHT'])) {
692 747 $maxWidth = empty($format->tag->maxwidth) ? '' : ' width: '.$format->tag->maxwidth.'px;';
693 748 if (empty($image)) {
694 749 $format->tag->format = 'TOP_LEFT';
@@ -700,8 +755,9 @@
700 755 }
701 756 }
702 757 }
703 758
759 + // Display the title
704 760 if (!empty($format->title)) {
705 761 if (!empty($format->link) && !empty($format->tag->clickable)) {
706 762 if (empty($format->tag->type) || $format->tag->type !== 'title') {
707 763 $format->title = '<h2 class="acym_title">'.$format->title.'</h2>';
@@ -733,8 +789,9 @@
733 789
734 790
735 791 $rowText = '<div class="acydescription">';
736 792 $endRow = '</div><br />';
793 + // Display the main content based on the chosen format
737 794 if (in_array($format->tag->format, ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'TOP_IMG'])) {
738 795 if (!empty($image) || !empty($format->description)) {
739 796 $result .= $rowText.$image.$format->description.$endRow;
740 797 }
@@ -756,8 +813,9 @@
756 813 }
757 814 $result .= '</td></tr></table>';
758 815 }
759 816
817 + // Add the custom fields on 1 or 2 columns, with or without the labels
760 818 if (!empty($format->customFields)) {
761 819 $result .= '<table style="width:100%;" class="customfieldsarea"><tr>';
762 820
763 821 $format->cols = empty($format->tag->nbcols) ? 1 : intval($format->tag->nbcols);
@@ -766,14 +824,12 @@
766 824 $i = 0;
767 825 foreach ($format->customFields as $oneField) {
768 826 if ($i != 0 && $i % $format->cols == 0) $result .= '</tr><tr>';
769 827
770 - $result .= '<td nowrap="nowrap" class="cf';
771 -
772 828 if (empty($oneField[1])) {
773 - $result .= 'value" colspan="2">';
829 + $result .= '<td class="cfvalue" colspan="2">';
774 830 } else {
775 - $result .= 'label">'.$oneField[1].'</td><td class="cfvalue">';
831 + $result .= '<td nowrap="nowrap" class="cflabel">'.$oneField[1].'</td><td class="cfvalue">';
776 832 }
777 833
778 834 $result .= $oneField[0].'</td>';
779 835 $i++;
@@ -786,8 +842,9 @@
786 842
787 843 $result .= '</tr></table>';
788 844 }
789 845
846 + // Add what should be displayed at the end (share buttons, read more link, etc...)
790 847 if (!empty($format->afterArticle)) {
791 848 $result .= $format->afterArticle;
792 849 }
793 850
@@ -793,8 +850,11 @@
793 850
794 851 return $result;
795 852 }
796 853
854 + /**
855 + * Resizes or removes the pictures according to the parameters
856 + */
797 857 public function managePicts(object $tag, string $result): string
798 858 {
799 859 if (!isset($tag->pict)) {
800 860 return $result;
@@ -815,471 +875,714 @@
815 875
816 876 return acym_absoluteURL($result);
817 877 }
818 878
819 - public function displayOptions(array $options, string $dynamicIdentifier, string $type = 'individual', $defaultValues = null): string
879 + public function displayOptions(array $options, string $dynamicIdentifier, string $type = 'individual', $defaultValues = null): void
820 880 {
821 - $suffix = preg_replace('[^a-zA-Z0-9]', '_', $dynamicIdentifier);
881 + $suffix = preg_replace('#[^a-zA-Z0-9]#U', '_', $dynamicIdentifier);
822 882 $updateFunction = 'updateDynamic'.$suffix;
823 883
824 - $outputStructure = [
825 - 'topOptions' => [],
826 - 'options' => [],
827 - ];
828 - $jsOptionsMerge = [];
884 + $jsOptions = [];
885 + $topOptions = [];
886 + $sections = [];
829 887
888 + // ---- Phase 1: classify options + collect the JS (in original loop order) ----
830 889 foreach ($options as $option) {
831 - $currentLabel = $option['title'];
832 - $currentOption = '';
833 -
834 890 if (isset($defaultValues->{$option['name']})) {
835 891 $option['default'] = $defaultValues->{$option['name']};
836 892 }
837 893
838 - if ($option['type'] === 'pictures') {
839 - $displayedPictures = $option['default'] ?? 'resized';
840 - if (isset($defaultValues->pict)) $displayedPictures = $defaultValues->pict;
841 - $resizeDisplay = 'resized' === $displayedPictures ? '' : 'style="display: none;"';
842 - $maxWidth = $defaultValues->maxwidth ?? 150;
843 - $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 + }
844 902
845 - $valImages = [];
846 - $valImages[] = acym_selectOption('1', 'ACYM_YES');
847 - $valImages[] = acym_selectOption('resized', 'ACYM_RESIZED');
848 - $valImages[] = acym_selectOption('0', 'ACYM_NO');
849 - $currentOption .= '<div class="cell large-5 acym_plugin_field">'.acym_translation('ACYM_DISPLAY').'</div>';
850 - $currentOption .= '<div class="cell large-7">'.acym_radio(
851 - $valImages,
852 - 'pict'.$suffix,
853 - $displayedPictures,
854 - ['onclick' => $updateFunction.'();'],
855 - ['containerClass' => 'dcontent_pictures'],
856 - !acym_isAdmin()
857 - ).'</div>';
858 - $currentOption .= '<div id="pictsize'.$suffix.'" class="cell grid-x margin-y margin-top-1" '.$resizeDisplay.'>
859 - <div class="cell large-5 acym_plugin_field">'.acym_translation('ACYM_MAX_WIDTH').'</div>
860 - <div class="cell large-7">
861 - <input class="intext_input" name="pictwidth'.$suffix.'" type="number" onchange="'.$updateFunction.'();" value="'.intval($maxWidth).'"/>
862 - </div>
863 - <div class="cell large-5 acym_plugin_field">'.acym_translation('ACYM_MAX_HEIGHT').'</div>
864 - <div class="cell large-7">
865 - <input class="intext_input" name="pictheight'.$suffix.'" type="number" onchange="'.$updateFunction.'();" value="'.intval($maxHeight).'"/>
866 - </div>
867 - </div>';
868 - if (!empty($option['caption'])) {
869 - $currentOption .= '<div class="cell grid-x margin-top-1">';
870 - $currentOption .= '<label class="cell large-5 acym_plugin_field">'.acym_translation('ACYM_CAPTION').'</label>';
871 - $currentOption .= acym_radio(
872 - [
873 - acym_selectOption('1', 'ACYM_YES'),
874 - acym_selectOption('0', 'ACYM_NO'),
875 - ],
876 - 'caption'.$suffix,
877 - $defaultValues->caption ?? '0',
878 - ['onclick' => $updateFunction.'();'],
879 - ['containerClass' => 'cell large-7']
880 - );
881 - $currentOption .= '</div>';
903 + $jsOptions[] = $option;
882 904
883 - $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];
884 917 }
918 + continue;
919 + }
885 920
886 - $jsOptionsMerge[] = '
887 - var _pictVal'.$suffix.' = jQuery(\'input[name="pict'.$suffix.'"]:checked\').val();
888 - otherinfo += "| pict:" + _pictVal'.$suffix.';
889 -
890 - if(_pictVal'.$suffix.' == "resized"){
891 - jQuery("#pictsize'.$suffix.'").show();
892 - otherinfo += "| maxwidth:" + jQuery(\'input[name="pictwidth'.$suffix.'"]\').val();
893 - otherinfo += "| maxheight:" + jQuery(\'input[name="pictheight'.$suffix.'"]\').val();
894 - }else{
895 - jQuery("#pictsize'.$suffix.'").hide();
896 - }';
897 - } elseif ($option['type'] === 'checkbox') {
898 - if (!empty($option['default'])) {
899 - $checkedValues = explode(',', $option['default']);
900 - foreach ($option['options'] as $key => $oneOption) {
901 - $oneOption[1] = in_array($key, $checkedValues);
902 - $option['options'][$key] = $oneOption;
903 - }
904 - }
921 + $section = empty($option['section']) ? 'ACYM_OTHER_OPTIONS' : $option['section'];
922 + $sections[$section][$option['name']] = $option;
923 + }
905 924
906 - $currentOption .= '<div class="cell grid-x">';
907 - foreach ($option['options'] as $value => $title) {
908 - $currentOption .= '<div class="cell medium-6" '.(empty($title[2]) ? '' : $title[2]).'>
909 - <input type="checkbox" name="'.acym_escape($option['name'].$suffix).'" value="'.acym_escape($value).'" id="'.acym_escape(
910 - $value.$suffix
911 - ).'" onclick="'.$updateFunction.'();" '.($title[1] ? 'checked="checked"' : '').'/>
912 - <label style="margin-left:5px" for="'.acym_escape($value.$suffix).'">'.acym_translation($title[0]).'</label>
913 - </div>';
914 - }
915 - $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 + }
916 931
917 - 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 + }
918 943
919 - $jsOptionsMerge[] = 'var _checked'.$option['name'].$suffix.' = [];
920 - jQuery("input:checkbox[name='.$option['name'].$suffix.']:checked").each(function(){
921 - _checked'.$option['name'].$suffix.'.push(jQuery(this).val());
922 - });
923 - if(_checked'.$option['name'].$suffix.'.length) otherinfo += "| '.$option['name'].':" + _checked'.$option['name'].$suffix.'.join("'.$option['separator'].'");';
924 - } elseif ($option['type'] === 'boolean') {
925 - if ($option['name'] === 'autologin' && $this->config->get('autologin_urls', 0) != 1) {
926 - continue;
927 - }
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 + }
928 957
929 - $currentOption .= acym_boolean(
930 - $option['name'].$suffix,
931 - $option['default'],
932 - $option['name'].$suffix,
933 - ['onclick' => $updateFunction.'();']
934 - );
958 + // ---- Phase 3: the inline script + individual extras ----
959 + $this->displayDynamicScript($jsOptions, $suffix, $updateFunction, $dynamicIdentifier, $type, $defaultValues);
935 960
936 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]:checked\').val();';
937 - } elseif ($option['type'] === 'radio') {
938 - $radioOptions = [];
939 - foreach ($option['options'] as $value => $title) {
940 - $radioOptions[] = acym_selectOption($value, $title);
941 - }
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 + }
942 966
943 - $currentOption .= acym_radio(
944 - $radioOptions,
945 - $option['name'].$suffix,
946 - $option['default'],
947 - ['onclick' => $updateFunction.'();'],
948 - ['pluginMode' => true],
949 - !acym_isAdmin()
950 - );
951 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]:checked\').val();';
952 - } elseif ($option['type'] === 'select') {
953 - $selectOptions = [];
954 - foreach ($option['options'] as $value => $title) {
955 - if (is_object($title)) {
956 - $selectOptions[] = acym_selectOption($title->value, $title->text);
957 - } else {
958 - $selectOptions[] = acym_selectOption($value, $title);
959 - }
960 - }
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 + }
961 974
962 - $default = empty($option['default']) ? null : $option['default'];
963 - 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 + }
964 990
965 - $attributes = [
966 - 'onchange' => $updateFunction.'();',
967 - 'id' => $option['name'].$suffix,
968 - ];
969 - if ($option['name'] === 'order') {
970 - $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']).'"';
971 1023 }
972 - $currentOption .= acym_select(
973 - $selectOptions,
974 - $option['name'].$suffix,
975 - $default,
976 - $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 + ]
977 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 + }
978 1083
979 - if ($option['name'] === 'order') {
980 - $dirs = [
981 - 'desc' => acym_translation('ACYM_DESC'),
982 - 'asc' => acym_translation('ACYM_ASC'),
983 - ];
984 - if (empty($defaultOrder)) $defaultOrder = empty($option['defaultdir']) ? null : $option['defaultdir'];
985 - $currentOption .= ' '.acym_select(
986 - $dirs,
987 - 'orderdir'.$suffix,
988 - $defaultOrder,
989 - [
990 - 'onchange' => $updateFunction.'();',
991 - 'style' => 'width: 115px;',
992 - 'class' => 'acym__dynamics__ordering__select',
993 - ]
994 - );
1084 + private function displayCustomOptionOutput(array $option): void
1085 + {
1086 + if (empty($option['output'])) {
1087 + return;
1088 + }
995 1089
996 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'[name="'.$option['name'].$suffix.'"]\').val() + "," + jQuery(\'[name="orderdir'.$suffix.'"]\').val();';
997 - } else {
998 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'[name="'.$option['name'].$suffix.'"]\').val();';
999 - }
1000 - } elseif ($option['type'] === 'multiselect') {
1001 - $selectOptions = [];
1002 - foreach ($option['options'] as $value => $title) {
1003 - $selectOptions[] = acym_selectOption($value, $title);
1004 - }
1090 + if (!is_string($option['output']) && is_callable($option['output'])) {
1091 + ($option['output'])();
1092 + }
1093 + }
1005 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;
1006 1103
1007 - if (!isset($option['default'])) $option['default'] = [];
1008 - 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 + ];
1009 1109
1010 - $currentOption .= acym_selectMultiple(
1011 - $selectOptions,
1012 - $option['name'].$suffix,
1013 - $option['default'],
1014 - ['onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix]
1015 - );
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>';
1016 1123
1017 - $jsOptionsMerge[] = '
1018 - var theMultiSelect = document.querySelector(\'[name="'.$option['name'].$suffix.'[]"]\');
1019 - var selectedOptions = [];
1020 - for(var i = 0 ; i < theMultiSelect.length ; i++){
1021 - if(theMultiSelect[i].selected){
1022 - selectedOptions.push(theMultiSelect[i].value);
1023 - }
1024 - }
1025 - otherinfo += "| '.$option['name'].':" + selectedOptions.join(",");';
1026 - } elseif ($option['type'] === 'text') {
1027 - if (!isset($option['default'])) $option['default'] = '';
1028 - $class = empty($option['class']) ? 'acym_plugin_text_field' : $option['class'];
1029 - $placeholder = empty($option['placeholder']) ? '' : ' placeholder="'.acym_escape($option['placeholder']).'"';
1030 - $currentOption .= '<input
1031 - type="text"
1032 - name="'.$option['name'].$suffix.'"
1033 - id="'.$option['name'].$suffix.'"
1034 - onchange="'.$updateFunction.'();"
1035 - value="'.acym_escape($option['default']).'"
1036 - class="'.acym_escape($class).'" '.$placeholder.'/>';
1037 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]\').val();';
1038 - } elseif ($option['type'] === 'number') {
1039 - $min = empty($option['min']) ? ' min="0"' : ' min="'.$option['min'].'"';
1040 - $max = empty($option['max']) ? '' : ' max="'.$option['max'].'"';
1041 - $class = empty($option['class']) ? 'acym_plugin_text_field' : $option['class'];
1042 - $currentOption .= '<input type="number"'.$min.$max.' name="'.$option['name'].$suffix.'" id="'.$option['name'].$suffix.'" onchange="'.$updateFunction.'();" value="'.intval(
1043 - $option['default']
1044 - ).'" class="'.acym_escape($class).'" />';
1045 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]\').val();';
1046 - } elseif ($option['type'] === 'intextfield') {
1047 - $inputType = 'text';
1048 - if (!empty($option['isNumber']) && $option['isNumber'] === 1) $inputType = 'number';
1049 - $currentOption .= acym_translationSprintf(
1050 - $option['text'],
1051 - '<input type="'.$inputType.'" name="'.$option['name'].$suffix.'" id="'.$option['name'].$suffix.'" class="intext_input" value="'.acym_escape(
1052 - $option['default']
1053 - ).'" onchange="'.$updateFunction.'();"/>'
1054 - );
1055 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]\').val();';
1056 - } elseif ($option['type'] === 'date') {
1057 - $relativeTime = '-';
1058 - if (!empty($option['relativeDate'])) $relativeTime = $option['relativeDate'];
1059 - if (!empty($option['default']) && !is_numeric($option['default']) && false === strpos($option['default'], '[time]')) {
1060 - $option['default'] = strtotime($option['default']);
1061 - }
1062 - $currentOption .= acym_dateField($option['name'].$suffix, $option['default'], '', ' onchange="'.$updateFunction.'();"', $relativeTime);
1063 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'input[name="'.$option['name'].$suffix.'"]\').val();';
1064 - } elseif ($option['type'] === 'language') {
1065 - $languageOptions = [];
1066 - $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>';
1067 1150
1068 - $languages = acym_getLanguages(true);
1069 - foreach ($languages as $language) {
1070 - $languageOptions[$language->language] = $language->name;
1071 - }
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 + }
1072 1169
1073 - if (empty($option['default'])) {
1074 - $option['default'] = acym_getVar('string', 'language');
1075 - if (acym_isMultilingual() && (empty($option['default']) || $option['default'] === 'main')) {
1076 - $option['default'] = $this->config->get('multilingual_default');
1077 - }
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).'"';
1078 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 + }
1079 1203
1080 - $currentOption .= acym_select(
1081 - $languageOptions,
1082 - $option['name'].$suffix,
1083 - empty($option['default']) ? null : $option['default'],
1084 - [
1085 - 'onchange' => $updateFunction.'();',
1086 - 'id' => $option['name'].$suffix,
1087 - ]
1088 - );
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 + }
1089 1219
1090 - $jsOptionsMerge[] = 'otherinfo += "| '.$option['name'].':" + jQuery(\'[name="'.$option['name'].$suffix.'"]\').val();';
1091 - } elseif ($option['type'] == 'custom') {
1092 - $currentOption .= $option['output'];
1093 - $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);
1094 1228 }
1229 + }
1095 1230
1096 - if (!empty($option['main']) || in_array($option['type'], ['pictures', 'checkbox'])) {
1097 - $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 + }
1098 1236
1099 - if ($option['type'] === 'checkbox' && $currentLabel === 'ACYM_DISPLAY' && (!isset($option['format']) || $option['format'])) {
1100 - $formatOption = '<div class="grid-x">';
1101 - $formatOption .= '<div class="cell large-3">'.acym_translation('ACYM_FORMAT').'</div>';
1102 - $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 + }
1103 1244
1104 - $default = empty($defaultValues->format) ? 'TOP_LEFT' : $defaultValues->format;
1105 - $formats = ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT'];
1106 - foreach ($formats as $oneFormat) {
1107 - $class = 'button-radio';
1108 - 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 + );
1109 1256
1110 - $formatOption .= '<button
1111 - class="'.$class.'"
1112 - acym-button-radio-group="dcontentFormat'.$suffix.'"
1113 - acym-data-type="'.$oneFormat.'"
1114 - acym-callback="'.$updateFunction.'">
1115 - <img alt="'.$oneFormat.'" src="'.ACYM_IMAGES.'editor/dcontent_formats/'.strtolower($oneFormat).'.png"/>
1116 - </button>';
1117 - }
1118 - $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 + }
1119 1283
1120 - if ($type === 'grouped') {
1121 - $formatOption .= '<div class="cell large-3">'.acym_translation('ACYM_ALTERNATE').acym_info('ACYM_ALTERNATE_DESC').'</div>';
1122 - $formatOption .= '<div class="cell large-9">';
1123 - $formatOption .= acym_boolean(
1124 - 'alternate'.$suffix,
1125 - !empty($defaultValues->alternate),
1126 - 'alternate'.$suffix,
1127 - ['onclick' => $updateFunction.'();']
1128 - );
1129 - $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 + }
1130 1290
1131 - $jsOptionsMerge[] = 'var alternate = jQuery(\'input[name="alternate'.$suffix.'"]:checked\').val();';
1132 - $jsOptionsMerge[] = 'if (!acym_helper.empty(alternate)) otherinfo += "| alternate";';
1133 - }
1291 + if (!isset($option['default'])) {
1292 + $option['default'] = [];
1293 + }
1294 + if (!is_array($option['default'])) {
1295 + $option['default'] = explode(',', $option['default']);
1296 + }
1134 1297
1135 - $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 + }
1136 1308
1137 - $jsOptionsMerge[] = 'var selectedFormatOption = jQuery(\'.button-radio-selected[acym-button-radio-group="dcontentFormat'.$suffix.'"]\')';
1138 - $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 + }
1139 1315
1140 - $outputStructure['topOptions']['ACYM_FORMAT'] = $formatOption;
1141 - }
1142 - 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');
1143 1320 }
1321 + }
1144 1322
1145 - if (empty($option['section'])) {
1146 - $option['section'] = 'ACYM_OTHER_OPTIONS';
1147 - }
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 + }
1148 1335
1149 - $currentLabel = acym_translation($currentLabel);
1150 - if (!empty($option['tooltip'])) {
1151 - $currentLabel .= '&nbsp;'.acym_info($option['tooltip'], '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';
1152 1350 }
1153 - $currentLabel = '<label class="cell large-5 acym_plugin_field acym_plugin_field_'.$option['type'].'" for="'.acym_escape(
1154 - $option['name'].$suffix
1155 - ).'">'.$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>';
1156 1359
1157 - $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>';
1158 1373 }
1159 1374
1160 - if (!empty($outputStructure['options'])) {
1161 - foreach ($outputStructure['options'] as $section => $options) {
1162 - $formattedOptions = '';
1163 - foreach ($options as $label => $option) {
1164 - $formattedOptions .= '<div class="cell grid-x margin-bottom-1">'.$label;
1165 - $formattedOptions .= '<div class="cell large-7">'.$option.'</div>';
1166 - $formattedOptions .= '</div>';
1375 + echo '</div>';
1376 + }
1377 +
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 + ';
1167 1385 }
1168 - $outputStructure['topOptions'][$section] = $formattedOptions;
1169 - }
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;
1170 1473 }
1474 + }
1171 1475
1172 - $output = '';
1173 - if (!empty($outputStructure['topOptions'])) {
1174 - foreach ($outputStructure['topOptions'] as $label => $oneOption) {
1175 - $output .= '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">';
1176 - $output .= acym_translation($label).'<i class="acymicon-keyboard-arrow-up"></i>';
1177 - $output .= '</p>';
1178 - $output .= '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container grid-x">';
1179 - $output .= $oneOption;
1180 - $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;
1181 1488 }
1182 - }
1183 1489
1184 - $output .= '
1185 - <script type="text/javascript">
1186 - var _selectedRows'.$suffix.' = [];
1187 - var _selectedRows = [];
1188 - if("undefined" === typeof _additionalInfo'.$suffix.') {
1189 - var _additionalInfo'.$suffix.' = {};
1190 - }
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).';
1191 1493 ';
1192 - if (!empty($defaultValues->id) && (empty($defaultValues->defaultPluginTab) || $dynamicIdentifier === $defaultValues->defaultPluginTab)) {
1193 - $delimiter = strpos($defaultValues->id, '-') ? '-' : ',';
1194 - $selected = explode($delimiter, $defaultValues->id);
1494 + continue;
1495 + }
1195 1496
1196 - foreach ($selected as $value) {
1197 - if (empty($value)) continue;
1198 - $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();
1199 1500 ';
1501 + continue;
1200 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 + );
1201 1508 }
1509 + }
1202 1510
1203 - $output .= '
1204 - function applyContent'.$suffix.'(contentid, row){
1205 - if(_selectedRows'.$suffix.'[contentid]){
1206 - jQuery(row).removeClass("selected_row");
1207 - delete _selectedRows'.$suffix.'[contentid];
1208 - }else{
1209 - ';
1511 + private function displayDynamicScript(array $jsOptions, string $suffix, string $updateFunction, string $dynamicIdentifier, string $type, $defaultValues): void
1512 + {
1513 + $storageVar = 'window._additionalInfo'.$suffix;
1210 1514
1211 - if ('individual' === $type) {
1212 - $output .= '
1213 - for(let elementKey in _selectedRows'.$suffix.') {
1214 - if(!_selectedRows'.$suffix.'.hasOwnProperty(elementKey)) continue;
1215 -
1216 - jQuery(\'[data-id="\' + elementKey + \'"]\').removeClass("selected_row");
1217 - delete _selectedRows'.$suffix.'[elementKey];
1218 - }
1219 - ';
1220 - }
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).' || {};';
1221 1519
1222 - $output .= '
1223 - jQuery(row).addClass("selected_row");
1224 - _selectedRows'.$suffix.'[contentid] = true;
1225 - }
1226 - '.$updateFunction.'();
1227 -
1228 - if(typeof _selectedRows !== "undefined"){
1229 - _selectedRows = _selectedRows'.$suffix.';
1230 - }
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;
1231 1525 }
1232 -
1233 - function '.$updateFunction.'(){
1234 - var tag = "";
1235 - var otherinfo = "";
1236 -
1237 - '.implode("\r\n\r\n", $jsOptionsMerge).'
1238 -
1239 - for (let [index, info] of Object.entries(_additionalInfo'.$suffix.')){
1240 - otherinfo += "| "+index+":"+info;
1241 - }
1242 - ';
1526 + echo '_selectedRows'.esc_attr($suffix).'['.intval($value).'] = true;';
1527 + }
1528 + }
1243 1529
1244 - if ($type == 'individual') {
1245 - $output .= '
1246 - for(var i in _selectedRows'.$suffix.'){
1247 - if(!_selectedRows'.$suffix.'.hasOwnProperty(i)) continue;
1248 -
1249 - tag = tag + "{'.$dynamicIdentifier.':" + i + otherinfo + "}";
1250 - }';
1251 - } elseif ($type == 'grouped') {
1252 - $output .= '
1253 - tag = "{'.$dynamicIdentifier.':";
1254 - for(var icat in _selectedRows'.$suffix.'){
1255 - if(!_selectedRows'.$suffix.'.hasOwnProperty(icat)) continue;
1256 - tag += icat + "-";
1257 - }
1258 - tag += otherinfo + "}";';
1259 - } elseif ($type == 'simple') {
1260 - $output .= '
1261 - 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 '}';
1262 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 '}';
1263 1548
1264 - $output .= '
1265 - acym_editorWysidDynamic.insertDContent(tag);
1266 - }
1267 -
1268 - function addAdditionalInfo'.$suffix.'(index, value){
1269 - _additionalInfo'.$suffix.'[index] = value;
1270 - '.$updateFunction.'();
1271 - }
1272 - </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; }';
1273 1555
1274 - if ($type == 'individual') {
1275 - acym_trigger('displayCustomViewEditor', [&$output], 'plgAcym'.ucfirst($dynamicIdentifier));
1276 - $output .= '<input type="hidden" id="acym__dynamic__update__function" value="'.$updateFunction.'">';
1556 + if ($type === 'individual') {
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 + "}";';
1277 1570 }
1278 1571
1279 - return $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>';
1280 1580 }
1281 1581
1582 + /**
1583 + * In Joomla, we can translate elements in extensions using the FaLang extension
1584 + */
1282 1585 public function translateItem(object &$item, object &$tag, string $referenceTable, int $referenceId = 0): void
1283 1586 {
1284 1587 if (!acym_isExtensionActive('com_falang')) {
1285 1588 return;
@@ -1289,11 +1592,14 @@
1289 1592 $langId = $tag->falang;
1290 1593 } elseif (!empty($tag->lang)) {
1291 1594 $langId = intval(substr($tag->lang, strpos($tag->lang, ',') + 1));
1292 1595 } elseif (!empty($this->contextLanguage)) {
1293 - $languages = acym_loadObjectList('SELECT `lang_id`, `lang_code` FROM #__languages', 'lang_code');
1294 - if (!empty($languages[$this->contextLanguage])) {
1295 - $langId = $languages[$this->contextLanguage]->lang_id;
1596 + $languages = acym_loadObjectList('SELECT `lang_id`, `lang_code`, `sef` FROM #__languages');
1597 + foreach ($languages as $language) {
1598 + if (in_array($this->contextLanguage, [$language->lang_code, $language->sef])) {
1599 + $langId = $language->lang_id;
1600 + break;
1601 + }
1296 1602 }
1297 1603 }
1298 1604
1299 1605 if (empty($langId)) {
@@ -1349,11 +1655,12 @@
1349 1655
1350 1656 $language = acym_getVar('string', 'language', 'main');
1351 1657 if (!empty($language)) {
1352 1658 if ($language === 'main') {
1353 - $language = $this->config->get('multilingual_default', ACYM_DEFAULT_LANGUAGE);
1659 + $language = acym_getDefaultNewsletterLanguage();
1354 1660 }
1355 1661 $email->links_language = $language;
1662 + $email->language = $language;
1356 1663 }
1357 1664
1358 1665 $email->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
1359 1666 $email->creator_id = acym_currentUserId();
@@ -1363,9 +1670,11 @@
1363 1670 $email->settings = '';
1364 1671 $email->stylesheet = '';
1365 1672 $email->attachments = '';
1366 1673
1674 + // This is only the dynamic text/content code
1367 1675 $email->body = $code;
1676 + // This is the whole editor current content
1368 1677 $email->previewBody = $previewBody;
1369 1678
1370 1679 return $email;
1371 1680 }