I'm using FMElfinder in association with TinyMCE for managing the assets (images, pdf ...) of the users (managed with FOSUSerBundle)
I've seen that this tool can handle multiple root folder, but in my case, it isn't quite usable : i would like to have a root folder for each user.
In the configuration file app/config/config.yml, there is the root path(s) defined :
fm_elfinder:
instances:
default:
locale: %locale%
...
connector:
roots:
uploads:
driver: LocalFileSystem
path: uploads/data
I was thining about "simply" changing the path to something like :
path: uploads/data/{the_username}
where the username would be the username of the currently logged user
In a controller i can do
$user = $this->get('security.token_storage')->getToken()->getUser();
$username = $user->getUsername();
But i don't know if it's possible (and if so, how) to access specifically the username of the logged user into a config file
Thank you if you have any suggestion
=================[EDIT] ==========================================
I've use the override of configuration. I think i followed the steps, but i haven't managed to make it work :
1 - Create the class
use FM\ElfinderBundle\Model\ElFinderConfigurationProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ElfinderConfigurator implements ElFinderConfigurationProviderInterface
{
protected $container;
protected $options;
/**
* #param ContainerInterface $container
*/
public function __construct($options, ContainerInterface $container)
{
$this->container = $container;
$this->storage = $container->get('security.token_storage');
$this->options = $options;
}
/**
* #param $instance
*
* #return array
*/
public function getConfiguration($instance)
{
//retrieve basepath
$basepath_abs = $this->container->get('kernel')->getRootDir()."/../web/uploads";
$basepath = "uploads/data";
//define path for user
$userid = $this->storage->getToken()->getUser()->getId();
$root = $basepath.'/'.$userid;
$this->options['instances']['default']['connector']['roots']['uploads']['path'] = $root.'/root';
$this->options['instances']['default']['connector']['roots']['uploads']['upload_max_size'] = '2M';
$option = [
'corsSupport' => false,
'roots' => $this->options['instances']['default']['connector']['roots'],
];
$root_abs = $basepath_abs.'/data/'.$userid;
//creates dir if not available
if (!is_dir($root_abs)) {
mkdir($root_abs.'/root', 0775, true);
}
return $option;
}
}
2 - Set my service :
myvendor.mybundle.elfinder_configurator:
class: Myvendor\Mybundle\Services\ElfinderConfigurator
arguments: ["%fm_elfinder%", "#service_container"]
3 - Call the service in app/config/config.yml
fm_elfinder:
configuration_provider: myvendor.mybundle.elfinder_configurator
...
It works partially : When i open the elfinde, the directory are correctly created if they don't exists. But there must be a path problem, and i'm not sure it's well overriden because :
- The thumbs are not displayed in elfinder
- When i add the image to the editor, i don't have the correct path of the image, i have :
//app_dev.php/efconnect?cmd=file&target=l1_Q2FwdHVyZSBkJ8OpY3JhbiBkZSAyMDE2LTAxLTI0IDE0OjM2OjI0LnBuZw
instead of the actual path of the image (if i don't use the override, the tool works and gives me this path)
../../../../uploads/data/1/root/img1.png
and no image is displayed.
Also, if i look in the js console for the
efconnect?cmd=open&target=&init=1&tree=1&_=1469377765664
I see that uplMaxSize is 200M,
in any case, there is no js error in the console
I think you are looking for a custom config provider:
https://github.com/helios-ag/FMElfinderBundle/blob/master/Resources/doc/advanced-configuration.md#custom-configuration-provider
You could then inject the token storage into the service and fetch the user from
like in any controller:
services:
my_elfinder_configurator:
class: Acme\DemoBundle\elFinder\UserAwareConfigurator
arguments: ["#token_storage", "%any_container_params%"]
Related
I created a custom module to create a /store/ID/tasks page
https://www.drupal.org/project/commerce
How to limit access to this page to the store owner ?
If the current user is owner of store ID 76, he can access this page :
/store/76/tasks
But if he goes to another store, he must have denied access :
/store/89/tasks
https://git.drupalcode.org/sandbox/zenimagine-3076032
task_notify/task_notify.routing.yml
task_notify.store_page.tasks:
path: '/store/{store}/tasks'
defaults:
_controller: '\Drupal\task_notify\Controller\TaskNotifyStoreController::Tasks'
_title: 'Liste des tâches'
requirements:
_custom_access: '\Drupal\task_notify\Controller\TaskNotifyStoreController::taskAccess'
task_notify/src/Controller/TaskNotifyStoreController.php
<?php
namespace Drupal\task_notify\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
use Drupal\commerce_store\Entity\StoreInterface;
class TaskNotifyStoreController extends ControllerBase {
public function Tasks() {
return [
'#theme' => 'task_notify_store_template',
];
}
public function taskAccess(StoreInterface $store, AccountInterface $account = NULL, $return_as_object = FALSE) {
$result = $store->access('edit', $account, TRUE);
return $return_as_object ? $result : $result->isAllowed();
}
}
This page should be accessible only if the current user can edit the store (the site administrator and the store owner).
Access in the module code must have the same conditions as in this view :
https://i.stack.imgur.com/ZfUMo.png
I was inspired by the two files below :
https://git.drupalcode.org/project/commerce_marketplace/-/blob/8.x-1.x/src/Plugin/Action/MarketplaceIncreaseStoreLimitByOne.php
https://git.drupalcode.org/project/commerce_marketplace/-/blob/8.x-1.x/src/Plugin/Action/MarketplaceMarkAsDefault.php
In this case, we can tell Drupal that {store} is an entity and it will load the object. So we don't have to do that in the Controller function.
So your routing file can include "parameters" settings to do that.
task_notify.store_page.tasks:
path: '/store/{store}/tasks'
defaults:
_controller: '\Drupal\task_notify\Controller\TaskNotifyStoreController::Tasks'
_title: 'Liste des tâches'
requirements:
_custom_access: '\Drupal\task_notify\Controller\TaskNotifyStoreController::taskAccess'
options:
parameters:
store:
type: entity:commerce_store
Now your controller function has access to that object.
public function Tasks(StoreInterface $store) { ...
In my experience, that is NOT true of the access() method (at least when using a type-hinted parameter as we are doing here). You get a string, so you'll have to load the store manually.
public function taskAccess(string $store, AccountInterface $account) {
$store = \Drupal\commerce_store\Entity\Store::load($store);
// Check store owner against current user.
if ($store->access('edit', $account)) {
return AccessResult::allowed();
}
else {
return AccessResult::forbidden();
}
Also we need to define $account in the routing file now, as we are using type-hinted parameters (I think). So add that to the options:.
task_notify.store_page.tasks:
path: '/store/{store}/tasks'
defaults:
_controller: '\Drupal\task_notify\Controller\TaskNotifyStoreController::Tasks'
_title: 'Liste des tâches'
requirements:
_custom_access: '\Drupal\task_notify\Controller\TaskNotifyStoreController::taskAccess'
options:
parameters:
store:
type: entity:commerce_store
account: \Drupal\Core\Session\AccountProxy
$account is one of a few special parameters that we can type-hint this way. More info: https://www.drupal.org/docs/8/api/routing-system/access-checking-on-routes/advanced-route-access-checking
let's say I have 3 databases:
prefix_db1
prefix_db2
prefix_db3
And I want to connect to them dynamically from the url like this http://localhost/my-project/web/app_dev.php/db1/books so I know which database to conenct to from the url (in this case prefix_db1)
And basically the idea was to prepare a listener that will be fired with each http request, get the database name from the url and then override doctrin's params, something like this:
Within services.yml:
dynamic_connection:
class: AppBundle\service\DynamicDBConnector
arguments: ['#request_stack']
calls:
- [ setDoctrineConnection, ['#doctrine.dbal.default_connection'] ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
My listener:
<?php
namespace AppBundle\service;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\RequestStack;
use Exception;
class DynamicDBConnector
{
/**
* #var Connection
*/
private $connection;
/*
* #var Request
*/
private $request;
public function __construct(RequestStack $requestStack)
{
$this->request = $requestStack->getCurrentRequest();
}
/**
* Sets the DB Name prefix to use when selecting the database to connect to
*
* #param Connection $connection
* #return DynamicDBConnector $this
*/
public function setDoctrineConnection(Connection $connection)
{
$this->connection = $connection;
return $this;
}
public function onKernelRequest()
{
if ($this->request->attributes->has('_company')) {
$connection = $this->connection;
$params = $this->connection->getParams();
$companyName = $this->request->get('_company');
// I did the concatenation here because in paramaters.yml I just put the prefix (database_name: prefix_) so after the concatenation I get the whole database name "prefix_db1"
$params['dbname'] = $params['dbname'] . $companyName;
// Set up the parameters for the parent
$connection->__construct(
$params,
$connection->getDriver(),
$connection->getConfiguration(),
$connection->getEventManager()
);
try {
$connection->connect();
} catch (Exception $e) {
// log and handle exception
}
}
return $this;
}
}
Now this worked very well I have tested it using a simple list of books and each time I change the url I get the list related to each database:
http://localhost/my-project/web/app_dev.php/db1/books // I get books of database prefix_db1
http://localhost/my-project/web/app_dev.php/db2/books // I get books of database prefix_db2
Now let's get to the problem shall we :):
The problem now is that when I secure my project with authentication system and try to login (of course each database has user table) using this url http://localhost/my-project/web/app_dev.php/db1/login
I get this exception :
An exception occured in driver: SQLSTATE[HY000] [1049] Base 'prefix_' unknown
As you can see symfony tried to login the user using the database_name declared in parameters.yml which means that the security_checker of symfony has been fired before my listener and before overriding Doctrine's params.
My question:
Is there any way to fire my listener before any other http request listener ? or maybe an alternative solution to make sure that any request to database must be with the right database name.
Sorry for the long post.
EDIT:
From the official documentation of symfony:
https://symfony.com/doc/2.3/cookbook/event_dispatcher/event_listener.html
The other optional tag attribute is called priority, which defaults
to 0 and it controls the order in which listeners are executed (the
highest the priority, the earlier a listener is executed). This is
useful when you need to guarantee that one listener is executed before
another. The priorities of the internal Symfony listeners usually
range from -255 to 255 but your own listeners can use any positive or
negative integer.
I set the priority of my listener to 10000:
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 10000 }
But the problem persist, still can't fire my listener before symfony!
I found a solution
The idea is tochange the default Connection class that symfony uses to create a database connection:
doctrine:
dbal:
connections:
default:
wrapper_class: AppBundle\Doctrine\DynamicConnection
driver: pdo_mysql
host: '%database_host%'
port: '%database_port%'
dbname: '%database_name%'
user: '%database_user%'
password: '%database_password%'
charset: UTF8
After that we can change the given params in the constructor:
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
class DynamicConnection extends Connection
{
public function __construct(array $params, Driver $driver, $config, $eventManager)
{
$params['dbname'] = 'teqsdqsdqst';
parent::__construct($params, $driver, $config, $eventManager);
}
}
Now we just need to get the parameter from the url and set inside $params['dbname'].
In this way we make sure that symfony will always use this class to create the connection and we no longer need to fire listeners with http requestes
Great solution but if you want get the parameter _company from the URL you can retrieve the container inside the constructor through the EventManager object passed in parameters and get the current request from it, in fact the container is injected into ContainerAwareEventManager the sub class of EventManager
class DynamicDBConnector extends Connection
{
public function __construct($params, $driver, $config, $eventManager)
{
if(!$this->isConnected()){
// Create default config and event manager if none given (case in command line)
if (!$config) {
$config = new Configuration();
}
if (!$eventManager) {
$eventManager = new EventManager();
}
$refEventManager = new \ReflectionObject($eventManager);
$refContainer = $refEventManager->getProperty('container');
$refContainer->setAccessible('public'); //We have to change it for a moment
/*
* #var \Symfony\Component\DependencyInjection\ContainerInterface $container
*/
$conrainer = $refContainer->getValue($eventManager);
/*
* #var Symfony\Component\HttpFoundation\Request
*/
$request = $conrainer->get('request_stack')->getCurrentRequest();
if ($request != null && $request->attributes->has('_company')) {
$params['dbname'] .= $request->attributes->get('_company');
}
$refContainer->setAccessible('private'); //We put in private again
parent::__construct($params, $driver, $config, $eventManager);
}
}
}
you should add the database name in your config.yml like this :
orm:
auto_generate_proxy_classes: '%kernel.debug%'
# naming_strategy: doctrine.orm.naming_strategy.underscore
# auto_mapping: true
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
DataMiningBundle: ~
AppBundle: ~
UserBundle: ~
your_second_db:
connection: your_second_db (decalared in parameters.yml)
mappings:
yourBundle: ~
and call it from your controller :
$em = $doctrine->getConnection('your_second_db');
I'm writting tests and task for a new app for an application and I need to access to the "app config parameters" of this new app defined in /apps/mynewapp/config/app.yml. I thought it will be as easy as written in the Symfony doc, but it seems I've forgotten something.
When I get my config: $actions = sfConfig::get("app_actions") it is NULL. I thought the config name is wrong, but when I get all the config parameters available with sfConfig::getAll(), I don't have my app config parameters.
Maybe I've forgotten to include my /apps/mynewapp/config/app.yml?
There is the content of my file:
all:
.array:
actions:
bind_destroy: BindDestroyAction
bind_subscribe: BindSubscriptionAction
messages:
bind_destroy: BindDestroyMessage
bind_subscribe: BindSubscriptionMessage
And there is how I try to access to my parameters in /apps/mynewapp/lib/GRM/GRMSender.class.php:
class GRMSender
{
private $actionClassNames;
private $messageClassNames;
public function __construct()
{
$this->actionClassNames = sfConfig::get("app_actions");
$this->messageClassNames = sfConfig::get("app_messages");
}
}
The class has already been autoloaded and I'm able to instantiate the class in my unit test scripts.
Thank you for your help.
EDIT
The problem is about my tests (in /test/unit) and my tasks (in /lib/task). I have to use what I did in my application "mynewapp". I did some things :
For the tasks, I defined the application in my task options :
class mynewappActionTask extends sfBaseTask
{
protected function configure()
{
// Do some configuration...
try {
$this->addOptions(array(
new sfCommandOption(
'application',
"app",
sfCommandOption::PARAMETER_REQUIRED,
'The application name',
"mynewapp" // There
),
));
} catch (sfCommandException $e) {}
}
}
For the tests, I wrote a file which loads my mynewapp config. IMHO it's a hack and there is a better way to do it :
$configMynewapp = ProjectConfiguration::getApplicationConfiguration("mynewapp", sfConfig::get("sf_environment"), true);
There must be better ways to get mynewapp config parameters in tasks and in tests. In mynewapp files (controller, lib, etc.) it's ok.
Try to do this:
/apps/mynewapp/config/app.yml
all:
actions:
bind_destroy: BindDestroyAction
bind_subscribe: BindSubscriptionAction
messages:
bind_destroy: BindDestroyMessage
bind_subscribe: BindSubscriptionMessage
Then you can get:
$actions = sfConfig::get('app_actions');
It will return:
$actions => array(
'bind_destroy' => 'BindDestroyAction',
'bind_subscribe' => 'BindSubscriptionAction'
)
Anyway, you can access one of them directly:
$action = sfConfig::get('app_actions_bind_destroy')
$action => 'BindDestroyAction'
I am trying to upload file with Symfony3 but with no luck. I have a Profile entity which is linked to User entity with 1-1 relationship. The profile contains a picture column.
I have created a ProfileType and Profile Model. Upon submitting the form, the model contains only the File name and nothing else. The $_FILES array is also empty. This is the code.
$builder
->add("name", TextType::class, array(
"required" => true,
))
->add("email", EmailType::class, array(
"required" => true,
))
->add("city", TextType::class, array(
"required" => false,
))
->add("country", ChoiceType::class, array(
"required" => false,
))
->add("picture", FileType::class, array(
"required" => false,
));
class ProfileModel
{
private $name;
private $email;
private $city;
private $country;
private $picture;
In Controller I am creating the form like this.
$profileForm = $this->createForm(ProfileType::class, $profileModel);
When I get the picture, It contains just the name.
$file = $profileForm->get("picture")->getData();
Hewwo rashidkhan~
Symfony doc is quite complete on the upload process, did you read it?
http://symfony.com/doc/current/controller/upload_file.html
After a few modifications, I choose to use it as service.
Here is the process:
1) Add a few parameters to app/config/config.yml:
under parameters:
parameters:
locale: en
profile_directory: '%kernel.root_dir%/../web/upload/profile'
another_directory: '%kernel.root_dir%/../web/upload/another'
under twig
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
globals:
profile_directory: '/upload/profile/'
another_directory: '/upload/another/'
The two profile_directoryadded just now will be used as variables in both your upload service and twig to point the targer directory.
I added another_directory to explain something more a bit after.
2) Create the service:
Create a new file under src/YourBundle/Services/FileUploader.php
From here, my code is a bit different than what you can find on the Symfony site.
FileUploader.php content:
<?php
namespace YourBundle\Services;
use YourBundle\Entity\ProfileModel;
use YourBundle\Entity\Another;
class FileUploader {
private $profileDir;
private $anotherDir;
public function __construct($profileDir) {
$this->profileDir=$profileDir;
$this->anotherDir=$anotherDir;
}
public function upload($class) {
if($class instanceof ProfileModel) {
$file=$class->getPicture();
$fileName='picture-'.uniqid().'.'.$file->guessExtension();
$file->move($this->profileDir, $fileName);
$class->setPicture($fileName);
}
if($class instanceof Another) {
$file=$class->getPicture();
$fileName='picture-'.uniqid().'.'.$file->guessExtension();
$file->move($this->anotherDir, $fileName);
$class->setPicture($fileName);
}
return $class;
}
}
3) Register the service to app/config/services.yml:
under services:
services:
app.file_uploader:
class: YourBundle\Services\FileUploader
arguments:
- '%profile_directory%'
- '%another_directory%'
Each argument must be in the same order as your privatein the FileUploader.php file.
Those arguments are the ones we setted in app/config/config.yml under parameters.
4) Edit your controller:
The controller part is quite simple.
Add use Symfony\Component\HttpFoundation\File\File; in the import section
Under newAction
public function newAction(Request $request)
{
$profileModel = new ProfileModel();
$form = $this->createForm('YourBundle\Form\ProfileModelType', $profileModel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// We upload the file with this line
$profileModel=$this->get('app.file_uploader')->upload($profileModel);
$em = $this->getDoctrine()->getManager();
$em->persist($profileModel);
$em->flush();
return $this->redirectToRoute('profile_model_show', array('id' => $profileModel->getId()));
}
return $this->render('YourBundle:Default:new.html.twig', array(
'profileModel' => $profileModel,
'form' => $form->createView(),
));
}
Under editAction
public function editAction(Request $request, ProfileModel $profileModel)
{
// Add this live above everything else in the code.
$profileModel->setPicture(new File($this->getParameter('profile_directory').'/'.$profileModel->getPicture()));
[...]
}
I haven't gone more far, so I can only explain what to modify after...
In your editAction, you will also have to check that $_FILES isn't empty.
If it's not, then you do the upload process.
If it's, then make sure to not edit the picture column in the SQL query (you will have to do a custom query)
5) Your twig views:
Under show.html.twig
Change
<tr>
<th>Picture</th>
<td>{{ profileModel.picture) }}</td>
</tr>
to
<tr>
<th>Picture</th>
<td><img src="{{ asset(profile_directory~profileModel.picture) }}"></td>
</tr>
Same goes for the index.html.twig.
And you can add (not replace) it to the edit.html.twig to get a preview of the actual picture.
6) Explanations:
In app/config/config.yml we added a few directory to use as parameters in your files.
It will later make it easier to change those directories if needed. (Won't have to edit tons of files... YAY!)
Twig directories always start from the /web folder.
Those directory are used when we register our service as arguments.
They will set our variable in the service file FileUploader.php.
Unlike the Symfony site exemple, we pass the whole object to the upload service.
We then, check from which class this object was created and do our upload process based in it.
Your upload process in the controller is then shortened to a single line.
In twig, we will also use the directory variable set in app/config/config.yml undet the twigproperty.
Like said above, if our upload directory change, we will then just have to edit the app/config/config.yml file.
I hope this will help you solve your upload issues.
Cordially,
Preciel.
You should try
$form = $this->createForm(ProfileType::class, $profileModel);
$form->handleRequest($request);
$file = $profileModel->getBrochure();
More: http://symfony.com/doc/current/controller/upload_file.html
Guys if you want to upload any kind of file in Symfony then I have very simple solution, which I have mentioned in the below. Why I am giving simple solutions because whenever new version come, you have to do some settings in services.yaml or you have to create extra files apart from your main controller.
So solutions is: Just use move($storing_place, $actual_filename) function in your main controller.
Put below codes in your controller file.
$folder_path = 'public/uploads/brochures/';
$file = $request->files->get('myfile');
$fileName = $request->files->get('myfile')->getClientOriginalName();
$file->move($folder_path, $fileName);
return new Response($file);
Hope given solution will help in your project.
I'm using Symfony3 with the KnpGaufretteBundle to connect to an Amazon S3 bucket with the AWS S3 method outlined on their Github Readme
aws_s3_adapter:
key: "%aws_key%"
secret_key: "%aws_secret%"
region: "%aws_region%"
knp_gaufrette:
adapters:
images:
aws_s3:
service_id: 'aws_s3_adapter.client'
bucket_name: '%aws_bucket%'
options:
directory: 'images'
filesystems:
images:
adapter: images
alias: images_fs
I also have a service defined that I want to use to manage this filesystem (and others) with.
Definition:
services:
test.image_manager:
class: TestBundle\Filesystem\FileManager
arguments:
filesystem: "#images_fs"
filesystem_name: "images"
mimetypes: ["image/jpeg", "image/png", "image/gif"]
Class:
<?php
namespace TestBundle\Filesystem;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Gaufrette\Filesystem;
use Gaufrette\StreamWrapper;
class FileManager
{
private $allowedMimeTypes;
private $filesystem;
private $filsystem_name;
public function __construct(Filesystem $filesystem, $filesystem_name, $mimetypes = array())
{
$this->filesystem = $filesystem;
$this->filesystem_name = $filesystem_name;
$this->allowedMimeTypes = $mimetypes;
}
public function upload(UploadedFile $file, $filename)
{
// Check if the file's mime type is in the list of allowed mime types.
if (!in_array($file->getClientMimeType(), $this->allowedMimeTypes)) {
throw new \InvalidArgumentException(sprintf('Files of type %s are not allowed.', $file->getClientMimeType()));
}
$adapter = $this->filesystem->getAdapter();
$adapter->setMetadata($filename, array('contentType' => $file->getClientMimeType()));
return $adapter->write($filename, file_get_contents($file->getPathname()));
}
public function fetch( $filename )
{
if( ! $this->filesystem->has( $filename ) )
{
return false;
}
/* -- PROBLEM -- */
StreamWrapper::register();
return new BinaryFileResponse( "gaufrette://{$this->filesystem_name}/{$filename}" );
/* -- PROBLEM -- */
}
public function delete( $filename )
{
if( ! $this->filesystem->has( $filename ) )
{
return false;
}
return $this->filesystem->delete( $filename );
}
}
I'm able to upload successfully to the bucket using the upload function, telling me that the filesystem exists and is working properly.
I am not, however, able to use the Gaufrette\StreamWrapper to serve the file using a BinaryFileResponse as it says I should do. Instead it is giving me the error that I put in the title: There is no filesystem defined for the "images" domain.
The filesystem definitely exists, as I'm using it to upload the images. Any clues as to what the problem might be that's preventing me from using that filesystem would be very helpful. The Gaufrette documentation is really sparse online so far as I've found, but I'm going to keep digging.
Looking at MainConfiguration.php showed that there's a steam_wrapper option in the configuration for the bundle. I added this into my config.yml under where the filesystems are defined like so:
knp_gaufrette:
adapters:
images:
aws_s3:
service_id: 'aws_s3_adapter.client'
bucket_name: '%aws_bucket%'
options:
directory: 'images'
.
.
.
filesystems:
images:
adapter: images
alias: images_fs
.
.
.
stream_wrapper:
filesystems: [ images, ... ]
and the above code now works.