Remove SKU from order invoice and email in virtuemart

Here are the list of files to modify to remove sku from virtuemart, order, invoices and emails:

com_virtuemart/cart/default_pricelist.php

com_virtuemart/invoice/invoice_items.php

com_virtuemart/invoice/mail_html_pricelist.php

com_virtuemart/invoice/mail_raw_pricelist.php

com_virtuemart/orders/details_items.php

When modifying tables, change the product name column to colspan=”3″ where you find colspan=”2″

Make sure to create overrides in your template

If your template already have overrides for those then save your files before updating the template as they would be overriden.

Ideally make a copy of your template to not accidently erase your modification when updating your template.

Modify Virtuemart User Account Maintenance URL and page name.

Issue: user account maintenant pages are targeted by spammer to create fake account on your site.

To mitigate this issue on top of a solid firewall, you can change the default URL and page name so that the crawler won’t be able to automatically find it.

Solution:

1-Edit or create a menu item for the Virtuemart USer Account Maintenance page if you didn’t have one previously

2-Set the page title and the alias to whatever you think is more suitable. You might for instance localize the string.

How to prevent home page modules to show on virtuemart search results page?

Issue: Home page modules show on virtuemart search result pages, which in some cases might not be desirable.

Solution: The solution that is widely found searching on the internet is that you need to create a menu item for the search module. If you do so, and use the virtuemart search module, this should not give any results. The missing step is that you need to create an overwrite for your virtuemart seach module and edit it. In the search form action, replace index.php by “search” or whatever you set the search menu item alias to be.

In short:

1- Create menu item for joomla search

When selecting the menu item type, select “Search Form or Search Results”
Note the alias for the page, as you will need it at a later step.

2-In your templates customization page, create an override for the mod_virtuemart_search default.php file:

3- Open the override you created and modify the route to point it to the newly created search page:

JRoute::_ ('index.php?option=com_virtuemart&view=category&limitstart=0', FALSE);
JRoute::_ ('search?option=com_virtuemart&view=category&limitstart=0', FALSE);

4- Control where the modules are displayed as usual.

Change Product Available Date Format in Virtuemart

You might have noticed that virtuemart date format for the “Product Available Date” notice is in US format (at least for my install). This format (YYYY-MM-DD) is a bit confusing in countries where the standard is different, for instance the standard in Europe is DD-MM-YYYY.

Now we will attempt to display this product available date in our choosen standard.

In com_virtuemart/product/details/default.php, you can see that the part controlling the display of the product availability date is stockhandel.php in sublayouts

By consulting this file you can quickly locate the variable for the product availabity date string followed by the variable for the date string: DATE_FORMAT_LC4

Now that we have this information we can go in Extensions -> Languages->overrides

select your front end language on the drop down menu. Enter the variable and in text enter the date format you want to use.

A user friendly format might be: j F yy

This will yield a date in this format: 28 February 2020

Implementing lazy loading in Virtuemart

[UPDATE] Now joomla 4 uses native lazy loading thanks to the loading attribute.

Exemple:

<img src="image.png" loading="lazy" alt="…" width="200" height="200">

https://github.com/joomla/joomla-cms/issues/27761

Let’s start this a quick refresh on lazy loading:

https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/

To implement lazy loading (with this method) in virtuemart category page, you will need to add a class to the thumbnail <img> element, which is easily done in /com_virtuemart/sublayouts/products.php

Look for the line:

echo $product->images[0]->displayMediaThumb('class="browseProductImage"', false);

and add the desired class to the displayMediaThumb() function:

echo $product->images[0]->displayMediaThumb('class="browseProductImage lazy"', false);

In your theme template, create an override for the index.php and add the javascript in the footer:

 document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Possibly fall back to a more compatible method here
  }
}); 

At this stage your code won’t work and no image will be displayed. For the code to work you need to have a data-src defined in the <img> element.

One way to do this is to locate where the <img> html for the thumbnail is generated and create an override to define a data-src instead of the usual src.

