Plugin development tutorialยถ
This tutorial explores the basic concepts of GLPI while building a simple plugin. It has been written to be explained during a training session, but most of this document could be read and used by people wanting to write plugins. Donโt hesitate to suggest enhancements or contribute at this address: https://github.com/glpi-project/docdev
Warning
โ ๏ธ Several prerequisites are required in order to follow this tutorial:
A base knowledge of GLPI usage
A correct level in web development:
PHP
HTML
CSS
SQL
JavaScript (JQuery)
Being familiar with command line usage
๐ In this first part, we will create a plugin named โMy pluginโ (key: myplugin
).
We will cover project startup as well as the setup of base elements.
Prerequisitesยถ
Here are all the things you need to start your GLPI plugin project:
a functional web server,
latest GLPI stable release installed locally
git version management software.
You may also need:
Start your projectยถ
Warning
โ ๏ธ If you have production data in your GLPI instance, make sure you disable all notifications before beginning the development. This will prevent sending of tests messages to users present in the imported data.
First of all, a few resources:
Empty plugin and its documentation. This plugin is a kind of skeleton for quick starting a brand new plugin.
Example plugin. It aims to do an exhaustive usage of GLPI internal API for plugins.
My new pluginยถ
Clone empty
plugin repository in you GLPI plugins
directory:
cd /path/to/glpi/plugins
git clone https://github.com/pluginsGLPI/empty.git
You can use the plugin.sh
script in the empty
directory to create your new plugin. You must pass it the name of your plugin and the first version number. In our example:
cd empty
chmod +x plugin.sh
./plugin.sh myplugin 0.0.1
Note
My-Plugin
will therefore create the MyPlugin
directory.Keep it simple!
When running the command, a new directory myplugin
will be created at the same level as the empty
directory (both in /path/to/glpi/plugin
directory) as well as files and methods associated with an empty plugin skeleton.
Note
โน๏ธ If you cloned the empty
project outside your GLPI instance, you can define a destination directory for your new plugin:
./plugin.sh myplugin 0.0.1 /path/to/another/glpi/plugins/
Retrieving Composer dependenciesยถ
In a terminal, run the following command:
cd /path/to/glpi/plugins/myplugin
composer install
Minimal plugin structureยถ
๐ glpi ๐ plugins ๐ myplugin ๐ ajax ๐ front ๐ src ๐ locales ๐ tools ๐ vendor ๐ composer.json ๐ hook.php ๐ LICENSE ๐ myplugin.xml ๐ myplugin.png ๐ Readme.md ๐ setup.php
๐ front
directory is used to store our object actions (create, read, update, delete).๐ ajax
directory is used for ajax calls.Your plugin own classes will be stored in the
๐ src
directory.gettext translations will be stored in the
๐ locales
directory.An optional
๐ templates
directory would contain your plugin Twig template files.๐ tools
directory provides some optional scripts from the empty plugin for development and maintenance of your plugin. It is now more common to get those scripts from๐ vendor
and๐ node_modules
directories.๐ vendor
directory contains:PHP libraries for your plugin,
helpful tools provided by
empty
model.
๐ node_modules
directory contains JavaScript libraries for your plugin.๐ composer.json
files describes PHP dependencies for your project.๐ package.json
file describes JavaScript dependencies for your project.๐ myplugin.xml
file contains data description for publishing your plugin.๐ myplugin.png
image is often included in previous XML file as a representation for GLPI plugins catalog๐ setup.php
file is meant to instantiate your plugin.๐ hook.php
file contains your plugin basic functions (install/uninstall, hooks, etc).
minimal setup.phpยถ
After running plugin.sh
script, there must be a ๐ setup.php
file in your ๐ myplugin
directory.
It contains the following code:
๐ setup.php
1<?php
2
3define('PLUGIN_MYPLUGIN_VERSION', '0.0.1');
An optional constant declaration for your plugin version number used later in the plugin_version_myplugin
function.
๐ setup.php
3<?php
4
5function plugin_init_myplugin() {
6 global $PLUGIN_HOOKS;
7
8 $PLUGIN_HOOKS['csrf_compliant']['myplugin'] = true;
9}
This instanciation function is important, we will declare later here Hooks on GLPI internal API.
Itโs systematically called on all GLPI pages except if the _check_prerequisites
fails (see below).
We declare here that our plugin forms are CSRF compliant even if for now our plugin does not contain any form.
๐ setup.php
9<?php
10
11// Minimal GLPI version, inclusive
12define("PLUGIN_MYPLUGIN_MIN_GLPI_VERSION", "10.0.0");
13
14// Maximum GLPI version, exclusive
15define("PLUGIN_MYPLUGIN_MAX_GLPI_VERSION", "10.0.99");
16
17function plugin_version_myplugin()
18{
19 return [
20 'name' => 'MonNouveauPlugin',
21 'version' => PLUGIN_MYPLUGIN_VERSION,
22 'author' => '<a href="http://www.teclib.com">Teclib\'</a>',
23 'license' => 'MIT',
24 'homepage' => '',
25 'requirements' => [
26 'glpi' => [
27 'min' => PLUGIN_MYPLUGIN_MIN_GLPI_VERSION,
28 'max' => PLUGIN_MYPLUGIN_MAX_GLPI_VERSION,
29 ]
30 ];
31}
This function specifies data that will be displayed in the Setup > Plugins
menu of GLPI as well as some minimal constraints.
We reuse the constant PLUGIN_MYPLUGIN_VERSION
declared above.
You can of course change data according to your needs.
Note
โน๏ธ Choosing a license
The choice of a license is important and has many consequences on the future use of your developments. Depending on your preferences, you can choose a more permissive or restrictive orientation. Websites that can be of help exists, like https://choosealicense.com/.
In our example, MIT license has been choose. Itโs a very popular choice which gives user enough liberty using your work. It just asks to keep the notice (license text) and respect the copyright. You canโt be dispossessed of your work, paternity must be kept.
๐ setup.php
32<?php
33
34function plugin_myplugin_check_config($verbose = false)
35{
36 if (true) { // Your configuration check
37 return true;
38 }
39
40 if ($verbose) {
41 _e('Installed / not configured', 'myplugin');
42 }
43
44 return false;
45}
This function is systematically called on all GLPI pages.
It allows to automatically deactivate plugin if defined criteria are not or no longer met (returning false
).
minimal hook.phpยถ
This file must contains installation and uninstallation functions:
๐ hook.php
1<?php
2
3function plugin_myplugin_install()
4{
5 return true;
6}
7
8function plugin_myplugin_uninstall()
9{
10 return true;
11}
When all steps are OK, we must return true
.
We will populate these functions later while creating/removing database tables.
Install your pluginยถ

Following those first steps, you should be able to install and activate your plugin from Setup > Plugins
menu.
Creating an objectยถ
CommonDBTM usage and classes creationยถ
This super class adds the ability to manage items in the database.
Your working classes (in the src
directory) can inherit from it and are called โitemtypeโ by convention.
Note
โน๏ธ Conventions:
Classes must respect PSR-12 naming conventions. We maintain a guide on coding standards
SQL tables related to your classes must respect that naming convention:
glpi_plugin_pluginkey_names
a global
glpi_
prefixa prefix for plugins
plugin_
plugin key
myplugin_
itemtype name in plural form
superassets
Tables columns must also follow some conventions:
there must be an
auto-incremented primary
field namedid
foreign keys names use that referenced table name without the global
glpi_
prefix and with and_id
suffix. example:plugin_myotherclasses_id
referencesglpi_plugin_myotherclasses
table
Warning! GLPI does not use database foreign keys constraints. Therefore you must not use
FOREIGN
orCONSTRAINT
keys.Some extra advice:
always end your files with an extra carriage return
never use the closing PHP tag
?>
- see https://www.php.net/manual/en/language.basic-syntax.instruction-separation.php
Main reason for that is to avoid concatenation errors when using require/include functions, and prevent unexpected outputs.
We will create our first class in ๐ Superasset.php
file in our plugin ๐src
directory:
๐glpi ๐plugins ๐myplugin ... ๐src ๐ Superasset.php ...
We declare a few parts:
๐ src/Superasset.php
1<?php
2namespace GlpiPlugin\Myplugin;
3
4use CommonDBTM;
5
6class Superasset extends CommonDBTM
7{
8 // right management, we'll change this later
9 static $rightname = 'computer';
10
11 /**
12 * Name of the itemtype
13 */
14 static function getTypeName($nb=0)
15 {
16 return _n('Super-asset', 'Super-assets', $nb);
17 }
18}
Warning
โ ๏ธ namespace
must be CamelCase
Note
โน๏ธ Here are most common CommonDBTM inherited methods:
add(array $input)
: Add an new object in database table.
input
parameter contains table fields.
If add goes well, the object will be populated with provided data.
It returns the id of the new added line, or false
if there were an error.
1 <?php
2
3 namespace GlpiPlugin\Myplugin;
4
5 $superasset = new Superasset;
6 $superassets_id = $superasset->add([
7 'name' => 'My super asset'
8 ]);
9 if (!superassets_id) {
10 //super asset has not been created :'(
11 }
getFromDB(integer $id)
: load an item from database into current object using its id.
Fetched data will be available from fields
object property.
It returns false
if the object does not exists.
11<?php
12
13if ($superasset->getFromDB($superassets_id)) {
14 //super $superassets_id has been lodaded.
15 //you can access its data from $superasset->fields
16}
update(array $input)
: update fields of id
identified line with $input
parameter.
The id
key must be part of $input
.
Returns a boolean.
16<?php
17
18if (
19 $superasset->update([
20 'id' => $superassets_id,
21 'comment' => 'my comments'
22 ])
23) {
24 //super asset comment has been updated in databse.
25}
delete(array $input, bool $force = false)
: remove id
identified line corresponding.
The id
key must be part of $input
.
$force
parameter indicates if the line must be place in trashbin (false
, and a is_deleted
field must be present in the table) or removed (true
).
Returns a boolean.
23<?php
24
25if ($superasset->delete(['id' => $superassets_id])) {
26 //super asset has been moved to trashbin
27}
28
29if ($superasset->delete(['id' => $superassets_id], true)) {
30 //super asset is no longer present in database.
31 //a message will be displayed to user on next displayed page.
32}
Installationยถ
In the plugin_myplugin_install
function of your ๐ hook.php
file, we will manage the creation of the database table corresponding to our itemtype Superasset
.
๐ hook.php
1<?php
2
3use DBConnection;
4use GlpiPlugin\Myplugin\Superasset;
5use Migration;
6
7function plugin_myplugin_install()
8{
9 global $DB;
10
11 $default_charset = DBConnection::getDefaultCharset();
12 $default_collation = DBConnection::getDefaultCollation();
13
14 // instantiate migration with version
15 $migration = new Migration(PLUGIN_MYPLUGIN_VERSION);
16
17 // create table only if it does not exist yet!
18 $table = Superasset::getTable();
19 if (!$DB->tableExists($table)) {
20 //table creation query
21 $query = "CREATE TABLE `$table` (
22 `id` int unsigned NOT NULL AUTO_INCREMENT,
23 `is_deleted` TINYINT NOT NULL DEFAULT '0',
24 `name` VARCHAR(255) NOT NULL,
25 PRIMARY KEY (`id`)
26 ) ENGINE=InnoDB
27 DEFAULT CHARSET={$default_charset}
28 COLLATE={$default_collation}";
29 $DB->queryOrDie($query, $DB->error());
30 }
31
32 //execute the whole migration
33 $migration->executeMigration();
34
35 return true;
36}
In addition, of a primary key, VARCHAR
field to store a name entered by the user and a flag for the the trashbin.
Note
๐ You of course can add some other fields with other types (stay reasonable ๐).
To handle migration from a version to another of our plugin, we will use GLPI Migration class.
๐ hook.php
1<?php
2
3use Migration;
4
5function plugin_myplugin_install()
6{
7 global $DB;
8
9 // instantiate migration with version
10 $migration = new Migration(PLUGIN_MYPLUGIN_VERSION);
11
12 ...
13
14 if ($DB->tableExists($table)) {
15 // missing field
16 $migration->addField(
17 $table,
18 'fieldname',
19 'string'
20 );
21
22 // missing index
23 $migration->addKey(
24 $table,
25 'fieldname'
26 );
27 }
28
29 //execute the whole migration
30 $migration->executeMigration();
31
32 return true;
33}
Warning
โน๏ธ Migration class provides several methods that permit to manipulate tables and fields.
All calls will be stored in queue that will be executed when calling executeMigration
method.
Here are some examples:
- addField($table, $field, $type, $options)
adds a new field to a table
- changeField($table, $oldfield, $newfield, $type, $options)
change a field name or type
- dropField($table, $field)
drops a field
- dropTable($table)
drops a table
- renameTable($oldtable, $newtable)
rename a table
See Migration documentation for all other possibilities.
$type
parameter of different functions is the same as the private Migration::fieldFormat() method it allows shortcut for most common SQL types (bool, string, integer, date, datetime, text, longtext, autoincrement, char)
Uninstallationยถ
To uninstall our plugin, we want to clean all related data.
๐ hook.php
1<?php
2
3use GlpiPlugin\Myplugin\Superasset;
4
5function plugin_myplugin_uninstall()
6{
7 global $DB;
8
9 $tables = [
10 Superasset::getTable(),
11 ];
12
13 foreach ($tables as $table) {
14 if ($DB->tableExists($table)) {
15 $DB->doQueryOrDie(
16 "DROP TABLE `$table`",
17 $DB->error()
18 );
19 }
20 }
21
22 return true;
23}
Framework usageยถ
Some useful functions
<?php
Toolbox::logError($var1, $var2, ...);
This method stored in glpi/files/_log/php-errors.log
file content of its parameters (may be strings, arrays, objects, etc).
<?php
Html::printCleanArray($var);
This method will display a โdebugโ array of the provided variable. It only accepts array
type.
Common actions on an objectยถ
Note
๐ We will now add most common actions to our Superasset
itemtype:
display a list and a form to add/edit
define add/edit/delete routes
In our front
directory, we will need two new files.
๐ glpi ๐ plugins ๐ myplugin ... ๐ front ๐ superasset.php ๐ superasset.form.php ...
Warning
โน๏ธ Into those files, we will import GLPI framework with the following:
<?php
include ('../../../inc/includes.php');
First file (superasset.php
) will display list of items stored in our table.
It will use the internal search engine show
method of the search engine.
๐ front/superasset.php
1<?php
2
3use GlpiPlugin\Myplugin\Superasset;
4use Search;
5use Html;
6
7include ('../../../inc/includes.php');
8
9Html::header(
10 Superasset::getTypeName(),
11 $_SERVER['PHP_SELF'],
12 "plugins",
13 Superasset::class,
14 "superasset"
15);
16Search::show(Superasset::class);
17Html::footer();
header
and footer
methods from Html class permit to rely on GLPI graphical user interface (menu, breadcrumb, page footer, etc).
Second file (superasset.form.php
- with .form
suffix) will handle CRUD actions.
๐ front/superasset.form.php
1<?php
2
3use GlpiPlugin\Myplugin\Superasset;
4use Html;
5
6include ('../../../inc/includes.php');
7
8$supperasset = new Superasset();
9
10if (isset($_POST["add"])) {
11 $newID = $supperasset->add($_POST);
12
13 if ($_SESSION['glpibackcreated']) {
14 Html::redirect(Superasset::getFormURL()."?id=".$newID);
15 }
16 Html::back();
17
18} else if (isset($_POST["delete"])) {
19 $supperasset->delete($_POST);
20 $supperasset->redirectToList();
21
22} else if (isset($_POST["restore"])) {
23 $supperasset->restore($_POST);
24 $supperasset->redirectToList();
25
26} else if (isset($_POST["purge"])) {
27 $supperasset->delete($_POST, 1);
28 $supperasset->redirectToList();
29
30} else if (isset($_POST["update"])) {
31 $supperasset->update($_POST);
32 \Html::back();
33
34} else {
35 // fill id, if missing
36 isset($_GET['id'])
37 ? $ID = intval($_GET['id'])
38 : $ID = 0;
39
40 // display form
41 Html::header(
42 Superasset::getTypeName(),
43 $_SERVER['PHP_SELF'],
44 "plugins",
45 Superasset::class,
46 "superasset"
47 );
48 $supperasset->display(['id' => $ID]);
49 Html::footer();
50}
All common actions defined here are handled from CommonDBTM class.
For missing display action, we will create a showForm
method in our Superasset
class.
Note this one already exists in CommonDBTM
and is displayed using a generic Twig template.
We will use our own template that will extends the generic one (because it only displays common fields).
๐ src/Superasset.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use CommonDBTM;
6use Glpi\Application\View\TemplateRenderer;
7
8class Superasset extends CommonDBTM
9{
10
11 ...
12
13 function showForm($ID, $options=[])
14 {
15 $this->initForm($ID, $options);
16 // @myplugin is a shortcut to the **templates** directory of your plugin
17 TemplateRenderer::getInstance()->display('@myplugin/superasset.form.html.twig', [
18 'item' => $this,
19 'params' => $options,
20 ]);
21
22 return true;
23 }
24}
๐ templates/superasset.form.html.twig
1{% extends "generic_show_form.html.twig" %}
2{% import "components/form/fields_macros.html.twig" as fields %}
3
4{% block more_fields %}
5 blabla
6{% endblock %}
After that step, a call in our browser to http://glpi/plugins/myplugin/front/superasset.form.php should display the creation form.
Warning
โน๏ธ ๐ components/form/fields_macros.html.twig
file imported in the example includes Twig functions or macros to display common HTML fields like:
{{ fields.textField(name, value, label = '', options = {}) }}
: HTML code for a text
input.
{{ fields.hiddenField(name, value, label = '', options = {}) }
: HTML code for a hidden
input.
{{ dateField(name, value, label = '', options = {}) }
: HTML code for a date picker (using flatpickr)
{{ datetimeField(name, value, label = '', options = {}) }
: HTML code for a datetime picker (using flatpickr)
See ๐ templates/components/form/fields_macros.html.twig
file in source code for more details and capacities.
Defining tabsยถ
GLPI proposes three methods to define tabs:
defineTabs(array $options = []): declares classes that provides tabs to current class.
getTabNameForItem(CommonGLPI $item, boolean $withtemplate = 0): declares titles displayed for tabs.
displayTabContentForItem(CommonGLPI $item, integer $tabnum = 1, boolean $withtemplate = 0): allow displaying tabs contents.
Standards tabsยถ
Some GLPI internal API classes allows you to add a behaviour with minimal code.
Itโs true for notes (Notepad) and history (Log).
Here is an example for both of them:
๐ src/Superasset.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use CommonDBTM;
6use Notepad;
7use Log;
8
9class Superasset extends CommonDBTM
10{
11 // permits to automaticaly store logs for this itemtype
12 // in glpi_logs table
13 public $dohistory = true;
14
15 ...
16
17 function defineTabs($options = [])
18 {
19 $tabs = [];
20 $this->addDefaultFormTab($tabs)
21 ->addStandardTab(Notepad::class, $tabs, $options)
22 ->addStandardTab(Log::class, $tabs, $options);
23
24 return $tabs;
25 }
26}
Display of an instance of your itemtype from the page front/superasset.php?id=1
should now have 3 tabs:
Main tab with your itemtype name
Notes tab
History tab
Custom tabsยถ
On a similar basis, we can target another class of our plugin:
๐ src/Superasset.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use CommonDBTM;
6use Notepad;
7use Log;
8
9class Superasset extends CommonDBTM
10{
11 // permits to automaticaly store logs for this itemtype
12 // in glpi_logs table
13 public $dohistory = true;
14
15 ...
16
17 function defineTabs($options = [])
18 {
19 $tabs = [];
20 $this->addDefaultFormTab($tabs)
21 ->addStandardTab(Superasset_Item::class, $tabs, $options);
22 ->addStandardTab(Notepad::class, $tabs, $options)
23 ->addStandardTab(Log::class, $tabs, $options);
24
25 return $tabs;
26 }
In this new class we will define two other methods to control title and content of the tab:
๐ src/Superasset_Item.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use CommonDBTM;
6use Glpi\Application\View\TemplateRenderer;
7
8class Superasset_Item extends CommonDBTM
9{
10 /**
11 * Tabs title
12 */
13 function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
14 {
15 switch ($item->getType()) {
16 case Superasset::class:
17 $nb = countElementsInTable(self::getTable(),
18 [
19 'plugin_myplugin_superassets_id' => $item->getID()
20 ]
21 );
22 return self::createTabEntry(self::getTypeName($nb), $nb);
23 }
24 return '';
25 }
26
27 /**
28 * Display tabs content
29 */
30 static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0)
31 {
32 switch ($item->getType()) {
33 case Superasset::class:
34 return self::showForSuperasset($item, $withtemplate);
35 }
36
37 return true;
38 }
39
40 /**
41 * Specific function for display only items of Superasset
42 */
43 static function showForSuperasset(Superasset $superasset, $withtemplate = 0)
44 {
45 TemplateRenderer::getInstance()->display('@myplugin/superasset_item_.html.twig', [
46 'superasset' => $superasset,
47 ]);
48 }
49}
As previously, we will use a Twig template to handle display.
๐ templates/superasset_item.html.twig
1{% import "components/form/fields_macros.html.twig" as fields %}
2
3example content
Note
๐ Exercise:
For the rest of this part, you will need to complete our plugin to allow the installation/uninstallation of the data of this new class Superasset_Item
.
Table should contains following fields:
an identifier (id)
a foreign key to
plugin_myplugin_superassets
tabletwo fields to link with an itemtype:
itemtype
which will store the itemtype class to link to (Computer for example)items_id
the id of the linked asset
Your plugin must be re-installed or updated for the table creation to be done.
You can force the plugin status to change by incrementing the version number in the setup.php
file.
For the exercise, we will only display computers (Computer) displayed with the following code:
{{ fields.dropdownField(
'Computer',
'items_id',
'',
__('Add a computer')
) }}
We will include a mini form to insert related items in our table. Form actions can be handled from myplugin/front/supperasset.form.php
file.
Note GLPI forms submitted as POST
will be protected with a CRSF token..
You can include a hidden field to validate the form:
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}">
We will also display a list of computers already associated below the form.
Using core objectsยถ
We can also allow our class to add tabs on core objects.
We will declare this in a new line in our init
function:
๐ setup.php
1<?php
2
3use Computer;
4
5function plugin_init_myplugin()
6{
7 ...
8
9 Plugin::registerClass(GlpiPlugin\Myplugin\Superasset_Item::class, [
10 'addtabon' => Computer::class
11 ]);
12}
Title and content for this tab are done as previously with:
CommonDBTM::getTabNameForItem()
CommonDBTM::displayTabContentForItem()
Note
๐ Exercise:
Complete previous methods to display on computers a new tab with associated Superasset
.
Defining Search optionsยถ
Search options is an array of columns for GLPI search engine. They are used to know for each itemtype how the database must be queried, and how data should be displayed.
In our class, we must declare a rawSearchOptions
method:
๐ src/Superasset.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use CommonDBTM;
6
7class Superasset extends CommonDBTM
8{
9 ...
10
11 function rawSearchOptions()
12 {
13 $options = [];
14
15 $options[] = [
16 'id' => 'common',
17 'name' => __('Characteristics')
18 ];
19
20 $options[] = [
21 'id' => 1,
22 'table' => self::getTable(),
23 'field' => 'name',
24 'name' => __('Name'),
25 'datatype' => 'itemlink'
26 ];
27
28 $options[] = [
29 'id' => 2,
30 'table' => self::getTable(),
31 'field' => 'id',
32 'name' => __('ID')
33 ];
34
35 $options[] = [
36 'id' => 3,
37 'table' => Superasset_Item::getTable(),
38 'field' => 'id',
39 'name' => __('Number of associated assets', 'myplugin'),
40 'datatype' => 'count',
41 'forcegroupby' => true,
42 'usehaving' => true,
43 'joinparams' => [
44 'jointype' => 'child',
45 ]
46 ];
47
48 return $options;
49 }
50}
Following this addition, we should be able to select our new columns from our asset list page:

Those options will also be present in search criteria list of that page.
Each option
is identified by an id
key.
This key is used in other parts of GLPI.
It must be absolutely unique.
By convention, โ1โ and โ2โ are โreservedโ for the object name and ID.
The search options documentation describes all possible options.
Using other objectsยถ
It is also possible to improve another itemtype search options. As an example, we would like to display associated โSuperassetโ on in the computer list:
๐ hook.php
50<?php
51
52use GlpiPlugin\Myplugin\Superasset;
53use GlpiPlugin\Myplugin\Superasset_Item;
54
55...
56
57function plugin_myplugin_getAddSearchOptionsNew($itemtype)
58{
59 $sopt = [];
60
61 if ($itemtype == 'Computer') {
62 $sopt[] = [
63 'id' => 12345,
64 'table' => Superasset::getTable(),
65 'field' => 'name',
66 'name' => __('Associated Superassets', 'myplugin'),
67 'datatype' => 'itemlink',
68 'forcegroupby' => true,
69 'usehaving' => true,
70 'joinparams' => [
71 'beforejoin' => [
72 'table' => Superasset_Item::getTable(),
73 'joinparams' => [
74 'jointype' => 'itemtype_item',
75 ]
76 ]
77 ]
78 ];
79 }
80
81 return $sopt;
82}
As previously, you must provide an id
for your new search options that does not override existing ones for Computer
.
You can use a script from the tools
folder of the GLPI git repository (not present in the โreleaseโ archives) to help you list the id already declared (by the core and plugins present on your computer) for a particular itemtype.
/usr/bin/php /path/to/glpi/tools/getsearchoptions.php --type=Computer
Search engine display preferencesยถ
We just have added new columns to our itemtype list.
Those columns are handled by DisplayPreference
object (glpi_displaypreferences
table).
They can be defined as global (set 0
for users_id
field) or personal (set users_id
field to the user id). They are sorted (rank
field) and target an itemtype plus a searchoption
(num
field).
Warning
โ ๏ธ Warning Global preferences are applied to all users that donโt have any personal preferences set.
Note
๐ Exercise: You will change installation and uninstallation functions of your plugin to add and remove global preferences so objects list display some columns.
Standard events hooksยถ
During a GLPI object life cycle, we can intervene via our plugin before and after each event (add, modify, delete).
For our own objects, following methods can be implemented:
For every event applied on the database, we have a method that is executed before, and another after.
Note
๐ Exercise:
Add required methods to PluginMypluginSuperasset
class to check the name
field is properly filled when adding and updating.
On effective removal, we must ensure linked data from other tables are also removed.
Plugins can also intercept standard core events to apply changes (or even refuse the event). Here are the names of the hooks:
1<?php
2
3use Glpi\Plugin\Hooks;
4
5...
6
7Hooks::PRE_ITEM_ADD;
8Hooks::ITEM_ADD;
9Hooks::PRE_ITEM_DELETE;
10Hooks::ITEM_DELETE;
11Hooks::PRE_ITEM_PURGE;
12Hooks::ITEM_PURGE;
13Hooks::PRE_ITEM_RESTORE;
14Hooks::ITEM_RESTORE;
15Hooks::PRE_ITEM_UPDATE;
16Hooks::ITEM_UPDATE;
More information are available from hooks documentation especially on standard events part.
For all those calls, we will get an instance of the current object in parameter of our callback
function. We will be able to access its current fields ($item->fields
) or those sent by the form ($item->input
).
As all PHP objects, this instance will be passed by reference.
We will declare one of those hooks usage in the plugin init function and add a callback
function:
๐ setup.php
1<?php
2
3use GlpiPlugin\Myplugin\Superasset;
4
5...
6
7function plugin_init_myplugin()
8{
9 ...
10
11 // callback a function (declared in hook.php)
12 $PLUGIN_HOOKS['item_update']['myplugin'] = [
13 'Computer' => 'myplugin_computer_updated'
14 ];
15
16 // callback a class method
17 $PLUGIN_HOOKS['item_add']['myplugin'] = [
18 'Computer' => [
19 Superasset::class, 'computerUpdated'
20 ]
21 ];
22}
In both cases (hook.php
function or class method), the prototype of the functions will be made on this model:
1<?php
2
3use CommonDBTM;
4use Session;
5
6function hookCallback(CommonDBTM $item)
7{
8 ...
9
10 // if we need to stop the process (valid for pre* hooks)
11 if ($mycondition) {
12 // clean input
13 $item->input = [];
14
15 // store a message in session for warn user
16 Session::addMessageAfterRedirect('Action forbidden because...');
17
18 return;
19 }
20}
Note
๐ Exercise:
Use a hook to intercept the purge of a computer and remove associated with a Superasset
lines if any.
Importing libraries (JavaScript / CSS)ยถ
Plugins can declare import of additional libraries from their init
function.
๐ setup.php
1<?php
2
3use Glpi\Plugin\Hooks;
4
5function plugin_init_myplugin()
6{
7 ...
8
9 // css & js
10 $PLUGIN_HOOKS[Hooks::ADD_CSS]['myplugin'] = 'myplugin.css';
11 $PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['myplugin'] = [
12 'js/common.js',
13 ];
14
15 // on ticket page (in edition)
16 if (strpos($_SERVER['REQUEST_URI'], "ticket.form.php") !== false
17 && isset($_GET['id'])) {
18 $PLUGIN_HOOKS['add_javascript']['myplugin'][] = 'js/ticket.js.php';
19 }
20
21 ...
22}
Several things to remember:
Loading paths are relative to plugin directory.
Scripts declared this way will be loaded on all GLPI pages. You must check the current page in the
init
function.You can rely on
Html::requireJs()
method to load external resources. Paths will be prefixed with GLPI root URL at load.If you want to modify page DOM and especially what is displayed in main form, you should call your code twice (on page load and on current tab load) and add a class to check the effective application of your code:
1$(function() {
2 doStuff();
3 $(".glpi_tabs").on("tabsload", function(event, ui) {
4 doStuff();
5 });
6});
7
8var doStuff = function()
9{
10 if (! $("html").hasClass("stuff-added")) {
11 $("html").addClass("stuff-added");
12
13 // do stuff you need
14 ...
15
16 }
17};
Note
๐ Exercises:
Add a new icon in preferences menu to display main GLPI configuration. You can use tabler-icons:
<a href='...' class='ti ti-mood-smile'></a>
On ticket edition page, add an icon to self-associate as a requester on the model of the one present for the โassigned toโ part.
Display hooksยถ
Since GLPI 9.1.2, it is possible to display data in native objects forms via new hooks. See display related hooks in plugins documentation.
As previous hooks, declaration will look like:
๐ setup.php
1<?php
2
3use Glpi\Plugin\Hooks;
4use GlpiPlugin\Myplugin\Superasset;
5
6function plugin_init_myplugin()
7{
8 ...
9
10 $PLUGIN_HOOKS[Hooks::PRE_ITEM_FORM]['myplugin'] = [
11 Superasset::class, 'preItemFormComputer'
12 ];
13}
Warning
โน๏ธ Important Those display hooks are a bit different from other hooks regarding parameters that are passed to callback underlying method. We will obtain an array with the following keys:
item
with currentCommonDBTM
object
options
, an array passed from current objectshowForm()
method
example of a call from core:
<?php
Plugin::doHook("pre_item_form", ['item' => $this, 'options' => &$options]);
Note
๐ Exercice:
Add the number of associated Superasset
in the computer form header.
It should be a link to the previous added tab to computers.
This link will target the same page, but with the forcetab=PluginMypluginSuperasset$1
parameter.
Adding a configuration pageยถ
We will add a tab in GLPI configuration so some parts of our plugin can be optional.
We previously added a tab to the form for computers using hooks in setup.php
file. We will define two configuration options to enable/disable those tabs.
GLPI provides a glpi_configs
table to store software configuration. It allows plugins to save their own data without defining additional tables.
First of all, letโs create a new Config.php
class in the src/
folder with the following skeleton:
๐ src/Config.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use Config;
6use CommonGLPI;
7use Dropdown;
8use Html;
9use Session;
10use Glpi\Application\View\TemplateRenderer;
11
12class Config extends \Config
13{
14
15 static function getTypeName($nb = 0)
16 {
17 return __('My plugin', 'myplugin');
18 }
19
20 static function getConfig()
21 {
22 return Config::getConfigurationValues('plugin:myplugin');
23 }
24
25 function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
26 {
27 switch ($item->getType()) {
28 case Config::class:
29 return self::createTabEntry(self::getTypeName());
30 }
31 return '';
32 }
33
34 static function displayTabContentForItem(
35 CommonGLPI $item,
36 $tabnum = 1,
37 $withtemplate = 0
38 ) {
39 switch ($item->getType()) {
40 case Config::class:
41 return self::showForConfig($item, $withtemplate);
42 }
43
44 return true;
45 }
46
47 static function showForConfig(
48 Config $config,
49 $withtemplate = 0
50 ) {
51 global $CFG_GLPI;
52
53 if (!self::canView()) {
54 return false;
55 }
56
57 $current_config = self::getConfig();
58 $canedit = Session::haveRight(self::$rightname, UPDATE);
59
60 TemplateRenderer::getInstance()->display('@myplugin/config.html.twig', [
61 'current_config' => $current_config,
62 'can_edit' => $canedit
63 ]);
64 }
65}
Once again, we manage display from a dedicated template file:
๐ templates/config.html.twig
1{% import "components/form/fields_macros.html.twig" as fields %}
2
3{% if can_edit %}
4 <form name="form" action="{{ "Config"|itemtype_form_path }}" method="POST">
5 <input type="hidden" name="config_class" value="GlpiPlugin\\Myplugin\\Config">
6 <input type="hidden" name="config_context" value="plugin:myplugin">
7 <input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}">
8
9 {{ fields.dropdownYesNo(
10 'myplugin_computer_tab',
11 current_config['myplugin_computer_tab'],
12 __("Display tab in computer", 'myplugin')
13 ) }}
14
15 {{ fields.dropdownYesNo(
16 'myplugin_computer_form',
17 current_config['myplugin_computer_form'],
18 __("Display information in computer form", 'myplugin')
19 ) }}
20
21 <button type="submit" class="btn btn-primary mx-1" name="update" value="1">
22 <i class="ti ti-device-floppy"></i>
23 <span>{{ _x('button', 'Save') }}</span>
24 </button>
25 </form>
26{% endif %}
This skeleton retrieves the calls to a tab in the Setup > General
menu to display the dedicated form.
It is useless to add a front
file because the GLPI Config
object already offers a form display.
Note that we display, from the myplugin_computer_form
two yes/no fields named myplugin_computer_tab
and myplugin_computer_form
.
Note
โ๏ธ Complete setup.php
file by defining the new tab in the Config
class.
You also have to add those new configuration entries management to install/uninstall methods. You can use the following:
<?php
use Config;
Config::setConfigurationValues('##context##', [
'##config_name##' => '##config_default_value##'
]);
<?php
use Config;
$config = new Config();
$config->deleteByCriteria(['context' => '##context##']);
Do not forget to replace ##
surrounded terms with your own values!
Managing rightsยถ
To limit access to our plugin features to some of our users, we can use the GLPI Profile class.
This will check $rightname
property of class that inherits CommonDBTM for all standard events.
Those check are done by static can*
functions:
In order to customize rights, we will redefine those static methods in our classes.
If we need to check a right manually in our code, the Session class provides some methods:
1<?php
2
3use Session;
4
5if (Session::haveRight(self::$rightname, CREATE)) {
6 // OK
7}
8
9// we can also test a set multiple rights with AND operator
10if (Session::haveRightsAnd(self::$rightname, [CREATE, READ])) {
11 // OK
12}
13
14// also with OR operator
15if (Session::haveRightsOr(self::$rightname, [CREATE, READ])) {
16 // OK
17}
18
19// check a specific right (not your class one)
20if (Session::haveRight('ticket', CREATE)) {
21 // OK
22}
Above methods return a boolean. If we need to stop the page with a message to the user, we can use equivalent methods with check
instead of have
prefix:
Warning
โน๏ธ If you need to check a right in an SQL query, use bitwise operators &
and |
:
<?php
$iterator = $DB->request([
'SELECT' => 'glpi_profiles_users.users_id',
'FROM' => 'glpi_profiles_users',
'INNER JOIN' => [
'glpi_profiles' => [
'ON' => [
'glpi_profiles_users' => 'profiles_id'
'glpi_profiles' => 'id'
]
],
'glpi_profilerights' => [
'ON' => [
'glpi_profilerights' => 'profiles_id',
'glpi_profiles' => 'id'
]
]
],
'WHERE' => [
'glpi_profilerights.name' => 'ticket',
'glpi_profilerights.rights' => ['&', (READ | CREATE)];
]
]);
In this code example, the READ | CREATE
make a bit sum, and the &
operator compare the value at logical level with the table.
Possible values for standard rights can be found in the inc/define.php
file of GLPI:
1<?php
2
3...
4
5define("READ", 1);
6define("UPDATE", 2);
7define("CREATE", 4);
8define("DELETE", 8);
9define("PURGE", 16);
10define("ALLSTANDARDRIGHT", 31);
11define("READNOTE", 32);
12define("UPDATENOTE", 64);
13define("UNLOCK", 128);
Add a new rightยถ
Note
โ๏ธ We previously defined a property $rightname = 'computer'
on which weโve automatically rights as super-admin
.
We will now create a specific right for the plugin.
First of all, letโs create a new class dedicated to profiles management:
๐ src/Profile.php
1<?php
2namespace GlpiPlugin\Myplugin;
3
4use CommonDBTM;
5use CommonGLPI;
6use Html;
7use Profile as Glpi_Profile;
8
9class Profile extends CommonDBTM
10{
11 public static $rightname = 'profile';
12
13 static function getTypeName($nb = 0)
14 {
15 return __("My plugin", 'myplugin');
16 }
17
18 public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
19 {
20 if (
21 $item instanceof Glpi_Profile
22 && $item->getField('id')
23 ) {
24 return self::createTabEntry(self::getTypeName());
25 }
26 return '';
27 }
28
29 static function displayTabContentForItem(
30 CommonGLPI $item,
31 $tabnum = 1,
32 $withtemplate = 0
33 ) {
34 if (
35 $item instanceof Glpi_Profile
36 && $item->getField('id')
37 ) {
38 return self::showForProfile($item->getID());
39 }
40
41 return true;
42 }
43
44 static function getAllRights($all = false)
45 {
46 $rights = [
47 [
48 'itemtype' => Superasset::class,
49 'label' => Superasset::getTypeName(),
50 'field' => 'myplugin::superasset'
51 ]
52 ];
53
54 return $rights;
55 }
56
57
58 static function showForProfile($profiles_id = 0)
59 {
60 $profile = new Glpi_Profile();
61 $profile->getFromDB($profiles_id);
62
63 TemplateRenderer::getInstance()->display('@myplugin/profile.html.twig', [
64 'can_edit' => self::canUpdate(),
65 'profile' => $profile
66 'rights' => self::getAllRights()
67 ]);
68 }
69}
Once again, display will be done from a Twig template:
๐ templates/profile.html.twig
1{% import "components/form/fields_macros.html.twig" as fields %}
2<div class='firstbloc'>
3 <form name="form" action="{{ "Profile"|itemtype_form_path }}" method="POST">
4 <input type="hidden" name="id" value="{{ profile.fields['id'] }}">
5 <input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}">
6
7 {% if can_edit %}
8 <button type="submit" class="btn btn-primary mx-1" name="update" value="1">
9 <i class="ti ti-device-floppy"></i>
10 <span>{{ _x('button', 'Save') }}</span>
11 </button>
12 {% endif %}
13 </form>
14</div>
We declare a new tab on Profile
object from our init
function:
๐ setup.php
1<?php
2
3use Plugin;
4use Profile;
5use GlpiPlugin\Myplugin\Profile as MyPlugin_Profile;
6
7function plugin_init_myplugin()
8{
9 ...
10
11 Plugin::registerClass(MyPlugin_Profile::class, [
12 'addtabon' => Profile::class
13 ]);
14}
And we tell installer to setup a minimal right for current profile (super-admin
):
๐ hook.php
1<?php
2
3use GlpiPlugin\Myplugin\Profile as MyPlugin_Profile;
4use ProfileRight;
5
6function plugin_myplugin_install() {
7 ...
8
9 // add rights to current profile
10 foreach (MyPlugin_Profile::getAllRights() as $right) {
11 ProfileRight::addProfileRights([$right['field']]);
12 }
13
14 return true;
15}
16
17function plugin_myplugin_uninstall() {
18 ...
19
20 // delete rights for current profile
21 foreach (MyPlugin_Profile::getAllRights() as $right) {
22 ProfileRight::deleteProfileRights([$right['field']]);
23 }
24
25}
Then, wa can define rights from Administration > Profiles
menu and can change the $rightname
property of our class to myplugin::superasset
.
Extending standard rightsยถ
If we need specific rights for our plugin, for example the right to perform associations, we must override the getRights
function in the class defining the rights.
In defined above example of the PluginMypluginProfile
class, we added a getAllRights
method which indicates that the right myplugin::superasset
is defined in the PluginMypluginSuperasset
class.
This one inherits from CommonDBTM
and has a getRights
method that we can override:
๐ src/Superasset.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use CommonDBTM;
6...
7
8class Superasset extends CommonDBTM
9{
10 const RIGHT_ONE = 128;
11
12 ...
13
14 function getRights($interface = 'central')
15 {
16 // if we need to keep standard rights
17 $rights = parent::getRights();
18
19 // define an additional right
20 $rights[self::RIGHT_ONE] = __("My specific rights", "myplugin");
21
22 return $rights;
23 }
24}
Massive actionsยถ
GLPI massive actions allow applying modifications to a selection.

