Plugins¶
Plugins are one of the key component of Tuleap. The code is structured around:
Core (everything under
src/)Plugins (located under
plugins/pluginname)
Plugins can provide a new service (like AgileDashboard or Git) or underlying plumbing with almost no dedicated UI (like LDAP). A plugin can depend on another (Cardwall depends on Tracker).
Plugins rely on events to change behaviour of a given part of the code (either Core or another Plugin).
Unless a very good reason, all new significant development MUST be done within a plugin. It’s always true for new services.
Structure¶
A plugin folder is structured like
db # Plugin tables and start data creation, uninstall & upgrade buckets etc # Configuration include # Plugin code site-content # Localisation strings template # mustache files tests # Unit & rest tests www # Entry point for web pages
As a good start, you can copy plugins/template and rename all template stuff
to your plugin name.
There are a handful of files already there, the most important are:
db/install.sqldefinition of tables and initial datadb/uninstall.sqlclean-up the base when the plugin is removed (purge)include/templatePlugin.class.phpthe entry point that define the plugin behaviourinclude/Template/Plugin/PluginDescription.phpdescribe the plugininclude/Template/Plugin/PluginInfo.phpplugin’s metadata (optionally access to the configuration values)
Note: if you copy the default plugin for “mercurial” plugin for instance, you will end up with:
mercurial/include/mercurialPlugin.class.phpmercurial/include/Mercurial/Plugin/PluginDescription.phpmercurial/include/Mercurial/Plugin/PluginInfo.php
pluginnamePlugin.class.php¶
This is the central place for plugin interaction with the rest of the application.
Let’s take a look at what our basic Mercurial plugin would look like
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; class mercurialPlugin extends Plugin { public function __construct($id) { parent::__construct($id); $this->setScope(self::SCOPE_PROJECT); } public function getPluginInfo() : Tuleap\Mercurial\Plugin\PluginInfo { if (! $this->pluginInfo) { $this->pluginInfo = new Tuleap\Mercurial\Plugin\PluginInfo($this); } return $this->pluginInfo; } public function getServiceShortname() : string { return 'plugin_mercurial'; } public function process() : void { $renderer = TemplateRendererFactory::build()->getRenderer(MERCURIAL_BASE_DIR.'/template'); $renderer->renderToPage('index', array('world' => 'World')); } }
Line by line:
L5: code must not explicitly require PHP class definitions, everything should pass through the autoloader (automatically generated by Composer)
L11: define the scope to either
SCOPE_SYSTEM(for system wide features like ldap, openidconnect, etc) orSCOPE_PROJECTfor plugins that is relevant in the context of a project (here mercurial would be a service of the project)L14 & L21: boilerplate to manage plugin description
L25: example of a basic controller (should only be done with very basic plugins).
Why process in the plugin class?
It aims to encapsulate the controller with plugin information to only execute when
plugin is activated. Example with the corresponding www/index.php:
$plugin_manager = PluginManager::instance(); $plugin = $plugin_manager->getPluginByName('mercurial'); if ($plugin && $plugin_manager->isPluginAvailable($plugin)) { $plugin->process(); } else { $GLOBALS['Response']->redirect('/'); }
Bring a new service to life¶
At this stage the plugin doesn’t do anything useful but it will display “Hello World” when
the plugin is activated and someone reach the URL https://tuleap.example.com/plugins/mercurial
TBC…