| 1 |
<?php |
| 2 |
|
| 3 |
use AcyMailing\Classes\UserClass; |
| 4 |
|
| 5 |
trait ManagetextInsertion |
| 6 |
{ |
| 7 |
private array $noIfStatementTags = []; |
| 8 |
|
| 9 |
public function replaceContent(object &$email): void |
| 10 |
{ |
| 11 |
$this->replaceRandom($email); |
| 12 |
$this->handleAnchors($email); |
| 13 |
$this->fixPicturesOutlook($email); |
| 14 |
} |
| 15 |
|
| 16 |
public function replaceUserInformation(object &$email, ?object &$user, bool $send = true): void |
| 17 |
{ |
| 18 |
$this->pluginHelper->cleanHtml($email->body); |
| 19 |
$this->pluginHelper->replaceVideos($email->body); |
| 20 |
|
| 21 |
$this->removeText($email); |
| 22 |
$this->ifStatement($email, $user); |
| 23 |
$this->replaceTranslations($email, $user); |
| 24 |
$this->replaceConfigValues($email); |
| 25 |
} |
| 26 |
|
| 27 |
/** |
| 28 |
* Replaces tags such as {trans:ACYM_MY_TRANSLATION} |
| 29 |
*/ |
| 30 |
private function replaceTranslations(&$email, $user) |
| 31 |
{ |
| 32 |
//load the tags |
| 33 |
$tags = $this->pluginHelper->extractTags($email, 'trans'); |
| 34 |
if (empty($tags)) { |
| 35 |
return; |
| 36 |
} |
| 37 |
|
| 38 |
$tagsReplaced = []; |
| 39 |
foreach ($tags as $i => $oneTag) { |
| 40 |
$val = ''; |
| 41 |
// Complex tag (contains "|param1|param2|...") for dynamic text with values to replace |
| 42 |
$arrayVal = []; |
| 43 |
foreach ($oneTag as $valname => $oneValue) { |
| 44 |
if ($valname == 'id') { |
| 45 |
$val = trim(acym_stripTags($oneValue)); |
| 46 |
} elseif ($valname != 'default') { |
| 47 |
// Recreate tags (not before to prevent premature end of tag when parsing) |
| 48 |
$arrayVal[] = '{'.$valname.'}'; |
| 49 |
} |
| 50 |
} |
| 51 |
|
| 52 |
if (empty($val)) { |
| 53 |
continue; |
| 54 |
} |
| 55 |
if (!empty($user->language)) { |
| 56 |
$language = $user->language; |
| 57 |
} elseif (!empty($email->language)) { |
| 58 |
$language = $email->language; |
| 59 |
} else { |
| 60 |
$language = acym_getLanguageTag(); |
| 61 |
} |
| 62 |
|
| 63 |
$previousLanguage = acym_setLanguage($language); |
| 64 |
acym_loadLanguage($language); |
| 65 |
|
| 66 |
static $done = false; |
| 67 |
if (!$done && strpos($val, 'COM_USERS') !== false) { |
| 68 |
$done = true; |
| 69 |
//Com_users? Let's load the com_users language file then! |
| 70 |
|
| 71 |
acym_loadLanguageFile('com_users'); |
| 72 |
} |
| 73 |
if (!empty($arrayVal)) { |
| 74 |
$translation = acym_translation($val); |
| 75 |
$paramsIncluded = vsprintf($translation, $arrayVal); |
| 76 |
if ($translation === $paramsIncluded) { |
| 77 |
$translation = preg_replace( |
| 78 |
'/\{[A-Z_]+\}/', |
| 79 |
'%s', |
| 80 |
$translation |
| 81 |
); |
| 82 |
$paramsIncluded = vsprintf($translation, $arrayVal); |
| 83 |
} |
| 84 |
$tagsReplaced[$i] = nl2br($paramsIncluded); |
| 85 |
} else { |
| 86 |
$tagsReplaced[$i] = acym_translation($val); |
| 87 |
} |
| 88 |
|
| 89 |
if (empty($previousLanguage)) $previousLanguage = acym_getLanguageTag(); |
| 90 |
acym_setLanguage($previousLanguage); |
| 91 |
acym_loadLanguage($previousLanguage); |
| 92 |
} |
| 93 |
|
| 94 |
//We replace standard tags |
| 95 |
$this->pluginHelper->replaceTags($email, $tagsReplaced, true); |
| 96 |
} |
| 97 |
|
| 98 |
private function replaceConfigValues(&$email) |
| 99 |
{ |
| 100 |
$tags = $this->pluginHelper->extractTags($email, 'config'); |
| 101 |
if (empty($tags)) { |
| 102 |
return; |
| 103 |
} |
| 104 |
|
| 105 |
$tagsReplaced = []; |
| 106 |
foreach ($tags as $i => $oneTag) { |
| 107 |
if (trim($oneTag->id) === 'sitename') { |
| 108 |
// We do not allow all config values for now only sitename. |
| 109 |
$tagsReplaced[$i] = acym_getCMSConfig('sitename'); |
| 110 |
} |
| 111 |
} |
| 112 |
|
| 113 |
$this->pluginHelper->replaceTags($email, $tagsReplaced, true); |
| 114 |
} |
| 115 |
|
| 116 |
private function replaceRandom(&$email) |
| 117 |
{ |
| 118 |
$randTag = $this->pluginHelper->extractTags($email, 'rand'); |
| 119 |
if (empty($randTag)) { |
| 120 |
return; |
| 121 |
} |
| 122 |
foreach ($randTag as $oneRandTag) { |
| 123 |
$results[$oneRandTag->id] = explode(';', $oneRandTag->id); |
| 124 |
$randNumber = acym_rand(0, count($results[$oneRandTag->id]) - 1); |
| 125 |
$results[$oneRandTag->id][count($results[$oneRandTag->id])] = $results[$oneRandTag->id][$randNumber]; |
| 126 |
} |
| 127 |
|
| 128 |
$tags = []; |
| 129 |
foreach (array_keys($results) as $oneResult) { |
| 130 |
$tags['{rand:'.$oneResult.'}'] = end($results[$oneResult]); |
| 131 |
} |
| 132 |
|
| 133 |
if (empty($tags)) { |
| 134 |
return; |
| 135 |
} |
| 136 |
$this->pluginHelper->replaceTags($email, $tags, true); |
| 137 |
} |
| 138 |
|
| 139 |
private function ifStatement(&$email, $user, $loop = 1) |
| 140 |
{ |
| 141 |
if (empty($email->id) || isset($this->noIfStatementTags[$email->id])) { |
| 142 |
return; |
| 143 |
} |
| 144 |
|
| 145 |
$isAdmin = acym_isAdmin(); |
| 146 |
|
| 147 |
if ($loop > 3) { |
| 148 |
if ($isAdmin) { |
| 149 |
acym_display('You cannot have more than 3 nested {if} tags.', 'warning'); |
| 150 |
} |
| 151 |
|
| 152 |
return; |
| 153 |
} |
| 154 |
|
| 155 |
$match = '#{if:(((?!{if).)*)}(((?!{if).)*){/if}#Uis'; |
| 156 |
$variables = ['subject', 'body', 'altbody', 'From', 'FromName', 'ReplyTo']; |
| 157 |
$found = false; |
| 158 |
$results = []; |
| 159 |
foreach ($variables as $var) { |
| 160 |
if (empty($email->$var)) continue; |
| 161 |
|
| 162 |
if (is_array($email->$var)) { |
| 163 |
foreach ($email->$var as $i => &$arrayField) { |
| 164 |
if (empty($arrayField) || !is_array($arrayField)) continue; |
| 165 |
|
| 166 |
foreach ($arrayField as $key => &$oneval) { |
| 167 |
$found = preg_match_all($match, $oneval, $results[$var.$i.'-'.$key]) || $found; |
| 168 |
if (empty($results[$var.$i.'-'.$key][0])) unset($results[$var.$i.'-'.$key]); |
| 169 |
} |
| 170 |
} |
| 171 |
} else { |
| 172 |
$found = preg_match_all($match, $email->$var, $results[$var]) || $found; |
| 173 |
//we unset the results so that we won't handle it later... it will save some memory and processing |
| 174 |
if (empty($results[$var][0])) unset($results[$var]); |
| 175 |
} |
| 176 |
} |
| 177 |
|
| 178 |
//If we didn't find anything... |
| 179 |
if (!$found) { |
| 180 |
if ($loop == 1) { |
| 181 |
$this->noIfStatementTags[$email->id] = true; |
| 182 |
} |
| 183 |
|
| 184 |
return; |
| 185 |
} |
| 186 |
|
| 187 |
// Handles error messages |
| 188 |
static $alreadyHandled = false; |
| 189 |
|
| 190 |
$userClass = new UserClass(); |
| 191 |
if (!empty($user->id)) { |
| 192 |
$acymUser = $userClass->getOneByIdWithCustomFields($user->id); |
| 193 |
} |
| 194 |
$tags = []; |
| 195 |
foreach ($results as $allresults) { |
| 196 |
foreach ($allresults[0] as $i => $oneTag) { |
| 197 |
if (empty($user->id)) { |
| 198 |
$tags[$oneTag] = ''; |
| 199 |
continue; |
| 200 |
} |
| 201 |
|
| 202 |
//Don't need to process twice a tag we already have! |
| 203 |
if (isset($tags[$oneTag])) { |
| 204 |
continue; |
| 205 |
} |
| 206 |
//We explode each argument of the tag |
| 207 |
$allresults[1][$i] = html_entity_decode($allresults[1][$i]); |
| 208 |
if ( |
| 209 |
!preg_match('#^(.+)(!=|<|>|>|<|!~)([^=!<>~]+)$#is', $allresults[1][$i], $operators) |
| 210 |
&& !preg_match('#^(.+)(=|~)([^=!<>~]+)$#is', $allresults[1][$i], $operators) |
| 211 |
) { |
| 212 |
if ($isAdmin) { |
| 213 |
acym_enqueueMessage(acym_translationSprintf('ACYM_OPERATION_NOT_FOUND', $allresults[1][$i]), 'error'); |
| 214 |
} |
| 215 |
$tags[$oneTag] = $allresults[3][$i]; |
| 216 |
continue; |
| 217 |
} |
| 218 |
$field = trim($operators[1]); |
| 219 |
$prop = ''; |
| 220 |
|
| 221 |
$operatorsParts = explode('.', $operators[1]); |
| 222 |
$type = 'acym'; |
| 223 |
if (count($operatorsParts) > 1 && in_array($operatorsParts[0], ['acym', 'joomla', 'var'])) { |
| 224 |
$type = $operatorsParts[0]; |
| 225 |
unset($operatorsParts[0]); |
| 226 |
$field = implode('.', $operatorsParts); |
| 227 |
} |
| 228 |
|
| 229 |
// Handle Joomla fields first |
| 230 |
if ($type === 'joomla') { |
| 231 |
if (!empty($user->userid)) { |
| 232 |
if ($field === 'gid') { |
| 233 |
//We have an userid... lets load its groups then! |
| 234 |
$prop = implode(';', acym_loadResultArray('SELECT group_id FROM #__user_usergroup_map WHERE user_id = '.intval($user->userid))); |
| 235 |
} else { |
| 236 |
//We need to load the value for that field... |
| 237 |
$juser = acym_loadObject('SELECT * FROM #__users WHERE id = '.intval($user->userid)); |
| 238 |
if (isset($juser->{$field})) { |
| 239 |
$prop = strtolower($juser->{$field}); |
| 240 |
} else { |
| 241 |
if ($isAdmin && !$alreadyHandled) { |
| 242 |
acym_enqueueMessage('User variable not set: '.$field.' in '.$allresults[1][$i], 'error'); |
| 243 |
} |
| 244 |
$alreadyHandled = true; |
| 245 |
} |
| 246 |
} |
| 247 |
} |
| 248 |
} elseif ($type === 'var') { |
| 249 |
// Static var to have a tag inside an if condition, for example {if:var.{cbtag:name}=adrien}...{/if} |
| 250 |
$prop = strtolower($field); |
| 251 |
} else { |
| 252 |
// AcyMailing fields |
| 253 |
if (!isset($acymUser[$field])) { |
| 254 |
// We make sure to display it only once |
| 255 |
if ($isAdmin && !$alreadyHandled) { |
| 256 |
acym_enqueueMessage('User variable not set: '.$field.' in '.$allresults[1][$i], 'error'); |
| 257 |
} |
| 258 |
$alreadyHandled = true; |
| 259 |
} else { |
| 260 |
$prop = strtolower($acymUser[$field]); |
| 261 |
} |
| 262 |
} |
| 263 |
|
| 264 |
$tags[$oneTag] = ''; |
| 265 |
$val = strtolower(trim($operators[3])); |
| 266 |
$prop = acym_stripTags($prop); |
| 267 |
//We can handle several propositions in one if it contains ; such as {if:category=34;214;53}... |
| 268 |
if ($operators[2] === '=' && ($prop == $val || in_array($prop, explode(';', $val)) || in_array($val, explode(';', $prop)))) { |
| 269 |
$tags[$oneTag] = $allresults[3][$i]; |
| 270 |
} elseif ($operators[2] === '!=' && $prop != $val) { |
| 271 |
$tags[$oneTag] = $allresults[3][$i]; |
| 272 |
} elseif (($operators[2] === '>' || $operators[2] === '>') && $prop > $val) { |
| 273 |
$tags[$oneTag] = $allresults[3][$i]; |
| 274 |
} elseif (($operators[2] === '<' || $operators[2] === '<') && $prop < $val) { |
| 275 |
$tags[$oneTag] = $allresults[3][$i]; |
| 276 |
} elseif ($operators[2] === '~' && strpos($prop, $val) !== false) { |
| 277 |
$tags[$oneTag] = $allresults[3][$i]; |
| 278 |
} elseif ($operators[2] === '!~' && strpos($prop, $val) === false) { |
| 279 |
$tags[$oneTag] = $allresults[3][$i]; |
| 280 |
} |
| 281 |
} |
| 282 |
} |
| 283 |
|
| 284 |
$this->pluginHelper->replaceTags($email, $tags, true); |
| 285 |
|
| 286 |
$this->ifStatement($email, $user, $loop + 1); |
| 287 |
} |
| 288 |
|
| 289 |
private function removeText(&$email) |
| 290 |
{ |
| 291 |
//Remove text |
| 292 |
$removeText = '{reg},{/reg},{pub},{/pub}'; |
| 293 |
if (!empty($removeText)) { |
| 294 |
$removeArray = explode(',', trim($removeText, ' ,')); |
| 295 |
if (!empty($email->body)) { |
| 296 |
$email->body = str_replace($removeArray, '', $email->body); |
| 297 |
} |
| 298 |
} |
| 299 |
|
| 300 |
|
| 301 |
$removetags = 'youtube'; |
| 302 |
if (!empty($removetags)) { |
| 303 |
$regex = []; |
| 304 |
$removeArray = explode(',', trim($removetags, ' ,')); |
| 305 |
foreach ($removeArray as $oneTag) { |
| 306 |
if (empty($oneTag)) { |
| 307 |
continue; |
| 308 |
} |
| 309 |
$regex[] = '#(?:{|%7B)'.preg_quote($oneTag, '#').'(?:}|%7D).*(?:{|%7B)/'.preg_quote($oneTag, '#').'(?:}|%7D)#Uis'; |
| 310 |
$regex[] = '#(?:{|%7B)'.preg_quote($oneTag, '#').'[^}]*(?:}|%7D)#Uis'; |
| 311 |
} |
| 312 |
|
| 313 |
if (!empty($email->body)) { |
| 314 |
$email->body = preg_replace($regex, '', $email->body); |
| 315 |
} |
| 316 |
} |
| 317 |
} |
| 318 |
|
| 319 |
private function handleAnchors(&$email) |
| 320 |
{ |
| 321 |
if (empty($email->body)) return; |
| 322 |
|
| 323 |
$newBody = preg_replace('/(<a +href="#[^"]*"[^>]*) target="_blank"([^>]*>)/Uis', '$1 $2', $email->body); |
| 324 |
|
| 325 |
if (!empty($newBody)) $email->body = $newBody; |
| 326 |
} |
| 327 |
|
| 328 |
/** |
| 329 |
* Make sure each image has a width/height |
| 330 |
* This function can be used by plugins as well |
| 331 |
*/ |
| 332 |
public function fixPicturesOutlook(&$email) |
| 333 |
{ |
| 334 |
$this->addPictureDimensions($email->body); |
| 335 |
$this->addPictureAlign($email->body); |
| 336 |
} |
| 337 |
|
| 338 |
public function addPictureDimensions(&$html) |
| 339 |
{ |
| 340 |
if (!preg_match_all('#(<img)([^>]*>)#i', $html, $results)) { |
| 341 |
return; |
| 342 |
} |
| 343 |
|
| 344 |
$replace = []; |
| 345 |
$widthheight = ['width', 'height']; |
| 346 |
foreach ($results[0] as $num => $oneResult) { |
| 347 |
$add = []; |
| 348 |
foreach ($widthheight as $whword) { |
| 349 |
//We only add it if the picture does not already have the height/width and only if the height is specified as px |
| 350 |
//About the second regex, we make sure to ignore all line-height or other similar CSS styles by making sure it does not start with a character or an underscore or an hyphen. |
| 351 |
if (preg_match('#'.$whword.' *=#i', $oneResult) || !preg_match('#[^a-z_\-]'.$whword.' *:([0-9 ]{1,8})px#i', $oneResult, $resultWH)) continue; |
| 352 |
|
| 353 |
//We don't have the width= ... but we have one width:... |
| 354 |
//We make sure it's not an empty one... |
| 355 |
if (empty($resultWH[1])) continue; |
| 356 |
//Let's add the width as img parameter then |
| 357 |
$add[] = $whword.'="'.trim($resultWH[1]).'" '; |
| 358 |
} |
| 359 |
|
| 360 |
if (!empty($add)) { |
| 361 |
$replace[$oneResult] = '<img '.implode(' ', $add).$results[2][$num]; |
| 362 |
} |
| 363 |
} |
| 364 |
|
| 365 |
//Nothing to do |
| 366 |
if (!empty($replace)) { |
| 367 |
$html = str_replace(array_keys($replace), $replace, $html); |
| 368 |
preg_match_all('#(<img)([^>]*>)#i', $html, $results); |
| 369 |
} |
| 370 |
|
| 371 |
static $replace = []; |
| 372 |
foreach ($results[0] as $num => $oneResult) { |
| 373 |
if (isset($replace[$oneResult])) continue; |
| 374 |
if (strpos($oneResult, 'width=') || strpos($oneResult, 'height=')) continue; |
| 375 |
if (preg_match('#[^a-z_\-]width *:([0-9 ]{1,8})#i', $oneResult, $res)) continue; |
| 376 |
if (preg_match('#[^a-z_\-]height *:([0-9 ]{1,8})#i', $oneResult, $res)) continue; |
| 377 |
if (!preg_match('#src="([^"]*)"#i', $oneResult, $url)) continue; |
| 378 |
|
| 379 |
//Ok we have an url, it's $url[1] |
| 380 |
$imageUrl = $url[1]; |
| 381 |
|
| 382 |
//If the getimagesize failed, we don't want to do it again and again and again... |
| 383 |
$replace[$oneResult] = $oneResult; |
| 384 |
|
| 385 |
//We are nice guys... sometimes users use www. or not... so we convert both, same thing for httpS or http |
| 386 |
$base = str_replace(['http://www.', 'https://www.', 'http://', 'https://'], '', ACYM_LIVE); |
| 387 |
$replacements = ['https://www.'.$base, 'http://www.'.$base, 'https://'.$base, 'http://'.$base]; |
| 388 |
$localpict = false; |
| 389 |
foreach ($replacements as $oneReplacement) { |
| 390 |
if (strpos($imageUrl, $oneReplacement) === false) continue; |
| 391 |
|
| 392 |
$imageUrl = str_replace( |
| 393 |
[$oneReplacement, '/'], |
| 394 |
[ACYM_ROOT, DS], |
| 395 |
urldecode($imageUrl) |
| 396 |
); |
| 397 |
$localpict = true; |
| 398 |
break; |
| 399 |
} |
| 400 |
|
| 401 |
//It's not a local picture... we skip it, we don't want to try to load it, it will take too much time |
| 402 |
if (!$localpict) continue; |
| 403 |
|
| 404 |
$dim = @getimagesize($imageUrl); |
| 405 |
//could not load the picture... |
| 406 |
if (!$dim) continue; |
| 407 |
if (empty($dim[0]) || empty($dim[1])) continue; |
| 408 |
|
| 409 |
$replace[$oneResult] = str_replace('<img', '<img width="'.$dim[0].'" height="'.$dim[1].'"', $oneResult); |
| 410 |
} |
| 411 |
|
| 412 |
if (!empty($replace)) { |
| 413 |
$html = str_replace(array_keys($replace), $replace, $html); |
| 414 |
} |
| 415 |
} |
| 416 |
|
| 417 |
public function addPictureAlign(&$html) |
| 418 |
{ |
| 419 |
preg_match_all('#< *img([^>]*)>#Ui', $html, $allPictures); |
| 420 |
|
| 421 |
foreach ($allPictures[0] as $i => $onePict) { |
| 422 |
// 1 - We add a align="right" or align="left" for the pictures in order to have good result on Outlook 2007 |
| 423 |
if (strpos($onePict, 'align=') !== false) continue; |
| 424 |
if (!preg_match('#(style="[^"]*)(float *: *)(right|left|top|bottom|middle)#Ui', $onePict, $pictParams)) continue; |
| 425 |
|
| 426 |
$newPict = str_replace('<img', '<img align="'.$pictParams[3].'" ', $onePict); |
| 427 |
$html = str_replace($onePict, $newPict, $html); |
| 428 |
|
| 429 |
|
| 430 |
// 2 - We also add a hspace based on the margin parameter |
| 431 |
if (strpos($onePict, 'hspace=') !== false) continue; |
| 432 |
|
| 433 |
$hspace = 5; |
| 434 |
if (preg_match('#margin(-right|-left)? *:([^";]*)#i', $onePict, $margins)) { |
| 435 |
//If we have spaces, it may be a margin:34px 24px 78px; format. |
| 436 |
$currentMargins = explode(' ', trim($margins[2])); |
| 437 |
//If we have more than one param then we always use the second one (which is "right")... if not we use the first one |
| 438 |
$myMargin = (count($currentMargins) > 1) ? $currentMargins[1] : $currentMargins[0]; |
| 439 |
//We use it only if it's in px |
| 440 |
if (strpos($myMargin, 'px') !== false) $hspace = preg_replace('#[^0-9]#i', '', $myMargin); |
| 441 |
} |
| 442 |
|
| 443 |
$lastPict = str_replace('<img', '<img hspace="'.$hspace.'" ', $newPict); |
| 444 |
|
| 445 |
$html = str_replace($newPict, $lastPict, $html); |
| 446 |
} |
| 447 |
} |
| 448 |
} |
| 449 |
|