News:

You may pay someone to create your store, or you visit our seminar and become a professional yourself with the silver certification

Main Menu

Recent posts

#1
I can add another real-world confirmation of this issue.

On a production VirtueMart store, we had exactly the same behaviour:

  • page 2 and subsequent pages worked correctly, for example:

/category/results,81-160.html

  • clicking page 1 changed the URL back to the bare category URL:

/category.html

However, the products from page 2 were still displayed.

The problem appeared only in some categories, which initially made it look inconsistent.

After comparing the Joomla menu items assigned to the affected and unaffected VirtueMart categories, we found an important difference.

Some category menu items contained only a short link such as:

index.php?option=com_virtuemart&view=category&virtuemart_category_id=2

After opening and saving the same menu item in Joomla, its link was expanded with additional parameters:

index.php?option=com_virtuemart&view=category&virtuemart_category_id=2&virtuemart_manufacturer_id=0&virtuemart_manufacturercategories_id=0&clearCart=0&limitstart=0

After updating the affected menu items, returning from page 2 to page 1 started working correctly.

The most relevant parameter for this pagination issue is:

&limitstart=0

Even though limitstart=0 is not visible in the final SEF URL, it is stored in the active Joomla menu item.

This appears to be consistent with Joomla routing behaviour. When parsing a URL, Joomla merges the active menu item's query variables with the variables parsed from the route:

$active = $this->router->menu->getActive();

if ($active) {
$vars = array_merge($active->query, $vars);
}

Therefore, even if the SEF router removes limitstart=0 from the visible URL, the value can still be supplied through the active menu item and made available to VirtueMart during the request.

This may also explain why the issue appears only in some categories or installations:

  • some VirtueMart category menu items already contain limitstart=0,
  • older or incomplete menu items may contain only the shortened link,
  • categories without a matching menu item cannot benefit from this workaround.

In our case, the store contained approximately 600 VirtueMart category menu items, so opening and saving them manually was not practical.

We applied the workaround directly in the Joomla menu table.

First, we checked which menu items would be affected:

SELECT
`id`,
`title`,
`link`
FROM `yourprefix_menu`
WHERE `link` LIKE 'index.php?option=com_virtuemart&view=category%';

Then we appended the missing parameters only when they were not already present:

UPDATE `yourprefix_menu`
SET `link` = CONCAT(
`link`,
IF(
`link` NOT LIKE '%virtuemart_manufacturer_id=%',
'&virtuemart_manufacturer_id=0',
''
),
IF(
`link` NOT LIKE '%virtuemart_manufacturercategories_id=%',
'&virtuemart_manufacturercategories_id=0',
''
),
IF(
`link` NOT LIKE '%clearCart=%',
'&clearCart=0',
''
),
IF(
`link` NOT LIKE '%limitstart=%',
'&limitstart=0',
''
)
)
WHERE `link` LIKE 'index.php?option=com_virtuemart&view=category%'
AND `published` = 1;

yourprefix_menu must be replaced with the actual Joomla menu table name, for example:

abc_menu

The Joomla #__menu placeholder cannot be used directly in phpMyAdmin.

A database backup should of course be made before running the UPDATE query.

After running the query and clearing the Joomla and VirtueMart caches, the affected categories correctly returned visitors from page 2 or later to page 1.

I did not test whether all the additional zero-value parameters are required individually. For this particular pagination problem, limitstart=0 is clearly the relevant parameter.

The other parameters were added because they matched the complete link generated after opening and saving a VirtueMart category menu item in Joomla.

This database change should still be treated only as a workaround, not as a core fix.

It depends on the request being associated with a matching Joomla menu item. It may not fully cover:

  • category URLs opened from external websites,
  • bookmarked bare category URLs,
  • categories without their own Joomla menu item,
  • breadcrumbs using a different menu context,
  • manufacturer or search result pagination.

The system plugin proposed above is therefore a broader workaround because it acts directly on the incoming request.

However, the underlying issue should still be fixed in VirtueMart core. A bare category URL should reliably open page 1 instead of inheriting the previous limitstart value stored in the visitor's session.
#2
Hello,

