File Manager

Prisma PHP File Manager is a powerful tool for uploading and managing files in PHP applications. It simplifies file uploads, storage, and organization within PHP projects. With Prisma PHP File Manager, you can manage files and directories effortlessly, offering a smooth experience for organizing files in your codebase.

Basic Example of File Upload

To upload files, create a form with the enctype attribute set to multipart/form-data and an input field of type file:

<form action="/" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <button>Upload</button>
  </form>

Understanding the $_FILES Array

In PHP, the $_FILES array contains information about the uploaded file. Here's an example of what it contains:

$_FILES
  Array
  (
      [file] => Array
          (
              [name] => test-upload.png
              [full_path] => test-upload.png
              [type] => image/png
              [tmp_name] => C:\xampp\tmp\php1E10.tmp
              [error] => 0
              [size] => 107018
          )
  )

Handling Multiple File Uploads

To allow multiple files to be uploaded at once, you can modify your form to include the multiple attribute on the file input. Additionally, you need to adjust your PHP code to handle the array structure in $_FILES for multiple files.

<form action="/" method="post" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple />
    <button>Upload</button>
  </form>

The $_FILES array structure for multiple files will look like this:

$_FILES
  Array
  (
      [files] => Array
          (
              [name] => Array
                  (
                      [0] => test-upload1.png
                      [1] => test-upload2.png
                  )
              [type] => Array
                  (
                      [0] => image/png
                      [1] => image/png
                  )
              [tmp_name] => Array
                  (
                      [0] => C:\xampp\tmp\php1E10.tmp
                      [1] => C:\xampp\tmp\php1E11.tmp
                  )
              [error] => Array
                  (
                      [0] => 0
                      [1] => 0
                  )
              [size] => Array
                  (
                      [0] => 107018
                      [1] => 204800
                  )
          )
  )

To handle multiple files in PHP, you can iterate through the arrays within $_FILES:

<?php
  if ($isPost) {
      foreach ($_FILES['files']['name'] as $key => $name) {
          if ($_FILES['files']['error'][$key] === 0) {
              $tmpName = $_FILES['files']['tmp_name'][$key];
              $size = $_FILES['files']['size'][$key];
              $type = $_FILES['files']['type'][$key];
              // Move the uploaded file to the desired directory
              move_uploaded_file($tmpName, "uploads/$name");
          }
      }
  }
  ?>

Common File Upload Errors

When handling file uploads, you might encounter various error codes. Below are the most common ones:

Error Code Meaning
0 No errors; file uploaded successfully.
1 File exceeds the upload_max_filesize directive in php.ini.
2 File exceeds the MAX_FILE_SIZE directive specified in the HTML form.
3 File was only partially uploaded.
4 No file was uploaded.
6 Missing a temporary folder.
7 Failed to write file to disk.
8 A PHP extension stopped the file upload.

File Upload Form Example

Below is a simple PHP script for uploading files:

<?php
  if ($isPost) {
      echo '_FILES <pre>';
      print_r($_FILES);
      echo '</pre>';
  }
  ?>
  
  <form action="/" method="post" enctype="multipart/form-data">
      <input type="file" name="file" />
      <button>Upload</button>
  </form>

Setting Maximum File Size

You can set a maximum file size in bytes using a hidden input field in your form. Ensure the field is placed before the file input.

<?php
  $maxFileSize = 50 * 1024; // 50 KB
  if ($isPost) {
      echo '_FILES <pre>';
      print_r($_FILES);
      echo '</pre>';
  }
  ?>
  
  <form action="/" method="post" enctype="multipart/form-data">
      <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo $maxFileSize; ?>" />
      <input type="file" name="file" />
      <button>Upload</button>
  </form>

Move upload file

To move the uploaded file to a specific directory, you can use the move_uploaded_file() function. The function takes two arguments: the temporary file path and the destination path.

