عارف
عارف
خواندن ۱۱ دقیقه·۳ سال پیش

آپلود فایل‌ها به شکل امن در PHP

تذکر: این یک پست آموزشی برای عموم نیست! بلکه تنها جایی برای یادداشت‌های من حین یادگیریه تا بهتر به خاطر بسپارم و در صورت لزوم به اون‌ها مراجعه کنم.

مشخصات دوره:

Lynda Uploading Files Securely with PHP - David Powers

برای درک این دوره باید با سینتکس اولیه PHP و توابع کاربردی زیر آشنا باشید:

<?php isset() is_array() in_array() str_replace() ?>

نکتهٔ مهم در طراحی فرم آپلود فایل در HTML، ست کردن attribute زیر هست:

enctype=&quotmultipart/form-data&quot

کلید PHP_SELF از آرایه سوپر گلوبال SERVER به فایل جاری اشاره می‌کنه:

<?php echo $_SERVER['PHP_SELF']; ?>

متد آپلود فایل‌ها حتما باید از نوع POST باشه.

فایل‌هایی که آپلود میشن، اول توی یک مسیر موقت توی سرور قرار می‌گیرن و بعد ما می‌تونیم با نوشتن یک قطعه کد ساده اون فایل‌ها رو به جایی که می‌خوایم منتقل کنیم. برای دیدن مسیر موقتی که توی php ست شده، کافیه که تابع phpinfo رو ران کنیم و دنبال کلید واژهٔ upload_tmp_dir باشیم.

اما باید دقت داشته باشیم که فایل آپلود شده، اگر به جای دیگه‌ای منتقل نشه، به سرعت از روی سرور ما حذف میشه.

باید دقت داشته باشیم که فیلد name ورودی file ما حتما پر شده باشد؛ چرا که اگر این کار را نکنیم، روی سرور خروجی زیر یک آرایه تهی خواهد بود:

print_r($_FILES):
var_dump($_FILES);

نکته: برای اینکه خروجی print_r به شکل خوانا و جذاب پرینت بشه، حتما باید خروجی تابع رو بین تگ های pre قرار بدیم:

Array ( [uploaded_file] => Array ( [name] => Screenshot_2022-05-23_09-27-39.png [full_path] => Screenshot_2022-05-23_09-27-39.png [type] => image/png [tmp_name] => /opt/lampp/temp/phpak1LmS [error] => 0 [size] => 381197 ) )


در حین آپلود فایل، ممکنه که با یه سری error مواجه بشیم و کد اون ارور رو توی آرایه FILES می‌بینیم. هر کد یک معنا داره که لیست اون‌ها رو اینجا می‌بینیم:

لیست کدهای error آپلود فایل در php (بخش اول)
لیست کدهای error آپلود فایل در php (بخش اول)
لیست کدهای error آپلود فایل در php  (بخش دوم)
لیست کدهای error آپلود فایل در php (بخش دوم)


سایز فایل آپلود شده به byte هست و همین‌طور نوع فایل هم به وسیلهٔ مرورگر حدس زده میشه که ممکنه گاهی نادرست باشه.

چطور می‌تونیم بزرگی فایلی که آپلود میشه رو محدود کنیم؟

<?php $max = 50 * 1024; if (isset($_POST['submit_pressed'])) { } ?> <!doctype html> <html lang=&quoten&quot> <head> <title>Title</title> </head> <body> <form method=&quotpost&quot enctype=&quotmultipart/form-data&quot> <input type=&quotfile&quot name=&quotuploaded_file&quot> <input type=&quothidden&quot name=&quotMAX_FILE_SIZE&quot value=&quot<?php echo $max ?>&quot> <button type=&quotsubmit&quot name=&quotsubmit_pressed&quot>Submit</button> </form> </body> </html>

