Twig Custom Function Needed To Keep Code Clean - php

I have a menu for my site that I only want to be able to display links based upon a users permissions.
Menu example:
<ul>
<li>Dashboard</li>
<li>Products</li>
<li>Suppliers</li>
</ul>
In Twig I am currently having to repeat code to accomplish what I am needing done. I DONT like doing this!
I first check to see if the user is an admin, if they are then then automatically get access to all the links...
{% set is_admin = false %}
{% if app.security.token.user.roles is iterable %}
{% for role in app.security.token.user.roles %}
{% if role == 'ROLE_ADMIN' or role == 'ROLE_SUPER_ADMIN' %}
{% set is_admin = true %}
{% endif %}
{% endfor %}
{% endif %}
This is my menu set up currently. I need to be able to authenticate whether the link can show up based upon the users roles and permissions. This is just mock up link, but in reality I could have upwards of 50 links, so as you can see this is just not a correct way to do this.
Any suggestions would be VERY welcome on how to perhaps create a function in Twig (not aware of any way to do this) or any other possible way recommended. I just need to have a reusable function.
<ul>
{% set is_dashboard = false %}
{% for role in app.user.roles %}
{% for roleAuth in role.appRoleAuthorizations %}
{% for roleAuthRoute in roleAuth.appRoleAuthorizationRoute %}
{% if roleAuthRoute.name == "dashboard" or is_admin %}
{% set is_dashboard = true %}
{% endif %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if is_dashboard == true %}
<li>Dashboard</li>
{% endif %}
{% set is_products = false %}
{% for role in app.user.roles %}
{% for roleAuth in role.appRoleAuthorizations %}
{% for roleAuthRoute in roleAuth.appRoleAuthorizationRoute %}
{% if roleAuthRoute.name == "product" or is_admin %}
{% set is_products = true %}
{% endif %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if is_products == true %}
<li>Products</li>
{% endif %}
{% set is_suppliers = false %}
{% for role in app.user.roles %}
{% for roleAuth in role.appRoleAuthorizations %}
{% for roleAuthRoute in roleAuth.appRoleAuthorizationRoute %}
{% if roleAuthRoute.name == "supplier" or is_admin %}
{% set is_suppliers = true %}
{% endif %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if not loop.last %}{% endif %}
{% endfor %}
{% if is_suppliers == true %}
<li>Suppliers</li>
{% endif %}
</ul>

Why don't you use Symfony's built-in {% is_granted() %} Twig function? Here's the doc. Using it should make your templates a bit more clean.
As for building menus, there's no need to do this in your templates, as this would break the single responsibility principle.
First, you should consider using KnpMenuBundle, as it allows you to dynamically build menus that could depend on basically any parameter that's accessible through the Service Container. The menu itself is usually built with EventListeners, which gives you a lot of flexibility.
If adding a bundle is not an option for you, then why don't you extract the checks you make in your template into a service itself? It could have access to virtually any value in your project. You could just pass the current context and user there, and get an array or object of booleans that can be then used in your template.

Related

get first item in loop

