A potpourri of Web Developmemt, Linux, and Windows tidbits and observations

My Favorite Joomla Extensions

Posted by imperialWicket | Joomla | Thursday 29 July 2010 6:32 pm

There are a lot of Joomla extensions out there, many of which are free (or at least offer limited release or branded versions for free). There are so many, I would say, that it can be frustrating to search for an extension when you need one. I can not fix this problem, but I hope I can reduce the search time for others when it comes to a few common extension needs.

My short list of must-have, FREE Joomla Extensions

Components:
1. akeebaBackup (formerly joomlapack) – Since updating to akeebaBackup, I think this particular component has started to cater to its commercial version a little too heavily. Nonetheless, it stands as the best backup utility that I have used. It backups the entire file system (allowing custom exclusions, and including standard exclusions for directories like tmp), it generates a backup of the database (including all the tables, not just the standard installation tables), and it creates its own custom installation directory for restoring the site using the file system and db backups. I use akeebaBackup for moving sites between my test servers, and among live servers. I also have akeebaBackups pre-built with all of my preferred extensions and templates installed, so I can hit the ground running on my new projects.
2. JCE (Joomla Content Editor) – Joomla Content Editor takes Tiny MCE project to the next level within Joomla sites. It occasionally causes issues with editor buttons, but these are worth troubleshooting, as the JCE interface is much more manageable, and offers a lot of configuration options. This component requires the JCE plugin to reach its full glory.
3. Phoca Gallery – I have posted a couple of hacks to alter Phoca Gallery plugins, and I am generally a big fan of their projects. Phoca Downloads, Phoca Maps, Phoca Documentation, and Phoca PDF have all served my needs well for projects – but I must admit to not investigating alternatives in these cases. As far as galleries go, I think the Phoca Gallery really has the best combination of user-friendly administration, feature-rich without being bloated with parameters (although it does have quite a few), and a great suite of complimentary Modules and Plugins (I never install the Phoca Gallery without the Phoca Image Module and Phoca Search Plugin).
4. ChronoForms – I wish forms would just go away. They’re ugly, annoying, browser-specific, and did I mention ugly and annoying? Anyway, they are not going anywhere, and I have spent a lot of time looking for a good (free) form component that can meet all my form building needs. I think ChronoForms is a bit lacking in UI elements, but it by far the best option I have found for putting forms in my Joomla sites.
5. JEvents – For event management. UI, options, functionality are all where they should be for a free component. JEvents is popular for a reason, and I use it when I need events data on a site. There are also good modules/plugins available to complement.
6. XMap – Everyone is concerned about SEO and making sure Google has their latest and greatest site map and has indexed every last word they posted. XMap easily takes care of this, and also provides a means to include your site map on your site.
7. VirtueMart – I prefer VirtueMart for e-commerce in Joomla. I hope I am not starting a war here.
8. My Custom Blank Component. I searched long and hard for a blank component, but could not seem to track one down. I know you should use a different template, or you can include a show component option in your template. But many times I just have one page, where all I want is to show a bunch of modules. In those cases, I just want to assign a menu item that is going to leave the component blank. So I made a blank component, and I use it a LOT.

This is getting long, so I’ll skim the modules:
1. Extended Menu Module – If you are not using this already, you should be.
2. AiDaNews Module – A good enhancement to the Latest News plugin that comes pre-installed.
3. Simple Image Holder – I use this thing everywhere. It is probably my most used extension on a per page basis.
4. Simple Image Rotator – I am fairly anti-flash, and I generally opt for the Simple Image Rotator over alternatives.

My Plugins are all complements to my components. Since I tend to avoid flash, my articles tend to be images and text. The only times I require plugins are when I want an article to interface with a component, hence you will find all my top Plugins at the links previously provided.

And for templates, I will defer to a good article I caught recently at JoomlaPraise. I will also say that many of my Joomla projects start with one of the free JoomlaPraise templates.

I intentionally did not list a flash player (I usually turn to the Flash Module or Embedia – moseasymedia, are these really the best?) or an all encompassing media component. I have tried too many to count, and just can’t seem to find one (or even two or three) that will meet all of my needs. Hopefully someone can recommend a good media component (plugin/module) or suite of utilities and help me figure what I am doing wrong on this front.

Well, other than the media component/flash player are there any others I missed? Or, does anyone have reasonable alternatives or disagreements?

