how to print node object in drupal 8 - drupal

in drupal 7 we could use kpr() from devel module or php print_r() and var_dump() functions to print $node object in node.tpl file or hook preprocess node in purpose of debugging and finding a field or ..
but in drupal 8 we can't use php functions in twig and I tried kint() and dump() to print node in twig but no success. ( I've already set debug: true in services.yml ) ( more detail: kint(node) in twig file makes an infinite loop and causes memory size exhausted in browser).
so the question is how to print node object in drupal 8 using twig or hook preprocess node?
Q update v 1.0:
I have custom twig template for article: node--article.html.twig and it works fine:
<article>
<div>
{{ content.body|render }} {# this works #}
</div>
<footer>
{{ kint(node.field_custom.value) }} {# prints the custom field value without any problem #}
{{ kint(node) }} {# this causes infinite loop and memory issue #}
{{ content }} {# prints all content fields without any problem #}
{{ kint(content) }} {# nothing happen or display with this! #}
{{ kint(label) }} {# infinite like node #}
</footer>

I knew that the kint() function is not the problem because it displays custom arrays and objects that I created for test.
so problem was the node object itself. it was very big and printing it using kint() made memory limit issues. when I changed memory limit to -1 memory_limit= -1 in php.ini file for testing , it took all 16GB ram of my system and it wasn't enough!
so I decreased the depth of kint() function from 7 to 4 in modules/devel/kint/kint/config.default.php ( $_kintSettings['maxLevels'] = 4; ) and memory_limit=128M in php.ini.
now every thing works,
hope it help someone.

Another option is to use the VarDumper module which isn't quite so memory intensive as Kint. I find it a bit more user-friendly and better-looking!
function YOURTHEME_preprocess_node(&$variables){
vardumper($variables);
}

1- install Devel + Twig VarDumper
2- in www.example.com/admin/config/development/devel
enable Display $page array
3- in www.example.com/admin/config/development/devel
enable Symfony var-dumper
4- in twig add
{{ dump() }} {# all #}
{{ dump(attributes) }} {# one #}
{{ dump(_charset) }} {# #}

Related

Translation of flash messages with parameters in Symfony 4

I have a problem with flash messages in Symfony 4 and translation.
Translation of simple flash messages is working fine:
$this->addFlash('success', 'flashmessage.project_deleted');
But now I want to add some parameters to the flash messages and I have no idea how to handle it. I tried a lot, but nothing is working. I want to show in the flash messages the title of projects after f.e. removing. For example:
$this->addFlash('success', sprintf('flashmessage.project_deleted: %s', $project->getTitle()));
But the translation is not recognized, because the parameter is replaces before translation happens (I think so). And it should also be possible to have parameters in the middle of a string and not only at the end or at the beginning and ideally more than one parameter.
I'm using this in my Controller which extends AbstractController.
Does anybody has a solution for this?
Usually you would pass in the parameters to the translation, so your code snippet should probably look your first example and then in twig you would have something like this:
{% for message in app.flashes('success') %}
<div class="alert alert-success">
{{ message|trans({ 'title': project.title }) }}
</div>
{% endfor %}
The translation then should contain the parameter that is replaced:
flashmessage:
project_created: 'The project "%title%" was created successfully.'
project_deleted: 'You successfully deleted the project "%title%".'
...
Obviously the downside is that you have to dynamically pass in the variables which does not make much sense for flash messages, as not all of them will require these parameters. Also, as you already mentioned, when you deleted the project you will probably not have it available anymore in the template.
Instead I would recommend translating the message before passing it into the flash bag:
$this->addFlash(
'success',
$this->translator->translate(
'flashmessage.project_deleted',
[
'title' => $project->getTitle(),
]
)
);
This will require that you pass in the translator to your controller. You could either create your own base controller similar to Symfony's AbstractController for this and create something like a $this->trans()-method to make it easier to translate things inside your controller. Also, you will still have to make sure that $project->getTitle() will still return a value, so you probably want to call this before you actually delete the entry or have the data in memory.
When you do it this way, then you should not translate the flash messages in the template itself because they are already translated. This will still work because when Symfony tries to translate the already translated message, e.g. You successfully deleted the project "foo". then it will not find a translation and just print the original text instead, but you will get warnings in your logs about missing translations. The solution is to remove the |trans in your template (see first snippet).
A possible solution is to add another flash with serialized parameters.
Then, when you display your flash message, check if that extra flash exists and, if so, deserialize it and use it as argument.
Example follows.
In controller:
$this->addFlash('success', 'flashmessage.project_deleted');
$this->addFlash('_params', serialize(['%project%' => $project->getTitle()]));
In template:
{% flashMessage = app.session.flashbag.get('info') %}
{% if app.session.flashbag.has('_params') %}
{% set flashParams = app.session.flashbag.get('_params')|first|unserialize %}
{{ flashMessage|trans(flashParams) }}
{% else %}
{{ flashMessage|trans }}
{% endif %}
You need to create a Twig extension that defines an unserialize filter (or use a library that provides it)
Since Symfony 5.2 you can use the TranslatableMessage object to achieve this.
https://symfony.com/doc/current/translation.html#translatable-objects
use Symfony\Component\Translation\TranslatableMessage;
$this->addFlash(
'success',
new TranslatableMessage(
'flashmessage.project_deleted',
['%project%' => $project->getTitle()]
)
);
Then in your Twig template you only need to use {{ flashMessage|trans }}.
This works without injecting the Translator service, or messing about with anything in Twig.
Have a look at the ICU Message Format: https://symfony.com/doc/current/translation/message_format.html

Symfony forcing translation into different language than the current one

I have the locale set to one language. Let's say german "de" and I want to have a part of the whole text translated into different languages (Ex. This happened 100 years ago -> This happened vor 100 Jahre). For this I am trying the following code:
//this returns 'site.dates.years'
{% set yearsText = yearsCount|displayTimeDivisions() %}
( {{ yearsCount }} {% trans with {'%content%': yearsText } from "messages" into "en" %}%content%{% endtrans %}
after using the trans method i get the string from yearsText ('site.dates.years') and not the translated content. Is this even possible to translate or I should drop it?
This works as expected, what you are trying to translate is %content%, not site.dates.years. Try this:
//this returns 'site.dates.years'
{% set yearsText = yearsCount|displayTimeDivisions() %}
( {{ yearsCount }} {% trans from "messages" into "en" %}{{ yearsText }}{% endtrans %}
Edit
The previous suggestion doesn't work as using trans in that way only works with simple text, not with variables.
This works for me:
{{ yearsText | trans({}, 'messages', 'en') }}
EDIT: clear cache
Sometimes Symfony has a too strong caching mechanism. If you work without locales first, and then modify controllers and twigs to be multi-locale and you also add a new messages file, Symfony loads all new controller/twigs but somehow misses that there are new message files. A simple cache clear, done ONLY ONCE (!) right after you add the message files, solves this problem. And thenceforth you don't need to clear the cache every time you change a translation.
Try this as a last resort. If this doesn't help I'd recommend to read carefully the documentation (link in the last line of this answer):
app/console cache:clear --env=dev
You may try one or all (!) of these, it's impossible to tell what is going wrong.
{{ yearsCount|trans }}
{{ 'site.dates.years'|trans }}
See if these translate. If the second does, you're setting the variable wrong, somehow.
If not, pass the _locale along in the route:
defaults: { _controller: Bundle:Controller:action, _locale: de }
Or have it as a route parameter:
path: /a/path/{_locale}/whatever
defaults: { _controller: Bundle:Controller:action }
requirements:
_locale: en|de
Print the locale to be sure it's properly set in the twig:
{{ app.request.locale }}
{{ app.request.getLocale() }}
If the locale prints out fine but the translation still does not work, it's because Symfony does not find the identifier. Go to the translations file and check that it's written correctly:
site:
dates:
years: Jahre
If you find it in messages.de.yml then maybe it's the wrong placement of the file. The loader searches for these files:
Symfony looks for message files (i.e. translations) in the following default locations:
the app/Resources/translations directory;
the app/Resources/MyAppBundle/translations directory;
the Resources/translations/ directory inside of any bundle.
If fiddling around with these files doesn't yield results, it's a configuration error in config.yml, or many overlapping errors (all of the above?) that work against you.
Here's the source of all pain/info about translations, but you've probably memorized every paragraph, by now: Symfony translations.

Twig safe html does not work inside macro

Using a twig macro on safe content seems to remove the safe flags
{% macro identity(value) %}
{{ value }}
{% endmacro %}
{{ "<br>"| raw }}
{{ _self.identity( "<br>"| raw) }}
{% autoescape false %}
{{ _self.identity( "<br>"| raw) }}
{% endautoescape %}
The code above show only 2 <br>. Is there a way to disable escaping, or marking my content as safe without editing my twig macro ?
You are indeed correct that you must put the raw filter in the macro in order to keep whatever tags inside the macro from escaping their input and this would, at least in my opinion, be the expected behavior.
In Twig, macros are not function or filters, they are simply shortcuts that keep you from repeating boilerplate tags and code. It helps, when you create and use a macro, to think of writing the whole code each time you will use it i.e.
Instead of thinking of your macro as a call {{ _self.identity( "<br>"| raw ) }} actually think that what you are writing is {{ {{ "<br>"| raw }} }} because you are passing the output of the macro's input as a literal, not the expression you have written.
If you would like your macros to operate using the provided input as a template, rather than, as designed, operating on the output of whatever you pass in, you may look at enabling the template_from_string function.

Salt file server "lazy copy" to minions behaviour / testing a file/directory exists

I'm trying to have salt automatically deploy files to users' home directory (after creating them). The directory structure (under file_roots) is as follows:
users/
init.sls
user_list.jinja
files/
userX/
some_dir_I_want_deployed_to_userX_homedir/
some_script_I_want_deployed.sh
etc
user_list.jinja has a list of users. The idea is that if there's a directory for userX under files/, that directory subtree should be deployed to userX's home. However, I'd rather not have to create empty directories for userY etc if they don't have anything to deploy, so I'm trying to test existence of the directory to avoid an error.
Here's the relevant excerpt from users/init.sls:
{% from "users/user_list.jinja" import users with context %}
{% for name, user in users.items() %}
{% set files_path = '{0}/files/{1}'.format(salt['file.dirname'](tplpath), name) %}
{% if salt['file.directory_exists'](files_path) %}
{{ user.home }}:
file.recurse:
- source: salt://users/files/{{ name }}
- user: {{ name }}
- group: {{ name }}
{% endif %}
{% endfor %}
With a fair bit of debugging (which was rather necessary to figure out the above), I've worked out that this is a chicken-and-egg situation, namely:
The file.directory_exists test is run on the minion (that's fair)
The salt file-server seems to have an optimization whereby it only deploys to minion (to their local cache under /var/cache/salt/minion/file) items which are referenced in states (more likely, the minions only request stuff which they see referenced).
So unless the directory subtree for users/files/userX already exists on the minion, file.directory_exists returns False, which means the entire portion which starts with {{ user.home }} is suppressed during Jinja rendering; hence it's not referenced and the copy (to the minion's local cache) never occurs.
If I manually create an empty directory structure for users/files/userX on the minion, everything starts to work. This tells me my theory is at least partially correct.
I can "feel" I'm doing smth wrong here (the whole thing feels too procedural). What's the best approach to achieve this? The requirement itself doesn't seem too far-fetched.
The more salt-ish way to do this is to have some data in pillar data and check for the existence of that key. Something like user.enabled. But, that would require you to keep settings in 2 places, in pillar and in the file_roots.
You don't want to check for the existence of the directory on the minion server, you want to check for the existence of the file in your file roots.
Unfortunately, I don't think it's possible to check for the existence of a file under salt:// scheme. If I'm wrong, then all you have to do is replace your check for directory existence with the syntax to check for file_root file existence.
The more salt-ish approach is to define which users are enabled/disabled on each machine in pillar data, and use the user module to add them to the system.
https://github.com/saltstack-formulas/users-formula
You can add to the standard pillar that's given with the standard users formula and put a key that says to sync files
#pillar
users:
ausername:
fullname: A User
syncfiles: True
busername:
fullname: B User
syncfiles: False
#state
{% for name, user in pillar.get('users', {}).items() if user.absent is not defined or not user.absent %}
{% if user.syncfiles %}
/home/{{ user.username }}:
file.recurse:
- source: salt://users/files/{{ user.username }}
- user: {{ user.username }}
{% if user.prime_group %}
- group: {{ user.prime_group.name }}
{% endif %}
{% endif %}
{% endfor %}
Actually, the standard users-formula already handles pre-populating with files. I would just use that formula. I know it's keeping track of data in 2 places, but you get the bonus of leveraging an already built state file.

Symfony2 HTML in the trans twig filter

I use the Symfony2.1 and have the default config.yml
Documentation said:
{# but static strings are never escaped #}
{{ '<h3>foo</h3>'|trans }}
But if I copy and paste it into the my empty template (without any additional autoescapes or another) I got the escaped string <h3>foo</h3>. What I do wrong?
Try it with the twig raw filter:
{{ '<h3>foo</h3>' | trans | raw }}
However, do not use the raw filter if you are processing any user input! It allows for cross-site-scripting attacks, according to the creators of Symfony. See this similar question for a secure but more tedious alternative.
Holding HTML stuff in translations is wrong, because translators usually break it. But if you really need it:
{% trans %}<h3>foo</h3>{% endtrans %}
https://github.com/symfony/symfony/issues/2713#issuecomment-12510417

Resources