1. piekielko
  2. RO CSVI
  3. Thursday, 12 March 2026
  4.  Subscribe via email
Hi RolandD,

Could you please consider adding the following code to the file
administrator/components/com_csvi/models/exports.php?

Since the latest version of the component, my custom PHP scripts for XML export have stopped working. The new class loading mechanism no longer detects custom exporters located in the com_csvi/helper/file/export/xml/ directory. Adding this small fallback loader allows custom exporters to be loaded correctly again without affecting the core functionality.

in line 2053

protected function loadExportFile(): void
{
$this->setExportFormat();

$exportsite = match ($this->exportFormat)
{
'xml', 'html' => $this->template->get('export_site', 'csvimproved'),
default => 'csvimproved',
};

$classname = '\\Rolandd\\Component\\Rocsvi\\Site\\File\\Export\\' . ucfirst($this->exportFormat) . '\\' . ucfirst($exportsite);

if (!class_exists($classname))
{
$customFile = JPATH_ADMINISTRATOR . '/components/com_csvi/helper/file/export/' . strtolower($this->exportFormat) . '/' . ucfirst($exportsite) . '.php';

if (is_file($customFile))
{
require_once $customFile;
}
}

if (!class_exists($classname))
{
throw new \RuntimeException('Export class not found: ' . $classname);
}

$this->exportClass = new $classname($this->template);
}


This restores compatibility with custom XML export scripts that worked in previous versions of CSVI.

Thank you for considering this improvement.

Kind regards
Chris
Accepted Answer Pending Moderation
Hi Chris,

That’s why I’ve created my own solution in a separate file (Merchant.php).
Yes, that is the best way to do that. However I think it would be beneficial if these custom exporters are picked up automatically. I will have a look at that as I proposed earlier.

My custom sitemap code for Virtuemart is working fine.
It is always nice to see what solutions people come up with.

The only thing I have to keep in mind is that the filename must be capitalized exactly the same way as the class name
Correct, that is because of the autoloader.
Kind regards,

RolandD

=========================
If you use our extensions, please post a rating and a review at the Joomla! Extension Directory
  1. 2 weeks ago
  2. RO CSVI
  3. # 1
Accepted Answer Pending Moderation
Hi RolandD
Having your custom exporter, you also probably changed the website dropdown with your own option(s), am I right?

Yes—this issue mainly concerns the pre-made template for Google Merchant. Having to modify the google.php file to suit your needs after every component update is a bit tedious and troublesome. That’s why I’ve created my own solution in a separate file (Merchant.php).


My custom sitemap code for Virtuemart is working fine. The only thing I have to keep in mind is that the filename must be capitalized exactly the same way as the class name—starting with a capital letter. for example, like this: Sitemap.php

My modification completely solves this problem—see :)

https://piekielko.com/sitemap.xml

<?php

namespace Rolandd\Component\Rocsvi\Site\File\Export\Xml;

defined('_JEXEC') or die;

use Rolandd\Component\Rocsvi\Site\Entity\Template as TemplateEntity;
use Rolandd\Component\Rocsvi\Site\File\Export\FileExportInterface;

