Upload více souborů, file[]

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
netrunner
Člen | 7
+
0
-

Přečetl jsem si tady na fóru hodně příspěvků o tom, že pro formulářové políčku typu upload nelze použít jméno se [] (např. image[]). Tím pádem se z jednoho políčka nevydoluje víc, než jeden soubor. A nějak cyklem procházet přes jména polí (image1, image2, …) mi přijde neohrabané.

Protože sám píšu aplikaci, která potřebuje nahrávat 0-X souborů zaráz, a protože podpora atributu multiple u nahrání souboru je v prohlížečích už solidní, hodně mi to překáželo. Rozhodl jsem se, že si přípíšu komponentu MultipleUpload, která bude defacto kopírovat chování komponenty Upload z nette formulářů.

Co funguje

  • nahrání jednoho souboru
  • nahrání více souborů z jednoho pole z prohlížečů s podporou (vše krom IE)
  • všechny validační procedury by měly fungovat
  • pravidlo isFilled kontroluje, zda existuje alespoň jeden soubor

Co nefunguje

  • vložení více polí se stejným názvem (nette se brání více komponentám se stejným jménem) – snad to jde obejít přes javascriptové rozšížení formuláře, pak by to mělo nahrát více souborů i v IE
  • pokud jeden soubor neprojde některým pravidlem, zahodí se všechny a nelze zjistit, který konkrétní sobour neprošel. Jen se standardně vypíše proč.

Jaký je výsledek

  • ve formuláři se objeví input s názvem doplněným o [] a atributem multiple
  • v poli formulářových dat je položka s názvem políčka (např. image), která obsahuje pole s jednotlivými uploady, a to i při jednom souboru.
<input type="file" class="text" name="image[]" id="..." data-nette-rules="..." multiple="multiple" />

Nette\ArrayHash(8) {
   description => "Text" (4)
   image => array(1) [
      0 => Nette\Http\FileUpload(5) {
         name private => "bw_hightrot.gif" (15)
         type private => "image/gif" (9)
         size private => 1628
         tmpName private => "C:\Windows\Temp\php989E.tmp" (27)
         error private => 0
      }
   ]
}
<?php
/*
* File MultiUpload.php
*/

namespace Nette\Forms\Controls;

use Nette,
		Nette\Http;

class MultiUploadControl extends UploadControl {

	public function getHtmlName() {
		return parent::getHtmlName() . '[]';
	}

	public function getControl() {
		return parent::getControl()->multiple(TRUE);
	}

	public function setValue($value) {
		foreach($value as $single) {
			if (is_array($single)) {
				$this->value[] = new Http\FileUpload($single);
			} elseif ($single instanceof Http\FileUpload) {
				$this->value[] = $single;
			} else {
				$this->value[] = new Http\FileUpload(NULL);
			}
		}
		return $this;
	}

	public function isFilled() {
		return $this->value[0] instanceof Http\FileUpload && $this->value[0]->isOK();
	}

	public static function validateFileSize(UploadControl $control, $limit)
	{
		$files = $control->getValue();
		foreach($files as $file) {
			if($file instanceof Http\FileUpload && $file->getSize() <= $limit)
				{}
			else
				return false;
		}

		return true;
	}

	public static function validateMimeType(UploadControl $control, $mimeType)
	{
		$files = $control->getValue();
		foreach($files as $file) {
			if ($file instanceof Http\FileUpload) {
				$type = strtolower($file->getContentType());
				$mimeTypes = is_array($mimeType) ? $mimeType : explode(',', $mimeType);
				if (!in_array($type, $mimeTypes, TRUE)) {
					return FALSE;
				}
				if (!in_array(preg_replace('#/.*#', '/*', $type), $mimeTypes, TRUE)) {
					return FALSE;
				}
			}
			return FALSE;
		}
		return TRUE;
	}

	public static function validateImage(UploadControl $control) {
		$files = $control->getValue();
		foreach($files as $file) {
			if($file instanceof Http\FileUpload && $file->isImage())
				{}
			else
				return false;
		}

		return true;
	}
}
?>

