Upload souboru na server a uložení cesty do db

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

Hoj, snažím se o upload souboru na server a následné uložení cesty k souboru a popisu souboru do db.
Zatím mám toto: formulář pro upload

protected function createComponentAkceForm() {
        $form = new Form();
        $folder = $this->getParam('folder');

        $form->addUpload('img', 'Soubor:')
                //->addRule(Form::MIME_TYPE, 'Přílohou musí být obrázek formátu .jpg .', 'image/jpeg') ;
                ->addRule(Form::IMAGE, 'Soubor musí být JPEG, PNG nebo GIF.');
        // ->addRule(Form::MAX_FILE_SIZE, 'Maximální velikost souboru je 5 MB.', '50000');
        $form->addDatePicker('datum', 'Datum: ', 5);

        $form->addText('popis', 'Popis: ', 50)
                ->addRule(Form::FILLED, 'Je nutné zadat informace o akci.');

        $form->addHidden('folder', $folder);

        $form->addSubmit('create', 'Nahrát');
        $form->addSubmit('cancel', 'Zrušit')
                ->setValidationScope(FALSE);

        $form->onSuccess[] = callback($this, 'akceFormSubmitted');

        return $form;
    }

a následná obsluha

public function akceFormSubmitted(Form $form) {

        if ($form->isSubmitted()) {
            if ($form['cancel']->isSubmittedBy()) {
                // process cancelled
                $this->flashMessage('Nahrávání zrušeno.', 'notice');
                $this->redirect('this');
            }
            if ($form['create']->isSubmittedBy() && $form->isValid()) {
                // submitted and valid
                $values = $form->getValues();
                /*
                 * Kontrola, zda-li byl obrazek skutecne nahran
                 */
                if ($values['img']->isOk()) {

                } else {
                    $this->flashMessage('Obrázek se nezdařilo nahrát na server.', 'warning');
                }
                dump($values['img']->isOk());
            }
        }

    }

Obsluha se mě zastaví o dump, což je v pořádku. Teď ale už moc nevím jak zajistit, aby se mě soubor nahrál do požadované složky, která bude umístěna ve „www/images/slozka“ a cesta k němu se uložila do db. Ze které budu potom budu tahat cestu pro zobrazení souboru (bude to pouze obrázek). Chtěl bych tímto tedy požádat o jakékoli rady, jak na to? předem díky

duke
Člen | 650
+
0
-

$form->values['img'] bude obsahovat instanci objektu Nette\Http\FileUpload (viz api). Nejvíc tě asi budou zajímat metody isOk() a move($dest).

Btw pokud se nemýlím, tak řádek:

if ($form->isSubmitted()) {

… je nadbytečný, neboť to již řeší „onSuccess“.

Editoval duke (31. 3. 2012 17:29)

.:M@rt!n:.
Člen | 201
+
0
-

Nebyla by prosím nějaká praktická ukázka?? Z toho api nejsem moc chytrý…

duke
Člen | 650
+
0
-

Volání isOk už ve svém kódu máš, takže nějak takto:

if ($values['img']->isOk()) {
	$filename = $values['img']->getSanitizedName();
	$targetPath = $this->getImagesDir();
	if ($values['folder'] !== '') {
		$targetPath .= "/$values[folder]";
	}
	// @TODO vyřešit kolize
	$values['img']->move("$targetPath/$filename");

	// uložení do db, např.:
	$this->db->table('images')->insert(array(
		'file' => "$targetPath/$filename",
		'description' => $values['popis'],
	));
} else {

Příklad nicméně neřeší možné kolize souborů, ale to už je jiný problém, na který ses neptal.

EDIT: Ještě by tam měla být kontrola položky $values['folder'] zda obsahuje povolené hodnoty, aby ti tam náhodou někdo nepodstrkával např. '../../../neco' nebo jména neexistujících složek.

Editoval duke (31. 3. 2012 21:43)

.:M@rt!n:.
Člen | 201
+
0
-

Tak už jsem upload i uložení do db vyřešil.
Kód zpracování formuláře:

public function akceFormSubmitted(Form $form) {
        if ($form['save']->isSubmittedBy()) {
            $id = (int) $this->getParameter('id');
            $file = $form['obrazek']->getValue();

            $imgUrl = $this->context->params['wwwDir'] . '/images/upload/' . $file->name;

            $file->move($imgUrl);


            if ($id > 0) {
                $this->context->createAkce()->find($id)->update(array(
                    'datumOd' => $form->values->datumOd,
                    'datumDo' => $form->values->datumDo,
                    'popis' => $form->values->text,
                    'url' => '/images/upload/' . $file->name
                ));

                $this->flashMessage('Akce upravena.', 'success');
            } else {
                $this->context->createAkce()->insert(array(
                    'datumOd' => $form->values->datumOd,
                    'datumDo' => $form->values->datumDo,
                    'popis' => $form->values->text,
                    'url' => '/images/upload/' . $file->name
                ));

                $this->flashMessage('Upload zrušen.', 'success');
            }
            //$this->flashMessage($dest);

            $this->redirect('default');
        }
    }

Uploadnutý obrázek mám přiřazený k akci, ale teď bych zase potřeboval, aby se mě ten obrázek ze složky, po smazaní akce, smazal taky.
Kód zpracovaní formuláře pro smazání z db:

public function deleteFormSubmitted(Form $form) {
       if ($form['delete']->isSubmittedBy()) {
           $this->akce->find($this->getParameter('id'))->delete();
           $this->flashMessage('Položka smazána.');



           }

       $this->redirect('default');
   }

snažil jsem se zase získat adresu uloženého obrázku a podle ní pak obrázek smazat, ale zatím neúspěšně. Jak by se dalo vyřešit?

duke
Člen | 650
+
0
-

Záznam z databáze si načteš pomocí metody fetch.

$akce = $this->context->createAkce()->find($id)->fetch();

Pak můžeš smazat soubor přes:

if ($akce) {
	unlink($this->context->params['wwwDir'] . '/images/upload/' . $akce->url);
}

Nicméně ještě několik výhrad:

  • Nevím proč jsi odstranil volání isOk. To by tam určitě mělo zůstat.
  • Opakuji, že $form['save']->isSubmittedBy() je nejspíš nadbytečné, pokud tedy nemáš víc submitovacích buttonů, které řešíš všechny metodou akceFormSubmitted.
  • Neřešíš kolize v názvech souborů. (mohlo by stačit místo $file->name použít $id . '_' .$file->name)
  • Flash message „Upload zrušen.“ lže, má tam být „Akce vytvořena.“.

Editoval duke (3. 4. 2012 20:29)

.:M@rt!n:.
Člen | 201
+
0
-

Takže metoda pro smazání by mohla vypadat nějak takto?

public function deleteFormSubmitted(Form $form) {
    $this->akce->find($this->getParameter('id'))->delete();
    $akce = $this->context->createAkce()->find($id)->fetch();
    if ($akce) {
        unlink($this->context->params['wwwDir'] . '/images/upload/' . $id . '_' . $file->name);
    }

    $this->flashMessage('Položka smazána.');


    $this->redirect('default');
}
duke
Člen | 650
+
0
-

Ne, to tedy nemohla.

  • nemáš nastavenou proměnnou $id a $file
  • proměnnou $akce se snažíš naplnit z databáze, až když jsou data v databázi smazána

Takže např. nějak takto:

public function deleteFormSubmitted(Form $form) {
	$id = $this->getParameter('id');
	// případně mít id v hidden inputu formuláře:
	// $id = $form['id']->value;
	if ($id) {
		$akce = $this->context->createAkce()->find($id)->fetch();
		if ($akce) {
			unlink($this->context->params['wwwDir'] . '/images/upload/' . $id . '_' . $akce->url);
			$akce->delete();
			$this->flashMessage('Položka smazána.');
		} else {
			$this->flashMessage('Tato akce již neexistuje.', 'error');
		}
	} else {
		throw new Nette\Application\BadRequestException('No id given for delete.');
	}

	$this->redirect('default');
}

A samozřejmě musíš to řešení kolizí (přes prefix $id_) přidat i do uploadovacího kódu.

Senik
Člen | 4
+
0
-

Mám dotaz ke zde řešenému, odpověď jsem hledal všude možně, ale nenašel. Do databáze přidávám inzeráty a při přidávání uploaduju obrázek, ten ale potřebuju ve složce odlišit podle IDčka toho inzerátu (to má v DB nastavený Auto Increment), dá se nějakým způsobem zjistit ID právě přidávaného záznamu do DB? Při úpravě inzerátu to je v pohodě, protože to ID už znám, ale nevím si rady, jak to zjistit při vytváření. Je vůbec něco takovýho možný?

Editoval Senik (7. 4. 2012 18:16)

Filip Procházka
Moderator | 4668
+
0
-

Je problém, nejprve uložit inzerát, zeptat se na jeho ID, pojmenovat soubor a pak upravit záznam v databázi? Jako bonus můžeš vynechat poslední krok, protože stejně víš, jak se souboru bude jmenovat.

Editoval HosipLan (7. 4. 2012 18:50)

duke
Člen | 650
+
0
-

Možná nevíš, že metoda insert pro vkládání záznamů do databáze vrací instanci ActionRow představující právě vložený řádek (obsahuje všechny původně vkládané hodnoty + primární klíč). Takže můžeš udělat (v duchu toho, co ti už napsal HospiLan) následující:

$inserted = $this->db->table('images')->insert(array(
	'description' => $values['popis'],
));

$filename = $inserted['id'] . '_' . $filename;

// nyní buď:
$inserted['file'] = "$targetPath/$filename";
$inserted->update();
// nebo by mělo jít také:
// $inserted->update(array('file' => "$targetPath/$filename"));

Nebo se prostě smířit s tím, že v databázi uložíš název souboru bez prefixu a id k němu budeš přidávat až dodatečně (pak nepotřebuješ ten následný update, jak správně poznamenal HospiLan). Pokud zvolíš cestu toho následného update, měl bys operace insert a update provést v transakci, aby jiný skript nenačítal napůl uložený záznam.

.:M@rt!n:.
Člen | 201
+
0
-

Trochu jsem musel upravit upload souboru a teď se mě nevypisuje hláška, když chci na uploadovat jiný soubor než obrázek. Obsluha formu:

public function akceFormSubmitted(Form $form) {

        $id = (int) $this->getParameter('id');

        if ($id > 0) {
            $this->context->createAkce()->find($id)->update(array(
                'datumOd' => $form->values->datumOd,
                'datumDo' => $form->values->datumDo,
                'popis' => $form->values->popis
            ));

            if ($form->values->agree) {
                $file = $form['obrazek']->getValue();
                $imgUrl = $this->context->params['wwwDir'] . '/images/upload/' . $id . '_' . $file->name;
                $file->move($imgUrl);

                $this->context->createAkce()->find($id)->update(array(
                    'url' => '/images/upload/' . $id . '_' . $file->name
                ));
            }
            $this->flashMessage('Akce upravena.', 'success');
        } else {
            $inserted = $this->context->createAkce()->insert(array(
                'datumOd' => $form->values->datumOd,
                'datumDo' => $form->values->datumDo,
                'popis' => $form->values->popis
                    ));
            $newid = $inserted->id;

            if ($form->values->agree) {
                $file = $form['obrazek']->getValue();
                $imgUrl = $this->context->params['wwwDir'] . '/images/upload/' . $newid . '_' . $file->name;
                $file->move($imgUrl);
                $this->context->createAkce()->find($newid)->update(array(
                    'url' => '/images/upload/' . $newid . '_' . $file->name
                ));
            }
            $this->flashMessage('Akce přidána.', 'success');
        }
    }

U vytvoření formuláře mám samozřejmě nastaveno

addRule(Form::IMAGE, 'Soubor musí být obrázek - .jpg, .gif, .png')

Už nevím, kde by mohl být problém?

jtousek
Člen | 951
+
0
-

V tom obslužném kódu akceFormSubmitted chyba nebude. Ta metoda se nemá vůbec co spouštět pokud neprošla validace formuláře. Co přesně ti to dělá? Validace projde, přestože by neměla nebo validace neprojde, ale hláška se nezobrazí?

EDIT: Mimochodem doporučuji nemíchat češtinu s angličtinou a zavést konvenci. ;-)

Editoval jtousek (7. 5. 2012 20:53)

.:M@rt!n:.
Člen | 201
+
0
-

Ano, validace projde (respektive neprojde,jde o to jak se na to díváš), ale nezobrazují se hlášky. Čili chci nahrát něco jiného než obrázek a při potvrzení mě to do db nic neuloží a odkáže mě to na vybrání jiného souboru, ale nevyskočí hláška, že to musí být jpg.
Celý formulář je takto:

protected function createComponentAkceForm() {
        $form = new Form();
        $presenter = $this;

        $form->addCheckbox('agree', '  Přidat nebo změnit obrázek')
                ->addCondition($form::EQUAL, TRUE)
                ->toggle('newPic');
        $form->addGroup()
                ->setOption('container', \Nette\Utils\Html::el('td')->id('newPic'));
        $form->addUpload('obrazek', 'Obrázek:')
                ->addConditionOn($form['agree'], $form::EQUAL, TRUE)
                ->addRule(Form::IMAGE, 'Soubor musí být obrázek - .jpg, .gif, .png')
                ->addRule(Form::MAX_FILE_SIZE, 'Soubor je příliš velký! Povolená velikost je 2M.', 2 * 1024 * 1024);
        $form->addDatePicker('datumOd', 'Datum od: ', 5);

        $form->addDatePicker('datumDo', 'Datum do: ', 5);

        $form->addTextArea('popis', 'Popis akce:')
                ->getControlPrototype()->class('mceEditor');



        $form->addSubmit('save', 'Uložit')->setAttribute('class', 'default');
        $form->addSubmit('cancel', 'Zrušit')
                        ->setValidationScope(FALSE)
                ->onClick[] = function () use ($presenter) {
                    $presenter->redirect('default');
                };



        $form->onSuccess[] = callback($this, 'akceFormSubmitted');
        $form->addProtection('Vypršel časový limit, odešlete formulář znovu.');

        return $form;
    }

Mám to udělaný tak, že po zaškrtnutí toho checkboxu se mě zobrazí pole pro upload.

Editoval .:M@rt!n:. (7. 5. 2012 21:16)

vvoody
Člen | 910
+
0
-

Ked tam teda vlozis napriklad pdf a odosles tak to pdf aj najdes na serveri v /images/upload/ ?

.:M@rt!n:.
Člen | 201
+
0
-

Nn, to ne. Když tam dám například pdf a dám odeslat, tak se mě vybraný soubor z formuláře smaže a musim vybrat jiný. Ale nevyhodí mě to hlášku, že ten prvni vybraný není formát obrázku.

vvoody
Člen | 910
+
0
-

renderujes cez {form } alebo {control }?

.:M@rt!n:.
Člen | 201
+
0
-

přes {form}:

{form akceForm}
    <table>
        <tr>
            <td colspan="2">{input agree} Přidat nebo změnit obrázek</td>
        </tr>
        <tr id="newPic">
            <td>{label obrazek/}</td>
            <td> {input obrazek}</td>
        </tr>
        <tr>
            <td>{label datumOd/}</td>
            <td>{input datumOd}</td>
            </tr>
        <tr>
            <td>{label datumDo/}</td>
            <td>{input datumDo}</td>
        </tr>

        <tr>
            <td>{label popis/}</td>
            <td> {input popis}</td>
        </tr>


        <tr>
            <td>{input save} {input cancel}</td>
        </tr>
    </table>
{/form akceForm}
vvoody
Člen | 910
+
0
-

Dobre som typoval :) ten formular sa ti validuje spravne, len ty cakas javascriptovu hlasku, ale mime type sa asi kontroluje len na serverovej strane. Problem je v tom ze ty nevypisujes errory zachytene na strane serveru.
https://doc.nette.org/cs/forms#…

<ul class="errors" n:if="$form->hasErrors()">
	<li n:foreach="$form->errors as $error">{$error}</li>
</ul>
.:M@rt!n:.
Člen | 201
+
0
-

Jo díky,to jsem nějak přehlédl. Ale teď mě to zvaliduje pouze, jestli je to obrázek. Ale když nahraji obrázek větší než 2MB, tak to nevypíše hlášku: „Soubor je příliš velký! Povolená velikost je 2MB.“ ale tu první: „Soubor musí být obrázek – .jpg, .gif, .png“ která je definovaná u kontroly, zda je to obrázek…

vvoody
Člen | 910
+
0
-

skus prehodit poradie tych kontrol

Editoval vvoody (7. 5. 2012 22:22)

.:M@rt!n:.
Člen | 201
+
0
-

Nepomohlo, stále stejný. U obou vypisuje stejnou hlášku.

vvoody
Člen | 910
+
0
-

to bol len taky pokus :) ale uz ma nic nenapada :( snad niekto iny

.:M@rt!n:.
Člen | 201
+
0
-

Tak nevím čim to je, ale po nahrání webu na server to vypíše správnou hlášku.