Twig: Determine whether a menu item has a translation - twig

In my Drupal 8 setup, I have two languages configured (German: default, English). Not all pages have a translation into English, but they show up in the navigation.
I'd like to highlight those menu items that link to pages that don't have a translation in the currently selected language.
So how can I do this in Twig? When I dump the menu item, I see an object of class MenuLinkContent that has a field entity which might contain the answer:
object(Drupal\menu_link_content\Plugin\Menu\MenuLinkContent)[31277]
[...]
protected 'entity' =>
object(Drupal\menu_link_content\Entity\MenuLinkContent)[31407]
[...]
protected 'translations' =>
array (size=2)
'x-default' =>
array (size=2)
...
'en' =>
array (size=2)
...
[...]
But I don't seem to be able to actually read those values.

It is easy, first, you have to add your current language to any variable by yourtheme_preprocess
yourtheme_preprocess(&$vars, $hook)
{
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();
$vars['langcode'] = $language;
}
and now in your twig template, you have to check your entity if has a translation,
{% if node.hasTranslation(langcode) %}
{% set node = node.getTranslation(langcode) %}
{% endif %}

Related

Twig multi dimensional array - Can't access values

I have a multidimensional array, but for some reason my twig is not responding with the array values.
Below is my twig Dump
array (size=2)
0 =>
object(App\Models\Entities\Strategy\CriticalSuccessFactor)[5]
private int 'csfId' => int 26
private iterable 'kpis' =>
array (size=1)
0 =>
object(App\Models\Entities\Strategy\KeyPerformanceIndicator)[10]
...
1 =>
object(App\Models\Entities\Strategy\CriticalSuccessFactor)[11]
private int 'csfId' => int 27
private iterable 'kpis' =>
array (size=1)
0 =>
object(App\Models\Entities\Strategy\KeyPerformanceIndicator)[12]
I did find this link, but it did not answer my question.
Multidimensional Array in Twig
Below is a respresentation of the array data I am working with
csfs[
private int 'csfId' => int 26
'kpis' => [
private int 'kpiId' => int 42
'objectives' => [
private int 'objectivesId' => int 40
]
]
]
when I am outputting the variable with twig I get nothing.
Here's my Twig:
{% for csf in csfs %}
{% for kpi in csf.kpis %}
<p> kpi ID : {{ kpi.kpiId }}</p>
{% endfor %}
{% endfor %}
{{ csf.csfId }} works. It prints the ID.
I can get the first array values no problem. But I cannot access kpis array
As seen in the documentation from twig,
For convenience’s sake foo.bar does the following things on the PHP
layer:
check if foo is an array and bar a valid element;
if not, and if foo is an object, check that bar is a valid property;
if not, and if foo is an object, check that bar is a valid method (even if bar is the constructor - use __construct() instead);
if not, and if foo is an object, check that getBar is a valid method;
if not, and if foo is an object, check that isBar is a valid method;
if not, and if foo is an object, check that hasBar is a valid method;
if not, return a null value.
Twig also supports a specific syntax for accessing items on PHP
arrays, foo['bar']:
check if foo is an array and bar a valid element;
if not, return a null value.
So you have two options here:
Adjust the model CriticalSuccessFactor
Either you could rename the method getKeyPerformanceIndicators to getKpis or add a method getKpis which just references getKeyPerformanceIndicators
public function getKpis() {
return $this->getKeyPerformanceIndicators();
}
Adjust your template and call the method getKeyPerformanceIndicatorsdirectly rather than let twig magically decide the method
{% for kpi in csf.getKeyPerformanceIndicators%}
...
{% endfor %}

Accessing the referencing / parent element in twig (paragraphs)

