Skip to content

Nahrávání souborů - Dropzone

Systém pro nahrávání PDF souborů, SVG obrázků, fotografií a jiných typů souborů s drag-and-drop interfacem.

Využíváme knihovnu Dropzone.js.

Postup implementace

  1. Definice pole v YAML
  2. Vykreslení pomocí Form.php
  3. Dropzone.js posílá fetch dle action
  4. Metoda "action" v kontroleru
  5. Volá se DropzoneHandler::upload()

Příklad: Fotogalerie

Praktický příklad je dostupný v Adminu ve fotogalerii.

1. Definice pole v YAML

form_fields_add_photos:
  -
    section: start
    title: Photos
  -
    name: file1
    label: Photos
    action: uploadPhoto
    id: 101
    text: "Přetáhněte sem soubory nebo klikněte pro nahrání."
    element:
      type: files
    description: ...

Důležitá pole: - id - ID elementu v DOM pro Dropzone - action - Metoda kontroleru volaná z JS pomocí fetch - element.type: files - Typ prvku pro vykreslení ve třídě Form

2. Vykreslení prvku ve formuláři

V třídě Form v metodě element_files():

private function element_files(array $element): void
{
    if ($this->uploadAfterFirstSave($element)) {
        return;
    }

    $queryParams = [
        'page'   => PAGE,
        'lang'   => LANG,
        'action' => array_get_str($element, 'action', ''),
        'id'     => array_get_str($this->_CMS['url'], 'id', ''),
        'PID'    => array_get_str($this->_CMS['url'], 'PID', ''),
        'width'  => array_get_str($element['element'], 'width', ''),
        'height' => array_get_str($element['element'], 'height', ''),
        'prefix' => array_get_str($element['element'], 'prefix', ''),
    ];

    $href = URL_APP_ADMIN . 'index.php?' . http_build_query($queryParams);

    echo '<div id="dropzone' . $element['id'] . '" class="dropzone" data-url="' . $href . '">';
    echo '<div class="dz-default dz-message"><span>{S ' . add_nbsp($element['text']) . '}</span></div>';
    echo '</div>';
}

Vygeneruje <div id="dropzone101" class="dropzone" data-url="...">.

3. JS konfiguraci pro Dropzone

V src/backend/js/dropzone/files.js:

import {Dropzone} from 'dropzone';
import {i18n} from '../translations.js';

const id = 'dropzone101';
const element = document.getElementById(id);

if (element) {
    new Dropzone(
        '#' + id,
        {
            url: element.getAttribute('data-url'),
            paramName: 'dzFile',
            maxFilesize: 50, // MB
            maxThumbnailFilesize: 50,

            // omezení typů souborů
            acceptedFiles: 'image/webp,image/jpeg,image/png',

            // zmenšení obrázků
            //resizeWidth: 1300,
            //resizeHeight: 1300,

            // překlady
            dictFileTooBig: i18n('dropzoneBigFile', '{{filesize}}', '{{maxFilesize}}'),
            dictInvalidFileType: i18n('dropzoneInvalidFileType'),

            // chunking (pro velké soubory)
            chunking: true,
            forceChunking: true,
            chunkSize: 1048576, // 1 MB
            retryChunks: true,
            retryChunksLimit: 3,
            parallelUploads: 1,

            init: function () {
                this.on('success', function (file, response) {
                    console.log('File uploaded:', response);
                });
                this.on('error', function (file, errorMessage) {
                    console.error(file.name + ' / ' + errorMessage);
                });
            }
        }
    );
}

Viz dokumentace Dropzone.

4-5. PHP metoda v kontroleru

public function uploadPhoto(): void
{
    // Defaultní options
    $response = DropzoneHandler::upload();

    // Nebo s vlastními options
    /*
    $options = [
        'path'              => DIR . '/var/tmp/',
        'maxFileSizeMB'     => 50,
        'paramName'         => 'dzFile',
        'allowedExtensions' => ['pdf', 'jpg', 'png'],
    ];
    $response = DropzoneHandler::upload($options);
    */

    if (!empty($response['error'])) {
        Debugger::log($response['error'], Debugger::ERROR);
        echo $response['error'];
    }

    if (!empty($response['status'])) {
        echo $response['status'];
    }

    if (!empty($response['name'])) {
        // Zde se mění specifická logika pro zpracování souboru
        if ($this->savePhoto($response)) {
            if (!is_file($this->path . 'main-th.webp')) {
                $this->mainPhoto(false);
            }
            $this->write_gallery($this->gallery, false);
            $this->change_count_photos();
        }
    }

    exit;
}

private function savePhoto($file): bool
{
    $in = $file['path'];
    // ... vaše logika pro uložení, resize, atd.
}

Pozn.: Tento základ by měl být všude stejný. Mění se jen blok po vrácení $response['name']. Metoda musí být vždy ukončena exitem.

DropzoneHandler

Statická metoda DropzoneHandler::upload() z core/Upload/DropzoneHandler.php:

  • Vyřešení spojování částí souborů (chunking)
  • Provádění bezpečnostních kontrol
  • Uložení souboru do definované cesty (defaultně /var/tmp/)

Vrací pole s:

[
    'name' => 'filename.jpg',
    'path' => '/var/tmp/filename.jpg',
    'error' => null,
    'status' => 'success'
]

Možnosti DropzoneHandler::upload()

$options = [
    'path'              => DIR . '/var/tmp/',        // Kde uložit soubor
    'maxFileSizeMB'     => 50,                       // Max velikost MB
    'paramName'         => 'dzFile',                 // Param jméno
    'allowedExtensions' => ['pdf', 'jpg', 'png'],  // Povolené koncovky
];

Poznámky

  • Soubory se nejdřív ukládají do dočasného adresáře /var/tmp/
  • Odtud si je kontroler zpracuje, zkrátí, uloží do finálního místa
  • Chunking je užitečný pro obcházení post_max_size omezení PHP
  • V DevTools konzoli se vypisují response odpovědi pro ladění