Magento: Eigenes TinyMCE Plugin aktivieren

Magento bringt mit TinyMCE von Haus aus einen Rich Text Editor mit, der im Backend für Attribute, CMS-Seiten u.a. verwendet wird. Die Integration erfolgt über diverse Adapterklassen und Skripte, die quer über den Magento-Core verteilt sind, darunter das Formular-Element Varien_Data_Form_Element_Editor, ein paar Models im CMS-Modul und diverse Blocks und Helpers im Adminhtml-Modul sowie eigene JavaScripts in js/mage/adminhtml/wysiwyg.

Ich habe nun für meine letzte Extension ein eigenes TinyMCE Plugin entwickelt und stand vor dem Problem, wie ich es aktivieren könnte. Ein einfaches einbinden des Plugin-Skripts schlug fehl, da der TinyMCE Editor vom Magento-Adapter dynamisch nachgeladen wird. Leider habe ich dafür auch nach langem Suchen keine brauchbare Dokumentation gefunden und da Replacements und Hacks von Core-Dateien nicht in Frage kommen blieb mir letztlich nichts übrig als die oben genannten Klassen und Skripte zu analysieren. Als Resultat gibt es hier nun eine Anleitung zum einbinden von eigenen Plugins:

Wysiwyg Konfiguration

Die Konfiguration für den Editor wird zentral vom ‘cms/wysiwyg_config’ Singleton geladen (auch außerhalb des CMS-Moduls!). Dort ist leider sehr viel hart kodiert und es gibt z.B. keine Konfigurationsquelle für Plugins. Dafür wird dort allerdings ein Event getriggert so dass man die Konfiguration mittels Observer erweitern kann. Das Event dazu heißt cms_wysiwyg_config_prepare und hat als Parameter ein Varien_Object namens config.

Ein Observer dafür könnte so aussehen:

class SSE_Example_Model_Observer
{
	public function onCmsWysiwygConfigPrepare(Varien_Event_Observer $observer)
	{
		$config = $observer->getEvent()->getConfig();
		$this->addPlugin($config, $this->getContentBlockPluginConfig());
	}

	private function addPlugin(Varien_Object $config, $plugin)
	{
		$config->setData('plugins', array_merge((array) $config['plugins'], array($plugin)));
	}
	
	private function getContentBlockPluginConfig()
	{
		return array(
			'options' => array(
			),
			'name' => 'example',
			'src' => Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_JS) . 'sse/example/editor_plugin.js',
		);
	}

In der Methode getContentBlockPluginConfig() wird hier die Konfiguration für ein neues Plugin angelegt:

options
Das Options-Array wird einer zusätzlichen Plugin-Schnittstelle von Magento verarbeitet. Hierüber lassen sich Plugins wie der “Insert Image”-Button realisieren, die außerhalb von TinyMCE funktionieren. Dies soll uns an dieser Stelle aber nicht interessieren. 1
name
Der Name des Plugins, so wie er im Plugin-Skript als Parameter für tinymce.PluginManager.add() angegeben wurde
src
Der Pfad zum Plugin-Skript. Dieser kann relativ zum Ort von TinyMCE (tinymce.baseUrl) sein, ich empfehle allerdings der Übersichtlichkeit zuliebe wie oben einen absoluten Pfad zu nutzen und das Plugin in einem eigenen Verzeichnis abzulegen. Da das Plugin von Magento explizit über diesen Pfad geladen wird, sind die Namenskonventionen von TinyMCE hier nicht von Belang, für andere Ressourcen gelten sie weiterhin (Die Sprachdatei wäre hier z.B. in js/sse/example/langs/en.js)
name und src werden clientseitig von der Setup-Klasse in js\mage\adminhtml\wysiwyg\tiny_mce\setup.js verarbeitet, die für das Laden des Editors zuständig ist.

Einfügen des Buttons/Steuerelements ins TinyMCE Menü

Üblicherweise muss man neue Steuerelemente über die theme_advanced_buttons{n} Parameter beim Initialisieren von TinyMCE im Menü einfügen. Da diese Initialisierung aber wie gesagt komplett von Magento übernommen wird, gibt es hier einfach die Konvention dass das Element genauso heißt wie das Plugin (Magento fügt den Namen an vorderster Position in der ersten Reihe ein):

	// ...
	tinymce.create('tinymce.plugins.Example', {
		init : function(editor, url) {
			// ...
			editor.addButton('example', {
				// ...
			});
		},
	// ...
	}

Aktivierung nur für bestimmte Formulare

Dadurch dass die Konfiguration zentral über ein Singleton geladen wird, ist sie leider für jede Editor-Instanz die selbe. In meinem Fall brauchte ich das Plugin jedoch nur für die Produktbeschreibung und wollte es entsprechend nicht in anderen Formularen aktiviert haben. Das ließ sich letztendlich am besten clientseitig bewerkstelligen, indem bei der Plugin-Initialisierung die ID des Editors abgefragt wird (Die ID lässt sich am besten über den DOM-Inspektor von Firebug oder einem ähnlichen Tool herausfinden):

	// ...
	tinymce.create('tinymce.plugins.Example', {
		init : function(editor, url) {
			if (editor.id == "description_editor") {
				this._init(ed, url);
			}
		},
		_init : function(editor, url) {
			// real initialization here
		},
	// ...
	}

Fazit

Wie so oft bei Magento: Eigentlich ganz einfach, wenn man nur weiß wie es geht. Ich hoffe, dem ein oder anderen hiermit das stundenlange Wühlen im Core zu ersparen was ich dafür auf mich nehmen musste 🙂

Notes:

  1. Zum Vertiefen empfehle ich den Quelltext von Varien_Data_Form_Element_Editor::_getPluginButtonsHtml() als Einstiegspunkt.