کافیه یک متغیر که بزرگی فایل رو به بایت توش ذخیره می‌کنه داشته باشیم و توی فرم html، یک ورودی از نوع hidden با نام MAZ_FILE_SIZE و value برابر با متغیر یاد شده داشته باشیم. حالا اگر فایلی که کاربر قصد داره آپلود کنه از اون مقدار محدود شده بیشتر باشه، فایل اصلا آپلود نمیشه و برای ما error_code شماره ۲ برگشت داده میشه.

ویژگی‌های پوشه آپلود فایل (upload directory):

۱- ست کردن دسترسی‌ها یا permissions:

وب سرور باید دسترسی نوشتن را داشته باشد.

توی لینوکس برای امنیت بیشتر باید به دایرکتوری‌های دسترسی ۷۵۵ داد (شاید هم ۷۷۵). اعداد به ترتیب از چپ به راست مربوط به صاحب (owner)، گروه (group) و تمامی کاربران (all users) است. جدول زیر مفهوم هر عدد را نشان می‌دهد:

سطوح دسترسی در linux
سطوح دسترسی در linux

آپلود کردن فایل‌ها درون روت، به تمامی افراد امکان دسترسی عمومی به فایل را می‌دهد. بنابراین ایجاد پوشه‌ای که بتوان درون آن فایل‌ها را آپلود کرد؛ ضروری هست.

مثلا می‌تونیم به راحتی در پوشه‌ای که می‌خوایم امکان دسترسی مستقیم رو محدود کنیم یک فایل htaccess ایجاد کنیم و دستور زیر رو توش قرار بدیم، که باعث میشه کاربری که به اون صفحه وارد شد؛ یک خطای ۴۰۳ دریافت کنه:

Options -Indexes

حالا که فایل رو آپلود کردیم و باید اون رو از پوشهٔ موقت یا upload_tmp_dir به پوشهٔ مورد نظر خودمون منتقل کنیم.

توی PHP یک ثابت داریم به اسم __DIR__ که مسیر جاری رو نشون میده. باید توجه داشته باشیم که این ثابت در آخر خودش / نداره.

برای انتقال فایل از تابع move_uploaded_file استفاده میشه:

<?php $destination = __DIR__ . '/path/to/your/upload/folder'; $from = $_FILES['name_of_input_in_html']['tmp_name']; $to = $destionation . $_FILES['name_of_input_in_html']['name']; $result = move_uploaded_file($from, $to); ?>


ما از طریق تابع php_info می‌تونیم به اطلاعات خیلی خوبی در خصوص نحوهٔ پیکربندی آپلود فایل‌ها دست پیدا کنیم؛ این اطلاعات در قسمت Core قرار گرفتن:

  • مورد اول، حداکثر تعداد فایلی رو که میشه در یک زمان آپلود کردن نشون میده، اگر تعداد بیشتر از این باشه، به طور خود به خود نادیده گرفته میشن.
  • مورد دوم، برای هر سینگل فایل محدودیت حجمی ایجاد می‌کنه.
  • مورد سوم، مقدار کل حجم نهایی رو محدود می‌کنه، مثلا ممکنه ۴ تا ۲ مگابایتی آپلود کنیم یا ۸ تا ۱ مگابایتی و... .
https://stackoverflow.com/a/23686617/12930583

یک سری از تنظیمات مربوط به آپلود رو می‌تونیم از طریق htaccess هم که یک فایل پیکربندی برای وب سرور آپاچی هست کنترل کنیم:


فضای نام یا namespaces در php:

https://www.w3schools.com/php/php_namespaces.asp


فضاهای نام در php نسخه ۵.۳ معرفی شدند؛ این قابلیت امکان دسته‌بندی کلاس‌ها را برای ما ساده‌تر کرده و همین‌طور احتمال تصادم بین کتابخانه‌ها و فریمورک‌ها رو از بین برده. قبل از فضاهای نام، برنامه‌نویس‌ها از نام‌های بلند برای کلاس‌ها جهت حل این مشکل استفاده می‌کردند.

