How to use images for menu items in Drupal with a simple preprocessing function

This tutorial is sponsored by the Save Joseph campaign, a grassroots effort to find a good friend, stellar artist and all around amazing person a satisfying, creative job in the next 8 days. I know the Drupal community could use this kind of talent. Learn more about the effort at savejoseph.org. If you have any ideas on how I can get the word out about this, let me know!

My use case was that I wanted to be able to use social media icons for menu items so that we could re-arrage, add or remove items directly from the menu management interface:. The result is what you see below:

To use images for menu items in Drupal, the first step is to create an override theme function for theme_menu_item_link() in your theme's template.php file. The idea is to first run your image handling bit to switch out text for images, and then hand it over to the parent theme to do the rest. In my case, I'm using the Zen theme.

/**
 * Implements theme_menu_item_link()
 */
function yourtheme_menu_item_link($link) {
  // Allows for images as menu items. Just supply the path to the image as the title
  if (strpos($link['title'], '.png') !== false || strpos($link['title'], '.jpg') !== false || strpos($link['title'], '.gif') !== false) {
    $link['title'] = '<img alt="'. $link['description'] .'" title="'. $link['description'] .'" src="'. url($link['title']) .'" />';
    $link['localized_options']['html'] = TRUE;
  }
  return zen_menu_item_link($link); // Let Zen take over from here.
}

Be sure to change the yourtheme in the function to the name of your theme. Also note that this only checks for .png files. If you want to use a jpeg or a gif, you will need to modify the code slightly.

Next, clear the theme registry (CTFC).

Now, you can use image paths instead of text for the image, as seen below:

Edit menu item Menu settings Path: The path this menu item links to. This can be an internal Drupal path such as node/add or page. Menu link title: sites/ ll/themes/v 1/itnages/icon-socia l-Fb png The fink text corresponding to this item that should appear in the MENU ,//www TOKEN /pagei/edit/?id= 115780915101836#!. /page to. can an OBSTREPEROUSNESS sodal- Fb DO hltp://www 115780915 10 1836#7page Tfle ESME Gallery Illustration Blog About Events Contact NASA ca Slog Abe o w iLEyents (■'-It'1, OS


Comments

You should be using theme('image') instead of writing the img HTML yourself.

I know this simply demonstrates my ignorance, but is "strpos" the name of your menu? As in your example I'll need one text menu (Gallery, Illustration, etc.) and one icon menu. Thanks!

No, "strpos" is a PHP function being used here to check if ".png" is part what the user provided for the Menu link title.

So with this example, a menu item will only render as an image if it has ".png" in the Menu link title. Otherwise it will output as a text link, like normal.

Got it working, thanks!

Good call, I made a note of this limitation in the post.

Hmmm...not quite sure what I did wrong. I placed the code inside the closing // */ of Zen's template.php, cleared the cache, ran update.php and my menu returns the path "sites/all/themes/[theme]/images/[image].png, not the image. (I know my path is correct.) Any ideas? Thanks.

Did you change "yourtheme" in the function name to the name of your theme?

BINGO! yourtheme was it. Thank You!

it looks like I've done everything correct
rechecked twice
rolled back to original template.php
make changes again

but still getting "sites/all/themes/[theme]/images/[image].png in menu

fyi, "yourtheme" in the function name replaced correctly

peoples, please help me, it looks like I'm "stuck between two trees" ...

btw, zen 2.0 used

Have you cleared your cache? Have you tried adding a debug statement to the function to make sure it's actually being called?

cache cleared for sure
not sure how this may be debugged
can you suggest?

so finally I've sorted out that if primary links published via default zen functions but not via blocks then solution do not work

to allow this working there is primary links should be rendered via blocks f.e. in header

Thanks for posting your solution, glad you got it working!

I've added an update to the post that mentions replacing out the *yourtheme*. Thanks!

There is a module for this.

http://drupal.org/project/spritemenu

maybe another module:
http://drupal.org/project/imagemenu ??

I'll have to give these two modules a go. I seem to remember looking briefly and deciding against them, but I can't remember why now. Maybe it was because you can't use a combination of image and textual entries? Or because you can't use the menu block module? I'll take a look and post back. Cheers!

There's a security vulnerability in your code. You are not checking whether the URL is safe before you create an img tag out of it. The URL could contain malicious JavaScript. You need to use check_url(), or better yet just use theme_image() and get some other benefits like image sizing for free.

Albeit someone who can edit menu items probably already has access to maim your site, it's still good practice to keep things secure for all situations. Sometime in the future your site may get reconfigured, or a function may get re-used by some other process, or the code may get copied and pasted somewhere else (like a public blog post), etc., etc..

P.S. The comment "Preview" button doesn't seem to work.