Use Joomla Login Credentials Outside of Joomla

Posted by imperialWicket | Joomla | Friday 2 July 2010 1:24 pm

On several occasions I needed to interface with a Joomla installation from a satellite web application. In these scenarios, it is useful to integrate a login check that accesses the Joomla username and password data. While this integration is not terribly difficult, I spent a long time digging around forums before I located helpful information. In the interest of saving time for others, here is what I found.

First, the Joomla process for creating a new user goes like this:
1. Validate that username is unique.
2. Generate a random 32 character “salt” value.
3. Combine the password + salt, and encrypt the compounded value.
4. Store the encrypted result followed by the salt value used to generate it.

For example, when I create a new user with username ‘joomla_user’ and password ‘joomla_password’, Joomla does the following:
1. Make sure there is not an existing ‘joomla_user’ username value in jos_users.
2. Generate a random 32 character salt (ex: TUIG6Wyx2gPavlcPm73mdpN4uWjZ8dvv).
3. Combine password and salt, then encrypt the entire value:
$value = ( md5(‘joomla_password’.'TUIG6Wyx2gPavlcPm73mdpN4uWjZ8dvv’);
4. Store encrypted value followed by original salt in database (separated by colon):
$value.’:TUIG6Wyx2gPavlcPm73mdpN4uWjZ8dvv’ is inserted.

In order to integrate your web application with the Joomla login, retrieve the appropriate values from the database, separate the salt and the encrypted password + salt combination, salt and encrypt the entered password value, and compare your result to the value in the database. Looks like this in PHP:

$dbhostname = 'localhost';
$dbusername = 'root';
$dbpassword = 'password';
$dbdatabase = 'joomla15';

// Get these from your form...
$username_for_check = 'admin';
$password_for_check = 'admin';

$joomla_user;
$joomla_pass;
$joomla_salt;

$mysqli = new mysqli($dbhostname,$dbusername,$dbpassword,$dbdatabase);

if ($result = $mysqli->query('SELECT j.username,j.password FROM joomla15.jos_users j WHERE j.username="'.$username_for_check.'" LIMIT 1;')) {

  if ($result->num_rows == 0){
      echo 'Username does not exist.';
  }else{
    while ($row = $result->fetch_object()) {
      $joomla_user = $row->username;

      $pass_array = explode(':',$row->password);
      $joomla_pass = $pass_array[0];
      $joomla_salt = $pass_array[1];
    }

    if($joomla_pass == md5($password_for_check.$joomla_salt)){
      echo 'Username and password combination validated.';
    }else{
      echo 'Invalid password for username.';
    }
  }
} else {
  echo 'LOGIN VALIDATION: MySQL Error - '.$mysqli->error;
}

$mysqli->close();

Joomla – Modules not Loading

Posted by dale | Joomla | Tuesday 29 June 2010 1:02 pm

Well, I drew the short straw on this one, and have been goaded into writing this one up by my cohort. Both of us have developed several sites with Joomla, and so we know a little about Joomla, which is why this bug surprised us.  It took both of us working together, and for a good part independently, about three hours to find the answer to this bug.

What I thought I’d do is walk you through the process, like a mystery, and see if we can save, at least, one of our readers a couple of hours in the future, and while were doing that we’ll talk about the operation of some of Joomla’s internals.

Let’s set the scene, one of the sites we maintain is a Joomla site with a Section Blog Layout front page.  The center of the page has three article snippets, each with a “read more” link to the full article on another page.

Normal operation: when you click on a “read more” link in each one of the three article snippets, a new page comes up with the full article in the center and a picture in the right sidebar.

The problem: When you click the “read more” link on one of the articles, the new page comes up with the entire article, however there is no picture in the right sidebar.  What made the problem more interesting was of the three articles, only one article did not bring up the picture, the other two articles operated normally, and brought up the picture in the right sidebar.

There you have it, pause number one. What’s your first guess of what could cause this problem?

Answer: something is different in the one article without the picture, than the articles with the pictures in the right sidebar.

Looking at each article in the editor, the html was identical, except for the actual article content, in each or the articles, no, not the problem. And when you think of it, it didn’t matter if the article html structure was different or not.

Let’s go over how the screen populates.  You click on the “read more.” This is a hidden menu with a link to the template page and article number of the full “read more” page.  When the template loads it goes to the database with the article number, retrieves the page, and inserts it into the center component section of the page.  The template then loads the module for the right sidebar, which goes to the media library, gets the correct picture, and places the picture in the module in the right sidebar, that’s the normal flow.  The module controls the picture load, not the component which loads the article.  The article load has nothing to do with the right picture problem.

Let’s move on. Pause number two, your next guess of what could be the cause of the problem?

Well, something in the module. The module’s job is to place the picture in the right sidebar, true, but, two of the articles work properly, so the module is working properly.

Next guess, the menu link is not hooked up properly, or is bringing up the wrong template. We checked. No not the problem, the menu hook ups were identical to those of the two good articles, as were the section and category.  As a matter of fact, all the parameters you can configure by clicking on menus, the media library, modules, templates, and the articles were identical in all three articles, hmmm.

We started going through the modules and plug ins and wondering if there wasn’t a conflict, yes, we were starting to pull at straws. Each time we’re shot down, by the fact, that two articles are working properly.

Then we came to the “AceSEF” component.  For those not familiar with this component, it has the same functionality as “sh404SEF,” and “JoomSEF.”  Let’s talk about what an SEF component does and how it works.

SEF stands for Search Engine Friendly. An SEF component’s job is to make easy URL’s for pages on your web site.  Instead of having a long URL with numbers and strange long cryptic text, you can call a page something like, “/the_cat_page,” which will show up in the top of the browser in the address window.  The easy URL is easy to remember, and makes it easy for Google to list your site higher up in their ratings.

How does AceSEF or any SEF component work?  When you create a menu item to bring up an article, Joomla assigns it a Joomla URL with the article id number, category id number, and section id number.  This is the sites “Real URL”.  The “Real URL” is usually cryptic, and hard to type.

In the case of a Section Blog Layout, actually three Real URL’s are created for each article.  One for the Section Blog page with the article snippet and the “read more,” one for the full length article page after you click “read more,” and one for the edit page you use when you bring the page up in the article editor.

AceSEF automatically picks up these three menu items and allows you to give each a SEF friendly URL.  SEF programs, including sh404SEF, will allow you to designate one of these three pages as the main page to load, at the exclusion of the other two pages.

The answer to our riddle was in the SEF component.  The “Real URL” of the page with the module to load,  has a picture id included in the Real URL,  which identifies the picture to load into the module.  If the wrong “SEF URL” is assigned to the wrong “Real URL,” then the wrong page loads.  The article will still load in it entirety as it is stored as a blob in the database, but the module will not be able to load the picture, since no picture is identified, so no module will load.  You’ll end up with a blank space, while the other two articles will have the correct SEF link with the picture module and load correctly.

Bottom line, if you find a page where the module is not loading, while other pages with the same module work correctly.  Check your SEF component, and save yourself a lot of the time, we wasted, tracking this one down.

Phoca Gallery Search Plugin: Display thumbnail images

Posted by imperialWicket | Joomla | Monday 7 June 2010 8:05 am

This is a follow up article to the earlier post, regarding exact, any, and all word search support for the Phoca Gallery Search Plugin. If you read the earlier post, you are aware that my three main concerns regarding the Phoca Gallery Search Plugin are the support of exact, any, and all (resolved in the previous post); the lack of a preview image, and the support for showing the correct image (not merely the category) when you select a search result item.

The Problem
Search results that pertain to an image should show the user a thumbnail of the image. When selecting the search result link, the user should see the full image information, not an category page.

To demonstrate these concerns notice the search results when I search for “green normal” (the green normal image is from the earlier post):

Search result with no thumbnail image.

Search result with no thumbnail image.

Now notice the resulting screen when I click on the search result link “fonts and colors: normal and green”. The page does not include the image for which I searched.

Resulting category display without target image.

Resulting category display without target image.

The resulting page is always the first page of the category. In my example, this isn’t a major concern, as there are only 10 images. But it is still an issue, as a user could select an result item from a search and be taken to a page that does not include that target result. For a real site implementation, it is reasonable for a category to have 60, 80, or 100s of images. The image for which the user searched might be far enough away to steer them from your site entirely.

The Solution
Since these both require modifications to the same files, we will address them together. This issue must be resolved in two areas. The first area is the Phoca Gallery Search Plugin, specifically the JOOMLA_ROOT/plugins/search/phocagallery.php file (This is the same file we modified to update the exact/any/all words support.). In this file we need to modify the SQL queries to retrieve more data from the database, and then we need to be sure that the additional data is formatted appropriately and passed along with the search results.

The SQL query modification requires additional column values. Simply modify the following line under the “// Images” comment (the line is 161 in my file, which is modified to support exact, any, all searching per the earlier post):

. ' "2" AS browsernav, b.id as catid, b.alias as catalias'

Update the line so it includes the additional columns below:

. ' "2" AS browsernav, b.id as catid, b.alias as catalias, a.filename, a.alias AS photoalias, a.ordering AS photoordering'

We also want to construct a thumbnail path, and add a flag so that we can easily identify a result item as a phoca image. Update the foreach loop that traverse the $listImages. I am including the entire loop for reference (should be near line 175).

foreach($listImages as $key => $value) {
	$listImages[$key]->href = JRoute::_(PhocaGalleryRoute::getCategoryRoute($value->catid, $value->catalias));

	// Phoca Gallery Search Updates For Thumbnails -- BEGIN

        // Store the filename, so we can generate a path to the thumbnail
        $phoca_filename = $listImages[$key]->filename;

	// Scrub the filename, which is a relative path, if there are sub-directories,
        // we only want the final filename,
	// then append that to the thumbs directory and prefix.
	if(strrpos($phoca_filename,'/') === false){
             $path = 'thumbs/phoca_thumb_s_'.$phoca_filename;
        }else{
             $path = substr($phoca_filename, 0, (strrpos($phoca_filename, '/')+1)).'thumbs/phoca_thumb_s_'.substr($phoca_filename, (strrpos($phoca_filename, '/'))+1);
        }

	// Update the filename with the full path to the thumbnail
        $listImages[$key]->filename = 'images/phocagallery/'.$path;

	//Add ordering info to the URL
	$listImages[$key]->href.='&limitstart='.($listImages[$key]->photoordering-1);

	// Phoca Gallery Search Updates for Thumbnails -- END
}

Now we have access to the image alias and the ordering information. With this data, we can proceed to change the way that the Joomla! Search Component displays its results. This is controlled by the JOOMLA_ROOT/components/com_search/views/search/tmpl/default_results.php file.

It is worth noting that you should override this file within your template directory instead of editing the actual component tmpl file. I will not go over reasons for this in detail, because this is not a Joomla! best practices post. If you want more info on this, search for something to the effect of “Joomla 1.5 template layout override” and you should find more than enough information.

So, instead of editing the current file that controls this behavior, we are going to override it in our template directory. Create a JOOMLA_ROOT/templates/rhuk_milkyway/html/com_search/search/default_results.php file. Note that “rhuk_milkyway” is my active template, and the directory must reflect your active template. If this is your introduction to layout overrides in Joomla!, I would also note that the two paths (the overriding and the overridden) are not a directory for directory match.

In the new default_results.php file we need to make a check to see if the result is a Phoca Gallery Image result, this avoids searching for thumbnails when a user is returned an article result item. We then need to add some custom html to the page to rearrange the result layout, and add our image thumbnail.

[Note that the following two code sections have spaces after opening angle brackets to avoid parsing of html and php code. You can download the updated default_results.php, without the extra whitespace.]

Immediately after the first div tag (line 9), add the following:

< ?php //Phoca Gallery BEGIN ************************************ ?>
    				< ?php if (array_key_exists('phocaImage',$result)): ?>

              < span style='float: left;' class="small< ?php echo $this->escape($this->params->get('pageclass_sfx')); ?>">
  						  < ?php echo $this->pagination->limitstart + $result->count.'. ';?>
  					  < /span>

              < div id='phoca-image' style='float:left; padding:0px 4px;'>

                    < ?php echo ' alt="< ?php echo $result->title ?>"  /> 

              < /div>
              < ?php if ( $result->href ) :
                echo 'Visit this category: ';
    						if ($result->browsernav == 1 ) : ?>
    							
    						< ?php else : ?>
    							
    						< ?php endif;

    						echo $this->escape($result->title);

    						if ( $result->href ) : ?>
    							
    						< ?php endif;
    						if ( $result->section ) : ?>
    							< br />
    							< span class="small< ?php echo $this->escape($this->params->get('pageclass_sfx')); ?>">
    								(< ?php echo $this->escape($result->section); ?>)
    							< /span>
    						< ?php endif; ?>
    					< ?php endif; ?>
    				< /div>

    					< ?php echo $result->text; ?>
    				< /div>
    				< ?php
    					if ( $this->params->get( 'show_date' )) : ?>
    				< div class="small< ?php echo $this->escape($this->params->get('pageclass_sfx')); ?>">
    					< ?php echo $result->created; ?>
    				< /div>
    				< ?php endif; ?>

            < /div>
            < ?php else : ?>

            < ?php //Phoca Gallery BREAK ************************************ ?> 

And immediately before the fieldset closing tag (11-12 lines from the bottom of the file), insert:

  < ?php //Phoca Gallery RESUME ************************************ ?>
  < ?php endif; ?>
  < ?php //Phoca Gallery END *************************************** ?>

Now when we execute a search, the search results display with a thumbnail icon, and the thumbnail icon as well as the result title link to the correct page of images within the target category. Notice the search result with thumbnail (My thumbnail is merely green text, not that impressive.):

Search result with thumbnail.

Search result with thumbnail.

And upon selecting either the thumbnail or the title of the result set item, I am taken to the correct page of the category display, as opposed to the previous links which always loaded the first page of the category:

Search result links directly to correct page within category.

Search result links directly to correct page within category.

A couple of caveats: This implementation requires additional updates to work properly with SEF URLs enabled, and the image paths are hardcoded as the Phoca Gallery/Joomla! defaults. There is a lot of information about the work-arounds necessary for handling SEF in the Phoca Gallery forums, I will note that you need to use the “photoalias” column that we added to the SQL query to build your SEF URL, and you will also need to make a non-displaying menu item to reference as an alias for your Phoca Gallery component.

Phoca Gallery Search Plugin: Multiple keywords (any, exact, and all)

Posted by imperialWicket | Joomla | Saturday 5 June 2010 9:37 am

The Phoca Gallery suite of Joomla! components, plugins, and modules add excellent features, are quite capable, and are actively developed/updated. Nonetheless, these are free extensions, and occasionally you will encounter bugs or undesirable intended functionality in such items. One functionality that affected a couple of my projects was lacking features in the Phoca Gallery Search Plugin. I have a couple concerns with the Search Plugin:

    1. Lacking support for multiple keywords and the any, exact, and all radio options
    2. No preview image
    3. When selecting a result you are taken to the category – and not necessarily the search result image

There are justifiable reasons for some of these, and the resolution for some of these issues can require unique implementation depending on your Joomla! configuration (menu configuration, SEF options, etc. can affect how the code must work).

Getting to the point, I am going to address concern (1) – Lacking support for multiple keywords and the any, exact, and all radio options. I am saving preview images and direct image linking from search results for later posts. A couple of notes/pre-requisites:

    1. I have posted this solution multiple times on the Phoca forums, the only differences here are the plugin/component versions.
    2. You should use the Phoca forums as a first reference for questions/feature requests.
    3. You should consider donations/endorsements for Phoca extensions if you use them regularly.
    4. This is not a how-to for Phoca Gallery use or writing a custom Joomla! Search Plugin, I will gloss over explanations of how/why things work in favor of accomplishing the goal more quickly.
    5. This post references code from the Phoca Gallery component version 2.7.1 and the Phoca Gallery Search Plugin version 2.7.1. The Joomla! installation is version 1.5.18, and all template/configuration options for Joomla! are the defaults from a clean installation.

I uploaded and configured 9 images. The images are title and described with keyword combinations of font type (bold, italic, normal) and font color (blue, green, red). Since we are reviewing particular search functionalities, I will add that the title/description values all follow these formats: “bold and blue” and “Some bold and blue text on white background”. I added a Phoca Gallery Category Layout menu item that links to the Phoca Gallery Category where my images are configured. I also added a Joomla! Search Internal Link menu item and enable the Phoca Gallery Search Plugin. I changed the Phoca Gallery Parameters to display the description in the Category Layout, and I reduced the display number to 5 images in the Category Layout.

Default Joomla! display of Phoca Gallery with sample images.

The Problem
Given the configuration described, when searching only Phoca Gallery content with the keywords “blue and bold”, no results are returned for All words, Any words, or Exact Phrase searches. All words and Any words should both have at least one result (the “bold and blue” image). Exact Phrase is working as expected, since there is no image available including title or description includeing “blue and bold”. Note that All words and Any words searches will strip common terms, thus removing the “and” from our search. Exact Phrase does not strip terms.

Sample screenshot of an errant search results set.

To highlight the root cause of the issue, when we search only Phoca Gallery content with the keywords “bold and blue”, and use the Exact Phrase search technique, we get one result back (which is appropriate). If we supplement this action by searching only Phoca Gallery content with the keyword “bold”, we see that a single word search returns appropriate results for all three search techniques. This indicates a likelihood that the Phoca Gallery Search Plugin is executing a search against the entered String, not the Array of keywords.

Screenshot of what seems like a successful search result set.

The Solution
We must update the search functionality in the Phoca Gallery Search Plugin to convert a keyword String to an Array of keywords, and then query the content based on the Array of keywords.

The logical code for the Phoca Gallery Search Plugin is located in the JOOMLA_ROOT/plugins/search/phocagallery.php file. To reiterate, the line numbers reference version 2.7.1.

First, we must add a case statement that generates unique WHERE clause contents depending on the search technique (We do not want to search based on an Array of keywords if the user selected the Exact Phrase search technique.). Add this case statement at line 52, note that I am including the preceding and following statements for reference:

   if ($text == '') {
      return array();
   }

  $wheres = array();
   switch ($phrase) {
      case 'exact':
         $text      = $db->Quote( '%'.$db->getEscaped( $text, true ).'%', false );
         $wheres2    = array();
         $wheres2[]    = 'a.title LIKE '.$text;
         $wheres2[]    = 'a.description LIKE '.$text;
         $wheres2[]    = 'a.metakey LIKE '.$text;
         $wheres2[]    = 'a.metadesc LIKE '.$text;
         $where       = '(' . implode( ') OR (', $wheres2 ) . ')';
         break;

      case 'all':
      case 'any':
      default:
         $words = explode( ' ', $text );
         $wheres = array();
         foreach ($words as $word) {
            $word      = $db->Quote( '%'.$db->getEscaped( $word, true ).'%', false );
            $wheres2    = array();
            $wheres2[]    = 'a.title LIKE '.$word;
            $wheres2[]    = 'a.description LIKE '.$word;
            $wheres2[]    = 'a.metakey LIKE '.$word;
            $wheres2[]    = 'a.metadesc LIKE '.$word;
            $wheres[]    = implode( ' OR ', $wheres2 );
         }
         $where = '(' . implode( ($phrase == 'all' ? ') AND (' : ') OR ('), $wheres ) . ')';
         break;
   }

   $section = JText::_( 'Phoca Gallery');

Now we are generating an appropriate group of WHERE clause constraints, but the search plugin is not using our new constraints. We must update the both of the SQL statements (the Phoca Gallery Search Plugin initiates two SQL statements, one for Categories and one for Images) to use our updated “$where” constraints.

Locate the categories SQL statement (should be at or near line 106 if you made the case statement code insertion; there is a “// Categories” comment at the top of the query if you need to search for the location).

Update the first portion of the original categories WHERE clause. Replace the following:

    . ' WHERE ( a.title LIKE '.$text
		. ' OR a.name LIKE '.$text
		. ' OR a.metakey LIKE '.$text
		. ' OR a.metadesc LIKE '.$text
		. ' OR a.description LIKE '.$text.' )'

With the updated:

    . ' WHERE ( '.$where.' )'

Now locate the images SQL statement (“// Images” comment at or around 132, “$query” value assigned at or around 152).

Update the first portion of the original images WHERE clause. Replace the following:

		. ' WHERE ( a.title LIKE '.$text
		. ' OR a.filename LIKE '.$text
		. ' OR a.metakey LIKE '.$text
		. ' OR a.metadesc LIKE '.$text
		. ' OR a.description LIKE '.$text.' )'

With the updated:

    . ' WHERE ( '.$where.' )'

Save these updates, and try the search exercise again. Notice an Any words search for “bold blue” returns the appropriate 5 results, and an All words search for “bold blue” returns the appropriate 1 result.

Screenshot of successful search result set after updating the search plugin code.

Happy searching. Now, wouldn’t it be nice if those search results displayed a thumbnail image?

Next Page »