reading symfony documentation about templating I found mention about twig being able to output css files.
how is this used? is it possible to generate dynamic css same way as I generate html?
for example when I want to display some html template I create controller action and inside I render .html.twig file possibly passing it some parameters.
can I render .css.twig same way? where would be this file stored and how could I possibly include it to another html template.
I would like to keep all styles in separate files, but some of these styles change under some conditions. for example, right now I'm setting height of some divs according to calculations in controller and I pass result height as parameter to template. but I don't feel like this is very MVC, having part of representation logic in controller (or even model).
It certainly is possible. You would do most of things exactly the same as if you would do for html template.
Create file, eg:
/* src/Acme/MyBundle/Resources/views/somefile.css.twig */
.someclasss {
background-color: {{ backgroundColor }};
}
Create controller action
// src/Acme/MyBundle/Controller/MyStyleController.php
// ...
public function styleAction()
{
// Fetch it from service or whatever strategy you have
$backgroundColor = '#ff0000';
return $this->render(
'AcmeMyBundle::somefile.css.twig',
['backgroundColor' => $backgroundColor],
['Content-Type' => 'text/css']
);
}
// ...
Create route for that action
# src/Acme/MyBundle/Resources/config/routing.yml
css_route:
path: /some/path
defaults: { _controller AcmeMyBundle:MyStyleController:style }
methods: [GET]
Use that css in your layout
{# src/AcmeMyBundle/Resources/views/mypage.html.twig #}
{# ... #}
<link href="{{ path('css_route') }}" rel="stylesheet">
{# ... #}
Now, whether or not this is a good idea should be a separate question. There certainly are some cases where this approach is perfectly valid, but there are cases where you can avoid this. Keep in mind that serving CSS file like this is a lot more expensive than serving static CSS file. Furthermore, since it's a CSS file and it's in HEAD section of your response, it will slow down page load time since it will have to fetch CSS file before rendering body itself.
If you do decide to do this be sure to check out caching possibilities you have to make this as fast as possible.
Actually
public function styleAction()
{
// Fetch it from service or whatever strategy you have
$backgroundColor = '#ff0000';
return $this->render(
'AcmeMyBundle::somefile.css.twig',
['backgroundColor' => $backgroundColor],
['Content-Type' => 'text/css']
);
}
should be more like this
/**
* #Route("/css/style", name="style")
* #param Request $request
* #return Response
*/
public function styleAction(Request $request)
{
$firstColor = 'rgba(130, 30, 30, 0.9)';
/** #var Response $response */
$response = $this->render(':css:style.css.twig', [
'firstColor' => $firstColor,
]);
$response->headers->set('Content-Type', 'text/css');
return $response;
}
Note that I have annotations because I use Symfony 3. But the important thing in my code sample is that for the response I set Content-Type to 'text/css'.
Related
Is posible to save SS template variable in database from CMS and after execute it in template?
Okay lets see example:
In CMS i have settings where i put social media links and contact informatios.
Also in CMS i have module where i create HTML block-s which after that i loop in website.
In that html block i want to put existing $SiteConfig.Email variable.
I Try that but that is rendered in template like $SiteConfig.Email not show real email?
Is this posible to do or i need some extra modification?
Check photo
The question you have written makes no sense to me, but I understand the screenshot.
So, SilverStripe renders .ss files with a class called SSViewer. Basically it reads the file as string and then runs it through SSViewer to generate the HTML output.
But, as you saw, the output of variables is not processed.
I can think of 3 ways to get what you want:
Run the variables through SSViewer aswell (in this example, use $RenderedHTMLContent in the template)
class MyDataObject extends DataObject {
private static array $db = [
'Title' => DBVarchar::class,
'HTMLContent' => DBText::class,
];
public function Foobar() { return "hello from foobar"; }
public function RenderedHTMLContent() {
$template = \SilverStripe\View\SSViewer::fromString($this->HTMLContent);
// using $this->renderWith() will allow you access to all things of $this in the template. so eg $ID, $Title or $Foobar. Probably also $SiteConfig because it's global
return $this->renderWith($template);
// if you want to add extra variables that are not part of $this, you can also do:
return $this->renderWith($template, ["ExtraVariable" => "Hello from extra variable"]);
// if you do not want $this, you can do:
return (new ArrayData(["MyVariable" => "my value"]))->renderWith($template);
}
}
Please be aware of the security implications this thing brings. SilverStripe is purposely built to not allow content authors to write template files. A content author can not only call the currently scoped object but also all global template variables. This includes $SiteConfig, $List, .... Therefore a "bad" content author can write a template like <% loop $List('SilverStripe\Security\Member') %>$ID $FirstName $LastName $Email $Salt $Password<% end_loop %> or perhaps might access methods that have file access. So only do this if you trust your content authors
Use shortcodes instead of variables. But I never liked shortcodes, so I don't remember how they work. You'll have to lookup the docs for that.
Build your own mini template system with str_replace.
class MyDataObject extends DataObject {
private static array $db = [
'Title' => DBVarchar::class,
'HTMLContent' => DBText::class,
];
public function Foobar() { return "hello from foobar"; }
public function RenderedHTMLContent() {
return str_replace(
[
'$SiteConfig.Title',
'$SiteConfig.Tagline',
'$Title',
'$Foobar',
],
[
SiteConfig::current_site_config()->Title,
SiteConfig::current_site_config()->Tagline,
$this->Title,
$this->Foobar(),
],
$this->HTMLContent
);
}
}
I have a question about Symfony 2.
I would like to know how if there is a function implemented in Symfony 2 who return the content mime-type ?
Why mime-type cause trouble ? i have some file and i dont want everyone access to it then i made a methode who check if you have the right to access to this ressource.
chdir("Directory/".$nameoftheressource);
$file = file_get_contents($nameoftheressource);/**/
$namearray=explode(".", $nameoftheressource);
$extension=end($namearray);
$returnFile= new Response();
$returnFile->setContent($file);
if($extension == "css" )
{ $returnFile->headers->set('Content-Type', 'text/css');
return $returnFile;}
Thanks to you xabbuh this is near to work perfectly and as u said saved lot of time
now the code look like
EDIT
use Symfony\Component\HttpFoundation\BinaryFileResponse;
//some code
return new BinaryFileResponse('/Directory/'.$nameoftheressource);
But now it does display the css file but propose me to download it bt i would like to display it as a css normal css file
You can save a lot of code by using the BinaryFileResponse class which among other things automatically adds the right content type header.
It seems that you want to serve a protected CSS file. In this case, you can use the following code and protect the access to this controller using the Symfony Security system:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
class DefaultController extends Controller
{
/**
* #Route("/css/app.css", name="css")
* #Security("has_role('ROLE_ADMIN')")
*/
public function renderCss()
{
$cssFilePath = $this->container->getParameter('kernel.root_dir').'/data/app.css';
$cssContent = file_get_contents($cssFilePath);
return Response($cssContent, 200, array('Content-Type' => 'text/css'));
}
}
In my Symfony2 app, I want to globally fetch a value from my database on each template and don't want to call on each Controller. I know I could define that as a service and inject that service into my twig templates (by defining it as a twig global).
Is that the common and recommended way? Or should I rather create an abstract Controller class where I fetch that value in my constructor and then inherit from all my other Controllers?
Note: It is actually not a static value that is the same for all users, but it is a user specific value which is different for each user.
If this variables are used to render the same spot on your page you can render an embedded controller. Like this:
<div id="sidebar">
{{ render(controller('YourBundle:User:stats')) }}
</div>
This will inject whole output of YourBundle/UserController/statsAction to the #sidebar div. Inside this action you can extract all inforamtion that you need.
If you need to use this variables in other way maybe you should look at response event.
Are you familiar with event listeners? http://symfony.com/doc/current/cookbook/service_container/event_listener.html
An event listener can be used to inject twig globals.
class ModelEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => array(
array('doProject', -1300),
),
KernelEvents::VIEW => array(
array('doView', -2100),
),
);
}
public function doProject(FilterControllerEvent $event)
{
$project = $whatever_is_needed_to_find_the_project();
if (!$project) throw new NotFoundHttpException('Project not found ' . $projectSearch);
// Add to request
$event->getRequest()->attributes->set('project',$project);
// Give all twig templates access to project
$twig = $this->container->get('twig');
$twig->addGlobal('project',$project);
}
# services.yml
cerad_core__model__event_listener:
class: '%cerad_core__model__event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
If it's a user value like you said you can get app.user.XXX on every twig template you need without processing nothing ;)
I need a simple way to fallback on a default template if no mobile version exists.
With some regular expressions I recognize mobile platforms and want to render a template with the following pattern:
<template_name>.mobile.html.twig
But if this template doesn't exist, I want it to automatically fallback on:
<template_name>.html.twig
which always exists.
I tried nearly all the answers from this post:
Symfony 2 load different template depending on user agent properties
but with no success. Unfortunately there are no version numbers referenced.
At the moment I am trying to copy and modify the default twig loader.
By the way, What I want to achieve with this is the possibility to deploy different templates for mobile devices by just adding a template of the same name and adding a .mobile.
UPDATE:
http://www.99bugs.com/handling-mobile-template-switching-in-symfony2/
This one is also a good approach. It modifies the format property of the request object which affects the automatic template guessing when you don't specify a template in the controller with the render function (or annotation) but just return an array.
Resulting template name:
view/<controller>/<action>.<request format>.<engine>
So you could switch the request format from html to mobile.html based on the device detection.
The downside of this is that every template needs a mobile.html pendant (which then could just include the non-mobile version if not needed).
UPDATE:
Besides using a custom templating provider there is also the possibility to hook into the kernel.view event.
You could create a service to handle it and then use it in the same way that you do the templating service like so..
Create a service with the templating and request service injected into it..
Service (YAML)
acme.templating:
class: Acme\AcmeBundle\Templating\TemplatingProvider
scope: request
arguments:
- #templating
- #request // I assume you use request in your platform decision logic,
// otherwise you don't needs this or the scope part
- 'html'
Class
class TemplatingProvider
{
private $fallback;
private $platform;
... __construct($templating, $request, $fallback) etc
private function setPlatform() ... Your platform decision logic
private function getPlatform()
{
if (null === $this->platform) {
$this->setPlatform();
}
return $this->platform;
}
private function getTemplateName($name, $platform)
{
if ($platform === 'html') {
return $name;
}
$template = explode('.', $name);
$template = array_merge(
array_slice($template, 0, -2),
array($platform),
array_slice($template, -2)
);
return implode('.', $template);
}
public function renderResponse($name, array $parameters = array())
{
$newname = $this->getTemplateName($name, $this->getPlatform());
if ($this->templating->exists($newname)) {
return $this->templating->render($newname);
}
return $this->templating->renderResponse($this->getTemplateName(
$name, $this->fallback));
}
And then you could just call your templating service instead of the current one..
return $this->container->get('acme.templating')
->renderResponse('<template_name>.html.twig', array());
Can't you check if the template exist before ?
if ( $this->get('templating')->exists('<templatename>.html.twig') ) {
// return this->render(yourtemplate)
} else {
// return your default template
}
OR :
You can create a generic method, to insert in your root controller like :
public function renderMobile($templateName, $params)
{
$templateShortName = explode('.html.twig', $templateName)[0];
$mobileName = $templateShortName.'.mobile.html.twig';
if ( $this->get('templating')->exists($mobileName) ) {
return $this->renderView($mobileName, $params);
} else {
return $this->renderView($templateName, $params)
}
}
with this you can do :
return $this->renderMobile('yourtemplate', [yourparams]);
You can easily do this by harnessing the bundle inheritance properties in Symfony2 http://symfony.com/doc/current/cookbook/bundles/inheritance.html
create a bundle which holds your desktop templates (AcmeDemoDesktopBundle)
create a bundle which will hold your mobile templates (AcmeDemoMobileBundle) and mark the parent as AcmeDemoDesktopBundle
Then when you render a template simply call AcmeDemoMobileBundle:: - if the template exists, it'll be rendered otherwise you'll neatly fall back to the desktop version. No extra code, listeners or anything none-obvious required.
The downside of this of course is that you move your templates out of the individual bundles.
The fallback behavior you describe isn't that easy to implement (we found out the hard way..). Good news is we wanted the same setup as you ask for and ended up using the LiipThemeBundle for this purpose. It allows you to have different "themes" based on for example a device. It will do the fallback part for you.
For example:
Rendering a template:
#BundleName/Resources/template.html.twig
Will render and fallback to in order:
app/Resources/themes/phone/BundleName/template.html.twig
app/Resources/BundleName/views/template.html.twig
src/BundleName/Resources/themes/phone/template.html.twig
src/BundleName/Resources/views/template.html.twig
Edit: so with this approach you can have default templates that will always be the final fallback and have a special template for mobile where you need it.
I have set up my Admin class to render a custom template:
public function getTemplate($name)
{
switch ($name)
{
default:
case 'list':
return 'MyBundle:Admin:list.html.twig';
break;
return parent::getTemplate($name);
break;
}
}
This is working OK. I can enter some html in my template file and it renders OK. However, I want to extend the existing templates from the admin bundle as I only want to make some minor changes for this entity.
I've added the following to my template file:
{% extends 'SonataAdminBundle:CRUD:base_list.html.twig' %}
But this gives me the following error:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 77 bytes)
Can anyone tell me what I am doing wrong?
Your switch/case is incorrect.
It should be:
public function getTemplate($name)
{
switch ($name) {
case 'list':
return 'MyBundle:Admin:list.html.twig';
break;
default:
return parent::getTemplate($name);
break;
}
}
I'm not sure if you are doing anything 'wrong' (besides the weird case syntax that doesn't do what I assume you think it does, see http://php.net/manual/en/control-structures.switch.php and scroll down to section describing the importance of 'break' statements).
It does seem like it's possible put symfony in an infinite loop when extending templates. I've seen this with a couple of templates. I haven't figured out exactly what triggers it, but I think it has something to do with bundle inheritance using EasyExtends. In my application I had a child sonata-admin bundle:
class ApplicationSonataAdminBundle extends Bundle
{
/**
* {#inheritdoc}
*/
public function getParent()
{
return 'SonataAdminBundle';
}
}
I then had overridden the standard_layout.html.twig with just the contents:
{% extends "SonataAdminBundle::standard_layout.html.twig" %}
This was causing "SonataAdminBundle::standard_layout.html.twig" to be loaded an infinite number of times because the template seems to effectively be extending itself.
Assuming your setup is similar to mine. I suspect that the only way to try to do what you are trying to do is to use a different template name (e.g. "my_standard_layout.html.twig") and then set that template as the application wide default as described here: https://sonata-project.org/bundles/admin/master/doc/reference/templates.html#configuring-templates