Rendering a Twig template to string, adding custom asset path - image

Our project allows the users to create custom theme files. Because of the way the project is structured, we have to render these files and save the output. The project uses Symfony2.
In order to render this Twig template and save the output to another file, I use a service which takes #templating as its argument.
The services.yml defines the service like this:
theme_renderer:
class: ApiBundle\Service\ThemeRenderer
arguments: [ #templating ]
This gives me a TwigEngine object that I can use like this:
public function __construct(TwigEngine $templateEngine) {
$this->templateEngine = $templateEngine;
}
public function renderTheme($filePath, $themeSettings) {
...
$renderedFileContents = $this->templateEngine->render($sourceFilePath, $themeSettings);
...
}
This works fine.
However, if the template contains an {{ asset('images', 'some_image') }} tag, the rendering fails. The exception is:
An exception has been thrown during the rendering of a template ("There is no "some_image" asset package.") in "<template_file_path_here>" at line 2
I need to be able to give a custom (CDN actually) URL for the rendering of the asset() tag. How would I go about doing this?

I think exception message There is no "some_image" asset package. is clear. Second argument of asset function is package name. Packages for templating component are defined under framework.templating key in Symfony configuration (etc. config.yml).
http://symfony.com/doc/current/reference/configuration/framework.html#packages
# app/config/config.yml
framework:
# ...
templating:
packages:
avatars:
base_urls: 'http://static_cdn.example.com/avatars'
Then you can use asset('my_image.jpg', 'avatars'). So, I suppose you have not defined package some_image or configured it improperly.

Related

Define alias for blade directory in laravel

How i can define blade directory in liravel ?
I wanna to use:
return view('panel::post.create');
// e.g : resurces/views/vendor/panel/post/create.blade.php
How i should define vendor/panel as panel:: ?
You may do this using the service provider's loadViewsFrom method. The loadViewsFrom method accepts two arguments: the path to your view templates and your package's name. For example, if your package's name is panel, you would add the following to your service provider's boot method:
public function boot()
{
$this->loadViewsFrom(__DIR__.'/path/to/views', 'panel');
}
Package views are referenced using the package::view syntax convention. So, once your view path is registered in a service provider, you may load the admin view from the panel package like so:
Route::get('admin', function () {
return view('panel::admin');
});
Read more at official Laravel documentation.

Configure langs in Laravel Translatable BootForms

I'm new to Laravel Translatable BootForms, and I was wondering something.
When I use this code :
{!!
TranslatableBootForm::text('Nom', 'name')
->required()
!!}
The render is as follows :
I don't know where this language list comes from.
I only want to list some languages specified in my database, as I do with this workaround :
#foreach($availableLangs as $availableLang)
{!!
TranslatableBootForm::text('Nom', 'name')
->renderLocale($availableLang['locale'])
!!}
#endforeach
Which gives me this :
My two questions are :
Where does this language list come from ?
How can I replace it by my own language list ?
Answering the first question may lead to an automatic answer for the second, though)
In Laravel, you should always try to read the Service Providers, they provide important clues about the project structures. Let's try to follow the trail of the function calls:
TranslatableBootForm is a facade and it resolves to and instance of translatable-bootform from the Service Container according to this line:
protected static function getFacadeAccessor() { return 'translatable-bootform'; }
Now, in the file TranslatableBootFormsServiceProvider.php we can see that translatable-bootform is an instance of TranslatableBootForm. So when you call TranslatableBootForm::text, you will be using the Facade which resolves to an instance of TranslatableBootForm
Opening the TranslatableBootForm class, we cannot find the text method, so there should be a __call method. The __call method always returns whatever is returned from the method render. So that's where the action is happening.
Reading the code there, you will find that it gets the locales from a method called locales and it will intersect it with the func_get_args() function to get whatever languages you pass to it. So renderLocale or simply render will do the same thing.
The method locales just returns an array which is by default empty in the class. If we return back to the TranslatableBootFormsServiceProvider we will see that there's an important line:
$formBuilder->setLocales($this->getLocales());
Which gets the locales from Translatable\TranslatableWrapper which is just a wrapper around this file in another package: https://github.com/dimsav/laravel-translatable/blob/master/src/Translatable/Translatable.php
Looking at the configuration file in the laravel-translatable package, we can see the languages:
https://github.com/dimsav/laravel-translatable/blob/master/src/config/translatable.php
Solutions
Now, you can simply copy the file translatable.php in your config folder and set your locales.
Or, you create a new service provider MyTranslatableBootFormsServiceProvider
class MyTranslatableBootFormsServiceProvider extends TranslatableBootFormsServiceProvider
{
/**
* Get Translatable's locales.
*
* #return array
*/
protected function getLocales()
{
// You can return a config key
// return config('yourconfig.locales');
// Or directly the array containing the languages
return ['en', 'fr', 'nl'];
}
}
Then, you will use this provider in your config/app.php instead of the original TranslatableBootFormsServiceProvider
Disclaimer:
I didn't try the code, you might have a bug, but you get the idea now how to find your way around Laravel packages.

How to override FOSUserBundle's EmailConfirmationListener

