======== Plugin Manager ========
{{ :en:entwickler:plugin_manager_overview.png?direct |}}
The Plugin Manager module provides a simple user interface and standardized procedures for
* installing,
* uninstalling,
* updating,
* managing and
* integrating
plugins in Admidio.
======= Make your plugin compatible with the Plugin Manager (Admidio 5) =======
In order to make your plugin compatible with the Plugin Manager certain requirements must first be met.
These include:
* [[en:entwickler:plugin_manager#naming_conventions|Naming conventions]]
* A consistent [[en:entwickler:plugin_manager#folder_structure|folder structure]]
* The use of the new plugin namespace //Plugins//
* A [[en:entwickler:plugin_manager#plugin_class|plugin class]]
* A JSON [[en:entwickler:plugin_manager#configuration_file|configuration file]]
====== Naming conventions ======
In order to use the new plugin namespace //Plugins// some naming conventions must be observed:
- Neither any folder of the plugin nor any file used by the plugin may contain delimiters like //"-"// or //"_"// especially if it should be used via a class import. For consistency reasons only the optional folder //db_scripts// (see: [[en:entwickler:plugin_manager#folder_structure|folder structure]]) should be an exception from this rule.
- For better readability file and folder names containing multiple words should follow the CamelCase notation.
====== Folder structure ======
The basic folder structure of any plugin is shown below using the overview plugin "Birthday" as example:
{{:en:entwickler:folder_structure_birthday.png?direct&400|}}
===== classes folder =====
==== Presenter classes ====
=== Plugin-specific classes ===
Inside the Presenter folder all necessary PagePresenter classes of the plugin took place. These should be simmilar to the module-specific PagePresenter classes.
=== Plugin preferences class ===
If the plugin provides preferences there should be a class //"[PluginName]**PreferencesPresenter**"// present. This class contains a single static preference method following the naming conventions of the Preferences class of Admidio.
Below an example snippet of the //createBirthdayForm// preference method from the "Birthday" overview plugin:
public static function createBirthdayForm(Smarty $smarty): string
{
global $gL10n, $gCurrentSession;
$pluginBirthday = Birthday::getInstance();
$formValues = $pluginBirthday::getPluginConfig();
$formBirthday = new FormPresenter(
'adm_preferences_form_birthday',
$pluginBirthday::getPluginPath() . '/templates/preferences.plugin.birthday.tpl',
SecurityUtils::encodeUrl(ADMIDIO_URL . FOLDER_MODULES . '/preferences.php', array('mode' => 'save', 'panel' => 'birthday')),
null,
array('class' => 'form-preferences')
);
$selectBoxEntries = array(
'0' => $gL10n->get('SYS_DISABLED'),
'1' => $gL10n->get('SYS_ENABLED'),
'2' => $gL10n->get('ORG_ONLY_FOR_REGISTERED_USER')
);
$formBirthday->addSelectBox(
'birthday_plugin_enabled',
Language::translateIfTranslationStrId($formValues['birthday_plugin_enabled']['name']),
$selectBoxEntries,
array(
'defaultValue' => $formValues['birthday_plugin_enabled']['value'],
'showContextDependentFirstEntry' => false,
'helpTextId' => $formValues['birthday_plugin_enabled']['description']
)
);
$formBirthday->addCheckbox(
'birthday_show_names_extern',
Language::translateIfTranslationStrId($formValues['birthday_show_names_extern']['name']),
$formValues['birthday_show_names_extern']['value'],
array('helpTextId' => $formValues['birthday_show_names_extern']['description'])
);
[...]
$formBirthday->addSubmitButton(
'adm_button_save_birthday',
$gL10n->get('SYS_SAVE'),
array('icon' => 'bi-check-lg', 'class' => 'offset-sm-3')
);
$formBirthday->addToSmarty($smarty);
$gCurrentSession->addFormObject($formBirthday);
return $smarty->fetch($pluginBirthday::getPluginPath() . '/templates/preferences.plugin.birthday.tpl');
}
==== Entity, Service and ValueObjects classes ====
If the plugin has specific Entity, Service or ValueObjects classes these should be placed in the corresponding subfolders as it is done with the Admidio modules.
==== Main plugin class ====
The main [[en:entwickler:plugin_manager#plugin_class|plugin class]] (e.g.: //Birthday//) has to take place directly in the classes folder.
===== db_scripts folder =====
This folder is optional and is used to place plugin-specific database scripts used when installing (//db-install.sql//) or uninstalling(//db-uninstall.sql//) the plugin.
In addition to possible database scripts update scripts (e.g.: //update_1_0.xml//) for the plugin could be placed in this folder. These should be structured like the update scripts used for Admidio to updateto a new version.
Simmilar to the update scripts used for Admidio it is possible to call plugin-specific update steps. To implement these methods a final class named //UpdateStepsCode// has to be present in the folder //"[PluginName]\classes\Service\"//.
The class implementation should look like this:
final class UpdateStepsCode
{
/**
* @var Database
*/
private static Database $db;
/**
* Set the database
* @param Database $database The database instance
*/
public static function setDatabase(Database $database)
{
self::$db = $database;
}
/**
* @throws Exception
*/
public static function /*[method name used in update_x_x.xml]*/()
{
[...]
}
}
===== languages folder =====
This folder contains the plugin specific translation files as before.
===== templates folder =====
To move away from the deprecated classes HtmlPage and HtmlTable plugins should exclusively use the Smarty template engine whose files are stored in this folder.
===== Root plugin folder =====
==== Main plugin file ====
Inside the root plugin folder a main plugin file is necessary. This file can contain plugin specific logic like the module entry files of Admidio or simply initialize the [[en:entwickler:plugin_manager#plugin_class|plugin class]] like following example:
doRender(isset($page) ? $page : null);
} catch (Throwable $e) {
echo $e->getMessage();
}
==== Plugin configuration file ====
Each plugin has to provide a JSON configuration file containing basic plugin informations and configurations (see: [[en:entwickler:plugin_manager#configuration_file|Configuration file]]).
====== Configuration file ======
Each plugin has o provide a JSON configuration file. The following table gives an overview of all keys currently available:
^ Key: ^ Mandatory: ^ Description: ^
| "name" | X | Defines the plugin name (can be a translation ID or plain text). |
| "description" | | A short description of the plugin shown on the Plugin Manager overview (can be a translation ID or plain text). |
| "version" | X | The current version of the plugin. |
| "author" | | The author of the plugin. |
| "url" | | A URL to a Wiki page or a projekt page. |
| "icon" | | The icon displayed in the Admidio sidebar and on the Plugin Manager overview. |
| "mainFile" | | The filename of the main plugin entry point. If not set "index.php" or "PluginName.php" is assumed. |
| "dependencies" | (X) |An array of all dependencies the plugin needs to work properly. If this is not set the Plugin Manager isn't able to detect whether the plugin can be installed properly. |
| "defaultConfig" | (X) | An object containing all configuration parameters used by the plugin. Every parameter needs to contain a "name", "descprition", "type" and "value" key. |
The following example shows the structure based on the overview plugin Birthday:
{
"name": "PLG_BIRTHDAY_PLUGIN_NAME",
"description": "PLG_BIRTHDAY_PLUGIN_DESCRIPTION",
"version": "1.0.0",
"author": "Admidio Team",
"url": "https://www.admidio.org",
"icon": "bi-cake2",
"mainFile": "index.php",
"dependencies": [
"Admidio\\Infrastructure\\Plugins\\Overview",
"Admidio\\Infrastructure\\Plugins\\PluginAbstract",
"Admidio\\Infrastructure\\Utils\\SecurityUtils",
"Admidio\\Roles\\Service\\RolesService"
],
"defaultConfig": {
"birthday_plugin_enabled": {
"name": "ORG_ACCESS_TO_PLUGIN",
"description": "ORG_ACCESS_TO_PLUGIN_DESC",
"type": "integer",
"value" : 1
},
"birthday_show_names_extern": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_NAMES_EXTERN",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_NAMES_EXTERN_DESC",
"type": "boolean",
"value" : false
},
"birthday_show_names": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_NAMES",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_NAMES_DESC",
"type": "integer",
"value" : 0
},
"birthday_show_age": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_AGE",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_AGE_DESC",
"type": "boolean",
"value" : false
},
"birthday_show_age_salutation": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_AGE_SALUTATION",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_AGE_SALUTATION_DESC",
"type": "integer",
"value" : 18
},
"birthday_show_notice_none": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_NOTICE_NONE",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_NOTICE_NONE_DESC",
"type": "boolean",
"value" : true
},
"birthday_show_past": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_PAST",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_PAST_DESC",
"type": "integer",
"value" : 1
},
"birthday_show_future": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_FUTURE",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_FUTURE_DESC",
"type": "integer",
"value" : 2
},
"birthday_show_display_limit": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_DISPLAY_LIMIT",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_DISPLAY_LIMIT_DESC",
"type": "integer",
"value" : 200
},
"birthday_show_email_extern": {
"name": "PLG_BIRTHDAY_PREFERENCES_SHOW_EMAIL_EXTERN",
"description": "PLG_BIRTHDAY_PREFERENCES_SHOW_EMAIL_EXTERN_DESC",
"type": "integer",
"value" : 0
},
"birthday_roles_view_plugin": {
"name": "PLG_BIRTHDAY_PREFERENCES_ROLES_VIEW_PLUGIN",
"description": "PLG_BIRTHDAY_PREFERENCES_ROLES_VIEW_PLUGIN_DESC",
"type": "array",
"value" : ["All"]
},
"birthday_roles_sql": {
"name": "PLG_BIRTHDAY_PREFERENCES_ROLES_SQL",
"description": "PLG_BIRTHDAY_PREFERENCES_ROLES_SQL_DESC",
"type": "array",
"value" : ["All"]
},
"birthday_sort_sql": {
"name": "PLG_BIRTHDAY_PREFERENCES_SORT_SQL",
"description": "PLG_BIRTHDAY_PREFERENCES_SORT_SQL_DESC",
"type": "string",
"value" : "DESC"
}
}
}
====== Plugin class ======
The Plugin class extends the basic functionality provided by the abstract class //PluginAbstract// and implements the //doRender// method. In it's simplest implementation the //doRender// method could just call the PagePresenter //show// method:
public static function doRender(?PagePresenter $page = null) : bool
{
if (!isset($page)) {
throw new Exception("The page parameter must be set.");
}
$page->show();
return true;
}
All methods provided by the //PluginAbstract// can be overwritten in this plugin class and adapted to the plugin needs. The following code shows an example overwriting of the //getPluginConfig()// method in the "Birthday" overview plugin implementation:
public static function getPluginConfig() : array
{
// get the plugin config from the parent class
$config = parent::getPluginConfig();
// if the key equals 'birthday_roles_view_plugin' and the value is still the default value, retrieve the categories from the database
if (array_key_exists('birthday_roles_view_plugin', $config)
&& $config['birthday_roles_view_plugin']['value'] === self::$defaultConfig['birthday_roles_view_plugin']['value']) {
$config['birthday_roles_view_plugin']['value'] = self::getAvailableRoles(1, true);
}
if (array_key_exists('birthday_roles_sql', $config)
&& $config['birthday_roles_sql']['value'] === self::$defaultConfig['birthday_roles_sql']['value']) {
$config['birthday_roles_sql']['value'] = self::getAvailableRoles(1, true);
}
return $config;
}
======= PluginAbstract class =======
methods documentation