If a (non-active) local task contains a "&" character, and the item is NOT marked as html, this character will be filtered twice in bootstrap_menu_local_task():
- filter_xss_admin()
- the usual check_plain() as part of l().

This causes the tab (local task) to have a visible "&" in its title. Sad!

Note: This does not happen for active local tasks, because.. well, look into the code.

Also this comment is wrong:

  // Filter the title if the "html" is not set, otherwise l() will automatically
  // sanitize using check_plain(), so no need to call that here.

It is the other way round: l() will automatically sanitize if html is NOT set. If html = TRUE, then l() will NOT sanitize.

CommentFileSizeAuthor
#5 2866798-4.patch1.36 KBmarkhalliwell
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

donquixote created an issue. See original summary.

donquixote’s picture

Maybe whenever stuff like this happens, it is time to replace the ternary ?: with if / else :)

donquixote’s picture

Related issues: +#2827761: Menu HTML breaks

git blame pointed me to commit e165225ed5 and issue #2827761: Menu HTML breaks.

donquixote’s picture

And commit b8be0646716d9bd6066128e403fcc9b8e22cefbd with message "Fix bugs".

markhalliwell’s picture

Status: Active » Needs review
FileSize
1.36 KB

Ah, good catch. It needs to set $options['html'] = TRUE if it uses filter_xss_admin(); which inadvertently requires an if/else block instead of a ternary operator.

Try this patch.

donquixote’s picture

(I wrote this before your comment, I think it applies anyway)

The implementation in Drupal core seems fine,
https://api.drupal.org/api/drupal/includes%21menu.inc/function/theme_men...

function theme_menu_local_task($variables) {
  $link = $variables['element']['#link'];
  $link_text = $link['title'];

  if (!empty($variables['element']['#active'])) {
    // Add text to indicate active tab for non-visual users.
    $active = '<span class="element-invisible">' . t('(active tab)') . '</span>';

    // If the link does not contain HTML already, check_plain() it now.
    // After we set 'html'=TRUE the link will not be sanitized by l().
    if (empty($link['localized_options']['html'])) {
      $link['title'] = check_plain($link['title']);
    }
    $link['localized_options']['html'] = TRUE;
    $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active));
  }

  return '<li' . (!empty($variables['element']['#active']) ? ' class="active"' : '') . '>' . l($link_text, $link['href'], $link['localized_options']) . "</li>\n";
}

The $link_text is left untouched for non-active local tasks.
Only for the active local task, the link text is checkplained before going into t().

The smelly thing about this core code is how it writes to array values instead of using local variables. But this should not have visible effects, it is just bad practice.

So why the different logic in Bootstrap?

donquixote’s picture

About your patch..

+  if (empty($options['html'])) {
+    $title = filter_xss_admin($link['title']);
+    $options['html'] = TRUE;
+  }

Core theme_menu_local_task(), via l(), would have applied check_plain() in this case. In your patched code you apply filter_xss_admin() instead of check_plain(). I'd say this is probably wrong..

This comment is still wrong..

+  // Otherwise, just pass the title as is and l() will use check_plain().
+  else {
+    $title = $link['title'];
+  }

In this case the thing will *not* be filtered by l() because $options['html'] === TRUE.

donquixote’s picture

Otherwise the code looks like it is mostly equivalent with core.. no additional classes added.
Maybe we could drop this theme implementation altogether!

If we do this, I think it might still be wise to have
- one commit to fix the theme implementation (possibly replacing it with a copy from core)
- another commit to drop the theme implementation.

This gives a more useful git history, and allows to revert to a non-broken state.

donquixote’s picture

Another difference I notice to core is the use of drupal_attributes()..

  $attributes = array();
  [..]
  if (!empty($variables['element']['#active'])) {
    [..]
    $attributes['class'][] = 'active';
    [..]
  }
  [..]
  return '<li' . drupal_attributes($attributes) . '>' . l($title, $href, $options) . "</li>\n";

The only two possible values of $attributes are array() and array('class' => array('active')).
So it would be faster to just do (!empty($variables['element']['#active']) ? ' class="active"' : '') like core.

markhalliwell’s picture

So why the different logic in Bootstrap?

Short version: it's not.

Long version:
I've just spent some time tracing back where this override was initially added.

Turns out it was actually added in the https://www.drupal.org/project/twitter_bootstrap project, commit aaec518 (well before I took over this project).

It seems they had originally added dropdowns to these for sub-tasks.

This file then became inherited by this project. It made its way through out the years getting whittled down.

Currently, these subtasks are now just rendered as pagination pills below the main tabs.

Thus, this has effectively made this override completely unnecessary.

Kind of funny how a bug actually determines that some inherited legacy code is irrelevant :D

---

So, I'm ok with your proposal and will commit both shortly:

- one commit to fix the theme implementation (possibly replacing it with a copy from core)
- another commit to drop the theme implementation.

markhalliwell’s picture

Title: bootstrap_menu_local_task() filters twice » Remove bootstrap_menu_local_task() override since it's no longer needed

  • markcarver committed 1a5f67f on 7.x-3.x
    Issue #2866798 by markcarver, donquixote: Remove...
  • markcarver committed e7b8238 on 7.x-3.x
    Invoke theme_menu_local_task() from bootstrap_menu_local_task()
    
    Replace...
markhalliwell’s picture

Status: Needs review » Fixed
donquixote’s picture

Thanks!

Kind of funny how a bug actually determines that some inherited legacy code is irrelevant :D

Or it means that noone likes to review existing code unless there is a bug..
(no blame or guilt implied)

donquixote’s picture

So, I'm ok with your proposal and will commit both shortly:

- one commit to fix the theme implementation (possibly replacing it with a copy from core)
- another commit to drop the theme implementation.

function bootstrap_menu_local_task($variables) {
  return theme_menu_local_task($variables);
}

My idea was to insert a copy of core implementation instead of this call.. this would allow to see the diff between the different implementations.
Then the second commit message would say "drop custom implementation, because it is identical with core".
But I don't care, main thing is it is fixed :)

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.