Open-source software for Joomla, Gitea, and web platforms. Home of MokoSuite, MokoGitea, and MokoCLI.
Tennessee
architecture/joomla-plugin-groups.-
Joomla Plugin Groups
Custom plugin groups and service interfaces in MokoSuite.
Custom Plugin Groups
MokoSuite uses custom Joomla plugin groups to create extensible service systems. The primary example is mokosuitecross — the cross-posting service plugin group.
Service Interface Pattern
Each custom plugin group defines a service interface that all plugins in that group must implement:
namespace MokoConsulting\Component\MokoSuiteCross\Administrator\Service;
interface MokoSuiteCrossServiceInterface
{
public function getName(): string;
public function getDisplayName(): string;
public function getIcon(): string;
public function isConfigured(object $service): bool;
public function post(object $service, object $article, object $template): PostResult;
public function validateConfig(array $params): array;
public function getConfigForm(): string;
}
Plugin Discovery
The component discovers available services by importing the plugin group:
use Joomla\CMS\Plugin\PluginHelper;
// Import all plugins in the mokosuitecross group
PluginHelper::importPlugin('mokosuitecross');
// Dispatch event to collect available services
$event = new CollectServicesEvent('onMokoSuiteCrossCollectServices');
$app->getDispatcher()->dispatch($event->getName(), $event);
$services = $event->getServices();
Service Plugin Structure
Each service plugin (e.g., Discord, Telegram, Slack):
plg_mokosuitecross_discord/
├── discord.xml # Plugin manifest (group="mokosuitecross")
├── discord.php # Legacy entry point
├── services/provider.php # DI registration
├── src/Extension/
│ └── DiscordService.php # Implements MokoSuiteCrossServiceInterface
└── language/en-GB/
├── plg_mokosuitecross_discord.ini
└── plg_mokosuitecross_discord.sys.ini
Event Naming
Custom events follow the pattern onMokoSuite{Component}{Action}:
onMokoSuiteCrossCollectServices # Collect available service plugins
onMokoSuiteCrossBeforePost # Before sending a cross-post
onMokoSuiteCrossAfterPost # After cross-post completes
onMokoSuiteCrossPostFailed # On cross-post failure
onMokoSuiteCalendarEventCreated # Calendar event published
onMokoSuiteGalleryImageUploaded # Gallery image added
Default vs Custom Bot Pattern
Services that support universal bots (Telegram, Discord, Slack, Facebook) use a two-mode system:
- Default mode: API keys stored in component-level encrypted params. Hidden from admin UI. Uses MokoConsulting's universal bot accounts.
- Custom mode: Admin provides their own API keys/tokens. Full control over the bot identity.
public function post(object $service, object $article, object $template): PostResult
{
$token = match ($service->mode) {
'default' => $this->getDefaultToken(), // Encrypted in component params
'custom' => $service->params->get('api_token'),
default => throw new \InvalidArgumentException('Invalid service mode'),
};
return $this->sendToDiscord($token, $this->formatMessage($article, $template));
}
Adding a New Service Plugin
- Create plugin directory
plg_mokosuitecross_{servicename}/ - Implement
MokoSuiteCrossServiceInterface - Register in
services/provider.php - Add language strings for name, description, config labels
- Add to the package manifest
pkg_mokosuitecross.xml - Test: verify service appears in admin, config saves, posts deliver
Pages