PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.1
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 +0 -1619 11.0.2 → 11.1.1 View file →
@@ -1,1619 +1,0 @@
1 -<?php
2 -
3 -namespace AcyMailing\Helpers;
4 -
5 -use AcyMailing\Classes\MailClass;
6 -use AcyMailing\Core\AcymObject;
7 -
8 -class PluginHelper extends AcymObject
9 -{
10 - private MailerHelper $mailerHelper;
11 - public string $name = 'content';
12 - public string $wrappedText = '';
13 - public string $contextLanguage;
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 - */
20 - public function getFormattedResult(array $elements, object $parameter): string
21 - {
22 - //We do not add an extra table or whatever when there is a single element...
23 - if (count($elements) < 2) {
24 - return implode('', $elements);
25 - }
26 -
27 - $beforeAll = [];
28 - $beforeAll['table'] = '<table cellspacing="0" cellpadding="0" border="0" width="100%" class="elementstable">'."\n";
29 - $beforeAll['ul'] = '<ul class="elementsul">'."\n";
30 - $beforeAll['br'] = '';
31 -
32 - $beforeBlock = [];
33 - $beforeBlock['table'] = '<tr class="elementstable_tr numrow{rownum}">'."\n";
34 - $beforeBlock['ul'] = '';
35 - $beforeBlock['br'] = '';
36 -
37 - $beforeOne = [];
38 - $beforeOne['table'] = '<td valign="top" width="{equalwidth}" style="{padding}" class="elementstable_td numcol{numcol}" >'."\n";
39 - $beforeOne['ul'] = '<li class="elementsul_li numrow{rownum}">'."\n";
40 - $beforeOne['br'] = '';
41 -
42 - $afterOne = [];
43 - $afterOne['table'] = '</td>'."\n";
44 - $afterOne['ul'] = '</li>'."\n";
45 - $afterOne['br'] = '<br />'."\n";
46 -
47 - $afterBlock = [];
48 - $afterBlock['table'] = '</tr>'."\n";
49 - $afterBlock['ul'] = '';
50 - $afterBlock['br'] = '';
51 -
52 - $afterAll = [];
53 - $afterAll['table'] = '</table>'."\n";
54 - $afterAll['ul'] = '</ul>'."\n";
55 - $afterAll['br'] = '';
56 -
57 -
58 - $type = 'table';
59 - $cols = 1;
60 - if (!empty($parameter->displaytype)) {
61 - $type = $parameter->displaytype;
62 - }
63 - if ($type == 'none') {
64 - return implode('', $elements);
65 - }
66 - if (!empty($parameter->cols)) {
67 - $cols = $parameter->cols;
68 - }
69 -
70 - $parameter->hpadding = !isset($parameter->hpadding) || is_null($parameter->hpadding) ? 10 : $parameter->hpadding;
71 - $parameter->vpadding = !isset($parameter->vpadding) || is_null($parameter->vpadding) ? 10 : $parameter->vpadding;
72 -
73 - $horizontalPadding = round($parameter->hpadding / 2);
74 - $verticalPadding = round($parameter->vpadding / 2);
75 -
76 - $string = $beforeAll[$type];
77 - $a = 0;
78 - $numrow = 1;
79 - foreach ($elements as $key => $oneElement) {
80 - $topPadding = $verticalPadding.'px';
81 - $rightPadding = $horizontalPadding.'px';
82 - $bottomPadding = $verticalPadding.'px';
83 - $leftPadding = $horizontalPadding.'px';
84 -
85 - if ($a == $cols) {
86 - $string .= $afterBlock[$type];
87 - $a = 0;
88 - $numrow++;
89 - }
90 -
91 - if ($a == 0) {
92 - $string .= str_replace('{rownum}', $numrow, $beforeBlock[$type]);
93 - $leftPadding = '0px';
94 - }
95 -
96 - if ($a + 1 == $cols) $rightPadding = '0px';
97 - if ($numrow == 1) $topPadding = '0px';
98 - if (round(count($elements) / $cols) == $numrow) $bottomPadding = '0px';
99 -
100 - $padding = 'padding: '.$topPadding.' '.$rightPadding.' '.$bottomPadding.' '.$leftPadding.' !important;';
101 -
102 - $string .= str_replace('{numcol}', $a + 1, $beforeOne[$type]).$oneElement.$afterOne[$type];
103 - $string = str_replace('{padding}', $padding, $string);
104 - $a++;
105 - }
106 - while ($cols > $a) {
107 - $string .= str_replace('{numcol}', $a + 1, $beforeOne[$type]).$afterOne[$type];
108 - $a++;
109 - }
110 -
111 - $string .= $afterBlock[$type];
112 - $string .= $afterAll[$type];
113 -
114 - $equalwidth = intval(100 / $cols).'%';
115 -
116 - return str_replace(['{equalwidth}'], [$equalwidth], $string);
117 - }
118 -
119 - /**
120 - * This function will apply extra parameters such as part:first, ucfirst, strtolower to the $string
121 - */
122 - public function formatString(&$replaceme, object $mytag): void
123 - {
124 - if (!empty($mytag->part)) {
125 - $parts = explode(' ', $replaceme);
126 - if ($mytag->part == 'last') {
127 - $replaceme = count($parts) > 1 ? end($parts) : '';
128 - } else {
129 - if (is_numeric($mytag->part) && count($parts) >= $mytag->part) {
130 - $replaceme = $parts[$mytag->part - 1];
131 - } else {
132 - $replaceme = reset($parts);
133 - }
134 - }
135 - }
136 -
137 - if (!empty($mytag->type)) {
138 - if (empty($mytag->format)) {
139 - $mytag->format = acym_translation('ACYM_DATE_FORMAT_LC3');
140 - }
141 - if ($mytag->type == 'date') {
142 - $replaceme = acym_getDate(acym_getTime($replaceme), $mytag->format);
143 - } elseif ($mytag->type == 'time') {
144 - $replaceme = acym_getDate($replaceme, $mytag->format);
145 - } elseif ($mytag->type == 'diff') {
146 - try {
147 - //We have a date? Sure?
148 - $date = $replaceme;
149 - if (is_numeric($date)) {
150 - $date = acym_getDate($replaceme, '%Y-%m-%d %H:%M:%S');
151 - }
152 - $dateObj = new \DateTime($date);
153 - $nowObj = new \DateTime();
154 - $diff = $dateObj->diff($nowObj);
155 - $replaceme = $diff->format($mytag->format);
156 - } catch (\Exception $e) {
157 - $replaceme = 'Error using the "diff" parameter in your tag. Please make sure the DateTime() and diff() functions are available on your server.';
158 - }
159 - }
160 - }
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
163 - if (!empty($mytag->lower) || !empty($mytag->lowercase)) {
164 - $replaceme = function_exists('mb_strtolower') ? mb_strtolower($replaceme, 'UTF-8') : strtolower($replaceme);
165 - }
166 - if (!empty($mytag->upper) || !empty($mytag->uppercase)) {
167 - $replaceme = function_exists('mb_strtoupper') ? mb_strtoupper($replaceme, 'UTF-8') : strtoupper($replaceme);
168 - }
169 - if (!empty($mytag->ucwords)) {
170 - $replaceme = ucwords($replaceme);
171 - }
172 - if (!empty($mytag->ucfirst)) {
173 - $replaceme = ucfirst($replaceme);
174 - }
175 - //Remove a character at the end of the string...
176 - if (isset($mytag->rtrim)) {
177 - $replaceme = empty($mytag->rtrim) ? rtrim($replaceme) : rtrim($replaceme, $mytag->rtrim);
178 - }
179 - if (!empty($mytag->urlencode)) {
180 - $replaceme = urlencode($replaceme);
181 - }
182 - if (!empty($mytag->substr)) {
183 - //the parameter |substr:4,6 will select the 6 characters after the 4.
184 - $args = explode(',', $mytag->substr);
185 - if (isset($args[1])) {
186 - $replaceme = substr($replaceme, intval($args[0]), intval($args[1]));
187 - } else {
188 - $replaceme = substr($replaceme, intval($args[0]));
189 - }
190 - }
191 -
192 -
193 - if (!empty($mytag->maxheight) || !empty($mytag->maxwidth)) {
194 - $imageHelper = new ImageHelper();
195 - $imageHelper->maxHeight = empty($mytag->maxheight) ? 999 : $mytag->maxheight;
196 - $imageHelper->maxWidth = empty($mytag->maxwidth) ? 999 : $mytag->maxwidth;
197 - $replaceme = $imageHelper->resizePictures($replaceme);
198 - }
199 - }
200 -
201 - public function replaceVideos(string &$text): void
202 - {
203 - //Youtube videos
204 - $text = preg_replace(
205 - '#\[embed=videolink][^}]*youtube[^=]*=([^"/}]*)[^}]*}\[/embed]#i',
206 - '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>',
207 - $text
208 - );
209 - $text = preg_replace(
210 - '#<video[^>]*youtube\.com/embed/([^"/]*)[^>]*>[^>]*</video>#i',
211 - '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>',
212 - $text
213 - );
214 - $text = preg_replace(
215 - '#{JoooidContent[^}]*youtube[^}]*id"[^"]*"([^}"]*)"[^}]*}#i',
216 - '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>',
217 - $text
218 - );
219 - $text = preg_replace(
220 - '#<iframe[^>]*src="[^"]*youtube[^"]*embed/([^"?]*)(\?[^"]*)?"[^>]*>[^<]*</iframe>#Uis',
221 - '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>',
222 - $text
223 - );
224 -
225 - $text = preg_replace(
226 - '#{youtube}[^{]+v=([^{&]+)(&[^{]*)?{/youtube}#Uis',
227 - '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>',
228 - $text
229 - );
230 -
231 - $text = preg_replace('#{vimeo}(https://vimeo.com/[^{]+){/vimeo}#Uis', '<iframe src="$1"></iframe>', $text);
232 - $text = preg_replace('#{vimeo}([^{]+){/vimeo}#Uis', '<iframe src="https://player.vimeo.com/video/$1"></iframe>', $text);
233 -
234 - // Vimeo iframes
235 - if (preg_match_all('#<iframe[^>]*src="[^"]*vimeo[^"]*/(\d+)([&/\?][^"]*)?"[^>]*>[^<]*</iframe>#Uis', $text, $matches)) {
236 - foreach ($matches[1] as $key => $match) {
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...
239 - $hash = @unserialize($hash);
240 - if (empty($hash)) continue;
241 -
242 - if (strpos($matches[0][$key], ' width="') !== false) {
243 - $extension = substr($hash[0]['thumbnail_large'], strrpos($hash[0]['thumbnail_large'], '.'));
244 - preg_match('#width="([^"]*)"#Uis', $matches[0][$key], $width);
245 -
246 - $replace = strpos($hash[0]['thumbnail_large'], '_') === false ? '.' : '_';
247 - $hash[0]['thumbnail_large'] = substr($hash[0]['thumbnail_large'], 0, strrpos($hash[0]['thumbnail_large'], $replace)).'_'.$width[1].$extension;
248 - }
249 - $thumbnail = 'https://i.vimeocdn.com/filter/overlay?src='.urlencode($hash[0]['thumbnail_large']);
250 - $thumbnail .= '&src='.urlencode('https://f.vimeocdn.com/p/images/crawler_play.png');
251 -
252 - $text = str_replace(
253 - $matches[0][$key],
254 - '<a target="_blank" href="'.esc_attr($hash[0]['url']).'"><img class="donotresize" alt="" src="'.esc_attr($thumbnail).'" /></a>',
255 - $text
256 - );
257 - }
258 - }
259 -
260 - //Other videos
261 - $text = preg_replace('#\[embed=videolink][^}]*video":"([^"]*)[^}]*}\[/embed]#i', '<a target="_blank" href="$1"><img src="'.ACYM_IMAGES.'/video.png"/></a>', $text);
262 - $text = preg_replace('#<video[^>]*src="([^"]*)"[^>]*>[^>]*</video>#i', '<a target="_blank" href="$1"><img src="'.ACYM_IMAGES.'/video.png"/></a>', $text);
263 - }
264 -
265 - /**
266 - * Convert pictures base64 code into a real picture
267 - */
268 - private function convertbase64pictures(string &$html): void
269 - {
270 - if (!preg_match_all('#<img[^>]*src=("data:image/([^;]{1,5});base64[^"]*")([^>]*)>#Uis', $html, $resultspictures)) {
271 - return;
272 - }
273 -
274 - //Just in case of... we will need it
275 -
276 -
277 - $dest = ACYM_MEDIA.'resized'.DS;
278 - acym_createDir($dest);
279 - foreach ($resultspictures[2] as $i => $extension) {
280 - $pictname = md5($resultspictures[1][$i]).'.'.$extension;
281 - $picturl = ACYM_LIVE.str_replace(DS, '/', ACYM_MEDIA_FOLDER).'resized/'.$pictname;
282 - $pictPath = $dest.$pictname;
283 - $pictCode = trim($resultspictures[1][$i], '"');
284 - if (file_exists($pictPath)) {
285 - //The picture is already there... lets use it then
286 - $html = str_replace($pictCode, $picturl, $html);
287 - continue;
288 - }
289 -
290 - if (!ini_get('allow_url_fopen')) {
291 - continue;
292 - }
293 -
294 - $getfunction = '';
295 - switch ($extension) {
296 - case 'gif':
297 - $getfunction = 'ImageCreateFromGIF';
298 - break;
299 - case 'jpg':
300 - case 'jpeg':
301 - $getfunction = 'ImageCreateFromJPEG';
302 - break;
303 - case 'png':
304 - $getfunction = 'ImageCreateFromPNG';
305 - break;
306 - }
307 -
308 - //The function does not exists or we didn't find the right function... we just skip that action
309 - if (empty($getfunction) || !function_exists($getfunction)) {
310 - continue;
311 - }
312 -
313 - $img = $getfunction($pictCode);
314 - if ($img === false) {
315 - continue;
316 - }
317 -
318 - if (in_array($extension, ['gif', 'png'])) {
319 - imagealphablending($img, false);
320 - imagesavealpha($img, false);
321 - }
322 -
323 - //We display the result and then save it to avoid the issue with FTP configurations
324 - ob_start();
325 - switch ($extension) {
326 - case 'gif':
327 - $status = imagegif($img);
328 - break;
329 - case 'jpg':
330 - case 'jpeg':
331 - $status = imagejpeg($img, null, 100);
332 - break;
333 - case 'png':
334 - $status = imagepng($img, null, 1);
335 - break;
336 - }
337 - $imageContent = ob_get_clean();
338 - $status = $status && acym_writeFile($pictPath, $imageContent);
339 -
340 - //we could not save or convert the picture, we skip it
341 - if (!$status) {
342 - continue;
343 - }
344 - $html = str_replace($pictCode, $picturl, $html);
345 - }
346 - }
347 -
348 - //This function will remove or replace code we should not have in an html view.
349 - public function cleanHtml(string &$html): void
350 - {
351 - $this->convertbase64pictures($html);
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>
355 - $pregreplace = [];
356 - $pregreplace['#<tr([^>"]*>([^<]*<td[^>]*>[ \n\s]*<img[^>]*>[ \n\s]*</ *td[^>]*>[ \n\s]*)*</ *tr)#Uis'] = '<tr style="line-height: 0px;" $1';
357 - $pregreplace['#<td(((?!style|>).)*>[ \n\s]*(<a[^>]*>)?[ \n\s]*<img[^>]*>[ \n\s]*(</a[^>]*>)?[ \n\s]*</ *td)#Uis'] = '<td style="line-height: 0px;" $1';
358 -
359 - //No number tab system {tab=...}{/tabs} or {tab }{/tabs} and jcomments system
360 - $pregreplace['#{tab[ =][^}]*}#is'] = '';
361 - $pregreplace['#{/tabs}#is'] = '';
362 - $pregreplace['#{jcomments\s+(on|off|lock)}#is'] = '';
363 -
364 - //Remove the JS...
365 - $pregreplace["#(onmouseout|onmouseover|onclick|onfocus|onload|onblur) *= *\"(?:(?!\").)*\"#Ui"] = '';
366 - $pregreplace["#< *script(?:(?!< */ *script *>).)*< */ *script *>#Uis"] = '';
367 - $pregreplace["#< *iframe(?:(?!< */ *iframe *>).)*< */ *iframe *>#Uis"] = '';
368 -
369 - // May God punish every person using Outlook, or imposing this monstrosity for his employees
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
372 - $pregreplace['#(<img [^>]*src="[^"]+\.webp"[^>]*>)#Uis'] = '<!--[if !mso]><!-->$1<!--<![endif]-->';
373 -
374 - $newbody = preg_replace(array_keys($pregreplace), $pregreplace, $html);
375 - //we do it in two steps as this regex can break the page
376 - if (!empty($newbody)) {
377 - $html = $newbody;
378 - }
379 -
380 - $body = preg_replace_callback('/src="([^"]* [^"]*)"/Ui', [$this, 'convertSpaces'], $html);
381 - if (!empty($body)) $html = $body;
382 -
383 - $html = acym_cmsCleanHtml($html);
384 - }
385 -
386 - public function convertSpaces(array $matches): string
387 - {
388 - return "src='".str_replace(' ', '%20', $matches[1])."'";
389 - }
390 -
391 - /*
392 - * Replace tags in all the email variables where it is possible, in text version or html version
393 - */
394 - public function replaceTags(object &$email, array $tags, bool $html = false): void
395 - {
396 - if (empty($tags)) return;
397 -
398 - $htmlVars = ['body'];
399 - $textVars = [
400 - 'subject',
401 - 'AltBody',
402 - 'From',
403 - 'FromName',
404 - 'ReplyTo',
405 - 'ReplyName',
406 - 'bcc',
407 - 'cc',
408 - 'fromname',
409 - 'fromemail',
410 - 'replyname',
411 - 'replyemail',
412 - 'params',
413 - 'preheader',
414 - ];
415 -
416 - $variables = array_merge($htmlVars, $textVars);
417 -
418 - if ($html) {
419 - if (empty($this->mailerHelper)) {
420 - $this->mailerHelper = new MailerHelper();
421 - }
422 -
423 - $textreplace = [];
424 - foreach ($tags as $i => $replacement) {
425 - if (isset($textreplace[$i])) continue;
426 - $textreplace[$i] = $this->mailerHelper->textVersion($replacement);
427 - }
428 - } else {
429 - $textreplace = $tags;
430 - }
431 -
432 - foreach ($variables as $var) {
433 - if (empty($email->$var)) continue;
434 - $email->$var = $this->replaceDText($email->$var, in_array($var, $htmlVars) ? $tags : $textreplace);
435 - }
436 - }
437 -
438 - public function replaceDText($text, $replacement)
439 - {
440 - if (is_array($text)) {
441 - foreach ($text as &$oneCell) {
442 - if (empty($oneCell)) continue;
443 - $oneCell = $this->replaceDText($oneCell, $replacement);
444 - }
445 - } elseif (is_string($text) && !empty($text)) {
446 - foreach ($replacement as $code => $value) {
447 - $codes = [$code, urlencode($code)];
448 - if (is_null($value)) {
449 - $value = '';
450 - }
451 - $safePregValue = str_replace('$', '\$', $value);
452 -
453 - foreach ($codes as $oneCode) {
454 - // Dtext specific syntax
455 - $text = preg_replace(
456 - '#<span[^>]+'.preg_quote($oneCode, '#').'.+</em>[^<]*</span>#Uis',
457 - $safePregValue,
458 - $text
459 - );
460 -
461 - // Dcontent specific syntax
462 - $text = preg_replace(
463 - '#(<tr[^>]+)data-dynamic="'.preg_quote($oneCode, '#').'"([^>]+>[^<]*<td[^>]*>).+</i>[^<]*</td>[^<]*</tr>#Uis',
464 - '${1}${2}'.$safePregValue.'</td></tr>',
465 - $text
466 - );
467 -
468 - // If the code was inserted directly in the email (in the subject for example, or a copy-paste)
469 - $text = str_replace($oneCode, $value, $text);
470 - }
471 - }
472 - }
473 -
474 - return $text;
475 - }
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 - */
481 - public function extractTags(object $email, string $tagfamily): array
482 - {
483 - $results = [];
484 -
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!
487 - $variables = [
488 - 'subject',
489 - 'AltBody',
490 - 'body',
491 - 'From',
492 - 'FromName',
493 - 'ReplyTo',
494 - 'ReplyName',
495 - 'bcc',
496 - 'cc',
497 - 'fromname',
498 - 'fromemail',
499 - 'replyname',
500 - 'replyemail',
501 - 'params',
502 - 'preheader',
503 - ];
504 - $found = false;
505 - foreach ($variables as $var) {
506 - if (empty($email->$var)) continue;
507 -
508 - if (is_array($email->$var)) {
509 - foreach ($email->$var as $i => $arrayField) {
510 - if (empty($arrayField)) continue;
511 -
512 - if (is_array($arrayField)) {
513 - foreach ($arrayField as $a => $oneval) {
514 - $found = preg_match_all($match, $oneval, $results[$var.$i.'-'.$a]) || $found;
515 - if (empty($results[$var.$i.'-'.$a][0])) unset($results[$var.$i.'-'.$a]);
516 - }
517 - } else {
518 - $found = preg_match_all($match, $arrayField, $results[$var.$i]) || $found;
519 - if (empty($results[$var.$i][0])) unset($results[$var.$i]);
520 - }
521 - }
522 - } else {
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
525 - if (empty($results[$var][0])) unset($results[$var]);
526 - }
527 - }
528 -
529 - //If we didn't find anything...
530 - if (!$found) {
531 - return [];
532 - }
533 -
534 - $tags = [];
535 - foreach ($results as $var => $allresults) {
536 - foreach ($allresults[0] as $i => $oneTag) {
537 - //Don't need to process twice a tag we already have!
538 - if (isset($tags[$oneTag])) {
539 - continue;
540 - }
541 - $tags[$oneTag] = $this->extractTag($allresults[1][$i]);
542 - }
543 - }
544 -
545 - return $tags;
546 - }
547 -
548 - /**
549 - * Converts a tag 23|file:myfile|price into an object.
550 - */
551 - public function extractTag(string $oneTag): \stdClass
552 - {
553 - $oneTag = str_replace(['[time]+', '[time]-'], [urlencode('[time]+'), urlencode('[time]-')], $oneTag);
554 - $arguments = explode('|', acym_stripTags(urldecode($oneTag)));
555 - $tag = new \stdClass();
556 - $tag->id = $arguments[0];
557 - $tag->default = '';
558 - for ($i = 1, $a = count($arguments); $i < $a; $i++) {
559 - $args = explode(':', $arguments[$i], 2);
560 - $arg0 = trim($args[0]);
561 - if (empty($arg0)) continue;
562 -
563 - if (isset($args[1])) {
564 - $tag->$arg0 = $args[1];
565 - //We may have an extra parameter, especially for date format.
566 - if (isset($args[2])) {
567 - $tag->{$args[0]} .= ':'.$args[2];
568 - }
569 - } else {
570 - $tag->$arg0 = true;
571 - }
572 - }
573 -
574 - return $tag;
575 - }
576 -
577 - /**
578 - * Wrap the text using the wrapValue
579 - */
580 - public function wrapText(string $text, object $tag): string
581 - {
582 - if (empty($tag->wrap)) {
583 - return $text;
584 - }
585 -
586 - $tag->wrap = intval($tag->wrap);
587 -
588 - $newText = str_replace(['&lt', '&gt'], ['<', '>'], $text);
589 -
590 - if (mb_strlen(acym_stripTags($newText)) <= $tag->wrap) {
591 - return $newText;
592 - }
593 -
594 - if (!class_exists('DOMDocument', false)) {
595 - return mb_substr(acym_stripTags($newText), 0, $tag->wrap).'…';
596 - }
597 -
598 - if (function_exists('libxml_use_internal_errors')) {
599 - libxml_use_internal_errors(true);
600 - }
601 -
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 - );
607 -
608 - $count = 0;
609 - $this->truncateNode($dom, $dom, $tag->wrap, $count);
610 -
611 - return $dom->saveHTML();
612 - }
613 -
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);
618 -
619 - // Text node
620 - if ($child->nodeType === XML_TEXT_NODE) {
621 - $text = $child->nodeValue;
622 - $len = mb_strlen($text);
623 -
624 - if ($count + $len > $limit) {
625 - $remaining = $limit - $count;
626 -
627 - // Take the max allowed slice
628 - $slice = mb_substr($text, 0, $remaining);
629 -
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]);
633 - }
634 -
635 - // Final assignment
636 - $child->nodeValue = $slice.'…';
637 -
638 - // Remove remaining siblings
639 - while ($child->nextSibling) {
640 - $node->removeChild($child->nextSibling);
641 - }
642 -
643 - return true;
644 - }
645 -
646 - $count += $len;
647 - }
648 -
649 - // Element node → recurse
650 - if ($child->hasChildNodes() && $this->truncateNode($dom, $child, $limit, $count)) {
651 - while ($child->nextSibling) {
652 - $node->removeChild($child->nextSibling);
653 - }
654 -
655 - return true;
656 - }
657 - }
658 -
659 - return false;
660 - }
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 - */
674 - public function getStandardDisplay(object $format): string
675 - {
676 - // By default, put float left on the picture
677 - if (empty($format->tag->format)) {
678 - $format->tag->format = 'TOP_LEFT';
679 - }
680 - if (!in_array($format->tag->format, ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT'])) {
681 - $format->tag->format = 'TOP_LEFT';
682 - }
683 -
684 - $invertValues = [
685 - 'TOP_LEFT' => 'TOP_RIGHT',
686 - 'TITLE_IMG' => 'TITLE_IMG_RIGHT',
687 - 'COL_LEFT' => 'COL_RIGHT',
688 - 'TOP_RIGHT' => 'TOP_LEFT',
689 - 'TITLE_IMG_RIGHT' => 'TITLE_IMG',
690 - 'COL_RIGHT' => 'COL_LEFT',
691 - ];
692 - if (!empty($format->tag->invert) && !empty($invertValues[$format->tag->format])) {
693 - $format->tag->format = $invertValues[$format->tag->format];
694 - }
695 -
696 - // Get the image with float left/right or no style
697 - $image = '';
698 - if (!empty($format->imagePath)) {
699 - $style = '';
700 - $linkStyle = '';
701 -
702 - if (in_array($format->tag->format, ['TOP_LEFT', 'TITLE_IMG'])) {
703 - $style = 'left';
704 - } elseif (in_array($format->tag->format, ['TOP_RIGHT', 'TITLE_IMG_RIGHT'])) {
705 - $style = 'right';
706 - }
707 -
708 - if (!empty($style)) {
709 - $linkStyle = 'style="float:'.$style.';"';
710 -
711 - if ($style === 'left') {
712 - $style = 'style="float:left; margin-right: 7px; margin-bottom: 7px;"';
713 - } else {
714 - $style = 'style="float:right; margin-left: 7px; margin-bottom: 7px;"';
715 - }
716 - }
717 -
718 - preg_match('#src="([^"]+)"#Uis', $format->imagePath, $matches);
719 - if (!empty($matches[1])) $format->imagePath = $matches[1];
720 - $altImage = !empty($format->altImage) ? $format->altImage : '';
721 - $image = '<img class="content_main_image" alt="'.esc_attr($altImage).'" src="'.$format->imagePath.'" '.$style.' />';
722 -
723 - if (!empty($format->imageCaption) && !in_array($format->tag->format, ['TITLE_IMG', 'TITLE_IMG_RIGHT'])) {
724 - $image .= '<p class="content_main_image_caption">'.esc_html($format->imageCaption).'</p>';
725 - }
726 - }
727 -
728 - // If TITLE_IMG, add the image to the title
729 - $result = '';
730 - if (in_array($format->tag->format, ['TITLE_IMG', 'TITLE_IMG_RIGHT'])) {
731 - $format->title = $image.$format->title;
732 - $image = '';
733 - }
734 -
735 - if (!empty($format->link) && !empty($image) && !empty($format->tag->clickableimg)) {
736 - $image = '<a target="_blank" href="'.$format->link.'" '.$linkStyle.'>'.$image.'</a>';
737 - }
738 -
739 - // If the image should be displayed before the title, do it
740 - if ($format->tag->format === 'TOP_IMG' && !empty($image)) {
741 - $result = $image;
742 - $image = '';
743 - }
744 -
745 - // If we want to keep the left/right column for the image, put the whole article in a table
746 - if (in_array($format->tag->format, ['COL_LEFT', 'COL_RIGHT'])) {
747 - $maxWidth = empty($format->tag->maxwidth) ? '' : ' width: '.$format->tag->maxwidth.'px;';
748 - if (empty($image)) {
749 - $format->tag->format = 'TOP_LEFT';
750 - } else {
751 - $result = '<table><tr><td valign="middle" style="vertical-align: middle; padding-right: 7px;" class="acyleftcol">';
752 - if ($format->tag->format === 'COL_LEFT') {
753 - $result = '<table><tr><td valign="middle" style="vertical-align: middle; padding-right: 7px; '.$maxWidth.'" class="acyleftcol">';
754 - $result .= $image.'</td><td valign="top" class="acyrightcol">';
755 - }
756 - }
757 - }
758 -
759 - // Display the title
760 - if (!empty($format->title)) {
761 - if (!empty($format->link) && !empty($format->tag->clickable)) {
762 - if (empty($format->tag->type) || $format->tag->type !== 'title') {
763 - $format->title = '<h2 class="acym_title">'.$format->title.'</h2>';
764 - }
765 -
766 - $title = '<a';
767 - if (!empty($format->tag->type) && $format->tag->type === 'title') $title .= ' class="acym_title"';
768 - $title .= ' href="'.$format->link.'" target="_blank" name="'.$this->name.'-'.$format->tag->id.'">';
769 - $title .= $format->title;
770 - $title .= '</a>';
771 - $format->title = $title;
772 - } else {
773 - if (empty($format->tag->type) || $format->tag->type != 'title') {
774 - $format->title = '<h2 class="acym_title">'.$format->title.'</h2>';
775 - }
776 - }
777 -
778 - $result .= $format->title;
779 - }
780 -
781 - if (!empty($format->afterTitle)) {
782 - $result .= $format->afterTitle;
783 - }
784 -
785 - if (!empty($format->description)) {
786 - $format->description = $this->wrapText($format->description, $format->tag);
787 - }
788 - $this->wrappedText = $format->description;
789 -
790 -
791 - $rowText = '<div class="acydescription">';
792 - $endRow = '</div><br />';
793 - // Display the main content based on the chosen format
794 - if (in_array($format->tag->format, ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'TOP_IMG'])) {
795 - if (!empty($image) || !empty($format->description)) {
796 - $result .= $rowText.$image.$format->description.$endRow;
797 - }
798 - } elseif ($format->tag->format == 'CENTER_IMG') {
799 - if (!empty($image)) {
800 - $result .= '<div class="acymainimage">'.$image.$endRow;
801 - }
802 -
803 - if (!empty($format->description)) {
804 - $result .= $rowText.$format->description.$endRow;
805 - }
806 - } elseif (in_array($format->tag->format, ['COL_LEFT', 'COL_RIGHT'])) {
807 - if (!empty($format->description)) {
808 - $result .= $rowText.$format->description.$endRow;
809 - }
810 -
811 - if ($format->tag->format === 'COL_RIGHT') {
812 - $result .= '</td><td valign="middle" style="vertical-align: middle; padding-left: 7px; '.$maxWidth.'" class="acyrightcol">'.$image;
813 - }
814 - $result .= '</td></tr></table>';
815 - }
816 -
817 - // Add the custom fields on 1 or 2 columns, with or without the labels
818 - if (!empty($format->customFields)) {
819 - $result .= '<table style="width:100%;" class="customfieldsarea"><tr>';
820 -
821 - $format->cols = empty($format->tag->nbcols) ? 1 : intval($format->tag->nbcols);
822 - if (empty($format->cols)) $format->cols = 1;
823 -
824 - $i = 0;
825 - foreach ($format->customFields as $oneField) {
826 - if ($i != 0 && $i % $format->cols == 0) $result .= '</tr><tr>';
827 -
828 - if (empty($oneField[1])) {
829 - $result .= '<td class="cfvalue" colspan="2">';
830 - } else {
831 - $result .= '<td nowrap="nowrap" class="cflabel">'.$oneField[1].'</td><td class="cfvalue">';
832 - }
833 -
834 - $result .= $oneField[0].'</td>';
835 - $i++;
836 - }
837 -
838 - while ($i % $format->cols != 0) {
839 - $result .= '<td colspan="2"></td>';
840 - $i++;
841 - }
842 -
843 - $result .= '</tr></table>';
844 - }
845 -
846 - // Add what should be displayed at the end (share buttons, read more link, etc...)
847 - if (!empty($format->afterArticle)) {
848 - $result .= $format->afterArticle;
849 - }
850 -
851 - return $result;
852 - }
853 -
854 - /**
855 - * Resizes or removes the pictures according to the parameters
856 - */
857 - public function managePicts(object $tag, string $result): string
858 - {
859 - if (!isset($tag->pict)) {
860 - return $result;
861 - }
862 -
863 - $imageHelper = new ImageHelper();
864 - if ($tag->pict === 'resized') {
865 - $imageHelper->maxHeight = empty($tag->maxheight) ? 150 : $tag->maxheight;
866 - $imageHelper->maxWidth = empty($tag->maxwidth) ? 150 : $tag->maxwidth;
867 - if ($imageHelper->available()) {
868 - $result = $imageHelper->resizePictures($result);
869 - } elseif (acym_isAdmin()) {
870 - acym_enqueueMessage($imageHelper->error, 'notice');
871 - }
872 - } elseif ($tag->pict == '0') {
873 - $result = $imageHelper->removePictures($result);
874 - }
875 -
876 - return acym_absoluteURL($result);
877 - }
878 -
879 - public function displayOptions(array $options, string $dynamicIdentifier, string $type = 'individual', $defaultValues = null): void
880 - {
881 - $suffix = preg_replace('#[^a-zA-Z0-9]#U', '_', $dynamicIdentifier);
882 - $updateFunction = 'updateDynamic'.$suffix;
883 -
884 - $jsOptionsMerge = [];
885 - $topOptions = [];
886 - $sections = [];
887 -
888 - // ---- Phase 1: classify options + collect the JS (in original loop order) ----
889 - foreach ($options as $option) {
890 - if (isset($defaultValues->{$option['name']})) {
891 - $option['default'] = $defaultValues->{$option['name']};
892 - }
893 -
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 - }
902 -
903 - $this->appendOptionJs($jsOptionsMerge, $option, $suffix, $type);
904 -
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];
917 - }
918 - continue;
919 - }
920 -
921 - $section = empty($option['section']) ? 'ACYM_OTHER_OPTIONS' : $option['section'];
922 - $sections[$section][$option['name']] = $option;
923 - }
924 -
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 - }
931 -
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 - }
943 -
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 - }
957 -
958 - // ---- Phase 3: the inline script + individual extras ----
959 - $this->displayDynamicScript($jsOptionsMerge, $suffix, $updateFunction, $dynamicIdentifier, $type, $defaultValues);
960 -
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 - }
966 -
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 - }
974 -
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 - }
990 -
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']).'"';
1023 - }
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 - ]
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 - echo $option['output'];
1080 - break;
1081 - }
1082 - }
1083 -
1084 - private function displayPicturesField(array $option, string $suffix, string $updateFunction, $defaultValues): void
1085 - {
1086 - $displayedPictures = $option['default'] ?? 'resized';
1087 - if (isset($defaultValues->pict)) {
1088 - $displayedPictures = $defaultValues->pict;
1089 - }
1090 - $maxWidth = $defaultValues->maxwidth ?? 150;
1091 - $maxHeight = $defaultValues->maxheight ?? 150;
1092 -
1093 - $valImages = [
1094 - acym_selectOption('1', 'ACYM_YES'),
1095 - acym_selectOption('resized', 'ACYM_RESIZED'),
1096 - acym_selectOption('0', 'ACYM_NO'),
1097 - ];
1098 -
1099 - echo '<div class="cell large-5 acym_plugin_field">';
1100 - echo esc_html(acym_translation('ACYM_DISPLAY'));
1101 - echo '</div>';
1102 - echo '<div class="cell large-7">';
1103 - acym_radio(
1104 - $valImages,
1105 - 'pict'.$suffix,
1106 - $displayedPictures,
1107 - ['onclick' => $updateFunction.'();'],
1108 - ['containerClass' => 'dcontent_pictures'],
1109 - !acym_isAdmin()
1110 - );
1111 - echo '</div>';
1112 -
1113 - echo '<div id="pictsize'.esc_attr($suffix).'" class="cell grid-x margin-y margin-top-1"';
1114 - if ('resized' !== $displayedPictures) {
1115 - echo ' style="display: none;"';
1116 - }
1117 - echo '>';
1118 - echo '<div class="cell large-5 acym_plugin_field">';
1119 - echo esc_html(acym_translation('ACYM_MAX_WIDTH'));
1120 - echo '</div>';
1121 - echo '<div class="cell large-7">';
1122 - echo '<input class="intext_input"
1123 - name="pictwidth'.esc_attr($suffix).'"
1124 - type="number"
1125 - onchange="'.esc_attr($updateFunction).'();"
1126 - value="'.intval($maxWidth).'"/>';
1127 - echo '</div>';
1128 - echo '<div class="cell large-5 acym_plugin_field">';
1129 - echo esc_html(acym_translation('ACYM_MAX_HEIGHT'));
1130 - echo '</div>';
1131 - echo '<div class="cell large-7">';
1132 - echo '<input class="intext_input"
1133 - name="pictheight'.esc_attr($suffix).'"
1134 - type="number"
1135 - onchange="'.esc_attr($updateFunction).'();"
1136 - value="'.intval($maxHeight).'"/>';
1137 - echo '</div>';
1138 - echo '</div>';
1139 -
1140 - if (!empty($option['caption'])) {
1141 - echo '<div class="cell grid-x margin-top-1">';
1142 - echo '<label class="cell large-5 acym_plugin_field">';
1143 - echo esc_html(acym_translation('ACYM_CAPTION'));
1144 - echo '</label>';
1145 - acym_radio(
1146 - [
1147 - acym_selectOption('1', 'ACYM_YES'),
1148 - acym_selectOption('0', 'ACYM_NO'),
1149 - ],
1150 - 'caption'.$suffix,
1151 - $defaultValues->caption ?? '0',
1152 - ['onclick' => $updateFunction.'();'],
1153 - ['containerClass' => 'cell large-7']
1154 - );
1155 - echo '</div>';
1156 - }
1157 - }
1158 -
1159 - private function displayCheckboxField(array $option, string $suffix, string $updateFunction): void
1160 - {
1161 - if (!empty($option['default'])) {
1162 - $checkedValues = explode(',', $option['default']);
1163 - foreach ($option['options'] as $key => $oneOption) {
1164 - $oneOption[1] = in_array($key, $checkedValues);
1165 - $option['options'][$key] = $oneOption;
1166 - }
1167 - }
1168 -
1169 - echo '<div class="cell grid-x">';
1170 - foreach ($option['options'] as $value => $title) {
1171 - echo '<div class="cell medium-6" ';
1172 - if (!empty($title[2]) && is_array($title[2])) {
1173 - foreach ($title[2] as $attribute => $value) {
1174 - echo esc_attr($attribute).'="'.esc_attr($value).'"';
1175 - }
1176 - }
1177 - echo '>';
1178 - echo '<input type="checkbox"
1179 - name="'.esc_attr($option['name'].$suffix).'"
1180 - value="'.esc_attr($value).'"
1181 - id="'.esc_attr($value.$suffix).'"
1182 - onclick="'.esc_attr($updateFunction).'();" ';
1183 - acym_checked((bool)$title[1]);
1184 - echo ' />';
1185 - echo '<label style="margin-left:5px" for="'.esc_attr($value.$suffix).'">';
1186 - echo esc_html(acym_translation($title[0]));
1187 - echo '</label>';
1188 - echo '</div>';
1189 - }
1190 - echo '</div>';
1191 - }
1192 -
1193 - private function displayRadioField(array $option, string $suffix, string $updateFunction): void
1194 - {
1195 - $radioOptions = [];
1196 - foreach ($option['options'] as $value => $title) {
1197 - $radioOptions[] = acym_selectOption($value, $title);
1198 - }
1199 - acym_radio(
1200 - $radioOptions,
1201 - $option['name'].$suffix,
1202 - $option['default'],
1203 - ['onclick' => $updateFunction.'();'],
1204 - ['pluginMode' => true],
1205 - !acym_isAdmin()
1206 - );
1207 - }
1208 -
1209 - private function displaySelectField(array $option, string $suffix, string $updateFunction): void
1210 - {
1211 - $selectOptions = [];
1212 - foreach ($option['options'] as $value => $title) {
1213 - if (is_object($title)) {
1214 - $selectOptions[] = acym_selectOption($title->value, $title->text);
1215 - } else {
1216 - $selectOptions[] = acym_selectOption($value, $title);
1217 - }
1218 - }
1219 -
1220 - $default = empty($option['default']) ? null : $option['default'];
1221 - $defaultOrder = null;
1222 - if (!empty($default) && strpos($default, ',')) {
1223 - [$default, $defaultOrder] = explode(',', $default);
1224 - }
1225 -
1226 - $attributes = [
1227 - 'onchange' => $updateFunction.'();',
1228 - 'id' => $option['name'].$suffix,
1229 - ];
1230 - if ($option['name'] === 'order') {
1231 - $attributes['class'] = 'acym__dynamics__ordering__select';
1232 - }
1233 -
1234 - acym_select(
1235 - $selectOptions,
1236 - $option['name'].$suffix,
1237 - $default,
1238 - $attributes,
1239 - 'value',
1240 - 'text',
1241 - null,
1242 - false,
1243 - true
1244 - );
1245 -
1246 - if ($option['name'] === 'order') {
1247 - $dirs = [
1248 - 'desc' => acym_translation('ACYM_DESC'),
1249 - 'asc' => acym_translation('ACYM_ASC'),
1250 - ];
1251 - if (empty($defaultOrder)) {
1252 - $defaultOrder = empty($option['defaultdir']) ? null : $option['defaultdir'];
1253 - }
1254 - echo ' ';
1255 - acym_select(
1256 - $dirs,
1257 - 'orderdir'.$suffix,
1258 - $defaultOrder,
1259 - [
1260 - 'onchange' => $updateFunction.'();',
1261 - 'style' => 'width: 115px;',
1262 - 'class' => 'acym__dynamics__ordering__select',
1263 - ],
1264 - 'value',
1265 - 'text',
1266 - null,
1267 - false,
1268 - true
1269 - );
1270 - }
1271 - }
1272 -
1273 - private function displayMultiselectField(array $option, string $suffix, string $updateFunction): void
1274 - {
1275 - $selectOptions = [];
1276 - foreach ($option['options'] as $value => $title) {
1277 - $selectOptions[] = acym_selectOption($value, $title);
1278 - }
1279 -
1280 - if (!isset($option['default'])) {
1281 - $option['default'] = [];
1282 - }
1283 - if (!is_array($option['default'])) {
1284 - $option['default'] = explode(',', $option['default']);
1285 - }
1286 -
1287 - acym_selectMultiple(
1288 - $selectOptions,
1289 - $option['name'].$suffix,
1290 - $option['default'],
1291 - ['onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix],
1292 - 'value',
1293 - 'text',
1294 - true
1295 - );
1296 - }
1297 -
1298 - private function displayLanguageField(array $option, string $suffix, string $updateFunction): void
1299 - {
1300 - $languageOptions = ['any' => acym_translation('ACYM_ANY')];
1301 - foreach (acym_getLanguages(true) as $language) {
1302 - $languageOptions[$language->language] = $language->name;
1303 - }
1304 -
1305 - if (empty($option['default'])) {
1306 - $option['default'] = acym_getVar('string', 'language');
1307 - if (acym_isMultilingual() && (empty($option['default']) || $option['default'] === 'main')) {
1308 - $option['default'] = $this->config->get('multilingual_default');
1309 - }
1310 - }
1311 -
1312 - acym_select(
1313 - $languageOptions,
1314 - $option['name'].$suffix,
1315 - empty($option['default']) ? null : $option['default'],
1316 - ['onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix],
1317 - 'value',
1318 - 'text',
1319 - null,
1320 - false,
1321 - true
1322 - );
1323 - }
1324 -
1325 - private function displayFormatPanel(string $suffix, string $updateFunction, string $type, $defaultValues): void
1326 - {
1327 - echo '<div class="grid-x">';
1328 - echo '<div class="cell large-3">';
1329 - echo esc_html(acym_translation('ACYM_FORMAT'));
1330 - echo '</div>';
1331 - echo '<div class="cell large-9 dcontentFormatContainer">';
1332 -
1333 - $default = empty($defaultValues->format) ? 'TOP_LEFT' : $defaultValues->format;
1334 - $formats = ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT'];
1335 - foreach ($formats as $oneFormat) {
1336 - $class = 'button-radio';
1337 - if ($default === $oneFormat) {
1338 - $class .= ' button-radio-selected';
1339 - }
1340 - echo '<button class="'.esc_attr($class).'"
1341 - acym-button-radio-group="dcontentFormat'.esc_attr($suffix).'"
1342 - acym-data-type="'.esc_attr($oneFormat).'"
1343 - acym-callback="'.esc_attr($updateFunction).'">';
1344 - echo '<img alt="'.esc_attr($oneFormat).'" src="'.esc_url(ACYM_IMAGES.'editor/dcontent_formats/'.strtolower($oneFormat).'.png').'"/>';
1345 - echo '</button>';
1346 - }
1347 - echo '</div>';
1348 -
1349 - if ($type === 'grouped') {
1350 - echo '<div class="cell large-3">';
1351 - echo esc_html(acym_translation('ACYM_ALTERNATE'));
1352 - acym_info(['textShownInTooltip' => 'ACYM_ALTERNATE_DESC']);
1353 - echo '</div>';
1354 - echo '<div class="cell large-9">';
1355 - acym_boolean(
1356 - 'alternate'.$suffix,
1357 - !empty($defaultValues->alternate),
1358 - 'alternate'.$suffix,
1359 - ['onclick' => $updateFunction.'();']
1360 - );
1361 - echo '</div>';
1362 - }
1363 -
1364 - echo '</div>';
1365 - }
1366 -
1367 - private function appendOptionJs(array &$jsOptionsMerge, array $option, string $suffix, string $type): void
1368 - {
1369 - switch ($option['type']) {
1370 - case 'pictures':
1371 - if (!empty($option['caption'])) {
1372 - $jsOptionsMerge[] = 'otherinfo += "| caption:" + jQuery(\'input[name="caption'.esc_attr($suffix).'"]:checked\').val();';
1373 - }
1374 - $jsOptionsMerge[] = '
1375 - var _pictVal'.esc_attr($suffix).' = jQuery(\'input[name="pict'.esc_attr($suffix).'"]:checked\').val();
1376 - otherinfo += "| pict:" + _pictVal'.esc_attr($suffix).';
1377 -
1378 - if(_pictVal'.esc_attr($suffix).' == "resized"){
1379 - jQuery("#pictsize'.esc_attr($suffix).'").show();
1380 - otherinfo += "| maxwidth:" + jQuery(\'input[name="pictwidth'.esc_attr($suffix).'"]\').val();
1381 - otherinfo += "| maxheight:" + jQuery(\'input[name="pictheight'.esc_attr($suffix).'"]\').val();
1382 - }else{
1383 - jQuery("#pictsize'.esc_attr($suffix).'").hide();
1384 - }';
1385 - break;
1386 -
1387 - case 'checkbox':
1388 - $separator = empty($option['separator']) ? ',' : $option['separator'];
1389 - $jsOptionsMerge[] = 'var _checked'.esc_attr($option['name'].$suffix).' = [];
1390 - jQuery("input:checkbox[name='.esc_attr($option['name'].$suffix).']:checked").each(function(){
1391 - _checked'.esc_attr($option['name'].$suffix).'.push(jQuery(this).val());
1392 - });
1393 - if(_checked'.esc_attr($option['name'].$suffix).'.length) otherinfo += "| '.esc_attr($option['name']).':" + _checked'.esc_attr(
1394 - $option['name'].$suffix
1395 - ).'.join("'.esc_attr($separator).'");';
1396 -
1397 - if ($option['title'] === 'ACYM_DISPLAY' && (!isset($option['format']) || $option['format'])) {
1398 - if ($type === 'grouped') {
1399 - $jsOptionsMerge[] = 'var alternate = jQuery(\'input[name="alternate'.esc_attr($suffix).'"]:checked\').val();';
1400 - $jsOptionsMerge[] = 'if (!acym_helper.empty(alternate)) otherinfo += "| alternate";';
1401 - }
1402 - $jsOptionsMerge[] = 'var selectedFormatOption = jQuery(\'.button-radio-selected[acym-button-radio-group="dcontentFormat'.esc_attr($suffix).'"]\')';
1403 - $jsOptionsMerge[] = 'if (!acym_helper.empty(selectedFormatOption)) otherinfo += "| format:" + selectedFormatOption.attr("acym-data-type");';
1404 - }
1405 - break;
1406 -
1407 - case 'boolean':
1408 - case 'radio':
1409 - $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'input[name="'.esc_attr($option['name'].$suffix).'"]:checked\').val();';
1410 - break;
1411 -
1412 - case 'select':
1413 - if ($option['name'] === 'order') {
1414 - $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr(
1415 - $option['name'].$suffix
1416 - ).'"]\').val() + "," + jQuery(\'[name="orderdir'.esc_attr($suffix).'"]\').val();';
1417 - } else {
1418 - $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr($option['name'].$suffix).'"]\').val();';
1419 - }
1420 - break;
1421 -
1422 - case 'language':
1423 - $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr($option['name'].$suffix).'"]\').val();';
1424 - break;
1425 -
1426 - case 'multiselect':
1427 - $jsOptionsMerge[] = '
1428 - var theMultiSelect = document.querySelector(\'[name="'.esc_attr($option['name'].$suffix).'[]"]\');
1429 - var selectedOptions = [];
1430 - for(var i = 0 ; i < theMultiSelect.length ; i++){
1431 - if(theMultiSelect[i].selected){
1432 - selectedOptions.push(theMultiSelect[i].value);
1433 - }
1434 - }
1435 - otherinfo += "| '.esc_attr($option['name']).':" + selectedOptions.join(",");';
1436 - break;
1437 -
1438 - case 'text':
1439 - case 'number':
1440 - case 'intextfield':
1441 - case 'date':
1442 - $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'input[name="'.esc_attr($option['name'].$suffix).'"]\').val();';
1443 - break;
1444 -
1445 - case 'custom':
1446 - //TODO
1447 - $jsOptionsMerge[] = $option['js'];
1448 - break;
1449 - }
1450 - }
1451 -
1452 - private function displayDynamicScript(array $jsOptionsMerge, string $suffix, string $updateFunction, string $dynamicIdentifier, string $type, $defaultValues): void
1453 - {
1454 - $storageVar = 'window._additionalInfo'.$suffix;
1455 -
1456 - echo '<script type="text/javascript">';
1457 - echo 'var _selectedRows'.esc_attr($suffix).' = [];';
1458 - echo 'var _selectedRows = [];';
1459 - echo esc_attr($storageVar).' = '.esc_attr($storageVar).' || {};';
1460 -
1461 - if (!empty($defaultValues->id) && (empty($defaultValues->defaultPluginTab) || $dynamicIdentifier === $defaultValues->defaultPluginTab)) {
1462 - $delimiter = strpos($defaultValues->id, '-') ? '-' : ',';
1463 - foreach (explode($delimiter, $defaultValues->id) as $value) {
1464 - if (empty($value)) {
1465 - continue;
1466 - }
1467 - echo '_selectedRows'.esc_attr($suffix).'['.intval($value).'] = true;';
1468 - }
1469 - }
1470 -
1471 - echo 'function applyContent'.esc_attr($suffix).'(contentid, row){';
1472 - echo 'if(_selectedRows'.esc_attr($suffix).'[contentid]){';
1473 - echo 'jQuery(row).removeClass("selected_row");';
1474 - echo 'delete _selectedRows'.esc_attr($suffix).'[contentid];';
1475 - echo '}else{';
1476 - if ($type === 'individual') {
1477 - echo 'for(let elementKey in _selectedRows'.esc_attr($suffix).') {';
1478 - echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(elementKey)) continue;';
1479 - echo 'jQuery(\'[data-id="\' + elementKey + \'"]\').removeClass("selected_row");';
1480 - echo 'delete _selectedRows'.esc_attr($suffix).'[elementKey];';
1481 - echo '}';
1482 - }
1483 - echo 'jQuery(row).addClass("selected_row");';
1484 - echo '_selectedRows'.esc_attr($suffix).'[contentid] = true;';
1485 - echo '}';
1486 - echo esc_attr($updateFunction).'();';
1487 - echo 'if(typeof _selectedRows !== "undefined"){ _selectedRows = _selectedRows'.esc_attr($suffix).'; }';
1488 - echo '}';
1489 -
1490 - echo 'function '.esc_attr($updateFunction).'(){';
1491 - echo 'var tag = ""; var otherinfo = "";';
1492 - echo implode("\r\n\r\n", $jsOptionsMerge);
1493 - echo 'for (let [index, info] of Object.entries('.esc_attr($storageVar).')){ otherinfo += "| "+index+":"+info; }';
1494 -
1495 - if ($type === 'individual') {
1496 - echo 'for(var i in _selectedRows'.esc_attr($suffix).'){';
1497 - echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(i)) continue;';
1498 - echo 'tag = tag + "{'.esc_attr($dynamicIdentifier).':" + i + otherinfo + "}";';
1499 - echo '}';
1500 - } elseif ($type === 'grouped') {
1501 - echo 'tag = "{'.esc_attr($dynamicIdentifier).':";';
1502 - echo 'for(var icat in _selectedRows'.esc_attr($suffix).'){';
1503 - echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(icat)) continue;';
1504 - echo 'tag += icat + "-";';
1505 - echo '}';
1506 - echo 'tag += otherinfo + "}";';
1507 - } elseif ($type === 'simple') {
1508 - echo 'tag = "{'.esc_attr($dynamicIdentifier).':" + otherinfo + "}";';
1509 - }
1510 -
1511 - echo 'acym_editorWysidDynamic.insertDContent(tag);';
1512 - echo '}';
1513 -
1514 - echo 'function addAdditionalInfo'.esc_attr($suffix).'(index, value){';
1515 - echo esc_attr($storageVar).'[index] = value;';
1516 - echo esc_attr($updateFunction).'();';
1517 - echo '}';
1518 - echo '</script>';
1519 - }
1520 -
1521 - /**
1522 - * In Joomla, we can translate elements in extensions using the FaLang extension
1523 - */
1524 - public function translateItem(object &$item, object &$tag, string $referenceTable, int $referenceId = 0): void
1525 - {
1526 - if (!acym_isExtensionActive('com_falang')) {
1527 - return;
1528 - }
1529 -
1530 - if (!empty($tag->falang)) {
1531 - $langId = $tag->falang;
1532 - } elseif (!empty($tag->lang)) {
1533 - $langId = intval(substr($tag->lang, strpos($tag->lang, ',') + 1));
1534 - } elseif (!empty($this->contextLanguage)) {
1535 - $languages = acym_loadObjectList('SELECT `lang_id`, `lang_code`, `sef` FROM #__languages');
1536 - foreach ($languages as $language) {
1537 - if (in_array($this->contextLanguage, [$language->lang_code, $language->sef])) {
1538 - $langId = $language->lang_id;
1539 - break;
1540 - }
1541 - }
1542 - }
1543 -
1544 - if (empty($langId)) {
1545 - return;
1546 - }
1547 -
1548 - if (empty($referenceId)) {
1549 - $referenceId = $tag->id;
1550 - }
1551 -
1552 - $translations = acym_loadObjectList(
1553 - 'SELECT `reference_field`, `value`
1554 - FROM `#__falang_content`
1555 - WHERE `published` = 1
1556 - AND `reference_table` = '.acym_escapeDB($referenceTable).'
1557 - AND `language_id` = '.intval($langId).'
1558 - AND `reference_id` = '.intval($referenceId)
1559 - );
1560 -
1561 - if (empty($translations)) {
1562 - return;
1563 - }
1564 -
1565 - foreach ($translations as $oneTranslation) {
1566 - if (empty($oneTranslation->value)) {
1567 - continue;
1568 - }
1569 -
1570 - $translatedField = $oneTranslation->reference_field;
1571 - $item->$translatedField = $oneTranslation->value;
1572 - }
1573 - }
1574 -
1575 - public function createDummyEmailObject(int $mailId, string $code, string $previewBody): object
1576 - {
1577 - if (!empty($mailId)) {
1578 - $mailClass = new MailClass();
1579 - $email = $mailClass->getOneById($mailId);
1580 - }
1581 -
1582 - if (empty($email)) {
1583 - $email = new \stdClass();
1584 - $email->id = 0;
1585 - $email->name = '';
1586 - $email->subject = '';
1587 - $email->from_name = '';
1588 - $email->from_email = '';
1589 - $email->reply_to_name = '';
1590 - $email->reply_to_email = '';
1591 - $email->bcc = '';
1592 - $email->links_language = '';
1593 - }
1594 -
1595 - $language = acym_getVar('string', 'language', 'main');
1596 - if (!empty($language)) {
1597 - if ($language === 'main') {
1598 - $language = $this->config->get('multilingual_default', ACYM_DEFAULT_LANGUAGE);
1599 - }
1600 - $email->links_language = $language;
1601 - }
1602 -
1603 - $email->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
1604 - $email->creator_id = acym_currentUserId();
1605 - $email->thumbnail = '';
1606 - $email->drag_editor = '1';
1607 - $email->type = MailClass::TYPE_STANDARD;
1608 - $email->settings = '';
1609 - $email->stylesheet = '';
1610 - $email->attachments = '';
1611 -
1612 - // This is only the dynamic text/content code
1613 - $email->body = $code;
1614 - // This is the whole editor current content
1615 - $email->previewBody = $previewBody;
1616 -
1617 - return $email;
1618 - }
1619 -}