/**
* Class handles Plugins and offers methods to access and set their configuration
* @class
*/
class Plugins {
/**
* @constructor
* @param {object} logger Logger to be used
*/
constructor(logger) {
this.coreDb = global.coreDb;
this.logger = logger;
}
/**
* Returns list of all plugins registered in core database
* @param {function} callback rows or null
*/
getPluginsList(callback) {
var sql = "SELECT * FROM pluginstore ORDER BY plgtype";
this.coreDb.all(sql, [], (err, rows) => {
if (err) {
this.logger.error(err);
callback(null);
}
callback(rows);
});
};
/**
* returns manifest object for a given plugin
* @param {string} namespace
* @returns {object} manifest object or null if not found
*/
getPluginManifestForNameSpace(namespace) {
var hitManifest = null;
global.plgManager.pluginList.forEach((manifest) => {
if(manifest.namespace === namespace) {
hitManifest = manifest;
}
})
return hitManifest;
};
/**
* Returns plugin configuration from core database for given plugin
* @param {string} namespace plugin namespace, the config shall be returned for
* @param {function} callback json containing the plugin configuration (field configstore from core database)
*/
getPluginConfig(namespace, callback) {
var sql = "SELECT * FROM pluginstore WHERE namespace = '" + namespace + "'";
this.coreDb.all(sql, [], (err, rows) => {
if (err) {
this.logger.error('MODELS | PLUGINS | Could not load config for plugin ' + namespace + ': ' + err);
callback(null);
}
if (rows.length > 0) {
callback(rows[0]);
} else {
callback('');
}
})
}
/**
* Writes plugin configuration to core database
* @param {string} namespace namespace of plugin the config shall be updated / set for
* @param {object} reqBody form Data (usually req.form)
* @param {function} callback success (true or false)
*/
setPluginConfig(namespace, reqBody, reqFiles, callback) {
// Get the configuration parameters from store
// Cylce through the possible configuration parameters
// Check if form sent a value for each parameter
var manifest = this.getPluginManifestForNameSpace(namespace);
if (manifest !== null) {
// Found valid manifest
if (typeof manifest.configstore.fields !== 'undefined') {
var plgActive = 0;
if (typeof reqBody['pluginConfig[plgActive]'] !== 'undefined') {
if (reqBody['pluginConfig[plgActive]'] === 'true') {
plgActive = 1;
}
}
if (manifest.configstore.fields.length > 0) {
var fields = manifest.configstore.fields;
var newConfigStore = { fields: [] };
fields.forEach((field) => {
if (field.type === 'file') {
if (reqFiles) {
// There has been a file uploaded
if (typeof reqFiles['pluginConfig[' + field.fieldname + ']'] !== 'undefined') {
// File seems to be chosen by user
let uploadedFile = reqFiles['pluginConfig[' + field.fieldname + ']'];
if (typeof uploadedFile.name !== 'undefined') {
field.value = uploadedFile.name;
// Get Plugin - Path
var pluginAttachmentPath = global.plgManager.getPluginPath(manifest.namespace) + '/uploads';
// create upload folder if it does not exist
var fs = require('fs');
if (!fs.existsSync(pluginAttachmentPath)) {
fs.mkdirSync(pluginAttachmentPath);
}
// copy attachment to folder
uploadedFile.mv(pluginAttachmentPath + '/' + uploadedFile.name, (err) => {
if (err) {
console.log('Error uploading file: ' + err.message)
// TBD Error-Handling
} else {
// Successfully moved file to plugin
// TBD -> write path to plugin settings in core database for field
console.log('successfully uploaded file')
field.value = uploadedFile.name;
}
});
}
}
}
} else {
if (typeof reqBody['pluginConfig[' + field.fieldname + ']'] !== 'undefined') {
field.value = reqBody['pluginConfig[' + field.fieldname + ']'];
} else if (typeof reqBody['pluginConfig[' + field.fieldname + '][0][\'key\']'] !== 'undefined') {
// Seems key value pair identified
// check how many rows have been set
var rowCountKeyValue = reqBody['counterKeyValue'];
// now we have the number of rows potentially filled
console.log('Identified a keyValue field');
var i = 0;
var fieldContent = [];
for (i = 0; i < rowCountKeyValue; i++) {
var keyValueArr = {
key : reqBody['pluginConfig[' + field.fieldname + ']['+i+'][\'key\']'],
value : reqBody['pluginConfig[' + field.fieldname + ']['+i+'][\'value\']']
};
fieldContent.push(keyValueArr);
}
if (fieldContent.length > 0) {
field.value = JSON.stringify(fieldContent);
}
}
}
newConfigStore.fields.push(field);
console.log(field.value);
});
// console.log(newConfigStore);
global.coreDb.run(`UPDATE pluginstore set configstore = ?, state = ? WHERE namespace = ?`,
[
JSON.stringify(newConfigStore),
plgActive,
namespace
],
function (err) {
if (err) {
callback(false);
} else {
global.plgManager.event_pluginConfig_refreshed(namespace);
callback(true);
}
});
} else {
// No plugin-specific form fields recieved. Only process state of plugin
global.coreDb.run(`UPDATE pluginstore SET state = ? WHERE namespace = ?`,
[
plgActive,
namespace
],
function (err) {
if (err) {
callback(false);
} else {
global.plgManager.event_pluginConfig_refreshed(namespace);
callback(true);
}
});
}
} else {
callback(false);
}
} else {
callback(false);
}
}
publishFileForPluginSettings(namespace, reqFiles, callback) {
try {
if (!reqFiles) {
return callback(false);
} else {
//Use the name of the input field (i.e. "avatar") to retrieve the uploaded file
let avatar = req.files.avatar;
//Use the mv() method to place the file in upload directory (i.e. "uploads")
avatar.mv('./uploads/' + avatar.name);
//send response
res.send({
status: true,
message: 'File is uploaded',
data: {
name: avatar.name,
mimetype: avatar.mimetype,
size: avatar.size
}
});
}
} catch (err) {
res.status(500).send(err);
}
}
/**
* Returns HTML-Form for the plugin configuration for plugin with given namespace
* @param {object} config configstore array for plugin to be rendered as html form
* @param {integer} state Active State of plugin (1/0)
* @param {string} namespace namespace of plugin the html form shall be rendered for
* @returns {string} HTML Code for the edit form for plugin configuration
*/
getPluginConfigFormCode(config, state, namespace) {
console.log(config);
var outHTML = '';
// render generic fields present at all plugins
// independant of plugin manifest
outHTML = '<form class="forms-sample" method="post" enctype="multipart/form-data" action="/admin/plugins/setPluginConfig/' + namespace + '">';
outHTML += '<ul class="list-group list-group-flush">';
outHTML += ' <li class="list-group-item">';
outHTML += ' <div class="form-group">';
outHTML += ' <label for="pluginConfig[plgActive]">Plugin aktiv</label>';
outHTML += ' <select class="form-control text-primary" name="pluginConfig[plgActive]" id="pluginConfig[plgActive]">';
console.log('Active-State: ' + state);
if (state === 0 ) {
outHTML += ' <option value="false" selected>nein</option>';
} else {
outHTML += ' <option value="false">nein</option>';
}
if (state === 1 ) {
outHTML += ' <option value="true" selected>ja</option>';
} else {
outHTML += ' <option value="true">ja</option>';
}
outHTML += ' </select>';
outHTML += ' <small class="form-text text-muted">Soll diese Plugin ausgeführt werden oder nicht.</small>';
outHTML += ' </div>';
outHTML += '<div> </div>';
outHTML += '</li>';
// render plugin-specific fields
// from manifest definition
if (typeof config.fields != 'undefined') {
if (config.fields.length > 0) {
outHTML += '<li class="list-group-item">';
var fields = config.fields;
fields.forEach((field) => {
if (typeof field.value === 'undefined') field.value = '';
// construct now the form elements
/**
* Text Field
*/
if (field.type === 'string') {
var required = '';
if (field.mandantory === true) { required = 'required'; };
outHTML += '<div class="form-group">';
outHTML += ' <label for="pluginConfig[' + field.fieldname + ']">' + field.label + '</label>';
outHTML += ' <input type="text" class="form-control text-primary" id="pluginConfig[' + field.fieldname + ']" name="pluginConfig[' + field.fieldname + ']" placeholder="' + field.presetvalue + '" value="' + field.value + '" ' + required + '>';
outHTML += ' <small class="form-text text-muted">' + field.description + '</small>';
outHTML += '</div>';
outHTML += '<div> </div>'
}
/**
* SELECT Field
*/
if (field.type === 'select') {
var required = '';
if (field.mandantory === true) { required = 'required'; };
// Text field to be constructed
outHTML += '<div class="form-group">';
outHTML += ' <label for="pluginConfig[' + field.fieldname + ']">'+ field.label + '</label>';
outHTML += ' <select class="form-control text-primary" name="pluginConfig[' + field.fieldname + ']" id="pluginConfig[' + field.fieldname + ']">';
field.selections.forEach((selection) => {
var selected = '';
if (field.value === selection.value) {
selected = ' selected';
}
outHTML += ' <option value="' + selection.value + '"' + selected + '>' + selection.label + '</option>';
});
outHTML += ' </select>';
outHTML += ' <small class="form-text text-muted">' + field.description + '</small>';
outHTML += '</div>';
outHTML += '<div> </div>';
}
/**
* BOOLEAN Field (yes / no)
*/
if (field.type === 'boolean') {
var required = '';
if (field.mandantory === true) { required = 'required'; };
// Text field to be constructed
outHTML += '<div class="form-group">';
outHTML += ' <label for="pluginConfig[' + field.fieldname + ']">'+ field.label + '</label>';
outHTML += ' <select class="form-control text-primary" name="pluginConfig[' + field.fieldname + ']" id="pluginConfig[' + field.fieldname + ']">';
if (field.value === "true" ) {
outHTML += ' <option value="true" selected>ja</option>';
} else {
outHTML += ' <option value="true">ja</option>';
}
if (field.value === "false" ) {
outHTML += ' <option value="false" selected>nein</option>';
} else {
outHTML += ' <option value="false">nein</option>';
}
outHTML += ' </select>';
outHTML += ' <small class="form-text text-muted">' + field.description + '</small>';
outHTML += '</div>';
outHTML += '<div> </div>';
}
/**
* UPLOAD Control Field
*/
if (field.type === 'file') {
var required = '';
if (field.mandantory === true) { required = 'required'; };
// Text field to be constructed
outHTML += '<div class="form-group">';
outHTML += ' <label for="pluginConfig[' + field.fieldname + ']">'+ field.label + '</label>';
outHTML += ' <div class="custom-file">';
outHTML += ' <input type="file" class="custom-file-input" id="pluginConfig[' + field.fieldname + ']" name="pluginConfig[' + field.fieldname + ']">';
outHTML += ' <label class="custom-file-label form-text text-muted" for="pluginConfig[' + field.fieldname + ']">Datei auswählen ... </label>';
outHTML += ' </div>';
outHTML += ' <small class="form-text text-muted">' + field.description + '</small>';
outHTML += '</div>';
outHTML += '<div> </div>';
}
/**
* BUTTON CONTROL
*/
if (field.type === 'button') {
// Button with URL to be constructed
outHTML += '<div class="form-group">';
outHTML += '<a href="' + field.presetvalue + '" class="btn btn-primary btn-lg active" target="_blank" role="button" aria-pressed="true">' + field.label + '</a>';
outHTML += ' <small class="form-text text-muted">' + field.description + '</small>';
outHTML += '</div>';
outHTML += '<div> </div>'
}
/**
* DIRECTORY CHOOSER
*/
if (field.type === 'directory') {
var required = '';
if (field.mandantory === true) { required = 'required'; };
// Text field to be constructed
outHTML += '<div class="form-group">';
outHTML += ' <label for="pluginConfig[' + field.fieldname + ']">'+ field.label + '</label>';
outHTML += ' <div class="custom-file">';
outHTML += ' <input type="file" class="custom-file-input" id="pluginConfig[' + field.fieldname + ']" name="pluginConfig[' + field.fieldname + ']" webkitdirectory multiple>';
outHTML += ' <label class="custom-file-label form-text text-muted" for="pluginConfig[' + field.fieldname + ']">Datei auswählen ... </label>';
outHTML += ' </div>';
outHTML += ' <small class="form-text text-muted">' + field.description + '</small>';
outHTML += '</div>';
outHTML += '<div> </div>';
}
/**
* MULTILINE
*/
if (field.type === 'multiline') {
var required = '';
if (field.mandantory === true) { required = 'required'; };
// Text field to be constructed
outHTML += '<div class="form-group">';
outHTML += ' <label for="pluginConfig[' + field.fieldname + ']">' + field.label + '</label>';
outHTML += ' <textarea class="form-control text-primary" rows="10" id="pluginConfig[' + field.fieldname + ']" name="pluginConfig[' + field.fieldname + ']" placeholder="015122378152" ' + required + '>' + field.value + '</textarea>';
outHTML += ' <small class="form-text text-muted">' + field.description + '</small>';
outHTML += '</div>';
outHTML += '<div> </div>'
}
/**
* KEY VALUE TABLE
*/
if (field.type === 'keyvalue') {
var required = '';
var keyValueObj = [];
try {
keyValueObj = JSON.parse(field.value);
} catch (err) {
keyValueObj = [];
}
var countKeyValueEntries = 0;
if (typeof keyValueObj === 'object') {
countKeyValueEntries = keyValueObj.length;
}
if (field.mandantory === true) { required = 'required'; };
// Text field to be constructed
outHTML += ' <div class="row clearfix">' +
'<div class="col-md-12 column">';
outHTML += '<div class="form-group">';
outHTML += ' <label>' + field.label + '</label>';
outHTML += '<table id="keyValueTable" class="table order-list table-bordered table-striped">' +
'<thead class="table-dark">' +
'<tr>' +
'<td class="w-25">Wert</td>' +
'<td class="w-25">Ersetzung</td>' +
'<td class="w-50"> </td>' +
'</tr>' +
'</thead>' +
'<tbody>';
console.log('countKeyValueEntries: ' + countKeyValueEntries);
if (countKeyValueEntries === 0) {
outHTML += '<tr>' +
'<td>' +
'<input type="text" name="pluginConfig[' + field.fieldname + '][0][\'key\']" class="form-control" />' +
'</td>' +
'<td>' +
'<input type="mail" name="pluginConfig[' + field.fieldname + '][0][\'value\']" class="form-control"/>' +
'</td>' +
'<td>' +
'<a class="deleteRow"></a>' +
'</td>' +
'</tr>';
} else {
outHTML += '<script type="text/javascript">' +
'var counterExisting = ' + countKeyValueEntries + ';' +
'</script>';
for (var property in keyValueObj) {
if (keyValueObj.hasOwnProperty(property)) {
// Do things here
console.log('out: ' + keyValueObj[property].key);
outHTML += '<tr>' +
'<td>' +
'<input type="text" name="pluginConfig[' + field.fieldname + '][' + property + '][\'key\']" class="form-control" value="' + keyValueObj[property].key +'"/>' +
'</td>' +
'<td>' +
'<input type="text" name="pluginConfig[' + field.fieldname + '][' + property + '][\'value\']" class="form-control" value="' + keyValueObj[property].value +'"/>' +
'</td>' +
'<td><input type="button" class="ibtnDel btn btn-md btn-danger " value="entfernen"></td>' +
'</tr>';
}
}
}
outHTML += '</tbody>' +
'<tfoot>' +
'<tr>' +
'<td colspan="5" style="text-align: left;">' +
'<input type="button" class="btn btn-sm btn-info btn-block " id="addrow" value="Weiterer Eintrag" />' +
'</td>' +
'</tr>' +
'<tr>' +
'</tr>' +
'</tfoot>' +
'</table>';
outHTML += '</div>';
outHTML += '<input type="text" value="1" name="counterKeyValue" id="counterKeyValue" hidden>'
outHTML += '<div> </div>';
outHTML += '</div></div>';
outHTML += '<script type="text/javascript">var keyValueFieldIdentifier = "' + field.fieldname + '";</script>'
}
})
outHTML += '</li>'
} else {
outHTML += 'Dieses Plugin benötigt keine Konfiguration.';
}
} else {
outHTML = 'Plugin Konfiguration konnte nicht gelesen werden.'
}
outHTML += '<li class="list-group-item">';
outHTML += '<button type="submit" class="btn btn-success mr-2">Speichern</button>';
outHTML += '<button class="btn btn-light">zurück</button>';
outHTML += '</li>';
outHTML += '</ul>';
outHTML += '</form>';
return outHTML;
}
/**
* Resets the plugin configuration in core database to factory defaults out of the manifest file of plugin
* @param {string} namespace namespace of the plugin the configuration shall be resetted to factory default
*/
resetManifestForNamespace(namespace) {
var retVal = global.plgManager.resetManifestForNamespace(namespace);
//this.logger.info('MODELS | PLUGINS | Refreshed Manifest: ' + retVal);
}
/**
* Returns list of plugins which consume fcm tokens from coredatabase
* @param {function} callback rows or null
*/
getPluginsConsumingFCMTokens(callback) {
var sql = "SELECT * FROM pluginstore WHERE fcmtokensconsumer = 1";
this.coreDb.all(sql, [], (err, rows) => {
if (err) {
this.logger.error(err);
callback(null);
}
callback(rows);
});
}
}
module.exports = Plugins;