طبق توضیحاتی که توی این دوره آموزشی اومده (که احتمالا الان استاندارها و قراردادها تغییر کرده)، معلم دوره میگه که طبق یک قرارداد ما باید در روت سایت یک پوشه به اسم src داشته باشیم و داخل اون باید برای هر فضای نام یک sub-directory داشته باشیم.
یک قرارداد دیگه اینه که اسم فایلی که توش یک کلاس خاص داریم باید هم اسم کلاسمون باشه.

نحوه تعریف namespace در یک فایل حاوی کلاس:

FileName.php: <?php namespace name_of_namespace; class ClassName { ... the body of the class ... } ?>

نحوهٔ استفاده از namespace در کد خودمون:

use name_of_namespace\ClassName as nickname;

نکتهٔ مهم در قطعه کد بالا اینه که backslash استفاده شده ربطی به ویندوز و یا لینوکس و... نداره، این فرمت درست فراخوانی یک فضای نام توی php هست و دیگه اینکه use کردن یک فضای نام هیچ ربطی به لود کردن اون با require و include و مشتقاتش نداره، بلکه ما باید فایل رو جداگانه require کنیم.
برخلاف require و include که می‌تونیم اون‌ها رو تقریبا در هر جایی از کد استفاده کنیم؛ اما use رو حتما باید در بالاترین نقطهٔ فایل انجام بدیم.

دو تا تابع کاربردی:

<?php // checks if the path passed as argument is a directory is_dir() // checks if the path passed as argument is writable is_writable() ?>


چطوری یک error رو throw کنیم؟

throw new \Exception(&quotError message...&quot) ;

باید دقت کنیم که فرم عمومی بدون \ هست، اما چون از داخل یک فضای نام داریم این کلاس رو فراخوانی می‌کنیم باید بگیم که دنبال کلاس Exception از مسیر روت هستیم.

حالا ما می‌خوایم یک کلاس ایجاد کنیم که کل پروسهٔ آپلود فایل رو توش هندل کنیم، بخش اول پیاده‌سازی این کلاس به شکل زیر خواهد بود:

<?php namespace foundationphp; class UploadFile { protected $destination; public function __construct($uploadFolder) { if (!is_dir($uploadFolder) || !is_writable($uploadFolder)) { throw new \Exception(&quot$uploadFolder must be a valid, writable folder.&quot); } if ($uploadFolder[strlen($uploadFolder)-1] != '/') { $uploadFolder .= '/'; } $this->destination = $uploadFolder; } }

و نحوهٔ استفاده از این کلاس در فایل index.php یا هر فایلی که بخش اصلی سایتمون هست:

<?php use foundationphp\UploadFile; $max = 50 * 1024; $message = ''; if (isset($_POST['upload'])) { require_once 'src/foundationphp/UploadFile.php'; $destination = __DIR__ . '/upload/'; try { $upload = new UploadFile($destination); } catch (Exception $e) { $message = $e->getMessage(); } } ?> <!doctype html> <html lang=&quoten&quot> <head> <meta charset=&quotUTF-8&quot> <title>File Uploads</title> <link href=&quotstyles/form.css&quot rel=&quotstylesheet&quot type=&quottext/css&quot> </head> <body> <h1>Uploading Files</h1> <?php if ($message) { echo &quot<p>$message</p>&quot } ?> <form action=&quot<?php echo $_SERVER['PHP_SELF'];?>&quot method=&quotpost&quot enctype=&quotmultipart/form-data&quot> <p> <input type=&quothidden&quot name=&quotMAX_FILE_SIZE&quot value=&quot<?php echo $max;?>&quot> <label for=&quotfilename&quot>Select File:</label> <input type=&quotfile&quot name=&quotfilename&quot id=&quotfilename&quot> </p> <p> <input type=&quotsubmit&quot name=&quotupload&quot value=&quotUpload File&quot> </p> </form> </body> </html>


