Překlad id na uri a zpět + jazykové mutace

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

Zdravím,

potýkám se s problémem, kdy se snažím na webu co nejvíce zjednodušit url z pohledu uživatele a zároveň mít url vícejazyčné. Aplikaci píši modulárně na PHP 5.3, používám vlastní Translator, vše se maximálně cachuje.

Problém nastává u PageModule, který si své routy nastavuje takhle:

public function setupRoutes($module, IRouter $router) {
    // Naroutovani frontendu modulu.
    $router[] = $moduleRouter = new MultiRouter('Page');
    $moduleRouter[] = new Route('<lang>/<id>/', array(
                'presenter' => 'Default',
                'action' => 'default',
                'id' => array(
                    Route::FILTER_IN => callback('PageModule::uriToId'),
                    Route::FILTER_OUT => callback('PageModule::idToUri'),
                ),
            ));

    // Vrati se zmodifikovany router.
    return $router;
}

Callbacky jsou definovány následovně:

static function uriToId($uri) {
    $lang = Environment::getService('Translator')->getLang();

    // Cachovani url pro stranky.
    $cache = Environment::getCache('PageRoutes');
    if (!is_array($cache[$lang])) {
        // Neni-li cache zalozena, predpripravi se pole.
        $cache[$lang] = array();
    }

    // Zjisti se, zda se id odpovidajici url.
    $id = array_search($uri, $cache[$lang]);
    if ($id === FALSE) {
        // Id se cachi nenachazi, koukne se do databaze.
        $id = dibi::fetchSingle('SELECT [pageLang.idPage]
                                     FROM [pageLang]
                                     WHERE [pageLang.uri] = %s', $uri);
        if (!empty($id)) {
            // Cachuji se jen validni adresy.
            $cache[$lang] += array(
                $id => $uri,
            );
        }
    }

    // Nebylo-li id nalezeno, prejde se na Error presenter.
    if (empty($id)) {
        throw new BadRequestException('error:pageNotFound', 404);
    }

    // Vrati se nalezene id.
    return $id;
}

static function idToUri($id) {
    $lang = Environment::getService('Translator')->getLang();

    // Cachovani url pro stranky.
    $cache = Environment::getCache('PageRoutes');
    if (!is_array($cache[$lang])) {
        // Neni-li cache zalozena, predpripravi se pole.
        $cache[$lang] = array();
    }

    if (!isset($cache[$lang][$id])) {
        // Id v cachi nebylo nalezeno, podiva se do databaze.
        $uri = dibi::fetchSingle('SELECT [pageLang.uri]
                                  FROM [pageLang]
                                  WHERE [pageLang.idPage] = %i
                                  AND [pageLang.lang] = %s', $id, $lang);

        if (!empty($uri)) {
            // Cachuji se jen validni uri.
            $cache[$lang] += array(
                $id => $uri,
            );
        }
    }

    return $cache[$lang][$id];
}

Problém se projevuje hlavně u přepínacích vlaječek na jazyk, ale očekávám, že se projeví u jakýchkoliv odkazů, které směřují na jinou jazykovou mutaci, než která je výchozí. Vlaječky jsou nadefinovány takto:

<!-- FLAGS START-->
<span id="flagEn" class="flag{ifCurrent this 'lang' => 'en'} langActive{/ifCurrent}"><a href="{plink this 'lang' => 'en'}" title="English"></a></span>
<span id="flagCs" class="flag{ifCurrent this 'lang' => 'cs'} langActive{/ifCurrent}"><a href="{plink this 'lang' => 'cs'}" title="Česky"></a></span>
<!-- FLAGS STOP -->

Jsem-li na stránkce „cs/kontakt“, vygenerují se vlajčky vedoucí na „cs/kontakt“ a „en/kontakt“, když se na anglickou vlajčku kliknu, url se sama „nějak“ přesměruje na „en/contact“. Potřeboval bych proto v callbacku pro generování odkazů znát cílový jazyk, na který odkaz směřuje. Nyní se tam vkládá jazyk, který je aktuálně zvolený. S tím souvisí problém, kdy někdy dojde k zacachování špatné jazykové mutace překladu.

S tím souvisí můj druhý dotaz – jde v této fázi nějak inteligentně zjistit, se kterým jazykem se zrovna pracuje? Aktuálně to tam zjišťuji parsováním url, ale to není zrovna optimální způsob. V období kolem Vánoc jsem na to nějak přišel, že jsem jazyk četl v bootstrapu nějakou metodou pro čtení persisteních parametrů, ale ten kód jsem ztratil a nedaří se mi to vymyslet znovu. Možná je to nějakou změnou v Nette.

Mockrát děkuji. Budu vděčný za jakoukoliv radu, jak to efektivně řešit.

Filip Procházka
Moderator | 4668
+
0
-

a co takhle nějak?

//edit: kód routeru přesunut na gist: SmarterRoute

a použiješ to takhle nějak

$router[] = $route = new SmarterRoute('<presenter>/<action>', array(...));
$route->addFilter('id', 'PageModule::uriToId', 'PageModule::idToUrl');

a s tím že do filteru se ti předá jako druhý argument Nette\Application\PresenterRequest, v původním tvaru.

static function uriToId($uri, Nette\Application\PresenterRequest $request)
{
	$translator = Nette\Environment::getService('ITranslator');
	$lang = $request->params['lang']; // zkratka na $request->getParams()

	// dál už to znáš :)

	return $id;
}

Psal jsem to z hlavy, asi tam bude pár chybek potřeba doladit :)

Editoval HosipLan (13. 2. 2011 19:04)

Dimitry
Člen | 11
+
0
-

Oh, mockrát děkuji. Opravdu funguje na výbornou. Pouze jsem musel upravit pár drobností. Kdyby to chtěl někdo použít, tak změny jsem udělal v metodě doFilterParams:

  • $param se omylem změnil na $name
  • přidal jsem jí další parametr PresenterRequest $request, který se předává callbackům, je pak třeba upravit volání v match a constructUrl

A díky tomuhle budu moci překládat i jména modulů, presenterů a akcí závisle na jazyce. Skvělé :-).

