ESMira plugins - KL-Psychological-Methodology/ESMira GitHub Wiki
ESMira supports plugins to extend its functionality. This allows adjusting the functionality of the ESMira server without having to modify the server source code itself.
[!CAUTION] In theory, plugins can leak, change or delete data on your ESMira server. They could also add malicious code to your server or break your ESMira installation. Please be aware of the risks when using plugins and only install plugins from trusted sources. Use plugins at your own risk.
[!CAUTION] New ESMira versions might break plugins. We cannot test new versions of ESMira against any existing plugin. So test your plugins after updating ESMira (or after updating plugins). And again, use plugins at your own risk.
Install a plugin
To install a new plugin, the account needs admin permissions. Navigate to the Plugins tab in the server settings and click Install new. You then have three options:
Install from web URL
Enter the URL to the metadata.json of a plugin and click Install. ESMira will download the metadata.json check for compatibility via minESMiraVersion and maxESMiraVersion and install the plugin from downloadUrl.
Install from file
Click Browse and select a zip file containing an ESMira plugin. When installing a plugin from a file, ESMira will NOT check for compatibility and just install / update the selected plugin.
Install from list
We maintain a list of plugins that are maintained by the ESMira community. If you want your plugin to be listed here, please contact us or create a pull request at https://github.com/KL-Psychological-Methodology/ESMira/pulls.
Create a plugin
[!NOTE] Creating a plugin for ESMira requires some knowledge of JavaScript and PHP. You might want to set up a local development environment to test your plugin before deploying it to the ESMira server.
[!NOTE] If you think your plugin can be useful for others, please consider publishing it by creating a pull request at https://github.com/KL-Psychological-Methodology/ESMira/pulls and adding an entry to plugins.json or share the plugin in the discussion forum.
An ESMira plugin is a zip file containing the following structure:
metadata.json
study.json
apiOverrides/
    [*.php]
locales/
    [*.json]
pluginApi/
    [*.php]
sections/
    [*.js]
(apiOverrides/, locales/, pluginApi/, sections/ and study.json are optional)
metadata.json
The metadata.json file is the heart of each plugin. It contains overall information about the plugin as well as data that is needed to update the plugin.
The allowed fields are:
name- The name of the plugin.version- The version of the plugin (in semVer format).downloadUrl- The URL where the zip file of this plugin can be downloaded from. Used to download updated versions of the plugin (ESMira first loads the newest metadata.json viametadataUrl, and ifversionis different, it extracts thedownloadUrlfrom the new metadata.json to download the zip file)metadataUrl- The URL to the metadata.json of the newest version of the plugin. Used to check for new versions of the plugin.description- Optional. A short description of the plugin.author- Optional. The author of the plugin.website- Optional. A URL to the website of the plugin.minESMiraVersion- Optional. A semVer string containing the minimum version of ESMira that is required to run the plugin.maxESMiraVersion- Optional. A semVer string containing the maximum version of ESMira that is required to run the plugin.
study.json - define additional study JSON data
When present, the JSON file of each study will be extended with a new entry in pluginData with the given structure.
The structure contains the default values for each field and has some limitations to be aware of:
- The defined value for each field is treated as its default value.
 - A starting 
$in the key name signals that the field is translatable. A translatable field will be different for each language version of a study, which means changes to the value will only be reflected in the current language version. Translatable fields can only contain strings or arrays of strings. - An array that contains an object does not have a default value and will be initialized as an empty array. The object is used as a blueprint for each added array element.
 - Arrays of arrays are not supported.
 