از اونجایی که دسترسی به عناصر آرایه درونی در آرایه associative سوپر گلوبال FILES کمی دشواره، می‌تونیم به راحتی براش یک reference در نظر بگیریم؛ برای این کار از یک تابع built-in به اسم current استفاده می‌کنیم؛ خوبی این کار اینه که دیگه نیازی نیست که مقدار name مربوط به input صفحه html رو بدونیم:

$uploaded = current($_FILES); $uploaded['name']; $uploaded['type']; ...

به جای:

<?php $_FILES['html_name_value']['name']; $_FILES['html_name_value']['type']; $_FILES['html_name_value']['tmp_name']; $_FILES['html_name_value']['error']; $_FILES['html_name_value']['size']; ?>

نحوه تعریف آرایه در php:

$my_array = array(); $my_array = [];

پارامترهای تابع str_replace:

str_replace($search_for, $replace_with, $main_string);
https://stackoverflow.com/questions/3828352/what-is-a-mime-type#:~:text=A%20MIME%20type%20has%20two,MIME%20type%20is%20application%2Fmsword.

پارامترهای تابع strpos:

strpos($main_string, $keyword);

تابع pathinfo اصلا کاری به این نداره که مسیر یا فایلی که بهش دادیم؛ واقعا روی فایل سیستم ما باشه یا خیر، فقط اون رو برای ما parse می‌کنه:

<?php print_r(pathinfo(&quot/testweb/test.txt&quot)); ?> The output: Array ( [dirname] => /testweb [basename] => test.txt [extension] => txt )

منظور از needle و haystack چیه؟ یه چیز تو مایه‌های سوزن در خرمن! needle که همون سوزن هست و haystack هم خرمن، وقتی یک تابع میگه این needle هست، یعنی ما دنبال این سوزن توی haystack که یه حجم عظیمی هست می‌گردیم!


تابع scandir میاد و مسیری که بهش می‌دیم رو روی فایل سیستم اسکن می‌کنه و لیست فایل‌ها و دایرکتوری‌های موجود رو در قالب یک آرایه ارائه میده:

print_r(scandir('.')); output: Array ( [0] => . [1] => .. [2] => file.txt [3] => index.php [4] => src )

توی این آموزش یک تابع داشتیم که بخشی از solution مربوط به نام گذاری اسم های تکراریش برام جالب اومد، این قطعه کد رو این پایین می‌ذارم با یه توضیح مختصر در مورد اینکه چطور کار می‌کنه:

<?php protected function checkName($file) { $this->newName = null; $nospaces = str_replace(' ', '_', $file['name']); if ($nospaces != $file['name']) { $this->newName = $nospaces; } $nameparts = pathinfo($nospaces); $extension = isset($nameparts['extension']) ? $nameparts['extension'] : ''; if (!$this->typeCheckingOn && !empty($this->suffix)) { if (in_array($extension, $this->notTrusted) || empty($extension)) { $this->newName = $nospaces . $this->suffix; } } if ($this->renameDuplicates) { $name = isset($this->newName) ? $this->newName : $file['name']; $existing = scandir($this->destination); if (in_array($name, $existing)) { $i = 1; do { $this->newName = $nameparts['filename'] . '_' . $i++; if (!empty($extension)) { $this->newName .= &quot.$extension&quot } if (in_array($extension, $this->notTrusted)) { $this->newName .= $this->suffix; } } while (in_array($this->newName, $existing)); } } } ?>


بخش جالب این تابع برای من مربوط میشه به renameDuplicates که میاد اول با scandir کل دایرکتوری‌ها و فایل‌های موجود توی مسیر آپلود رو توی یک آرایه ذخیره می‌کنه، بعد میاد و چک می‌کنه که اسم فایلی که قراره آپلود بشه، آیا داخل آرایهٔ scandir هست یا خیر، اگر بود که حلقهٔ do...while ادامه پیدا می‌کنه و هر سری به آخر اسم فایل با counter عدد اضافه می‌کنه تا اینکه شرط نقض بشه و اسمی تولید بشه که توی لیست نیست.