Pouze se mi ještě objevuje problém pokud při generování odkazů, když není nalezeno odpovídající uri, protože článek v daném jazyce neexistuje. Pak se vytvoří odkaz ve tvaru „/en/page.default/?id=3“. Nicméně správně vede na ErrorPresenter. Nedalo by se mu nějak vnutit, aby rovnou vedl na „cs/error“, kde bude chybový presenter?

Ale v ErrorPreseneru se mi za žádnou cenu nedaří přepnout jazyk na jiný, než na češtinu. I přesto, že se url změní na „/en/“, tak Error presenter má v sobě stále „lang ⇒ cs“.

O jazyky se mi stará BasePresenter:

<?php

use \Nette,
 \Nette\Application\Presenter,
 \Nette\Environment,
 \Nette\Debug;

abstract class BasePresenter extends Presenter {

    public $lang;

    /**
     * Zajistuje spravu persistentnich parametru.
     * @return array Pole persistentnich parametru.
     */
    public static function  getPersistentParams() {
        return array('lang');
    }

    /**
     * Startovani vychoziho presenteru.
     */
    protected function startup() {
        parent::startup();

        // Zvaliduje se jazyk. Pokud je nevalidni, vstoupi se na vychozi.
        $langs = Environment::getVariable('langs');
        if (!in_array($this->lang, $langs->toArray())) {
            $this->lang = $langs[0];
        }

        // Jazyk se preda sablone.
        $this->template->lang = $this->lang;

        // Nastavi se jazyk Translatoru.
        Environment::getService('Translator')->setLang($this->lang);

    }

    protected function  createTemplate() {
        $template = parent::createTemplate();
        $template->setTranslator(Environment::getService('Translator'));
        return $template;
    }

}
?>

Pak mám ještě speciální BasePresenter pro frontend a administraci.

<?php

abstract class BaseFrontPresenter extends BasePresenter {

    protected function  startup() {
        parent::startup();

        $this->setLayout('front');
    }

}
?>

Samotný Error presenter vypadá takto:

<?php

use Nette\Application\BadRequestException,
 Nette\Debug;

final class ErrorPresenter extends BaseFrontPresenter {

    public function actionDefault() {
    }

    public function renderDefault($exception) {
        if ($this->isAjax()) {
            // jedná-li se o ajax, zaznamená se chyba payloadu
            $this->payload->error = TRUE;
        } elseif ($exception instanceof BadRequestException) {
            // přejde se na šablonu pro 404
            $this->view = '404';
            $this->template->exception = $exception->getMessage();
        } else {
            // přejde se na šablonu pro 500
            $this->view = '500';
            Debug::processException($exception);
        }

    }

}
?>

Je mi zvláštní, že na ostatních stránkách přepínání jazyka funguje v pořádku, ale tady ne. I když se chyba vygeneruje na anglické stránce, tak to stejně uživatele hodí na český Error presenter. Jako kdyby ten se ten $lang nepředal a byl stále na defaultní hodnotě.

Filip Procházka
Moderator | 4668
+
0
-

asi by bylo vhodné udělat si nějaký ErrorForwardingPresenter, který nastavíš do application

$application->errorPresenter = 'ErrorForwardingPresenter';

a v něm by bylo něco jako

use Nette\Application\BadRequestException;

final class ErrorForwardingPresenter extends BaseFrontPresenter
{

	public function actionDefault($exception)
	{
		Nette\Debug::log($exception, Nette\Debug::ERROR);

		if ($this->isAjax()) {
			// jedná-li se o ajax, zaznamená se chyba payloadu
			$this->payload->error = TRUE;
			$this->sendPayload();
		}

		if ($exception instanceof BadRequestException) {
			$code = '404';
		} elseif ($exception->getCode()) {
			$code = $exception->getCode();
		} else {
			$code = 500;
		}

		$this->redirect(':Error:default', array('code' => $code));
	}

}

