Hi,
I have found a bug in the way VirtueMart generates canonical URLs in the category view:
components/com_virtuemart/views/category/view.html.php.
Currently, the function setCanonicalLink() builds a canonical link without taking into account the active Itemid, orderby parameter, or sometimes even the manufacturer ID. This causes several SEO problems:
Canonical mismatch - Example:
QuotePage: /shop/bracelets/india?orderby=created_on
Canonical: /shop/india
Google reports a conflict between canonical and hreflang.
Current defective function
public function setCanonicalLink($tpl,$document,$categoryId,$manId){
// Set Canonic link
if (!empty($tpl)) {
$format = $tpl;
} else {
$format = vRequest::getCmd('format', 'html');
}
if ($format == 'html') {
// remove joomla canonical before adding it
foreach ( $document->_links as $k => $array ) {
if ( $array['relation'] == 'canonical' ) {
unset($document->_links[$k]);
break;
}
}
$link = 'index.php?option=com_virtuemart&view=category';
if($categoryId!==-1){
$link .= '&virtuemart_category_id='.$categoryId;
}
if($manId!==-1 and !empty($manId)){
$link .= '&virtuemart_manufacturer_id='.$manId;
}
//vmdebug('caetgory view setCanonicalLink',$link);
$document->addHeadLink( JUri::getInstance()->toString(array('scheme', 'host', 'port')).JRoute::_($link, FALSE) , 'canonical', 'rel', '' );
}
}VirtuemartViewCategory::setCanonicalLink() the canonical URL should be generated using the active Itemid and all relevant parameters (category_id, manufacturer_id, orderby). This Patch prevents Google Search Console errors (duplicate or conflicting canonical/hreflang).Ensures consistent canonical across filters, sorting, and multi-language pages.
Replace the above function with a new one that is fully SEO-compliant:
public function setCanonicalLink($tpl, $document, $categoryId, $manId) {
if (!empty($tpl)) {
$format = $tpl;
} else {
$format = vRequest::getCmd('format', 'html');
}
if ($format == 'html') {
// remove old canonical
foreach ($document->_links as $k => $array) {
if ($array['relation'] == 'canonical') {
unset($document->_links[$k]);
break;
}
}
// build a base link
$link = 'index.php?option=com_virtuemart&view=category&Itemid=' . $this->Itemid;
if ($categoryId !== -1) {
$link .= '&virtuemart_category_id=' . $categoryId;
}
if ($manId !== -1 && !empty($manId)) {
$link .= '&virtuemart_manufacturer_id=' . $manId;
}
// ADD active sorting parameters (so that canonical is consistent with hreflang)
$orderby = vRequest::getCmd('orderby', '');
$dir = vRequest::getCmd('dir', '');
$limit = vRequest::getInt('limit', 0);
$start = vRequest::getInt('limitstart', 0);
if ($orderby !== '') $link .= '&orderby=' . urlencode($orderby);
if ($dir !== '') $link .= '&dir=' . urlencode($dir);
if ($limit > 0) $link .= '&limit=' . $limit;
if ($start > 0) $link .= '&limitstart=' . $start;
// full link
$canonicalUrl = JRoute::_($link, true, -1);
$uri = JUri::getInstance($canonicalUrl);
$uri->setScheme('https');
$canonicalUrl = $uri->toString(['scheme','host','port','path','query']);
$document->addHeadLink($canonicalUrl, 'canonical', 'rel');
}
}
Isnt that against the idea of canonical? For exampel I have a category, all products are on one page. So I think there shouldnt be different links, if we just sort the products different.
on the other hand, if we have 2 pages,... it makes a difference. We have different products. Nasty. I would like to get more opinions about that matter.
Yes, Max — you're right. The whole problem comes from having the site in two language versions.
Now it works fine — that was my fix.
Sort by price:
public function setCanonicalLink($tpl, $document, $categoryId)
{
$format = !empty($tpl) ? $tpl : vRequest::getCmd('format', 'html');
if ($format === 'html') {
// Remove existing canonical
foreach ($document->_links as $k => $array) {
if ($array['relation'] === 'canonical') {
unset($document->_links[$k]);
break;
}
}
// Build canonical link
$link = 'index.php?option=com_virtuemart&view=category&Itemid=' . $this->Itemid;
if ($categoryId !== -1) {
$link .= '&virtuemart_category_id=' . $categoryId;
}
// Generate final canonical URL
$canonicalUrl = JRoute::_($link, true, -1);
$uri = JUri::getInstance($canonicalUrl);
$uri->setScheme('https');
$canonicalUrl = $uri->toString(['scheme', 'host', 'port', 'path', 'query']);
$document->addHeadLink($canonicalUrl, 'canonical', 'rel');
}
}
<link href="/shop/category" rel="canonical">
<link href="/shop/category/manufacturer/by-price" rel="alternate" hreflang="pl">
<link href="/en/shop/category/manufacturer/by-price" rel="alternate" hreflang="en"><link href="/shop/category" rel="canonical">
<link href="/shop/category/by-price" rel="alternate" hreflang="pl">
<link href="/en/shop/category/by-price" rel="alternate" hreflang="en">I changed the order of parameters in the router for the manufacturer — previously it was placed before the category name, which caused issues with the logical URL structure.
For me it looks like, that you just added the Itemid