I have found a reproducible problem with product image uploads in VirtueMart 4.6.8.

### Environment

* VirtueMart: 4.6.8 build 11258
* Joomla: 5.4.7
* PHP: 8.4.22

### Problem description

When a new product image has the same filename as an image that already exists in the VirtueMart product image directory, VirtueMart overwrites the existing physical file.

This means that uploading an image to one product can unexpectedly replace an image already used by another product.

This is particularly dangerous in stores with many products, because administrators cannot realistically ensure that every uploaded image has a globally unique filename.

### Steps to reproduce

1. Create or edit Product A.
2. Upload an image named: 1.jpg
3. Save Product A.
4. Create or edit Product B.
5. Upload a different image which is also named: 1.jpg
6. Save Product B.
7. Return to Product A or view it on the frontend.

### Actual result

The physical file `1.jpg` is overwritten by the image uploaded to Product B.

As a result, Product A starts displaying the image uploaded for Product B.

The problem occurs without any warning to the administrator.

### Expected result

When `$overwrite` is set to `false` and a file with the same name already exists, VirtueMart should generate and use a unique filename, for example:

1.jpg
17.jpg
173.jpg

The existing physical file should not be overwritten.

### Code analysis

The problem appears to be located in:

administrator/components/com_virtuemart/helpers/vmuploader.php


in the method:
static function checkUploadFile($uploadPath, &$media, $overwrite, &$isimage)


The current code correctly checks whether a file with the same filename already exists:
if(!$overwrite){
    $i = 0;

    while (file_exists($uploadPath.$mediaPure.'.'.$mediaExtension) and $i<20) {
        $mediaPure = $mediaPure.rand(1,9);
        $i++;
    }

    if($i>=20){
        vmError('Could not upload file, would overwrite existing '.$media['name']);
        return false;
    }
}


The loop modifies `$mediaPure` and generates a new filename base.

For example:
1


may be changed to:
17


However, the generated filename is never assigned back to:
$media['name']


The upload is then performed using the original filename:

$uploaded = JFile::upload(
    $media['tmp_name'],
    $uploadPath.$media['name'],
    false,
    $trusted
);


At this point, `$media['name']` still contains:
1.jpg


Therefore, the existing file is overwritten even though the collision detection code generated a different value in `$mediaPure`.

### Suggested fix

After the filename collision check and before calling `JFile::upload()`, the generated filename should be assigned back to `$media['name']`:

$media['name'] = $mediaPure.'.'.$mediaExtension;


The corrected code would be:

if(!$overwrite){
    $i = 0;

    while (file_exists($uploadPath.$mediaPure.'.'.$mediaExtension) and $i<20) {
        $mediaPure = $mediaPure.rand(1,9);
        $i++;
    }

    if($i>=20){
        vmError('Could not upload file, would overwrite existing '.$media['name']);
        return false;
    }
}

$media['name'] = $mediaPure.'.'.$mediaExtension;

$uploaded = JFile::upload(
    $media['tmp_name'],
    $uploadPath.$media['name'],
    false,
    $trusted
);

Because `$media` is passed by reference:

static function checkUploadFile($uploadPath, &$media, $overwrite, &$isimage)


the corrected filename will also be available later in `uploadFile()`:
$obj->file_name = $media['name'];


and should therefore be saved correctly in the VirtueMart media record.

### Additional information

I checked both:

* the `vmuploader.php` file currently installed on the website,
* the `vmuploader.php` file from the original VirtueMart 4.6.8 installation package.

Both files contain the same code.

Therefore, the issue does not appear to be caused by a modified, incomplete or corrupted installation.

Adding the following line before `JFile::upload()` prevents the existing file from being overwritten:


$media['name'] = $mediaPure.'.'.$mediaExtension;


Could you please confirm whether this is a bug and include the fix in the next VirtueMart release?

Thank you.
#3
Hi Gingerweb,

