8. díl - Jednoduchý redakční systém v Nette - Dokončení administrace

PHP Nette Framework Základy Jednoduchý redakční systém v Nette - Dokončení administrace

V minulém tutoriálu o tvorbě jednoduchého redakčního systému v Nette frameworku jsme rozpracovali administraci článků. Dnes ji spolu s kontaktním formulářem dokončíme.

app/CoreModule/presenters/ContactPresenter.php

Nejprve přidáme ještě presenter pro kontaktní formulář:

<?php

namespace App\CoreModule\Presenters;

use App\Presenters\BasePresenter;
use Nette\Application\UI\Form;
use Nette\InvalidStateException;
use Nette\Mail\Message;
use Nette\Mail\SendmailMailer;
use Nette\Utils\ArrayHash;

/**
 * Zpracovává kontaktní formulář.
 * @package App\CoreModule\Presenters
 */
class ContactPresenter extends BasePresenter
{
        /** Email administrátora, na který se budou posílat emaily z kontaktního formuláře. */
        const EMAIL = 'admin@localhost.cz';

        /**
         * Vytváří a vrací komponentu kontaktního formuláře.
         * @return Form kontaktní formulář
         */
        protected function createComponentContactForm()
        {
                $form = new Form;
                $form->addText('email', 'Vaše emailová adresa')->setType('email')->setRequired();
                $form->addText('y', 'Zadejte aktuální rok')->setRequired()
                        ->addRule(Form::EQUAL, 'Chybně vyplněný antispam.', date("Y"));
                $form->addTextArea('message', 'Zpráva')->setRequired()
                        ->addRule(Form::MIN_LENGTH, 'Zpráva musí být minimálně %d znaků dlouhá.', 10);
                $form->addSubmit('submit', 'Odeslat');
                $form->onSuccess[] = [$this, 'contactFormSucceeded'];
                return $form;
        }

        /**
         * Funkce se vykonaná při úspěsném odeslání kontaktního formuláře a odešle email.
         * @param Form $form kontaktní formulář
         * @param ArrayHash $values odeslané hodnoty formuláře
         */
        public function contactFormSucceeded($form, $values)
        {
                try {
                        $mail = new Message;
                        $mail->setFrom($values->email)
                                ->addTo(self::EMAIL)
                                ->setSubject('Email z webu')
                                ->setBody($values->message);
                        $mailer = new SendmailMailer;
                        $mailer->send($mail);
                        $this->flashMessage('Email byl úspěšně odeslán.');
                        $this->redirect('this');
                } catch (InvalidStateException $ex) {
                        $this->flashMessage('Email se nepodařilo odeslat.');
                }
        }
}

Zde si povšimněte především použití Nette rozhraní pro odesílání e-mailů z formuláře.

app/config/config.neon

Pokud jste pozorně četli kód, všimli jste si, že u tvorby formulářů v metodě ->setRequired() není vyplněna chybová hláška. To je z toho důvodu, že nám bude stačit všude stejná a tu si globálně nastavíme v konfiguračním souboru připsáním následujících řádků:

# Nastavení výchozích chybových hlášek pro formuláře.
forms:
        messages:
                REQUIRED: 'Povinné pole.'

app/router/RouterFactory.php

Nakonec musíme ještě opět upravit routování, aby naše aplikace brala v potaz nové presentery a jejich akce. Také si zde ukáže příklad překladu českých URL adres na anglické názvy akcí.

<?php

namespace App;

use Nette\Application\Routers\Route;
use Nette\Application\Routers\RouteList;

/**
 * Routovací továrnička.
 * Řídí routování v celé aplikaci.
 * @package App
 */