class Sitemap implements FileExportInterface
{
private TemplateEntity $template;
private bool $open = false;
private array $row = [];

public function __construct(TemplateEntity $template)
{
$this->template = $template;
}

public function headerText($exportFields): string
{
if ($this->open)
{
return '';
}

$this->open = true;

return
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" " .
"xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" " .
"xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">";
}

public function footerText(): string
{
return "</urlset>";
}

public function nodeStart(int $productId = 0, string $type = ''): string
{
$this->row = [];

return "<url>\n";
}

public function nodeEnd(int $productId = 0): string
{
if (empty($this->row['product_url']))
{
return "</url>\n";
}

$productPL = $this->row['product_url'];
$productEN = str_replace('piekielko.com/', 'piekielko.com/en/', $productPL);

$lastmod = $this->row['lastmod'] ?? '';
$priority = $this->row['priority'] ?? '';
$changefreq = $this->row['changefreq'] ?? '';

$xml = '';
$xml .= '<xhtml:link rel="alternate" hreflang="pl" href="' . htmlspecialchars($productPL, ENT_XML1) . "\" />\n";
$xml .= '<xhtml:link rel="alternate" hreflang="en" href="' . htmlspecialchars($productEN, ENT_XML1) . "\" />\n";
$xml .= '<xhtml:link rel="alternate" hreflang="x-default" href="' . htmlspecialchars($productPL, ENT_XML1) . "\" />\n";

$xml .= "</url>\n";

$xml .= "<url>\n";
$xml .= "<loc>" . htmlspecialchars($productEN, ENT_XML1) . "</loc>\n";

if ($lastmod)
{
$xml .= "<lastmod>" . htmlspecialchars($lastmod, ENT_XML1) . "</lastmod>\n";
}

if ($priority)
{
$xml .= "<priority>" . htmlspecialchars($priority, ENT_XML1) . "</priority>\n";
}

if ($changefreq)
{
$xml .= "<changefreq>" . htmlspecialchars($changefreq, ENT_XML1) . "</changefreq>\n";
}

$xml .= '<xhtml:link rel="alternate" hreflang="pl" href="' . htmlspecialchars($productPL, ENT_XML1) . "\" />\n";
$xml .= '<xhtml:link rel="alternate" hreflang="en" href="' . htmlspecialchars($productEN, ENT_XML1) . "\" />\n";
$xml .= '<xhtml:link rel="alternate" hreflang="x-default" href="' . htmlspecialchars($productPL, ENT_XML1) . "\" />\n";
$xml .= "</url>";

return $xml;
}

public function contentText($content, $columnHeader, $fieldName, $cdata = false): string
{
$this->row[$fieldName] = trim((string) $content);
$xml = '';

switch ($fieldName)
{
case 'product_url':
$xml .= '<loc>' . htmlspecialchars(trim((string) $content), ENT_XML1) . "</loc>\n";
break;

case 'file_url':
$parts = explode('^', (string) ($content ?? ''));
$imagePart = $parts[0] ?? '';
$title = trim($parts[1] ?? '');
$caption = trim($parts[2] ?? '');

$caption = strip_tags(html_entity_decode($caption));
if (mb_strlen($caption) > 200)
{
$caption = mb_substr($caption, 0, 200) . '…';
}
$caption = htmlspecialchars($caption, ENT_XML1);

$images = strpos($imagePart, '|') !== false ? explode('|', $imagePart) : [$imagePart];

foreach ($images as $img)
{
$img = trim($img);

if (!$img)
{
continue;
}

if (strpos($img, 'http') !== 0)
{
$img = 'https://piekielko.com/' . ltrim($img, '/');
}

$xml .= "<image:image>\n";
$xml .= "<image:geo_location>Polska</image:geo_location>\n";
$xml .= "<image:loc>" . htmlspecialchars($img, ENT_XML1) . "</image:loc>\n";

if ($title)
{
$xml .= "<image:title>" . htmlspecialchars($title, ENT_XML1) . "</image:title>\n";
}

if ($caption)
{
$xml .= "<image:caption>{$caption}</image:caption>\n";
}

$xml .= "<image:license>https://piekielko.com/licencja</image:license>\n";
$xml .= "</image:image>\n";
}
break;

default:
if (!empty($columnHeader))
{
$xml .= '<' . $columnHeader . '>' . htmlspecialchars(trim((string) $content), ENT_XML1) . '</' . $columnHeader . ">\n";
}
break;
}

return $xml;
}

public function prepareContent(array $contents, array $fields, bool $lastRecord): string
{
return implode('', $contents);
}
}
Overall, I think your component is the best solution for building custom site maps. I created separate indexes for products and articles, and it works perfectly. I don’t need any additional third-party solutions. :)

Kind regards
Chris
  1. 2 weeks ago
  2. RO CSVI
  3. # 2
Accepted Answer Pending Moderation
Hi Chris,

The suggested change is not how I would like to see this implemented. Even with this code, it should fail unless your custom exported is also of the type FileExportInterface. Your custom exporter should also work if you move the file to the folder components/com_csvi/src/File/Export/Xml and change the filename and classname to match the site you have configured.

However I would like to see if we can come up with a more robust solution. By placing custom files within the component you may risk losing them or getting them overwritten and having to restore them. Having your custom exporter, you also probably changed the website dropdown with your own option(s), am I right?

We already have an override system in place for the import/export routines; the same system can be applied to the File exporters. The import/export routines are located in administrator/templates/admintemplate/html/com_csvi/component/model/export/override.php where some of the names in the path are variables. We can add support for administrator/templates/admintemplate/html/com_csvi/component/file/export/override.php. The code would get a list of files it finds there and add them to the website dropdown. For example, let's say a file is called chris.php. In the dropdown you would see Chris as an option and if selected, RO CSVI would look in the override export folder for a Chris.php file.

How does that sound?
Kind regards,

RolandD

=========================
If you use our extensions, please post a rating and a review at the Joomla! Extension Directory
  1. 2 weeks ago
  2. RO CSVI
  3. # 3
  • Page :
  • 1


There are no replies made for this post yet.
Be one of the first to reply to this post!