a tohle by ti potom dovolilo zobrazovat a přesměrovávat na „normální“ ErrorPresenter

final class ErrorForwardingPresenter extends BaseFrontPresenter
{

	public function actionDefault($code)
	{
		$this->setView($code);
	}

}

a pak bys měl šablony podle kódů chyby, jako je teď v sandboxu :)

jann
Člen | 30
+
0
-

Nevíš, kde by mohla být chyba?
Na řádku:

<?php
->addFilter('id', 'PageModule::uriToId', 'PageModule::idToUrl');
?>

to háže chybu:

Parse error: parse error in D:\Software\wamp\www\nette\app\bootstrap.php on line 104
Dimitry
Člen | 11
+
0
-

Jo, tohle zřetězení mi také nefungovalo. Rozdělil jsem to na víc řádků:

<?php
        // Naroutovani frontendu modulu.
        $router[] = $moduleRouter = new MultiRouter('Page');

        $route = new SmartRoute('<lang>/<id>/', array(
                    'lang' => $langs[0],
                    'presenter' => 'Default',
                    'action' => 'default',
                ));

        $route->addFilter('id', 'PageModule::uriToId', 'PageModule::idToUri');
        $moduleRouter[] = $route;
?>
jann
Člen | 30
+
0
-

Dík, funguje. Ještě, jde tam nějak udělat, abych mohl filtrovat i názvy presenterů? Něco jako:

<?php
$route->addFilter('presenter', 'FrontModule\LibraryModel::getPresenterByUrl', 'FrontModule\LibraryModel::getUrlByPresenter');
?>

Doteď jsem na to měl zvlášť routu pro EN a CS verzi + Route::FILTER_TABLE pro každou verzi, ale nějak to blblo. Jako by se použily obě najednou a při generování adresy tam pak byla nějaká část 2×. Respektive záleželo na pořadí. Nejprve byla routa pro CZ a ta fungovala normálně. Pod ní byla routa pro EN a tady byl problém. Provedl se totiž filtr jak z CZ, tak pak i z EN routy…

Aurielle
Člen | 1281
+
0
-

Nehodilo by se ti gettextové překládání v routách – LanguageTable? Možná je to pro tvoje potřeby trošku overkill, ale pokud vyžaduješ překlady tak to funguje spolehlivě.

jann
Člen | 30
+
0
-

Na překlady používám Nette Translator od Patrika Votočka, se kterým už jsem se celkem sžil…
Jinak to zdvojování byla moje chyba. Pokud jsem byl na české URL a překlikl jsem na anglickou, tak ten filtr vzal celou českou ID část url (včetně doplněného textu) a za toto přidal anglický text. Správně to má pracovat jen s číselnými ID, moje chyba…

Aurielle
Člen | 1281
+
0
-

LanguageTable právě není nové řešení pro gettext, jen využívá gettextového překladače (taky NetteTranslator) pro překlad URL.

Filip Procházka
Moderator | 4668
+
0
-

Doplnil a opravil jsem na gistu to filtrování presenterů

co se týče toho řetězení, tak jsem si neuvědomil, že tam je new a né metoda, takže se omlouvám za zmatení. :)

$router[] = $route = new SmarterRoute('<presenter>/<action>', array(...));

$route
	->addFilter('id', 'PageModule::uriToId', 'PageModule::idToUrl')
	->addFilter('action', 'Model::actionToId', 'Model::idToAction')
	->addFilter('presenter', 'Model::presenterToId', 'Model::idToPresenter');
// ...

Editoval HosipLan (13. 2. 2011 19:02)

jann
Člen | 30
+
0
-

Co myslíš, že je lepší na překlad akcí a presenterů v URL:

1) Klasické překladové tabulky
Takto to mám aktuálně a funguje to, v jednotlivých tabulkách mám na začátku i data ze druhé tabulky, aby se mi správě vytvořila adresa, i když bych ručně v URL změnil kód jazyka (např při změně na EN to akceptuje český název akce, ale přesměruje se to pak na anglický kanonický tvar, který je na konci tabulky):

<?php
Route::addStyle('#cs-action', 'action');
Route::addStyle('#en-action', 'action');
Route::setStyleProperty('#en-action', Route::FILTER_TABLE, array(
        'zeme'      => 'zeme',
        'polozka'   => 'polozka',

        'country'   => 'zeme',
        'item'      => 'polozka',
));

Route::setStyleProperty('#cs-action', Route::FILTER_TABLE, array(
        'country'   => 'zeme',
        'item'      => 'polozka',

        'zeme'      => 'zeme',
        'polozka'   => 'polozka',
));
?>

A přidám 2 routy, pro každý jazyk jedna s následovně definovanýma akcema:

<action #cs-action> a <action #en-action>

2. Řešení pomocí tvé třídy
Tzn. že bych si přidal filtry pro prezentery i akce. A v příslušných IN a OUT callback funkcích by bylo něco podobného jako překladová tabulka v první verzi… Tady by teoreticky stačila jen jedna routa.

S Nette začínám, takže neznám ještě různé „best practicles“. Jde mi třeba o nějaké rychlostní problémy atd. Zatím mi funguje první metoda a pokud to nějak extra nezpomaluje nebo něco tak, tak ať to zbytečně nepředělávám…

Aurielle
Člen | 1281
+
0
-

Já si tu třídu napsal spíš pro efektivitu, abych mohl překládat přímo z TranslationPanelu a nemusel to dělat v kódu + automatické zjišťování presenterů a akcí je taky výhoda.

Filip Procházka
Moderator | 4668
+
0
-

Méně rout by mělo být lepší, ale něco mi říká, že gmvaskova tabulka tohle řeší. Je to spíš o tom co ti víc vyhovuje, nejjednodužším příkladem by asi bylo

class PresenterTranslations extends Nette\Object
{

	public static $translations = array(
		'cs' => array(
			'zeme' => 'zeme',
		        'polozka' => 'polozka',
		),
		'en' => array(
			'country' => 'zeme',
			'item' => 'polozka',
		),
	);


	public static function presenterToUrl($presenter, PresenterRequest $request)
	{
		$lang = $request->params['lang'];
		$table = array_flip(self::$translations[$lang]);
		return $table[$presenter];
	}


	public static function urlToPresenter($url)
	{
		$lang = $request->params['lang'];
		$table = self::$translations[$lang];
		return $table[$url];
	}
}

$router[] = $route = new SmartRoute('/[!<lang>]/<presenter>/<action>', array(
	'presenter' => 'Homepage',
	'action' => 'default',
	'lang' => 'cs'
));

$route->addFilter('presenter', 'PresenterTranslations::urlToPresenter', 'PresenterTranslations::presenterToUrl');

Samozřejmě můžeš udělat to, že si tam dáš napojení na databázi a budeš tahat pouze to, co opravdu potřebuješ :)

Editoval HosipLan (13. 2. 2011 20:38)

Dimitry
Člen | 11
+
0
-

HosipLan napsal(a):

To s tím forward presenterem trochu nechápu. Možná jsem to špatně popsal. Jde mi to, že mám na stránce dvě vlaječky, které mění jazyk aktuální strany. Ale co když zobrazovaná strana v češtině nemá svou anglickou variantu v databázi? Jaký odkaz se má vygenerovat?

Zatím jsem to vyřešil poněkud ošklivě v metodě idToUri:

<?php

        if (!isset($cache[$lang][$id])) {
            // Pokud stale neznam id, pak strana neexistuje.
            return 'error';
        }

?>

Díky to mu se vygeneruje odkaz „/en/error“ a spadne to do error presenteru. Jde to dělat nějak chytřeji? Když to ta routa odmítne přes „return NULL;“ tak se mi odkaz zvýrazňuje rudě, když vypnu varování nevalidních linků, tak se z toho udělá jen „#“, což může být pro uživatele matoucí.

Editoval Dimitry (15. 2. 2011 22:01)

Dimitry
Člen | 11
+
0
-

Chtěl bych se ještě zeptat, zda by se to dalo rozšířit tak, aby to dokázalo překládat i názvy modulů. Název modulu mi slouží jako takový „prefix“ v url.

Zkoušel jsem to všelijak přiohnout, ale název modulu se mi pak vždycky přilepí k názvu presenteru a nedaří se mi je překládat odděleně, ani společně (je to alergické na vracené lomítko).

Jde mi o to, že mám UserModule a chtěl bych, aby měl adresy:

/cs/uzivatel/prihlasit

/cs/uzivatel/registrace

/cs/uzivatel/profil

/en/user/login

/en/user/registration

/en/user/profile

Překládání presenteru funguje, ale jak překládat ten modul? Předem děkuji.

Editoval Dimitry (20. 2. 2011 23:29)

newPOPE
Člen | 648
+
0
-

Riesim to (v sucastnosti) nejak takto (mozno ti to pomoze):

bootstrap.php

<?php
$router[] = $frontRouter = new MultiRouter('Front');

$moduleAliasTable = new AliasTable(AliasTable::MODULES);
Route::setStyleProperty('module', Route::FILTER_TABLE, (array) $moduleAliasTable);

$frontRouter[] = new Route('<lang>/<module>/<presenter>/<action>/[<id>/]', array(
	'lang' => NULL,
	'module' => 'Home',
	'presenter' => 'Default',
	'action' => 'default',
	'id' => NULL,
));

?>

AliasTable.php

<?php
class AliasTable {