یک راه حل دیگه‌ای که اینجا بهش اشاره نکرد ولی یا خودم قبلا ازش استفاده کردم و یا اینکه جایی دیدم، اینه که به آخر اسم فایل timestamp رو اضافه کنیم.


آپلود به صورت multiple و درک اینکه html و php چطور این موضوع رو هندل می‌کنند.

در حالت single file ما یه همچین input ای داخل html داریم:

<input type=&quotfile&quot name=&quotuploaded_file&quot id=&quotupload_file&quot>

حالا برای اینکه به php بفهمونیم که ما قراره چندین فایل رو از طریق این فیلد آپلود کنیم؛ کافیه که یک جفت square brackets به انتهای پروپرتی name اضافه کنیم و همین‌طور attribute مربوط به آپلود چندگانه یعنی multiple رو هم اضافه کنیم که یک attribute جدید هست که توی html5 اضافه شده؛ بعنی اینجوری:

<input type=&quotfile&quot name=&quotuploaded_file[]&quot id=&quotupload_file&quot multiple>

اما نکته جالب اینه که سوپر گلوبال FILES خودش راهی برای هندل کردن این آپلود چندگانه داره و در حالت عادی شاید نخوایم که تغییری توی کدهای سمت سرور بدیم؛ php میاد و مشخصات فایل‌ها رو به شکل ترکیبی از associative و indexed مرتب می‌کنه:

<?php Array ( [uploaded_file] => Array ( [name] => Array ( [0] => Screenshot_2022-02-26_23-24-15:.png [1] => Screenshot_2022-03-08_14-50-50.png [2] => Screenshot_2022-05-23_09-27-39.png [3] => Screenshot_2022-05-12_21-11-27.png [4] => Screenshot_2022-03-30_22-50-28.png ) [full_path] => Array ( [0] => Screenshot_2022-02-26_23-24-15:.png [1] => Screenshot_2022-03-08_14-50-50.png [2] => Screenshot_2022-05-23_09-27-39.png [3] => Screenshot_2022-05-12_21-11-27.png [4] => Screenshot_2022-03-30_22-50-28.png ) [type] => Array ( [0] => image/png [1] => image/png [2] => image/png [3] => image/png [4] => image/png ) [tmp_name] => Array ( [0] => /opt/lampp/temp/phptQVsL5 [1] => /opt/lampp/temp/phpne1RT1 [2] => /opt/lampp/temp/phpIQIBF5 [3] => /opt/lampp/temp/phpl4rKJ3 [4] => /opt/lampp/temp/phpM06yj3 ) [error] => Array ( [0] => 0 [1] => 0 [2] => 0 [3] => 0 [4] => 0 ) [size] => Array ( [0] => 441503 [1] => 431455 [2] => 381197 [3] => 366068 [4] => 335107 ) ) ) ?>


برای iterate کردن key-value داخل php:

<?php $my_array = ['name' => 'Aref', 'age' => 25]; foreach($my_array as $key => $value){ echo &quotKey: &quot.$key; echo '<br>'; echo &quotValue: &quot.$value; echo '<br>'; echo '<br>'; } ?> output: Key: name Value: Aref Key: age Value: 25


کلاس نهایی که برای آپلود فایل استفاده می‌کنیم؛ به شکل زیر خواهد بود:

لینک pastebin به شکل هایلایت شده.

