Skip to content

Commit f8542e5

Browse files
authored
Merge pull request #883 from bobvandevijver/1.0
[Loader] Add bundle resource path auto-registration option and root path placeholder referencing
2 parents de43101 + 4e8d5bd commit f8542e5

21 files changed

+942
-276
lines changed

Binary/Locator/FileSystemInsecureLocator.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ class FileSystemInsecureLocator extends FileSystemLocator
2121
*/
2222
protected function generateAbsolutePath($root, $path)
2323
{
24-
if (false !== strpos($path, '..'.DIRECTORY_SEPARATOR)) {
24+
if (false !== strpos($path, '..'.DIRECTORY_SEPARATOR) ||
25+
false !== strpos($path, DIRECTORY_SEPARATOR.'..') ||
26+
false === file_exists($absolute = $root.DIRECTORY_SEPARATOR.$path)) {
2527
return false;
2628
}
2729

28-
return file_exists($absolute = $root.DIRECTORY_SEPARATOR.$path) ? $absolute : false;
30+
return $absolute;
2931
}
3032
}

Binary/Locator/FileSystemLocator.php

+40-3
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,52 @@ public function setOptions(array $options = array())
4848
* @return string
4949
*/
5050
public function locate($path)
51+
{
52+
if (false !== $absolute = $this->locateUsingRootPlaceholder($path)) {
53+
return $this->sanitizeAbsolutePath($absolute);
54+
}
55+
56+
if (false !== $absolute = $this->locateUsingRootPathsSearch($path)) {
57+
return $this->sanitizeAbsolutePath($absolute);
58+
}
59+
60+
throw new NotLoadableException(sprintf('Source image not resolvable "%s" in root path(s) "%s"',
61+
$path, implode(':', $this->roots)));
62+
}
63+
64+
/**
65+
* @param string $path
66+
*
67+
* @return bool|string
68+
*/
69+
private function locateUsingRootPathsSearch($path)
5170
{
5271
foreach ($this->roots as $root) {
5372
if (false !== $absolute = $this->generateAbsolutePath($root, $path)) {
54-
return $this->sanitizeAbsolutePath($absolute);
73+
return $absolute;
5574
}
5675
}
5776

58-
throw new NotLoadableException(sprintf('Source image not resolvable "%s" in root path(s) "%s"',
59-
$path, implode(':', $this->roots)));
77+
return false;
78+
}
79+
80+
/**
81+
* @param string $path
82+
*
83+
* @return bool|string
84+
*/
85+
private function locateUsingRootPlaceholder($path)
86+
{
87+
if (0 !== strpos($path, '@') || 1 !== preg_match('{@(?<name>[^:]+):(?<path>.+)}', $path, $matches)) {
88+
return false;
89+
}
90+
91+
if (isset($this->roots[$matches['name']])) {
92+
return $this->generateAbsolutePath($this->roots[$matches['name']], $matches['path']);
93+
}
94+
95+
throw new NotLoadableException(sprintf('Invalid root placeholder "%s" for path "%s"',
96+
$matches['name'], $matches['path']));
6097
}
6198

