Makro <n:if-allowed>, jak to vypadá?

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

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
+
0
-

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
+
0
-

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)

Aurielle
Člen | 1281
+
0
-

S mým řešením je to zhruba takhle:

<a n:href="Presenter:action" n:if-allowed>Link</a>
mr.mac
Člen | 87
+
0
-

To je méně psaní. Díky, až bude víc času asi to přepíšu.