<?php namespace foundationphp; class UploadFile { protected $destination; protected $messages = array(); protected $maxSize = 51200; protected $permittedTypes = array( 'image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/webp' ); protected $newName; protected $typeCheckingOn = true; protected $notTrusted = array('bin', 'cgi', 'exe', 'js', 'pl', 'php', 'py', 'sh'); protected $suffix = '.upload'; protected $renameDuplicates; public function __construct($uploadFolder) { if (!is_dir($uploadFolder) || !is_writable($uploadFolder)) { throw new \Exception(&quot$uploadFolder must be a valid, writable folder.&quot); } if ($uploadFolder[strlen($uploadFolder)-1] != '/') { $uploadFolder .= '/'; } $this->destination = $uploadFolder; } public function setMaxSize($bytes) { $serverMax = self::convertToBytes(ini_get('upload_max_filesize')); if ($bytes > $serverMax) { throw new \Exception('Maximum size cannot exceed server limit for individual files: ' . self::convertFromBytes($serverMax)); } if (is_numeric($bytes) && $bytes > 0) { $this->maxSize = $bytes; } } public static function convertToBytes($val) { $val = trim($val); $last = strtolower($val[strlen($val)-1]); if (in_array($last, array('g', 'm', 'k'))){ switch ($last) { case 'g': $val *= 1024; case 'm': $val *= 1024; case 'k': $val *= 1024; } } return $val; } public static function convertFromBytes($bytes) { $bytes /= 1024; if ($bytes > 1024) { return number_format($bytes/1024, 1) . ' MB'; } else { return number_format($bytes, 1) . ' KB'; } } public function allowAllTypes($suffix = null) { $this->typeCheckingOn = false; if (!is_null($suffix)) { if (strpos($suffix, '.') === 0 || $suffix == '') { $this->suffix = $suffix; } else { $this->suffix = &quot.$suffix&quot } } } public function upload($renameDuplicates = true) { $this->renameDuplicates = $renameDuplicates; $uploaded = current($_FILES); if (is_array($uploaded['name'])) { foreach ($uploaded['name'] as $key => $value) { $currentFile['name'] = $uploaded['name'][$key]; $currentFile['type'] = $uploaded['type'][$key]; $currentFile['tmp_name'] = $uploaded['tmp_name'][$key]; $currentFile['error'] = $uploaded['error'][$key]; $currentFile['size'] = $uploaded['size'][$key]; if ($this->checkFile($currentFile)) { $this->moveFile($currentFile); } } } else { if ($this->checkFile($uploaded)) { $this->moveFile($uploaded); } } } public function getMessages() { return $this->messages; } protected function checkFile($file) { if ($file['error'] != 0) { $this->getErrorMessage($file); return false; } if (!$this->checkSize($file)) { return false; } if ($this->typeCheckingOn) { if (!$this->checkType($file)) { return false; } } $this->checkName($file); return true; } protected function getErrorMessage($file) { switch($file['error']) { case 1: case 2: $this->messages[] = $file['name'] . ' is too big: (max: ' . self::convertFromBytes($this->maxSize) . ').'; break; case 3: $this->messages[] = $file['name'] . ' was only partially uploaded.'; break; case 4: $this->messages[] = 'No file submitted.'; break; default: $this->messages[] = 'Sorry, there was a problem uploading ' . $file['name']; break; } } protected function checkSize($file) { if ($file['size'] == 0) { $this->messages[] = $file['name'] . ' is empty.'; return false; } elseif ($file['size'] > $this->maxSize) { $this->messages[] = $file['name'] . ' exceeds the maximum size for a file (' . self::convertFromBytes($this->maxSize) . ').'; return false; } else { return true; } } protected function checkType($file) { if (in_array($file['type'], $this->permittedTypes)) { return true; } else { $this->messages[] = $file['name'] . ' is not permitted type of file.'; return false; } } protected function checkName($file) { $this->newName = null; $nospaces = str_replace(' ', '_', $file['name']); if ($nospaces != $file['name']) { $this->newName = $nospaces; } $nameparts = pathinfo($nospaces); $extension = isset($nameparts['extension']) ? $nameparts['extension'] : ''; if (!$this->typeCheckingOn && !empty($this->suffix)) { if (in_array($extension, $this->notTrusted) || empty($extension)) { $this->newName = $nospaces . $this->suffix; } } if ($this->renameDuplicates) { $name = isset($this->newName) ? $this->newName : $file['name']; $existing = scandir($this->destination); if (in_array($name, $existing)) { $i = 1; do { $this->newName = $nameparts['filename'] . '_' . $i++; if (!empty($extension)) { $this->newName .= &quot.$extension&quot } if (in_array($extension, $this->notTrusted)) { $this->newName .= $this->suffix; } } while (in_array($this->newName, $existing)); } } } protected function moveFile($file) { $filename = isset($this->newName) ? $this->newName : $file['name']; $success = move_uploaded_file($file['tmp_name'], $this->destination . $filename); if ($success) { $result = $file['name'] . ' was uploaded successfully'; if (!is_null($this->newName)) { $result .= ', and was renamed ' . $this->newName; } $result .= '.'; $this->messages[] = $result; } else { $this->messages[] = 'Could not upload ' . $file['name']; } } }