class RouterFactory
{
        /**
         * Vytváří router pro aplikaci.
         * @return RouteList výsledný router pro aplikaci
         */
        public static function createRouter()
        {
                $router = new RouteList();
                $router[] = new Route('kontakt/', 'Core:Contact:default');
                $router[] = new Route('administrace/', 'Core:Administration:default');
                $router[] = new Route('[<action>/][<url>]', array(
                        'presenter' => 'Core:Article',
                        'action' => array(
                                Route::VALUE => 'default',
                                Route::FILTER_TABLE => array(
                                        // řetězec v URL => akce presenteru
                                        'seznam-clanku' => 'list',
                                        'editor' => 'editor',
                                        'odstranit' => 'remove'
                                ),
                                Route::FILTER_STRICT => true
                        ),
                        'url' => null,
                ));
                $router[] = new Route('[<url>]', 'Core:Article:default');
                return $router;
        }
}

Pokud se divíte, proč musí mít každý presenter svojí routu, tak je to z toho důvodu, že potřebujeme rozlišit mezi URL článku a názvem presenteru, protože obě mají stejný tvar - /presenter vs. /url. V tomto případě pak vždy dostane přednost presenter před vykreslením článku.

Šablony

Nyní se můžeme podívat zase na šablony (templates).

app/templates/@layout.latte

Zde pouze upravíme odkazy na jednotlivé presentery a jejich akce pomocí Latte maker a přidáme odkaz na administrační rozhraní. Nebudu sem vypisovat znovu celou šablonu, pouze provedené změny:

...
<ul>
        <li><a n:href=":Core:Article:">Úvod</a></li>
        <li><a n:href=":Core:Article:list">Seznam článků</a></li>
        <li><a n:href=":Core:Contact:">Kontakt</a></li>
</ul>
...
<footer>
        <p>Ukázkový tutoriál pro jednoduchý redakční systém v Nette z programátorské sociální sítě
        <a href="http://www.itnetwork.cz" target="_blank">itnetwork.cz</a>. <a n:href=":Core:Administration:">Administrace</a></p>
</footer>
...

app/CoreModule/templates/Article/list.latte

Přidáme šablonu pro výpis článků:

{define title}Výpis článků{/define}
{define description}Výpis všech článků.{/define}
{block content}
<table>
        <tr n:foreach="$articles as $article">
                <td>
                        <h2><a n:href=":Core:Article: $article->url">{$article->title}</a></h2>
                        {$article->description}
                        <br />
                        <a n:href=":Core:Article:editor $article->url">Editovat</a>
                        <a n:href=":Core:Article:remove $article->url">Odstranit</a>
                </td>
        </tr>
</table>

app/CoreModule/templates/Article/editor.latte

Nyní šablonu pro editor článků. Zde si povšimněte jak jednoduše jsme zařídili renderování formuláře a dále způsobu přidávání dalších JavaScript knihoven:

{define title}Editor{/define}
{define description}Editor článků.{/define}
{block content}
{* Formulář pro editaci. *}
{control editorForm}
{/block}

{block scripts}
{include parent}
<script type="text/javascript" src="//tinymce.cachefly.net/4.0/tinymce.min.js"></script>
<script type="text/javascript">
    tinymce.init({
        selector: "textarea[name=content]",
        plugins: [
            "advlist autolink lists link image charmap print preview anchor",
            "searchreplace visualblocks code fullscreen",
            "insertdatetime media table contextmenu paste"
        ],
        toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
        entities: "160,nbsp",
        entity_encoding: "named",
        entity_encoding: "raw"
});
</script>
{/block}

app/CoreModule/templates/Administration/default.latte

Dále následuje šablona pro administrační rozhraní:

{define title}Administrace webu{/define}
{define description}Administrace webu.{/define}
{block content}
<p>Vítejte v administraci!</p>
<h2><a n:href=":Core:Article:editor">Editor článků</a></h2>
<h2><a n:href=":Core:Article:list">Seznam článků</a></h2>

app/CoreModule/templates/Contact/default.latte

A na závěr šablona pro stránku s kontaktním formulářem:

{define title}Kontaktní formulář{/define}
{define description}Kontaktní formulář.{/define}
{block content}
<p>Kontaktujte nás odesláním formuláře níže.</p>
{* Formulář pro kontakt. *}
{control contactForm}
Stránka s kontaktním formulářem

Gratuluji, právě vám běží jednoduché administrační rozhraní pro články v Nette s kontaktním formulářem jako bonus. ;)