	const PRESENTERS	= "presenters.ini";
	const ACTIONS			= "actions.ini";
	const MODULES			= "modules.ini";

	public function __construct($configFile) {
		$request = Nette\Environment::getHttpRequest();
		$lang = substr($request->getUri()->getPath(), strlen($request->getUri()->getScriptPath()), 2);

		$lang = $lang ? $lang : 'sk';

		$ini = parse_ini_file(CONFIG_DIR . '/aliases/' . $lang . '/' . $configFile);

		foreach ($ini as $key => $value) {
			$this->$key = $value;
		}
	}
}
?>

struktura adresarov

<?php
/app
	/config
		/aliases
			/sk
				actions.ini
				presenters.ini
				modules.ini
			/en
				...

?>

modules.ini

<?php
;
; module aliases
;
pouzivatel			= User
ponuka				= Category
objednavka			= Order
?>
Dimitry
Člen | 11
+
0
-

Bohužel si nemyslím, že to je právě to, co potřebuji. Ty používáš ty překladové tabulky – ty jsou sice fajn, ale nebudou mi fungovat korektně pro vícejazyčné odkazy. Tj. jsem na české stránce a chci tam umístit odkaz na anglickou stránku – pak mi ta překladová tabulky vygeneruje chybně odkaz, protože bude stále pracovat v češtině.

Mě spíš jde o spojení s tím SmarterRoute. Včera jsem si s tím ještě hrál a zjistil jsem, že když nastavím do routy ‚module‘ ⇒ ‚User‘, tak to začne fungovat, jenže nedaří se mi to zkřížit s tím, když používám MultiRouter(‚User‘). Musím pak využívat MultiRouter, který není svázaný s modulem, ale to mi pak ta routa začne chytat i požadavky, které do ní nepatří a které mají spadnout do následujících route.

Filip Procházka
Moderator | 4668
+
0
-

To je proto, že když použiješ modul v MultiRoute, tak se ti pak do Route nedostane. Ukaž jak to máš teď napsane.

Dimitry
Člen | 11
+
0
-

K tomu stejnému jsem dospěl také. Bohužel nevím, jak řešit, aby MultiRouter platil jen pro daný modul a zároveň ta Routa dostávala modul.

<?php

use \Nette\Application\IRouter,
 \Nette\Application\MultiRouter,
 \Nette\Application\Route,
 \Nette\Environment,
 \Nette\Application\BadRequestException,
 \Nette\Application\PresenterRequest;

class UserModule implements IModule {

    /**
     * Nastavi cesty pro modul User.
     * @param string $module
     * @param IRouter $router
     */
    public function setupRoutes($module, IRouter $router) {
        $langs = Environment::getVariable('lang');

        // Naroutovani frontendu modulu.
        $router[] = $moduleRouter = new MultiRouter();


        $route = new SmartRoute('[<lang>/]<module>/<presenter>/', array(
                    'lang' => $langs[0],
                    'module' => $module,
                    'presenter' => 'Default',
                    'action' => 'default',
                ));

        $route->addFilter('module', 'Translator::urlToModule', 'Translator::moduleToUrl');
        $route->addFilter('presenter', 'Translator::urlToPresenter', 'Translator::presenterToUrl');

        $moduleRouter[] = $route;

        // Vrati se zmodifikovany router.
        return $router;
    }

}
?>

Takhle to funguje, aby to začalo to module filtrovat. Problém je, že mi tahle routa zachytí v podstatě všechno a nepokračuje dál. Původně jsem to $module neměl v té routě, ale v konstruktoru MultiRouteru.

Když tam místo toho <module> dám do masky natvrdo „user“, tak se mi to zase nebude překládat v url. A mě jde právě o to, aby to bylo celé multijazyčné.

Je to vůbec správný přístup snažit se, aby každý modul se zaváděl sám? Uvažoval jsem nad tím, jak to udělat tak, aby mi to pokrylo pár route, jenže já chci mít maximální kontrolu pro podobu route jednolivých modulů a hlavně mít po každý modul pak například jiné filtrování id. Tady to ještě není dopsané, ale pak zde ještě bude routa vedoucí do submodulu Admin. Mám to koncipované tak, že aplikace se skládá z modulů, které zavádějí sebe a zároveň zavádějí svůj submodule „Admin“ pro svou administraci.

Filip Procházka
Moderator | 4668
+
0
-

Na to mám jednoduchou odpověď :) necpat do MultiRoute moduly, kdo to vymyslel takovou blbost teda nevim, sou s tim akorat problemy (nebo jsem nepochopil na co je to dobre) :D :P

use \Nette\Application\IRouter,
 \Nette\Application\MultiRouter,
 \Nette\Application\Route,
 \Nette\Environment,
 \Nette\Application\BadRequestException,
 \Nette\Application\PresenterRequest;

class UserModule implements IModule
{