I am looping through an array and would like to add a class if it is the first item.
I have tried
{% if field|first %}
{% if field.first %}
{% if field.first() %}
{% if loop|first %}
{% if loop.first %}
{% if loop.first() %}
none work, please help. (the reason I don't use css pseudo first is that this is a grid layout and I have am doing responsive layout things with it)
{% for field in row.fields %}
<div class="c-product-table__item {% if field|first %}{{ first_item }}{% endif %} ">
<span class="c-product-table__text">{{ field.text }}</span>
</div>
{% endfor %}
I don't know why twig filters are not working, I solved it another way. I set a var and made it blank after first loop
{% set thiscount = 'first_item' %}
{% for field in row.fields %}
<div class="c-product-table__item {{ thiscount }}">
<span class="c-product-table__text">{{ field.text }}</span>
</div>
{% set thiscount = '' %}
{% endfor %}

Twig: Pass Twig Markup to an Include

I want to pass a macro(icon()) to a nested template of mine, through passing the markup as text.
But due to the |raw filter, Twig wont render the markup as Twig. Is there a way to achieve this? Basically I get this rendered {{ icon("phone-call") }}
main.html.twig
{% embed "Block/context_box_column_lane.html.twig" with {
'title': 'Test',
'subtitle': 'Subtitle',
'text': '
<p class="context-box-text">{{ icon("phone-call") }}</p>
'
} %}{% endembed %}
context_box_column_lane.html.twig
{% extends "Block/section_sidebar.html.twig" %}
{% block section_content %}
{% embed 'Block/context-box.html.twig' %}{% endembed %}
{% endblock %}
context-box.html.twig
{% from "#html/macros/icons.twig" import get as icon %}
<div class="b-context-box {% if has_border %}has-border{% endif %}">
{% if subtitle %}
<h3 class="section-subtitle">{{ subtitle|trans }}</h3>
{% endif %}
{% block content %}
{% if text is defined %}
{{ text|trans|raw }}
{% endif %}
{% endblock %}
</div>

SonataUserBundle override user profile

i want to override my user dashboard [profile show.html.twig], but i don't know how to do it. Someone can explain me or show me his code, so i can have an idea how he did it.
this is my show.html.twig:
{% extends "SonataUserBundle:Profile:action.html.twig" %}
{% block sonata_profile_content %}
{% sonata_template_box 'This is the user profile template. Feel free to override it.' %}
<div class="row row-fluid">
{% set has_center = false %}
{% for block in blocks %}
{% if block.position == 'center' %}
{% set has_center = true %}
{% endif %}
{% endfor %}
<div class="{% if has_center %}span4 col-lg-4{% else %}span6 col-lg-6{% endif %}">
{% for block in blocks %}
{% if block.position == 'left' %}
{{ sonata_block_render({ 'type': block.type, 'settings': block.settings}) }}
{% endif %}
{% endfor %}
</div>
{% if has_center %}
<div class="span4 col-lg-4">
{% for block in blocks %}
{% if block.position == 'center' %}
{{ sonata_block_render({ 'type': block.type, 'settings': block.settings}) }}
{% endif %}
{% endfor %}
</div>
{% endif %}
<div class="{% if has_center %}span4 col-lg-4{% else %}span6 col-lg-6{% endif %}">
{% for block in blocks %}
{% if block.position == 'right' %}
{{ sonata_block_render({ 'type': block.type, 'settings': block.settings}) }}
{% endif %}
{% endfor %}
</div>
</div>
{% endblock %}
Thank you.
You can override the show template in app/Resources/SonataUserBundle/views/Profile/show.html.twig.
You must clear caché at the end

symfony2 / twig : how to use include in a block used in a form theme?

To process my form collections I have a custom form theme for the block collection widget. This block collection widget is rendered as a table and so depends on a block_collection_header and a block_collection_body.
The block collection widget always stays the same, but sometimes I customize the two other blocks, block collection header and block collection body
My working code :
{# From file myview.html.twig #}
{% form_theme form ':Model:prototype_table_collection.html.twig' %}
{% form(form) %}
Ant this form theme is the following :
{# From file ':Model:prototype_table_collection.html.twig' #}
{% block collection_widget %}
{% spaceless %}
<div class="collection">
{% if prototype is defined %}
{% set body = prototype %}
{% set attr = attr|merge({'data-prototype': block('collection_body') }) %}
{% set header = prototype %}
{% set attr = attr|merge({'data-header': block('collection_header') }) %}
{% endif %}
{% if form.vars.allow_delete is defined and form.vars.allow_delete %}
{% set allow_delete = true %}
{% else %}
{% set allow_delete = false %}
{% endif %}
<div {{ block('widget_container_attributes') }} class="protoype">
{{ form_errors(form) }}
<table class="subtable table">
<thead>
<tr class="headers" style="display: none;">
{% if form.children|length > 0 %}
{% if form.children[0]|length > 0 %}
{% set header = form.children[0] %}
{{ block('collection_header') }}
{% endif %}
{% endif %}
</tr>
</thead>
<tbody class="container_rows">
{% for rows in form %}
{% spaceless %}
{% if rows.children|length > 0 %}
{% set body = rows %}
{{ block('collection_body') }}
{% endif %}
{% endspaceless %}
{% endfor %}
</tbody>
</table>
{% if prototype is defined %}
{% if form.vars.attr['data-add_label'] is defined %}
{% set add_label = form.vars.attr['data-add_label'] ~ ' ' %}
{% else %}
{% set add_label = 'Ajouter ' %}
{% endif %}
{{ add_label }}<i class="fa fa-plus"></i>
{% endif %}
<br>
</div>
</div>
{% endspaceless %}
{% endblock collection_widget %}
{% block collection_header %}
{% for field in header %}
<th>
{% if 'checkbox' not in field.vars.block_prefixes %}
{{ form_label(field)|raw }}
{% else %}
{% if field.vars.attr['data-label'] is defined %}
{{ field.vars.attr['data-label'] }}
{% else %}
Options
{% endif %}
{% endif %}
</th>
{% endfor %}
{% if allow_delete %}
<th class="align_center">Supprimer</th>
{% endif %}
{% endblock %}
{% block collection_body %}
{% spaceless %}
{% set fieldNum = 1 %}
<tr class="row_to_delete child_collection">
{{ form_errors(body) }}
{% for field in body %}
<td class="field{{ fieldNum }} data-label">
{{ form_widget(field) }}
{{ form_errors(field) }}
</td>
{% set fieldNum = fieldNum + 1 %}
{% endfor %}
{% if allow_delete %}
<td class="align_center align_middle"><i class="fa fa-times"></i></td>
{% endif %}
</tr>
{% endspaceless %}
{% endblock %}
The code I'd like to use and which is not working:
The view stays the same
{# From file myview.html.twig #}
{% form_theme form ':Model:prototype_table_collection.html.twig' %}
{% form(form) %}
Here I am trying to externalize the code from within the first block
{# From file ':Model:prototype_table_collection.html.twig' #}
{% block collection_widget %}
{{include(':Model:collection_widget.html.twig')}}
{%end block%}
{% block collection_header %}
{#stays the same as the previous code for this block. It is called by the block collection_widget #}
{%end block%}
{% block collection_body %}
{#stays the same as the previous code for this block. It is called by the block collection_widget #}
{%end block%}
The new externalized file :
{#From file ':Model:collection_widget.html.twig' #}
{# Here I put the same exact code as I had before inside the block collection_widget, I'm not changing the code, I'm just trying to externalize this part #}
The include does not work, my collection does not load.
I have tried with extending a layout, it doesn't work either.
Example :
{# From file ':Model:prototype_table_collection.html.twig' #}
{% extends :Model:parent.html.twig' %}
{% block content %}
{% block collection_header %}
{#stays the same as the previous code for this block. It is called by the block collection_widget #}
{%end block%}
{% block collection_body %}
{#stays the same as the previous code for this block. It is called by the block collection_widget #}
{%end block%}
{%end block%}
and the parent :
{# From file ':Model:parent.html.twig' #}
{% block collection_widget %}
{# same code as brefore #}
{%end block%}
{% block content %}
{% endblock %}
How can I avoid repeating this {% block collection_widget %} code in every form template where I use it ?
I believe you are looking for the horizontal reuse functionality:
Horizontal reuse is an advanced Twig feature that is hardly ever needed in regular templates. It is mainly used by projects that need to make template blocks reusable without using inheritance.
Just include the use tag in the main template:
{# :Model:prototype_table_collection.html.twig #}
{% use ':Model:collection_widget.html.twig' %}
{% block collection_header %}
{# code #}
{%end block%}
{% block collection_body %}
{# code #}
{%end block%}
Then define the collection_widget block as if it was inside of the prototype_table_collection.html.twig file in the first place:
{# :Model:collection_widget.html.twig #}
{% block collection_widget %}
{# code #}
{% endblock %}

How to move "current" flag from <li> to <a> object in knpMenuBundle?

I have been looking at this twig code for a while, and tried a few trix to move the flag of the "current" item down to the link object (a) instead of the list item object (li) but I can't seem to get it right.
The output I get at the moment is
<ul>
<li class="current first">
Hem
</li>
The desired output is
<ul>
<li class="first">
<a class="current" href="/hemekonomi/web/app_dev.php/">Hem</a>
</li>
Here is the twig file (the standard twig template file for knpMenuBundle with irrelevant modifications to it).
{% macro attributes(attributes) %}
{% for name, value in attributes %}
{%- if value is not none and value is not sameas(false) -%}
{{- ' %s="%s"'|format(name, value is sameas(true) ? name|e : value|e)|raw -}}
{%- endif -%}
{%- endfor -%}
{% endmacro %}
{% block compressed_root %}
{% spaceless %}
{{ block('root') }}
{% endspaceless %}
{% endblock %}
{% block root %}
{% set listAttributes = item.childrenAttributes %}
{{ block('list') -}}
{% endblock %}
{% block list %}
{% if item.hasChildren and options.depth is not sameas(0) and item.displayChildren %}
<ul class="art-hmenu">
{{ block('children') }}
</ul>
{% endif %}
{% endblock %}
{% block children %}
{# save current variables #}
{% set currentOptions = options %}
{% set currentItem = item %}
{# update the depth for children #}
{% if options.depth is not none %}
{% set options = currentOptions|merge({'depth': currentOptions.depth - 1}) %}
{% endif %}
{% for item in currentItem.children %}
{{ block('item') }}
{% endfor %}
{# restore current variables #}
{% set item = currentItem %}
{% set options = currentOptions %}
{% endblock %}
{% block item %}
{% if item.displayed %}
{# building the class of the item #}
{%- set classes = item.attribute('class') is not empty ? [item.attribute('class')] : [] %}
{%- if item.current %}
{%- set classes = classes|merge([options.currentClass]) %}
{% set aFlag = ' class="active"' %}
{%- elseif item.currentAncestor %}
{%- set classes = classes|merge([options.ancestorClass]) %}
{%- endif %}
{%- if item.actsLikeFirst %}
{%- set classes = classes|merge([options.firstClass]) %}
{%- endif %}
{%- if item.actsLikeLast %}
{%- set classes = classes|merge([options.lastClass]) %}
{%- endif %}
{%- set attributes = item.attributes %}
{%- if classes is not empty %}
{%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
{%- endif %}
{# displaying the item #}
<li{{ _self.attributes(attributes) }}>
{%- if item.uri is not empty and (not item.current or options.currentAsLink) %}
{{ block('linkElement') }}
{%- else %}
{{ block('spanElement') }}
{%- endif %}
{# render the list of children#}
{%- set childrenClasses = item.childrenAttribute('class') is not empty ? [item.childrenAttribute('class')] : [] %}
{%- set childrenClasses = childrenClasses|merge(['menu_level_' ~ item.level]) %}
{%- set listAttributes = item.childrenAttributes|merge({'class': childrenClasses|join(' ') }) %}
{{ block('list') }}
</li>
{% endif %}
{% endblock %}
{% block linkElement %}<a href="{{ item.uri }}"{{ _self.attributes(item.linkAttributes) }}>{{ block('label') }}</a>{% endblock %}
{% block spanElement %}<span{{ _self.attributes(item.labelAttributes) }}>{{ block('label') }}</span>{% endblock %}
{% block label %}{% if options.allow_safe_labels and item.getExtra('safe_label', false) %}{{ item.label|raw }}{% else %}{{ item.label }}{% endif %}{% endblock %}
Instead of moving the "current" class, I drilled down to the desired object with this css:
ul.mainmenu>li.current > a > div {
opacity:0.6;
}

Categories