I activated user confirmation for FOSUserBundle. But I don't want to take the response from the original listener
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
I want to chose another route. I tried to extend the EventListener
namespace Acme\MyBundle\EventListener;
use FOS\UserBundle\EventListener\EmailConfirmationListener as BaseListener;
// ...
class EmailConfirmationListener extends BaseListener
{
public function onRegistrationSuccess(FormEvent $event)
{
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
}
}
Unfortunately, EventListeners don't seem to be extendable, just as Controllers or Forms are. (Just in case you wonder: of course my bundle is a child of the FOSUserBundle.)
So I want to avoid editing those two lines directly in the vendor folder (as it would be very bad practice to do so!). So what are my ways out of this calamity?
Just override the service fos_user.listener.email_confirmation by creating a service with the same name in your config.yml ...
# app/config/config.yml
services:
fos_user.listener.email_confirmation:
class: "Acme\MyBundle\EventListener\EmailConfirmationListener"
arguments: ["#fos_user.mailer", "#fos_user.util.token_generator", "#router", "#session"]
tags:
- { name: kernel.event_subscriber }
... or even cleaner - create a parameter that's being used by your service:
parameters:
my.funky_parameter.class: "Acme\MyBundle\EventListener\EmailConfirmationListener"
services:
fos_user.listener.email_confirmation:
class: "%my.funky_parameter.class%"
# ...
... or inside your bundle's xml/yml/php configuration file loaded by the bundle's extension. Make sure your bundle is being registered after FOSUserBundle in AppKernel.php when choosing this way.
... or the best method:
change the original service's class name in a compiler pass as the documentation chapter How to Override any Part of a Bundle suggests.
Maybe take a dive into the chapter How to work with Compiler Passes before choosing this option.

Getting view to use correct region automatically

As mentioned in an earlier question of mine, I'm new to ATK4 and I'm currently learning, so there might come a few more question. Now to my issue.
I've created a region "Sidebar" in my template shared.html and adding the view to it like this:
class Frontend extends ApiFrontend {
function init(){
parent::init();
/*
Other stuff here
*/
$this->addLayout('Sidebar');
}
function layout_Sidebar() {
$this->add('View_Menu', null, 'Sidebar');
}
}
Then I'm creating the view like this:
class View_Menu extends AbstractView {
function init(){
parent::init();
$this->add('HtmlElement')
->setElement('a')
->setAttr('href', 'testurl')
->set('Link');
}
}
This gives me the following error:
Spot is not found in owner's template
Additional information:
spot: Content
Supplying the add function with $this->template->top_tag as third argument solves this problem:
$this->add('HtmlElement', null, $this->template->top_tag)
->setElement('a')
->setAttr('href', 'testurl')
->set('Link');
...but do I really have to add that to every add() call in the view? It doesn't seem right and I'm quite sure it's not!
When you are creating AbstractView, you need to specify a default template. By default your AbstractView will use the region of from your shared.html. In other words AbstractObject assumes the template of the region it replaces.
when you create defaultTemplate() or pass 4th argument to the add() you can specify a different file to be used for template of your sidebar menu.
In either way - the template should contain a where output of any sub-elements will be displayed.
You may inherit from "View" class which already relies on the custom template containing just a . Your idea of using HtmlElement is just like this, because HtmlElement extends View.

Add method to be available in all Views

I would like to have a method available to all Views in my app.
I would like to be able to make calls like this:
<span>${ getDynamicText() }</span>
The most obvious ways (to me) to implement this are:
Call the method in the controller and and pass it to the View.
Make the method static on some Util class and call it from the code ${ UtilClass.getDynamicText() }
Using meta programming to somehow make the method available to all Views.
The benefit of #3 is that the change would only have to be made in one place. #1 would have to be made in each controller action; and #2 would need an import on every View page which wants to use the method.
So is there a way to add a method to be available to all views in my app?
I have to admit I don't know in a lot of detail how .gsp files are processed behind-the-scenes so maybe they don't have a corresponding class and therefore can't be manipulated in this way. Links to good articles/docs will get extra good karma.
GSPs are compiled into classes that extend org.codehaus.groovy.grails.web.pages.GroovyPage, so you can add methods to that metaclass and they'll be available to all GSPs. The best place to do this is in BootStrap.groovy (or in a plugin's doWithDynamicMethods closure):
import org.codehaus.groovy.grails.web.pages.GroovyPage
class BootStrap {
def init = { servletContext ->
GroovyPage.metaClass.getDynamicText = { ... }
}
}
The recommended way to reuse functionality across GSPs is to define it as a tag, e.g.
class MyTagLib {
static namespace = 'my'
def dynamicText = {attrs ->
out << 'not very dynamic'
}
}
You can then call this tag in a GSP using:
<my:dynamicText/>
4th way: make a class/service that have method '.getDynamicText' and put it's intance into request at before filter ( request.setAttribute('x', myDynamicTextGeneratorObject) )
Now you can use x.dynamicText in any GSP
This is how I would do it:
Add a new class to your controllers folder containing your method
Do a grails install-templates
Navigate to the templates: \src\templatesscaffolding
Add the extends part to the controller template: class ${className}Controller extends NewController
re-generate your controllers
You can now use the method in every class and gsp.

Resources