	public function setupRoutes($module, IRouter $router)
	{
		$langs = Environment::getVariable('lang');

		// Naroutovani frontendu modulu.
		$router[] = $route = new SmartRoute('[<lang>/]<module>/<presenter>/', array(
			'lang' => reset($langs),
			'module' => array(
				Route::VALUE => $module,
				Route::FILTER_IN => function($param) use ($module) {
					// pokud nebude modul povolený, tak routa nematchne
					if ($param !== $module) return NULL;
					return $param;
				}
			),
			'presenter' => 'Default',
			'action' => 'default',
		));

		$route->addFilter('module', 'Translator::urlToModule', 'Translator::moduleToUrl');
		$route->addFilter('presenter', 'Translator::urlToPresenter', 'Translator::presenterToUrl');
	}

}
Dimitry
Člen | 11
+
0
-

Co jsem četl, tak právě MultiRouter by měl zajišťovat čistě routování právě jen pro daný modul.

Zkoušel jsem tohle řešení, jenže problém je, že když ta SmartRoute provede překlad na češtinu, tak ten FILTER_IN to začne odmítat, protože v $param namísto „user“ dostane „uzivatel“, což už se samozřejmě s názvem modulu neshoduje.

Nejlepší by asi bylo, kdyby to dokázal odmítnout přímo ten vylepšený filter. Spíše mi je divné, že to nedělá implicitně.

Editoval Dimitry (21. 2. 2011 23:17)

Filip Procházka
Moderator | 4668
+
0
-

Jenže on to dokáže, jenom musíš vrátit datový typ NULL :)

Dimitry
Člen | 11
+
0
-

Asi se mi toho podařilo docílit. Routování vypadá takto:

<?php

use \Nette\Application\IRouter,
 \Nette\Application\MultiRouter,
 \Nette\Application\Route,
 \Nette\Environment,
 \Nette\Application\BadRequestException,
 \Nette\Application\PresenterRequest;

class UserModule implements IModule {

    /**
     * Nastavi cesty pro modul User.
     * @param string $module
     * @param IRouter $router
     */
    public function setupRoutes($module, IRouter $router) {
        $langs = Environment::getVariable('lang');

        $router[] = $route = new SmartRoute('[<lang>/]<module>/<presenter>/', array(
                    'lang' => $langs[0],
                    'module' => $module,
                    'fix' => $module,
                    'presenter' => 'Default',
                    'action' => 'default',
                ));

        $route->addFilter('module', 'Translator::urlToModule', 'Translator::moduleToUrl');
        $route->addFilter('presenter', 'Translator::urlToPresenter', 'Translator::presenterToUrl');

        // Vrati se zmodifikovany router.
        return $router;
    }
}

?>

A v samotné SmartRoute jsem zmodifikoval match a contructUrl:

<?php
    /**
     * @param Nette\Web\IHttpRequest $httpRequest
     * @return Nette\Application\PresenterRequest|NULL
     */
    public function match(Nette\Web\IHttpRequest $httpRequest) {
        $appRequest = parent::match($httpRequest);
        if (!$appRequest) {
            return $appRequest;
        }

        $params = $this->doFilterParams($this->getRequestParams($appRequest), $appRequest, self::WAY_IN);

        // Fixace na modul.
        if (isset($params['fix']) && $params['fix'] !== $params['module']) {
            return NULL;
        }

        if ($params) {
            return $this->setRequestParams($appRequest, $params);
        }

        return NULL;
    }

    /**
     * @param Nette\Application\PresenterRequest $appRequest
     * @param Nette\Web\Uri $refUri
     * @return string
     */
    public function constructUrl(PresenterRequest $appRequest, Nette\Web\Uri $refUri) {

        $paramsBefore = $this->getRequestParams($appRequest);

        // Fixace na modul.
        if (isset ($paramsBefore['fix']) && $paramsBefore['fix'] !== $paramsBefore['module']) {
            return NULL;
        }

        if ($params = $this->doFilterParams($paramsBefore, $appRequest, self::WAY_OUT)) {
            $appRequest = $this->setRequestParams($appRequest, $params);
            return parent::constructUrl($appRequest, $refUri);
        }

        return NULL;
    }
?>

Ten parametr fix s sebou vždycky nese nemodifikované jméno modulu a je-li nastaven, porovnává se s jménem modulu před/po filtru (dle potřeby). Tím dosáhnu toho, že routa je napevno „fixována“ na jeden modul.

Filip Procházka
Moderator | 4668
+
0
-

Moc pěkný „hack“ :)

Oggy
Člen | 306
+
0
-

dostávám tuto chybu? kde by mohl být problém?

Cannot read an undeclared property FilterRoute::$metadata.

Route má $metadata jako private

Editoval Oggy (9. 4. 2011 14:31)

Filip Procházka
Moderator | 4668
+
0
-

Opravil jsem to v https://gist.github.com/824624, ale nevyzkoušel, můžeš to zkusit? Nechce se mi rozjíždět kvůli tomu sandbox :)