I have an entity reference field inside a (parent-)paragraph, which references multiple child-paragraphs.
Is it possible to access field values of the referencing paragraph in the children's (referenced paragraph's) twig templates?
Actually I'm just trying to count the total referenced items within one of the referenced item's twig templates itself. So I want to count its siblings + 1, if you want.
I'm aware of the fact that I could preprocess this in a module, but I would like to know if this is possible in twig.
In twig:
{% set paragraph_parent = paragraph.getParentEntity() %}
{% set width = paragraph_parent.field_width.0.value %}
<div class="{{ width }}">{{ content.field_images }}</div>
Since there is no response to this question I have to assume that this is not possible in Twig, but wanted to quick share the mentioned workaround via module...
getParentEntity()
is your friend.
A short example for making the referenced element's count available...
/* implements hook_preprocess_paragraph() (paragraph type product_teaser) */
function mymodule_preprocess_paragraph__product_teaser(&$variables) {
/* makes paragraph siblings count (+ 1/self) available to template */
$siblings_total = 1;
$paragraph = $variables['paragraph'];
$parent_paragraph = $paragraph->getParentEntity();
if ( ( isset($parent_paragraph) ) && ( $parent_paragraph->hasField('field_paragraph_reference') ) ) {
$field_content = $parent_paragraph->get('field_paragraph_reference')->getValue();
if ( isset($field_content[0]['target_id']) ) {
$siblings_total = count($field_content);
}
}
$variables['mymodule_theming_sources']['siblings_total'] = $siblings_total;
}
Another method using twig with 2 simple lines. It works fine.
{% set parent = paragraph._referringItem.parent.parent.entity %}
{{ parent.title.value }}

Set a value on a multi level array

dump(item.url.options.query)
returns: array (size=0)
{% set item = item|merge({'ref': 'xyz'}) %}
returns: no error, item is now set
{% set item.url.options.query = item.url.options.query|merge({'ref': 'xyz'}) %}
errors: Twig_Error_Syntax: Unexpected token 'punctuation'; of value '.'
How do I set the array index item.url.options.query?
You have a deeply nested array so you need to use the merge filter many times:
{% set item = item|merge({
url: item.urls|merge({
options: item.url.options|merge({
query: item.url.options.query|merge({
ref: 'xyz'
})
})
})
}) %}
If the item variable or some of the array items are objects instead of arrays, you might get an error (because the merge filter only works with arrays and Traversable objects), or the objects might get converted into arrays. If that's the case, you might want to take a look at the question DarkBee linked to.

Django-Haystack - How to use Haystack with django-comments?

I'm struggling with Django-Haystack.
I need to do an Index that have Articles and Comment articles. My doubt is how can I put in a document based index the Articles and the Comments.
How can I search for keywords in the comments and in the articles and output the article with that keywords(article comments, article)?
It is possible?
Best Regards,
The first thing to do is forget the notion that a SearchIndex must correspond exactly to a model. It's only sourced from one.
The simplest way to do this would be to add the comments to the indexed document using a template. This presume your Article model as a title field:
class ArticleIndex(SearchIndex, indexes.Indexable):
text = CharField(document=True, use_template=True)
title = CharField(model_attr='title')
def get_model(self):
return Article
Note the keyword argument use_template is set to true. The default value for this is search/indexes/{app_label}/{model_name}_{field_name}.txt. In that template just output the content you want to index. E.g.
{{ object.title|safe }}
{{ object.body|safe }}
{% for comment in object.comments.all %}
{{ comment|safe }}
{% endfor %}
While I'm afraid the specific reverse relation name here is probably wrong, that's the gist of what you want to do. Again, this is a simple way of accomplishing what you've specifically stated.
This is what worked for me:
In your models.py, presuming comments are attached to an Article, you want a method that returns comments attached to it (there is no easy way to do this):
class Article:
def comments(self):
ids = [self.id]
ctype = ContentType.objects.get_for_model(Article)
comments = Comment.objects.filter(content_type=ctype,
object_pk__in=ids,
is_removed=False)
return comments
In your search_indexes.py, make sure the ArticleIndex has use_template=True:
from django.contrib.contenttypes.models import ContentType
from django.contrib.comments.models import Comment
class ArticleIndex(SearchIndex):
text = CharField(use_template=True)
In your index template, e.g. templates/search/indexes/article_text.txt:
{% for comment in object.comments.all %}
{{ comment }}
{% endfor %}
Now, the only remaining problem is to update that specific index object when a comment is added or removed. Here we use signals:
In your models.py:
from django.dispatch import receiver
from haystack import site
from django.contrib.comments.signals import (comment_was_posted,
comment_was_flagged)
#receiver(comment_was_posted)
def comment_posted(sender, **kwargs):
site.get_index(Article).update_object(kwargs['comment'].content_object)
#receiver(comment_was_flagged)
def comment_flagged(sender, **kwargs):
site.get_index(Article).update_object(kwargs['comment'].content_object)