توضیحات تکمیلی در مورد این کلاس و ویژگی‌های آن:

  • امنیت:
امکان محدود کردن حجم هر فایل برای آپلود
چک کردن MIME هر فایل و تغییر نام فایل‌های مشکوک
  • انعطاف‌پذیری:
مدیریت فایل‌های single یا multiple
تنظیمات راحت بدون نیاز به تغییر در سورس
  • تنظیمات پیش فرض:
ماکزیمم حجم برای هر فایل ۵۰ کیلوبایت است.
آپلود به فایل‌های تصویری محدود شده
اگر امکان آپلود همهٔ فایل‌ها را فعال کنیم؛ به انتهای فایل‌های ریسکی پسوند .upload اضافه می‌شود.
فایل‌های با اسم یکسان، تغییر نام داده می‌شوند.
  • سازگاری:
فایل‌های برای سازگاری با فایل سیستم تغییر نام داده شده و space با _ جایگزین می‌شود.
به php نسخه ۵.۳ یا بالاتر نیازه


نحوهٔ استفاده از کلاس:

باید namespace مورد نظر رو use کنیم و یک شی از کلاس UploadFile با یک آرگومان ورودی برای متد سازنده که در واقع مسیر آپلود فایل هست ایجاد کنیم.

<?php use foundationphp\UploadFile; $max = 5000 * 1024; $result = array(); if (isset($_POST['upload'])) { require_once 'src/foundationphp/UploadFile.php'; $destination = __DIR__ . '/uploaded/'; try { $upload = new UploadFile($destination); $upload->setMaxSize($max); $upload->upload(); $result = $upload->getMessages(); } catch (Exception $e) { $result[] = $e->getMessage(); } } ?>

این کلاس، ۴ تابع public دارد:

مورد اول setMaxSize هست که حداکثر حجم هر فایل رو مشخص می‌کنه. ست کردنش optional هست و مقدار دیفالت اون ۵۰ کیلوبایته:

setMaxSize($bytes);

مورد دوم تابع allowAllTypes هست که به ما این اجازه رو میده که به جای مقدار پیش فرض که فقط شامل تصاویر میشه، تمامی فایل‌ها رو بتونیم آپلود کنیم. یک پارامتر هم داره به اسم suffix که به صورت پیش فرض null هست و می‌تونیم یک مقداری رو پاس بدیم تا به انتهای فایل‌های مشکوک اضافه کنه:

allowAllTypes($suffix = null);

در حالت پیش فرض، پسوند .upload به انتهای فایل‌های ریسکی اضافه میشه، اما اگر یک رشتهٔ خالی با سینگل یا دابل کوت پاس بدیم؛ نام فایل هیچ تغییری نخواهد کرد.

مورد سوم تابع upload هست که یک آرگومان ورودی می‌گیره و اون هم مقدار renameDuplicates هست که مقادیر boolean رو می‌پذیره و اگر فعال باشه، فایل‌هایی که با اسم مشابه روی فایل سیستم باشن رو تغییر نام میده و اگر غیر فعال باشه replace میشن. نکتهٔ دیگه اینکه این متد باید بعد از setMaxSize و allowAllTypes مورد استفاده قرار بگیره.

