PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Helpers / global / file.php

file.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.1.0, at back/Helpers/global/file.php

717 lines 22.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 defined('ABSPATH') || die('Restricted Access');
3
4 use AcyMailing\Helpers\ImageHelper;
5
6 /**
7 * Function to return the number of bytes of a val like 2M
8 */
9 function acym_bytes(string $val): int
10 {
11 $val = trim($val);
12 if (empty($val)) {
13 return 0;
14 }
15 $last = strtolower($val[strlen($val) - 1]);
16 switch ($last) {
17 case 'g':
18 $val = intval($val) * 1073741824;
19 break;
20 case 'm':
21 $val = intval($val) * 1048576;
22 break;
23 case 'k':
24 $val = intval($val) * 1024;
25 break;
26 }
27
28 return (int)$val;
29 }
30
31 //Create a dir and an index.html file inside
32 function acym_createDir(string $dir, bool $report = true, bool $secured = false): bool
33 {
34 if (is_dir($dir)) {
35 return true;
36 }
37
38 $indexhtml = '<html><body bgcolor="#FFFFFF"></body></html>';
39
40 //Create the directory with an index file inside
41 try {
42 $status = acym_createFolder($dir);
43 } catch (Exception $e) {
44 $status = false;
45 }
46
47 if (!$status) {
48 if ($report) {
49 acym_display('Could not create the directory '.$dir, 'error');
50 }
51
52 return false;
53 }
54
55 try {
56 $status = acym_writeFile($dir.DS.'index.html', $indexhtml);
57 } catch (Exception $e) {
58 $status = false;
59 }
60
61 if (!$status && $report) {
62 acym_display('Could not create the file '.$dir.DS.'index.html', 'error');
63 }
64
65 if ($secured) {
66 try {
67 $htaccess = 'Order deny,allow'."\r\n".'Deny from all';
68 $status = acym_writeFile($dir.DS.'.htaccess', $htaccess);
69 } catch (Exception $e) {
70 $status = false;
71 }
72
73 if (!$status && $report) {
74 acym_display('Could not create the file '.$dir.DS.'.htaccess', 'error');
75 }
76 }
77
78 return $status;
79 }
80
81 function acym_isForbiddenFileExtension(string $filename): bool
82 {
83 // Always blocked regardless of the "allowed files" configuration, for security reasons
84 return (bool)preg_match('#\.(php.?|.?htm.?|pl|py|jsp|asp|sh|cgi)#Ui', $filename);
85 }
86
87 function acym_importFile(array $file, string $uploadPath, bool $onlyPict): ?string
88 {
89 // Check the token... no import without token!
90 wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
91
92 $config = acym_config();
93 $additionalMsg = '';
94
95 if ($file['error'] > 0) {
96 $file['error'] = intval($file['error']);
97 if ($file['error'] > 8) {
98 $file['error'] = 0;
99 }
100
101 $phpFileUploadErrors = [
102 0 => 'Unknown error',
103 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
104 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
105 3 => 'The uploaded file was only partially uploaded',
106 4 => 'No file was uploaded',
107 6 => 'Missing a temporary folder',
108 7 => 'Failed to write file to disk',
109 8 => 'A PHP extension stopped the file upload',
110 ];
111
112 acym_enqueueMessage(acym_translationSprintf('ACYM_ERROR_UPLOADING_FILE_X', $phpFileUploadErrors[$file['error']]), 'error');
113
114 return null;
115 }
116
117 acym_createDir($uploadPath, true);
118
119 if (!acym_isWritable($uploadPath)) {
120 acym_chmod($uploadPath, 0755);
121 if (!acym_isWritable($uploadPath)) {
122 acym_display(acym_translationSprintf('ACYM_WRITABLE_FOLDER', $uploadPath), 'error');
123
124 return null;
125 }
126 }
127
128 if ($onlyPict) {
129 $allowedExtensions = ['png', 'jpeg', 'jpg', 'gif', 'ico', 'bmp'];
130 } else {
131 $allowedExtensions = explode(',', $config->get('allowed_files'));
132 }
133
134 // We don't allow to upload anything else than a picture extension
135 if (!preg_match('#\.('.implode('|', $allowedExtensions).')$#Ui', $file['name'], $extension)) {
136 $ext = substr($file['name'], strrpos($file['name'], '.') + 1);
137 acym_display(
138 acym_translationSprintf(
139 'ACYM_ACCEPTED_TYPE',
140 esc_html($ext),
141 implode(', ', $allowedExtensions)
142 ),
143 'error'
144 );
145
146 return null;
147 }
148
149 // We will never allow some files to be uploaded...
150 // This should never happen... only if there is an hack tentative so no need to translate this error message
151 if (acym_isForbiddenFileExtension($file['name'])) {
152 acym_display(
153 'This extension name is blocked by the system regardless your configuration for security reasons',
154 'error'
155 );
156
157 return null;
158 }
159
160 // We remove all dots or space from the file name to avoid the double extension security issue and the fact some mail clients don't like spaces
161 $file['name'] = str_replace(
162 ['-', '.', ' ', '\''],
163 '_',
164 substr($file['name'], 0, strrpos($file['name'], '.'))
165 ).'.'.$extension[1];
166
167 if ($onlyPict) {
168 // Extra security: we make sure we only have a valid picture format
169 $imageSize = @getimagesize($file['tmp_name']);
170 if (empty($imageSize)) {
171 acym_display(acym_translation('ACYM_INVALID_IMAGE'), 'error');
172
173 return null;
174 }
175 }
176
177 if (file_exists($uploadPath.DS.$file['name'])) {
178 $i = 1;
179 $nameFile = preg_replace('/\\.[^.\\s]{3,4}$/', '', $file['name']);
180 $ext = substr($file['name'], strrpos($file['name'], '.') + 1);
181 while (file_exists($uploadPath.DS.$nameFile.'_'.$i.'.'.$ext)) {
182 $i++;
183 }
184
185 $file['name'] = $nameFile.'_'.$i.'.'.$ext;
186 $additionalMsg = '<br />'.acym_translationSprintf('ACYM_FILE_RENAMED', $file['name']);
187 if ($onlyPict) {
188 $additionalMsg .= '<br /><a style="color: blue; cursor: pointer;" onclick="confirmBox(\'rename\', \''.$file['name'].'\', \''.$nameFile.'.'.$ext.'\')">'.acym_translation(
189 'ACYM_RENAME_OR_REPLACE'
190 ).'</a>';
191 }
192 }
193
194 if (!acym_uploadFile($file['tmp_name'], rtrim($uploadPath, DS).DS.$file['name'])) {
195 acym_display(
196 acym_translationSprintf(
197 'ACYM_FAIL_UPLOAD',
198 '<b><i>'.esc_html($file['tmp_name']).'</i></b>',
199 '<b><i>'.esc_html(rtrim($uploadPath, DS).DS.$file['name']).'</i></b>'
200 ),
201 'error'
202 );
203
204 return null;
205 }
206
207 if ($onlyPict && $imageSize[0] > 1000) {
208 $imageHelper = new ImageHelper();
209 if ($imageHelper->available()) {
210 $imageHelper->maxHeight = 9999;
211 $imageHelper->maxWidth = 700;
212 $message = 'ACYM_IMAGE_RESIZED';
213 $imageHelper->destination = $uploadPath;
214 $thumb = $imageHelper->generateThumbnail(rtrim($uploadPath, DS).DS.$file['name']);
215 if (!empty($thumb)) {
216 acym_moveFile($thumb['file'], $uploadPath.DS.$file['name']);
217 $additionalMsg .= '<br />'.acym_translation($message);
218 }
219 }
220 }
221 acym_enqueueMessage(acym_translation('ACYM_SUCCESS_FILE_UPLOAD').$additionalMsg, 'success');
222
223 return $file['name'];
224 }
225
226 function acym_inputFile(
227 string $name,
228 string $value = '',
229 string $class = '',
230 array $attributes = [],
231 string $downloadUrl = ''
232 ): void {
233 $hasValue = !empty($value);
234 $noFileText = acym_translation('ACYM_NO_FILE_CHOSEN');
235 $baseName = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name;
236 ?>
237
238 <div class="cell <?php echo esc_attr($class); ?> grid-x acym__input__file__container">
239 <input type="hidden" name="<?php echo esc_attr($baseName); ?>" value="<?php echo esc_attr($value); ?>" class="acym__input__file__clear">
240 <input <?php
241 foreach ($attributes as $oneAttribute => $oneValue) {
242 if (is_array($oneValue) || is_object($oneValue)) {
243 $oneValue = json_encode($oneValue);
244 } elseif ($oneValue === true) {
245 $oneValue = $oneAttribute;
246 }
247
248 echo ' '.esc_attr($oneAttribute).'="'.esc_attr($oneValue).'"';
249 }
250 ?> style="display: none" type="file" name="<?php echo esc_attr($name); ?>">
251 <button type="button" class="acym__button__file button button-secondary cell shrink">
252 <?php echo esc_html(acym_translation('ACYM_CHOOSE_FILE')); ?>
253 </button>
254 <span class="cell shrink margin-left-2 margin-right-2 acym__input__file__name" data-no-file="<?php echo esc_attr($noFileText); ?>">
255 <?php echo esc_html($hasValue ? $value : $noFileText); ?>
256 </span>
257 <i class="acym__input__file__download cell shrink acym__color__blue cursor-pointer acymicon-download margin-right-1"
258 <?php echo $hasValue ? '' : ' style="display: none;"'; ?>
259 <?php echo !empty($downloadUrl) ? ' data-download-url="'.esc_url($downloadUrl).'"' : ''; ?>
260 title="<?php echo esc_attr(acym_translation('ACYM_DOWNLOAD')); ?>" aria-label="<?php echo esc_attr(acym_translation('ACYM_DOWNLOAD')); ?>"></i>
261 <i class="acymicon-close acym__color__red acym__input__file__delete cursor-pointer cell shrink margin-left-1"
262 <?php echo $hasValue ? '' : ' style="display: none;"'; ?>
263 title="<?php echo esc_attr(acym_translation('ACYM_DELETE')); ?>" aria-label="<?php echo esc_attr(acym_translation('ACYM_DELETE')); ?>"></i>
264 </div>
265 <?php
266 }
267
268 function acym_getFilesFolder(bool $ignoreVariables = false): string
269 {
270 $config = acym_config();
271 $uploadFolder = $config->get('uploadfolder', ACYM_UPLOAD_FOLDER);
272 if ($ignoreVariables) {
273 $uploadFolder = str_replace(['{userid}', '{groupname}', '{year}', '{month}', '{day}'], '', $uploadFolder);
274 }
275 $uploadFolder = trim($uploadFolder, '/');
276
277 $uploadFolder = str_replace('{userid}', acym_currentUserId(), $uploadFolder);
278 $uploadFolder = str_replace('{year}', gmdate('Y'), $uploadFolder);
279 $uploadFolder = str_replace('{month}', gmdate('m'), $uploadFolder);
280 $uploadFolder = str_replace('{day}', gmdate('d'), $uploadFolder);
281
282 return acym_replaceGroupTags($uploadFolder);
283 }
284
285 /**
286 * Find all sub folders...
287 * Return an array of all folders ordered properly
288 */
289 function acym_generateArborescence(array $folders): array
290 {
291 //Recursive algorythm to catch subfolders
292 $folderList = [];
293 foreach ($folders as $folder) {
294 $folderPath = acym_cleanPath(ACYM_ROOT.trim(str_replace('/', DS, trim($folder)), DS));
295 if (!file_exists($folderPath)) {
296 acym_createDir($folderPath);
297 }
298 $subFolders = acym_listFolderTree($folderPath, '', 15);
299 $folderList[$folder] = [];
300 foreach ($subFolders as $oneFolder) {
301 $subFolder = str_replace(ACYM_ROOT, '', $oneFolder['relname']);
302 $subFolder = str_replace(DS, '/', $subFolder);
303 $folderList[$folder][$subFolder] = ltrim($subFolder, '/');
304 }
305 $folderList[$folder] = array_unique($folderList[$folder]);
306 }
307
308 return $folderList;
309 }
310
311 function acym_makeSafeFile(string $file): string
312 {
313 $file = rtrim($file, '.');
314 $regex = ['#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#'];
315
316 return trim(preg_replace($regex, '', $file));
317 }
318
319 function acym_getFolders(
320 string $path,
321 string $filter = '.',
322 bool $recurse = false,
323 bool $full = false,
324 array $exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
325 array $excludefilter = ['^\..*']
326 ): array {
327 $path = acym_cleanPath($path);
328
329 if (!is_dir($path)) {
330 acym_enqueueMessage(acym_translationSprintf('ACYM_IS_NOT_A_FOLDER', $path), 'error');
331
332 return [];
333 }
334
335 if (count($excludefilter)) {
336 $excludefilter_string = '/('.implode('|', $excludefilter).')/';
337 } else {
338 $excludefilter_string = '';
339 }
340
341 $arr = acym_getItems($path, $filter, $recurse, $full, $exclude, $excludefilter_string, false);
342 asort($arr);
343
344 return array_values($arr);
345 }
346
347 function acym_getFiles(
348 string $path,
349 string $filter = '.',
350 bool $recurse = false,
351 bool $full = false,
352 array $exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
353 array $excludefilter = [
354 '^\..*',
355 '.*~',
356 ],
357 bool $naturalSort = false
358 ): array {
359 $path = acym_cleanPath($path);
360
361 if (!is_dir($path)) {
362 acym_enqueueMessage(acym_translationSprintf('ACYM_IS_NOT_A_FOLDER', $path), 'error');
363
364 return [];
365 }
366
367 if (count($excludefilter)) {
368 $excludefilter_string = '/('.implode('|', $excludefilter).')/';
369 } else {
370 $excludefilter_string = '';
371 }
372
373 $arr = acym_getItems($path, $filter, $recurse, $full, $exclude, $excludefilter_string, true);
374
375 if ($naturalSort) {
376 natsort($arr);
377 } else {
378 asort($arr);
379 }
380
381 return array_values($arr);
382 }
383
384 function acym_getItems(
385 string $path,
386 string $filter,
387 bool $recurse,
388 bool $full,
389 array $exclude,
390 string $excludefilter_string,
391 bool $findfiles
392 ): array {
393 $arr = [];
394
395 if (!($handle = @opendir($path))) {
396 return $arr;
397 }
398
399 while (($file = readdir($handle)) !== false) {
400 if (
401 $file == '.'
402 || $file == '..'
403 || in_array($file, $exclude)
404 || (!empty($excludefilter_string) && preg_match($excludefilter_string, $file))
405 ) {
406 continue;
407 }
408 $fullpath = rtrim($path, '/').'/'.$file;
409
410 $isDir = is_dir($fullpath);
411
412 if (($isDir xor $findfiles) && preg_match("/$filter/", $file)) {
413 if ($full) {
414 $arr[] = $fullpath;
415 } else {
416 $arr[] = $file;
417 }
418 }
419
420 if ($isDir && $recurse) {
421 $arr = array_merge(
422 $arr,
423 acym_getItems(
424 $fullpath,
425 $filter,
426 true,
427 $full,
428 $exclude,
429 $excludefilter_string,
430 $findfiles
431 )
432 );
433 }
434 }
435
436 closedir($handle);
437
438 return $arr;
439 }
440
441 function acym_copyFolder(string $src, string $dest, ?string $path = null, bool $force = false, bool $use_streams = false): bool
442 {
443 if (!empty($path)) {
444 $src = acym_cleanPath($path.'/'.$src);
445 $dest = acym_cleanPath($path.'/'.$dest);
446 }
447
448 $src = rtrim($src, DIRECTORY_SEPARATOR);
449 $dest = rtrim($dest, DIRECTORY_SEPARATOR);
450
451 if (!file_exists($src)) {
452 acym_enqueueMessage(acym_translationSprintf('ACYM_FOLDER_DOES_NOT_EXIST', $src), 'error');
453
454 return false;
455 }
456
457 if (file_exists($dest) && !$force) {
458 acym_enqueueMessage(acym_translationSprintf('ACYM_FOLDER_ALREADY_EXIST', $dest), 'error');
459
460 return true;
461 }
462
463 if (!acym_createFolder($dest)) {
464 acym_enqueueMessage(acym_translation('ACYM_CANNOT_CREATE_DESTINATION_FOLDER'), 'error');
465
466 return false;
467 }
468
469 if (!($dh = @opendir($src))) {
470 acym_enqueueMessage(acym_translation('ACYM_CANNOT_OPEN_SOURCE_FOLDER'), 'error');
471
472 return false;
473 }
474
475 while (($file = readdir($dh)) !== false) {
476 $sfid = $src.'/'.$file;
477 $dfid = $dest.'/'.$file;
478
479 switch (filetype($sfid)) {
480 case 'dir':
481 if ($file != '.' && $file != '..') {
482 $ret = acym_copyFolder($sfid, $dfid, null, $force, $use_streams);
483
484 if ($ret !== true) {
485 return $ret;
486 }
487 }
488 break;
489
490 case 'file':
491 if (!@copy($sfid, $dfid)) {
492 acym_enqueueMessage(acym_translationSprintf('ACYM_COPY_FILE_FAILED_PERMISSION', $sfid), 'error');
493
494 return false;
495 }
496 break;
497 }
498 }
499
500 return true;
501 }
502
503 function acym_listFolderTree(string $path, string $filter, int $maxLevel = 3, int $level = 0, int $parent = 0): array
504 {
505 $dirs = [];
506
507 if ($level === 0) {
508 $GLOBALS['acym_folder_tree_index'] = 0;
509 }
510
511 if ($level < $maxLevel) {
512 $folders = acym_getFolders($path, $filter);
513
514 foreach ($folders as $name) {
515 $id = ++$GLOBALS['acym_folder_tree_index'];
516 $fullName = acym_cleanPath($path.'/'.$name);
517 $dirs[] = [
518 'id' => $id,
519 'parent' => $parent,
520 'name' => $name,
521 'fullname' => $fullName,
522 'relname' => str_replace(ACYM_ROOT, '', $fullName),
523 ];
524 $dirs2 = acym_listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
525 $dirs = array_merge($dirs, $dirs2);
526 }
527 }
528
529 return $dirs;
530 }
531
532 /**
533 * @param mixed $buffer
534 */
535 function acym_writeFile(string $file, $buffer, int $flags = 0): bool
536 {
537 if (!file_exists(dirname($file)) && !acym_createFolder(dirname($file))) {
538 return false;
539 }
540
541 $file = acym_cleanPath($file);
542
543 return is_int(file_put_contents($file, $buffer, $flags));
544 }
545
546 function acym_copyFile(string $src, string $dest, string $path = ''): bool
547 {
548 if (!empty($path)) {
549 $src = acym_cleanPath($path.DS.$src);
550 $dest = acym_cleanPath($path.DS.$dest);
551 }
552
553 if (!is_readable($src)) {
554 acym_enqueueMessage(acym_translationSprintf('ACYM_COULD_NOT_FIND_FILE_SOURCE_PERMISSION', $src), 'error');
555
556 return false;
557 }
558
559 if (!@copy($src, $dest)) {
560 acym_enqueueMessage(acym_translationSprintf('ACYM_COULD_NOT_COPY_FILE_X_TO_X', $src, $dest), 'error');
561
562 return false;
563 }
564
565 return true;
566 }
567
568 function acym_fileGetExt(string $file): string
569 {
570 $pathInfo = pathinfo($file);
571
572 return empty($pathInfo['extension']) ? '' : strtolower($pathInfo['extension']);
573 }
574
575 function acym_cleanPath(string $path, string $ds = DIRECTORY_SEPARATOR): string
576 {
577 $path = trim($path);
578
579 if (empty($path)) {
580 $path = ACYM_ROOT;
581 } elseif (($ds === '\\') && substr($path, 0, 2) == '\\\\') {
582 $path = "\\".preg_replace('#[/\\\\]+#', $ds, $path);
583 } else {
584 $path = preg_replace('#[/\\\\]+#', $ds, $path);
585 }
586
587 return $path;
588 }
589
590 function acym_createArchive(string $name, array $files): bool
591 {
592 $contents = [];
593 $ctrldir = [];
594
595 $timearray = getdate();
596 $dostime = (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
597 $dtime = dechex($dostime);
598 $hexdtime = chr(hexdec($dtime[6].$dtime[7])).chr(hexdec($dtime[4].$dtime[5])).chr(hexdec($dtime[2].$dtime[3])).chr(
599 hexdec($dtime[0].$dtime[1])
600 );
601
602 foreach ($files as $file) {
603 $data = $file['data'];
604 $filename = str_replace('\\', '/', $file['name']);
605
606 $fr = "\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00".$hexdtime;
607
608 $unc_len = strlen($data);
609 $crc = crc32($data);
610 $zdata = gzcompress($data);
611 $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
612 $c_len = strlen($zdata);
613
614 $fr .= pack('V', $crc).pack('V', $c_len).pack('V', $unc_len).pack('v', strlen($filename)).pack(
615 'v',
616 0
617 ).$filename.$zdata;
618
619 $old_offset = strlen(implode('', $contents));
620 $contents[] = $fr;
621
622 $cdrec = "\x50\x4b\x01\x02\x00\x00\x14\x00\x00\x00\x08\x00".$hexdtime;
623 $cdrec .= pack('V', $crc).pack('V', $c_len).pack('V', $unc_len).pack('v', strlen($filename)).pack('v', 0).pack(
624 'v',
625 0
626 ).pack('v', 0).pack('v', 0).pack('V', 32).pack('V', $old_offset).$filename;
627
628 $ctrldir[] = $cdrec;
629 }
630
631 $data = implode('', $contents);
632 $dir = implode('', $ctrldir);
633 $buffer = $data.$dir."\x50\x4b\x05\x06\x00\x00\x00\x00".pack('v', count($ctrldir)).pack('v', count($ctrldir)).pack(
634 'V',
635 strlen($dir)
636 ).pack('V', strlen($data))."\x00\x00";
637
638 return acym_writeFile($name.'.zip', $buffer);
639 }
640
641 function acym_loaderLogo(bool $withContainer = true): void
642 {
643 if ($withContainer) {
644 echo '<div class="cell shrink acym_loader_logo">';
645 }
646 ?>
647 <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 1024 1024">
648 <path class="acym__svg__loader"
649 fill="#a6a9ab"
650 d="M553.074 174.168c-12.201 7.319-26.84 10.573-40.668 10.573s-27.655-3.253-40.668-10.573l-242.376-139.081-229.362 132.575v732.011l254.576 143.963v-430.26l219.602 124.442c11.388 6.507 24.401 9.76 37.415 9.76 0 0 0 0 0 0s0 0 0 0c12.201 0 25.214-3.253 36.6-9.76l221.23-124.442v430.26l254.576-144.775v-732.011l-229.362-131.762-241.563 139.081zM491.261 701.215l-217.164-122.815-61.001-34.161v430.26l-173.243-97.601v-662.063l422.94 245.629c0 0 0 0 0 0 8.947 4.881 17.895 8.135 27.655 10.573v230.178zM983.334 876.086l-173.243 98.416v-431.073l-61.001 34.161-217.164 122.815v-229.362c9.76-2.441 18.707-5.694 27.655-10.573l423.753-246.444v662.063zM539.246 425.493c-17.080 9.76-38.227 9.76-55.307 0 0 0 0 0 0 0l-422.94-245.629 168.362-97.601 222.043 127.696c18.707 10.573 39.853 16.267 61.001 16.267s42.294-5.694 61.001-16.267l222.043-127.696 168.362 97.601-424.566 245.629z"></path>
651 </svg>
652 <?php
653 if ($withContainer) {
654 echo '</div>';
655 }
656 }
657
658 function acym_fileNameValid(string $filename): bool
659 {
660 if (empty($filename)) return false;
661
662 if (strpos($filename, '..') !== false || strpos($filename, '/') !== false || strpos($filename, '\\') !== false) return false;
663
664 return true;
665 }
666
667 function acym_isPathWithinDirectory(string $baseDir, string $path): bool
668 {
669 $baseDir = rtrim($baseDir, DS).DS;
670
671 if (strpos($path, $baseDir) !== 0) return false;
672
673 return preg_match('#(^|[/\\\\])\.\.([/\\\\]|$)#', substr($path, strlen($baseDir))) !== 1;
674 }
675
676 function acym_getImageFileExtensions(bool $allowSvg = false): array
677 {
678 $imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'ico', 'bmp'];
679 if ($allowSvg) {
680 $imageFormats[] = 'svg';
681 }
682
683 return $imageFormats;
684 }
685
686 function acym_isSvgFileSafe(string $path): bool
687 {
688 if (!is_file($path)) {
689 return false;
690 }
691
692 $content = @file_get_contents($path);
693 if ($content === false || trim($content) === '') {
694 return false;
695 }
696
697 // Decode HTML entities so obfuscated payloads (e.g. "javascript&#58;") are caught, and drop NULL bytes.
698 $content = str_replace("\0", '', html_entity_decode($content, ENT_QUOTES | ENT_HTML5));
699
700 $dangerousPatterns = [
701 '#<\s*script#i',
702 '#<\s*(foreignObject|iframe|embed|object|audio|video|handler|listener)#i',
703 '#\son[a-z]+\s*=#i',
704 '#javascript\s*:#i',
705 '#data\s*:\s*text/html#i',
706 '#<!\s*(DOCTYPE|ENTITY)#i',
707 ];
708
709 foreach ($dangerousPatterns as $pattern) {
710 if (preg_match($pattern, $content)) {
711 return false;
712 }
713 }
714
715 return true;
716 }
717