Makro <n:if-allowed>, jak to vypadá?
- mr.mac
- Člen | 87
Rád bych upravil svou aplikaci tak, aby se mi nezobrazovaly odkazy tam, kam
nemá aktuální uživatel práva. Používám statické ACL (dle návodu zde) a
aktuálně nemám dotaženo vykreslování v šabloně dle práv.
Vím že se to tu diskutovalo
a ZDE je odkaz na zajímavé
řešení. Jenže jako nette novic mi není jasná implementace. Dalo by se ve
stručnosti popsat jak postupovat při implementaci? Kam kterou třídu uložit
(první do BasePresenteru, pak asi model User, jak s LatteMacros.php? je to
další model?) a hlavně jak celou věc použít v šabloně.
Díky moc (asi otázka pro autora gmvasek).
Anebo zda existuje obdobné funkční řešení…?
Editoval mr.mac (19. 11. 2011 19:10)
- Aurielle
- Člen | 1281
Zrovna dneska jsem mazal mám pocit funkční implementaci ze svého gitu (přešel jsem na jiný způsob řešení ACL), každopádně něco takového by mělo být funkční. Po updatu Latte se to muselo měnit a hackovat v o dost víc souborech, proto tolik kódu…
BasePresenter.php
/**
* n:if-allowed functionality
* @param Nette\Application\UI\PresenterComponent $component
* @param string $destination
* @return bool
*/
public function userAllowed($component, $destination = NULL)
{
if($destination === NULL) // No destination specified, can cause unexpected results when used with n:if-allowed as it would check for previous link!
{
$request = $this->lastCreatedRequest;
$destination = ':' . $request->presenterName . ':' . $request->params[self::ACTION_KEY];
return $this->getUser()->isAllowedP($destination);
}
else
{
// Following behavior is merely reproduced from Nette\Application\UI\Presenter::createRequest()
// PARSE DESTINATION
// 1) fragment
$a = strpos($destination, '#');
if($a !== FALSE)
$destination = substr($destination, 0, $a);
// 2) ?query syntax
$a = strpos($destination, '?');
if($a !== FALSE)
$destination = substr($destination, 0, $a);
// 3) URL scheme
$a = strpos($destination, '//');
if($a !== FALSE)
$destination = substr($destination, $a + 2);
// 4) signal or empty
if(!($component instanceof Nette\Application\UI\Presenter) || substr($destination, -1) === '!') {
$signal = rtrim($destination, '!');
$a = strrpos($signal, ':');
if($a !== FALSE) {
$component = $component->getComponent(strtr(substr($signal, 0, $a), ':', '-'));
$signal = (string) substr($signal, $a + 1);
}
if($signal == NULL) { // intentionally ==
throw new Nette\Application\UI\InvalidLinkException("Signal must be non-empty string.");
}
return $this->user->isAllowed($component->reflection->name, $signal);
}
if($destination == NULL) { // intentionally ==
throw new Nette\Application\UI\InvalidLinkException("Destination must be non-empty string.");
}
// 5) presenter: action
$a = strrpos($destination, ':');
if($a === FALSE) {
$action = $destination === 'this' ? $this->action : $destination;
$presenter = $this->getName();
$destination = ":$presenter:$action";
} else {
$action = (string) substr($destination, $a + 1);
if ($destination[0] === ':') { // absolute
if ($a < 2) {
throw new Nette\Application\UI\InvalidLinkException("Missing presenter name in '$destination'.");
}
} else { // relative
$destination = ':' . $this->getName() . ':' . $action;
}
}
return $this->user->isAllowedP($destination);
}
}
public function templatePrepareFilters($template)
{
$template->registerFilter(new Avalon\Latte\Engine);
}
Configurator/nějaký jiný způsob jak předat Userovi vlastní kontext:
// lazyCopy je HosipLanův nápad, podívej se do Kdyby jak vlastně funguje
public static function createServiceUser(DI\Container $container)
{
$context = new Container;
$context->lazyCopy('authenticator', $container);
$context->lazyCopy('authorizator', $container);
$context->addService('session', $container->session);
$context->addService('presenterFactory', $container->presenterFactory);
return new Avalon\Security\User($context);
}
Vlastní User:
class User extends Nette\Http\User
{
/** @var Nette\Application\IPresenterFactory */
protected $presenterFactory;
/**
* Constructor
*/
public function __construct(Nette\DI\IContainer $context)
{
$this->presenterFactory = $context->presenterFactory;
parent::__construct($context);
}
/**
* Has a user effective access to the Resource?
* @param string $presenter Fully qualified action
* @return bool
*/
public function isAllowedP($presenter)
{
if(Nette\Utils\Strings::startsWith($presenter, '//')) {
return $this->isAllowed(trim($presenter, '/'), 'view');
}
if(substr($presenter, -1) === ':') {
$action = 'default';
$presenter = trim($presenter, ':');
}
else {
$action = ltrim(strrchr($presenter, ':'), ':');
$presenter = ltrim(substr($presenter, 0, -(strlen($action) + 1)), ':');
}
$presenterClass = $this->presenterFactory->getPresenterClass($presenter);
return $this->isAllowed($presenterClass, $action);
}
}
Latte\Engine:
use Nette\Latte\Macros;
class Engine extends Nette\Latte\Engine
{
/**
* Constructor
*/
public function __construct()
{
$this->parser = new Parser;
Macros\CoreMacros::install($this->parser);
$this->parser->addMacro('cache', new Macros\CacheMacro($this->parser));
Macros\UIMacros::install($this->parser);
Macros\FormMacros::install($this->parser);
AvalonMacros::install($this->parser);
}
}
AvalonMacros:
class AvalonMacros extends Nette\Latte\Macros\MacroSet
{
public static function install(Nette\Latte\Parser $parser)
{
$me = new static($parser);
$me->addMacro('@if-allowed', array($me, 'macroIfAllowed'));
}
/**
* Process <tag n:if-allowed>
* @param MacroNode $node
* @param PhpWriter $writer
* @return string
*/
public function macroIfAllowed(MacroNode $node, $writer)
{
// empty
}
}
Latte\Parser:
class Parser extends Nette\Latte\Parser
{
/**
* Generates code for macro <tag n:attr> to the output.
* @param string
* @param array
* @param bool
* @return void
*/
public function writeAttrsMacro($code, $attrs, $closing)
{
if(isset($attrs['if-allowed']) && isset($attrs['href']))
{
unset($attrs['if-allowed']);
$a = strpos($attrs['href'], ',');
$b = strpos($attrs['href'], ' ');
if($a !== FALSE || $b !== FALSE) {
$pos = ($a !== FALSE) ? $a : $b;
$attrs['if'] = '$presenter->userAllowed($control, "' . substr($attrs['href'], 0, $pos) . '")';
} else {
$attrs['if'] = '$presenter->userAllowed($control, "' . $attrs['href'] . '")';
}
}
elseif(isset($attrs['if-allowed']))
{
if(!empty($attrs['if-allowed']))
{
$attrs['if'] = '$presenter->userAllowed($control, "' . $attrs['if-allowed'] . '")';
unset($attrs['if-allowed']);
}
else
{
unset($attrs['if-allowed']);
trigger_error('Using n:if-allowed with no context!', E_USER_WARNING);
}
}
return parent::writeAttrsMacro($code, $attrs, $closing);
}
}
- mr.mac
- Člen | 87
Díky za návod – ale je pro mě dost obtížné to narychlo pozřít. Zatím to vypadá, že se přikloním k aplikaci zde doporučené varianty typu:
<a n:if="$user->isAllowed('Produkt','delete')"
n:href="delete, $prod->id">Odstranit</a>
Defacto je zde na obtíž psát dokola název presenteru a akce, na kterou je
href
odkaz, ale jinak je to docela úsporné.
Mohl bys uvést příklad zápisu v šabloně? Díky.
Editoval mr.mac (20. 11. 2011 9:57)