<?php
  if ($isPost) {
      $tmpName = $_FILES['file']['tmp_name'];
      $name = $_FILES['file']['name'];
      move_uploaded_file($tmpName, DOCUMENT_PATH . "/uploads/$name");
  }
  ?>
  
  <form action="/" method="post" enctype="multipart/form-data">
      <input type="file" name="file" />
      <button>Upload</button>
  </form>

System Settings

To ensure that file uploads work correctly, you need to configure the following settings in your php.ini file:

Directive Default Role
file_uploads On Enables or disables file uploads
upload_temp_dir System default Specifies the temporary directory for file uploads

Individually Configurable Settings

You can configure the following settings in your PHP script using the .htaccess or ini_set() function:

Directive Default Role
max_file_uploads 20 The maximum number of files that can be uploaded in a single request.
Main
upload_max_filesize 2M The maximum size of an uploaded file.
Main
post_max_size 8M The maximum size of POST data that PHP will accept.
Main
max_input_time 60 The maximum time in seconds a script is allowed to parse input data.
max_execution_time 30 The maximum time in seconds a script is allowed to run.
memory_limit 128M The maximum amount of memory a script is allowed to allocate.

NOTE: You can also configure the maximum file size in your .htaccess file if you are using an Apache server. For example, you can add the following line to your .htaccess file: php_value upload_max_filesize 4194304, which sets the maximum file size to 4MB, or php_value post_max_size 16777216 to set the maximum POST size to 16MB.

The UploadFile class

The UploadFile class provides a convenient way to handle file uploads in PHP applications. It is located in the src/Lib/FileManager/UploadFile.php file. This class offers several benefits:

  1. Easy to reuse: The class can be easily integrated into different parts of your application.
  2. Size validation: It includes built-in size validation to ensure that uploaded files meet your requirements.
  3. Type restriction: You can specify permitted file types or neutralize risk ones to enhance security.
  4. Filename cleanup: The class can clean up filenames and optionally prevent overwriting existing files.
  5. Multiple uploads: It supports handling multiple file uploads in a single request.
  6. Inform user of the outcome: The class can provide feedback to users about the upload status.

The UploadFile class receives the destination folder as a parameter in its constructor. This folder is where the uploaded file will be moved to. Make sure that the destination directory is valid and writable. For example, you can create an instance of the UploadFile class like this: $uploadFile = new UploadFile(DOCUMENT_PATH . '/uploads/'); The destination directory should exist and have the necessary permissions to write files.

Public methods available in the UploadFile class include:

  • static convertToBytes(string $val): int: This method converts a human-readable file size to bytes, its a static method.
  • static convertFromBytes(int $bytes): string: This method converts a file size in bytes to a human-readable format, its a static method.
  • allowAllTypes(?string $suffix = null): void: This method allows all file types to be uploaded. You can optionally specify a file suffix to allow only files with that suffix.
    It is important to call this method before calling the upload method.
  • setMaxSize(int $bytes): void: This method sets the maximum file size allowed for uploads.
    It is important to call this method before calling the upload method.
  • upload(bool $renameDuplicates = true): void: Uploads a file to the destination directory.
  • update(array $file, string $oldFilename): bool: Updates an existing file in the destination directory, using the old filename.
  • rename(string $oldName, string $newName): bool: Renames a file in the destination directory.
  • delete(string $filename): bool: Deletes a file from the destination directory.
  • getMessages(): array: return an array of messages about the upload process.
    It is important to call this method after calling the upload method.
  • getErrorCode(): array: return an array of error codes about the upload process.
    It is important to call this method after calling the upload method.

The UploadFile class allows you to specify the permitted file types for uploads. By default, it includes the following MIME types: 'image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/webp'. However, each browser may have its own MIME types for files. You can refer to the IANA Media Types list for more information on MIME types.

File Manager APP

The File Manager APP is a web-based application that allows you to easily manage files and directories in your PHP projects. It provides a user-friendly interface for uploading, downloading, and organizing files. With the File Manager APP, you can effortlessly handle file uploads using the powerful UploadFile class provided by Prisma PHP. Say goodbye to the hassle of dealing with file uploads and enjoy a seamless file management experience.

