PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / V3.3.2
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder vV3.3.2
V3.3.2 3.3.1 V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 All 139 releases
← All changes | includes/Core/Util/FileHandler.php +1142 -81 1.4.18 → V3.3.2 View file →
@@ -1,100 +1,1161 @@
1 1 <?php
2 +
2 3 namespace BitCode\BitForm\Core\Util;
3 4
5 +use BitCode\BitForm\Admin\Form\Helpers;
6 +use BitCode\BitForm\Core\Form\FormManager;
7 +use BitCode\BitForm\enshrined\svgSanitize\Sanitizer;
8 +
9 +if (!defined('ABSPATH')) {
10 + exit;
11 +}
4 12 final class FileHandler
5 13 {
6 - public function rmrf($dir)
7 - {
8 - if (is_dir($dir)) {
9 - $objects = scandir($dir);
10 - foreach ($objects as $object) {
11 - if ($object != "." && $object != "..") {
12 - if (is_dir($dir. DIRECTORY_SEPARATOR .$object) && !is_link($dir.DIRECTORY_SEPARATOR.$object)) {
13 - $this->rmrf($dir. DIRECTORY_SEPARATOR .$object);
14 - } else {
15 - unlink($dir. DIRECTORY_SEPARATOR .$object);
16 - }
17 - }
14 + public function rmrf($dir)
15 + {
16 + $fs = self::initWpFilesystem();
17 + if ($fs && $fs->exists($dir)) {
18 + $fs->delete($dir, true);
19 + }
20 + }
21 +
22 + public function cpyr($source, $destination)
23 + {
24 + if (is_dir($source)) {
25 + wp_mkdir_p($destination);
26 + // chmod($destination, 0744);
27 + $objects = scandir($source);
28 + foreach ($objects as $object) {
29 + if ('.' !== $object && '..' !== $object) {
30 + if (is_dir($source . DIRECTORY_SEPARATOR . $object) && !is_link($source . DIRECTORY_SEPARATOR . $object)) {
31 + cpyr($source . DIRECTORY_SEPARATOR . $object, $destination . DIRECTORY_SEPARATOR . $object);
32 + } elseif (is_file($source . DIRECTORY_SEPARATOR . $object)) {
33 + copy($source . DIRECTORY_SEPARATOR . $object, $destination . DIRECTORY_SEPARATOR . $object);
34 + // chmod($destination. DIRECTORY_SEPARATOR .$object, 0644);
35 + } else {
36 + symlink($source . DIRECTORY_SEPARATOR . $object, $destination . DIRECTORY_SEPARATOR . $object);
37 + }
38 + }
39 + }
40 + } else {
41 + copy($source, $destination);
42 + }
43 + }
44 +
45 + public function moveUploadedFiles($file_details, $form_id, $entry_id)
46 + {
47 + require_once ABSPATH . 'wp-admin/includes/file.php';
48 +
49 + $file_upoalded = [];
50 + $_upload_dir = self::getEntriesFileUploadDir($form_id, $entry_id);
51 + $_upload_url = self::getEntriesFileUploadURL($form_id, $entry_id);
52 + $this::createIndexFile($_upload_dir);
53 +
54 + $upload_dir_filter = function ($uploads) use ($_upload_dir, $_upload_url) {
55 + $uploads['path'] = $_upload_dir;
56 + $uploads['url'] = $_upload_url;
57 + $uploads['subdir'] = '';
58 + $uploads['basedir'] = dirname($_upload_dir);
59 + $uploads['baseurl'] = dirname($_upload_url);
60 + return $uploads;
61 + };
62 +
63 + if (is_array($file_details['name'])) {
64 + foreach ($file_details['name'] as $key => $value) {
65 + if (!empty($value)) {
66 + $fileNameCount = 1;
67 + $file_upoalded[$key] = sanitize_file_name($value);
68 + while (file_exists($_upload_dir . DIRECTORY_SEPARATOR . $file_upoalded[$key])) {
69 + $fileNameWithSeparator = BITFORMS_BF_SEPARATOR . $fileNameCount;
70 + $file_upoalded[$key] = sanitize_file_name(preg_replace('/(.[a-z A-Z 0-9]+)$/', "{$fileNameWithSeparator}$1", $value));
71 + $fileNameCount = $fileNameCount + 1;
72 + if (11 === $fileNameCount) {
73 + break;
18 74 }
19 - rmdir($dir);
75 + }
76 + $file = [
77 + 'name' => $file_upoalded[$key],
78 + 'type' => $file_details['type'][$key] ?? '',
79 + 'tmp_name' => $file_details['tmp_name'][$key],
80 + 'error' => $file_details['error'][$key] ?? 0,
81 + 'size' => $file_details['size'][$key] ?? 0,
82 + ];
83 + add_filter('upload_dir', $upload_dir_filter);
84 + $upload_result = wp_handle_upload($file, ['test_form' => false]);
85 + remove_filter('upload_dir', $upload_dir_filter);
86 + if (isset($upload_result['error'])) {
87 + unset($file_upoalded[$key]);
88 + } else {
89 + $file_upoalded[$key] = basename($upload_result['file']);
90 + }
91 + }
92 + }
93 + } else {
94 + if (!empty($file_details['name'])) {
95 + $fileNameCount = 1;
96 + $file_upoalded[0] = sanitize_file_name($file_details['name']);
97 + while (file_exists($_upload_dir . DIRECTORY_SEPARATOR . $file_upoalded[0])) {
98 + $fileNameWithSeparator = BITFORMS_BF_SEPARATOR . $fileNameCount;
99 + $file_upoalded[0] = sanitize_file_name(preg_replace('/(.[a-z A-Z 0-9]+)$/', "{$fileNameWithSeparator}$1", $file_details['name']));
100 + $fileNameCount = $fileNameCount + 1;
101 + if (11 === $fileNameCount) {
102 + break;
103 + }
104 + }
105 + $file = [
106 + 'name' => $file_upoalded[0],
107 + 'type' => $file_details['type'] ?? '',
108 + 'tmp_name' => $file_details['tmp_name'],
109 + 'error' => $file_details['error'] ?? 0,
110 + 'size' => $file_details['size'] ?? 0,
111 + ];
112 + add_filter('upload_dir', $upload_dir_filter);
113 + $upload_result = wp_handle_upload($file, ['test_form' => false]);
114 + remove_filter('upload_dir', $upload_dir_filter);
115 + if (isset($upload_result['error'])) {
116 + unset($file_upoalded[0]);
20 117 } else {
21 - unlink($dir);
118 + $file_upoalded[0] = basename($upload_result['file']);
22 119 }
120 + }
23 121 }
122 + return $file_upoalded;
123 + }
24 124
25 - public function cpyr($source, $destination)
26 - {
27 - if (is_dir($source)) {
28 - mkdir($destination);
29 - // chmod($destination, 0744);
30 - $objects = scandir($source);
31 - foreach ($objects as $object) {
32 - if ($object != "." && $object != "..") {
33 - if (is_dir($source. DIRECTORY_SEPARATOR .$object) && !is_link($source.DIRECTORY_SEPARATOR.$object)) {
34 - cpyr($source. DIRECTORY_SEPARATOR .$object, $destination. DIRECTORY_SEPARATOR .$object);
35 - } elseif (is_file($source. DIRECTORY_SEPARATOR .$object)) {
36 - copy($source. DIRECTORY_SEPARATOR .$object, $destination. DIRECTORY_SEPARATOR .$object);
37 - // chmod($destination. DIRECTORY_SEPARATOR .$object, 0644);
38 - } else {
39 - symlink($source. DIRECTORY_SEPARATOR .$object, $destination. DIRECTORY_SEPARATOR .$object);
40 - }
41 - }
42 - }
43 - } else {
44 - copy($source, $destination);
125 + public static function isSafeFileName($name)
126 + {
127 + if (!is_string($name)) {
128 + return false;
129 + }
130 +
131 + $trimmed = trim($name);
132 + if ('' === $trimmed) {
133 + return false;
134 + }
135 +
136 + $baseName = basename($trimmed);
137 + if ('' === $baseName || $baseName !== $trimmed || 'index.php' === $baseName) {
138 + return false;
139 + }
140 +
141 + return sanitize_file_name($trimmed) === $trimmed;
142 + }
143 +
144 + public function deleteFiles($form_id, $entry_id, $files)
145 + {
146 + $_upload_dir = self::getEntriesFileUploadDir($form_id, $entry_id);
147 + $resolvedBitformsUploadDir = realpath(BITFORMS_UPLOAD_DIR);
148 + $resolvedUploadDir = realpath($_upload_dir);
149 + if (false === $resolvedBitformsUploadDir || false === $resolvedUploadDir) {
150 + return;
151 + }
152 +
153 + $bitformsUploadDirPrefix = trailingslashit(wp_normalize_path($resolvedBitformsUploadDir));
154 + $uploadDirPrefix = trailingslashit(wp_normalize_path($resolvedUploadDir));
155 + if (0 !== strpos($uploadDirPrefix, $bitformsUploadDirPrefix)) {
156 + return;
157 + }
158 +
159 + foreach ((array) $files as $name) {
160 + if (!self::isSafeFileName($name)) {
161 + continue;
162 + }
163 +
164 + $candidatePath = $resolvedUploadDir . DIRECTORY_SEPARATOR . $name;
165 + $resolvedPath = realpath($candidatePath);
166 + if (false === $resolvedPath || !is_file($resolvedPath)) {
167 + continue;
168 + }
169 +
170 + $normalizedPath = wp_normalize_path($resolvedPath);
171 + if (0 !== strpos($normalizedPath, $uploadDirPrefix)) {
172 + continue;
173 + }
174 +
175 + wp_delete_file($resolvedPath);
176 + }
177 + }
178 +
179 + public static function getFileUploadError($code)
180 + {
181 + $errors = [
182 + 0 => __('Unknown upload error', 'bit-form'),
183 + 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini.', 'bit-form'),
184 + 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.', 'bit-form'),
185 + 3 => __('The uploaded file was only partially uploaded.', 'bit-form'),
186 + 4 => __('No file was uploaded.', 'bit-form'),
187 + 6 => __('Missing a temporary folder.', 'bit-form'),
188 + 7 => __('Failed to write file to disk.', 'bit-form'),
189 + 8 => __('A PHP extension stopped the file upload.', 'bit-form'),
190 + ];
191 + return $errors[$code];
192 + }
193 +
194 + public static function fileCopy($tmpdir, $destinationDir, $file)
195 + {
196 + $tmpBase = realpath($tmpdir);
197 + $destBase = realpath($destinationDir);
198 + if (false === $tmpBase || false === $destBase) {
199 + Log::debug_log([
200 + 'message' => 'FileHandler::fileCopy base path invalid',
201 + 'tmpdir' => $tmpdir,
202 + 'destinationDir' => $destinationDir,
203 + ]);
204 + return;
205 + }
206 +
207 + $safeFile = is_string($file) ? trim($file) : '';
208 + if ('' === $safeFile) {
209 + return;
210 + }
211 +
212 + $candidate = $tmpBase . DIRECTORY_SEPARATOR . $safeFile;
213 + $resolved = realpath($candidate);
214 + if (false === $resolved || 0 !== strpos($resolved, $tmpBase . DIRECTORY_SEPARATOR)) {
215 + Log::debug_log([
216 + 'message' => 'FileHandler::fileCopy blocked path traversal',
217 + 'file' => $file,
218 + 'resolved' => $resolved,
219 + 'tmpBase' => $tmpBase,
220 + ]);
221 + return;
222 + }
223 + if (!is_readable($resolved)) {
224 + return;
225 + }
226 +
227 + $newFile = $destBase . DIRECTORY_SEPARATOR . basename($resolved);
228 + copy($resolved, $newFile);
229 + }
230 +
231 + public static function tempDirToUploadDir($submitted_data, $fields, $formId, $entryID)
232 + {
233 + $upload_dir = wp_upload_dir();
234 + $tempDir = $upload_dir['basedir'] . '/bitforms/temp';
235 + $destinationDir = self::getEntriesFileUploadDir($formId, $entryID) . DIRECTORY_SEPARATOR;
236 + self::createIndexFile($destinationDir);
237 +
238 + $consumedFiles = [];
239 +
240 + foreach ($submitted_data as $key => $data) {
241 + if (isset($fields[$key]) && 'advanced-file-up' === $fields[$key]['type']) {
242 + $fldData = $submitted_data[$key];
243 + // A repeater row (or a pre-split value) hands this field over as an array; explode()
244 + // on an array is a TypeError on PHP 8, which would fatal mid-submission.
245 + $files = \is_array($fldData) ? $fldData : explode(',', \is_scalar($fldData) ? (string) $fldData : '');
246 + foreach ($files as $file) {
247 + $safeFile = \is_scalar($file) ? trim((string) $file) : '';
248 + if ('' === $safeFile) {
249 + continue;
250 + }
251 + self::fileCopy($tempDir, $destinationDir, $safeFile);
252 + $consumedFiles[] = $safeFile;
45 253 }
254 + if (!empty($files)) {
255 + $submitted_data[$key] = $files;
256 + }
257 + }
46 258 }
47 259
48 - public function moveUploadedFiles($file_details, $form_id, $entry_id)
49 - {
50 - $file_upoalded = array();
51 - $_upload_dir = BITFORMS_UPLOAD_DIR.DIRECTORY_SEPARATOR.$form_id.DIRECTORY_SEPARATOR.$entry_id;
52 - wp_mkdir_p($_upload_dir);
53 - if (is_array($file_details['name'])) {
54 - foreach ($file_details['name'] as $key => $value) {
55 - //check accepted filetype in_array($file_details['name'][$key], $supported_files) else \
56 - if (!empty($value)) {
57 - $fileNameCount = 1;
58 - // $file_upoalded[$key] = time()."_$value";
59 - $file_upoalded[$key] = sanitize_file_name($value);
60 - while (file_exists($_upload_dir.DIRECTORY_SEPARATOR.$file_upoalded[$key])) {
61 - $file_upoalded[$key] = sanitize_file_name(preg_replace("/(.[a-z A-Z 0-9]+)$/", "__{$fileNameCount}$1", $value));
62 - $fileNameCount = $fileNameCount + 1;
63 - if ($fileNameCount === 11) {
64 - break;
65 - }
66 - }
67 - $move_status = \move_uploaded_file($file_details['tmp_name'][$key], $_upload_dir.DIRECTORY_SEPARATOR.$file_upoalded[$key]);
68 - if (!$move_status) {
69 - unset($file_upoalded[$key]);
70 - }
71 - }
72 - }
73 - } else {
74 - if (!empty($file_details['name'])) {
75 - $fileNameCount = 1;
76 - $file_upoalded[0] = sanitize_file_name($file_details['name']);
77 - while (file_exists($_upload_dir.DIRECTORY_SEPARATOR.$file_upoalded[0])) {
78 - $file_upoalded[0] = sanitize_file_name(preg_replace("/(.[a-z A-Z 0-9]+)$/", "__{$fileNameCount}$1", $file_details['name']));
79 - $fileNameCount = $fileNameCount + 1;
80 - if ($fileNameCount === 11) {
81 - break;
82 - }
83 - }
84 - $move_status = \move_uploaded_file($file_details['tmp_name'], $_upload_dir.DIRECTORY_SEPARATOR.$file_upoalded[0]);
85 - if (!$move_status) {
86 - unset($file_upoalded[0]);
87 - }
88 - }
260 + self::cleanupTempUploads($tempDir, $consumedFiles);
261 +
262 + return $submitted_data;
263 + }
264 +
265 + /**
266 + * Clear the shared temp upload staging area after a submission has taken what it needs.
267 + *
268 + * @param string $tempDir shared staging directory
269 + * @param array $consumedFiles file names copied into the entry directory by this submission
270 + *
271 + * @return void
272 + */
273 + private static function cleanupTempUploads($tempDir, array $consumedFiles)
274 + {
275 + $tempBase = realpath($tempDir);
276 + if (false === $tempBase) {
277 + return;
278 + }
279 + $boundary = $tempBase . DIRECTORY_SEPARATOR;
280 +
281 + // Never remove the directory-hardening files the uploads dir relies on.
282 + $protected = ['index.php', 'index.html', '.htaccess'];
283 +
284 + foreach ($consumedFiles as $file) {
285 + $resolved = realpath($boundary . $file);
286 + if (false === $resolved || 0 !== strpos($resolved, $boundary) || !is_file($resolved)) {
287 + continue;
288 + }
289 + if (\in_array(basename($resolved), $protected, true)) {
290 + continue;
291 + }
292 + wp_delete_file($resolved);
293 + }
294 +
295 + // Sweep abandoned uploads. Anything still here after the retention window belongs to a form
296 + // that was never submitted. Filterable so a site with very long multi-step forms can extend
297 + // it; 0 or less disables the sweep entirely.
298 + $retention = apply_filters('bitform_temp_upload_retention', DAY_IN_SECONDS);
299 + $retention = is_numeric($retention) ? (int) $retention : DAY_IN_SECONDS;
300 + if ($retention <= 0) {
301 + return;
302 + }
303 +
304 + $cutoff = time() - $retention;
305 + $tmpFiles = glob($boundary . '*');
306 + foreach ((array) $tmpFiles as $tmpFile) {
307 + $resolved = realpath($tmpFile);
308 + if (false === $resolved || 0 !== strpos($resolved, $boundary) || !is_file($resolved)) {
309 + continue;
310 + }
311 + if (\in_array(basename($resolved), $protected, true)) {
312 + continue;
313 + }
314 + $modified = @filemtime($resolved);
315 + if (false !== $modified && $modified < $cutoff) {
316 + wp_delete_file($resolved);
317 + }
318 + }
319 + }
320 +
321 + private function getByteSizeByUnit($sizeString, $base = 1024)
322 + {
323 + // split 2MB into 2 and MB
324 + $size = (float) preg_replace('/[^0-9\.]/', '', (string) $sizeString);
325 + $unit = preg_replace('/[^a-zA-Z]/', '', (string) $sizeString);
326 + $unit = strtolower($unit);
327 + if ('kb' === $unit) {
328 + return $size * $base;
329 + } elseif ('mb' === $unit) {
330 + return $size * $base * $base;
331 + } elseif ('gb' === $unit) {
332 + return $size * $base * $base * $base;
333 + } else {
334 + return $size;
335 + }
336 + }
337 +
338 + /**
339 + * How many files an Advanced File Upload field takes per submission, mirroring FilePond: a
340 + * single-file field holds one, and maxFiles only applies once multiple files are allowed.
341 + *
342 + * @param object|null $config field config
343 + *
344 + * @return int|null null when there is no limit
345 + */
346 + public static function getMaxFilesLimit($config)
347 + {
348 + if (!is_object($config)) {
349 + return null;
350 + }
351 + if (empty($config->allowMultiple)) {
352 + return 1;
353 + }
354 + $max = isset($config->maxFiles) && is_numeric($config->maxFiles) ? (int) $config->maxFiles : 0;
355 +
356 + return $max > 0 ? $max : null;
357 + }
358 +
359 + /**
360 + * Files submitted for one field: names already uploaded on select (comma list or array) plus
361 + * the files sent with the form.
362 + *
363 + * @param mixed $uploadedNames posted value of the field
364 + * @param mixed $postedFileNames $_FILES[field]['name']
365 + *
366 + * @return int
367 + */
368 + public static function countSubmittedFiles($uploadedNames, $postedFileNames)
369 + {
370 + $count = 0;
371 + foreach ([$uploadedNames, $postedFileNames] as $source) {
372 + $names = is_array($source) ? $source : (is_scalar($source) ? explode(',', (string) $source) : []);
373 + foreach ($names as $name) {
374 + if (is_scalar($name) && '' !== trim((string) $name)) {
375 + $count++;
89 376 }
90 - return $file_upoalded;
377 + }
91 378 }
92 379
93 - public function deleteFiles($form_id, $entry_id, $files)
94 - {
95 - $_upload_dir = BITFORMS_UPLOAD_DIR.DIRECTORY_SEPARATOR.$form_id.DIRECTORY_SEPARATOR.$entry_id;
96 - foreach ($files as $name) {
97 - unlink($_upload_dir.DIRECTORY_SEPARATOR.$name);
380 + return $count;
381 + }
382 +
383 + /**
384 + * The browser drops files past the limit, so more can only arrive from a crafted request.
385 + *
386 + * @param object|null $config field config
387 + * @param mixed $uploadedNames posted value of the field
388 + * @param mixed $postedFileNames $_FILES[field]['name']
389 + *
390 + * @return string error message, '' when within the limit
391 + */
392 + public static function getMaxFilesError($config, $uploadedNames, $postedFileNames)
393 + {
394 + $limit = self::getMaxFilesLimit($config);
395 + if (null === $limit || self::countSubmittedFiles($uploadedNames, $postedFileNames) <= $limit) {
396 + return '';
397 + }
398 +
399 + return sprintf(
400 + /* translators: %d: maximum number of files */
401 + _n('You can upload only %d file.', 'You can upload up to %d files.', $limit, 'bit-form'),
402 + $limit
403 + );
404 + }
405 +
406 + /**
407 + * getMaxFilesError() for a field of a stored form.
408 + *
409 + * @param int|string $formId
410 + * @param string $fieldKey
411 + * @param mixed $uploadedNames posted value of the field
412 + * @param mixed $postedFileNames $_FILES[field]['name']
413 + *
414 + * @return string
415 + */
416 + public static function getMaxFilesErrorForField($formId, $fieldKey, $uploadedNames, $postedFileNames)
417 + {
418 + $formContent = FormManager::getInstance($formId)->getFormContent();
419 + $field = is_object($formContent) && isset($formContent->fields->{$fieldKey}) ? $formContent->fields->{$fieldKey} : null;
420 + if (!is_object($field) || !isset($field->typ) || 'advanced-file-up' !== $field->typ) {
421 + return '';
422 + }
423 +
424 + return self::getMaxFilesError(isset($field->config) ? $field->config : null, $uploadedNames, $postedFileNames);
425 + }
426 +
427 + /**
428 + * Whether a MIME matches a `type/*` entry of the allow list, as FilePond accepts it. Markup and
429 + * script types never match a wildcard: they must be listed explicitly.
430 + *
431 + * @param mixed $mime detected MIME, lowercase
432 + * @param array $allowMimes allow-listed MIME entries, lowercase
433 + *
434 + * @return bool
435 + */
436 + public static function matchesWildcardMime($mime, $allowMimes)
437 + {
438 + $neverByWildcard = [
439 + 'text/html',
440 + 'application/xhtml+xml',
441 + 'text/javascript',
442 + 'application/javascript',
443 + 'application/x-javascript',
444 + 'application/ecmascript',
445 + 'text/ecmascript',
446 + ];
447 + if (!is_string($mime) || in_array($mime, $neverByWildcard, true)) {
448 + return false;
449 + }
450 + $slash = strpos($mime, '/');
451 + if (false === $slash || 0 === $slash) {
452 + return false;
453 + }
454 +
455 + return in_array(substr($mime, 0, $slash) . '/*', (array) $allowMimes, true);
456 + }
457 +
458 + public function validation($field_key, $file_details, $form_id)
459 + {
460 + if (!function_exists('wp_check_filetype_and_ext')) {
461 + require_once ABSPATH . 'wp-admin/includes/file.php';
462 + }
463 +
464 + $formManager = FormManager::getInstance($form_id);
465 + $form_contents = $formManager->getFormContent();
466 + $field_content_details = $form_contents->fields;
467 + $fieldDetail = $field_content_details->{$field_key};
468 + $fieldType = $fieldDetail->typ;
469 + $maxSizeDetails = [];
470 + $allowFileTypes = [];
471 + $maxSize = null;
472 + $minSize = null;
473 + $fieldConfig = isset($fieldDetail->config) && is_object($fieldDetail->config) ? $fieldDetail->config : null;
474 + if ('file-up' === $fieldType) {
475 + $allowFileTypes = !empty($fieldDetail->config->allowedFileType) ? $fieldDetail->config->allowedFileType : [];
476 + if (!empty($fieldDetail->config->allowMaxSize)) {
477 + if (!empty($fieldDetail->config->maxSize)) {
478 + $maxSizeDetails['maxSize'] = $fieldDetail->config->maxSize . $fieldDetail->config->sizeUnit;
98 479 }
480 + if (!empty($fieldDetail->config->isItTotalMax)) {
481 + $maxSizeDetails['maxTotalFileSize'] = $fieldDetail->config->maxSize . $fieldDetail->config->sizeUnit;
482 + }
483 + }
484 + if (!empty($allowFileTypes)) {
485 + $allowFileTypes = explode(',', $allowFileTypes);
486 + }
487 + } elseif ('advanced-file-up' === $fieldType) {
488 + $allowFileTypes = !empty($fieldDetail->config->allowFileTypeValidation) ? $fieldDetail->config->acceptedFileTypes : [];
489 + if (!empty($fieldDetail->config->allowFileSizeValidation)) {
490 + if (!empty($fieldDetail->config->maxFileSize)) {
491 + $maxSizeDetails['maxSize'] = $fieldDetail->config->maxFileSize;
492 + }
493 + if (!empty($fieldDetail->config->maxTotalFileSize)) {
494 + $maxSizeDetails['maxTotalFileSize'] = $fieldDetail->config->maxTotalFileSize;
495 + }
496 + // A client transform re-encodes images, so the uploaded copy can legitimately come out
497 + // smaller than the original the browser checked.
498 + if (!empty($fieldDetail->config->minFileSize) && empty($fieldDetail->config->allowImageTransform)) {
499 + // FilePond counts 1KB as 1000 bytes; a 1024 base would reject files the browser accepted.
500 + $minSize = $this->getByteSizeByUnit($fieldDetail->config->minFileSize, 1000);
501 + }
502 + }
99 503 }
504 + if (!empty($maxSizeDetails['maxSize'])) {
505 + $maxSize = $this->getByteSizeByUnit($maxSizeDetails['maxSize']);
506 + }
507 + $maxTotalFileSize = null;
508 + if (!empty($maxSizeDetails['maxTotalFileSize'])) {
509 + $maxTotalFileSize = $this->getByteSizeByUnit($maxSizeDetails['maxTotalFileSize']);
510 + }
511 +
512 + if ($formManager->isRepeatedField($field_key)) {
513 + foreach ($file_details['name'] as $rowIndex => $file) {
514 + if (!empty($file)) {
515 + $fileDetails = [
516 + 'name' => $file,
517 + 'type' => $file_details['type'][$rowIndex],
518 + 'tmp_name' => $file_details['tmp_name'][$rowIndex],
519 + 'error' => $file_details['error'][$rowIndex],
520 + 'size' => $file_details['size'][$rowIndex],
521 + ];
522 + $countState = $this->maxFilesState($fieldType, $fieldConfig, $file);
523 + if (!empty($countState)) {
524 + return $countState;
525 + }
526 + $validateState = $this->validateFileInfo($fieldType, $fileDetails, $allowFileTypes, $maxSize, $maxTotalFileSize, $minSize);
527 + if (!empty($validateState) && !empty($validateState['message'])) {
528 + return $validateState;
529 + }
530 + }
531 + }
532 + } else {
533 + $countState = $this->maxFilesState($fieldType, $fieldConfig, isset($file_details['name']) ? $file_details['name'] : null);
534 + if (!empty($countState)) {
535 + return $countState;
536 + }
537 + return $this->validateFileInfo($fieldType, $file_details, $allowFileTypes, $maxSize, $maxTotalFileSize, $minSize);
538 + }
539 + return [];
540 + }
541 +
542 + /**
543 + * @param string $fieldType
544 + * @param object|null $fieldConfig
545 + * @param mixed $postedFileNames $_FILES[field]['name'] (or one repeater row of it)
546 + *
547 + * @return array validation state, empty when within the limit
548 + */
549 + private function maxFilesState($fieldType, $fieldConfig, $postedFileNames)
550 + {
551 + if ('advanced-file-up' !== $fieldType) {
552 + return [];
553 + }
554 + $message = self::getMaxFilesError($fieldConfig, null, $postedFileNames);
555 +
556 + return '' === $message ? [] : ['message' => $message, 'error_type' => 'file_count_error'];
557 + }
558 +
559 + private function validateFileInfo($fieldType, $file_details, $allowFileTypes, $maxSize, $maxTotalFileSize, $minSize = null)
560 + {
561 + $errorMessage = [
562 + 'message' => '',
563 + 'error_type' => '',
564 + ];
565 + if (is_array($file_details['name'])) {
566 + $totalSize = 0;
567 + foreach ($file_details['name'] as $key => $file) {
568 + if (!empty($file)) {
569 + $fileInfo = [
570 + 'name' => $file,
571 + 'type' => $file_details['type'][$key],
572 + 'tmp_name' => $file_details['tmp_name'][$key],
573 + 'error' => $file_details['error'][$key],
574 + 'size' => $file_details['size'][$key],
575 + ];
576 + $totalSize += $fileInfo['size'];
577 + $validateState = $this->validateSingleFile($fieldType, $fileInfo, $allowFileTypes, $maxSize, $minSize);
578 + if (!empty($validateState)) {
579 + return $validateState;
580 + }
581 + }
582 + }
583 + if (isset($maxTotalFileSize) && !is_null($maxTotalFileSize) && $totalSize > $maxTotalFileSize) {
584 + $errorMessage['message'] = __('Total File size is too large', 'bit-form');
585 + $errorMessage['error_type'] = 'file_size_error';
586 + return $errorMessage;
587 + }
588 + } else {
589 + $validateState = $this->validateSingleFile($fieldType, $file_details, $allowFileTypes, $maxSize, $minSize);
590 + if (!empty($validateState)) {
591 + return $validateState;
592 + }
593 + }
594 +
595 + return $errorMessage;
596 + }
597 +
598 + private function validateSingleFile($fieldType, &$file, $allowTypes, $maxSize = null, $minSize = null)
599 + {
600 + // 0) Basic sanity & transport integrity
601 + if (!is_array($file) || empty($file['tmp_name'])) {
602 + // return ['message' => __('No file uploaded.', 'bit-form'), 'error_type' => 'file_missing'];
603 + return null;
604 + }
605 + if (!isset($file['error']) || UPLOAD_ERR_OK !== (int)$file['error']) {
606 + return ['message' => __('Upload failed', 'bit-form'), 'error_type' => 'file_upload_error'];
607 + }
608 + if (!is_uploaded_file($file['tmp_name'])) {
609 + return ['message' => __('Untrusted upload source', 'bit-form'), 'error_type' => 'file_upload_error'];
610 + }
611 + if (!is_file($file['tmp_name']) || !is_readable($file['tmp_name'])) {
612 + return ['message' => __('Temporary file not accessible', 'bit-form'), 'error_type' => 'file_upload_error'];
613 + }
614 +
615 + $fileName = sanitize_file_name((string)($file['name'] ?? ''));
616 + if ('' === $fileName) {
617 + return ['message' => __('Empty filename', 'bit-form'), 'error_type' => 'file_type_error'];
618 + }
619 +
620 + // 1) Enforce max size (header + actual)
621 + $onDiskSize = @filesize($file['tmp_name']);
622 + if (false === $onDiskSize) {
623 + return ['message' => __('Cannot read file size', 'bit-form'), 'error_type' => 'file_upload_error'];
624 + }
625 + if (!empty($maxSize) && $onDiskSize > $maxSize) {
626 + return ['message' => __('File size is too large', 'bit-form'), 'error_type' => 'file_size_error'];
627 + }
628 + if (!empty($minSize) && $onDiskSize < $minSize) {
629 + return ['message' => __('File size is too small', 'bit-form'), 'error_type' => 'file_size_error'];
630 + }
631 +
632 + // 2) Determine ext + MIME using WP + finfo
633 + $wpCheck = wp_check_filetype_and_ext($file['tmp_name'], $fileName); // ['ext'=>'jpg','type'=>'image/jpeg']
634 + $fileExtension = strtolower((string)(empty($wpCheck['ext']) ? pathinfo($fileName, PATHINFO_EXTENSION) : $wpCheck['ext']));
635 + $wpExt = strtolower((string)(empty($wpCheck['ext']) ? $fileExtension : $wpCheck['ext']));
636 + $wpType = strtolower((string)($wpCheck['type'] ?? ''));
637 + $fi = function_exists('finfo_open') ? @finfo_open(FILEINFO_MIME_TYPE) : false;
638 + $detectedMime = $fi ? @finfo_file($fi, $file['tmp_name']) : false;
639 + if ($fi) {
640 + @finfo_close($fi);
641 + }
642 + if (!$detectedMime && function_exists('mime_content_type')) {
643 + $detectedMime = @mime_content_type($file['tmp_name']);
644 + }
645 + if (!$detectedMime) {
646 + $detectedMime = '' !== $wpType ? $wpType : 'application/octet-stream';
647 + }
648 + $detectedMime = strtolower(trim($detectedMime));
649 +
650 + // Hard-block risky types regardless
651 + // 3) Block obvious executable types regardless of allow list
652 + $disallowedMimes = apply_filters('bitform_filter_upload_disallowed_mimes', [
653 + 'application/x-php',
654 + 'text/x-php',
655 + 'application/x-msdownload',
656 + 'application/x-msdos-program',
657 + 'application/x-sh',
658 + 'application/x-csh',
659 + 'text/x-shellscript',
660 + 'application/java-archive'
661 + ]);
662 + $denyExt = apply_filters('bitform_filter_upload_denied_extensions', ['php', 'phtml', 'phar', 'htaccess', 'html', 'js', 'exe', 'sh', 'bat', 'cmd']);
663 + if (in_array($detectedMime, $disallowedMimes, true) || in_array($fileExtension, $denyExt, true)) {
664 + return ['message' => __('This file type is not allowed', 'bit-form'), 'error_type' => 'file_type_error'];
665 + }
666 +
667 + // 4) Normalize allowlist: support both extensions (.jpg or jpg) and MIME types
668 + // --- ALLOWLIST NORMALIZATION (extensions + MIME) ---
669 + $normalizedAllow = array_values(array_unique(array_map(static function ($t) {
670 + return strtolower(trim((string)$t));
671 + }, (array)$allowTypes)));
672 +
673 + $allowExts = [];
674 + $allowMimes = [];
675 + foreach ($normalizedAllow as $t) {
676 + if ('' === $t) {
677 + continue;
678 + }
679 + if (false !== strpos($t, '/')) {
680 + // looks like a MIME
681 + $allowMimes[] = $t;
682 + } else {
683 + // extension: may include leading dot; normalize without dot
684 + $allowExts[] = ltrim($t, '.');
685 + }
686 + }
687 +
688 + $allowExts = array_values(array_unique($allowExts));
689 + $allowMimes = array_values(array_unique($allowMimes));
690 +
691 + $hasAllowlist = (!empty($allowExts) || !empty($allowMimes));
692 + $extMatch = in_array($fileExtension, $allowExts, true);
693 + $mimeMatch = in_array($detectedMime, $allowMimes, true) || self::matchesWildcardMime($detectedMime, $allowMimes);
694 +
695 + // 5) Require BOTH a legit WP mapping AND a match to the allowlist
696 + $wpOk = ('' !== $wpExt && '' !== $wpType);
697 +
698 + if ($hasAllowlist) {
699 + if (!($extMatch || $mimeMatch)) {
700 + return ['message' => __('File type is not allowed', 'bit-form'), 'error_type' => 'file_type_error'];
701 + }
702 + } else {
703 + if (!$wpOk) {
704 + return ['message' => __('File type is not allowed', 'bit-form'), 'error_type' => 'file_type_error'];
705 + }
706 + }
707 +
708 + // 5) Special SVG handling (by MIME, not just extension)
709 + if ('svg' === $fileExtension || 'image/svg+xml' === $detectedMime || 'image/svg+xml' === $wpType) {
710 + if ('image/svg+xml' !== $detectedMime) {
711 + return ['message' => __('Invalid SVG', 'bit-form'), 'error_type' => 'file_type_error'];
712 + }
713 +
714 + $dirty = file_get_contents($file['tmp_name']);
715 + $svg_sanitizer = new Sanitizer();
716 + $clean = $svg_sanitizer->sanitize($dirty);
717 + if (false === $clean) {
718 + return ['message' => __('SVG file is not valid', 'bit-form'), 'error_type' => 'file_type_error'];
719 + }
720 + file_put_contents($file['tmp_name'], $clean, LOCK_EX);
721 + // Re-check size post-sanitize
722 + if (!empty($maxSize) && filesize($file['tmp_name']) > $maxSize) {
723 + return ['message' => __('File size is too large after sanitation', 'bit-form'), 'error_type' => 'file_size_error'];
724 + }
725 + }
726 +
727 + if ('pdf' === $fileExtension || 'application/pdf' === $detectedMime || 'application/pdf' === $wpType) {
728 + $blockedList = [
729 + '/\/JS\b/',
730 + '/\/JavaScript\b/',
731 + '/eval\(/i',
732 + '/app\.alert/i',
733 + '/console\.log\b/i',
734 + '/document\.write\b/i',
735 +
736 + '/\/GoToR\b/i',
737 + '/\/Launch\b/i',
738 +
739 + '/\/EmbeddedFile\b/i',
740 + '/\/EmbeddedFiles\b/i',
741 + '/\/Filespec\b/i',
742 + '/\/FileAttachment\b/i',
743 +
744 + '/\/SubmitForm\b/i',
745 + '/\/ResetForm\b/i',
746 + '/\/ImportData\b/i',
747 +
748 + '/\/RichMedia\b/i',
749 + ];
750 +
751 + $pdfContent = file_get_contents($file['tmp_name']);
752 + foreach ($blockedList as $blockedKeyWrd) {
753 + if (preg_match($blockedKeyWrd, $pdfContent)) {
754 + return ['message' => __('Invalid file', 'bit-form'), 'error_type' => 'file_type_error'];
755 + }
756 + }
757 + }
758 +
759 + // --- Allow developer to make a final decision override (optional) ---
760 + $final = apply_filters('bitform_filter_upload_allow_file', true, [
761 + 'file_name' => $fileName,
762 + 'extension' => $fileExtension,
763 + 'detected_mime' => $detectedMime,
764 + 'wp_ext' => $wpExt,
765 + 'wp_type' => $wpType,
766 + 'allow_exts' => $allowExts,
767 + 'allow_mimes' => $allowMimes,
768 + 'has_allowlist' => $hasAllowlist,
769 + ]);
770 + if (true !== $final) {
771 + // If a dev returns a WP_Error, you could extract message/type; here we just block.
772 + return ['message' => __('File not allowed by policy', 'bit-form'), 'error_type' => 'file_policy_block'];
773 + }
774 + return null; // success
775 + }
776 +
777 + public static function deleteIsFileExists($path)
778 + {
779 + if (file_exists($path)) {
780 + wp_delete_file($path);
781 + }
782 + }
783 +
784 + public static function getEntriesFileUploadDir($form_id, $entry_id)
785 + {
786 + $uploadDir = rtrim(BITFORMS_UPLOAD_DIR, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $form_id . DIRECTORY_SEPARATOR;
787 + $encrypted_directoryId = Helpers::getEncryptedEntryId($entry_id);
788 + $encryptedDirectory = $uploadDir . $encrypted_directoryId;
789 + if (is_dir($encryptedDirectory)) {
790 + return $encryptedDirectory;
791 + }
792 + $oldEntriesFileUploadDir = Helpers::getOldEntriesFileUploadDir($uploadDir, $entry_id);
793 + if (!empty($oldEntriesFileUploadDir) && is_dir($oldEntriesFileUploadDir)) {
794 + return $oldEntriesFileUploadDir;
795 + }
796 + return $encryptedDirectory;
797 + }
798 +
799 + private static function replaceDocumentRoot($path)
800 + {
801 + $uploadDir = wp_upload_dir();
802 + $basedir = wp_normalize_path(rtrim($uploadDir['basedir'], '/\\'));
803 + $baseurl = rtrim($uploadDir['baseurl'], '/');
804 + $normalPath = wp_normalize_path($path);
805 +
806 + if (0 === strpos($normalPath, $basedir . '/') || $normalPath === $basedir) {
807 + return $baseurl . substr($normalPath, strlen($basedir));
808 + }
809 +
810 + // Fallback: path outside uploads dir — strip ABSPATH and prepend home URL.
811 + return home_url(ltrim(str_replace(wp_normalize_path(ABSPATH), '', $normalPath), '/'));
812 + }
813 +
814 + public static function getEntriesFileUploadURL($form_id, $entry_id)
815 + {
816 + $documentRoot = self::getEntriesFileUploadDir($form_id, $entry_id);
817 + $url = self::replaceDocumentRoot($documentRoot);
818 + return $url;
819 + }
820 +
821 + public static function createIndexFile($directory)
822 + {
823 + if (wp_mkdir_p($directory)) {
824 + $indexFilePath = rtrim($directory, '/') . '/index.php';
825 + if (!file_exists($indexFilePath)) {
826 + try {
827 + if (false === self::writeFile($indexFilePath, "<?php\n// No direct access allowed.")) {
828 + throw new \Exception("Failed to create index.php in $directory");
829 + }
830 + } catch (\Exception $e) {
831 + Log::debug_log('File creation Failed:' . $e->getMessage()); // Log the error for debugging
832 + }
833 + }
834 + }
835 + return false;
836 + }
837 +
838 + /**
839 + * Initialise and return the WP_Filesystem abstraction layer.
840 + *
841 + * @return WP_Filesystem_Base|false
842 + */
843 + private static function initWpFilesystem()
844 + {
845 + global $wp_filesystem;
846 + if (empty($wp_filesystem)) {
847 + require_once ABSPATH . 'wp-admin/includes/file.php';
848 + WP_Filesystem();
849 + }
850 + return $wp_filesystem;
851 + }
852 +
853 + /**
854 + * Write (overwrite) content to a file using WP_Filesystem.
855 + *
856 + * @param string $filePath Absolute path to the file.
857 + * @param string $content Content to write.
858 + * @return bool True on success, false on failure.
859 + */
860 + public static function writeFile($filePath, $content)
861 + {
862 + $fs = self::initWpFilesystem();
863 + if ($fs) {
864 + return $fs->put_contents($filePath, $content, FS_CHMOD_FILE);
865 + }
866 + // Fallback: file_put_contents is acceptable when WP_Filesystem is unavailable.
867 + return false !== file_put_contents($filePath, $content);
868 + }
869 +
870 + /**
871 + * Append content to a file using WP_Filesystem.
872 + * WP_Filesystem has no native append; we read + concatenate + write.
873 + *
874 + * @param string $filePath Absolute path to the file.
875 + * @param string $content Content to append.
876 + * @return bool True on success, false on failure.
877 + */
878 + public static function appendFile($filePath, $content)
879 + {
880 + $fs = self::initWpFilesystem();
881 + if ($fs) {
882 + $existing = $fs->exists($filePath) ? (string) $fs->get_contents($filePath) : '';
883 + return $fs->put_contents($filePath, $existing . $content, FS_CHMOD_FILE);
884 + }
885 + // Fallback: file_put_contents is acceptable when WP_Filesystem is unavailable.
886 + return false !== file_put_contents($filePath, $content, FILE_APPEND | LOCK_EX);
887 + }
888 +
889 + /**
890 + * Read and return the full content of a file using WP_Filesystem.
891 + *
892 + * @param string $filePath Absolute path to the file.
893 + * @return string File contents, or empty string if unreadable.
894 + */
895 + public static function readFile($filePath)
896 + {
897 + $fs = self::initWpFilesystem();
898 + if ($fs && $fs->exists($filePath)) {
899 + $content = $fs->get_contents($filePath);
900 + return false !== $content ? $content : '';
901 + }
902 + // Fallback.
903 + if (file_exists($filePath)) {
904 + $content = file_get_contents($filePath);
905 + return false !== $content ? $content : '';
906 + }
907 + return '';
908 + }
909 +
910 + public static function processRepeaterAttachment($repeaterKey, $fileKey, $fieldValue, $basePath, &$attachments)
911 + {
912 + if (!isset($fieldValue[$repeaterKey]) || !is_array($fieldValue[$repeaterKey])) {
913 + return;
914 + }
915 +
916 + foreach ($fieldValue[$repeaterKey] as $repeaterRow) {
917 + if (!isset($repeaterRow[$fileKey]) || empty($repeaterRow[$fileKey])) {
918 + continue;
919 + }
920 +
921 + $fileValue = $repeaterRow[$fileKey];
922 + self::addAttachmentFiles($fileValue, $basePath, $attachments);
923 + }
924 + }
925 +
926 + public static function processRegularAttachment($fileKey, $fieldValue, $basePath, &$attachments)
927 + {
928 + if (!isset($fieldValue[$fileKey]) || empty($fieldValue[$fileKey])) {
929 + return;
930 + }
931 +
932 + $fileValue = $fieldValue[$fileKey];
933 + self::addAttachmentFiles($fileValue, $basePath, $attachments);
934 + }
935 +
936 + private static function addAttachmentFiles($fileValue, $basePath, &$attachments)
937 + {
938 + $baseResolved = realpath($basePath);
939 + if (false === $baseResolved) {
940 + Log::debug_log([
941 + 'message' => 'FileHandler::addAttachmentFiles base path invalid',
942 + 'basePath' => $basePath,
943 + ]);
944 + return;
945 + }
946 +
947 + $addFile = static function ($candidate) use ($baseResolved, &$attachments) {
948 + $safeFile = is_string($candidate) ? trim($candidate) : '';
949 + if ('' === $safeFile) {
950 + return;
951 + }
952 + $path = $baseResolved . DIRECTORY_SEPARATOR . $safeFile;
953 + $resolved = realpath($path);
954 + if (false === $resolved || 0 !== strpos($resolved, $baseResolved . DIRECTORY_SEPARATOR)) {
955 + Log::debug_log([
956 + 'message' => 'FileHandler::addAttachmentFiles blocked path traversal',
957 + 'file' => $candidate,
958 + 'resolved' => $resolved,
959 + 'baseResolved' => $baseResolved,
960 + ]);
961 + return;
962 + }
963 + if (is_readable($resolved)) {
964 + $attachments[] = $resolved;
965 + }
966 + };
967 +
968 + if (is_array($fileValue)) {
969 + foreach ($fileValue as $singleFile) {
970 + $addFile($singleFile);
971 + }
972 + return;
973 + }
974 +
975 + $addFile($fileValue);
976 + }
977 +
978 + /**
979 + * Return file type by checking with mime type
980 + * @param string $mime
981 + * @return string
982 + */
983 + public static function getFileTypeByMime(string $mime)
984 + {
985 + $mime = strtolower($mime);
986 +
987 + if (preg_match('/^image\//', $mime)) {
988 + return 'image';
989 + }
990 +
991 + $compressed = [
992 + 'application/zip',
993 + 'application/x-rar-compressed',
994 + 'application/x-7z-compressed',
995 + 'application/gzip',
996 + 'application/x-tar',
997 + 'application/x-gtar',
998 + 'application/x-bzip2',
999 + 'application/x-archive',
1000 + 'application/vnd.debian.binary-package',
1001 + ];
1002 + if (in_array($mime, $compressed, true)) {
1003 + return 'compressed';
1004 + }
1005 +
1006 + $presentation = [
1007 + 'application/vnd.ms-powerpoint',
1008 + 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
1009 + 'application/vnd.oasis.opendocument.presentation',
1010 + 'application/vnd.apple.keynote',
1011 + ];
1012 + if (in_array($mime, $presentation, true)) {
1013 + return 'presentation';
1014 + }
1015 +
1016 + $document = [
1017 + 'application/pdf',
1018 + 'application/msword',
1019 + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1020 + 'application/rtf',
1021 + 'text/plain',
1022 + 'application/vnd.oasis.opendocument.text',
1023 + 'application/x-tex',
1024 + 'text/rtf',
1025 + ];
1026 + if (in_array($mime, $document, true)) {
1027 + return 'document';
1028 + }
1029 +
1030 + $data = [
1031 + 'text/csv',
1032 + 'application/xml',
1033 + 'text/xml',
1034 + 'application/sql',
1035 + 'application/vnd.ms-excel',
1036 + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1037 + 'application/x-sqlite3',
1038 + 'application/octet-stream', // generic binary (could be db files)
1039 + ];
1040 + if (in_array($mime, $data, true)) {
1041 + return 'data';
1042 + }
1043 +
1044 + if (preg_match('/^audio\//', $mime)) {
1045 + return 'audio';
1046 + }
1047 +
1048 + if (preg_match('/^video\//', $mime)) {
1049 + return 'video';
1050 + }
1051 +
1052 + return 'other';
1053 + }
1054 +
1055 + /**
1056 + * Return file type by checking with extension
1057 + * @param string $extension
1058 + * @return string
1059 + */
1060 + public static function getFileTypeByExtension($extension)
1061 + {
1062 + switch (strtolower($extension)) {
1063 + case 'xbm':
1064 + case 'tif':
1065 + case 'pjp':
1066 + case 'pjpeg':
1067 + case 'svgz':
1068 + case 'jpg':
1069 + case 'jpeg':
1070 + case 'ico':
1071 + case 'tiff':
1072 + case 'gif':
1073 + case 'svg':
1074 + case 'bmp':
1075 + case 'png':
1076 + case 'jfif':
1077 + case 'webp':
1078 + return 'image';
1079 +
1080 + case '7z':
1081 + case 'arj':
1082 + case 'deb':
1083 + case 'pkg':
1084 + case 'rar':
1085 + case 'rpm':
1086 + case 'gz':
1087 + case 'z':
1088 + case 'zip':
1089 + return 'compressed';
1090 +
1091 + case 'key':
1092 + case 'odp':
1093 + case 'pps':
1094 + case 'ppt':
1095 + case 'pptx':
1096 + return 'presentation';
1097 +
1098 + case '_rf_':
1099 + case 'doc':
1100 + case 'docx':
1101 + case 'odt':
1102 + case 'pdf':
1103 + case 'rtf':
1104 + case 'tex':
1105 + case 'txt':
1106 + case 'wks':
1107 + case 'wps':
1108 + case 'wpd':
1109 + return 'document';
1110 +
1111 + case 'csv':
1112 + case 'dat':
1113 + case 'db':
1114 + case 'dbf':
1115 + case 'log':
1116 + case 'mdb':
1117 + case 'sav':
1118 + case 'sql':
1119 + case 'tar':
1120 + case 'sqlite':
1121 + case 'xml':
1122 + return 'data';
1123 +
1124 + case 'opus':
1125 + case 'flac':
1126 + case 'webm':
1127 + case 'weba':
1128 + case 'wav':
1129 + case 'ogg':
1130 + case 'm4a':
1131 + case 'mp3':
1132 + case 'oga':
1133 + case 'mid':
1134 + case 'amr':
1135 + case 'aiff':
1136 + case 'wma':
1137 + case 'au':
1138 + case 'acc':
1139 + case 'wpl':
1140 + return 'audio';
1141 +
1142 + case 'ogm':
1143 + case 'wmv':
1144 + case 'mpg':
1145 + case 'ogv':
1146 + case 'mov':
1147 + case 'asx':
1148 + case 'mpeg':
1149 + case 'mp4':
1150 + case 'm4v':
1151 + case 'avi':
1152 + case '3gp':
1153 + case 'flv':
1154 + case 'mkv':
1155 + case 'swf':
1156 + return 'video';
1157 + default:
1158 + return 'other';
1159 + }
1160 + }
100 1161 }