Ack, my bad. You are using url() which is safe. Somehow my eyes skipped right over that the first time.

Whenever I see the words "security vulnerability," I get a little PTSD. Once I crawled back out from under the table, it's good to find out it was a false start. ;)

A couple people suggested theme_image(), I'll give it a test and probably update the code above.

Cheers!

What to do with other themes (Not Zen)?

That's a good question. I think what you'd want to do is replace that last line that references the zen theme override with the following:

return theme_menu_item_link($link);

I think you mean
return theme('menu_item_link', $link);

I thought about that, but I'm pretty sure that would result in infinite recursion. So, this is the next best thing.

Good idea, though!

The method described serves good for educational & uneducational purposes ;P

You clearly show where to hack theming **BUT DO A BIG CMS NO-NO** when you obviate the right meaning of properties, attributes...

"Title" property for menu items shouldn't be utilized for another purpouse. The method has a negative SEO & usability impact since avoids setting an HTML image "title", and provides no way to set an image "alt".

A better approach would be using imagemenu module.-

what seo usability are you talking about ? that php code will never be visible by search engine, the resulting html is pure images ..

You're right about it taking over the title and alt tags, and that is unfortunate for both SEO and usability. I'll have to take a look at the imagemenu module and see if it would work the way I'd need it to.

Cheers!

how to put another image for mouse over?

I have three themes enabled (Giordani (default), Garland, Zen) and only the core module. I want to put the image, sites/default/files/facebook.png, on my Navigation menu.

The Giordani theme is in the themes/zen/marinelli folder (i.e, /themes/zen/marinelli/giordani). The Giordani theme doesn't have a template.php file but the Marinelli does and so does zen. I put in the code with yourtheme=giordani in /marinelli/template.php and when I flush the cache, I get the Fatal Error: Call to undefined function zen_menu_item_link() in themes\marinelli\template.php .

Any ideas as to what is wrong?

usng drupal 6.19

Hey Chris,

You're the man!! Thank you, thank you, thank you. After hours and hours of search and trying different methods, I finally came up to this post.

Imagemenu does not have the facility to include icons in primary links, it just creates a block that could then be placed in block regions. Spritemenu doesn't do the trick either. Your solution is the only that works.

Could you please comment on how it affects SEO? The edited version for Acquia Prosper that I used:

/**
* Implements theme_menu_item_link()
*/
function acquia_prosper_menu_item_link($link) {
// Allows for images as menu items. Just supply the path to the image as the title
if (strpos($link['title'], '.png') !== false || strpos($link['title'], '.jpg') !== false || strpos($link['title'], '.gif') !== false) {
$link['title'] = ''. $link['description'] .'';
$link['localized_options']['html'] = TRUE;
}
return theme_menu_item_link($link); // Let AP take over from here.
}

Nice hack, Chris. I've also been unable to get this kind of functionality with any of the above-mentioned modules.

I'm not big on module development, but I have the feeling it would be relatively straightforward to write a module that modifies the menu-customise page (admin/build/menu-customize/primary-links/add) so that there is an optional extra textfield for a URL. With the module then somehow incorporating you code, it would be pretty sweet.

Excited of finally finding something to solve my problem, without being a big hack, I tried this on the Drupal 7 theme I am making.

No luck. Have you tried this with Drupal 7 and Zen?

I haven't tried this in Drupal 7 yet, but if someone gets it working, please post back here. Thanks!

I got it working in a Drupal 7 Zen subtheme using:

/**
* Implements theme_links()
*/
function datasmith_links__system_main_menu($data) {
  // Allows for images as menu items. Just supply the path to the image as the title
  foreach($data['links'] as $name=>$link) {
    if (strpos($link['title'], '.png') !== false || strpos($link['title'], '.jpg') !== false || strpos($link['title'], '.gif') !== false) {

    $variables = array(
      'path' => $link['title'],
      'alt' => $link['attributes']['title'],
      'title' => $link['attributes']['title'],
      'attributes' => array('class' => 'main-menu-image', 'id' => $name . '-image'),
    );
    $link['title'] = $img = theme('image', $variables);
    $link['html'] = TRUE;  //otherwise theme_link will use check_plain on the title
    }
  $data['links'][$name]=$link;
  }
  return theme('links', $data);
}

Hi Barrett

I couldn't get this code working with my zen subtheme

I swapped datasmith with my zen subtheme's name and put it in my subthemes template.php...am I missing something?

This works for the main menu displayed on lines 104-116 of the page.tpl.php in Zen, not with separate menu blocks. If that's what's not working for you, is it somewhere you could post a link to let us see it?

Unfortunately I am working locally

It is a separate menu block to the main menu though

----sorry sticking the reply in the right place

