Actions

Plugins - advanced: Difference between revisions

From LimeSurvey Manual

m (Added translate-tags)
(Marked this version for translation)
(41 intermediate revisions by 3 users not shown)
Line 1: Line 1:
<languages /><translate>
<languages /><translate>
== Overview ==


== Overview == <!--T:1-->
<!--T:2-->
Starting from LimeSurvey 2.05, LimeSurvey will officially support plugins. Some plugins will be supported by the LimeSurvey team and will go into core. Some will be supported by others outside the LimeSurvey team. To help find them, check out the [[Available third party plugins]] and add your own plugin to it!
Starting from LimeSurvey 2.05, LimeSurvey will officially support plugins. Some plugins will be supported by the LimeSurvey team and will go into core. Some will be supported by others outside the LimeSurvey team. To help find them, check out the [[Available third party plugins]] and add your own plugin to it!


<!--T:3-->
Plugins allow users to customize the functionality of their installation while still being able to benefit from regular software updates.
Plugins allow users to customize the functionality of their installation while still being able to benefit from regular software updates.


<!--T:4-->
This documentation is meant for developers that are extending LimeSurvey for their own use or for their clients; end users will not be helped by this documentation.
This documentation is meant for developers that are extending LimeSurvey for their own use or for their clients; end users will not be helped by this documentation.