I didn’t find any documentation as to where this code is generated, so I add to search for it. I used eclipse IDE and added a local copy of my virtuemart installation as a workspace in the IDE then search the function in the folder. It was located in:

/administrator/components/com_virtuemart/helpers/mediahandler.php

The html per say is generated by the displayIt() function. We change src to data-src in the file for the moment as it is not clear to me how I’ll be able to override this function in my theme.

My first test after settings all this is not running properly. Actually it does even seem to act the opposite it should, with the picture first loaded and that disappear when it crawls into view.

Let’s debug that. First, I checked that all my code is properly loaded, namely the class for the image and the data-src to prevent the picture to load before it come into view.

At that stage the “lazy” class is properly added to the source code but the image source is not changed to data-src as expected.

Changing this in the developer console does not solve the issue (picture still not showing) unless the srcset=”undefined” is deleted. This srcset attribute is added by the lazyload js.

By searching for <img in the mediahandler.php file I found the function displayImage(). I changed the src attribute to data-src and remove the srcset line from the lazyload javascript before running another test.

Changed src to data-src in evey img element in displayImage() and displayIt() function. Now the lazy loading function is working except for some css issue (product name appearing on overlay on the image instead of displaying below the image). Some pictures do not appear at all.

Product img elements have now data-src attribute instead of src but , they don’t have the “lazy” class so they are not displayed when they come into the view port. This might be changed in the single product view.

Product image has no class set but they don’t load. The lazy load script should not prevent image without the lazy class to load, so the issue might be that I changed the src attribute to data-src.

So the displayIt() function in /administrator/components/com_virtuemart/helpers/mediahandler.php handles picture in the product view as well as in the category view.

To add the lazy class to the product image and additional thumbnails, we need to modify default_images.php and default_images_additional.php in templates/your_template_name/html/com_virtuemart/productdetails/

in defaut_images.php look for the following code:

if(!empty($width) or !empty($height)){

            echo $image->displayMediaThumb("",true,"rel='vm-additional-images' class='block'", true, true, false, $width, $height);

        } else {

            echo $image->displayMediaFull("",true,"rel='vm-additional-images' class='block'");

        }

and change it to:

if(!empty($width) or !empty($height)){
echo $image->displayMediaThumb("class='lazy'",true,"rel='vm-additional-images' class='block'", true, true, false, $width, $height);
} else {
echo $image->displayMediaFull("class='lazy'",true,"rel='vm-additional-images' class='block'");
}

And in default:images_additional.php

 if(VmConfig::get('add_img_main', 1)) {
            echo $image->displayMediaThumb('class="product-image thumbnail" style="cursor: pointer"',false,false);
            echo '';
        } else {
            echo $image->displayMediaThumb("class='thumbnail'",true,"rel='vm-additional-images'",true,false);
        }

becomes:

if(VmConfig::get('add_img_main', 1)) {
            echo $image->displayMediaThumb('class="product-image thumbnail lazy" style="cursor: pointer"',false,false);
            echo '';
        } else {
            echo $image->displayMediaThumb("class='thumbnail lazy'",true,"rel='vm-additional-images'",true,false);
        }

Note: that you must override these two files in your template if you don’t want to see your changes deleted by the next update.

At that point we are left with the issue that thumbnails div does not have the appropriate height which leads to the product name to overlay on the picture, which in our case is not desirable. It seems to be handled in the products.php sublayouts and the class of the div we are looking for is vm-product-media-container. The div is indeed located in this file but it does not have the style attribute that causes the issue, nor does it call a variable for this, that suppose that the inline style is added through a filter on the server side or through js or jquery on the client side.

In our case, the following inline style is injected and cause display issue: style=”height: 42px;”

 Line 1165  $imgHeight = VmConfig::get('img_height','');

        if(!empty($imgHeight)){

            $imgHeight = 'height:'.VmConfig::get('img_height',90).'px;';

        } else {

            $imgHeight = '';

        }

To be specific the issue in on the div wrapping the image:

<div class=”vm-product-media-container” data-mh=”media-container” style=”height: 63px;”>

This code is generated in : /html/com_virtuemart/sublayouts/products.php

