Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit cc67098

Browse files
committed
Implement template namespaces for ZendView adapter
- Creates a NamespacedPathStackResolver implementation, which extends the basic TemplatePathStack in order to ensure things like LFI security and template streams also work. It segregates paths by namespace, and resolves based on the namespace provided with the template, falling back to the default namespace if no match is found. - The ZendView adapter now always composes an AggregateResolver. If an alternate resolver is found, it is pushed into an AggregateResolver. Additionally, a NamespacedPathStackResolver is always injected at priority 0 (lower than default) so that we can push paths to it.
1 parent 3e32f7a commit cc67098

File tree

2 files changed

+326
-34
lines changed

2 files changed

+326
-34
lines changed

src/Template/ZendView.php

Lines changed: 100 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,19 @@
1313
use Zend\View\Model\ViewModel;
1414
use Zend\View\Renderer\RendererInterface;
1515
use Zend\View\Renderer\PhpRenderer;
16+
use Zend\View\Resolver\AggregateResolver;
1617
use Zend\View\Resolver\ResolverInterface;
17-
use Zend\View\Resolver\TemplatePathStack;
1818
use Zend\Expressive\Exception;
1919

2020
/**
2121
* Template implementation bridging zendframework/zend-view.
22+
*
23+
* This implementation provides additional capabilities.
24+
*
25+
* First, it always ensures the resolver is an AggregateResolver, pushing any
26+
* non-Aggregate into a new AggregateResolver instance. Additionally, it always
27+
* registers a ZendView\NamespacedPathStackResolver at priority 0 (lower than
28+
* default) in the Aggregate to ensure we can add and resolve namespaced paths.
2229
*/
2330
class ZendView implements TemplateInterface
2431
{
@@ -40,7 +47,7 @@ class ZendView implements TemplateInterface
4047
private $renderer;
4148

4249
/**
43-
* @var ResolverInterface
50+
* @var ZendView\NamespacedPathStackResolver
4451
*/
4552
private $resolver;
4653

@@ -67,9 +74,17 @@ public function __construct(RendererInterface $renderer = null, $layout = null)
6774
{
6875
if (null === $renderer) {
6976
$renderer = $this->createRenderer();
77+
$resolver = $renderer->resolver();
78+
} else {
79+
$resolver = $renderer->resolver();
80+
if (! $resolver instanceof AggregateResolver) {
81+
$aggregate = $this->getDefaultResolver();
82+
$aggregate->attach($resolver);
83+
$resolver = $aggregate;
84+
} elseif (! $this->hasNamespacedResolver($resolver)) {
85+
$this->injectNamespacedResolver($resolver);
86+
}
7087
}
71-
$this->renderer = $renderer;
72-
$this->resolver = $renderer->resolver();
7388

7489
if ($layout && is_string($layout)) {
7590
$model = new ViewModel();
@@ -85,29 +100,9 @@ public function __construct(RendererInterface $renderer = null, $layout = null)
85100
));
86101
}
87102

88-
$this->layout = $layout;
89-
}
90-
91-
/**
92-
* Returns a PhpRenderer object
93-
*
94-
* @return PhpRenderer
95-
*/
96-
private function createRenderer()
97-
{
98-
$render = new PhpRenderer();
99-
$render->setResolver($this->getDefaultResolver());
100-
return $render;
101-
}
102-
103-
/**
104-
* Get the default resolver
105-
*
106-
* @return TemplatePathStack
107-
*/
108-
private function getDefaultResolver()
109-
{
110-
return new TemplatePathStack();
103+
$this->renderer = $renderer;
104+
$this->resolver = $this->getNamespacedResolver($resolver);
105+
$this->layout = $layout;
111106
}
112107

113108
/**
@@ -143,10 +138,7 @@ public function render($name, $params = [])
143138
*/
144139
public function addPath($path, $namespace = null)
145140
{
146-
$this->resolver->addPath($path);
147-
148-
// Normalize the path to be compliant with the TemplatePathStack
149-
$this->paths[TemplatePathStack::normalizePath($path)] = $namespace;
141+
$this->resolver->addPath($path, $namespace);
150142
}
151143

152144
/**
@@ -157,10 +149,20 @@ public function addPath($path, $namespace = null)
157149
public function getPaths()
158150
{
159151
$paths = [];
160-
foreach ($this->resolver->getPaths() as $path) {
161-
$namespace = array_key_exists($path, $this->paths) ? $this->paths[$path] : null;
162-
$paths[] = new TemplatePath($path, $namespace);
152+
153+
foreach ($this->resolver->getPaths() as $namespace => $namespacedPaths) {
154+
if ($namespace === ZendView\NamespacedPathStackResolver::DEFAULT_NAMESPACE
155+
|| empty($namespace)
156+
|| is_int($namespace)
157+
) {
158+
$namespace = null;
159+
}
160+
161+
foreach ($namespacedPaths as $path) {
162+
$paths[] = new TemplatePath($path, $namespace);
163+
}
163164
}
165+
164166
return $paths;
165167
}
166168

@@ -246,4 +248,68 @@ private function renderModel(ModelInterface $model, RendererInterface $renderer)
246248

247249
return $renderer->render($model);
248250
}
251+
252+
/**
253+
* Returns a PhpRenderer object
254+
*
255+
* @return PhpRenderer
256+
*/
257+
private function createRenderer()
258+
{
259+
$renderer = new PhpRenderer();
260+
$renderer->setResolver($this->getDefaultResolver());
261+
return $renderer;
262+
}
263+
264+
/**
265+
* Get the default resolver
266+
*
267+
* @return ZendView\NamespacedPathStackResolver
268+
*/
269+
private function getDefaultResolver()
270+
{
271+
$resolver = new AggregateResolver();
272+
$this->injectNamespacedResolver($resolver);
273+
return $resolver;
274+
}
275+
276+
/**
277+
* Attaches a new ZendView\NamespacedPathStackResolver to the AggregateResolver
278+
*
279+
* A priority of 0 is used, to ensure it is the last queried.
280+
*
281+
* @param AggregateResolver $aggregate
282+
*/
283+
private function injectNamespacedResolver(AggregateResolver $aggregate)
284+
{
285+
$aggregate->attach(new ZendView\NamespacedPathStackResolver(), 0);
286+
}
287+
288+
/**
289+
* @param AggregateResolver $aggregate
290+
* @return bool
291+
*/
292+
private function hasNamespacedResolver(AggregateResolver $aggregate)
293+
{
294+
foreach ($aggregate as $resolver) {
295+
if ($resolver instanceof ZendView\NamespacedPathStackResolver) {
296+
return true;
297+
}
298+
}
299+
300+
return false;
301+
}
302+
303+
/**
304+
* @param AggregateResolver $aggregate
305+
* @return null|ZendView\NamespacedPathStackResolver
306+
*/
307+
private function getNamespacedResolver(AggregateResolver $aggregate)
308+
{
309+
foreach ($aggregate as $resolver) {
310+
if ($resolver instanceof ZendView\NamespacedPathStackResolver) {
311+
return $resolver;
312+
}
313+
}
314+
}
249315
}

0 commit comments

Comments
 (0)