<!--T:5-->
Plugins must implement the [https://github.com/LimeSurvey/LimeSurvey/blob/master/application/libraries/PluginManager/iPlugin.php iPlugin] interface. We recommend extending your plugin class from the [https://github.com/LimeSurvey/LimeSurvey/blob/master/application/libraries/PluginManager/PluginBase.php PluginBase] class.  
Plugins must implement the [https://github.com/LimeSurvey/LimeSurvey/blob/master/application/libraries/PluginManager/iPlugin.php iPlugin] interface. We recommend extending your plugin class from the [https://github.com/LimeSurvey/LimeSurvey/blob/master/application/libraries/PluginManager/PluginBase.php PluginBase] class.  


<!--T:6-->
Plugins are developed around an [http://manual.limesurvey.org/Plugin_events event] mechanism.
Plugins are developed around an [http://manual.limesurvey.org/Plugin_events event] mechanism.


== Plugin settings ==
== Plugin settings == <!--T:7-->


<!--T:8-->
By extending you benefit from common functionality required by plugins that we already have implemented for you. One of these function is the implementation of the getPluginSettings function. This function must return an array describing the configuration options for the user.
By extending you benefit from common functionality required by plugins that we already have implemented for you. One of these function is the implementation of the getPluginSettings function. This function must return an array describing the configuration options for the user.


<!--T:9-->
The example plugin exposes just 1 configurable setting, the message it'll show.
The example plugin exposes just 1 configurable setting, the message it'll show.


<!--T:10-->
<syntaxhighlight lang="php" enclose="div">
<syntaxhighlight lang="php" enclose="div">
protected $settings = array(
protected $settings = array(
Line 25: Line 34:
      ),
      ),


<!--T:11-->
     'message' => array(
     'message' => array(
         'type' => 'string',
         'type' => 'string',
Line 32: Line 42:
</syntaxhighlight>
</syntaxhighlight>


<!--T:12-->
The array contains a name for each setting as a key. The values are arrays containing the required meta data.
The array contains a name for each setting as a key. The values are arrays containing the required meta data.


<!--T:13-->
Supported types are:
Supported types are:


<!--T:14-->
* logo
* logo
* string
* string
Line 43: Line 56:
* info
* info


<!--T:15-->
Besides type a number of other keys are available:
Besides type a number of other keys are available:


* label, defines a label (use English, the label specified here will be passed through the translation functions)
<!--T:16-->
* default, defines a value to show if no value is specified.
* label, defines a label  
* default, defines a value to show if no value is specified (only for global settings, not for survey settings)
* current, defines the current value.
* current, defines the current value.
* readOnly, specifies the setting is read only.
* readOnly : shown the settings as readonly
* htmlOptions, the htmlOptions of the input part ( see Yii manual [[https://www.yiiframework.com/doc/api/1.1/CHtml]])
* pluginOptions, for some settings (html or select) : set the widget option
* labelOptions : htmlOptions of the label
* controlOptions : htmlOptions of the wrapper of label and input


You can find a plugin example using all actual settings at [https://framagit.org/Shnoulle/exampleSettings exampleSettings]  
You can find a plugin example using all actual settings at [https://framagit.org/Shnoulle/exampleSettings exampleSettings]  
=== Read and write plugin settings ===
=== Read and write plugin settings === <!--T:17-->


<!--T:18-->
It's possible to read and write plugin settings directly from your plugin code.
It's possible to read and write plugin settings directly from your plugin code.


<!--T:19-->
Example:
Example:


<!--T:20-->
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
$mySetting = $this->get('mySetting');
$mySetting = $this->get('mySetting');
Line 62: Line 84:
</syntaxhighlight>
</syntaxhighlight>


<!--T:21-->
You can get a default value if the setting happens to be null:
You can get a default value if the setting happens to be null:


<!--T:22-->
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
$mySetting = $this->get('mySetting', null, null, 10);  // 10 is default
$mySetting = $this->get('mySetting', null, null, 10);  // 10 is default
</syntaxhighlight>
</syntaxhighlight>


== Events ==
== Events == <!--T:23-->


<!--T:24-->
Plugins subscribe to events and can interact with LimeSurvey when the event is fired. For a list of currently available events check [[Plugin events]].
Plugins subscribe to events and can interact with LimeSurvey when the event is fired. For a list of currently available events check [[Plugin events]].


== API ==
== API == <!--T:25-->


<!--T:26-->
Plugins should only extend LimeSurvey via its "public" API. This means that directly using classes found in the source code is a bad practice. Though we can't force you not to, you risk having a broken plugin with every minor update we do.
Plugins should only extend LimeSurvey via its "public" API. This means that directly using classes found in the source code is a bad practice. Though we can't force you not to, you risk having a broken plugin with every minor update we do.


As much as possible interact with LimeSurvey only via methods described [http://api.limesurvey.org/classes/ls.pluginmanager.LimesurveyApi.html here]. Same as for events.
<!--T:27-->
As much as possible interact with LimeSurvey only via methods described [http://api.limesurvey.org/classes/LimeSurvey.PluginManager.LimesurveyApi.html here]. Same as for events.


<!--T:28-->
The API object is available via <code>$this->api</code> when extending from PluginBase, otherwise you can get it from the PluginManager instance that is passed to your plugins' constructor.
The API object is available via <code>$this->api</code> when extending from PluginBase, otherwise you can get it from the PluginManager instance that is passed to your plugins' constructor.


<!--T:29-->
New functions can be added to the API object upon request.
New functions can be added to the API object upon request.


== Localization (Not yet in production) ==
== Localization {{NewIn|v=3}} == <!--T:30-->


<!--T:31-->
It's possible for plugins to add their own locale files. File format used is .mo, same as core translations. The files must be stored in  
It's possible for plugins to add their own locale files. File format used is .mo, same as core translations. The files must be stored in  


  <plugin root folder>/locale/<language>/<language>.mo
  <!--T:32-->
<plugin root folder>/locale/<language>/<language>.mo


<!--T:33-->
where "<language>" is a two letter word like "de" or "fr".
where "<language>" is a two letter word like "de" or "fr".


<!--T:34-->
To use the specific locale file, use the plugin function gT:
To use the specific locale file, use the plugin function gT:


<!--T:35-->
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
$this->gT("A plugin text that needs to be translated");
$this->gT("A plugin text that needs to be translated");
</syntaxhighlight>
</syntaxhighlight>


<!--T:36-->
If the given string can't be found in the plugin specific locale file, the function will look in the core locale files. So it's safe to use strings like "Cancel":
If the given string can't be found in the plugin specific locale file, the function will look in the core locale files. So it's safe to use strings like "Cancel":


<!--T:37-->
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
$this->gT("Cancel");  // Will be translated even if "Cancel" is not in the plugin locale file
$this->gT("Cancel");  // Will be translated even if "Cancel" is not in the plugin locale file
</syntaxhighlight>
</syntaxhighlight>


<!--T:38-->
If you are using views together with your plugin, you should use
If you are using views together with your plugin, you should use


<!--T:39-->
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
$plugin->gT("Translate me");
$plugin->gT("Translate me");
</syntaxhighlight>
</syntaxhighlight>


<!--T:40-->
to do plugin specific translation in your view.
to do plugin specific translation in your view.


== Logging (Not yet in production) ==
== Logging {{NewIn|v=3}}  == <!--T:41-->


<!--T:42-->
If you want to log something from your plugin, just write
If you want to log something from your plugin, just write


<!--T:43-->
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
$this->log("Your message");
$this->log("Your message");
</syntaxhighlight>
</syntaxhighlight>


<!--T:44-->
The default logging level is trace, but you can give another log level as an optional second argument:
The default logging level is trace, but you can give another log level as an optional second argument:


<!--T:45-->
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
$this->log("Something went wrong!", CLogger::LEVEL_ERROR);
$this->log("Something went wrong!", CLogger::LEVEL_ERROR);
</syntaxhighlight>
</syntaxhighlight>


<!--T:46-->
The log file can be found in folder
The log file can be found in folder


  <limesurvey root folder>/tmp/runtime/plugin.log
  <!--T:47-->
<limesurvey root folder>/tmp/runtime/plugin.log


<!--T:48-->
Your plugin name is automatically used as category. A nice way to see only the errors from your plugin is using grep (on Linux):
Your plugin name is automatically used as category. A nice way to see only the errors from your plugin is using grep (on Linux):


  $ tailf tmp/runtime/plugin.log | grep <your plugin name>
  <!--T:49-->
$ tailf tmp/runtime/plugin.log | grep <your plugin name>
 
== Extension updates {{NewIn|v=4}} == <!--T:55-->
 
<!--T:56-->
Since LimeSurvey version 4.0.0, there's a system in place to deal with plugin and other extension updates. To use this system, your extension config.xml file needs to include updater configuration.
 
<!--T:57-->
<syntaxhighlight lang="xml">
<updaters>
    <updater>
        <stable>1</stable>
        <type>rest</type>
        <source>https://comfortupdate.limesurvey.org/index.php?r=limestorerest</source>
        <manualUpdateUrl>https://somedownloadlink.com/maybegithub</manualUpdateUrl>
    </updater>
</updaters>
</syntaxhighlight>
 
<!--T:58-->
(The source tag above points to the LimeStore REST API, which will be used for all extensions available in our LimeStore.)
 
<!--T:59-->
{| class="wikitable"
|+ Updater tag descriptions
|-
! Tag
! Description
|-
| stable
| "1" if this source only gives you stable version numbers; "0" if the source will also provide unstable versions, like <code>0.3.3-beta</code>.
|-
| type
| For now, only type <code>rest</code> is supported. It's easy to add new updater types (version checkers), like git, wget, etc.
|-
| source
| The URL to fetch new versions from.
|-
| manualUpdateUrl
| URL which the user can go to to update the latest version of the extension.
|-
| automaticUpdateUrl
| TODO
|}
 
<!--T:60-->
If you don't want to supply an updater, you should put the following text in your config XML file:
 
<!--T:61-->
<syntaxhighlight lang="xml">
<updaters disabled="disabled">
</updaters>
</syntaxhighlight>
 
<!--T:62-->
This way, you tell the system that you purposefully disabled the update system, and didn't just forget to add it.
 
<!--T:63-->
The new plugin '''UpdateCheck''' - installed and activated by default - checks for new updates for ''all'' installed extensions when a super admin logs in, asynchronously, max one time every 24 hours. If any new versions are found, a notification is pushed.
 
<!--T:64-->
[[File:availableupdates.png||Available updates]]
 
<!--T:65-->
If a new security update is found, the notification will open automatically and be styled in "danger" class.
 
<!--T:66-->
[[File:availablesecurityupdates.png||Available security updates]]
 
<!--T:67-->
You can manually check for updates by going to the plugin manager view and click on "Check updates". Note that this button is only visible if the UpdateCheck plugin is activated.
 
<!--T:68-->
[[File:manuallycheckforupdates.png||Manually check for updates]]
 
=== Under the hood === <!--T:69-->
 
<!--T:70-->
This section provides a brief overview over the extension updater implementation.
 
<!--T:71-->
The extension updater is part of the ExtensionInstaller library. Below is a UML diagram for the classes related to the updater process.
 
<!--T:72-->
[[File:extensionupdateruml.png||Extension updater UML diagram]]
 
<!--T:73-->
Program flow when Yii starts:
 
<!--T:74-->
<syntaxhighlight lang="txt">
Yii init
  VersionFetcherServiceLocator->init()
    Add REST version fetcher
  ExtensionUpdaterServiceLocator->init()
    Add PluginUpdater
    TODO: Add an updater for each extension type (theme, question template, ...)
</syntaxhighlight>
 
<!--T:75-->
Program flow when running the UpdaterCheck plugin:
 
<!--T:76-->
<syntaxhighlight lang="txt">
Get all updaters from ExtensionUpdaterServiceLocator
Loop each updater
  For each updater, loop through version fetchers configured by <updater> XML
    For each version fetcher, contact remote source and get version information
Compose all versions into a notification
</syntaxhighlight>
 
<!--T:77-->
The [https://github.com/LimeSurvey/LimeSurvey/blob/develop/application/core/plugins/UpdateCheck/UpdateCheck.php#L130 checkAll] method in the UpdateCheck plugin provides an example of how to query all extensions for new versions.
 
==== Adding new version fetchers ==== <!--T:78-->
 
<!--T:79-->
To add a new custom version fetcher, run this during Yii initialization:
 
<!--T:80-->
<syntaxhighlight lang="php">
$service = \Yii::app()->versionFetcherServiceLocator
$service->addVersionFetcherType(
  'myNewVersionFetcherType',
  function (\SimpleXMLElement $updaterXml) {
    return new MyNewVersionFetcher($updaterXml);
  }
);
</syntaxhighlight>
 
<!--T:81-->
Of course, the class <code>MyNewVersionFetcher</code> has to subclass <code>VersionFetcher</code>.
 
<!--T:82-->
To use your new version fetcher, configure the <code>type</code> tag in the updater XML to use
<code>myNewVersionFetcherType</code> (instead of e.g. <code>rest</code>).
 
==== Adding new extension updaters ==== <!--T:83-->
 
<!--T:84-->
To add a new custom extension updater, run this during Yii initialization:
 
<!--T:85-->
<syntaxhighlight lang="php">
$service = \Yii::app()->extensionUpdaterServiceLocator;
$service->addUpdaterType(
  'myNewExtensionUpdater',
  function () {
    return MyNewExtensionUpdater::createUpdaters();
  }
);
</syntaxhighlight>
 
<!--T:86-->
Class <code>MyNewExtensionUpdater</code> has to subclass <code>ExtensionUpdater</code>.
 
<!--T:87-->
The top <code>type</code> tag in config.xml ('plugin', 'theme', ...) will control which extension updater are used for this extension. The system is not fully customizable  yet, since you also need to add a custom ExtensionInstaller, menu items, etc. But in theory, and maybe in the future, it should be possible to add a new type of extension this way.


== Special plugins ==
== Special plugins == <!--T:50-->


<!--T:51-->
* [[Authentication plugin development]]
* [[Authentication plugin development]]
* [[Export plugin development]]
* [[Export plugin development]]


== Available plugins ==
== Available plugins == <!--T:52-->
* [[Authentication plugins]]
* [[Authentication plugins]]
* [[Audit log]]
* [[Audit log]]
Line 143: Line 348:
* [[Available third party plugins]]
* [[Available third party plugins]]


== Tutorial ==
== Tutorial == <!--T:53-->
[https://medium.com/@evently/creating-limesurvey-plugins-adcdf8d7e334 This] step-by-step tutorial shows how to create a plugin that sends a post request on every survey response submission. The tutorial shows you how to create and save global and per-survey settings, how to register events and more.
[https://medium.com/@evently/creating-limesurvey-plugins-adcdf8d7e334 This] step-by-step tutorial shows how to create a plugin that sends a post request on every survey response submission. The tutorial shows you how to create and save global and per-survey settings, how to register events and more.


<!--T:54-->
[[Category:Development]]
[[Category:Development]]
[[Category:Plugins]]
[[Category:Plugins]]
</translate>
</translate>

Revision as of 20:39, 17 October 2018

Overview

Starting from LimeSurvey 2.05, LimeSurvey will officially support plugins. Some plugins will be supported by the LimeSurvey team and will go into core. Some will be supported by others outside the LimeSurvey team. To help find them, check out the Available third party plugins and add your own plugin to it!

Plugins allow users to customize the functionality of their installation while still being able to benefit from regular software updates.

This documentation is meant for developers that are extending LimeSurvey for their own use or for their clients; end users will not be helped by this documentation.

Plugins must implement the iPlugin interface. We recommend extending your plugin class from the PluginBase class.

Plugins are developed around an event mechanism.

Plugin settings

By extending you benefit from common functionality required by plugins that we already have implemented for you. One of these function is the implementation of the getPluginSettings function. This function must return an array describing the configuration options for the user.

The example plugin exposes just 1 configurable setting, the message it'll show.

protected $settings = array(
    'logo' => array(
          'type' => 'logo',
          'path' => 'assets/logo.png'
     ),

     'message' => array(
          'type' => 'string',
          'label' => 'Message'
     )
);

The array contains a name for each setting as a key. The values are arrays containing the required meta data.

Supported types are:

  • logo
  • string
  • html
  • choice
  • relevance
  • info

Besides type a number of other keys are available:

  • label, defines a label
  • default, defines a value to show if no value is specified (only for global settings, not for survey settings)
  • current, defines the current value.
  • readOnly : shown the settings as readonly
  • htmlOptions, the htmlOptions of the input part ( see Yii manual [[1]])
  • pluginOptions, for some settings (html or select) : set the widget option
  • labelOptions : htmlOptions of the label
  • controlOptions : htmlOptions of the wrapper of label and input

You can find a plugin example using all actual settings at exampleSettings

Read and write plugin settings

It's possible to read and write plugin settings directly from your plugin code.

Example:

$mySetting = $this->get('mySetting');
$this->set('mySetting', $mySetting + 1);

You can get a default value if the setting happens to be null:

$mySetting = $this->get('mySetting', null, null, 10);  // 10 is default

Events

Plugins subscribe to events and can interact with LimeSurvey when the event is fired. For a list of currently available events check Plugin events.

API

Plugins should only extend LimeSurvey via its "public" API. This means that directly using classes found in the source code is a bad practice. Though we can't force you not to, you risk having a broken plugin with every minor update we do.

As much as possible interact with LimeSurvey only via methods described here. Same as for events.

The API object is available via $this->api when extending from PluginBase, otherwise you can get it from the PluginManager instance that is passed to your plugins' constructor.

New functions can be added to the API object upon request.

Localization (New in 3 )

It's possible for plugins to add their own locale files. File format used is .mo, same as core translations. The files must be stored in

<plugin root folder>/locale/<language>/<language>.mo

where "<language>" is a two letter word like "de" or "fr".

To use the specific locale file, use the plugin function gT:

$this->gT("A plugin text that needs to be translated");

If the given string can't be found in the plugin specific locale file, the function will look in the core locale files. So it's safe to use strings like "Cancel":

$this->gT("Cancel");  // Will be translated even if "Cancel" is not in the plugin locale file

If you are using views together with your plugin, you should use

$plugin->gT("Translate me");

to do plugin specific translation in your view.

Logging (New in 3 )

If you want to log something from your plugin, just write

$this->log("Your message");

The default logging level is trace, but you can give another log level as an optional second argument:

$this->log("Something went wrong!", CLogger::LEVEL_ERROR);

The log file can be found in folder

<limesurvey root folder>/tmp/runtime/plugin.log

Your plugin name is automatically used as category. A nice way to see only the errors from your plugin is using grep (on Linux):

$ tailf tmp/runtime/plugin.log | grep <your plugin name>

Extension updates (New in 4 )

Since LimeSurvey version 4.0.0, there's a system in place to deal with plugin and other extension updates. To use this system, your extension config.xml file needs to include updater configuration.

<updaters>
    <updater>
        <stable>1</stable>
        <type>rest</type>
        <source>https://comfortupdate.limesurvey.org/index.php?r=limestorerest</source>
        <manualUpdateUrl>https://somedownloadlink.com/maybegithub</manualUpdateUrl>
    </updater>
</updaters>

(The source tag above points to the LimeStore REST API, which will be used for all extensions available in our LimeStore.)

Updater tag descriptions
Tag Description
stable "1" if this source only gives you stable version numbers; "0" if the source will also provide unstable versions, like 0.3.3-beta.
type For now, only type rest is supported. It's easy to add new updater types (version checkers), like git, wget, etc.
source The URL to fetch new versions from.
manualUpdateUrl URL which the user can go to to update the latest version of the extension.
automaticUpdateUrl TODO

If you don't want to supply an updater, you should put the following text in your config XML file:

<updaters disabled="disabled">
</updaters>

This way, you tell the system that you purposefully disabled the update system, and didn't just forget to add it.

The new plugin UpdateCheck - installed and activated by default - checks for new updates for all installed extensions when a super admin logs in, asynchronously, max one time every 24 hours. If any new versions are found, a notification is pushed.

Available updates

If a new security update is found, the notification will open automatically and be styled in "danger" class.

Available security updates

You can manually check for updates by going to the plugin manager view and click on "Check updates". Note that this button is only visible if the UpdateCheck plugin is activated.

Manually check for updates

Under the hood

This section provides a brief overview over the extension updater implementation.

The extension updater is part of the ExtensionInstaller library. Below is a UML diagram for the classes related to the updater process.

Extension updater UML diagram

Program flow when Yii starts:

 Yii init
   VersionFetcherServiceLocator->init()
     Add REST version fetcher
   ExtensionUpdaterServiceLocator->init()
     Add PluginUpdater
     TODO: Add an updater for each extension type (theme, question template, ...)

Program flow when running the UpdaterCheck plugin:

 Get all updaters from ExtensionUpdaterServiceLocator
 Loop each updater
   For each updater, loop through version fetchers configured by <updater> XML
     For each version fetcher, contact remote source and get version information
 Compose all versions into a notification

The checkAll method in the UpdateCheck plugin provides an example of how to query all extensions for new versions.

Adding new version fetchers

To add a new custom version fetcher, run this during Yii initialization:

$service = \Yii::app()->versionFetcherServiceLocator
$service->addVersionFetcherType(
  'myNewVersionFetcherType',
  function (\SimpleXMLElement $updaterXml) {
    return new MyNewVersionFetcher($updaterXml);
  }
);

Of course, the class MyNewVersionFetcher has to subclass VersionFetcher.

To use your new version fetcher, configure the type tag in the updater XML to use myNewVersionFetcherType (instead of e.g. rest).

Adding new extension updaters

To add a new custom extension updater, run this during Yii initialization:

$service = \Yii::app()->extensionUpdaterServiceLocator;
$service->addUpdaterType(
  'myNewExtensionUpdater',
  function () {
    return MyNewExtensionUpdater::createUpdaters();
  }
);

Class MyNewExtensionUpdater has to subclass ExtensionUpdater.

The top type tag in config.xml ('plugin', 'theme', ...) will control which extension updater are used for this extension. The system is not fully customizable yet, since you also need to add a custom ExtensionInstaller, menu items, etc. But in theory, and maybe in the future, it should be possible to add a new type of extension this way.

Special plugins

Available plugins

Tutorial

This step-by-step tutorial shows how to create a plugin that sends a post request on every survey response submission. The tutorial shows you how to create and save global and per-survey settings, how to register events and more.