This question already has an answer here:
How let Twig write the HTML output to a file, instead to a browser?
(1 answer)
Closed 1 year ago.
In this code below I have just generated in the browser a .xml page that shows a specific sitemap of the distributor route.
I just want to save this generated .xml in a file (in /public/sitemap folder).
what I have, and what I WANT TO USE, are only this two (like the render at the bottom of the Controller):
the urls array (see: inside the foreach)
the template of the xml file
I have already found a possible solution but is not pratical, and is not what I want. (Create new xml file and save in system using symfony2.8)
Controller:
/**
* #Route("/sitemap/sitemap.xml", name="distributor_sitemap", defaults={"_format"="xml"})
*/
public function getSitemap(Request $request) : Response
{
$urls = [];
$hostname = $request->getSchemeAndHttpHost();
// return an array of routes
$routes = $this->sitemapService->getThreeRoutesByIndexes('/distributor/');
foreach ($routes as $route){
$urls[] = array('loc' => $this->generateUrl($route));
}
// save locally the file
// -----------------------------
// ----- add the code HERE -----
// -----------------------------
// return response in XML format
$response = new Response(
$this->renderView('sitemap/sitemap.html.twig', array(
'urls' => $urls,
'hostname' => $hostname
))
);
$response->headers->set('Content-type', 'text/xml');
return $response;
}
Template:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
{% for url in urls %}
<url>{# check if hostname is not alreay in url#}
<loc>{%if url.loc|replace({hostname:''}) == url.loc%}{{hostname}}{{url.loc}}{%else%}{{url.loc}}{%endif%}</loc>
{% if url.lastmod is defined %}
<lastmod>{{url.lastmod}}</lastmod>
{% endif %}
{% if url.changefreq is defined %}
<changefreq>{{url.changefreq}}</changefreq>
{% endif %}
{% if url.priority is defined %}
<priority>{{url.priority}}</priority>
{% endif %}
{% if url.image is defined and url.image is not empty %}
<image:image>
<image:loc>{%if url.image.loc|replace({hostname:''}) == url.image.loc%}{{hostname}}{{url.image.loc}}{%else%}{{url.image.loc}}{%endif%}</image:loc>
<image:title>{{ url.image.title }}</image:title>
</image:image>
{% endif %}
</url>
{% endfor %}
The Browser result
So again the final result is to generate a .xml file in the /public/sitemap folder, and this file must contain the urls array informations disposed by the template, as it is showed in the browser. ( all the code that to this should be added in the Controller near the ---- add the code HERE ----)
Assign the content of your render template to a var, then write it in the file you want.
/**
* #Route("/sitemap/sitemap.xml", name="distributor_sitemap", defaults={"_format"="xml"})
*/
public function getSitemap(Request $request) : Response
{
$urls = [];
$hostname = $request->getSchemeAndHttpHost();
// return an array of routes
$routes = $this->sitemapService->getThreeRoutesByIndexes('/distributor/');
foreach ($routes as $route){
$urls[] = array('loc' => $this->generateUrl($route));
}
// save locally the file
$xmlContent = $this->renderView('sitemap/sitemap.html.twig', array(
'urls' => $urls,
'hostname' => $hostname
));
// Assuming the folder sitemap exists in `public`
$path = $this->getParameter('kernel.project_dir') . '/public/sitemap/sitemap.xml';
$fileSystem = new Filesystem();
$fileSystem->dumpFile($path, $xmlContent);
// -----------------------------
// ----- add the code HERE -----
// -----------------------------
// return response in XML format
$response = new Response($xmlContent);
$response->headers->set('Content-type', 'text/xml');
return $response;
}
Related
I want to display a user picture (avatar) and some more fields in the menu.html.twig template.
I know that we can display these fields in a user.html.twig template.
{{ content.user_picture }}
{{ user.getDisplayName() }}
{{ content.field_name_user[0] }}
and etc.
But I want to display these fields in the menu.html.twig template.
As I think. we can make a variable in preprocess_block () and print the desired value.
Or if there is no necessary variable in the template - do it in the preprocessor of this template!
Help please make a decision on this issue. And what code you need to write.
It is better to write a pre-process and define a variable and insert the desired elements in it.
hook_preprocess_menu__menu_name(array &$variables) {
$userDetails = \Drupal\user\Entity\User::load(\Drupal::currentUser()->id());
/**
fetch the desired elements and pass to $variables eg:
**/
$variables['userPictureUrl'] = $userDetails->user_picture->entity->url();
}
You can use hook_preprocess_menu:
function YOURMODULE_preprocess_menu(&$variables)
{
$uid = \Drupal::currentUser()->id();
if($uid > 0)
{
// Load user
$user = User::load($uid);
$userName = $user->getUsername();
// If user have a picture, add it to variable
if(!$user->user_picture->isEmpty()){
$pictureUri = $user->user_picture->entity->getFileUri();
// Add style to picture
$userPicture = [
'#theme' => 'image_style',
'#style_name' => 'profile_picture',
'#uri' => $pictureUri,
];
}
// Set variables
$variables['MYMODULE'] = [
'profile_name' => $userName,
'profile_picture' => $userPicture,
'profile_id' => $userId
];
}
}
End show in your menu.html.twig file:
{{ YOURMODULE.profile_name }}
{{ YOURMODULE.profile_picture }}
{{ YOURMODULE.profile_id }}
Lets say that i have the following route annotations for a controller action:
* #Route("/post/{post_id}/", name="post.view")
* #Route("/post/", name="post.current_view")
And I want to use twig to generate the url for this:
{{ url(basePath~'view', {'post_id':post.postId}) }}
//basePath will either be "post." or "post.current_"
What i currently get is:
domain.com/post/1/
domain.com/post/?post_id=1
What i want though is for the second route to be generated ignoring any "EXTRA" parameters passed to it so that i would only get:
domain.com/post/
Does anyone know if this is something that can be natively accomplished? I know i could right a custom twig function that uses the router and then i can generate the routes and strip the query string but i want to avoid that if there is an easy toggle somewhere that i have missed.
Solution #1 you could just add an if clause
{% if BasePath == 'post.' %}
{{ url(BasePath~'view', {'post_id':post.postId}) }}
{% elseif BasePath == 'post.current_' %}
{{ url(BasePath~'view') }}
{% endif %}
maybe not the most elegant but should work.
Solution #2
spliting url with question marks and getting the first string
{% set myUrl = url(basePath~'view', {'post_id':post.postId}) %}
{{ myUrl|split("?")|first }}
Solution #3 Or you can override the url function by extending RoutingExtension class of twig.
Symfony\Bridge\Twig\Extension\RoutingExtension
can find an example here with path but url should be the same.
You should override this function
public function getUrl($name, $parameters = array(), $schemeRelative = false)
{
return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL);
}
your function could look like this:
public function getUrl($name, $parameters = array(), $schemeRelative = false)
{
$yourUrl = parent::getUrl($name, $parameters = array(), $schemeRelative = false);
return strstr($yourUrl, '?' , true);
}
what id oes it removes everything afther the question mark.
To override the default class you have to add to the parameters
twig.extension.routing.class: MyNamespace\MyRoutingExtension
I guess not, you need a preg_replace filter and this isn't natively defined
I have this function in the controller, that returns a form with a select box. When the values are selected, I retrieve them with
$manifestations = $form['manifestations']->getData();
then put them in a repository function that queries the database $invites = $repository->searchInviteByManif($manifestations);
public function indexAction() {
$entity = new Invite();
$form = $this->createForm(new ManifSearchType(), $entity);
$request = $this->get('request');
$invites = null;
if ($request->getMethod() == 'POST') {
$form->handleRequest($request);
$message = '';
$manifestations = $form['manifestations']->getData();
$repository = $this->getDoctrine()
->getManager()
->getRepository('PrifProtocoleBundle:Invite');
$invites = $repository->searchInviteByManif($manifestations);
$response1 = $this->render('PrifProtocoleBundle:Invite:index.html.twig', array(
'form' => $form->createView(),
'entities' => $invites,
'message' => $message,
));
return $response1;
}
return array(
'form' => $form->createView(),
'entities' => $invites,
);
}
This function then returns a view index.html.twig with a table and all the fields found in the db.
What I want is to export all the queried data $invites in a CSV file, by clicking on a link directly from the HTML table.
So I've put an href="" link in the Twig file,
{% if message is defined %}
<div class="pdf">
<img height="40px" width="40px" src={{ asset('bundles/prifprotocole/images/excel.jpg') }}>
{% for entity in entities %}
<tr class="{{ cycle(['odd', 'even'], loop.index0) }}">
<td>{% if entity.etat == 1 %} Actif {% else %} Inactif {% endif %}</td>
<td>{{ entity.titreGrade }} {{ entity.prenom }} {{ entity.nom }}</td>
<td>{{ entity.fonction }}</td>
This is how I use to export the CSV file without the link :
$response2 = $this->render('PrifProtocoleBundle:Invite:export.csv.twig', array(
'entities' => $invites));
$response2->headers->set('Content-Type', 'text/csv');
$csvfile = $response2->headers->set('Content-Disposition', 'attachment; filename="export.csv"');
return $csvfile;
export.csv.twig file
{% for entity in entities %}
Id {{ entity.id }};
Etat {{ entity.etat }};
Titregrade {{ entity.titreGrade }};
Prenom {{ entity.prenom }};
Nom {{ entity.nom }};
Fonction {{ entity.fonction }};
{% endfor %}
Can someone give me a detailed solution on how to perform this? Much thanks!
You should simply pass the filter criterias, here the $manifestations array as a route parameter, by serializing it first.
Then, you should put a controller like downloadCsvAction() handling this route, querying the $invites from the database, and rendering your export.csv.twig template.
Another option, if you cannot serialize the data in URI, is to create a form with hidden fields containing the data. Then you replace the download link with the submit button of this hidden form.
Guillaume's answer is great if you don't mind fetching your data twice. Once when you display your page, and then when you download the CSV. Another way to do this is to cache the answer. I will give an example using memcache (which is what I use).
First, make sure that memcache is active in your php.ini.
Then before rendering your first page (after fetching the data), cache it:
$key = "csv.$userId";
$memcache = new \Memcache();
$memcache->connect($yourServerHost, $yourServerPort);
$memcache->set($key, json_encode($invites), MEMCACHE_COMPRESSED, 86400); // 24h
The trick is to find a good key, to add your report to the cache, and then be able to retrieve it. It has to be unique. Here, I assume that you have a user that is logged in, and I use the user ID to create a key. If that is not the case, you could create yourself a token, and pass it when rendering the page (along with $invites). This way, you can add the token to the URL to generate the CSV.
Then, in your second action, where you download the CSV, you just fetch the cached data:
$memcache = new \Memcache();
$memcache->connect($yourServerHost, $yourServerPort);
$json = $memcache->get($key);
$invites = json_decode($json , true);
That's it. You should probably create yourself a service for Memcache, so you don't have to create an object, and connect everytime.
I'm sharing templates between client and server and would like to output the raw template inside a script tag which is possible with verbatim.
http://twig.sensiolabs.org/doc/tags/verbatim.html
However it would be nicer if this could be applied as a filter to the include but it doesn't seem possible?
I'm new to twig so excuse me if i've missed obvious functionality.
I ran into the same problem, and this page came up in my search results. In the time since this question was answered, the Twig developers added this functionality into the library. I figured I should add some details for future searchers.
The functionality to include raw text (aka for client-side templates using the same syntax as Twig) is accomplished with the source function.
Ie: {{ source('path/to/template.html.twig') }}
http://twig.sensiolabs.org/doc/functions/source.html
I was looking for something like this too because I'm using Twig.js for some client-side templating along with Symfony. I was trying to share templates between the server-side and client-side code, so I needed content to be parsed in some cases and treated as verbatim in others, which proved to be a bit tricky.
I couldn't find anything built into Twig to help with this, but luckily, it's pretty easy to extend Twig to get what you're looking for. I implemented it as a function, but you may be able to do it as a filter too.
services.yml
statsidekick.twig.include_as_template_extension:
class: StatSidekick\AnalysisBundle\Twig\IncludeAsTemplateExtension
tags:
- { name: twig.extension }
IncludeAsTemplateExtension.php
<?php
namespace StatSidekick\AnalysisBundle\Twig;
use Twig_Environment;
use Twig_Extension;
class IncludeAsTemplateExtension extends Twig_Extension {
/**
* Returns a list of global functions to add to the existing list.
*
* #return array An array of global functions
*/
public function getFunctions() {
return array(
new \Twig_SimpleFunction( 'include_as_template', array( $this, 'includeAsTemplate' ), array( 'needs_environment' => true, 'is_safe' => array( 'html' ) ) )
);
}
function includeAsTemplate( Twig_Environment $env, $location, $id ) {
$contents = $env->getLoader()->getSource( $location );
return "<script data-template-id=\"{$id}\" type=\"text/x-twig-template\">{$contents}</script>";
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName() {
return 'include_as_template_extension';
}
}
Usage in Twig file
{{ include_as_template( 'Your:Template:here.html.twig', 'template-id' ) }}
If you have a Twig file like this:
<ul class="message-list">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
The output will be this:
<script data-template-id="template-id" type="text/x-twig-template">
<ul class="message-list">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</script>
The work is derived from Kari Söderholm's answer here. Hope that helps!
I am using Symfony2 and Twig. I have a function (below) in my controller that returns a specific text. Is it possible to call that function directly from my template and change the {{text}} in my template to whatever the function returns, possibly via Ajax?
Here's my function:
public function generateCode($url) {
$url = $_SERVER['SERVER_NAME'] . '/embed/' . $url;
$return = '<iframe>'.$url.'</iframe>';
return $return;
}
Another controller function calls the function above and renders my template:
public function getCodeAction($url) {
$text = $this->generateCode($url);
return $this->render('MyMyBundle:User:code.html.twig', array('text' => $text));
}
In my template I am using:
{{ text }}
to display the value.
In Symfony 2.2, this was changed.
The render tag signature and arguments changed.
Before:
{% render 'BlogBundle:Post:list' with { 'limit': 2 }, { 'alt': BlogBundle:Post:error' } %}
After:
{% render controller('BlogBundle:Post:list', { 'limit': 2 }), { 'alt': 'BlogBundle:Post:error' } %}
or
{{ render(controller('BlogBundle:Post:list', { 'limit': 2 }), { 'alt': 'BlogBundle:Post:error'}) }}
Note: The function is the preferred way.
See https://github.com/symfony/symfony/blob/2.2/UPGRADE-2.2.md
You can use ajax if you have dynamic data, but as far as I can see from your brief info, you can always execute that controller function directly from your view:
{% render "MyMyBundle:User:generateCode" with { 'url': 'your url here' } %}
More Information on this available at:
http://symfony.com/doc/2.0/quick_tour/the_view.html, under Embedding other Controllers
For the record, in new versions you need to use the absolute URL:
{{ render url('my_route_id', {'param': value}) }}
{{ render(controller("AcmeDemoBundle:Demo:topArticles", {'num': 10})) }}
In Silex I solved it like this:
{{ render(url('route_name', {'param': value})) }}
If you do not have the route name, URL can be used:
{{ render(app.request.baseUrl ~ '/some-path/' ~ value) }}
If using URL we should always concat the baseUrl.
Symfony 2.6+
in twig:
{{ render(controller('AppBundle:PropertySearch:featuredProperties', {'limit': 15})) }}
controller:
/**
* featuredPropertiesAction
*
* #param Request $request
* #param int $limit
*
* #return Response
*/
public function featuredPropertiesAction(Request $request, $limit)
{
$search = $this->resultsHelper->featuredSearch($limit);
return $this->render('HASearchBundle::featured_properties.html.twig', [
'search' => $search,
]);
}