Aby to všechno fungovalo, je potřeba uložit soubor MultiUpload.php někam, kde jej najde autoloader, a někde (třeba v basepresenteru) ještě ke třídě Nette\Application\UI\Form připojit metodu addMultiUpload:

\Nette\Application\UI\Form::extensionMethod('addMultiUpload', function(\Nette\Application\UI\Form $form, $name, $label = NULL) {
	$form[$name] = new \Nette\Forms\Controls\MultiUploadControl($label);
	return $form[$name];
});

Poté už u libovolného formuláře stačí přidat input, např.:

$form->addMultiUpload("image", "Obrázek")->addRule(Form::IMAGE, "Lze vložit pouze obrázky");

Nezaručuju, že bude vše fungovat dokonale. Zkoušel jsem nahrávat soubory a základní validační pravidla pro vyplnění, typ a velikost. Pokud někdo najde nějakou nefunkčnost, klidně mě doplňte, rád si to upravím.

Postesk na závěr
Škoda, že to Nette neumí samo. Přitom MultiSelect je v podstatě stejná situace a je dostupný.

Editoval netrunner (5. 11. 2012 13:08)

ic
Člen | 430
+
0
-

Tohle je super, něco tak užitečného rozhodně vyzkouším.

Felix
Nette Core | 1245
+
0
-

Nezaručuju, že bude vše fungovat dokonale. Zkoušel jsem nahrávat soubory a základní validační pravidla pro vyplnění, typ a velikost. Pokud někdo najde nějakou nefunkčnost, klidně mě doplňte, rád si to upravím.

Postesk na závěr
Škoda, že to Nette neumí samo. Přitom MultiSelect je v podstatě stejná situace a je dostupný.

Hod to na github, at tam muzeme kdyztak napsat nejaky issue nebo to forknout.
Zatim jsem si to jen zkopcil, upravil par veci, ktery se mi tam libi a doplnil easy phpDoc. Je to zde.

mildabre
Člen | 62
+
0
-

Není lepší místo extensionMethod pro třídu Form v bootstrap.php podědit novou třídu betterForm a tam metodu přidat ? Jistě se najdeou další oblasti, kde si člověk bude chtít vylepšit základní Nette Form a „mastit“ to v bootstrapu.php by bylo nepřehledné. Jinak to funkčně bude totéž, jde jen o dodržení „best practises“

Jan Tvrdík
Nette guru | 2595
+
0
-

@netrunner: extension method by se neměla vázat na form ale na formcontainer

Re4DeR
Člen | 71
+
0
-
$form->addMultiUpload("images", "Images");
	->addRule(Form::IMAGE, "Only images");

mi neprojde validaci pokud nezvolim k nahrani aspon jeden obrazek.

$form->addMultiUpload("images", "Images");

mi projde, ale nahraje cokoliv.

EDIT:
vypada ze funguje_

	$form->addMultiUpload("images", "Images")
			->addCondition(Form::FILLED)
			->addRule(Form::IMAGE, "Only images")

Editoval Re4DeR (7. 5. 2013 16:09)

mrfazolka
Člen | 24
+
0
-

perfektní, chtěl byh říct moc dík :)

Ekimik
Člen | 6
+
+1
-

Zdravím,
stáhnul jsem si z GitHubu verzi MultiUploadControl, kterou tam uveřejnil @Felix. Možná to sem nepatří (spíš asi report jako issue nebo pull request na GitHub), ale v mém případě bylo nutné upravit hodnotu jedné z konstant ve třídě MultiUploadControl, konkrétně MAX_TOTAL_SIZE z hodnoty :totalSize na :maxTotalSize. Úprava byla nutná, jelikož Nette vyhazovalo výjimku kvůli volání nedefinované statické metody validatetotalSize(), přičemž ona metoda, která se má volat se jmenuje validateMaxTotalSize(). Věřím, že na to snadno přišel každý, kdo potřeboval tuto třídu použít, tento příspěvek přidávám jen pro ušetření času ostatním.