Ah. Yeah, this code doesn't work for that. Try the Menu Icons module.

Unfortunately I am working locally

It is a separate menu block to the main menu though

Hi Chris,

This looks like the exact thing I need, and honestly none of the other modules mentioned seem to have it nailed. I'm using a torn up version of the Basic Theme and when I add the code to my template.php file it's conflicting with another function calling basic_menu_link_item. I've tried deleting various aspects but am at a bit of a loss of how to add the code.

Any suggestions?

Thanks in advance,

Dave

Here is the conflicting Code:

function basic_menu_item_link($link) {
if (empty($link['localized_options'])) {
$link['localized_options'] = array();
}

// If an item is a LOCAL TASK, render it as a tab
if ($link['type'] & MENU_IS_LOCAL_TASK) {
$link['title'] = '' . check_plain($link['title']) . '';
$link['localized_options']['html'] = TRUE;
}

return l($link['title'], $link['href'], $link['localized_options']);
}

it dosent work.
can you help me? i use the theme forest floor

I ended up working with my brother (www.projecttau.com) to come up with a solution. Hopefully some of you find this helpful. Seems to work perfectly on the theme I'm using.

function basic_links($links, $attributes = array('class' => 'links')) {
foreach ($links as $key => $link) {
if (preg_match('/\.(png|gif|jpg)$/', $link['title']) ) {
$links[$key]['title'] = ''. $link['description'] .'';
$links[$key]['html'] = true;
}
}
return theme_links($links, $attributes);
}

Thanks for this code snippet, great work on replacing the text on a Drupal Nice Menu with an image.

Hello everyone,

First of all I'd like to thank Barrett for his snippet. I was able to insert images in the main menu in DRUPAL 7 without a hitch and turn the whole thing into an Apple-dock. So thanks for your input.
However....
Once I started installing the Internationalisation module I knew that I had to shift the menu in main-menu to a custom menu (and this in 3 languages). It's too bad I had to change this but I have no choice(I think). But this causes the images not to work anymore as the function calls the system_main_menu.
So...

Is there anyway to quickly adapt the system_main_menu to a custom_menu?

like this:
function MYTHEME_links__system_main_menu($data) to
function MYTHEME_links__MYCUSTOMMENU($data) ??

I have tried many different functions to call the custom_menu but I simply can't make this work. Any help would be appreciated.

P.S.: I'm currently working locally

Does this work with Drupal 7??? I need to add icons to menu items in Drupal 7.

Hmmm... it doesn't seem to work in Drupal 7. Or is it just me?

Anyone?

I am using the menu block module and need to be able to have icons on my last layer of links.

I've been struggling all day with this one, but here's what I've managed to do for those working with Drupal 7.

I used Barret's code (thankyou), and encountered the same problem as some others with it only affecting the menu at the top, when I wanted it down the side.

But, tinkering with the layout css, I was able to move the navigation over the right, and above the right sidebar, so that the images dropped down one after another. Result.

T

Has anyone ever found a viable solution for an image change on-hover,

whilst I have done this using css and menu id numbers, it's clunky and your option (above) is so much cleaner - would much prefer to use this option if I can,

thanks, (still in drupal 6)

i tried this and it works fine but when i have the template.php in my themes directory ,log off and try to log in drupal just shows me an empty page showing the page url to be localhost/drupal_theme/node/51?destination=node%2F51 instead of /node/51.i am new to both drupal and php. please help.

this is in reference to my last post. i am also getting the following errors:
* warning: Cannot modify header information - headers already sent by (output started at C:\xampp\htdocs\drupal_theme\sites\all\themes\idwone\template.php:1) in C:\xampp\htdocs\drupal_theme\includes\session.inc on line 98.
* warning: session_regenerate_id() [function.session-regenerate-id]: Cannot regenerate session id - headers already sent in C:\xampp\htdocs\drupal_theme\includes\session.inc on line 101.

I'm working with Barretts solution, and it's working, but returning an error:

Notice: Undefined index: title in earthish_links__system_main_menu() (line 89 of /home/.../sites/all/themes/earthish/template.php).
Notice: Undefined index: title in earthish_links__system_main_menu() (line 90 of /home/.../sites/all/themes/earthish/template.php).

Any ideas why?

Hi,
Here is what i did for my drupal 7. I wrote this in template.php file and it works.