I can confirm this bug, and your diagnosis is exactly right. I ran into the same issue on Joomla 5.4.6 / VirtueMart 4.6.8 / PHP 8.x and traced it to the same two interacting pieces:

  • vmpagination.php builds the "Start", "Previous" and "Page 1" links with limitstart=0&limit=N
  • the SEF router strips limitstart=0 because it is the default value, so the request arrives with no pagination parameter at all
  • the model then calls getUserStateFromRequest('limitstart', ...), which falls back to the value stored in the session, so you stay stuck on the current page

Two things I tried that do NOT work, in case it saves someone time:

  • A template override of the pagination chrome that appends start=0 to the href: the canonical SEF redirect strips the parameter server side anyway, so the effective URL is bare again and the bug persists.
  • Patching vmpagination.php directly: it works but core hacks get wiped on every VM update, so I ruled it out.

The fix that works reliably for me is a tiny system plugin acting on the incoming request instead of the generated URL. On onAfterRoute (after Joomla has parsed the URL and populated the input), if the request targets a paginated VirtueMart view and carries no limitstart at all, force it to 0:

public function onAfterRoute(Event $event): void
{
    $app = Factory::getApplication();
    if (!$app->isClient('site')) {
        return;
    }
    $input = $app->getInput();
    if ($input->getCmd('option') !== 'com_virtuemart') {
        return;
    }
    $view = $input->getCmd('view');
    if (!\in_array($view, ['category', 'virtuemart', 'manufacturer', 'search'], true)) {
        return;
    }
    // A real pagination request (page 2+) always carries limitstart in the URL.
    // A bare URL means "page 1", so do not let the session value win.
    if ($input->get('limitstart', null) !== null) {
        return;
    }
    $input->set('limitstart', 0);
}

This covers every path back to page 1: the Start / Previous / Page 1 pagination links, menu links, breadcrumbs and bookmarked category URLs.

One side effect to be aware of: any bare VirtueMart URL (without limitstart) now always lands on page 1 instead of restoring the page remembered in the session. In my opinion that is the expected behaviour anyway (it is what every visitor coming from a menu link or an external link would expect), but it is worth mentioning.

It would be nice to have this handled in VM core, for example by having vmpagination.php emit the page 1 links with a parameter the SEF layer does not strip, or by not falling back to the session state when the request is a plain category URL.

Hope this helps.
#4
Category pagination — cannot return to page 1 when SEF enabled (limitstart lost, session retains last page)

Version: VirtueMart 4.6.8 (11258), Joomla 5.4.6, PHP 8.x, Joomla core SEF enabled (no third-party SEF/router)
Summary:

On a category with more than one page of products, navigating to page 2 and then clicking the "1" (or "Start"/"Prev") pagination link does not return the visitor to page 1. The page reloads still showing page 2.
Steps to reproduce:

Enable Joomla SEF URLs.
Open a category with 2+ pages of products (default limit, e.g. 12 per page).
Click "2" — URL becomes /category/results,13-24, page 2 displays correctly.
Click "1" (or « / ‹).
Observed: browser navigates to the bare category URL /category and page 2 is still shown.
Expected: page 1 is displayed.

Root cause (traced):
Two interacting pieces of core code:

In administrator/components/com_virtuemart/models/product.php (setPaginationLimits), when the visitor is already within the same category/manufacturer, limitStart is read via getUserStateFromRequest() against a per-category session key (com_virtuemart.<view>c<cateid>m<manid>.limitstart). This only overrides the stored session value if limitstart is explicitly present in the request.

In components/com_virtuemart/router.php, the build routine only emits a results,X-Y segment when limitstart > 0. For page 1 (limitstart = 0) no segment is added, so the page-1 pagination link is generated as the bare category URL. Joomla's SEF router additionally strips limitstart=0 as it equals the default.

Result: the page-1 link carries no page position. When it loads, getUserStateFromRequest() finds no limitstart in the request and falls back to the session value, which still holds the offset for page 2 — so the visitor is stuck on page 2.

Note: This only reproduces with an active session already advanced past page 1. A fresh/cookieless request to /category/results,1-12 returns page 1 correctly, which is why it can look intermittent.

