CCM19- Plugins
CCM19 kann durch Plugins um diverse Funktionalitäten erweitert werden.
Namensgebung
Jedes Plugin hat einen eindeutigen Namen, der aus einem Hersteller
Hersteller
Für das Plugin
Als Pluginnamecomposer.json wird der Name als hersteller/pluginname in snake_case geschrieben.
Vom CCM19Ccm19.
Ein Plugin von CCM19 für "erweiterten Iframeccm19/extended_iframe bzw. Ccm19ExtendedIframe heißen, ein Plugin des Herstellers "Example Vendor" z.B. examplevendor/really_special_plugin bzw. ExamplevendorReallySpecialPlugin.
Grundstruktur
Ein Plugin wird als Ordner im Verzeichnis plugins angelegt und enthält folgende Dateien und Ordner:
composer.json – Metadaten über das Plugin
preview.{pngjpegsvgwebpgif} – Vorschaubild
src/ – PHPsrc/Controller/ – Controllerconfig/ – Symfonytemplates/ – Twigtranslations/ – Übersetzungsdateien (optional)
Composer.json
Die composer.json sieht folgendermaßen aus:
{
"name": "examplevendor/really_special_plugin",
"description": "This plugin does something really special that should be described here.",
"version": "1.0",
"type": "ccm19<nt>-</nt>plugin",
"license": "proprietary",
"authors": [
{
"name": "ExampleVendor GmbH",
"role": "Manufacturer"
}
],
"extra": {
"copyright": "(c) Copyright...",
"activatePerDomain": true, // Plugin in der Agency<nt>-</nt>Version für einzelne Domains (de<nt>-</nt>)aktivierbar
"label": {
"en": "Really special plugin", // Anzeigename in der Pluginverwaltung
"de": "Wirklich besonderes Plugin",
"fr": "Plugin vraiment spécial"
},
"description": {
"de": "Dieses Plugin macht etwas ganz Besonderes, das hier beschrieben werden sollte.",
"fr": "Ce plugin fait quelque chose de vraiment spécial qui devrait être décrit ici."
},
"manufacturerLink": {
"de": "https://examplevendor.example",
"en": "https://examplevendor.example"
},
"supportLink": {
"de": "https://examplevendor.example/support_de/",
"en": "https://examplevendor.example/support_en/"
},
"preview": "pfad/zu/preview.png" // OPTIONAL: Wenn das Vorschaubild nicht am o.g. Standard<nt>-</nt>Ort liegt
}
}
Wenn übersetzbare Angaben in einer Sprache fehlen, wird als Fallback die englische Angabe verwendet.
Plugins können auch eigene Abhängigkeiten mittels Composer mitliefern. Dafür müssen die composer.lock und das vendor/
Templates
Templates aus dem templates/@plugin:PluginVerzeichnis/ eingebunden.
Beim obrigen Beispieltemplates/index.html.twig also @plugin:ExamplevendorReallySpecialPlugin/index.html.twig.
Templates des Hauptsystems verändern
Um Templates anderer Menüpunkte als der eigenen zu ergänzen oder zu bearbeiten, können die Events App\Event\TemplateResolveEvent und App\Event\TemplateRenderEvent verwendet werden.
Mit App\Event\TemplateResolveEvent kann jede Template{% include(…) %} eingebunden werden) mit einem eigenen Template erweitert oder ersetzt werden. Dafür wird in der Regel $event<nt>-</nt>>extendTemplate('@plugin:ExamplevendorReallySpecialPlugin/….twig') verwendet, um mit Twig
Mit App\Event\TemplateRenderEvent können Template$event<nt>-</nt>>get() und $event<nt>-</nt>>set() gelesen bzw. gesetzt werden und auch Menüpunkt
class BackendTemplateListener implements EventSubscriberInterface
{
private $pluginState;
public function __construct(PluginState $pluginState)
{
$this<nt>-</nt>>pluginState = $pluginState;
}
/**
* @return array
*/
public static function getSubscribedEvents()
{
return [
TemplateRenderEvent::nameForView('domain/index.html.twig') => ['onRenderDomainIndex', 100],
];
}
/**
* @return void
*/
public function onRenderDomainIndex(TemplateRenderEvent $event)
{
if (!$this<nt>-</nt>>pluginState<nt>-</nt>>isActiveForCurrentDomain()) {
return;
}
$event<nt>-</nt>>extendTemplate('@plugin:ExamplevendorReallySpecialPlugin/domain_index.html.twig');
$event<nt>-</nt>>set('someVariable', ...);
}
}
Routen und Menüpunkte
Routen und Menüpunkte können über die @Route und @Menusrc/Controller/ erzeugt werden. Die Abhängigkeiten für die Annotationen werden bereits vom Hauptsystem importiert. Im Plugin müssen Sie die Annotationen also nur mit use Symfony\Component\Routing\Annotation\Route; und use App\Component\Menu\Annotation\Menu; importieren.
Routenplugin_herstellername_pluginname_ beginnen, damit die Rechteverwaltung greift und Plugincomposer.json zu schreiben (nur mit Unterstrich statt Schrägstrich).
@Menu- Annotation
Die Menu
Sie kann folgende Parameter haben:
name: string Der Name der für die Anzeige des Menüpunkts verwendet wird. Dieser wird mit dem normalen Übersetzungssystem übersetzt.
group: string Menügruppe in dem der Menüpunkt angezeigt werden soll (optional, standardmäßig unter "Plugins")
icon: string Iconglyphicon<nt>-</nt>… und fa<nt>-</nt>… unterstützt.
order: integer Orderroute: string Route zu der der Menüpunkt führen soll (Standard: wird aus der @Routeroute_group: string Präfix aller Routen, die zu dem Menüpunkt gehören (Standard: wird aus der @Routeroute). Das wird für die Anzeige, ob der Menüpunkt noch aktiv ist, verwendet.
access={…}: string[] Wer Zugriff auf den Menüpunkt haben soll. Mögliche Werte sind "admin", "user" und "subuser". (Standard: wird aus der Superklasse der Controllernavigation: string In welcher Navigation der Menüpunkt angezeigt werden soll. Mögliche Werte sind main, domain und meta. Dabei entspricht main der Navigation die der Benutzer beim Einloggen angezeigt bekommt. Also entweder domain oder meta, je nach Edition und Benutzertyp. (Standard: domain bei DomainDependantControllern, ansonsten main).
editions={…}: string[] Editionen, in denen der Menüpunkt angezeigt werden soll. (Standard: {"extended"} für HostingController, ansonsten alle Editionen).
envs={…}: string[] Environments in denen der Menüpunkt angezeigt werden soll. Z.B. {"dev"} für nur im Entwicklermodus. (Standard: alle)
Voreinstellung der Zugriffsrechte und des Navigationsbereichs
Wenn nicht explizit mit access und navigation gesetzt, hängt es von der Klasse ab, von der der Controller ableitet, wer wo den Menüpunkt zu sehen bekommt.
DomainDependantController: In der DomainAdminController: In der Hauptnavigation. Nur sichtbar für Admins in der AgencyHostingController: In der Hauptnavigation. Nur sichtbar für Admins in der AgencyAbstractController: In der Hauptnavigation . Nur sichtbar für Benutzer die Domainzugriff haben können (keine AgencyUniversalController: In der Hauptnavigation. Für alle sichtbar.
UnrestrictedController: In der Hauptnavigation. Für alle sichtbar.
Was die Hauptnavigation ist, unterscheidet sich je nach Edition und Benutzertyp. Es ist immer die Navigation, die nach dem Einloggen erscheint. Also die Domain
Beispiel:
namespace Plugins\ExamplevendorReallySpecialPlugin;
use App\Component\Menu\Annotation\Menu;
use App\Controller\DomainDependantController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/domains/{_domainId}/plugins/examplevendor/really_special/", name="plugin_examplevendor_really_special_plugin_")
*/
class MainController extends DomainDependantController
{
/**
* @Menu("Really special", icon="fa<nt>-</nt>plug")
* @Route("", name="index", methods={"HEAD", "GET"})
*/
public function index(...): Response
{
...
}
/**
* @Route("", name="save")
*/
public function indexSave(...): Response
{
...
}
}
Das Symfony
App\Model\PluginState liefert das Pluginstring $PLUGINPATH liefert den vollständigen Pfad zum Plugin
Menüpunkte im DomainDomainDependantController ableitet) werden automatisch ein
Menüpunkte die nicht domainabhängig sind, werden in der Agency
Für speziellere Anforderungen ist es auch möglich Menüeinträge über einen EventApp\Event\MenuGenerationEvent manuell zu setzen.
Auf Routen anderer Controller reagieren
Mit dem Event ccm19.controller.request.<route_name> also z.B. ccm19.controller.request.app_dashboard kann auf eine Route, bevor der jeweilige Controller ausgeführt wird, reagiert werden.
Das EventSymfony\Component\HttpKernel\Event\ControllerEvent. Mit der Methode setController() kann der Request auch auf einen anderen Controller umgeleitet werden.
Das Event ccm19.controller.response.<route_name> ermöglicht dagegen Code auszuführen, nachdem der Controller durchgelaufen ist.
Beide Events werden nicht bei unautorisiertem Zugriff ausgelöst, also z.B. wenn eine Route, die einen angemeldeten Benutzer erfordert, ohne gültige Session aufgerufen wird.
Wichtig: Bei allen Event$pluginState<nt>-</nt>>isActiveForCurrentDomain() bzw. je nach Kontext $pluginState<nt>-</nt>>isAllowedForCurrentUser() zu überprüfen, ob eine Aktion des Plugins im aktuellen Kontext erwünscht ist.