I have a System Plugin to filter Joomla 4 Category Blog Articles by Custom Fields. The Plugin basically overrides the core content ArticlesModel by loading it in the onAfterRoute Event like this:
require_once __DIR__ . '/models/com_content/ArticlesModel.php';
This somehow works but I know it is not the way to go when using the Joomla 4 service provider.
- Is it still possible to override core Model class from within a Plugin in Joomla 4?
- How should this be done in Joomla 4 (best practice)?
Edit:
Following @sharky's comment on this post https://joomla.stackexchange.com/a/32099/12652 which describes the overriding of the Admin Model, I tried to create a Plugin to override a Site Model. As the Plugin should also change the generated HTML, there is an onAfterRender. The onAfterExtensionBoot and onAfterRender are loaded by the getSubscribedEvents - which seems to work:
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\MVC\Factory\MVCFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Component\Content\Site\Model\ArticlesModel;
use Joomla\DI\Container;
use Joomla\Event\EventInterface;
use MyCompany\Plugin\System\MyPlugin\Model\MyCustomSiteArticlesModel;
final class PlgSystemOverride extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onAfterExtensionBoot' => 'onAfterExtensionBoot',
'onAfterRender' => ['onAfterRender', Priority::LOW],
];
}
public function onAfterExtensionBoot(EventInterface $event)
{
// Test that a component is being booted.
if ($event->getArgument('type') !== ComponentInterface::class) {
return;
}
// Test that this is com_content component.
if ($event->getArgument('extensionName') !== 'content') {
return;
}
// Get the service container.
$container = $event->getArgument('container');
if (!($container instanceof Container)) {
return;
}
// Check that MVC factory is used and can be overridden.
if (!$container->has(MVCFactoryInterface::class) || $container->isProtected(MVCFactoryInterface::class)) {
return;
}
// Register the custom MVC factory. Here an anonymous class is used but you can use a concrete class.
$container->set(
MVCFactoryInterface::class,
static fn () => new class('Joomla\\Component\\Content') extends MVCFactory
{
protected function getClassName(string $suffix, string $prefix)
{
$class = parent::getClassName($suffix, $prefix);
// How to override the site article list model with a custom class?
if ($class === ArticlesModel::class) {
return 'MyCustomSiteArticlesModel';
}
return $class;
}
}
);
}
public function onAfterRoute()
{
// ... do onAfterRoute stuff
}
}
As the code shows, I created a MyCustomSiteArticlesModel class and put it in the src/Model folder of the Plugin:
use Joomla\Component\Content\Site\Model\ArticlesModel;
class CustomfieldsFilterArticlesModel extends ArticlesModel
{
protected function getListQuery()
{
// ... generate the query
}
}
In the getClassName() the ArticlesModel::class
seems to return the Joomla\Component\Content\Administrator\Model\ArticlesModel class and not the Joomla\Component\Content\Site\Model\ArticlesModel. Therefore the classname will not be returned.
Questions:
- How does the getClassName() load the MyCustomSiteArticlesModel?
- Where should the overridden MyCustomSiteArticlesModel class be located?
- Is this the correct approach to make this work on Joomla Article Blog posts?
Thank you for a few explanations.