SEO BUG: Wrong canonical URLs in category view (manufacturer, orderby, Itemid)

Started by hazael, October 03, 2025, 13:07:00 PM

Previous topic - Next topic

hazael

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