By default, GLPI proposes following actions:
Edit: to edit fields that are defined in search options (excepted those where
massiveaction
is set tofalse
)Put in trashbin/Delete
It is possible to declare extra massive actions.
To achieve that in your plugin, you must declare a hook in the init
function:
๐ setup.php
1<?php
2
3function plugin_init_myplugin()
4{
5 ...
6
7 $PLUGIN_HOOKS['use_massive_action']['myplugin'] = true;
8}
Then, in the Superasset
class, you must add 3 methods:
getSpecificMassiveActions
: massive actions declaration.showMassiveActionsSubForm
: sub-form display.processMassiveActionsForOneItemtype
: handle form submit.
Here is a minimal implementation example:
๐ src/Superasset.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use CommonDBTM;
6use Html;
7use MassiveAction;
8
9class Superasset extends CommonDBTM
10{
11 ...
12
13 function getSpecificMassiveActions($checkitem = NULL)
14 {
15 $actions = parent::getSpecificMassiveActions($checkitem);
16
17 // add a single massive action
18 $class = __CLASS__;
19 $action_key = "myaction_key";
20 $action_label = "My new massive action";
21 $actions[$class . MassiveAction::CLASS_ACTION_SEPARATOR . $action_key] = $action_label;
22
23 return $actions;
24 }
25
26 static function showMassiveActionsSubForm(MassiveAction $ma)
27 {
28 switch ($ma->getAction()) {
29 case 'myaction_key':
30 echo __("fill the input");
31 echo Html::input('myinput');
32 echo Html::submit(__('Do it'), ['name' => 'massiveaction']) . "</span>";
33
34 break;
35 }
36
37 return parent::showMassiveActionsSubForm($ma);
38 }
39
40 static function processMassiveActionsForOneItemtype(
41 MassiveAction $ma,
42 CommonDBTM $item,
43 array $ids
44 ) {
45 switch ($ma->getAction()) {
46 case 'myaction_key':
47 $input = $ma->getInput();
48
49 foreach ($ids as $id) {
50
51 if (
52 $item->getFromDB($id)
53 && $item->doIt($input)
54 ) {
55 $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
56 } else {
57 $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
58 $ma->addMessage(__("Something went wrong"));
59 }
60 }
61 return;
62 }
63
64 parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
65 }
66}
Note
๐ Exercise: With the help of the official documentation on massive actions, complete in your plugin the above methods to allow the linking with a computer from โSuper assetsโ massive actions.
You can display a list of computers with:
Computer::dropdown();
It is also possible to add massive actions to GLPI native objects.
To achieve that, you must declare a _MassiveActions
function in the hook.php
file:
๐ hook.php
1<?php
2
3use Computer;
4use MassiveAction;
5use GlpiPlugin\Myplugin\Superasset;
6
7...
8
9function plugin_myplugin_MassiveActions($type)
10{
11 $actions = [];
12 switch ($type) {
13 case Computer::class:
14 $class = Superasset::class;
15 $key = 'DoIt';
16 $label = __("plugin_example_DoIt", 'example');
17 $actions[$class.MassiveAction::CLASS_ACTION_SEPARATOR.$key]
18 = $label;
19
20 break;
21 }
22 return $actions;
23}
Sub form display and processing are done the same way as you did for your plugin itemtypes.
Note
๐ Exercise: As the previous exercise, add a massive action to link a computer to a โSuper assetโ from the computer list.
Do not forget to use unique keys and labels.
Notificationsยถ
Warning
โ ๏ธ Access to an SMTP server is recommended; it must be properly configured in Setup > Notifications
menu.
On a development environment, you can install mailhog or mailcatcher which expose a local SMTP server and allow you to get emails sent by GLPI in a graphical interface.
Please also note that GLPI queues all notifications rather than sending them directly. The only exception to this is the test email notification.
All โpendingโ notifications are visible in the Administration > Notification queue
menu.
You can send notifications immediately from this menu or by forcing the queuednotification
automatic action.
The GLPI notification system allows sending alerts to the actors of a recorded event. By default, notifications can be sent by email or as browser notifications, but other channels may be available from plugins (or you can add your own one).
That system is divided in several classes:
Notification
: the triggering item. It receives common data like name, if it is active, sending mode, event, content (NotificationTemplate
), etc.NotificationTarget
: defines notification recipients.It is possible to define recipients based on the triggering item (author, assignee) or static recipients (a specific user, all users of a specific group, etc).
NotificationTemplate
: notification templates are used to build the content, which can be chosen from Notification form. CSS can be defined in the templates and it receives one or moreNotificationTemplateTranslation
instances.NotificationTemplateTranslation
: defines the translated template content. If no language is specified, it will be the default translation. If no template translation exists for a userโs language, the default translation will be used.The content is dynamically generated with tags provided to the user and completed by HTML.
All of these notification-related object are natively managed by GLPI core and does not require any development intervention from us.
We can however trigger a notification execution via the following code:
<?php
use NotificationEvent;
NotificationEvent::raiseEvent($event, $item);
The event
key corresponds to the triggering event name defined in the Notification
object and the item
key to the triggering item.
Therefore, the raiseEvent
method will search the glpi_notifications
table for an active line with these 2 characteristics.
To use this trigger in our plugin, we will add a new class PluginMypluginNotificationTargetSuperasset
.
This ones targets our Superasset
object. It is the standard way to develop notifications in GLPI. We have an itemtype with its own life and a notification object related to it.
๐ src/NotificationTargetSuperasset.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use NotificationTarget;
6
7class NotificationTargetSuperasset extends NotificationTarget
8{
9
10 function getEvents()
11 {
12 return [
13 'my_event_key' => __('My event label', 'myplugin')
14 ];
15 }
16
17 function getDatasForTemplate($event, $options = [])
18 {
19 $this->datas['##myplugin.name##'] = __('Name');
20 }
21}
We have to declare our Superasset
object can send notifications in our init
function:
๐ setup.php
1<?php
2
3use Plugin;
4use GlpiPlugin\Myplugin\Superasset;
5
6function plugin_init_myplugin()
7{
8 ...
9
10 Plugin::registerClass(Superasset::class, [
11 'notificationtemplates_types' => true
12 ]);
13}
With this minimal code itโs possible to create using the GLPI UI, a new notification targeting our Superasset
itemtype and with the โMy event labelโ event and then use the raiseEvent
method with these parameters.
Note
๐ Exercise: Along with an effective sending test, you will manage installation and uninstallation of notification and related objects (templates, translations).
You can see an example (still incomplete) on notifications in plugins documentation.
Automatic actionsยถ
This GLPI feature provides a task scheduler executed silently from user usage (GLPI mode) or by the server in command line (CLI mode) via a call to the front/cron.php
file of GLPI.