Suggested fix direction:
Either (a) have the router emit an explicit results,1-N segment for limitstart = 0 so the page-1 link isn't collapsed to the bare URL, or (b) in the category model, treat a request with no limitstart/start parameter as page 1 rather than inheriting the stored session offset.
#5
VmMediaHandler::$theme_url is never assigned, breaking the "no image" placeholder URL (front and admin)

Environment
  • VirtueMart 4.6.8
  • Joomla 5.4.6
  • PHP 8.1 / 8.5

Summary
The static property VmMediaHandler::$theme_url is declared as null and is never assigned anywhere in the codebase (I checked both the site and administrator sides, including the legacy VmMedia class). Because of that, every "no image" placeholder built from it resolves to a broken URL that is missing the components/com_virtuemart/ segment.

Where it happens
In administrator/components/com_virtuemart/helpers/mediahandler.php:

// line 45
static $theme_url = null;

// line 591, inside setNoImageSet()
$this->file_url_folder = self::$theme_url.'assets/images/vmgeneral/';

// line 736, inside getIcon()
$tC = self::$theme_url.'assets/images/vmgeneral/filetype_'.$this->file_extension.'.png';

// line 751, inside getIcon()
$file_url = self::$theme_url.'assets/images/vmgeneral/'.VmConfig::get('no_image_found');

Since $theme_url is always null, the concatenation collapses to just assets/images/vmgeneral/..., with the components/com_virtuemart/ prefix missing entirely.

Then in displayIt() (same file, around line 785):

if( substr( $this->file_url, 0, 2) == "//"  or substr( $this->file_url, 0, 4) == "http" ) {
} else if($absUrl){
$root = JURI::root(false);
} else {
$root = JURI::root(true).'/';
}
...
$imageArgs['src'] = $root.$file_url;

On a shop installed at the domain root, JURI::root(true) returns an empty string, so $root becomes just /. The final src ends up as:

/assets/images/vmgeneral/noimage_new.gif
instead of the correct:

/components/com_virtuemart/assets/images/vmgeneral/noimage_new.gif
The correct path is confirmed elsewhere in core, for example in administrator/components/com_virtuemart/models/config.php (around line 222), which hardcodes:

$dirs[] = VMPATH_ROOT.'/components/com_virtuemart/assets/images/vmgeneral';
Steps to reproduce
  • Create or open a manufacturer with no logo assigned.
  • Go to Manufacturer > Edit > Images tab.
  • Inspect the placeholder image: src="/assets/images/vmgeneral/noimage_new.gif", which 404s.

The same broken prefix affects any entity that falls back to setNoImageSet() or the getIcon() fallback (vendor, product, category, etc.) whenever no image/file is set, on both front and back end.

Suggested fix
Assign VmMediaHandler::$theme_url to 'components/com_virtuemart/' (the same segment already hardcoded in config.php) at class init, or replace the direct use of self::$theme_url in setNoImageSet() and getIcon() with a call that returns that path directly, since the property is currently dead weight that is only ever read, never written.

Happy to test a patch on a staging VM 4.6.8 / Joomla 5.4.6 install if useful.
#6
Frontend Modules / Re: VirtueMart Search Product ...
Last post by iWim - July 06, 2026, 07:58:22 AM
Open the VM search module.
Under tab Module set the option Search Filter Category to No.
#7
Frontend Modules / VirtueMart Search Product how ...
Last post by Emma Iana - July 05, 2026, 22:20:58 PM
How can I make VirtueMart Search Product search across all products rather than just the category in which the module is embedded?
#8
General Questions / Re: Google Merchant Center Pro...
Last post by p.barg - July 01, 2026, 12:04:15 PM
Hi,

we are using CVSI for the export of Google Feeds.

Best regards,

Petra
#9
General Questions / Re: Issues with add to Cart . ...
Last post by hotrod - June 30, 2026, 23:27:33 PM
I moved on to the J4 site I built 2 years ago..  No Issues..  Now I am going to work on moving to J5
#10
Installation, Migration & Upgrade / Re: Error after VM update
Last post by PRO - June 30, 2026, 21:26:06 PM
have you looked up if there is a duplicate? category_child_id