6299
/**

DependencyInjection/Factory/Loader/FileSystemLoaderFactory.php

+100-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Liip\ImagineBundle\DependencyInjection\Factory\Loader;
1313

14+
use Liip\ImagineBundle\Exception\InvalidArgumentException;
1415
use Liip\ImagineBundle\Utility\Framework\SymfonyFramework;
1516
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -25,7 +26,7 @@ class FileSystemLoaderFactory extends AbstractLoaderFactory
2526
public function create(ContainerBuilder $container, $loaderName, array $config)
2627
{
2728
$definition = $this->getChildLoaderDefinition();
28-
$definition->replaceArgument(2, $config['data_root']);
29+
$definition->replaceArgument(2, $this->resolveDataRoots($config['data_root'], $config['bundle_resources'], $container));
2930
$definition->replaceArgument(3, $this->createLocatorReference($config['locator']));
3031

3132
return $this->setTaggedLoaderDefinition($loaderName, $definition, $container);
@@ -56,14 +57,112 @@ public function addConfiguration(ArrayNodeDefinition $builder)
5657
->ifString()
5758
->then(function ($value) { return array($value); })
5859
->end()
60+
->treatNullLike(array())
61+
->treatFalseLike(array())
5962
->defaultValue(array('%kernel.root_dir%/../web'))
6063
->prototype('scalar')
6164
->cannotBeEmpty()
6265
->end()
6366
->end()
67+
->arrayNode('bundle_resources')
68+
->addDefaultsIfNotSet()
69+
->children()
70+
->booleanNode('enabled')
71+
->defaultFalse()
72+
->end()
73+
->enumNode('access_control_type')
74+
->values(array('blacklist', 'whitelist'))
75+
->info('Sets the access control method applied to bundle names in "access_control_list" into a blacklist or whitelist.')
76+
->defaultValue('blacklist')
77+
->end()
78+
->arrayNode('access_control_list')
79+
->defaultValue(array())
80+
->prototype('scalar')
81+
->cannotBeEmpty()
82+
->end()
83+
->end()
84+
->end()
85+
->end()
6486
->end();
6587
}
6688

89+
90+
/*
91+
* @param string[] $staticPaths
92+
* @param array $config
93+
* @param ContainerBuilder $container
94+
*
95+
* @return string[]
96+
*/
97+
private function resolveDataRoots(array $staticPaths, array $config, ContainerBuilder $container)
98+
{
99+
if (false === $config['enabled']) {
100+
return $staticPaths;
101+
}
102+
103+
$resourcePaths = array();
104+
105+
foreach ($this->getBundleResourcePaths($container) as $name => $path) {
106+
if (('whitelist' === $config['access_control_type']) === in_array($name, $config['access_control_list']) && is_dir($path)) {
107+
$resourcePaths[$name] = $path;
108+
}
109+
}
110+
111+
return array_merge($staticPaths, $resourcePaths);
112+
}
113+
114+
/**
115+
* @param ContainerBuilder $container
116+
*
117+
* @return string[]
118+
*/
119+
private function getBundleResourcePaths(ContainerBuilder $container)
120+
{
121+
if ($container->hasParameter('kernel.bundles_metadata')) {
122+
$paths = $this->getBundlePathsUsingMetadata($container->getParameter('kernel.bundles_metadata'));
123+
} else {
124+
$paths = $this->getBundlePathsUsingNamedObj($container->getParameter('kernel.bundles'));
125+
}
126+
127+
return array_map(function ($path) {
128+
return $path.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'public';
129+
}, $paths);
130+
}
131+
132+
/**
133+
* @param array[] $metadata
134+
*
135+
* @return string[]
136+
*/
137+
private function getBundlePathsUsingMetadata(array $metadata)
138+
{
139+
return array_combine(array_keys($metadata), array_map(function ($data) {
140+
return $data['path'];
141+
}, $metadata));
142+
}
143+
144+
/**
145+
* @param string[] $classes
146+
*
147+
* @return string[]
148+
*/
149+
private function getBundlePathsUsingNamedObj(array $classes)
150+
{
151+
$paths = array();
152+
153+
foreach ($classes as $c) {
154+
try {
155+
$r = new \ReflectionClass($c);
156+
} catch (\ReflectionException $exception) {
157+
throw new InvalidArgumentException(sprintf('Unable to resolve bundle "%s" while auto-registering bundle resource paths.', $c), null, $exception);
158+
}
159+
160+
$paths[$r->getShortName()] = dirname($r->getFileName());
161+
}
162+
163+
return $paths;
164+
}
165+
67166
/**
68167
* @param string $reference
69168
*

README.md

+69-6
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,12 @@ $response = $imagine
367367
```
368368

369369

370-
## Images Outside Data Root
370+
## Data Roots
371371

372-
When your setup requires your source images reside outside the web root,
373-
you have to set your loader's `data_root` parameter in your configuration
374-
(often `app/config/config.yml`) with the absolute path to your source image
375-
location.
372+
By default, Symfony's `web/` directory is registered as a data root to load
373+
assets from. For many installations this will be sufficient, but sometime you
374+
may need to load images from other locations. To do this, you must set the
375+
`data_root` parameter in your configuration (often located at `app/config/config.yml`).
376376

377377
```yml
378378
liip_imagine:
@@ -382,7 +382,70 @@ liip_imagine:
382382
data_root: /path/to/source/images/dir
383383
```
384384

385-
This location must be readable by your web server. On a system that supports
385+
As of version `1.7.2` you can register multiple data root paths, and the
386+
file locator will search each for the requested file.
387+
388+
```yml
389+
liip_imagine:
390+
loaders:
391+
default:
392+
filesystem:
393+
data_root:
394+
- /path/foo
395+
- /path/bar
396+
```
397+
398+
As of version `1.7.3` you ask for the public resource paths from all registered bundles
399+
to be auto-registered as data roots. This allows you to load assets from the
400+
`Resources/public` folders that reside within the loaded bundles. To enable this
401+
feature, set the `bundle_resources.enabled` configuration option to `true`.
402+
403+
```yml
404+
liip_imagine:
405+
loaders:
406+
default:
407+
filesystem:
408+
bundle_resources:
409+
enabled: true
410+
```
411+
412+
If you want to register some of the `Resource/public` folders, but not all, you can do
413+
so by blacklisting the bundles you don't want registered or whitelisting the bundles you
414+
do want registered. For example, to blacklist (not register) the bundles "FooBundle" and
415+
"BarBundle", you would use the following configuration.
416+
417+
```yml
418+
liip_imagine:
419+
loaders:
420+
default:
421+
filesystem:
422+
bundle_resources:
423+
enabled: true
424+
access_control_type: blacklist
425+
access_control_list:
426+
- FooBundle
427+
- BarBundle
428+
```
429+
430+
Alternatively, if you want to whitelist (only register) the bundles "FooBundle" and "BarBundle",
431+
you would use the following configuration.
432+
433+
```yml
434+
liip_imagine:
435+
loaders:
436+
default:
437+
filesystem:
438+
bundle_resources:
439+
enabled: true
440+
access_control_type: whitelist
441+
access_control_list:
442+
- FooBundle
443+
- BarBundle
444+
```
445+
446+
### Permissions
447+
448+
Image locations must be readable by your web server. On a system that supports
386449
`setfacl` (such as Linux/BSD), use
387450

388451
```sh

0 commit comments

Comments
 (0)