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');
}
}