upload($renameDuplicates = true);

مورد چهارم تابع getMessages هست که یک آرایه از پیام های log شده رو برمی‌گردونه و باید بعد از متد upload فراخوانی بشه تا وضعیت و یا خطاهای رخ داده حین آپلود رو گزارش کنه.

همین‌طور این کلاس، ۴ تابع public از نوع static داره، یعنی توابعی که بدون نیاز به ایجاد شی از کلاس با استفاده از فرمول ClassName::public_static_method قابل دسترسی هستند:

مورد اول convertToBytes که می‌تونیم یک مقدار رو به بایت تبدیل کنه و بعدی convertFromBytes که مقدار داده شده رو human readable می‌کنه.

توی php یه تابع جالب داریم به نام error_get_last که آخرین error های اتفاق افتاده رو به ما نشون میده که اینجا نمونه خروجی اون رو می‌بینیم:

Array ( [type] => 2 [message] => POST Content-Length of 1169141131 bytes exceeds the limit of 41943040 bytes [file] => Unknown [line] => 0 )

توی php اگر به max_file_uploads برسیم و اقدام به آپلود تعداد بیشتری از این مقدار کرده باشیم، فقط تا همون تعداد آپلود میشن و بقیه discard خواهد شد؛ این رو تنها زمانی که پروسهٔ آپلود به خاتمه رسیده متوجه میشیم و این user-friendly نیست.

یک راهکار برای اینکه جلوی این کار رو بگیریم و تازه بعد از اینکه کلی دیتا آپلود شد؛ بگیم مشکلی داشتیم و... اینه که محدودیت های سرور و php رو توی متغییرهای session ذخیره کنیم و به کاربر نشون بدیم. اما این هم زیاد جالب نیست و اگر طوری بود که اگر کاربر چیزی رو select می‌کرد و با محدودیت‌های ما مچ می‌شد فوراً هشدار میداد خیلی خوب بود؛ اما php یک زبان سمت server هست و توی این مورد کمک چندانی نمی تونه بکنه، بنابراین باید دست به دامان JS بشیم. البته استفاده از input با name ثابت MAX_FILE_SIZE برای سینگل فایل جوابه، اما برای آپلود چندگانه و Userfriendly سازی پروژه استفادهٔ ترکیبی از JS و PHP بهتره.


یه نکتهٔ مهم در مورد attributeهای با فرمت:

data-*=&quot&quot
https://www.w3schools.com/tags/att_data-.asp


فایل JS که برای مدیریت محدودیت‌های آپلود فایل در سمت کاربر طراحی شده:

var field = document.getElementById('filename'); field.addEventListener('change', countFiles, false); function countFiles(e) { if (this.files != undefined) { var elems = this.form.elements, submitButton, len = this.files.length, max = document.getElementsByName('MAX_FILE_SIZE')[0].value, maxfiles = this.getAttribute('data-maxfiles'), maxpost = this.getAttribute('data-postmax'), displaymax = this.getAttribute('data-displaymax'), filesize, toobig = [], total = 0, message = ''; for (var i = 0; i < elems.length; i++) { if (elems[i].type == 'submit') { submitButton = elems[i]; break; } } for (i = 0; i < len; i++) { filesize = this.files[i].size; if (filesize > max) { toobig.push(this.files[i].name); } total += filesize; } if (toobig.length > 0) { message = 'The following file(s) are too big:\n' + toobig.join('\n') + '\n\n'; } if (total > maxpost) { message += 'The combined total exceeds ' + displaymax + '\n\n'; } if (len > maxfiles) { message += 'You have selected more than ' + maxfiles + ' files'; } if (message.length > 0) { submitButton.disabled = true; alert(message); } else { submitButton.disabled = false; } } }

لیستی از تمامی MIME Types:

https://www.iana.org/assignments/media-types/media-types.xhtml














phpآپلودfile uploadبرنامه نویسیامنیت
شاید از این پست‌ها خوشتان بیاید