function arras_menu_link(array $variables) {
global $base_path;
$element = $variables['element'];
$output = l($element['#title'], $element['#href'], $element['#localized_options']);

if($element['#title'] == 'Home')
{
$element['#localized_options']['html'] = TRUE;
$get = '' . $element['#title'] . '';
$output1 = l($get,$element['#href'], $element['#localized_options']);
return '

  • ' .$output1 . "
  • \n";
    }
    else
    {
    return '

  • ' . $output . "
  • \n";
    }

    }

    What is supposed to be after $get =' All I see is an unrendered image box.

    Chris,

    Thanks for sharing such a cool approach to setting up Social Media Icons. I added the code and cleared the cache as instructed. So far, so good, however when I try to setup the link to the PNG file I get the following -
    The path 'sites/all/images/fb_glass' is either invalid or you do not have access to it.

    I've confirmed the path and permission of the parent and sub-directory. I am using Drupal 7.8. Being a novice I am really not sure where to go from here. Any suggestions?

    Jean B,verify that your path is correct. Mine for example is "sites/all/themes/mytheme/images/". So you might be missing the /mytheme/ in your path.

    Hi,

    I know very little about this stuff, but I'm using the Marvin theme and don't see a template.php file? What do I do?

    I don't even know what version of drupal I'm using. Someone else set this thing up and I'm trying to fancify it.

    blarg.

    Coming a bit late to the party. I've implemented a version of this function for Drupal 7 (the hook changed from theme_menu_item_link to theme_menu_link, among other things). Here's my version based off yours (a more general version of Barett's, I guess. It will work for all menus.)

    $element = $link['element'];
      // Allows for images as menu items. Just supply the path to the image as the title
      if ( strpos($element ['#title'], '.png') !== false ||
    strpos($element ['#title'], '.jpg') !== false ||
    strpos($element ['#title'], '.gif') !== false)
      {

    $link['element']['#title'] =  '<img title="'. $element['#original_link']['description'] .'" alt="'. $element['#original_link']['description'].'" src="'. url($link['element']['#title']) .'" />';
        $link['element']['#localized_options']['html'] = TRUE;
      }


      return theme_menu_link($link);
     

    Hi Thaddeus, I've tried your solution, but it seems to work only for core menus. Would you have any tricks to make it work with custom menus ?
    Thanks in advance

    I couldn't get this to work with teleco theme (don't believe theme is the issue) and .bmp image. Replaced .bmp in .png in your code. I believe I inserted all the tags correctly. Using straight html in other code places find the images, but not through this piece in template.php. I tried putting the directory path directly into the code for 'title' or using that as 'title' = no difference. The name of the path to the image shows up as the menu link on the page. Except that I have such a long path name in my menu that now I have to track down how to widen my administration screen just to delete the link. I was barely able to click the "disable" box.

    Hello.

    I've tried this and works fine.

    My primary links menu is full, if I would like to do the same but with secondary links menu, which changes will be neccesary?

    Thank you.

    Kind Regards.

    If you're using the framework theme in drupal 6, modify the following function as such:

    function yourtheme_links($links, $attributes = array('class' => 'links'), $heading = '') {
    global $language;
    $output = '';

    if (count($links) > 0) {

    // Treat the heading first if it is present to prepend it to the
    // list of links.
    if (!empty($heading)) {
    if (is_string($heading)) {
    // Prepare the array that will be used when the passed heading
    // is a string.
    $heading = array(
    'text' => $heading,
    // Set the default level of the heading.
    'level' => 'h2',
    );
    }
    $output .= '<' . $heading['level'];
    if (!empty($heading['class'])) {
    $output .= drupal_attributes(array('class' => $heading['class']));
    }
    $output .= '>' . check_plain($heading['text']) . '';
    }

    $output .= '

      ';

    $num_links = count($links);
    $i = 1;

    foreach ($links as $key => $link) {
    $class = $key;

    if (strpos($link['title'], '.png') !== false || strpos($link['title'], '.jpg') !== false || strpos($link['title'], '.gif') !== false) {
    $link['title'] = ''. $link['description'] .'';
    $link['html'] = TRUE;
    }

    // Add first, last and active classes to the list of links to help out themers.
    if ($i == 1) {
    $class .= ' first';
    }
    if ($i == $num_links) {
    $class .= ' last';
    }
    if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '' && drupal_is_front_page()))
    && (empty($link['language']) || $link['language']->language == $language->language)) {
    $class .= ' active';
    }
    $output .= '

  • $class)) .'>';
  • if (isset($link['href'])) {
    // Pass in $link as $options, they share the same keys.
    $output .= l($link['title'], $link['href'], $link);
    }
    else if (!empty($link['title'])) {
    // Some links are actually not links, but we wrap these in for adding title and class attributes
    if (empty($link['html'])) {
    $link['title'] = check_plain($link['title']);
    }
    $span_attributes = '';
    if (isset($link['attributes'])) {
    $span_attributes = drupal_attributes($link['attributes']);
    }
    $output .= ''. $link['title'] .'';
    }

    $i++;
    $output .= "

    \n";
    }

    $output .= '

    ';
    }

    return $output;
    }