Haystack - Why does RealtimeSearchIndex sometimes not update my saved object

I'm using Haystack and Whoosh with Django
Within search_index.py I have this
class PageIndex(RealTimeSearchIndex):
text = CharField(document=True, use_template=True)
creator = CharField(model_attr='creator')
created = DateTimeField(model_attr='created')
org = CharField(model_attr='organisation')
site.register(Page, PageIndex)
My template looks like this
{{ object.name }}
{{ object.description }}
{{ object.template|striptags }}
{% for k,v in object.get_variables.items %}
{{ v }}
{% endfor %}
If I save the Page with an updated name or description then it updates straight away and includes the variables from get_variables.items in the template. However if I update just the variable then it doesn't update.
Is it because variable is another object that's related to it and even though I am saving on the same page it does not pick up a change to the Page? If so how do I force to update the Page item when I'm updating related objects?
I concur with Daniel Hepper, but I think the easiest solution here is to attach a listener to your related model's post_save signal (see https://docs.djangoproject.com/en/dev/topics/signals/) and in that, reindex the model.
E.g, in myapp/models.py, given model MyRelatedModel which has a foreignkey to MyModel
from myapp.search_indexes import MyModelIndex
def reindex_mymodel(sender, **kwargs):
MyModelIndex().update_object(kwargs['instance'].mymodel)
models.signals.post_save.connect(reindex_mymodel, sender=MyRelatedModel)
A RealTimeSearchIndex only updates the search index when a model it is registered on is saved or deleted, or to be more precise, when the post_save/post_delete signal of the model is emitted. These signals are not emitted if a related model is deleted/saved or when a bulk update/delete operation is executed.
To solve your problem, you could create a subclass of RealTimeSearchIndex that also updates the index on post_save/post_delete signals of the related model.
Just a note for more recent viewers of this post ---- RealTimeSearchIndex has been deprecated.
See here for the Haystack post about it.
For recent viewers, here's a solution based on the new RealtimeSignalProcessor:
In myapp/signals.py:
class RelatedRealtimeSignalProcessor(RealtimeSignalProcessor):
def handle_save(self, sender, instance, **kwargs):
if hasattr(instance, 'reindex_related'):
for related in instance.reindex_related:
related_obj = getattr(instance, related)
self.handle_save(related_obj.__class__, related_obj)
return super(RelatedRealtimeSignalProcessor, self).handle_save(sender, instance, **kwargs)
def handle_delete(self, sender, instance, **kwargs):
if hasattr(instance, 'reindex_related'):
for related in instance.reindex_related:
related_obj = getattr(instance, related)
self.handle_delete(related_obj.__class__, related_obj)
return super(RelatedRealtimeSignalProcessor, self).handle_delete(sender, instance, **kwargs)
In settings.py:
HAYSTACK_SIGNAL_PROCESSOR = 'myapp.signals.RelatedRealtimeSignalProcessor'
In models.py:
class Variable(models.Model):
reindex_related = ('page',)
page = models.ForeignKey(Page)
Now when a Variable is saved, the index for the related Page will also be updated.
(TODO: This doesn't work for extended relationships like foo__bar, or for many-to-many fields. But it should be straightforward to extend it to handle those if you need to.)

Resources