To add one or more automatic actions to our class, we will add those methods:
cronInfo
: possible actions for the class, and associated labelscron*Action*
: a method for each action defined incronInfo
. Those are called to manage each action.
๐ src/Superasset.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5use CommonDBTM;
6
7class Superasset extends CommonDBTM
8{
9 ...
10
11 static function cronInfo($name)
12 {
13
14 switch ($name) {
15 case 'myaction':
16 return ['description' => __('action desc', 'myplugin')];
17 }
18 return [];
19 }
20
21 static function cronMyaction($task = NULL)
22 {
23 // do the action
24
25 return true;
26 }
27}
To tell GLPI that the automatic action exists, you just have to register it:
๐ hook.php
1<?php
2
3use CronTask;
4
5function plugin_myplugin_install()
6{
7
8 ...
9
10 CronTask::register(
11 PluginMypluginSuperasset::class,
12 'myaction',
13 HOUR_TIMESTAMP,
14 [
15 'comment' => '',
16 'mode' => \CronTask::MODE_EXTERNAL
17 ]
18 );
19}
No need to manage uninstallation (unregister) as GLPI will handle that itself when the plugin is uninstalled.
Publishing your pluginยถ
When you consider your plugin is ready and covers a real need, you can submit it to the community.
The plugins catalog allows GLPI users to discover, download and follow plugins provided by the community as well as first-party plugins provided by Teclibโ.
Just publish your code to an publicly accessible GIT repository (github, gitlab, โฆ) with an open source license of your choice and prepare an XML description file of your plugin. The XML file must follow this structure:
1<root>
2 <name>Displayed name</name>
3 <key>System name</key>
4 <state>stable</state>
5 <logo>http://link/to/logo/with/dimensions/40px/40px</logo>
6 <description>
7 <short>
8 <en>short description of the plugin, displayed on list, text only</en>
9 <lang>...</lang>
10 </short>
11 <long>
12 <en>short description of the plugin, displayed on detail, Markdown accepted</en>
13 <lang>...</lang>
14 </long>
15 </description>
16 <homepage>http://link/to/your/page</homepage>
17 <download>http://link/to/your/files</download>
18 <issues>http://link/to/your/issues</issues>
19 <readme>http://link/to/your/readme</readme>
20 <authors>
21 <author>Your name</author>
22 </authors>
23 <versions>
24 <version>
25 <num>1.0</num>
26 <compatibility>10.0</compatibility>
27 <download_url>http://link/to/your/download/glpi-myplugin-1.0.tar.bz2</download_url>
28 </version>
29 </versions>
30 <langs>
31 <lang>en_GB</lang>
32 <lang>...</lang>
33 </langs>
34 <license>GPL v2+</license>
35 <tags>
36 <en>
37 <tag>tag1</tag>
38 </en>
39 <lang>
40 <tag>tag1</tag>
41 </lang>
42 </tags>
43 <screenshots>
44 <screenshot>http://link/to/your/screenshot</screenshot>
45 <screenshot>http://link/to/your/screenshot</screenshot>
46 <screenshot>...</screenshot>
47 </screenshots>
48</root>
To market this plugin to a wide range of users, you should add a detailed description in several languages and provide screenshots that represent your plugin.
Finally, submit your XML file on the dedicated page of the plugins catalog (registration is required).
Note
Path to plugin XML file must display the raw XML file itself. For example, the following URL for the exmple plugin would be incorrect:
https://github.com/pluginsGLPI/example/blob/main/example.xml
The correct one (use Github UI raw button) would be:
https://raw.githubusercontent.com/pluginsGLPI/example/refs/heads/main/example.xml
Teclibโ will receive a notification for this submission and after some checks, will activate the publication on the catalog.
Miscellaneousยถ
Querying databaseยถ
Rely on DBmysqlIterator. It provides an exhaustive query builder
.
1<?php
2
3
4// => SELECT * FROM `glpi_computers`
5$iterator = $DB->request(['FROM' => 'glpi_computers']);
6foreach ($ierator as $row) {
7 //... work on each row ...
8}
9
10$DB->request([
11 'FROM' => ['glpi_computers', 'glpi_computerdisks'],
12 'LEFT JOIN' => [
13 'glpi_computerdisks' => [
14 'ON' => [
15 'glpi_computers' => 'id',
16 'glpi_computerdisks' => 'computer_id'
17 ]
18 ]
19 ]
20]);
Dashboardsยถ
Since GLPI 9.5, dashboards are available from:
Central page
Assets menu
Assistance menu
Ticket search results (mini dashboard)
This feature is split into several concepts - sub classes:
a placement grid (
Glpi\Dashboard\Grid
)a widget collection (
Glpi\Dashboard\Widget
) to graphically display dataa data provider collection (
Glpi\Dashboard\Provider
) that queries the databaserights (
Glpi\Dashboard\Right
) on each dashboardfilters (
Glpi\Dashboard\Filter
) that can be displayed in a dashboard header and impacting providers.
With these classes, we can build a dashboard that will display cards on its grid. A card is a combination of a widget, a data provider, a place on grid and various options (like a background colour for example).
Completing existing conceptsยถ
From your plugin, you can complete these concepts with your own data and code.
๐ setup.php
1<?php
2
3use Glpi\Plugin\Hooks;
4use GlpiPlugin\Myplugin\Dashboard;
5
6function plugin_init_myplugin()
7{
8 ...
9
10 // add new widgets to the dashboard
11 $PLUGIN_HOOKS[Hooks::DASHBOARD_TYPES]['myplugin'] = [
12 Dashboard::class => 'getTypes',
13 ];
14
15 // add new cards to the dashboard
16 $PLUGIN_HOOKS[Hooks::DASHBOARD_CARDS]['myplugin'] = [
17 Dashboard::class => 'getCards',
18 ];
19}
We will create a dedicated class for our dashboards:
๐ src/Dashboard.php
1<?php
2
3namespace GlpiPlugin\Myplugin;
4
5class Dashboard
6{
7 static function getTypes()
8 {
9 return [
10 'example' => [
11 'label' => __("Plugin Example", 'myplugin'),
12 'function' => __class__ . "::cardWidget",
13 'image' => "https://via.placeholder.com/100x86?text=example",
14 ],
15 'example_static' => [
16 'label' => __("Plugin Example (static)", 'myplugin'),
17 'function' => __class__ . "::cardWidgetWithoutProvider",
18 'image' => "https://via.placeholder.com/100x86?text=example+static",
19 ],
20 ];
21 }
22
23 static function getCards($cards = [])
24 {
25 if (is_null($cards)) {
26 $cards = [];
27 }
28 $new_cards = [
29 'plugin_example_card' => [
30 'widgettype' => ["example"],
31 'label' => __("Plugin Example card"),
32 'provider' => "PluginExampleExample::cardDataProvider",
33 ],
34 'plugin_example_card_without_provider' => [
35 'widgettype' => ["example_static"],
36 'label' => __("Plugin Example card without provider"),
37 ],
38 'plugin_example_card_with_core_widget' => [
39 'widgettype' => ["bigNumber"],
40 'label' => __("Plugin Example card with core provider"),
41 'provider' => "PluginExampleExample::cardBigNumberProvider",
42 ],
43 ];
44
45 return array_merge($cards, $new_cards);
46 }
47
48 static function cardWidget(array $params = [])
49 {
50 $default = [
51 'data' => [],
52 'title' => '',
53 // this property is "pretty" mandatory,
54 // as it contains the colors selected when adding widget on the grid send
55 // without it, your card will be transparent
56 'color' => '',
57 ];
58 $p = array_merge($default, $params);
59
60 // you need to encapsulate your html in div.card to benefit core style
61 $html = "<div class='card' style='background-color: {$p["color"]};'>";
62 $html.= "<h2>{$p['title']}</h2>";
63 $html.= "<ul>";
64 foreach ($p['data'] as $line) {
65 $html.= "<li>$line</li>";
66 }
67 $html.= "</ul>";
68 $html.= "</div>";
69
70 return $html;
71 }
72
73 static function cardWidgetWithoutProvider(array $params = [])
74 {
75 $default = [
76 // this property is "pretty" mandatory,
77 // as it contains the colors selected when adding widget on the grid send
78 // without it, your card will be transparent
79 'color' => '',
80 ];
81 $p = array_merge($default, $params);
82
83 // you need to encapsulate your html in div.card to benefit core style
84 $html = "<div class='card' style='background-color: {$p["color"]};'>
85 static html (+optional javascript) as card is not matched with a data provider
86 <img src='https://www.linux.org/images/logo.png'>
87 </div>";
88
89 return $html;
90 }
91
92 static function cardBigNumberProvider(array $params = [])
93 {
94 $default_params = [
95 'label' => null,
96 'icon' => null,
97 ];
98 $params = array_merge($default_params, $params);
99
100 return [
101 'number' => rand(),
102 'url' => "https://www.linux.org/",
103 'label' => "plugin example - some text",
104 'icon' => "fab fa-linux", // font awesome icon
105 ];
106 }
107}
A few explanations on those methods:
getTypes()
: define available widgets for cards and methods to call for display.getCards()
: define available cards for dashboards (when added to the grid). As previously explained, each is defined from a label, widget and optional data provider (from core or your plugin) combinationcardWidget()
: use provided parameters to display HTML. You are free to delegate display to a Twig template, and use your favourite JavaScript library.cardWidgetWithoutProvider()
: almost the same as thecardWidget()
, but does not use parameters and just returns a static HTML.cardBigNumberProvider()
: provider and expected return example when grid will display card.
Display your own dashboardยถ
GLPI dashboards system is modular and you can use it in your own displays.
1<?php
2
3use Glpi\Dashboard\Grid;
4
5$dashboard = new Grid('myplugin_example_dashboard', 10, 10, 'myplugin');
6$dashboard->show();
By adding a context (myplugin
), you can filter dashboards available in the dropdown list at the top right of the grid. You will not see GLPI core ones (central, assistance, etc.).
Translating your pluginsยถ
In many places in current document, code exmaples takes care of using gettext GLPI notations to display strings to users. Even if your plugin will be private, it is a good practice to keep this gettext usage.
See developper guide translation documentation for more explanations and list of PHP functions that can be used.
On your local instance, you can use software like poedit to manage your translations.
You can also rely on online services like Transifex or Weblate (both are free for open source projects).
If you have used the Empty plugin skeleton, you will benefit from command line tools to manage your locales:
# extract strings to translate from your source code
# and put them in the locales/myplugin.pot file
vendor/bin/extract-locales
Warning
โน๏ธ It is possible your translations are not updated after compiling MO files, a restart of your PHP (or web server, depending on your configuration) may be required.
REST APIยถ
Since GLPI (since 9.1 release) has an external API in REST format. An XMLRPC format is also still available, but is deprecated.