Nyní, v rámci seriálu, budeme dále pokračovat v rozšiřování administrace a začneme se věnovat zabezpečení našeho webu, což by mělo vyústit v plně funkční přihlašování a registraci uživatelů s definicí jejich práv, takže se určitě máte na co těšit. :) Konkrétně v příštím díle začneme rozšířením databáze a přidáním modelu uživatelů. ;)


 

Stáhnout

Staženo 258x (657.03 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

  Aktivity (1)

Článek pro vás napsal Jindřich Máca
Avatar
Autor se věnuje převážně webovým technologiím, ale má velkou zálibu ve všem vědeckém, nejen ze světa IT. :-)

Jak se ti líbí článek?
Celkem (6 hlasů) :
55555


 



 

 

Komentáře
Zobrazit starší komentáře (15)

Avatar
Milan Gallas
Redaktor
Avatar
Milan Gallas:

Ten řádek v routeFactory mě fakt potrápil

$router[] = new Route('[<action>/][<url>]', array(

Měl sem tam napsané

$router[] = new Route('[<action/>][<url>]', array(

a chybu jsem hledal asi hodinu :D :D
Ale spoň vím na co si dát příště pozor.

 
Odpovědět  +1 5.10.2015 18:14
Avatar
danhosek
Člen
Avatar
danhosek:

ahoj prosím o pomoc s RouteFactory.php přidal jsem do projektu Reference (-např.) ale nevím, jak tuto podstránku přidat do route tak, aby mi fungovala i administrace referencí i článků.
Děkuji za pomoc.

 
Odpovědět 26. března 17:44
Avatar
danhosek
Člen
Avatar
danhosek:

upravil jsem RouterFactory takto dle specifikací:

<?php

/*  _____ _______         _                      _
*ANOTACE
*/

namespace App;

use Nette\Application\Routers\Route;
use Nette\Application\Routers\RouteList;
/**
 * Routovací továrnička.
 * Řídí routování v celé aplikaci.
 * @package App
 */
class RouterFactory
{
        /**
         * Vytváří router pro aplikaci.
         * @return RouteList výsledný router pro aplikaci
         */
        public static function createRouter()
        {
                $router = new RouteList();
                $router[] = new Route('kontakt/', 'Core:Contact:default');
                $router[] = new Route('<action>/', array(
                        'presenter' => 'Core:Administration',
                        'action' => array(
                                // řetězec v URL => akce presenteru
                                Route::FILTER_TABLE => array(
                                        'administrace' => 'default',
                                        'prihlaseni' => 'login',
                                        'odhlasit' => 'logout',
                                        'registrace' => 'register'
                                ),
                                Route::FILTER_STRICT => true
                        )
                ));
        $router[] = new Route('reference/[<action>/][<url>]', array(
            'presenter' => 'Core:Reference',
            'action' => array(
                Route::VALUE => 'default',
                Route::FILTER_TABLE => array(
                    // řetězec v URL => akce presenteru
                    'reference' => 'list',
                    'editor' => 'editor',
                    'odstranit' => 'remove'
                ),
                Route::FILTER_STRICT => true
            ),
            'url' => null,
        ));
        $router[] = new Route('[<action>/][<url>]', array(
            'presenter' => 'Core:Article',
            'action' => array(
                Route::VALUE => 'default',
                Route::FILTER_TABLE => array(
                    // řetězec v URL => akce presenteru
                    'seznam-clanku' => 'list',
                    'editor' => 'editor',
                    'odstranit' => 'remove'
                ),
                Route::FILTER_STRICT => true
            ),
            'url' => null,
        ));
        $router[] = new Route('[<url>]', 'Core:Article:default');
        return $router;
    }
}

nyní je funkční přesměrování na výpis referencí i článků, ale reference se mi nevypisují
Ani administrace nefunguje. po pokusu uložit referenci s form action: /reference/editor/
jsem přesměrován na /chyba
Je vůbec možné upravit tento projekt tak, že jsem upravit article na reference a aby mi fungovala administrace k obojímu? Já si myslím, že je už problém pouze v RouterFactory, ale 100% si jistý nejsem. a lépe upravit router factory sám nedokážu.
Děkuji.

 
Odpovědět 26. března 22:42
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na danhosek
Jindřich Máca:

Ahoj,
vezmu tvůj komentář asi od konce. Co se týče psaní routování v Nette, není to úplně snadné. Doporučoval bych poctivě pročíst dokumentaci - https://doc.nette.org/cs/2.3/routing a potom se třeba ještě podívat na nějaké příklady např. http://zlml.cz/…te-prakticky. Obecný postup je pak následující:

  • Sepsat si všechny URL adresy, které má / může můj web obsahovat.
  • Seřadit adresy od nejkonkrétnějších po nejobecnější a to kvůli pořadí jejich zpracování.
  • Zkontrolovat, jestli se někde částečně nepřekrývají a pokud ano, je potřeba to napravit. Zkrátka každá URL adresa na webu musí být unikátní.
  • Vymyslet jak svůj seznam URL adres nejoptimálněji zapsat do routeru. K tomu je potřeba dobrá znalost všech jeho vlastností viz. odkazy výše uvedené.

Další Tvoje otázka byla, jestli je routování v projektu možné upravit Tvým specifickým způsobem. Určitě ano, ale budeš k tomu muset využít postup uvedený výše.

Abych Ti pomohl, dal jsem si tu práci a analyzoval jsem kód, který jsi uvedl, plus jsem v něm udělal pár úprav. ;) Vyložená chyba tam není, otázka zní, jestli to dělá to, co by jsi od toho očekával. Onu analýzu můžeš najít zde - http://www.itnetwork.cz/dev-lighter/703. Zkrátka zadaná adresa prochází seznam, přesně tak, jak jsem to očísloval a pokud se najde shoda, je zavolána akce presenteru s danými parametry.

Pokud je vše, jak má být, ještě bych ověřil pomocí laděnky (Tracy), že dané URL se opravdu mapují správně. Laděnka Ti při jejich zadání vypíše všechny podrobnosti. :)

Pokud vše souhlasí, chyba bude opravdu s největší pravděpodobností někde jinde, obzvlášť např. u toho odesílání formuláře bych ji nehledal v routeru. Bohužel, bez dalších zdrojových kódů projektu Ti nedokážu říct více.

Na závěr Ti popřeji hodně štěstí a pevné nervy! :D

 
Odpovědět  +1 28. března 22:30
Avatar
danhosek
Člen
Avatar
Odpovídá na Jindřich Máca
danhosek:

sepsal jsem si ty odkazy na webu a zjistil jsem problem u editoru článků a editoru referencí. Podle Url nelze zjistit, zda jde o referenci nebo článek:
/editor/ //Editor článku

/editor/ //uložení nového článku
editor/clanek1 //odkaz na editaci čánku + uložení změn článku
/odstranit/clanek1 //smazání článku
u reference ty odkazy vypadají stejně.
Mydlim, že tento problem by vyřešily změny v routovacím poly:
$router[] = new Route('[<acti­on>/][<url>]', array(
'presenter' => 'Core:Reference',
'action' => array(
Route::VALUE => 'default',
Route::FILTER_TABLE => array(
// řetězec v URL => akce presenteru
'reference' => 'list',
'editor_ref' => 'editor',
'odstranit_ref' => 'remove'
),
Route::FILTER_STRICT => true
),
'url' => null,
));
Stačilo by to? nebo by to nerozeznalo, zda jde o referenci nebo článek?

 
Odpovědět 29. března 22:17
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na danhosek
Jindřich Máca:

Ahoj,
díky tomu, že tam máš Route::FILTER_STRICT => true, což znamená, že to bere pouze explicitně uvedené routy a nic jiného (pak to pokračuje dál), by tohle mělo stačit. :) Ostatně, proč to nevyzkoušíš sám? ;)

 
Odpovědět 29. března 22:35
Avatar
danhosek
Člen
Avatar
danhosek:

Ahoj, jak mohu v ContactPresenter nastavit formuláři id nebo class? abych docílil tohodle? <form id="formular" class="formular">?? nebo lze definovat v latte element div? Abych mohl mít něco podobného?

<div class="formular">
    {define title}Kontaktní formulář{/define}
    {define description}Kontaktní formulář.{/define}
    {block content}
    <p>Kontaktujte nás odesláním formuláře níže.</p>
    {* Formulář pro kontakt. *}

                {control contactForm}
</div>

hledal jsem v dokumentacích a forech ale nic jsem nenašel. Všede se řeší pouze set atrubute u inputů.
Děkuji.

 
Odpovědět 18. října 17:51
Avatar
danhosek
Člen
Avatar
Odpovídá na danhosek
danhosek:

nebo v latte {define title}Kontaktní formulář{/define} -> h1.. můžu tomu naspisu nastavit id nebo class?

 
Odpovědět 18. října 17:58
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na danhosek
Jindřich Máca:

No nazdar! :D

Pokud chceš v HTML přiřadit elementu, jako např. div, id nebo class, je to v pohodě. Ale to samotné použití div v uvedeném příkladu Latte šablony je špatné ve všech směrech. Ta šablona definuje 3 nezávislé bloky, konkrétně title, description a content, jejichž obsah se dosadí do celkové šablony layoutu. Pokud to takhle obalíš tím div, tak jeho první část se vůbec nepoužije a ta druhá </div> se vloží do bloku content a ve výsledné HTML struktuře nadělá kompletní paseku.

