Source: models/plugins.js


/**
 * 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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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">&nbsp;</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>&nbsp;</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&uuml;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;