Moko Consulting

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

  1. Create plugin directory plg_mokosuitecross_{servicename}/
  2. Implement MokoSuiteCrossServiceInterface
  3. Register in services/provider.php
  4. Add language strings for name, description, config labels
  5. Add to the package manifest pkg_mokosuitecross.xml
  6. Test: verify service appears in admin, config saves, posts deliver