JezK
Edit File: PluginHelper.php
<?php namespace AcyMailing\Helpers; use AcyMailing\Classes\MailClass; use AcyMailing\Core\AcymObject; class PluginHelper extends AcymObject { private MailerHelper $mailerHelper; public string $name = 'content'; public string $wrappedText = ''; public string $contextLanguage; /* * Convert an array of elements into a table with multiple columns based on the $parameter variable * $parameter->displaytype = table by default * $parameter->cols = 1 by default */ public function getFormattedResult(array $elements, object $parameter): string { //We do not add an extra table or whatever when there is a single element... if (count($elements) < 2) { return implode('', $elements); } $beforeAll = []; $beforeAll['table'] = '<table cellspacing="0" cellpadding="0" border="0" width="100%" class="elementstable">'."\n"; $beforeAll['ul'] = '<ul class="elementsul">'."\n"; $beforeAll['br'] = ''; $beforeBlock = []; $beforeBlock['table'] = '<tr class="elementstable_tr numrow{rownum}">'."\n"; $beforeBlock['ul'] = ''; $beforeBlock['br'] = ''; $beforeOne = []; $beforeOne['table'] = '<td valign="top" width="{equalwidth}" style="{padding}" class="elementstable_td numcol{numcol}" >'."\n"; $beforeOne['ul'] = '<li class="elementsul_li numrow{rownum}">'."\n"; $beforeOne['br'] = ''; $afterOne = []; $afterOne['table'] = '</td>'."\n"; $afterOne['ul'] = '</li>'."\n"; $afterOne['br'] = '<br />'."\n"; $afterBlock = []; $afterBlock['table'] = '</tr>'."\n"; $afterBlock['ul'] = ''; $afterBlock['br'] = ''; $afterAll = []; $afterAll['table'] = '</table>'."\n"; $afterAll['ul'] = '</ul>'."\n"; $afterAll['br'] = ''; $type = 'table'; $cols = 1; if (!empty($parameter->displaytype)) { $type = $parameter->displaytype; } if ($type == 'none') { return implode('', $elements); } if (!empty($parameter->cols)) { $cols = $parameter->cols; } $parameter->hpadding = !isset($parameter->hpadding) || is_null($parameter->hpadding) ? 10 : $parameter->hpadding; $parameter->vpadding = !isset($parameter->vpadding) || is_null($parameter->vpadding) ? 10 : $parameter->vpadding; $horizontalPadding = round($parameter->hpadding / 2); $verticalPadding = round($parameter->vpadding / 2); $string = $beforeAll[$type]; $a = 0; $numrow = 1; foreach ($elements as $key => $oneElement) { $topPadding = $verticalPadding.'px'; $rightPadding = $horizontalPadding.'px'; $bottomPadding = $verticalPadding.'px'; $leftPadding = $horizontalPadding.'px'; if ($a == $cols) { $string .= $afterBlock[$type]; $a = 0; $numrow++; } if ($a == 0) { $string .= str_replace('{rownum}', $numrow, $beforeBlock[$type]); $leftPadding = '0px'; } if ($a + 1 == $cols) $rightPadding = '0px'; if ($numrow == 1) $topPadding = '0px'; if (round(count($elements) / $cols) == $numrow) $bottomPadding = '0px'; $padding = 'padding: '.$topPadding.' '.$rightPadding.' '.$bottomPadding.' '.$leftPadding.' !important;'; $string .= str_replace('{numcol}', $a + 1, $beforeOne[$type]).$oneElement.$afterOne[$type]; $string = str_replace('{padding}', $padding, $string); $a++; } while ($cols > $a) { $string .= str_replace('{numcol}', $a + 1, $beforeOne[$type]).$afterOne[$type]; $a++; } $string .= $afterBlock[$type]; $string .= $afterAll[$type]; $equalwidth = intval(100 / $cols).'%'; return str_replace(['{equalwidth}'], [$equalwidth], $string); } /** * This function will apply extra parameters such as part:first, ucfirst, strtolower to the $string */ public function formatString(&$replaceme, object $mytag): void { if (!empty($mytag->part)) { $parts = explode(' ', $replaceme); if ($mytag->part == 'last') { $replaceme = count($parts) > 1 ? end($parts) : ''; } else { if (is_numeric($mytag->part) && count($parts) >= $mytag->part) { $replaceme = $parts[$mytag->part - 1]; } else { $replaceme = reset($parts); } } } if (!empty($mytag->type)) { if (empty($mytag->format)) { $mytag->format = acym_translation('ACYM_DATE_FORMAT_LC3'); } if ($mytag->type == 'date') { $replaceme = acym_getDate(acym_getTime($replaceme), $mytag->format); } elseif ($mytag->type == 'time') { $replaceme = acym_getDate($replaceme, $mytag->format); } elseif ($mytag->type == 'diff') { try { //We have a date? Sure? $date = $replaceme; if (is_numeric($date)) { $date = acym_getDate($replaceme, '%Y-%m-%d %H:%M:%S'); } $dateObj = new \DateTime($date); $nowObj = new \DateTime(); $diff = $dateObj->diff($nowObj); $replaceme = $diff->format($mytag->format); } catch (\Exception $e) { $replaceme = 'Error using the "diff" parameter in your tag. Please make sure the DateTime() and diff() functions are available on your server.'; } } } //Two possibles keywords for that... we used to have "lower" and "upper" only but I added lowercase and uppercase as well on December 2014 if (!empty($mytag->lower) || !empty($mytag->lowercase)) { $replaceme = function_exists('mb_strtolower') ? mb_strtolower($replaceme, 'UTF-8') : strtolower($replaceme); } if (!empty($mytag->upper) || !empty($mytag->uppercase)) { $replaceme = function_exists('mb_strtoupper') ? mb_strtoupper($replaceme, 'UTF-8') : strtoupper($replaceme); } if (!empty($mytag->ucwords)) { $replaceme = ucwords($replaceme); } if (!empty($mytag->ucfirst)) { $replaceme = ucfirst($replaceme); } //Remove a character at the end of the string... if (isset($mytag->rtrim)) { $replaceme = empty($mytag->rtrim) ? rtrim($replaceme) : rtrim($replaceme, $mytag->rtrim); } if (!empty($mytag->urlencode)) { $replaceme = urlencode($replaceme); } if (!empty($mytag->substr)) { //the parameter |substr:4,6 will select the 6 characters after the 4. $args = explode(',', $mytag->substr); if (isset($args[1])) { $replaceme = substr($replaceme, intval($args[0]), intval($args[1])); } else { $replaceme = substr($replaceme, intval($args[0])); } } if (!empty($mytag->maxheight) || !empty($mytag->maxwidth)) { $imageHelper = new ImageHelper(); $imageHelper->maxHeight = empty($mytag->maxheight) ? 999 : $mytag->maxheight; $imageHelper->maxWidth = empty($mytag->maxwidth) ? 999 : $mytag->maxwidth; $replaceme = $imageHelper->resizePictures($replaceme); } } public function replaceVideos(string &$text): void { //Youtube videos $text = preg_replace( '#\[embed=videolink][^}]*youtube[^=]*=([^"/}]*)[^}]*}\[/embed]#i', '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>', $text ); $text = preg_replace( '#<video[^>]*youtube\.com/embed/([^"/]*)[^>]*>[^>]*</video>#i', '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>', $text ); $text = preg_replace( '#{JoooidContent[^}]*youtube[^}]*id"[^"]*"([^}"]*)"[^}]*}#i', '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>', $text ); $text = preg_replace( '#<iframe[^>]*src="[^"]*youtube[^"]*embed/([^"?]*)(\?[^"]*)?"[^>]*>[^<]*</iframe>#Uis', '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>', $text ); $text = preg_replace( '#{youtube}[^{]+v=([^{&]+)(&[^{]*)?{/youtube}#Uis', '<a target="_blank" href="https://www.youtube.com/watch?v=$1"><img src="https://img.youtube.com/vi/$1/0.jpg"/></a>', $text ); $text = preg_replace('#{vimeo}(https://vimeo.com/[^{]+){/vimeo}#Uis', '<iframe src="$1"></iframe>', $text); $text = preg_replace('#{vimeo}([^{]+){/vimeo}#Uis', '<iframe src="https://player.vimeo.com/video/$1"></iframe>', $text); // Vimeo iframes if (preg_match_all('#<iframe[^>]*src="[^"]*vimeo[^"]*/(\d+)([&/\?][^"]*)?"[^>]*>[^<]*</iframe>#Uis', $text, $matches)) { foreach ($matches[1] as $key => $match) { $hash = acym_fileGetContent('https://vimeo.com/api/v2/video/'.$match.'.php'); // @ on purpose, the unserialize returns false if $hash isn't a serialized data, but it also throws a notice... $hash = @unserialize($hash); if (empty($hash)) continue; if (strpos($matches[0][$key], ' width="') !== false) { $extension = substr($hash[0]['thumbnail_large'], strrpos($hash[0]['thumbnail_large'], '.')); preg_match('#width="([^"]*)"#Uis', $matches[0][$key], $width); $replace = strpos($hash[0]['thumbnail_large'], '_') === false ? '.' : '_'; $hash[0]['thumbnail_large'] = substr($hash[0]['thumbnail_large'], 0, strrpos($hash[0]['thumbnail_large'], $replace)).'_'.$width[1].$extension; } $thumbnail = 'https://i.vimeocdn.com/filter/overlay?src='.urlencode($hash[0]['thumbnail_large']); $thumbnail .= '&src='.urlencode('https://f.vimeocdn.com/p/images/crawler_play.png'); $text = str_replace( $matches[0][$key], '<a target="_blank" href="'.esc_attr($hash[0]['url']).'"><img class="donotresize" alt="" src="'.esc_attr($thumbnail).'" /></a>', $text ); } } //Other videos $text = preg_replace('#\[embed=videolink][^}]*video":"([^"]*)[^}]*}\[/embed]#i', '<a target="_blank" href="$1"><img src="'.ACYM_IMAGES.'/video.png"/></a>', $text); $text = preg_replace('#<video[^>]*src="([^"]*)"[^>]*>[^>]*</video>#i', '<a target="_blank" href="$1"><img src="'.ACYM_IMAGES.'/video.png"/></a>', $text); } /** * Convert pictures base64 code into a real picture */ private function convertbase64pictures(string &$html): void { if (!preg_match_all('#<img[^>]*src=("data:image/([^;]{1,5});base64[^"]*")([^>]*)>#Uis', $html, $resultspictures)) { return; } //Just in case of... we will need it $dest = ACYM_MEDIA.'resized'.DS; acym_createDir($dest); foreach ($resultspictures[2] as $i => $extension) { $pictname = md5($resultspictures[1][$i]).'.'.$extension; $picturl = ACYM_LIVE.str_replace(DS, '/', ACYM_MEDIA_FOLDER).'resized/'.$pictname; $pictPath = $dest.$pictname; $pictCode = trim($resultspictures[1][$i], '"'); if (file_exists($pictPath)) { //The picture is already there... lets use it then $html = str_replace($pictCode, $picturl, $html); continue; } if (!ini_get('allow_url_fopen')) { continue; } $getfunction = ''; switch ($extension) { case 'gif': $getfunction = 'ImageCreateFromGIF'; break; case 'jpg': case 'jpeg': $getfunction = 'ImageCreateFromJPEG'; break; case 'png': $getfunction = 'ImageCreateFromPNG'; break; } //The function does not exists or we didn't find the right function... we just skip that action if (empty($getfunction) || !function_exists($getfunction)) { continue; } $img = $getfunction($pictCode); if ($img === false) { continue; } if (in_array($extension, ['gif', 'png'])) { imagealphablending($img, false); imagesavealpha($img, false); } //We display the result and then save it to avoid the issue with FTP configurations ob_start(); switch ($extension) { case 'gif': $status = imagegif($img); break; case 'jpg': case 'jpeg': $status = imagejpeg($img, null, 100); break; case 'png': $status = imagepng($img, null, 1); break; } $imageContent = ob_get_clean(); $status = $status && acym_writeFile($pictPath, $imageContent); //we could not save or convert the picture, we skip it if (!$status) { continue; } $html = str_replace($pictCode, $picturl, $html); } } //This function will remove or replace code we should not have in an html view. public function cleanHtml(string &$html): void { $this->convertbase64pictures($html); //add line-height: 0px; in the TR style when there is only images in the cell, not extra text (new issue with gmail) //handle a line-height automatically for <tr> <td> <img...></td></tr> $pregreplace = []; $pregreplace['#<tr([^>"]*>([^<]*<td[^>]*>[ \n\s]*<img[^>]*>[ \n\s]*</ *td[^>]*>[ \n\s]*)*</ *tr)#Uis'] = '<tr style="line-height: 0px;" $1'; $pregreplace['#<td(((?!style|>).)*>[ \n\s]*(<a[^>]*>)?[ \n\s]*<img[^>]*>[ \n\s]*(</a[^>]*>)?[ \n\s]*</ *td)#Uis'] = '<td style="line-height: 0px;" $1'; //No number tab system {tab=...}{/tabs} or {tab }{/tabs} and jcomments system $pregreplace['#{tab[ =][^}]*}#is'] = ''; $pregreplace['#{/tabs}#is'] = ''; $pregreplace['#{jcomments\s+(on|off|lock)}#is'] = ''; //Remove the JS... $pregreplace["#(onmouseout|onmouseover|onclick|onfocus|onload|onblur) *= *\"(?:(?!\").)*\"#Ui"] = ''; $pregreplace["#< *script(?:(?!< */ *script *>).)*< */ *script *>#Uis"] = ''; $pregreplace["#< *iframe(?:(?!< */ *iframe *>).)*< */ *iframe *>#Uis"] = ''; // May God punish every person using Outlook, or imposing this monstrosity for his employees $pregreplace['#(<p style=")([^>]*>\s*<img *[^>]*margin-left: auto; margin-right: auto;[^>]*>\s*</p>)#Uis'] = '$1text-align: center;$2'; // Outlook doesn't handle webp images $pregreplace['#(<img [^>]*src="[^"]+\.webp"[^>]*>)#Uis'] = '<!--[if !mso]><!-->$1<!--<![endif]-->'; $newbody = preg_replace(array_keys($pregreplace), $pregreplace, $html); //we do it in two steps as this regex can break the page if (!empty($newbody)) { $html = $newbody; } $body = preg_replace_callback('/src="([^"]* [^"]*)"/Ui', [$this, 'convertSpaces'], $html); if (!empty($body)) $html = $body; $html = acym_cmsCleanHtml($html); } public function convertSpaces(array $matches): string { return "src='".str_replace(' ', '%20', $matches[1])."'"; } /* * Replace tags in all the email variables where it is possible, in text version or html version */ public function replaceTags(object &$email, array $tags, bool $html = false): void { if (empty($tags)) return; $htmlVars = ['body']; $textVars = [ 'subject', 'AltBody', 'From', 'FromName', 'ReplyTo', 'ReplyName', 'bcc', 'cc', 'fromname', 'fromemail', 'replyname', 'replyemail', 'params', 'preheader', ]; $variables = array_merge($htmlVars, $textVars); if ($html) { if (empty($this->mailerHelper)) { $this->mailerHelper = new MailerHelper(); } $textreplace = []; foreach ($tags as $i => $replacement) { if (isset($textreplace[$i])) continue; $textreplace[$i] = $this->mailerHelper->textVersion($replacement); } } else { $textreplace = $tags; } foreach ($variables as $var) { if (empty($email->$var)) continue; $email->$var = $this->replaceDText($email->$var, in_array($var, $htmlVars) ? $tags : $textreplace); } } public function replaceDText($text, $replacement) { if (is_array($text)) { foreach ($text as &$oneCell) { if (empty($oneCell)) continue; $oneCell = $this->replaceDText($oneCell, $replacement); } } elseif (is_string($text) && !empty($text)) { foreach ($replacement as $code => $value) { $codes = [$code, urlencode($code)]; if (is_null($value)) { $value = ''; } $safePregValue = str_replace('$', '\$', $value); foreach ($codes as $oneCode) { // Dtext specific syntax $text = preg_replace( '#<span[^>]+'.preg_quote($oneCode, '#').'.+</em>[^<]*</span>#Uis', $safePregValue, $text ); // Dcontent specific syntax $text = preg_replace( '#(<tr[^>]+)data-dynamic="'.preg_quote($oneCode, '#').'"([^>]+>[^<]*<td[^>]*>).+</i>[^<]*</td>[^<]*</tr>#Uis', '${1}${2}'.$safePregValue.'</td></tr>', $text ); // If the code was inserted directly in the email (in the subject for example, or a copy-paste) $text = str_replace($oneCode, $value, $text); } } } return $text; } /** * This function extracts tags from the mail by checking the subject,body and altbody and returns an array of tag objects * tagfamily is "vmproduct" for example to handle tags such as {vmproduct:23|file|price} */ public function extractTags(object $email, string $tagfamily): array { $results = []; $match = '#(?:{|%7B)'.$tagfamily.'(?:%3A|\\:)(.*)(?:}|%7D)#Ui'; //If you add a variable there, don't forget to add it in the replaceTags function as well! $variables = [ 'subject', 'AltBody', 'body', 'From', 'FromName', 'ReplyTo', 'ReplyName', 'bcc', 'cc', 'fromname', 'fromemail', 'replyname', 'replyemail', 'params', 'preheader', ]; $found = false; foreach ($variables as $var) { if (empty($email->$var)) continue; if (is_array($email->$var)) { foreach ($email->$var as $i => $arrayField) { if (empty($arrayField)) continue; if (is_array($arrayField)) { foreach ($arrayField as $a => $oneval) { $found = preg_match_all($match, $oneval, $results[$var.$i.'-'.$a]) || $found; if (empty($results[$var.$i.'-'.$a][0])) unset($results[$var.$i.'-'.$a]); } } else { $found = preg_match_all($match, $arrayField, $results[$var.$i]) || $found; if (empty($results[$var.$i][0])) unset($results[$var.$i]); } } } else { $found = preg_match_all($match, $email->$var, $results[$var]) || $found; //we unset the results so that we won't handle it later... it will save some memory and processing if (empty($results[$var][0])) unset($results[$var]); } } //If we didn't find anything... if (!$found) { return []; } $tags = []; foreach ($results as $var => $allresults) { foreach ($allresults[0] as $i => $oneTag) { //Don't need to process twice a tag we already have! if (isset($tags[$oneTag])) { continue; } $tags[$oneTag] = $this->extractTag($allresults[1][$i]); } } return $tags; } /** * Converts a tag 23|file:myfile|price into an object. */ public function extractTag(string $oneTag): \stdClass { $oneTag = str_replace(['[time]+', '[time]-'], [urlencode('[time]+'), urlencode('[time]-')], $oneTag); $arguments = explode('|', acym_stripTags(urldecode($oneTag))); $tag = new \stdClass(); $tag->id = $arguments[0]; $tag->default = ''; for ($i = 1, $a = count($arguments); $i < $a; $i++) { $args = explode(':', $arguments[$i], 2); $arg0 = trim($args[0]); if (empty($arg0)) continue; if (isset($args[1])) { $tag->$arg0 = $args[1]; //We may have an extra parameter, especially for date format. if (isset($args[2])) { $tag->{$args[0]} .= ':'.$args[2]; } } else { $tag->$arg0 = true; } } return $tag; } /** * Wrap the text using the wrapValue */ public function wrapText(string $text, object $tag): string { if (empty($tag->wrap)) { return $text; } $tag->wrap = intval($tag->wrap); $newText = str_replace(['<', '>'], ['<', '>'], $text); if (mb_strlen(acym_stripTags($newText)) <= $tag->wrap) { return $newText; } if (!class_exists('DOMDocument', false)) { return mb_substr(acym_stripTags($newText), 0, $tag->wrap).'…'; } if (function_exists('libxml_use_internal_errors')) { libxml_use_internal_errors(true); } $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->loadHTML( '<?xml encoding="UTF-8">'.$newText, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); $count = 0; $this->truncateNode($dom, $dom, $tag->wrap, $count); return $dom->saveHTML(); } private function truncateNode(\DOMDocument $dom, \DOMNode $node, int $limit, int &$count): bool { for ($i = 0; $i < $node->childNodes->length; $i++) { $child = $node->childNodes->item($i); // Text node if ($child->nodeType === XML_TEXT_NODE) { $text = $child->nodeValue; $len = mb_strlen($text); if ($count + $len > $limit) { $remaining = $limit - $count; // Take the max allowed slice $slice = mb_substr($text, 0, $remaining); // Try to cut at a word boundary (spaces or punctuation) if (preg_match('/^(.+?)([\s\p{P}]+)[^\s\p{P}]*$/u', $slice, $m)) { $slice = rtrim($m[1]); } // Final assignment $child->nodeValue = $slice.'…'; // Remove remaining siblings while ($child->nextSibling) { $node->removeChild($child->nextSibling); } return true; } $count += $len; } // Element node → recurse if ($child->hasChildNodes() && $this->truncateNode($dom, $child, $limit, $count)) { while ($child->nextSibling) { $node->removeChild($child->nextSibling); } return true; } } return false; } /** * Returns a formatted version of the passed content * * TOP_LEFT : title on top, image with float left and covering description * TOP_RIGHT : title on top, image with float rigth and covering description * TITLE_IMG : image on title's left on top, description below * TITLE_IMG_RIGHT : image on title's right on top, description below * CENTER_IMG : title on top, image on one line and description below * TOP_IMG : image on top, then title and description * COL_LEFT : image on left column, title and description on right column * COL_RIGHT : image on right column, title and description on left column */ public function getStandardDisplay(object $format): string { // By default, put float left on the picture if (empty($format->tag->format)) { $format->tag->format = 'TOP_LEFT'; } if (!in_array($format->tag->format, ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT'])) { $format->tag->format = 'TOP_LEFT'; } $invertValues = [ 'TOP_LEFT' => 'TOP_RIGHT', 'TITLE_IMG' => 'TITLE_IMG_RIGHT', 'COL_LEFT' => 'COL_RIGHT', 'TOP_RIGHT' => 'TOP_LEFT', 'TITLE_IMG_RIGHT' => 'TITLE_IMG', 'COL_RIGHT' => 'COL_LEFT', ]; if (!empty($format->tag->invert) && !empty($invertValues[$format->tag->format])) { $format->tag->format = $invertValues[$format->tag->format]; } // Get the image with float left/right or no style $image = ''; if (!empty($format->imagePath)) { $style = ''; $linkStyle = ''; if (in_array($format->tag->format, ['TOP_LEFT', 'TITLE_IMG'])) { $style = 'left'; } elseif (in_array($format->tag->format, ['TOP_RIGHT', 'TITLE_IMG_RIGHT'])) { $style = 'right'; } if (!empty($style)) { $linkStyle = 'style="float:'.$style.';"'; if ($style === 'left') { $style = 'style="float:left; margin-right: 7px; margin-bottom: 7px;"'; } else { $style = 'style="float:right; margin-left: 7px; margin-bottom: 7px;"'; } } preg_match('#src="([^"]+)"#Uis', $format->imagePath, $matches); if (!empty($matches[1])) $format->imagePath = $matches[1]; $altImage = !empty($format->altImage) ? $format->altImage : ''; $image = '<img class="content_main_image" alt="'.esc_attr($altImage).'" src="'.$format->imagePath.'" '.$style.' />'; if (!empty($format->imageCaption) && !in_array($format->tag->format, ['TITLE_IMG', 'TITLE_IMG_RIGHT'])) { $image .= '<p class="content_main_image_caption">'.esc_html($format->imageCaption).'</p>'; } } // If TITLE_IMG, add the image to the title $result = ''; if (in_array($format->tag->format, ['TITLE_IMG', 'TITLE_IMG_RIGHT'])) { $format->title = $image.$format->title; $image = ''; } if (!empty($format->link) && !empty($image) && !empty($format->tag->clickableimg)) { $image = '<a target="_blank" href="'.$format->link.'" '.$linkStyle.'>'.$image.'</a>'; } // If the image should be displayed before the title, do it if ($format->tag->format === 'TOP_IMG' && !empty($image)) { $result = $image; $image = ''; } // If we want to keep the left/right column for the image, put the whole article in a table if (in_array($format->tag->format, ['COL_LEFT', 'COL_RIGHT'])) { $maxWidth = empty($format->tag->maxwidth) ? '' : ' width: '.$format->tag->maxwidth.'px;'; if (empty($image)) { $format->tag->format = 'TOP_LEFT'; } else { $result = '<table><tr><td valign="middle" style="vertical-align: middle; padding-right: 7px;" class="acyleftcol">'; if ($format->tag->format === 'COL_LEFT') { $result = '<table><tr><td valign="middle" style="vertical-align: middle; padding-right: 7px; '.$maxWidth.'" class="acyleftcol">'; $result .= $image.'</td><td valign="top" class="acyrightcol">'; } } } // Display the title if (!empty($format->title)) { if (!empty($format->link) && !empty($format->tag->clickable)) { if (empty($format->tag->type) || $format->tag->type !== 'title') { $format->title = '<h2 class="acym_title">'.$format->title.'</h2>'; } $title = '<a'; if (!empty($format->tag->type) && $format->tag->type === 'title') $title .= ' class="acym_title"'; $title .= ' href="'.$format->link.'" target="_blank" name="'.$this->name.'-'.$format->tag->id.'">'; $title .= $format->title; $title .= '</a>'; $format->title = $title; } else { if (empty($format->tag->type) || $format->tag->type != 'title') { $format->title = '<h2 class="acym_title">'.$format->title.'</h2>'; } } $result .= $format->title; } if (!empty($format->afterTitle)) { $result .= $format->afterTitle; } if (!empty($format->description)) { $format->description = $this->wrapText($format->description, $format->tag); } $this->wrappedText = $format->description; $rowText = '<div class="acydescription">'; $endRow = '</div><br />'; // Display the main content based on the chosen format if (in_array($format->tag->format, ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'TOP_IMG'])) { if (!empty($image) || !empty($format->description)) { $result .= $rowText.$image.$format->description.$endRow; } } elseif ($format->tag->format == 'CENTER_IMG') { if (!empty($image)) { $result .= '<div class="acymainimage">'.$image.$endRow; } if (!empty($format->description)) { $result .= $rowText.$format->description.$endRow; } } elseif (in_array($format->tag->format, ['COL_LEFT', 'COL_RIGHT'])) { if (!empty($format->description)) { $result .= $rowText.$format->description.$endRow; } if ($format->tag->format === 'COL_RIGHT') { $result .= '</td><td valign="middle" style="vertical-align: middle; padding-left: 7px; '.$maxWidth.'" class="acyrightcol">'.$image; } $result .= '</td></tr></table>'; } // Add the custom fields on 1 or 2 columns, with or without the labels if (!empty($format->customFields)) { $result .= '<table style="width:100%;" class="customfieldsarea"><tr>'; $format->cols = empty($format->tag->nbcols) ? 1 : intval($format->tag->nbcols); if (empty($format->cols)) $format->cols = 1; $i = 0; foreach ($format->customFields as $oneField) { if ($i != 0 && $i % $format->cols == 0) $result .= '</tr><tr>'; if (empty($oneField[1])) { $result .= '<td class="cfvalue" colspan="2">'; } else { $result .= '<td nowrap="nowrap" class="cflabel">'.$oneField[1].'</td><td class="cfvalue">'; } $result .= $oneField[0].'</td>'; $i++; } while ($i % $format->cols != 0) { $result .= '<td colspan="2"></td>'; $i++; } $result .= '</tr></table>'; } // Add what should be displayed at the end (share buttons, read more link, etc...) if (!empty($format->afterArticle)) { $result .= $format->afterArticle; } return $result; } /** * Resizes or removes the pictures according to the parameters */ public function managePicts(object $tag, string $result): string { if (!isset($tag->pict)) { return $result; } $imageHelper = new ImageHelper(); if ($tag->pict === 'resized') { $imageHelper->maxHeight = empty($tag->maxheight) ? 150 : $tag->maxheight; $imageHelper->maxWidth = empty($tag->maxwidth) ? 150 : $tag->maxwidth; if ($imageHelper->available()) { $result = $imageHelper->resizePictures($result); } elseif (acym_isAdmin()) { acym_enqueueMessage($imageHelper->error, 'notice'); } } elseif ($tag->pict == '0') { $result = $imageHelper->removePictures($result); } return acym_absoluteURL($result); } public function displayOptions(array $options, string $dynamicIdentifier, string $type = 'individual', $defaultValues = null): void { $suffix = preg_replace('#[^a-zA-Z0-9]#U', '_', $dynamicIdentifier); $updateFunction = 'updateDynamic'.$suffix; $jsOptionsMerge = []; $topOptions = []; $sections = []; // ---- Phase 1: classify options + collect the JS (in original loop order) ---- foreach ($options as $option) { if (isset($defaultValues->{$option['name']})) { $option['default'] = $defaultValues->{$option['name']}; } // Preserve the early skip: never rendered, no JS collected. if ( $option['type'] === 'boolean' && $option['name'] === 'autologin' && $this->config->get('autologin_urls', 0) != 1 ) { continue; } $this->appendOptionJs($jsOptionsMerge, $option, $suffix, $type); $isTop = !empty($option['main']) || in_array($option['type'], ['pictures', 'checkbox'], true); if ($isTop) { $topOptions[$option['title']] = ['kind' => 'field', 'option' => $option]; // The format block is attached right after the "display" checkbox. if ( $option['type'] === 'checkbox' && $option['title'] === 'ACYM_DISPLAY' && (!isset($option['format']) || $option['format']) ) { $topOptions['ACYM_FORMAT'] = ['kind' => 'format', 'option' => $option]; } continue; } $section = empty($option['section']) ? 'ACYM_OTHER_OPTIONS' : $option['section']; $sections[$section][$option['name']] = $option; } // "Other Options" is always rendered last. if (isset($sections['ACYM_OTHER_OPTIONS'])) { $otherOptions = $sections['ACYM_OTHER_OPTIONS']; unset($sections['ACYM_OTHER_OPTIONS']); $sections['ACYM_OTHER_OPTIONS'] = $otherOptions; } // ---- Phase 2: render (echo directly, in the original visual order) ---- foreach ($topOptions as $headerLabel => $entry) { $this->displayPanelHeader($headerLabel); echo '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container grid-x">'; if ($entry['kind'] === 'format') { $this->displayFormatPanel($suffix, $updateFunction, $type, $defaultValues); } else { $this->displayOptionField($entry['option'], $suffix, $updateFunction, $defaultValues); } echo '</div>'; } foreach ($sections as $sectionName => $sectionOptions) { $this->displayPanelHeader($sectionName); echo '<div class="acym__wysid__right__toolbar__design--show acym__wysid__right__toolbar__design acym__wysid__context__modal__container grid-x">'; foreach ($sectionOptions as $option) { echo '<div class="cell grid-x margin-bottom-1">'; $this->displayFieldLabel($option, $suffix); echo '<div class="cell large-7">'; $this->displayOptionField($option, $suffix, $updateFunction, $defaultValues); echo '</div>'; echo '</div>'; } echo '</div>'; } // ---- Phase 3: the inline script + individual extras ---- $this->displayDynamicScript($jsOptionsMerge, $suffix, $updateFunction, $dynamicIdentifier, $type, $defaultValues); if ($type === 'individual') { acym_trigger('displayCustomViewEditor', [], 'plgAcym'.ucfirst($dynamicIdentifier)); echo '<input type="hidden" id="acym__dynamic__update__function" value="'.esc_attr($updateFunction).'">'; } } private function displayPanelHeader(string $labelKey): void { echo '<p class="acym__wysid__right__toolbar__p acym__wysid__right__toolbar__p__open acym__title">'; echo esc_html(acym_translation($labelKey)); echo '<i class="acymicon-keyboard-arrow-up"></i>'; echo '</p>'; } private function displayFieldLabel(array $option, string $suffix): void { echo '<label class="cell large-5 acym_plugin_field acym_plugin_field_'.esc_attr($option['type']).'" for="'.esc_attr($option['name'].$suffix).'">'; echo esc_html(acym_translation($option['title'])); if (!empty($option['tooltip'])) { echo ' '; acym_info( [ 'textShownInTooltip' => $option['tooltip'], 'classIcon' => 'acym_plugin_field_'.$option['name'], ] ); } echo '</label>'; } private function displayOptionField(array $option, string $suffix, string $updateFunction, $defaultValues): void { switch ($option['type']) { case 'pictures': $this->displayPicturesField($option, $suffix, $updateFunction, $defaultValues); break; case 'checkbox': $this->displayCheckboxField($option, $suffix, $updateFunction); break; case 'boolean': acym_boolean($option['name'].$suffix, $option['default'], $option['name'].$suffix, ['onclick' => $updateFunction.'();']); break; case 'radio': $this->displayRadioField($option, $suffix, $updateFunction); break; case 'select': $this->displaySelectField($option, $suffix, $updateFunction); break; case 'multiselect': $this->displayMultiselectField($option, $suffix, $updateFunction); break; case 'text': $default = $option['default'] ?? ''; $class = empty($option['class']) ? 'acym_plugin_text_field' : $option['class']; echo '<input type="text" name="'.esc_attr($option['name'].$suffix).'" id="'.esc_attr($option['name'].$suffix).'" onchange="'.esc_attr($updateFunction).'();" value="'.esc_attr($default).'" class="'.esc_attr($class).'"'; if (!empty($option['placeholder'])) { echo ' placeholder="'.esc_attr($option['placeholder']).'"'; } echo '/>'; break; case 'number': $class = empty($option['class']) ? 'acym_plugin_text_field' : $option['class']; echo '<input type="number" min="'.esc_attr(empty($option['min']) ? '0' : $option['min']).'" id="'.esc_attr($option['name'].$suffix).'" onchange="'.esc_attr($updateFunction).'();" value="'.intval($option['default']).'" name="'.esc_attr($option['name'].$suffix).'" class="'.esc_attr($class).'" '; if (!empty($option['max'])) { echo ' max="'.esc_attr($option['max']).'"'; } echo ' />'; break; case 'intextfield': $inputType = (!empty($option['isNumber']) && $option['isNumber'] === 1) ? 'number' : 'text'; echo wp_kses( acym_translationSprintf( $option['text'], '<input type="'.esc_attr($inputType).'" name="'.esc_attr($option['name'].$suffix).'" id="'.esc_attr($option['name'].$suffix).'" class="intext_input" value="'.esc_attr($option['default']).'" onchange="'.esc_attr($updateFunction).'();"/>' ), [ 'input' => [ 'type' => true, 'name' => true, 'id' => true, 'class' => true, 'value' => true, 'onchange' => true, ], ] ); break; case 'date': $relativeTime = empty($option['relativeDate']) ? '-' : $option['relativeDate']; $dateDefault = $option['default']; if (!empty($dateDefault) && !is_numeric($dateDefault) && strpos($dateDefault, '[time]') === false) { $dateDefault = strtotime($dateDefault); } echo wp_kses( acym_dateField($option['name'].$suffix, $dateDefault, '', ' onchange="'.esc_attr($updateFunction).'();"', $relativeTime), SecurityHelper::ALLOWED_HTML_DATE ); break; case 'language': $this->displayLanguageField($option, $suffix, $updateFunction); break; case 'custom': echo $option['output']; break; } } private function displayPicturesField(array $option, string $suffix, string $updateFunction, $defaultValues): void { $displayedPictures = $option['default'] ?? 'resized'; if (isset($defaultValues->pict)) { $displayedPictures = $defaultValues->pict; } $maxWidth = $defaultValues->maxwidth ?? 150; $maxHeight = $defaultValues->maxheight ?? 150; $valImages = [ acym_selectOption('1', 'ACYM_YES'), acym_selectOption('resized', 'ACYM_RESIZED'), acym_selectOption('0', 'ACYM_NO'), ]; echo '<div class="cell large-5 acym_plugin_field">'; echo esc_html(acym_translation('ACYM_DISPLAY')); echo '</div>'; echo '<div class="cell large-7">'; acym_radio( $valImages, 'pict'.$suffix, $displayedPictures, ['onclick' => $updateFunction.'();'], ['containerClass' => 'dcontent_pictures'], !acym_isAdmin() ); echo '</div>'; echo '<div id="pictsize'.esc_attr($suffix).'" class="cell grid-x margin-y margin-top-1"'; if ('resized' !== $displayedPictures) { echo ' style="display: none;"'; } echo '>'; echo '<div class="cell large-5 acym_plugin_field">'; echo esc_html(acym_translation('ACYM_MAX_WIDTH')); echo '</div>'; echo '<div class="cell large-7">'; echo '<input class="intext_input" name="pictwidth'.esc_attr($suffix).'" type="number" onchange="'.esc_attr($updateFunction).'();" value="'.intval($maxWidth).'"/>'; echo '</div>'; echo '<div class="cell large-5 acym_plugin_field">'; echo esc_html(acym_translation('ACYM_MAX_HEIGHT')); echo '</div>'; echo '<div class="cell large-7">'; echo '<input class="intext_input" name="pictheight'.esc_attr($suffix).'" type="number" onchange="'.esc_attr($updateFunction).'();" value="'.intval($maxHeight).'"/>'; echo '</div>'; echo '</div>'; if (!empty($option['caption'])) { echo '<div class="cell grid-x margin-top-1">'; echo '<label class="cell large-5 acym_plugin_field">'; echo esc_html(acym_translation('ACYM_CAPTION')); echo '</label>'; acym_radio( [ acym_selectOption('1', 'ACYM_YES'), acym_selectOption('0', 'ACYM_NO'), ], 'caption'.$suffix, $defaultValues->caption ?? '0', ['onclick' => $updateFunction.'();'], ['containerClass' => 'cell large-7'] ); echo '</div>'; } } private function displayCheckboxField(array $option, string $suffix, string $updateFunction): void { if (!empty($option['default'])) { $checkedValues = explode(',', $option['default']); foreach ($option['options'] as $key => $oneOption) { $oneOption[1] = in_array($key, $checkedValues); $option['options'][$key] = $oneOption; } } echo '<div class="cell grid-x">'; foreach ($option['options'] as $value => $title) { echo '<div class="cell medium-6" '; if (!empty($title[2]) && is_array($title[2])) { foreach ($title[2] as $attribute => $value) { echo esc_attr($attribute).'="'.esc_attr($value).'"'; } } echo '>'; echo '<input type="checkbox" name="'.esc_attr($option['name'].$suffix).'" value="'.esc_attr($value).'" id="'.esc_attr($value.$suffix).'" onclick="'.esc_attr($updateFunction).'();" '; acym_checked((bool)$title[1]); echo ' />'; echo '<label style="margin-left:5px" for="'.esc_attr($value.$suffix).'">'; echo esc_html(acym_translation($title[0])); echo '</label>'; echo '</div>'; } echo '</div>'; } private function displayRadioField(array $option, string $suffix, string $updateFunction): void { $radioOptions = []; foreach ($option['options'] as $value => $title) { $radioOptions[] = acym_selectOption($value, $title); } acym_radio( $radioOptions, $option['name'].$suffix, $option['default'], ['onclick' => $updateFunction.'();'], ['pluginMode' => true], !acym_isAdmin() ); } private function displaySelectField(array $option, string $suffix, string $updateFunction): void { $selectOptions = []; foreach ($option['options'] as $value => $title) { if (is_object($title)) { $selectOptions[] = acym_selectOption($title->value, $title->text); } else { $selectOptions[] = acym_selectOption($value, $title); } } $default = empty($option['default']) ? null : $option['default']; $defaultOrder = null; if (!empty($default) && strpos($default, ',')) { [$default, $defaultOrder] = explode(',', $default); } $attributes = [ 'onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix, ]; if ($option['name'] === 'order') { $attributes['class'] = 'acym__dynamics__ordering__select'; } acym_select( $selectOptions, $option['name'].$suffix, $default, $attributes, 'value', 'text', null, false, true ); if ($option['name'] === 'order') { $dirs = [ 'desc' => acym_translation('ACYM_DESC'), 'asc' => acym_translation('ACYM_ASC'), ]; if (empty($defaultOrder)) { $defaultOrder = empty($option['defaultdir']) ? null : $option['defaultdir']; } echo ' '; acym_select( $dirs, 'orderdir'.$suffix, $defaultOrder, [ 'onchange' => $updateFunction.'();', 'style' => 'width: 115px;', 'class' => 'acym__dynamics__ordering__select', ], 'value', 'text', null, false, true ); } } private function displayMultiselectField(array $option, string $suffix, string $updateFunction): void { $selectOptions = []; foreach ($option['options'] as $value => $title) { $selectOptions[] = acym_selectOption($value, $title); } if (!isset($option['default'])) { $option['default'] = []; } if (!is_array($option['default'])) { $option['default'] = explode(',', $option['default']); } acym_selectMultiple( $selectOptions, $option['name'].$suffix, $option['default'], ['onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix], 'value', 'text', true ); } private function displayLanguageField(array $option, string $suffix, string $updateFunction): void { $languageOptions = ['any' => acym_translation('ACYM_ANY')]; foreach (acym_getLanguages(true) as $language) { $languageOptions[$language->language] = $language->name; } if (empty($option['default'])) { $option['default'] = acym_getVar('string', 'language'); if (acym_isMultilingual() && (empty($option['default']) || $option['default'] === 'main')) { $option['default'] = $this->config->get('multilingual_default'); } } acym_select( $languageOptions, $option['name'].$suffix, empty($option['default']) ? null : $option['default'], ['onchange' => $updateFunction.'();', 'id' => $option['name'].$suffix], 'value', 'text', null, false, true ); } private function displayFormatPanel(string $suffix, string $updateFunction, string $type, $defaultValues): void { echo '<div class="grid-x">'; echo '<div class="cell large-3">'; echo esc_html(acym_translation('ACYM_FORMAT')); echo '</div>'; echo '<div class="cell large-9 dcontentFormatContainer">'; $default = empty($defaultValues->format) ? 'TOP_LEFT' : $defaultValues->format; $formats = ['TOP_LEFT', 'TOP_RIGHT', 'TITLE_IMG', 'TITLE_IMG_RIGHT', 'CENTER_IMG', 'TOP_IMG', 'COL_LEFT', 'COL_RIGHT']; foreach ($formats as $oneFormat) { $class = 'button-radio'; if ($default === $oneFormat) { $class .= ' button-radio-selected'; } echo '<button class="'.esc_attr($class).'" acym-button-radio-group="dcontentFormat'.esc_attr($suffix).'" acym-data-type="'.esc_attr($oneFormat).'" acym-callback="'.esc_attr($updateFunction).'">'; echo '<img alt="'.esc_attr($oneFormat).'" src="'.esc_url(ACYM_IMAGES.'editor/dcontent_formats/'.strtolower($oneFormat).'.png').'"/>'; echo '</button>'; } echo '</div>'; if ($type === 'grouped') { echo '<div class="cell large-3">'; echo esc_html(acym_translation('ACYM_ALTERNATE')); acym_info(['textShownInTooltip' => 'ACYM_ALTERNATE_DESC']); echo '</div>'; echo '<div class="cell large-9">'; acym_boolean( 'alternate'.$suffix, !empty($defaultValues->alternate), 'alternate'.$suffix, ['onclick' => $updateFunction.'();'] ); echo '</div>'; } echo '</div>'; } private function appendOptionJs(array &$jsOptionsMerge, array $option, string $suffix, string $type): void { switch ($option['type']) { case 'pictures': if (!empty($option['caption'])) { $jsOptionsMerge[] = 'otherinfo += "| caption:" + jQuery(\'input[name="caption'.esc_attr($suffix).'"]:checked\').val();'; } $jsOptionsMerge[] = ' var _pictVal'.esc_attr($suffix).' = jQuery(\'input[name="pict'.esc_attr($suffix).'"]:checked\').val(); otherinfo += "| pict:" + _pictVal'.esc_attr($suffix).'; if(_pictVal'.esc_attr($suffix).' == "resized"){ jQuery("#pictsize'.esc_attr($suffix).'").show(); otherinfo += "| maxwidth:" + jQuery(\'input[name="pictwidth'.esc_attr($suffix).'"]\').val(); otherinfo += "| maxheight:" + jQuery(\'input[name="pictheight'.esc_attr($suffix).'"]\').val(); }else{ jQuery("#pictsize'.esc_attr($suffix).'").hide(); }'; break; case 'checkbox': $separator = empty($option['separator']) ? ',' : $option['separator']; $jsOptionsMerge[] = 'var _checked'.esc_attr($option['name'].$suffix).' = []; jQuery("input:checkbox[name='.esc_attr($option['name'].$suffix).']:checked").each(function(){ _checked'.esc_attr($option['name'].$suffix).'.push(jQuery(this).val()); }); if(_checked'.esc_attr($option['name'].$suffix).'.length) otherinfo += "| '.esc_attr($option['name']).':" + _checked'.esc_attr( $option['name'].$suffix ).'.join("'.esc_attr($separator).'");'; if ($option['title'] === 'ACYM_DISPLAY' && (!isset($option['format']) || $option['format'])) { if ($type === 'grouped') { $jsOptionsMerge[] = 'var alternate = jQuery(\'input[name="alternate'.esc_attr($suffix).'"]:checked\').val();'; $jsOptionsMerge[] = 'if (!acym_helper.empty(alternate)) otherinfo += "| alternate";'; } $jsOptionsMerge[] = 'var selectedFormatOption = jQuery(\'.button-radio-selected[acym-button-radio-group="dcontentFormat'.esc_attr($suffix).'"]\')'; $jsOptionsMerge[] = 'if (!acym_helper.empty(selectedFormatOption)) otherinfo += "| format:" + selectedFormatOption.attr("acym-data-type");'; } break; case 'boolean': case 'radio': $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'input[name="'.esc_attr($option['name'].$suffix).'"]:checked\').val();'; break; case 'select': if ($option['name'] === 'order') { $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr( $option['name'].$suffix ).'"]\').val() + "," + jQuery(\'[name="orderdir'.esc_attr($suffix).'"]\').val();'; } else { $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr($option['name'].$suffix).'"]\').val();'; } break; case 'language': $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'[name="'.esc_attr($option['name'].$suffix).'"]\').val();'; break; case 'multiselect': $jsOptionsMerge[] = ' var theMultiSelect = document.querySelector(\'[name="'.esc_attr($option['name'].$suffix).'[]"]\'); var selectedOptions = []; for(var i = 0 ; i < theMultiSelect.length ; i++){ if(theMultiSelect[i].selected){ selectedOptions.push(theMultiSelect[i].value); } } otherinfo += "| '.esc_attr($option['name']).':" + selectedOptions.join(",");'; break; case 'text': case 'number': case 'intextfield': case 'date': $jsOptionsMerge[] = 'otherinfo += "| '.esc_attr($option['name']).':" + jQuery(\'input[name="'.esc_attr($option['name'].$suffix).'"]\').val();'; break; case 'custom': //TODO $jsOptionsMerge[] = $option['js']; break; } } private function displayDynamicScript(array $jsOptionsMerge, string $suffix, string $updateFunction, string $dynamicIdentifier, string $type, $defaultValues): void { $storageVar = 'window._additionalInfo'.$suffix; echo '<script type="text/javascript">'; echo 'var _selectedRows'.esc_attr($suffix).' = [];'; echo 'var _selectedRows = [];'; echo esc_attr($storageVar).' = '.esc_attr($storageVar).' || {};'; if (!empty($defaultValues->id) && (empty($defaultValues->defaultPluginTab) || $dynamicIdentifier === $defaultValues->defaultPluginTab)) { $delimiter = strpos($defaultValues->id, '-') ? '-' : ','; foreach (explode($delimiter, $defaultValues->id) as $value) { if (empty($value)) { continue; } echo '_selectedRows'.esc_attr($suffix).'['.intval($value).'] = true;'; } } echo 'function applyContent'.esc_attr($suffix).'(contentid, row){'; echo 'if(_selectedRows'.esc_attr($suffix).'[contentid]){'; echo 'jQuery(row).removeClass("selected_row");'; echo 'delete _selectedRows'.esc_attr($suffix).'[contentid];'; echo '}else{'; if ($type === 'individual') { echo 'for(let elementKey in _selectedRows'.esc_attr($suffix).') {'; echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(elementKey)) continue;'; echo 'jQuery(\'[data-id="\' + elementKey + \'"]\').removeClass("selected_row");'; echo 'delete _selectedRows'.esc_attr($suffix).'[elementKey];'; echo '}'; } echo 'jQuery(row).addClass("selected_row");'; echo '_selectedRows'.esc_attr($suffix).'[contentid] = true;'; echo '}'; echo esc_attr($updateFunction).'();'; echo 'if(typeof _selectedRows !== "undefined"){ _selectedRows = _selectedRows'.esc_attr($suffix).'; }'; echo '}'; echo 'function '.esc_attr($updateFunction).'(){'; echo 'var tag = ""; var otherinfo = "";'; echo implode("\r\n\r\n", $jsOptionsMerge); echo 'for (let [index, info] of Object.entries('.esc_attr($storageVar).')){ otherinfo += "| "+index+":"+info; }'; if ($type === 'individual') { echo 'for(var i in _selectedRows'.esc_attr($suffix).'){'; echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(i)) continue;'; echo 'tag = tag + "{'.esc_attr($dynamicIdentifier).':" + i + otherinfo + "}";'; echo '}'; } elseif ($type === 'grouped') { echo 'tag = "{'.esc_attr($dynamicIdentifier).':";'; echo 'for(var icat in _selectedRows'.esc_attr($suffix).'){'; echo 'if(!_selectedRows'.esc_attr($suffix).'.hasOwnProperty(icat)) continue;'; echo 'tag += icat + "-";'; echo '}'; echo 'tag += otherinfo + "}";'; } elseif ($type === 'simple') { echo 'tag = "{'.esc_attr($dynamicIdentifier).':" + otherinfo + "}";'; } echo 'acym_editorWysidDynamic.insertDContent(tag);'; echo '}'; echo 'function addAdditionalInfo'.esc_attr($suffix).'(index, value){'; echo esc_attr($storageVar).'[index] = value;'; echo esc_attr($updateFunction).'();'; echo '}'; echo '</script>'; } /** * In Joomla, we can translate elements in extensions using the FaLang extension */ public function translateItem(object &$item, object &$tag, string $referenceTable, int $referenceId = 0): void { if (!acym_isExtensionActive('com_falang')) { return; } if (!empty($tag->falang)) { $langId = $tag->falang; } elseif (!empty($tag->lang)) { $langId = intval(substr($tag->lang, strpos($tag->lang, ',') + 1)); } elseif (!empty($this->contextLanguage)) { $languages = acym_loadObjectList('SELECT `lang_id`, `lang_code`, `sef` FROM #__languages'); foreach ($languages as $language) { if (in_array($this->contextLanguage, [$language->lang_code, $language->sef])) { $langId = $language->lang_id; break; } } } if (empty($langId)) { return; } if (empty($referenceId)) { $referenceId = $tag->id; } $translations = acym_loadObjectList( 'SELECT `reference_field`, `value` FROM `#__falang_content` WHERE `published` = 1 AND `reference_table` = '.acym_escapeDB($referenceTable).' AND `language_id` = '.intval($langId).' AND `reference_id` = '.intval($referenceId) ); if (empty($translations)) { return; } foreach ($translations as $oneTranslation) { if (empty($oneTranslation->value)) { continue; } $translatedField = $oneTranslation->reference_field; $item->$translatedField = $oneTranslation->value; } } public function createDummyEmailObject(int $mailId, string $code, string $previewBody): object { if (!empty($mailId)) { $mailClass = new MailClass(); $email = $mailClass->getOneById($mailId); } if (empty($email)) { $email = new \stdClass(); $email->id = 0; $email->name = ''; $email->subject = ''; $email->from_name = ''; $email->from_email = ''; $email->reply_to_name = ''; $email->reply_to_email = ''; $email->bcc = ''; $email->links_language = ''; } $language = acym_getVar('string', 'language', 'main'); if (!empty($language)) { if ($language === 'main') { $language = $this->config->get('multilingual_default', ACYM_DEFAULT_LANGUAGE); } $email->links_language = $language; } $email->creation_date = acym_date('now', 'Y-m-d H:i:s', false); $email->creator_id = acym_currentUserId(); $email->thumbnail = ''; $email->drag_editor = '1'; $email->type = MailClass::TYPE_STANDARD; $email->settings = ''; $email->stylesheet = ''; $email->attachments = ''; // This is only the dynamic text/content code $email->body = $code; // This is the whole editor current content $email->previewBody = $previewBody; return $email; } }