Tohle bylo spíš varování s vysvětlením, aby jsi Ty, ani nikdo jiný, tohle v kódu nedělal a teď k odpovědi na otázku. Formuláři se class dá nastavit poměrně jednoduše pomocí parametru:

{control contactForm, class => 'formular'}

Zato id mu automaticky generuje Nette a to bys nejlépe měnit vůbec neměl. Naopak by ses tomu měl podřídit, jelikož se podle toho provádí např. i mapování odeslaných hodnot. Zkrátka Nette formuláře mají vždy vlastní generované id a neměl by jsi to měnit, pokud přesně nevíš, co děláš. ;)

A k té druhé otázce ohledně nastavení id nebo class u definice bloku. Ne, tady se to přímo nijak nastavit nedá, jak už jsem psal, ten obsah toho bloku se celý vezme a vloží na příslušné místo do celkového layoutu. Ten blok samotný ale není reprezentovaný žádným HTML elementem, takže mu logicky nejdou přiřadit žádné HTML vlastnosti. :)

Obecně, pokud by jsi chtěl měnit třídu u nadřazeného elementu daného bloku, musel by jsi to udělat jedině nějakou volitelnou hodnotou, mimo ten blok, která by se dosazovala na správné místo v šabloně nadřazené.

Doufám, že to takhle vysvětlené dává smysl a že v tom najdeš odpovědi, které jsi hledal, když tak se hold budeš muset ptát dále.

 
Odpovědět  +1 19. října 23:26
Avatar
danhosek
Člen
Avatar
Odpovídá na Jindřich Máca
danhosek:

děkuji za odpověď, je to přesně to co jsem potřeboval..
jinak, když jsem čekal na odpověď, zkoušel jsem různá řešení a zkusil toto:

<div class="kontaktni_formular">
    {define title}Kontaktní formulář{/define}
    {define description}Kontaktní formulář.{/define}
    {block content}
    <p>Kontaktujte nás odesláním formuláře níže.</p>
    {* Formulář pro kontakt. *}
      <div id="form-main">
        <div id="form-div">
            <div class="container">

                {control contactForm}
            </div>
        </div>
    </div>
</div>

<div class="kontak­tni_formular"> se opravdu nevipisoval, ale <div id="form-main">, <div id="form-div">, <div class="container"> , které jsou už v bloku {block content}, tak jsem na toto přišel metodou pokus omyl sám.(ten pokus/omyl slyšíš nerad co?):-D

 
Odpovědět  +1 20. října 0:46
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 25. Zobrazit vše