For modern plugins that use service provider VM creates multiple instances of the plugin. It's causing issues like duplicated HTML in vmcustom plugins, among other things. The problem is coming from this code block https://dev.virtuemart.net/projects/virtuemart/repository/virtuemart/revisions/11044/entry/trunk/virtuemart/administrator/components/com_virtuemart/helpers/vdispatcher.php#L209
Not sure what was the idea here but it will eventually cause even more issues like dependency conflicts. If it needs to return the plugin object, maybe $app->bootPlugin() should be used instead.
VM 4.2.18.11050.
Do you mean line 236?
the part line 209 - 226 uses the joomla plugin container and should not create multiple instances.
Lines 209-226. I can see the plugin being instantiated at least twice and listeners called even more times. First time it is constructed by Joomla application when plugins are imported using PluginHelper::importPlugin(). This is correct. Then this code registers the plugin and its dependencies again but now with global DIC and returns a new instance. What issue was this code trying solve?
Maybe this will change something?
<?php
use Joomla\Event\Dispatcher as EventDispatcher;
class vDispatcher {
static $dispatcher = null;
static function trigger ($name, $params) {
if (self::$dispatcher === null) {
if (JVM_VERSION < 4) {
self::$dispatcher = JEventDispatcher::getInstance();
} else {
self::$dispatcher = JFactory::getApplication();
}
}
if (JVM_VERSION < 4) {
return self::$dispatcher->trigger($name, $params);
} else {
return self::$dispatcher->triggerEvent($name, $params);
}
}
static function setDispatcher() {
// Implementation for setting a custom dispatcher, if needed.
}
/**
* Triggers Joomla 5 plugins directly, backward compatible for J3, J4.
* @param $plg
* @param $trigger
* @param $args
* @param $ret
* @return array|mixed|void|null
* @throws ReflectionException
*/
static function triggerJ5($plg, $trigger, $args, &$ret) {
if (is_object($plg)) {
$className = get_class($plg);
if (method_exists($className, 'getSubscribedEvents')) {
$evts = $className::getSubscribedEvents();
if (!empty($evts[$trigger])) {
$fn = $evts[$trigger];
$reflector = new ReflectionClass($plg);
$parameters = $reflector->getMethod($fn)->getParameters();
$EventClass = '\Joomla\Event\Event';
foreach ($parameters as $param) {
$cTest = $param->getType();
if ($cTest instanceof ReflectionNamedType && strpos($cTest->getName(), 'Event') !== false) {
$EventClass = $cTest->getName();
break;
}
}
$event = new $EventClass($trigger, $args);
if (is_array($args)) {
foreach ($args as $named => $value) {
$event->setArgument($named, $value);
}
}
if (is_array($ret)) {
$ret[] = call_user_func_array([$plg, $fn], [$event]);
} else {
return call_user_func_array([$plg, $fn], [$event]);
}
}
} elseif (method_exists($plg, $trigger)) {
if (is_array($ret)) {
$ret[] = call_user_func_array([$plg, $trigger], $args);
} else {
return call_user_func_array([$plg, $trigger], $args);
}
}
return $ret;
}
return null;
}
static public function directTrigger($type, $element, $trigger, $args, $enabled = true) {
JPluginHelper::importPlugin($type);
$app = JFactory::getApplication();
$plg = $app->bootPlugin($element, $type);
if ($plg) {
$ret = false;
return self::triggerJ5($plg, $trigger, $args, $ret);
}
return null;
}
static function importVMPlugins($ptype) {
static $types = ['vmextended' => true, 'vmuserfield' => true, 'vmcalculation' => true, 'vmcustom' => true, 'vmcoupon' => true, 'vmshipment' => true, 'vmpayment' => true];
if (!isset($types[$ptype])) return;
foreach ($types as $type => $v) {
JPluginHelper::importPlugin($type);
unset($types[$type]);
if ($type == $ptype) {
break;
}
}
}
static public function createPlugin($type, $element, $enabled = true) {
if (empty($type) || empty($element)) {
vmdebug('Developer error, class vmpluglin function createPlugin: empty type or element');
}
$plugin = self::getPlugin($type, $element, $enabled);
if (!isset($plugin->type) || !isset($plugin->name)) {
vmdebug('VmPlugin function createPlugin, plugin not found or unpublished', $type, $element);
return false;
}
$app = JFactory::getApplication();
$pluginObject = $app->bootPlugin($element, $type);
if ($pluginObject) {
return $pluginObject;
}
$className = 'Plg' . str_replace('-', '', $type) . $element;
if (class_exists($className)) {
if (JVM_VERSION < 4) {
if (self::$dispatcher === null) {
self::$dispatcher = JEventDispatcher::getInstance();
}
return new $className(self::$dispatcher, (array) $plugin);
} else {
$dummy = new EventDispatcher();
return new $className($dummy, (array) $plugin);
}
} else {
vmdebug('VmPlugin function createPlugin, class does not exist', $className, $type, $element);
return false;
}
}
static function getPlugin($type, $element, $enabled = true) {
$db = JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(['extension_id', 'folder', 'element', 'params', 'state']))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote($type))
->where($db->quoteName('element') . ' = ' . $db->quote($element));
if ($enabled) {
$query->where($db->quoteName('enabled') . ' = 1')
->where($db->quoteName('state') . ' = 0');
}
$db->setQuery($query);
try {
return $db->loadObject();
} catch (Exception $e) {
vmError('Could not load Plugin ' . $type . ' ' . $element . ': ' . $e->getMessage(), 'Plugin Load Error');
return null;
}
}
}