<?php

  use Lib\Validator;
  use Lib\StateManager;
  use Lib\FileManager\UploadFile;
  
  const UPLOADS = DOCUMENT_PATH . '/uploads/';
  
  $maxSize = 50 * 1024; // 50KB
  $uploadFile = new UploadFile(UPLOADS);
  $uploadedFiles = scandir(UPLOADS);
  $uploadMessage = StateManager::getState('uploadMessage', ['error' => [], 'message' => []]);
  $serverMaxFiles = StateManager::getState('serverMaxFiles', ini_get('max_file_uploads'));
  $serverPostMax = StateManager::getState('serverPostMax', UploadFile::convertToBytes(ini_get('post_max_size')));
  $serverDisplayMax = StateManager::getState('serverDisplayMax', UploadFile::convertFromBytes($serverPostMax));
  
  function uploadFile()
  {
      global $state, $uploadFile, $maxSize;
  
      try {
          $uploadFile->setMaxSize($maxSize);
          $uploadFile->upload();
          StateManager::setState('uploadMessage', ['error' => $uploadFile->getErrorCode(), 'message' => $uploadFile->getMessages()]);
      } catch (Throwable $th) {
          StateManager::setState('uploadMessage', ['error' => [(current($uploadFile->getErrorCode()) !== 0)], 'message' => [$th->getMessage  ()]]);
      }
  }
  
  function deleteFile($data)
  {
      global $uploadFile, $state;
  
      try {
          $uploadFile->delete($data->name);
          StateManager::setState('uploadMessage', ['error' => [(current($uploadFile->getErrorCode()) !== 0)], 'message' => [current  ($uploadFile->getMessages())]]);
      } catch (Throwable $th) {
          return [
              'error' => true,
              'message' => $th->getMessage()
          ];
      }
  }
  
  function renameFile($data)
  {
      global $uploadFile, $state;
  
      try {
          if (($validatedValue = Validator::withRules($data->newName, 'required|min:3')) !== true) {
              return [
                  'error' => true,
                  'message' => $validatedValue
              ];
          }
  
          $uploadFile->rename($data->oldFileName, $data->newName);
          StateManager::setState('uploadMessage', ['error' => [(current($uploadFile->getErrorCode()) !== 0)], 'message' => [current  ($uploadFile->getMessages())]]);
      } catch (Throwable $th) {
          return [
              'error' => true,
              'message' => $th->getMessage()
          ];
      }
  }
  
  ?>
  
  <div class="w-screen h-screen grid place-items-center bg-gray-100">
      <div class="bg-white shadow-md rounded-lg p-6 flex flex-col gap-4 max-w-md w-full">
          <!-- Upload Section -->
          <div class="flex flex-col gap-4">
              <h1 class="text-3xl font-semibold text-gray-800">Upload File</h1>
              <ul class="list-disc ml-8">
                  <li>Up to <?= $serverMaxFiles ?> files can be uploaded simultaneously.</li>
                  <li>Each file should be no more than <?= UploadFile::convertFromBytes($maxSize); ?>.</li>
                  <li>Combined total should not exceed <?= $serverDisplayMax ?>.</li>
              </ul>
              <form class="flex flex-col gap-4" onsubmit="uploadFile" enctype="multipart/form-data" pp-suspense="{'disabled': true}">
                  <input type="hidden" name="MAX_FILE_SIZE" value="<?= $maxSize ?>" />
                  <input onchange="countFiles" type="file" name="file[]" class="block w-full text-sm text-gray-900 border   border-gray-300 rounded-lg cursor-pointer bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 p-1"   multiple data-max-files="<?= $serverMaxFiles ?>"
                      data-post-max="<?= $serverPostMax ?>"
                      data-display-max="<?= $serverDisplayMax ?>" />
                  <button class="w-full bg-blue-500 text-white font-semibold py-2 rounded-lg hover:bg-blue-600 transition-colors   duration-300 disabled:bg-gray-500" pp-suspense="{'textContent': 'Uploading...'}">
                      Upload
                  </button>
              </form>
          </div>
  
          <!-- Success/Error Message -->
          <?php if ($uploadMessage['message']) : ?>
              <ol class="flex flex-col justify-center list-disc gap-2 overflow-y-auto max-h-40" style="display: block;"   pp-display="10s">
                  <?php foreach ($uploadMessage['message'] as $key => $message) : ?>
                      <li class="text-sm mt-2 px-4 py-2 rounded-lg shadow-sm <?= $uploadMessage['error'][$key] ? 'bg-red-100   text-red-600' : 'bg-green-100 text-green-600' ?> <?= $message ? 'block' : 'hidden' ?>">
                          <?= $message ?>
                      </li>
                  <?php endforeach; ?>
              </ol>
          <?php endif; ?>
  
          <!-- Uploaded Files Section -->
          <div class="flex flex-col gap-4">
              <h2 class="text-2xl font-semibold text-gray-800">Uploaded Files</h2>
              <div class="grid gap-4 max-h-60 overflow-y-auto bg-gray-50 p-4 rounded-lg shadow-inner border border-gray-200">
                  <?php foreach ($uploadedFiles as $file) : ?>
                      <?php if ($file === '.' || $file === '..') continue; ?>
                      <div class="flex justify-between items-center p-4 bg-white rounded-lg shadow-md border">
                          <a href="<?= $documentUrl . '/uploads/' . urlencode($file) ?>" target="_blank" class="text-blue-500   hover:underline"><?= $file ?></a>
                          <div class="flex items-center gap-2">
                              <a href="<?= $documentUrl . '/uploads/' . urlencode($file) ?>" download class="text-gray-500   hover:text-green-500 transition-colors">
                                  <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"   fill="currentColor">
                                      <path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.  5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z" />
                                  </svg>
                              </a>
                              <button onclick="renameClickHandler({'name': '<?= $file ?>'})" class="text-gray-500   hover:text-gray-700 transition-colors">
                                  <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"   fill="currentColor">
                                      <path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0   31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56   56Zm-141 85-28-29 57 57-29-28Z" />
                                  </svg>
                              </button>
                              <button onclick="deleteClickHandler({'name': '<?= $file ?>'})" class="text-gray-500   hover:text-red-500 transition-colors">
                                  <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"   fill="currentColor">
                                      <path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56   56-224-224-224 224Z" />
                                  </svg>
                              </button>
                          </div>
                      </div>
                  <?php endforeach; ?>
              </div>
          </div>
      </div>
  </div>
  
  <dialog id="deleteModal" class="w-fit max-w-sm p-6 bg-white shadow-lg rounded-xl dark:bg-gray-800">
      <div class="w-full text-center flex flex-col gap-6">
          <h1 class="text-2xl font-bold text-gray-800 dark:text-white">Delete File</h1>
          <p class="text-gray-600 dark:text-gray-300">Are you sure you want to delete this file?</p>
  
          <div class="flex gap-4 items-center justify-center">
              <!-- Delete Form -->
              <form onsubmit="deleteFile" class="flex items-center gap-2">
                  <input type="hidden" id="deleteFileName" name="name" />
                  <button class="flex items-center gap-2 bg-red-500 text-white px-4 py-2 rounded-md hover:bg-red-600   transition-colors disabled:bg-gray-500" title="Delete"
                      pp-suspense="{'disabled': true}">
                      <!-- Delete Icon -->
                      <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"   fill="currentColor">
                          <path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z" />
                      </svg>
                      <span>Delete</span>
                  </button>
              </form>
  
              <!-- Cancel Form -->
              <form method="dialog">
                  <button class="flex items-center gap-2 bg-gray-500 text-white px-4 py-2 rounded-md hover:bg-gray-600   transition-colors" title="Cancel">
                      <!-- Cancel Icon -->
                      <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"   fill="currentColor">
                          <path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224   224Z" />
                      </svg>
                      <span>Cancel</span>
                  </button>
              </form>
          </div>
      </div>
  </dialog>
  
  <dialog id="renameModal" class="w-fit max-w-md p-6 bg-white shadow-lg rounded-xl dark:bg-gray-800">
      <div class="w-full flex flex-col gap-4">
          <!-- Title -->
          <h1 class="text-2xl font-bold text-gray-800 dark:text-white text-center">Rename File</h1>
  
          <div class="flex flex-col gap-2">
              <!-- New Name Input -->
              <form id="renameForm" onsubmit="renameFile" pp-after-request="renameFileValidation" class="flex flex-col gap-2"   pp-suspense="{'disabled': true}">
                  <input type="hidden" id="oldFileName" name="oldFileName" />
                  <input
                      id="newName"
                      class="border border-gray-300 rounded-lg p-3 w-full dark:bg-gray-700 dark:text-white focus:outline-none   focus:ring-2 focus:ring-blue-500"
                      type="text"
                      name="newName"
                      placeholder="New name"
                      required />
  
                  <!-- Error Message -->
                  <span class="text-red-500 text-center" id="renameMessage"></span>
              </form>
  
              <!-- Buttons: Rename and Cancel -->
              <div class="flex gap-4 justify-center">
                  <!-- Rename Button -->
                  <button
                      class="flex items-center gap-2 bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors   w-full disabled:bg-gray-500"
                      form="renameForm"
                      type="submit"
                      title="Rename"
                      pp-suspense="{'disabled': true}">
                      <!-- Rename Icon -->
                      <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"   fill="currentColor">
                          <path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55   56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z" />
                      </svg>
                      <span>Rename</span>
                  </button>
  
                  <!-- Cancel Button -->
                  <form method="dialog" class="w-full">
                      <button
                          class="flex items-center gap-2 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600   transition-colors w-full"
                          title="Cancel">
                          <!-- Cancel Icon -->
                          <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"   fill="currentColor">
                              <path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224   224Z" />
                          </svg>
                          <span>Cancel</span>
                      </button>
                  </form>
              </div>
          </div>
  </dialog>
  
  <script>
      function deleteClickHandler(data) {
          document.getElementById('deleteFileName').value = data.name;
          document.getElementById('deleteModal').showModal();
      }
  
      function renameClickHandler(data) {
          document.getElementById('oldFileName').value = data.name;
          const fileName = data.name.split('.');
          document.getElementById('newName').value = fileName[0];
          document.getElementById('renameModal').showModal();
      }
  
      function renameFileValidation(data) {
          if (data.response?.error) {
              document.getElementById('renameMessage').innerText = data.response.message;
          } else {
              document.getElementById('renameMessage').innerText = '';
          }
      }
  
      function countFiles(data) {
          const inputElement = data.element; // Get the input element from the event
          if (!inputElement.files) return;
  
          const formElements = Array.from(inputElement.form.elements);
          const submitButton = formElements.find(el => el.type === 'submit');
          const max = document.getElementsByName('MAX_FILE_SIZE')[0]?.value || 0;
          const maxFiles = parseInt(inputElement.getAttribute('data-max-files'), 10) || Infinity;
          const maxPost = parseInt(inputElement.getAttribute('data-post-max'), 10) || Infinity;
          const displayMax = inputElement.getAttribute('data-display-max') || 'the allowed limit';
          let totalSize = 0;
          const tooBigFiles = [];
  
          // Check each file size and accumulate the total size
          Array.from(inputElement.files).forEach(file => {
              if (file.size > max) {
                  tooBigFiles.push(file.name);
              }
              totalSize += file.size;
          });
  
          // Construct the error message based on validation results
          let message = '';
          if (tooBigFiles.length > 0) {
              message += `The following file(s) are too big:\n${tooBigFiles.join('\n')}\n\n`;
          }
          if (totalSize > maxPost) {
              message += `The combined total exceeds ${displayMax}\n\n`;
          }
          if (inputElement.files.length > maxFiles) {
              message += `You have selected more than ${maxFiles} files\n\n`;
          }
  
          // Display the message and disable the submit button if any validation failed
          if (message.length > 0) {
              submitButton.disabled = true;
              alert(message);
          } else {
              submitButton.disabled = false;
          }
      }
  </script>