Configurationยถ
For security reasons, API is disabled bu default.
From the Setup > General, API tab
menu, you can enable it.
Itโs available from your instance at:
http://path/to/glpi/apirest.php
http://path/to/glpi/apixmlrpc.php
The first link includes an integrated documentation when you access it from a simple browser (a link is provided as soon as the API is active).
For the rest of the configuration:
login allows to use
login
/password
as well as web interfacetoken connection use the token displayed in user preferences
API clients allow to limit API access from some IP addresses and log if necessary. A client allowing access from any IP is provided by default.
You can use the API usage bootstrap. This one is written in PHP and relies on Guzzle library to handle HTTP requests.
By default, it does a connection with login details defined in the config.inc.php
file (that you must create by copying the config.inc.example
file).
Warning
โ ๏ธ Make sure the script is working as expected before continuing.
API usageยถ
To learn this part, with the help of integrated documentation (or latest stable GLPI API documentation on github), we will do several exercises:
Note
๐ Exercise: Test a new connection using GLPI user external token
Note
๐ Exercise: Close the session at the end of your script.
Note
๐ Exercise: Simulate computer life cycle:
add a computer and some volumes (
Item_Disk
),edit several fields,
add commercial and administrative information (
Infocom
),display its detail in a PHP page,
put it in the trashbin,
and then remove it completely.
Note
๐ Exercise: Retrieve computers list and display them an HTML array. The endpoint to use us โSearch itemsโ. If you want to display columns labels, you will have to use the โList searchOptionsโ endpoint.