To access study plugin data use getStudyPluginData() in methods.
[!CAUTION] The extended JSON structure will ONLY be saved while the plugin is installed on the server. If you save the study while the plugin is not installed, all plugin data in the study JSON will be lost.
[!NOTE] Do not be confused when debugging: The resulting study JSON structure is filtered and only contains values that are different from the defined default values.
Example
{
    "value": 6,
    "value2": [],
    "value3": [true, false], // this array will be initialized with two default elements
    "$value4": "test", // this value is translatable and will be different for each language version of the study
    "$value5": ["test", "test2"], // a translatable array initialized with two default elements
    
    "value6": {
        "subValue1": 6,
        "subValue2": 2026,
    },
    "value7": [{ // an array of objects. By default the array will be empty. The object is the predefined structure for each array element
        "subValue1": 15,
        "subValue2": 0,
    }]
}
apiOverrides – extend existing API endpoints
To extend existing API endpoints, create a folder named apiOverrides in the plugin folder and create a file with the name of the endpoint you want to extend. The scope of the file has access to $helper which is a helper class (see https://github.com/KL-Psychological-Methodology/ESMira-web/blob/main/src/backend/PluginHelper.php) giving you access to the file system and the API data via getApiData(). The type and contents of the API data depend on the API endpoint.
The following endpoints can be extended:
datasets.php
Extends the endpoint for saving data.
getApiData() returns an array containing:
dataset- a two-dimensional array. Its first level keys are study ids, its second level keys are questionnaire ids (internalId) and values are the corresponding DataSetCacheContainer filled with request ids and content that should be saved.createDataSet- Access to the current CreateDataSet class instance.
file_upload.php
Extends the endpoint for uploading a file as part of study data (e.g. from an Image item).
getApiData() returns an array containing:
studyId- The study id for which the file is uploaded.userId- The user id of the uploader.identifier- this unique id of the file which will be saved in the data CSV to link the file to the data.uploader- the FileUploader object that is used for uploading the file.
save_message.php
Extends the endpoint for saving a study message.
getApiData() returns an array containing:
studyId- The study id to which the message is sent to.userId- The user id that sent the message.content- The content of the message.
save_merlin_log.php
Extends the endpoint for saving a merlin log.
getApiData() returns an array containing:
studyId- The study id to which the log is sent to.content- The content of the log.
save_errors.php
Extends the endpoint for saving n error log.
getApiData() returns an array containing:
content- The content of the log.
admin_SaveStudy.php
Extends the admin API for SaveStudy. getApiData()` returns an array containing:
studyCollection- an object containing the study JSON for each language. The main study JSON (the default language for the study) is found in$data->studyCollection->_.pluginStudyData- the plugin data for the current study which are also saved in the study JSON.
[!NOTE] Please create a new issue if you want to extend an endpoint that was not added yet.
pluginApi – add a new API endpoint
To add new API endpoints for your plugin, create a folder named pluginApi in the plugin folder and create a php file with the name of the endpoint you want to add. The scope of the file has access to $helper which is a helper class (see https://github.com/KL-Psychological-Methodology/ESMira-web/blob/main/src/backend/PluginHelper.php) giving you access to the file system.
The endpoint will be available under www.example.com/api/plugin_api.php?plugin=[name-of-the-plugin]&api=[name-of-the-endpoint].
Example
After creating MyPlugin/pluginApi/new_api.php, you can access the endpoint under www.example.com/api/plugin_api.php?plugin=MyPlugin&api=new_api.
sections/ – manipulate page sections
[!CAUTION] When adding new elements, you must use
createElementprovided by themethodsobject. ThecreateElementmethod ensures that the element is cleaned up when the section is changed. Without this, the element might be left behind when the user navigates to another section that is structurally similar.
The sections folder contains one js file for each section that should be extended or created. The file name needs to either match the section name in ESMira (https://github.com/KL-Psychological-Methodology/ESMira-web/blob/main/src/frontend/ts/sections) to override an existing section or will be treated as a new section. The file needs to export default a function (which can be async) that returns an object with multiple optional callback functions (see PluginFrontend in https://github.com/KL-Psychological-Methodology/ESMira-web/blob/main/src/frontend/ts/site/PluginInterfaces.ts).
The function gets several parameters:
methods
Several convenience methods. See PluginMethods in https://github.com/KL-Psychological-Methodology/ESMira-web/blob/main/src/frontend/ts/site/PluginInterfaces.ts.
section
Access to the section data object (see https://github.com/KL-Psychological-Methodology/ESMira-web/blob/main/src/frontend/ts/site/SectionData.ts) of the current section.
Lang
The Lang class of ESMira which hold strings for the current language (including the language strings from plugins). Use Lang.get() to get a translated string. Returns the provided string and logs an error to the JavaScript console if no translation for the string is found.
Example
export default async function Plugin(methods, section, Lang) {
    const data = await options.getStudyPluginData();
    return {
        load: () => { // Called, when the plugin is first loaded
        
        },
        manipulateSectionView: (parent) => { // Called, when the section view is constructed
            const content = parent.querySelector(".sectionContent .dashRow")
            
            const div = methods.createElement("div", {className: "dashEl"}, content)
            const label = methods.createElement("label", {className: "noTitle"}, div)
            methods.createElement("input", {type: "checkbox", ...methods.bindObservable(data.enabled)}, label)
            methods.createElement("span", {innerText: Lang.get("enabled")}, label)
        },
        changeSectionTitle: (title) => { // Called, when the section title is determined
            return `${title} (altered)`;
        },
        manipulateExtras: (parent) => { // Called when the icon buttons to the top right are constructed
            const div = methods.createElement("div", {innerText: "Plugin"}, content)
        },
        onClose: () => { // Called, when the section is closed
            // do something
        }	
    }
}
[!NOTE] If you want to provide plugin settings, add a file named
pluginSettings.jsto thesections/folder. They will be linked in thePluginstab in the server settings.
locales – provide your own translations
To provide your own translations, create a folder named locales in the plugin folder and create a json file with the name of the language you want to provide translations for. ESMira will automatically load the correct translation file depending on the user language. If the user language is not available, ESMira will load the English language instead. If a language string is already defined in ESMira, the plugin version will overwrite it.