PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Helpers/global/file.php +716 -0 10.1.0 → 11.1.0 View file →
@@ -1,0 +1,716 @@
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 +}