Search code examples
phphtmlsymfonyknpmenubundle

KNP Menu Bundle with HTML Special Chars


I'm using the KNP Menu Bundle within my Symfony2 project and create my Menus as a service.

I have the problem that the Route Labels quotes and other special chars won't displayed correctly.

As an example:

Test Text & Stuff will be displayed as Test Text & Stuff and I can't figure out how do deal with it.

I'm creating the route as following:

$menu->addChild('seller', array(
    'route' => 'routename',
    'routeParameters' => $array,
    'label' => $sellername
))->setLinkAttribute('class', 'dark-color active-hover');

I tried to this commands to get rid of it:

  1. html_entity_decode()
  2. htmlspecialchars_decode()
  3. htmlspecialchars()
  4. htmlentities()

But neither of them worked. It wouldn't be a big deal if the browser would translate them corectly, but the browser isn't doing it because of this:

                 Test Text & Stuff                     

There's plenty of whitespace before and after my text and I can't figure out where it's comming from. I trimed the $sellername and I also added the trim commands from twig into the knp_menu.html.twig.

Any suggestions how I can deal with this situation?

Edit:

What I figured out now is that if I manually remove the whitespaces from the text the text will be displayed correctly. I tried to trim the whitespaces with javascript, but I had no success with it by now.

Edit:

Here's the knp_menu.html.twig template

{% extends 'knp_menu.html.twig' %}

{% block item %}
    {% import "knp_menu.html.twig" as macros %}
    {% if item.displayed %}
        {%- set attributes = item.attributes %}
        {%- set is_dropdown = attributes.dropdown|default(false) %}
        {%- set icon = attributes.icon|default(false) %}
        {%- set span = attributes.span|default(false) %}
        {%- set spanContent = attributes.spanContent|default(false) %}
        {%- set notification = attributes.notification|default(false) %}
        {%- set divider_prepend = attributes.divider_prepend|default(false) %}
        {%- set divider_append = attributes.divider_append|default(false) %}

        {# unset bootstrap specific attributes #}
        {%- set attributes = attributes|merge({'dropdown': null, 'icon': null, 'span': null, 'spanContent': null, 'notification': null, 'divider_prepend': null, 'divider_append': null }) %}

        {%- if divider_prepend %}
            {{ block('dividerElement') }}
        {%- endif %}

        {# building the class of the item #}
        {%- set classes = item.attribute('class') is not empty ? [item.attribute('class')] : [] %}
        {%- if matcher.isCurrent(item) %}
            {%- set classes = classes|merge([options.currentClass]) %}
        {%- elseif matcher.isAncestor(item, options.depth) %}
            {%- 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 %}

        {# building the class of the children #}
        {%- set childrenClasses = item.childrenAttribute('class') is not empty ? [item.childrenAttribute('class')] : [] %}
        {%- set childrenClasses = childrenClasses|merge(['menu_level_' ~ item.level]) %}

        {# adding classes for dropdown #}
        {%- if is_dropdown %}
            {%- if item.level > 1 %}
                {%- set classes = classes|merge(['dropdown-submenu']) %}
            {%- else %}
                {%- set classes = classes|merge(['dropdown']) %}
            {%- endif %}
            {%- set childrenClasses = childrenClasses|merge(['dropdown-menu']) %}
        {%- endif %}

        {# putting classes together #}
        {%- if classes is not empty %}
            {%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
        {%- endif %}
        {%- set listAttributes = item.childrenAttributes|merge({'class': childrenClasses|join(' ') }) %}

        {# displaying the item #}
        <li{{ macros.attributes(attributes) }}>
            {%- if is_dropdown %}
                {{- block('dropdownElement') -}}
            {%- elseif item.uri is not empty and (not matcher.isCurrent(item) or options.currentAsLink) %}
                {{- block('linkElement') -}}
            {%- else %}
                {{- block('spanElement') -}}
            {%- endif %}
            {# render the list of children#}
            {{- block('list') -}}
        </li>

        {%- if divider_append %}
            {{ block('dividerElement') }}
        {%- endif %}
    {% endif %}
{% endblock %}

{% block linkElement %}
    <a href="{{ item.uri }}"{{ knp_menu.attributes(item.linkAttributes) }}>
        {% if item.attribute('icon') is not empty %}
            <i class="{{ item.attribute('icon') }}"></i>
        {% endif %}
        {{ block('label')|trim }}
        {% if item.attribute('notification') is not empty %}
            <span class="bagde"><icon class=" {{ item.attribute('notification') }}"></icon></span>
        {% endif %}
        {% if item.attribute('span') is not empty %}
            <span class="{{ item.attribute('span') }}">{% if item.attribute('spanContent') is not empty %}{{ item.attribute('spanContent')}}{% endif %}</span>
        {% endif %}
    </a>
{% endblock %}

{% block dividerElement %}
    {% if item.level == 1 %}
        <li class="sidebar-divider"></li>
    {% else %}
        <li class="divider"></li>
    {% endif %}
{% endblock %}

{% block dropdownElement %}
    {%- set classes = item.linkAttribute('class') is not empty ? [item.linkAttribute('class')] : [] %}
    {%- set classes = classes|merge(['dropdown-toggle']) %}
    {%- set attributes = item.linkAttributes %}
    {%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
    {%- set attributes = attributes|merge({'data-toggle': 'dropdown'}) %}
    <a href="#"{{ macros.attributes(attributes) }}>
        {% if item.attribute('icon') is not empty %}
            <i class="{{ item.attribute('icon') }}"></i>
        {% endif %}
        {{ block('label')|trim }}
        {% if item.level <= 1 %} <b class="caret"></b>{% endif %}</a>
{% endblock %}

{% block label %}{{ item.label|trim|trans }}{% endblock %}

Solution

  • What does raw do?

    By default, when displaying something in Twig (using {{ }}), Twig will escape it to make sure it's safe. We don't want any evil HTML ending up in our pages.

    Sometimes the thing we want to display is already HTML, so we don't want to escape it again. That's where the raw filter comes in! It tells twig that the thing it just "filtered" is already escaped, we want to use it in it's raw form (e.g. not escape it again).

    How to work with raw

    block('label') will render a block with the name "label". This rendering will be done html-safe, which means it is escaped.

    By displaying it in another template (by doing {{ block('label') }}), it will be escaped again. If you don't want this, you should use the raw filter: {{ block('label')|raw }}

    The raw filter has to be used last, otherwise it has no effect. So if you want to trim the rendering, do it first: {{ block('label')|trim|raw }}

    PS: Even when doing something more elaborate, you'll still have to use raw last and as a whole. For example: {{ ('<span>' ~ block('label')|trim ~ '</span>')|raw }}

    Documentation

    Read more in the docs about the raw filter, and escaper extension