function displayMediaThumb($imageArgs='',$lightbox=true,$effect="class='modal' rel='group'",$return = true,$withDescr = false,$absUrl = false, $width=0,$height=0)

Let’s see where the display MediaThumb() is called and find out where the 42px height is defined.

in default_images.php

if (!empty($this->product->images)) {
    $image = $this->product->images[0];
?>
displayMediaThumb("",true,"rel='vm-additional-images'", true, true, false, $width, $height);
} else {
echo $image->displayMediaFull("",true,"rel='vm-additional-images'");
        }
?>

This div height is related to the thumbnails height settings in the template configuration panel. In the current state, the height takes various value each time the page is refreshed. Setting the width and height to zero or empty in the configuration panel leads to no image being displayed, dynamic resizing being off.

With dynamic resizing on and a value of 100 we got a good rendering, without issue.

200 leads to some issue. This might have to do with the dynamic resizing. Let’s try to locate this code to check what’s happening there.

The style does not appear in the source code. It appears in the DOM only (chrome and firefox). This tend to show that the inline style is generated by javascript. To try to spot the script causing the issue, we will add <script>debugger</script> after the element to debug. Reload the page and add a breakpoint by following the instruction here:

https://developers.google.com/web/tools/chrome-devtools/javascript/breakpoints

Results: Paused on attribute modification: jquery.min.jsmedia/jui/js/jquery.min.js

Stack frame:

style (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
(anonymous) (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
Q (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
Q (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
css (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
(anonymous) (jquery.matchHeight-min.js:8)
each (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
each (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)
r.apply (jquery.matchHeight-min.js:8) t.fn.matchHeight (jquery.matchHeight-min.js:7) (anonymous) (jquery.matchHeight-min.js:11) each (jquery.min.js?105ec84b263645980d07d0cf26df4607:2) r._applyDataApi (jquery.matchHeight-min.js:11) u (jquery.min.js?105ec84b263645980d07d0cf26df4607:2) fireWith (jquery.min.js?105ec84b263645980d07d0cf26df4607:2) ready (jquery.min.js?105ec84b263645980d07d0cf26df4607:2) (jquery.min.js?105ec84b263645980d07d0cf26df4607:2)

jquery.matchHeight is a jQuery library to achieve equal height between div. In modern browsers, there are css solutions that achieve the same results: check Flexbox and CSS Grid.

Horme3 template uses /js/jquery.matchHeight-min.js

So matchHeight library does not handle properly the height of the div with the lazy loading technique I am using at the moment. The script is called in the index.php file of the template. Let’s have a look how the site behave without this script.

jquery.matchHeight catch the data-mh=”media-container” attributes to perform its operation. I tried to remove it from the thumbnail wrapper and add it to the product wrapper but the result is a mess. I will check if I can rewrite the template to support Flexbox instead.

So the issue of height not being set when the src is missing or invalid has also caught the attention of css-tricks which wrote a nice article about the topic. Let’s see if we can implement their options.

Lazy loading draw back: Content Reflow

The solution I will try is to give a svg source at the right size and aspect ratio encoded in base64 (or not: https://css-tricks.com/probably-dont-base64-svg/ )

I got to work out how to configure the viewport attribute in image to get the right aspect ratio.

Also the number of line of the short description is now an issue with the div height. Got to trouble shoot that.

Maybe best would be to restore all changes and reimplement lazy loading and psvg place holder from the ground up to avoid errors introduced when fiddling in the code to find out how to fix the height :)

This is my base64 placeholder (<svg></svg> method did not work):

src=”data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgaWQ9InN2ZzgyMSIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMTQ2Ljg0Mzc1IDkzLjkyNzA4NiIKICAgaGVpZ2h0PSIzNTUiCiAgIHdpZHRoPSI1NTUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM4MTUiIC8+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhODE4Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICAgIDxkYzp0aXRsZT48L2RjOnRpdGxlPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZwogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTIwMy4wNzI5KSIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxyZWN0CiAgICAgICByeT0iMC4yNjcyNjk1MiIKICAgICAgIHk9IjIwMi45NDg3NiIKICAgICAgIHg9Ii01LjgxNjU0NjVlLTAwOCIKICAgICAgIGhlaWdodD0iOTQuMDA1MzQxIgogICAgICAgd2lkdGg9IjE0OS42NzA5NCIKICAgICAgIGlkPSJyZWN0NDUwOSIKICAgICAgIHN0eWxlPSJvcGFjaXR5OjAuOTgwMDAwMDQ7ZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDowO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7c3Ryb2tlLW9wYWNpdHk6MSIgLz4KICA8L2c+Cjwvc3ZnPgo=”

Current issue is due to single line description not being correctly adjusted by matchHeight. Otherwise all the rest is working.

Issue was caused by some script being disabled in virtuemart configuration panel. Virtuemart CSS, jquery and Fancybox were disabled.

The place holder should have a height matching the height of the highest thumbnails. Working with normalized thumbnail picture (in terms of dimension) would make things way easier and cleaner.

Overriding templates of admin component takes place in administrator/templates/html/com_virtuemart/helpers/mediahandler.php

Check which admin theme you are using.

Log in the admin dashboard and check the page source, search for

/administrator/templates/your template name/css/template.css?105ec84b263645980d07d0cf26df4607

So at that point all issues have been solved. We need to check why plain SVG failed to load correctly. Specially even with encoding an inkscape file saved as plain svg in base64, we have a file size that have nearly double compared with our current page size. Off course we don’t need to load pictures that are not in the view port which is saving bandwidth and we even gained a smoother experience as the element are already placed properly on the page thanks to the svg place holder which is inline which means no loading time and no recalculation.

That seems to be a win. We need to ensure that the file that needs changes can be overriden properly and also browser compatibility to avoid bad experience.

Remove component/virtuemart from search results’ URLs

To remove component/virtuemart from URL’s, all you need to do is to create a menu with all your category hierarchy.

It might occur that you want to keep your navigation menu simple and that choose to not put the sub categories (of the nth degrees) in your main menu. In that case URLs of the items in these subcategories will appear with the component/virtuemart part in their URL, which might lead to duplicate content issue and is not very tidy.

There are two solutions:

Solution 1: Hide sub-categories with css in your navigation menu.

Solution 2: create a hidden menu that reproduce the hierarchy of the main menu and add your sub-categories as child of their respective parent category there.

Note that you can’t two URL with the same alias, that means that in order to add the top levels categories that are already in your main menu you can’t use “Virtuemart > Category layout” as menu item type. Instead use “system links > URL” for all your category that are in your main menu. For the sub-categories you can use
“Virtuemart > Category layout” as usual. Remember to attribute the correct parent for each sub-categories to keep a clean navigation structure and avoid potential duplicat content.

It’s quite trivial but it took me a while to figure out so I thought I might as well share it with you.

Virtuemart Check default Radio Custom field.

The javascript for this is really simple:

radiobtn = document.getElementById("theid"); 
radiobtn.checked = true;

The difficulty will lie in the fact that the id of the element is changing depending on the product. Indeed the product id is used in the radio id.

A convenient option is to use CSS selectors in javascript:


radiobtn =document.querySelector(CSS selectors) .

We want to select the first radio input where the parent is div.controls

radiobtn =document.querySelector(" div.controls> label  > input");  
radiobtn.checked = true;

For good measure we should check that div.controls exists on the page and that we are on a product page.

if(document.querySelector(" div.controls > label  > input")){
radiobtn = document.querySelector(" div.controls > label > input");
radiobtn.checked = true;
}

This query only catch the first radio button custom field. In case we have a second custom field options for our product we need to catch that too. We will use jquery for that purpose to catch the radio button id whatever product page it is set on (remember that the product id is part of the radio button id and that’s the only changing part).

jQuery.noConflict();
jQuery(document).ready(function(){
if (jQuery(“[id$=_885843]”)){
jQuery(“[id$=_885843]”).prop(“checked”,true);
}
});

Repeat the operation for any additional custom fields needed a default check.