Oggy
Člen | 306
+
0
-

HosipLan napsal(a):

Opravil jsem to v https://gist.github.com/824624, ale nevyzkoušel, můžeš to zkusit? Nechce se mi rozjíždět kvůli tomu sandbox :)

Funguje:-)

Oggy
Člen | 306
+
0
-

Neozkoušel jsem to teda kompletně.. jen jsem nahlédl, jestli to generuje správné url..
ale má to problém z url dostat presenter..

<?php
 public static $translations = array(
                'cs' => array(
                        'o-nas' => 'AboutUs',
....

public static function urlToPresenter($url, PresenterRequest $request)
        {
                $lang = $request->params['lang'];
                $table = self::$translations[$lang];
                Debug::barDump($url);
                if(array_key_exists($url, $table)) return $table[$url];
                return NULL;
        }
?>

Tam kde se url dumpuje má hodnotu ONas namísto o-nas ..

Oggy
Člen | 306
+
0
-

funguje mi to pokud pole translations vypadá takto: :-)

<?php
public static $translations = array(
                'cs' => array(
                        'ONas' => 'AboutUs',
                        'NapsaliONas' => 'WroteAboutUs',
			...
?>

to mě trošku zmátlo.. psal jsem to pole klasicky:

<?php
'cs' => array(
                        'o-nas' => 'AboutUs',
                        'napsali-o-nas' => 'WroteAboutUs',
?>
Oggy
Člen | 306
+
0
-

Někdo tu psal, že to úspěšně používá..
pokud přidám ještě filter pro actions, přestanou mi fungovat překlady presenterů.
Neukážete prosím vaše funkční řešení?

Morlok
Člen | 26
+
0
-

Oggy napsal(a):

Někdo tu psal, že to úspěšně používá..
pokud přidám ještě filter pro actions, přestanou mi fungovat překlady presenterů.
Neukážete prosím vaše funkční řešení?

Defaultne ma action a presenter nastavene filtre path2presenter, presenter2path …to ti tam bude robit gulas pri preklade, ja to mam nastavene takto:

<?php
       $route = new FilterRoute('[<lang [a-z]{2}>/]<presenter>/<action>', array(
		'presenter' => 'Default',
		'action' => array(
			Route::VALUE => 'default',
			Route::FILTER_IN => null,
			Route::FILTER_OUT => null
		),
		'lang' => 'cs'
	));
?>
hAssassin
Člen | 293
+
0
-

Zdravim, procital sem to tady celkem detailne, ale mam dva problemy. Pouzivam HospiLanuv FilterRoute a je to dobra vecicka. A ted k tem problemum:

  1. Potreboval bych nastavit vychozi hodnoty pro presenter a action dany route podle jazyka. Jenze vychozi hodnoty se nastavuji uz v konstruktoru a ukladaji se do $metadat a ty nemaji setter cili nejdou zmenit. Jde mi o to abych vychozi hodnoty mohl nastavit az pozdeji podle aktualniho jazyka, cili pokud bude jazyk cs aby to nastavilo Uvod:hlavni a pokud bude jazyk en tak aby to nastavilo Homepage:default. Bohuzel se mi nedari k tomu dostat. Zkousel jsem i setStyleProperty() a Route::VALUE ale to nejak nechce fungovat. Takze pokud ma nekdo nejaky reseni, budu vdecny (pokud vubec existuje).
  2. Mam problem s moduly. Vytvori routu a jako masku predam:

admin/[<module (vycet_povolenych_modulu)>/]<presenter>/<action>/[<id>/]

Nazvy modulu bych rad pochopitelne taky prekladal. On sice preklad funguje, ale pouze jednim smerem. Cili pokud mam modul cms a jeho preklad je treba sprava-obsahu a ja do adresy natvrdo zadam localhost/admin/sprava-obsahu/ tak to spravne zobrazi vychozi presenter (Default) z modulu Cms. Problem je ze u generovani odkazu do prislusneho modulu to generuje odkaz localhost/admin/cms.default/, ktery se zobrazi opet spravne, ale to se mi nelibi. Chtel bych to mit cesky. Nejake napady jak na to jit?

P.S. u tech modulu je problem ten, ze muze byt volitelny, jelikoz napr. hlavni stranka administrace a prihlaseni je primo v modulu Admin, ostatni veci (sprava obsahu, sprava uzivatelu, apod) jsou pak v prislusnych modulech. Diky za jakykoliv navrhy a reseni…

Aurielle
Člen | 1281
+
0
-

U FilterRoute jsem narazil na problém při skládání URL, kdy se zkouší konstrukce podle rout a definovaný filtr ve FilterRoute může změnit parametry Nette\Application\Requestu a další filtr poté již nematchne. Fix:

/**
 * @param Nette\Application\Request $appRequest
 * @param Nette\Web\Url $refUrl
 * @return string
 */
public function constructUrl(Request $appRequest, Nette\Http\Url $refUrl)
{
	$request = clone $appRequest;
	if ($params = $this->doFilterParams($this->getRequestParams($request), self::WAY_OUT)) {
		$request = $this->setRequestParams($request, $params);
		$return = parent::constructUrl($request, $refUrl);
		if($return !== NULL) {
			$appRequest = $request;
			return $return;
		}
	}

	return NULL;
}
hAssassin
Člen | 293
+
0
-

@gmvasek > drobna chybicka tam je, radek:

if ($params = $this->doFilterParams($this->getRequestParams($request), self::WAY_OUT)) {

zmenit na:

if ($params = $this->doFilterParams($this->getRequestParams($request), $request, self::WAY_OUT)) {

Chybi tam druhy parametr, ktery musi byt Request… Ale jinak good postrech ;-)

K memu dotazu vyse, bod 2 sem se zda vyresil, ale s tim prvnim bodem si nevim co pocit, nevi nekdo jak zmenit vychozi hodnoty pro presenter a akci pozdeji? Dik.

Editoval hAssassin (21. 7. 2011 20:41)

Aurielle
Člen | 1281
+
0
-

@hAssassin a já se divil proč mi to nechybí, HosipLan to upravoval a já používám vlastní upravenou verzi. Ale díky :)

hAssassin
Člen | 293
+
0
-

@gmvasek > tak to sorry za mystifikaci :D

@all > mam jeste jeden dotaz ohledne prekladu. jde nejak zarucit preklad i parametru v query stringu? cili abych v anglictine mel domena.tld/en/presenter/action/?search=1 a v cestine domena.tld/cs/prezenter/akce/?hledat=1? Jde mi o ten search/hledat. A je to vubec spravny prekladat parametry v query? Dik

Aurielle
Člen | 1281
+
0
-

Názvy parametrů v QS bych nepřekládal, způsobí ti to minimálně bordel v aplikaci při zpracování. Hodnota by měla jít překládat. (btw k tvému dotazu 1, je to vůbec třeba?)

hAssassin
Člen | 293
+
0
-

@gmvasek > no bordel by to snad zpusobovat nemelo, nebo me nenapada jak by mohlo. A jak to myslis s tou hodnotou? Ta by se ale asi preklada az v ramci presenteru a jeho logiky a ne primo v route, ne? Tady mi jde o to, aby napr. parametry pro filtrovani podle datumu nebyly date_from/date_to, ale datum_od/datum_do coz bych sice mohl mit, ale zase nechci motat cesky a anglicky nazvy parametru metod.

Jinak k tomu bodu 1. Treba to neni a uz sem se s tim naucil zit:-) Ale slo mi o to, ze jako vyhozi mam Homepage:default. Pokud to budu prekladat, tak se automaticky bude doplnovat adresa na tvar admin/uvod/hlavni/ pro cestinu, ale pro anglictinu bude jen admin/. Pokud to prekladat nebudu, tak adresa bude jen admin/, ale pokud to bude potreba (napr. pro zpracovani signalu) se doplni na admin/homepage/default/?do=signal a to se mi prave nelibi:-) Resenim by bylo nastavit proste natvrdo do routy jako vychozi hodnoty Uvod:hlavni (pro cestinu) s tim ze pro jiny jazyky by se to proste doplnovalo na plnou URL, ale nevim jestli to je uplne koser reseni. Ideal by bylo podle jazyka nastavit vychozi hodnoty, coz vsak bohuzel asi s pouzitim vychozi tridy Route nejde.

Aurielle
Člen | 1281
+
0
-

Stačí nastavit překladovou tabulku tak, aby výchozí hodnoty byly přesně shodné s originálem. Potom neuvidíš tebou popisované doplňování default a jiných.

hAssassin
Člen | 293
+
0
-

@gmvasek > vidis, to me vubec nenapadlo, ja totiz preklad resim pomoci vlastniho translatoru s pouzitim toho FilterRoute a tam to prave doplnuje, ale ta FILTER_TABLE by to alespon pro ty vychozi hodnoty mohla resit… Prave ze sem to cely chtel delat pres FILTER_TABLE jenze ta nezna jazyk, cili bych musel tam musel pro vsechny jazyk dat vsechny kombinace, coz neni hezky, ale pro vychozi hodnoty by to vadit nemelo… Diky, vyzkousim a uvidime :)

Aurielle
Člen | 1281
+
0
-

Aha, myslel jsem si že používáš filter table. V tom případě nevadí, s FilterRoute to jde taky. Moje LanguageTable dělá přesně tohle :)

hAssassin
Člen | 293
+
0
-

@gmvasek > mohl bys please nastinit jak? sice mi to v zakladu vali, ale ty vychozi hodnoty porad nemuzu zmenit aby se pak nepridaly do URL a naopak… :( to sem psal vyse jsem zkousel ale bohuzel to vali jeste hur nez ted :-)