<commit_before>.import QtQuick.LocalStorage 2.0 as Sql

/*
  new QMDatabase('myApp', '1.0')
  define : returns an object with functions create/filter/delete
  create: returns an object with the properties
  */

function QMDatabase(appName, version) {
    this.migrate = false;
    //Tables to handle version control
    var qMDatabase = this;
    this.conn = Sql.LocalStorage.openDatabaseSync(appName + '_db', "1.0", appName, 100000,
        function(db) {
            qMDatabase.migrate = true;
            console.log("*** DATABASE DOESNT EXIST ****");
            db.changeVersion("", "1.0");
        }
    );
    var AppVersion = this.define('__AppVersion__', {
        appName: this.String('App Name', {
            accept_null: false
        }),
        version: this.String('Version', {
            accept_null: false
        })
    });

    var appVersion = AppVersion.filter({
        appName: appName
    }).get();

    if (appVersion) {
        if (appVersion.version !== version) {
            appVersion.version = version;
            appVersion.save();
            this.migrate = true;
        }
    } else {
        appVersion = AppVersion.create({
            appName: appName,
            version: version
        });
        this.migrate = true;
    }
}

QMDatabase.prototype = {
    constructor: QMDatabase,

    String: function(label, params) {
        return new QMField('TEXT', label, params);
    },
    Integer: function(label, params) {
        return new QMField('INTEGER', label, params);
    },
    Float: function(label, params) {
        return new QMField('FLOAT', label, params);
    },
    Real: function(label, params) {
        return new QMField('REAL', label, params);
    },
    Date: function(label, params) {
        return new QMField('DATE', label, params);
    },
    DateTime: function(label, params) {
        return new QMField('DATETIME', label, params);
    },
    Boolean: function(label, params) {
        return new QMField('BOOLEAN', label, params);
    },
    PK: function(label, params) {
        return new QMField('PK', label, params);
    },
    FK: function(label, params) {
        return new QMField('FK', label, params);
    },
    _defineField: function(column, data) {
        var sql;
        var items = [];
        var fk = [];

        //If is a foreign key
        if (data.type === 'FK') {
            items.push(column);
            items.push('INTEGER');
            fk.push('FOREIGN KEY(' + column + ')');
        } else if (data.type === 'PK') {
            items.push(column);
            items.push('INTEGER PRIMARY KEY');
        } else {
            items.push(column);
            items.push(data.type);
        }

        for (var param in data.params) {
            switch (param) {
                case 'accept_null':
                    if (!data.params[param]) {
                        items.push('NOT NULL');
                    }
                    break;
                case 'unique':
                    if (unique) {
                        items.push('UNIQUE');
                    }
                    break;
                case 'references':
                    fk.push('REFERENCES ' + data.params[param] + '(id) ON DELETE CASCADE ON UPDATE CASCADE');
                    break;
                case 'default':
                    items.push('DEFAULT ' + data.params[param]);
                    break;

            }
        }

        return {
            field: items.join(' '),
            fk: fk.join(' ')
        };
    },
    retrieveFields: function(name) {
        var rs;
        var sql = "PRAGMA table_info(" + name + ")";
        this.conn.transaction(
            function(tx) {
                console.log("Run SQL: " + sql);
                rs = tx.executeSql(sql);
            }
        )

        var fields = {};
        for (var idx = 0; idx < rs.rows.length; idx++) {
            fields[rs.rows[idx].name] = null;
        }

        return fields;
    },
    define: function(name, fields) {
        var sql_create = "CREATE TABLE IF NOT EXISTS " + name + " (";
        var idx = 0;
        this.properties = {};
        this.tableName = name;

        fields['id'] = this.PK('Primary Key', []);

        var foreign_keys = [];
        for (var column in fields) {
            var definitions = fields[column];
            var field_data = this._defineField(column, fields[column]);
            if (idx > 0) sql_create += ", ";
            sql_create += field_data['field'];

            if (field_data['fk'].length > 0) {
                foreign_keys.push(field_data['fk']);
            }
            idx++;
        }

        //Create foreign key references
        for (var ifk = 0; ifk < foreign_keys.length; ifk++) {
            sql_create += ", " + foreign_keys[ifk];
        }

        sql_create += ")";
        var model = new QMModel(this, name, fields);

        if (this.migrate) {
            var oldObjs = [];
            var oldFields = this.retrieveFields(name);
            if (!isEmpty(oldFields)) {
                var oldModel = new QMModel(this, name, oldFields);
                oldObjs = oldModel.all();
                this._runSQL("DROP TABLE " + name);
            }

            //Run create table
            this._runSQL(sql_create);

            for (var i = 0; i < oldObjs.length; i++) {
                for (var field in oldObjs[i]) {
                    if (!(field in fields) && field !== '_model' && field !== 'save') {
                        delete oldObjs[i][field];
                    }
                }
                oldObjs[i].save(true);
            }
        }

        return model;
    },
    _runSQL: function(sql) {
        this.conn.transaction(
            function(tx) {
                console.log("Run SQL: " + sql);
                tx.executeSql(sql);
            }
        )
    }
};

/*******************************
  QMModel
  Define a class referencing a database table
  *****************************/

function QMModel(db, tableName, fields) {
    this.filterConditions = {};
    this.sorters = [];
    this.limiters = null;

    this._meta = {
        db: db,
        tableName: tableName,
        fields: fields,
    };
}

function isEmpty(obj) {
    // null and undefined are "empty"
    if (obj == null) return true;

    // Assume if it has a length property with a non-zero value
    // that that property is correct.
    if (obj.length > 0) return false;
    if (obj.length === 0) return true;

    // If it isn't an object at this point
    // it is empty, but it can't be anything *but* empty
    // Is it empty?  Depends on your application.
    if (typeof obj !== "object") return true;

    // Otherwise, does it have any properties of its own?
    // Note that this doesn't handle
    // toString and valueOf enumeration bugs in IE < 9
    for (var key in obj) {
        if (hasOwnProperty.call(obj, key)) return false;
    }

    return true;
}

QMModel.prototype = {
    create: function(data) {
        var obj = this.makeObject(data);
        var insertId = this.insert(obj);

        var objs = this.filter({
            id: insertId
        }).all();
        if (objs.length > 0) {
            return objs[0];
        }

        return null;
    },
    filter: function(conditions) {
        this.filterConditions = conditions;
        return this;
    },
    order: function(sorters) {
        if (typeof sorters === 'string') {
            if (!this.sorters) {
                this.sorters = [];
            }
            this.sorters.push(sorters);
        } else if (Array.isArray(sorters)) {
            this.sorters = sorters;
        }
        return this;
    },
    limit: function(limiter) {
        this.limiter = limiter;
        return this;
    },
    get: function() {
        var objs = this.limit(1).all();
        if (objs.length > 0)
            return objs[0];

        return null;
    },
    all: function() {
        var sql = "SELECT ";
        var fields = [];
        for (var field in this._meta.fields) {
            fields.push(field);
        }

        sql += fields.join(',');
        sql += " FROM " + this._meta.tableName;
        sql += this._defineWhereClause();

        if (this.sorters && this.sorters.constructor === String) {
            this.sorters = [this.sorters];
        }

        if (this.sorters && this.sorters.length > 0) {
            sql += " ORDER BY ";
            for (var idxOrder = 0; idxOrder < this.sorters.length; idxOrder++) {
                if (idxOrder > 0) sql += ", ";
                var ord = this.sorters[idxOrder];
                if (ord[0] === '-') {
                    sql += ord.substring(1) + " DESC ";
                } else {
                    sql += ord;
                }
            }
        }

        if (this.limiter) {
            sql += " LIMIT " + this.limiter;
        }

        var rs = [];

        this._meta.db.conn.transaction(
            function(tx) {
                console.log("Run SQL: " + sql);
                rs = tx.executeSql(sql)

                //console.log("RESULT SET: " + rs);
            }
        )

        var objs = [];
        for (var i = 0; i < rs.rows.length; i++) {
            var item = rs.rows.item(i);
            var obj = this.makeObject(item);
            objs.push(obj);
        }

        this.filterConditions = {};
        this.limiter = null;
        this.sorters = null;

        return objs;
    },
    update: function(obj) {
        var sql = "UPDATE " + this._meta.tableName + " SET ";
        var idx = 0;
        for (var field in obj) {
            if (field === '_model' || field === 'save')
                continue;
            if (field === 'id' && isEmpty(obj[field]))
                continue;

            if (idx > 0) sql += ",";
            sql += field + " = " + this._convertToSqlType(obj[field]) + "";
            idx++;
        }
        sql += this._defineWhereClause(this.filterConditions);

        this._meta.db._runSQL(sql);
        this.filterConditions = {};
    },
    insert: function(obj) {
        var sql = "INSERT INTO " + this._meta.tableName + "(";
        var fields = [];
        var values = [];
        for (var field in obj) {
            if (field === '_model' || field === 'save')
                continue;
            if (field === 'id' && isEmpty(obj[field]))
                continue;
            fields.push(field);
            if (obj[field]) {
                values.push(this._convertToSqlType(obj[field]));
            } else {
                values.push('NULL');
            }
        }
        sql += fields.join(', ');
        sql += ") VALUES (" + values.join(', ') + ")";

        var rs;
        this._meta.db.conn.transaction(
            function(tx) {
                console.log("Run SQL: " + sql);
                rs = tx.executeSql(sql);
            }
        )

        return rs.insertId;
    },
    remove: function() {
        var sql = "DELETE FROM " + this._meta.tableName;
        sql += this._defineWhereClause();
        this._meta.db._runSQL(sql);
        this.filterConditions = {};
    },
    _typeof: function(value) {
        var l_type = typeof value;

        // adjusting type based on object instanceof
        if (l_type === 'object') {
            if (value instanceof Date) {
                l_type = 'date';
            } else
            if (value instanceof Number) {
                l_type = 'number';
            } else
            if (value instanceof String) {
                l_type = 'string';
            } else
            if (value instanceof Boolean) {
                l_type = 'boolean';
            }
        }

        return l_type;
    },
    _convertToSqlType: function(value) {
        var l_type = this._typeof(value);

        if (l_type === 'boolean') {
            value = value ? 1 : 0;
            l_type = 'number';
        }
        if (l_type === 'date') {
            value = value.toISOString();
            l_type = 'string';
        }
        if (l_type === 'string') {
            value = "'" + value.replace("'", "''") + "'";
        }

        return value;
    },
    _convertFromSqlValue: function(value, definition) {
        if (!definition) return value;
        if (!value) return value;

        var l_type = this._typeof(value);
        var l_desiredType = definition.type;

        if (l_type === 'number') {
            if (l_desiredType === 'BOOLEAN') {
                value = value !== 0;
            }
        }

        if (l_type === 'string') {
            if ((l_desiredType === 'FLOAT') ||
                (l_desiredType === 'REAL') ||
                (l_desiredType === 'INTEGER') ||
                (l_desiredType === 'FK') ||
                (l_desiredType === 'PK')) {
                value = Number(value);
            } else
            if ((l_desiredType === 'DATE') ||
                (l_desiredType === 'DATETIME')) {
                value = new Date(value);
            } else
            if (l_desiredType === 'BOOLEAN') {
                switch (value) {
                    case "true":
                    case "1":
                    case "on":
                    case "yes":
                        value = true;
                        break;
                    default:
                        var isNum = value.match(/^[0-9]+$/);
                        if (!isNum)
                            value = false;
                        else
                            value = value !== 0;
                        break;
                }
            }
        }

        return value;
    },
    _defineWhereClause: function() {
        var sql = '';
        var idx = 0;

        for (var cond in this.filterConditions) {
            if (idx > 0) sql += " AND ";
            var operator;
            var newOperator = '=';
            var field = cond;
            var position;
            if (cond.indexOf('__') > -1) {
                var operands = cond.split('__');
                field = operands[0];
                operator = operands[1];

                switch (operator) {
                    case 'gt':
                        newOperator = '>';
                        break;
                    case 'ge':
                        newOperator = '>=';
                        break;
                    case 'lt':
                        newOperator = '<';
                        break;
                    case 'le':
                        newOperator = '<=';
                        break;
                    case 'null':
                        if (this.filterConditions[cond])
                            newOperator = 'IS NULL';
                        else
                            newOperator = 'IS NOT NULL';
                        break;
                    case 'like':
                        newOperator = 'LIKE';
                        position = 'BEGINEND';
                        break;
                    case 'startswith':
                        newOperator = 'LIKE';
                        position = 'END';
                        break;
                    case 'endswith':
                        newOperator = 'LIKE';
                        position = 'BEGIN';
                        break;
                }
            } else if (this.filterConditions[cond].constructor === Array) {
                newOperator = 'IN';
            }

            sql += field + " " + newOperator + " ";
            if (newOperator === 'LIKE') {
                sql += "'";
                if (position.indexOf('BEGIN') > -1) {
                    sql += "%";
                }
                sql += this.filterConditions[cond];
                if (position.indexOf('END') > -1) {
                    sql += "%";
                }
                sql += "'";
            } else if (operator !== 'null') {
                if (this.filterConditions[cond].constructor === String) {
                    sql += "'" + this.filterConditions[cond] + "'";
                } else if (newOperator === 'IN') {
                    sql += "('" + this.filterConditions[cond].join("','") + "')";
                } else {
                    sql += this._convertToSqlType(this.filterConditions[cond]);
                }
            }
            idx++;
        }

        if (sql.length > 0) {
            sql = " WHERE " + sql;
        }

        return sql;
    },
    makeObject: function(values) {
        var obj = new QMObject(this);
        for (var field in values) {
            if (field.startsWith('_') || field === 'save' || !(field in this._meta.fields))
                continue;
            obj[field] = this._convertFromSqlValue(values[field], this._meta.fields[field]);
        }
        return obj;
    }
};

/**************************************
  QMObject
  reference a single instance of a object in the database
  *************************************/
function QMObject(model) {
    this._model = model;
    this.id = null;
}

QMObject.prototype = {
    //Functions for single object
    save: function(forceInsert) {
        if (typeof forceInsert === 'undefined') {
            forceInsert = false;
        }
        if (this.id && !forceInsert) {
            this._model.filter({
                id: this.id
            }).update(this);
        } else {
            this.id = this._model.insert(this);
        }
        return this;
    }
}

/*******************************************
  QMField
  Define a database field with attributes
  ******************************************/

function QMField(type, label, params) {
    this.type = type;
    this.params = params;
}

//TODO: Migrations!
//TODO: Replace concatenations with binds '?'
<commit_msg>checking if value is a valid date<commit_after>.import QtQuick.LocalStorage 2.0 as Sql

/*
  new QMDatabase('myApp', '1.0')
  define : returns an object with functions create/filter/delete
  create: returns an object with the properties
  */

function QMDatabase(appName, version) {
    this.migrate = false;
    //Tables to handle version control
    var qMDatabase = this;
    this.conn = Sql.LocalStorage.openDatabaseSync(appName + '_db', "1.0", appName, 100000,
        function(db) {
            qMDatabase.migrate = true;
            console.log("*** DATABASE DOESNT EXIST ****");
            db.changeVersion("", "1.0");
        }
    );
    var AppVersion = this.define('__AppVersion__', {
        appName: this.String('App Name', {
            accept_null: false
        }),
        version: this.String('Version', {
            accept_null: false
        })
    });

    var appVersion = AppVersion.filter({
        appName: appName
    }).get();

    if (appVersion) {
        if (appVersion.version !== version) {
            appVersion.version = version;
            appVersion.save();
            this.migrate = true;
        }
    } else {
        appVersion = AppVersion.create({
            appName: appName,
            version: version
        });
        this.migrate = true;
    }
}

QMDatabase.prototype = {
    constructor: QMDatabase,

    String: function(label, params) {
        return new QMField('TEXT', label, params);
    },
    Integer: function(label, params) {
        return new QMField('INTEGER', label, params);
    },
    Float: function(label, params) {
        return new QMField('FLOAT', label, params);
    },
    Real: function(label, params) {
        return new QMField('REAL', label, params);
    },
    Date: function(label, params) {
        return new QMField('DATE', label, params);
    },
    DateTime: function(label, params) {
        return new QMField('DATETIME', label, params);
    },
    Boolean: function(label, params) {
        return new QMField('BOOLEAN', label, params);
    },
    PK: function(label, params) {
        return new QMField('PK', label, params);
    },
    FK: function(label, params) {
        return new QMField('FK', label, params);
    },
    _defineField: function(column, data) {
        var sql;
        var items = [];
        var fk = [];

        //If is a foreign key
        if (data.type === 'FK') {
            items.push(column);
            items.push('INTEGER');
            fk.push('FOREIGN KEY(' + column + ')');
        } else if (data.type === 'PK') {
            items.push(column);
            items.push('INTEGER PRIMARY KEY');
        } else {
            items.push(column);
            items.push(data.type);
        }

        for (var param in data.params) {
            switch (param) {
                case 'accept_null':
                    if (!data.params[param]) {
                        items.push('NOT NULL');
                    }
                    break;
                case 'unique':
                    if (unique) {
                        items.push('UNIQUE');
                    }
                    break;
                case 'references':
                    fk.push('REFERENCES ' + data.params[param] + '(id) ON DELETE CASCADE ON UPDATE CASCADE');
                    break;
                case 'default':
                    items.push('DEFAULT ' + data.params[param]);
                    break;

            }
        }

        return {
            field: items.join(' '),
            fk: fk.join(' ')
        };
    },
    retrieveFields: function(name) {
        var rs;
        var sql = "PRAGMA table_info(" + name + ")";
        this.conn.transaction(
            function(tx) {
                console.log("Run SQL: " + sql);
                rs = tx.executeSql(sql);
            }
        )

        var fields = {};
        for (var idx = 0; idx < rs.rows.length; idx++) {
            fields[rs.rows[idx].name] = null;
        }

        return fields;
    },
    define: function(name, fields) {
        var sql_create = "CREATE TABLE IF NOT EXISTS " + name + " (";
        var idx = 0;
        this.properties = {};
        this.tableName = name;

        fields['id'] = this.PK('Primary Key', []);

        var foreign_keys = [];
        for (var column in fields) {
            var definitions = fields[column];
            var field_data = this._defineField(column, fields[column]);
            if (idx > 0) sql_create += ", ";
            sql_create += field_data['field'];

            if (field_data['fk'].length > 0) {
                foreign_keys.push(field_data['fk']);
            }
            idx++;
        }

        //Create foreign key references
        for (var ifk = 0; ifk < foreign_keys.length; ifk++) {
            sql_create += ", " + foreign_keys[ifk];
        }

        sql_create += ")";
        var model = new QMModel(this, name, fields);

        if (this.migrate) {
            var oldObjs = [];
            var oldFields = this.retrieveFields(name);
            if (!isEmpty(oldFields)) {
                var oldModel = new QMModel(this, name, oldFields);
                oldObjs = oldModel.all();
                this._runSQL("DROP TABLE " + name);
            }

            //Run create table
            this._runSQL(sql_create);

            for (var i = 0; i < oldObjs.length; i++) {
                for (var field in oldObjs[i]) {
                    if (!(field in fields) && field !== '_model' && field !== 'save') {
                        delete oldObjs[i][field];
                    }
                }
                oldObjs[i].save(true);
            }
        }

        return model;
    },
    _runSQL: function(sql) {
        this.conn.transaction(
            function(tx) {
                console.log("Run SQL: " + sql);
                tx.executeSql(sql);
            }
        )
    }
};

/*******************************
  QMModel
  Define a class referencing a database table
  *****************************/

function QMModel(db, tableName, fields) {
    this.filterConditions = {};
    this.sorters = [];
    this.limiters = null;

    this._meta = {
        db: db,
        tableName: tableName,
        fields: fields,
    };
}

function isEmpty(obj) {
    // null and undefined are "empty"
    if (obj == null) return true;

    // Assume if it has a length property with a non-zero value
    // that that property is correct.
    if (obj.length > 0) return false;
    if (obj.length === 0) return true;

    // If it isn't an object at this point
    // it is empty, but it can't be anything *but* empty
    // Is it empty?  Depends on your application.
    if (typeof obj !== "object") return true;

    // Otherwise, does it have any properties of its own?
    // Note that this doesn't handle
    // toString and valueOf enumeration bugs in IE < 9
    for (var key in obj) {
        if (hasOwnProperty.call(obj, key)) return false;
    }

    return true;
}

QMModel.prototype = {
    create: function(data) {
        var obj = this.makeObject(data);
        var insertId = this.insert(obj);

        var objs = this.filter({
            id: insertId
        }).all();
        if (objs.length > 0) {
            return objs[0];
        }

        return null;
    },
    filter: function(conditions) {
        this.filterConditions = conditions;
        return this;
    },
    order: function(sorters) {
        if (typeof sorters === 'string') {
            if (!this.sorters) {
                this.sorters = [];
            }
            this.sorters.push(sorters);
        } else if (Array.isArray(sorters)) {
            this.sorters = sorters;
        }
        return this;
    },
    limit: function(limiter) {
        this.limiter = limiter;
        return this;
    },
    get: function() {
        var objs = this.limit(1).all();
        if (objs.length > 0)
            return objs[0];

        return null;
    },
    all: function() {
        var sql = "SELECT ";
        var fields = [];
        for (var field in this._meta.fields) {
            fields.push(field);
        }

        sql += fields.join(',');
        sql += " FROM " + this._meta.tableName;
        sql += this._defineWhereClause();

        if (this.sorters && this.sorters.constructor === String) {
            this.sorters = [this.sorters];
        }

        if (this.sorters && this.sorters.length > 0) {
            sql += " ORDER BY ";
            for (var idxOrder = 0; idxOrder < this.sorters.length; idxOrder++) {
                if (idxOrder > 0) sql += ", ";
                var ord = this.sorters[idxOrder];
                if (ord[0] === '-') {
                    sql += ord.substring(1) + " DESC ";
                } else {
                    sql += ord;
                }
            }
        }

        if (this.limiter) {
            sql += " LIMIT " + this.limiter;
        }

        var rs = [];

        this._meta.db.conn.transaction(
            function(tx) {
                console.log("Run SQL: " + sql);
                rs = tx.executeSql(sql)

                //console.log("RESULT SET: " + rs);
            }
        )

        var objs = [];
        for (var i = 0; i < rs.rows.length; i++) {
            var item = rs.rows.item(i);
            var obj = this.makeObject(item);
            objs.push(obj);
        }

        this.filterConditions = {};
        this.limiter = null;
        this.sorters = null;

        return objs;
    },
    update: function(obj) {
        var sql = "UPDATE " + this._meta.tableName + " SET ";
        var idx = 0;
        for (var field in obj) {
            if (field === '_model' || field === 'save')
                continue;
            if (field === 'id' && isEmpty(obj[field]))
                continue;

            if (idx > 0) sql += ",";
            sql += field + " = " + this._convertToSqlType(obj[field]) + "";
            idx++;
        }
        sql += this._defineWhereClause(this.filterConditions);

        this._meta.db._runSQL(sql);
        this.filterConditions = {};
    },
    insert: function(obj) {
        var sql = "INSERT INTO " + this._meta.tableName + "(";
        var fields = [];
        var values = [];
        for (var field in obj) {
            if (field === '_model' || field === 'save')
                continue;
            if (field === 'id' && isEmpty(obj[field]))
                continue;
            fields.push(field);
            if (obj[field]) {
                values.push(this._convertToSqlType(obj[field]));
            } else {
                values.push('NULL');
            }
        }
        sql += fields.join(', ');
        sql += ") VALUES (" + values.join(', ') + ")";

        var rs;
        this._meta.db.conn.transaction(
            function(tx) {
                console.log("Run SQL: " + sql);
                rs = tx.executeSql(sql);
            }
        )

        return rs.insertId;
    },
    remove: function() {
        var sql = "DELETE FROM " + this._meta.tableName;
        sql += this._defineWhereClause();
        this._meta.db._runSQL(sql);
        this.filterConditions = {};
    },
    _typeof: function(value) {
        var l_type = typeof value;

        // adjusting type based on object instanceof
        if (l_type === 'object') {
            if (value instanceof Date) {
                l_type = 'date';
            } else
            if (value instanceof Number) {
                l_type = 'number';
            } else
            if (value instanceof String) {
                l_type = 'string';
            } else
            if (value instanceof Boolean) {
                l_type = 'boolean';
            }
        }

        return l_type;
    },
    _convertToSqlType: function(value) {
        var l_type = this._typeof(value);

        if (l_type === 'boolean') {
            value = value ? 1 : 0;
            l_type = 'number';
        }
        if (l_type === 'date') {
            if (isNaN(value)) {
                value = 'null';
            } else {
                value = value.toString();
                l_type = 'string';
            }
        }
        if (l_type === 'string') {
            value = "'" + value.replace("'", "''") + "'";
        }

        return value;
    },
    _convertFromSqlValue: function(value, definition) {
        if (!definition) return value;
        if (!value) return value;

        var l_type = this._typeof(value);
        var l_desiredType = definition.type;

        if (l_type === 'number') {
            if (l_desiredType === 'BOOLEAN') {
                value = value !== 0;
            }
        }

        if (l_type === 'string') {
            if ((l_desiredType === 'FLOAT') ||
                (l_desiredType === 'REAL') ||
                (l_desiredType === 'INTEGER') ||
                (l_desiredType === 'FK') ||
                (l_desiredType === 'PK')) {
                value = Number(value);
            } else
            if ((l_desiredType === 'DATE') ||
                (l_desiredType === 'DATETIME')) {
                value = new Date(value);
            } else
            if (l_desiredType === 'BOOLEAN') {
                switch (value) {
                    case "true":
                    case "1":
                    case "on":
                    case "yes":
                        value = true;
                        break;
                    default:
                        var isNum = value.match(/^[0-9]+$/);
                        if (!isNum)
                            value = false;
                        else
                            value = value !== 0;
                        break;
                }
            }
        }

        return value;
    },
    _defineWhereClause: function() {
        var sql = '';
        var idx = 0;

        for (var cond in this.filterConditions) {
            if (idx > 0) sql += " AND ";
            var operator;
            var newOperator = '=';
            var field = cond;
            var position;
            if (cond.indexOf('__') > -1) {
                var operands = cond.split('__');
                field = operands[0];
                operator = operands[1];

                switch (operator) {
                    case 'gt':
                        newOperator = '>';
                        break;
                    case 'ge':
                        newOperator = '>=';
                        break;
                    case 'lt':
                        newOperator = '<';
                        break;
                    case 'le':
                        newOperator = '<=';
                        break;
                    case 'null':
                        if (this.filterConditions[cond])
                            newOperator = 'IS NULL';
                        else
                            newOperator = 'IS NOT NULL';
                        break;
                    case 'like':
                        newOperator = 'LIKE';
                        position = 'BEGINEND';
                        break;
                    case 'startswith':
                        newOperator = 'LIKE';
                        position = 'END';
                        break;
                    case 'endswith':
                        newOperator = 'LIKE';
                        position = 'BEGIN';
                        break;
                }
            } else if (this.filterConditions[cond].constructor === Array) {
                newOperator = 'IN';
            }

            sql += field + " " + newOperator + " ";
            if (newOperator === 'LIKE') {
                sql += "'";
                if (position.indexOf('BEGIN') > -1) {
                    sql += "%";
                }
                sql += this.filterConditions[cond];
                if (position.indexOf('END') > -1) {
                    sql += "%";
                }
                sql += "'";
            } else if (operator !== 'null') {
                if (this.filterConditions[cond].constructor === String) {
                    sql += "'" + this.filterConditions[cond] + "'";
                } else if (newOperator === 'IN') {
                    sql += "('" + this.filterConditions[cond].join("','") + "')";
                } else {
                    sql += this._convertToSqlType(this.filterConditions[cond]);
                }
            }
            idx++;
        }

        if (sql.length > 0) {
            sql = " WHERE " + sql;
        }

        return sql;
    },
    makeObject: function(values) {
        var obj = new QMObject(this);
        for (var field in values) {
            if (field.startsWith('_') || field === 'save' || !(field in this._meta.fields))
                continue;
            obj[field] = this._convertFromSqlValue(values[field], this._meta.fields[field]);
        }
        return obj;
    }
};

/**************************************
  QMObject
  reference a single instance of a object in the database
  *************************************/
function QMObject(model) {
    this._model = model;
    this.id = null;
}

QMObject.prototype = {
    //Functions for single object
    save: function(forceInsert) {
        if (typeof forceInsert === 'undefined') {
            forceInsert = false;
        }
        if (this.id && !forceInsert) {
            this._model.filter({
                id: this.id
            }).update(this);
        } else {
            this.id = this._model.insert(this);
        }
        return this;
    }
}

/*******************************************
  QMField
  Define a database field with attributes
  ******************************************/

function QMField(type, label, params) {
    this.type = type;
    this.params = params;
}

//TODO: Migrations!
//TODO: Replace concatenations with binds '?'
<|endoftext|>


<commit_before>var util = require('util');
var bleno = require('bleno');
var redis = require("redis");

var publisher  = redis.createClient();
var channel = '/dbc/webui2uart';

var BlenoCharacteristic = bleno.Characteristic;

var characterID = '9a10ba1d-cd1c-4f00-9cca-1f3178d5fe8a';

var DBCCharacteristic = function() {
  DBCCharacteristic.super_.call(this, {
    uuid: characterID,
    properties: ['read', 'write', 'notify'],
    value: null
  });

  this._value = new Buffer(0);
  this._updateValueCallback = null;
};

util.inherits(DBCCharacteristic, BlenoCharacteristic);

DBCCharacteristic.prototype.onReadRequest = function(offset, callback) {
  console.log('DBCCharacteristic - onReadRequest: value = ' + this._value.toString('hex'));

  callback(this.RESULT_SUCCESS, this._value);
};

DBCCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
  this._value = data;

  //console.log('DBCCharacteristic - onWriteRequest: value = ' + this._value.toString('hex'));
  console.log('DBCCharacteristic - onWriteRequest: value=<',this._value.toString('hex'),'>');
  publisher.publish(channel, this._value).toString('utf8');
  if (this._updateValueCallback) {
    console.log('DBCCharacteristic - onWriteRequest: notifying');

    this._updateValueCallback(this._value);
  }

  callback(this.RESULT_SUCCESS);
};

DBCCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
  console.log('DBCCharacteristic - onSubscribe');

  this._updateValueCallback = updateValueCallback;
};

DBCCharacteristic.prototype.onUnsubscribe = function() {
  console.log('DBCCharacteristic - onUnsubscribe');

  this._updateValueCallback = null;
};

module.exports = DBCCharacteristic;
<commit_msg>Update characteristic.js<commit_after>var util = require('util');
var bleno = require('bleno');
var redis = require("redis");

var publisher  = redis.createClient();
var channel = '/dbc/webui2uart';

var BlenoCharacteristic = bleno.Characteristic;

var characterID = '9a10ba1d-cd1c-4f00-9cca-1f3178d5fe8a';

var DBCCharacteristic = function() {
  DBCCharacteristic.super_.call(this, {
    uuid: characterID,
    properties: ['read', 'write', 'notify'],
    value: null
  });

  this._value = new Buffer(0);
  this._updateValueCallback = null;
};

util.inherits(DBCCharacteristic, BlenoCharacteristic);

DBCCharacteristic.prototype.onReadRequest = function(offset, callback) {
  console.log('DBCCharacteristic - onReadRequest: value = ' + this._value.toString('hex'));

  callback(this.RESULT_SUCCESS, this._value);
};

DBCCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
  console.log('DBCCharacteristic - onWriteRequest: value=<',data,'>');
  console.log('DBCCharacteristic - onWriteRequest: offset=<',offset,'>');
  this._value = data;

  //console.log('DBCCharacteristic - onWriteRequest: value = ' + this._value.toString('hex'));
  console.log('DBCCharacteristic - onWriteRequest: value=<',this._value.toString('hex'),'>');
  publisher.publish(channel, this._value).toString('utf8');
  if (this._updateValueCallback) {
    console.log('DBCCharacteristic - onWriteRequest: notifying');

    this._updateValueCallback(this._value);
  }

  callback(this.RESULT_SUCCESS);
};

DBCCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
  console.log('DBCCharacteristic - onSubscribe');

  this._updateValueCallback = updateValueCallback;
};

DBCCharacteristic.prototype.onUnsubscribe = function() {
  console.log('DBCCharacteristic - onUnsubscribe');

  this._updateValueCallback = null;
};

module.exports = DBCCharacteristic;
<|endoftext|>


<commit_before>var factoryApp = angular.module('FactoryApp', []);

factoryApp.controller('ModuleTemplateCtrl', function ($scope, $http) {

	$scope.fields = [{}];

	$http.get('polyiigon-smart-field').success(function(data) {

		$scope.smartFields = data;
	});

	$scope.save = function(){

		if ($scope.moduleTemplateForm.$valid){
			data = {
				name: 'Teste',
				fields: $scope.fields
			}

			$http.post('polyiigon-module-template', data).success(function(data) {
				console.log(data);
			});
		} else {

			$scope.moduleTemplateForm.submited = true;
		}
	}

	$scope.doIt = function(){
		console.log($scope.fields);
	}
});<commit_msg>POST funcionando pro module template<commit_after>var factoryApp = angular.module('FactoryApp', []);

factoryApp.controller('ModuleTemplateCtrl', function ($scope, $http) {

	$scope.fields = [{}];

	$http.get('polyiigon-smart-field').success(function(data) {

		$scope.smartFields = data;
	});

	$scope.save = function(){

		if ($scope.moduleTemplateForm.$valid){
			data = {
				title: 'Teste',
				slug:'dfsdfsf',
				//fields: $scope.fields
			}

			$http.post('polyiigon-module-template/create', data).success(function(data) {
				console.log(data);
			});
		} else {

			$scope.moduleTemplateForm.submited = true;
		}
	}

	$scope.doIt = function(){
		console.log($scope.fields);
	}
});<|endoftext|>


<commit_before>var _ = require('lodash');
var assign = require('object-assign');
var async = require('async');
var FieldType = require('../Type');
var keystone = require('../../../');
var util = require('util');
var utils = require('keystone-utils');

function getEmptyValue () {
	return {
		public_id: '',
		version: 0,
		signature: '',
		format: '',
		resource_type: '',
		url: '',
		width: 0,
		height: 0,
		secure_url: '',
	};
}

function truthy (value) {
	return value;
}

/**
 * CloudinaryImages FieldType Constructor
 */
function cloudinaryimages (list, path, options) {
	this._underscoreMethods = ['format'];
	this._fixedSize = 'full';
	this._properties = ['select', 'selectPrefix', 'autoCleanup', 'publicID', 'folder', 'filenameAsPublicID'];

	// TODO: implement filtering, usage disabled for now
	options.nofilter = true;

	cloudinaryimages.super_.call(this, list, path, options);

	// validate cloudinary config
	if (!keystone.get('cloudinary config')) {
		throw new Error('Invalid Configuration\n\n'
			+ 'CloudinaryImages fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n'
			+ 'See http://keystonejs.com/docs/configuration/#services-cloudinary for more information.\n');
	}
}
cloudinaryimages.properName = 'CloudinaryImages';
util.inherits(cloudinaryimages, FieldType);

/**
 * Gets the folder for images in this field
 */
cloudinaryimages.prototype.getFolder = function () {
	var folder = null;

	if (keystone.get('cloudinary folders') || this.options.folder) {
		if (typeof this.options.folder === 'string') {
			folder = this.options.folder;
		} else {
			var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
			folderList.push(this.list.path);
			folderList.push(this.path);
			folder = folderList.join('/');
		}
	}

	return folder;
};

/**
 * Registers the field on the List's Mongoose Schema.
 */
cloudinaryimages.prototype.addToSchema = function (schema) {

	var cloudinary = require('cloudinary');
	var mongoose = keystone.mongoose;
	var field = this;

	this.paths = {
		// virtuals
		folder: this._path.append('.folder'),
		// form paths
		upload: this._path.append('_upload'),
		uploads: this._path.append('_uploads'),
		action: this._path.append('_action'),
		order: this._path.append('_order'),
	};

	var ImageSchema = new mongoose.Schema({
		public_id: String,
		version: Number,
		signature: String,
		format: String,
		resource_type: String,
		url: String,
		width: Number,
		height: Number,
		secure_url: String,
	});

	// Generate cloudinary folder used to upload/select images
	var folder = function (item) { // eslint-disable-line no-unused-vars
		var folderValue = '';

		if (keystone.get('cloudinary folders')) {
			if (field.options.folder) {
				folderValue = field.options.folder;
			} else {
				var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
				folderList.push(field.list.path);
				folderList.push(field.path);
				folderValue = folderList.join('/');
			}
		}

		return folderValue;
	};

	// The .folder virtual returns the cloudinary folder used to upload/select images
	schema.virtual(field.paths.folder).get(function () {
		return folder(this);
	});

	var src = function (img, options) {
		if (keystone.get('cloudinary secure')) {
			options = options || {};
			options.secure = true;
		}
		options.format = options.format || img.format;
		return img.public_id ? cloudinary.url(img.public_id, options) : '';
	};

	var addSize = function (options, width, height, other) {
		if (width) options.width = width;
		if (height) options.height = height;
		if (typeof other === 'object') {
			assign(options, other);
		}
		return options;
	};
	ImageSchema.method('src', function (options) {
		return src(this, options);
	});
	ImageSchema.method('scale', function (width, height, options) {
		return src(this, addSize({ crop: 'scale' }, width, height, options));
	});
	ImageSchema.method('fill', function (width, height, options) {
		return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('lfill', function (width, height, options) {
		return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('fit', function (width, height, options) {
		return src(this, addSize({ crop: 'fit' }, width, height, options));
	});
	ImageSchema.method('limit', function (width, height, options) {
		return src(this, addSize({ crop: 'limit' }, width, height, options));
	});
	ImageSchema.method('pad', function (width, height, options) {
		return src(this, addSize({ crop: 'pad' }, width, height, options));
	});
	ImageSchema.method('lpad', function (width, height, options) {
		return src(this, addSize({ crop: 'lpad' }, width, height, options));
	});
	ImageSchema.method('crop', function (width, height, options) {
		return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('thumbnail', function (width, height, options) {
		return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height, options));
	});

	schema.add(this._path.addTo({}, [ImageSchema]));

	this.removeImage = function (item, id, method, callback) {
		var images = item.get(field.path);
		if (typeof id !== 'number') {
			for (var i = 0; i < images.length; i++) {
				if (images[i].public_id === id) {
					id = i;
					break;
				}
			}
		}
		var img = images[id];
		if (!img) return;
		if (method === 'delete') {
			cloudinary.uploader.destroy(img.public_id, function () {});
		}
		images.splice(id, 1);
		if (callback) {
			item.save((typeof callback !== 'function') ? callback : undefined);
		}
	};
	this.underscoreMethod('remove', function (id, callback) {
		field.removeImage(this, id, 'remove', callback);
	});
	this.underscoreMethod('delete', function (id, callback) {
		field.removeImage(this, id, 'delete', callback);
	});
	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 */
cloudinaryimages.prototype.format = function (item) {
	return _.map(item.get(this.path), function (img) {
		return img.src();
	}).join(', ');
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * Deprecated
 */
cloudinaryimages.prototype.inputIsValid = function (data) { // eslint-disable-line no-unused-vars
	// TODO - how should image field input be validated?
	return true;
};

/**
 * Updates the value for this field in the item from a data object
 */
cloudinaryimages.prototype.updateItem = function (item, data, callback) {

	var cloudinary = require('cloudinary');
	var field = this;
	var values = this.getValueFromData(data);

	// Early exit path: reset value when falsy, or bail if no value was provided
	if (!values || values === 'null') {
		if (values !== undefined) {
			item.set(field.path, []);
		}
		return process.nextTick(callback);
	}

	// When the value exists, but isn't an array, turn it into one (this just
	// means a single field was submitted in the formdata)
	if (!Array.isArray(values)) {
		values = [values];
	}

	// Precalc these options, it's inefficient when not uploading images but
	// avoids recalculating them on each iteration in the map below
	// TODO: is this worth optimising?
	var tagPrefix = keystone.get('cloudinary prefix') || '';
	var uploadOptions = {
		tags: [],
	};
	if (tagPrefix.length) {
		uploadOptions.tags.push(tagPrefix);
		tagPrefix += '_';
	}
	uploadOptions.tags.push(tagPrefix + field.list.path + '_' + field.path);
	if (keystone.get('env') !== 'production') {
		uploadOptions.tags.push(tagPrefix + 'dev');
	}
	var folder = this.getFolder();
	if (folder) {
		uploadOptions.folder = folder;
	}

	async.map(values, function (value, next) {
		// When the value is a string, it may be JSON serialised data. If so, parse
		// it. Otherwiser, we assume it's base64 data or a remote URL and upload it
		// to cloudinary as a file path. More logic could be added here to
		// detect/prevent invalid uploads (as per cloudinaryimage field)
		if (typeof value === 'string') {
			if (value.charAt(0) === '{' && value.charAt(value.length - 1) === '}') {
				try {
					value = JSON.parse(value);
				} catch (e) {
					// value isn't JSON
				}
			} else {
				value = { path: value };
			}
		}
		if (typeof value === 'object' && value.uploadFromKey) {
			value = data[value.uploadFromKey];
		}
		if (typeof value === 'object' && 'public_id' in value) {
			// Cloudinary Image data provided
			if (value.public_id) {
				var v = assign(getEmptyValue(), value);
				return next(null, v);
			} else {
				return next(null);
			}
		} else if (typeof value === 'object' && value.path) {
			// File provided - upload it
			// NOTE: field.options.publicID has been deprecated (tbc)
			if (field.options.filenameAsPublicID && value.originalname && typeof value.originalname === 'string') {
				uploadOptions = assign({}, uploadOptions, {
					public_id: value.originalname.substring(0, value.originalname.lastIndexOf('.')),
				});
			}
			// TODO: implement autoCleanup; should delete existing images before uploading
			cloudinary.uploader.upload(value.path, function (result) {
				if (result.error) {
					next(result.error);
				} else {
					next(null, result);
				}
			}, uploadOptions);
		} else {
			// Nothing to do
			return next();
		}
	}, function (err, result) {
		if (err) return callback(err);
		result = result.filter(truthy);
		item.set(field.path, result);
		return callback();
	});
};

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` in syntax `delete:public_id,public_id|remove:public_id,public_id`
 * - `field.paths.upload` in `req.files` (uploads the images to cloudinary)
 */
cloudinaryimages.prototype.getRequestHandler = function (item, req, paths, callback) {

	var cloudinary = require('cloudinary');
	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function () {};

	return function () {

		// Order
		if (req.body[paths.order]) {
			var images = item.get(field.path);
			var newOrder = req.body[paths.order].split(',');

			images.sort(function (a, b) {
				return (newOrder.indexOf(a.public_id) > newOrder.indexOf(b.public_id)) ? 1 : -1;
			});
		}

		// Removals & Deletes
		if (req.body && req.body[paths.action]) {
			var actions = req.body[paths.action].split('|');

			actions.forEach(function (action) {
				action = action.split(':');
				var method = action[0];
				var ids = action[1];

				if (!method.match(/^(remove|delete)$/) || !ids) return;

				ids.split(',').forEach(function (id) {
					field.removeImage(item, id, method);
				});
			});
		}

		// Upload References (direct uploading)
		if (req.body[paths.uploads]) {
			var uploads = JSON.parse(req.body[paths.uploads]);

			uploads.forEach(function (file) {
				item.get(field.path).push(file);
			});
		}

		// Upload Data (form submissions)
		if (req.files && req.files[paths.upload]) {
			var files = [].concat(req.files[paths.upload]);

			var tp = keystone.get('cloudinary prefix') || '';

			if (tp.length) {
				tp += '_';
			}

			var uploadOptions = {
				tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id],
			};

			if (keystone.get('cloudinary folders')) {
				uploadOptions.folder = item.get(paths.folder);
			}

			if (keystone.get('cloudinary prefix')) {
				uploadOptions.tags.push(keystone.get('cloudinary prefix'));
			}

			if (keystone.get('env') !== 'production') {
				uploadOptions.tags.push(tp + 'dev');
			}

			async.each(files, function (file, next) {

				if (!file.size) return next();

				if (field.options.filenameAsPublicID) {
					uploadOptions.public_id = file.originalname.substring(0, file.originalname.lastIndexOf('.'));
				}

				cloudinary.uploader.upload(file.path, function (result) {
					if (result.error) {
						return next(result.error);
					} else {
						item.get(field.path).push(result);
						return next();
					}
				}, uploadOptions);

			}, function (err) {
				return callback(err);
			});
		} else {
			return callback();
		}
	};
};

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 */
cloudinaryimages.prototype.handleRequest = function (item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
};

/*!
 * Export class
 */
module.exports = cloudinaryimages;
<commit_msg>Minor cleanup in CloudinaryImagesType.js<commit_after>var _ = require('lodash');
var assign = require('object-assign');
var async = require('async');
var FieldType = require('../Type');
var keystone = require('../../../');
var util = require('util');
var utils = require('keystone-utils');

function getEmptyValue () {
	return {
		public_id: '',
		version: 0,
		signature: '',
		format: '',
		resource_type: '',
		url: '',
		width: 0,
		height: 0,
		secure_url: '',
	};
}

function truthy (value) {
	return value;
}

/**
 * CloudinaryImages FieldType Constructor
 */
function cloudinaryimages (list, path, options) {
	this._underscoreMethods = ['format'];
	this._fixedSize = 'full';
	this._properties = ['select', 'selectPrefix', 'autoCleanup', 'publicID', 'folder', 'filenameAsPublicID'];

	// TODO: implement filtering, usage disabled for now
	options.nofilter = true;

	cloudinaryimages.super_.call(this, list, path, options);

	// validate cloudinary config
	if (!keystone.get('cloudinary config')) {
		throw new Error('Invalid Configuration\n\n'
			+ 'CloudinaryImages fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n'
			+ 'See http://keystonejs.com/docs/configuration/#services-cloudinary for more information.\n');
	}
}
cloudinaryimages.properName = 'CloudinaryImages';
util.inherits(cloudinaryimages, FieldType);

/**
 * Gets the folder for images in this field
 */
cloudinaryimages.prototype.getFolder = function () {
	var folder = null;

	if (keystone.get('cloudinary folders') || this.options.folder) {
		if (typeof this.options.folder === 'string') {
			folder = this.options.folder;
		} else {
			var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
			folderList.push(this.list.path);
			folderList.push(this.path);
			folder = folderList.join('/');
		}
	}

	return folder;
};

/**
 * Registers the field on the List's Mongoose Schema.
 */
cloudinaryimages.prototype.addToSchema = function (schema) {

	var cloudinary = require('cloudinary');
	var mongoose = keystone.mongoose;
	var field = this;

	this.paths = {
		// virtuals
		folder: this._path.append('.folder'),
		// form paths
		upload: this._path.append('_upload'),
		uploads: this._path.append('_uploads'),
		action: this._path.append('_action'),
		order: this._path.append('_order'),
	};

	var ImageSchema = new mongoose.Schema({
		public_id: String,
		version: Number,
		signature: String,
		format: String,
		resource_type: String,
		url: String,
		width: Number,
		height: Number,
		secure_url: String,
	});

	// Generate cloudinary folder used to upload/select images
	var folder = function (item) { // eslint-disable-line no-unused-vars
		var folderValue = '';

		if (keystone.get('cloudinary folders')) {
			if (field.options.folder) {
				folderValue = field.options.folder;
			} else {
				var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
				folderList.push(field.list.path);
				folderList.push(field.path);
				folderValue = folderList.join('/');
			}
		}

		return folderValue;
	};

	// The .folder virtual returns the cloudinary folder used to upload/select images
	schema.virtual(field.paths.folder).get(function () {
		return folder(this);
	});

	var src = function (img, options) {
		if (keystone.get('cloudinary secure')) {
			options = options || {};
			options.secure = true;
		}
		options.format = options.format || img.format;
		return img.public_id ? cloudinary.url(img.public_id, options) : '';
	};

	var addSize = function (options, width, height, other) {
		if (width) options.width = width;
		if (height) options.height = height;
		if (typeof other === 'object') {
			assign(options, other);
		}
		return options;
	};
	ImageSchema.method('src', function (options) {
		return src(this, options);
	});
	ImageSchema.method('scale', function (width, height, options) {
		return src(this, addSize({ crop: 'scale' }, width, height, options));
	});
	ImageSchema.method('fill', function (width, height, options) {
		return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('lfill', function (width, height, options) {
		return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('fit', function (width, height, options) {
		return src(this, addSize({ crop: 'fit' }, width, height, options));
	});
	ImageSchema.method('limit', function (width, height, options) {
		return src(this, addSize({ crop: 'limit' }, width, height, options));
	});
	ImageSchema.method('pad', function (width, height, options) {
		return src(this, addSize({ crop: 'pad' }, width, height, options));
	});
	ImageSchema.method('lpad', function (width, height, options) {
		return src(this, addSize({ crop: 'lpad' }, width, height, options));
	});
	ImageSchema.method('crop', function (width, height, options) {
		return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('thumbnail', function (width, height, options) {
		return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height, options));
	});

	schema.add(this._path.addTo({}, [ImageSchema]));

	this.removeImage = function (item, id, method, callback) {
		var images = item.get(field.path);
		if (typeof id !== 'number') {
			for (var i = 0; i < images.length; i++) {
				if (images[i].public_id === id) {
					id = i;
					break;
				}
			}
		}
		var img = images[id];
		if (!img) return;
		if (method === 'delete') {
			cloudinary.uploader.destroy(img.public_id, function () {});
		}
		images.splice(id, 1);
		if (callback) {
			item.save((typeof callback !== 'function') ? callback : undefined);
		}
	};
	this.underscoreMethod('remove', function (id, callback) {
		field.removeImage(this, id, 'remove', callback);
	});
	this.underscoreMethod('delete', function (id, callback) {
		field.removeImage(this, id, 'delete', callback);
	});
	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 */
cloudinaryimages.prototype.format = function (item) {
	return _.map(item.get(this.path), function (img) {
		return img.src();
	}).join(', ');
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * Deprecated
 */
cloudinaryimages.prototype.inputIsValid = function (data) { // eslint-disable-line no-unused-vars
	// TODO - how should image field input be validated?
	return true;
};

/**
 * Updates the value for this field in the item from a data object
 */
cloudinaryimages.prototype.updateItem = function (item, data, callback) {

	var cloudinary = require('cloudinary');
	var field = this;
	var values = this.getValueFromData(data);

	// Early exit path: reset value when falsy, or bail if no value was provided
	if (!values) {
		if (values !== undefined) {
			item.set(field.path, []);
		}
		return process.nextTick(callback);
	}

	// When the value exists, but isn't an array, turn it into one (this just
	// means a single field was submitted in the formdata)
	if (!Array.isArray(values)) {
		values = [values];
	}

	// Precalc these options, it's inefficient when not uploading images but
	// avoids recalculating them on each iteration in the map below
	// TODO: is this worth optimising?
	var tagPrefix = keystone.get('cloudinary prefix') || '';
	var uploadOptions = {
		tags: [],
	};
	if (tagPrefix.length) {
		uploadOptions.tags.push(tagPrefix);
		tagPrefix += '_';
	}
	uploadOptions.tags.push(tagPrefix + field.list.path + '_' + field.path);
	if (keystone.get('env') !== 'production') {
		uploadOptions.tags.push(tagPrefix + 'dev');
	}
	var folder = this.getFolder();
	if (folder) {
		uploadOptions.folder = folder;
	}

	async.map(values, function (value, next) {
		// TODO: Do we actually want to support JSON encoded data here? Seems messy

		// When the value is a string, it may be JSON serialised data. If so, parse
		// it. Otherwiser, we assume it's base64 data or a remote URL and upload it
		// to cloudinary as a file path. More logic could be added here to
		// detect/prevent invalid uploads (as per cloudinaryimage field)
		if (typeof value === 'string') {
			if (value.charAt(0) === '{' && value.charAt(value.length - 1) === '}') {
				try {
					value = JSON.parse(value);
				} catch (e) {
					// value isn't JSON
				}
			} else {
				value = { path: value };
			}
		}
		if (typeof value === 'object' && value.uploadFromKey) {
			value = data[value.uploadFromKey];
		}
		if (typeof value === 'object' && 'public_id' in value) {
			// Cloudinary Image data provided
			if (value.public_id) {
				var v = assign(getEmptyValue(), value);
				return next(null, v);
			} else {
				return next(null);
			}
		} else if (typeof value === 'object' && value.path) {
			// File provided - upload it
			// NOTE: field.options.publicID has been deprecated (tbc)
			if (field.options.filenameAsPublicID && value.originalname && typeof value.originalname === 'string') {
				uploadOptions = assign({}, uploadOptions, {
					public_id: value.originalname.substring(0, value.originalname.lastIndexOf('.')),
				});
			}
			// TODO: implement autoCleanup; should delete existing images before uploading
			cloudinary.uploader.upload(value.path, function (result) {
				if (result.error) {
					next(result.error);
				} else {
					next(null, result);
				}
			}, uploadOptions);
		} else {
			// Nothing to do
			return next();
		}
	}, function (err, result) {
		if (err) return callback(err);
		result = result.filter(truthy);
		item.set(field.path, result);
		return callback();
	});
};

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` in syntax `delete:public_id,public_id|remove:public_id,public_id`
 * - `field.paths.upload` in `req.files` (uploads the images to cloudinary)
 */
cloudinaryimages.prototype.getRequestHandler = function (item, req, paths, callback) {

	var cloudinary = require('cloudinary');
	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function () {};

	return function () {

		// Order
		if (req.body[paths.order]) {
			var images = item.get(field.path);
			var newOrder = req.body[paths.order].split(',');

			images.sort(function (a, b) {
				return (newOrder.indexOf(a.public_id) > newOrder.indexOf(b.public_id)) ? 1 : -1;
			});
		}

		// Removals & Deletes
		if (req.body && req.body[paths.action]) {
			var actions = req.body[paths.action].split('|');

			actions.forEach(function (action) {
				action = action.split(':');
				var method = action[0];
				var ids = action[1];

				if (!method.match(/^(remove|delete)$/) || !ids) return;

				ids.split(',').forEach(function (id) {
					field.removeImage(item, id, method);
				});
			});
		}

		// Upload References (direct uploading)
		if (req.body[paths.uploads]) {
			var uploads = JSON.parse(req.body[paths.uploads]);

			uploads.forEach(function (file) {
				item.get(field.path).push(file);
			});
		}

		// Upload Data (form submissions)
		if (req.files && req.files[paths.upload]) {
			var files = [].concat(req.files[paths.upload]);

			var tp = keystone.get('cloudinary prefix') || '';

			if (tp.length) {
				tp += '_';
			}

			var uploadOptions = {
				tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id],
			};

			if (keystone.get('cloudinary folders')) {
				uploadOptions.folder = item.get(paths.folder);
			}

			if (keystone.get('cloudinary prefix')) {
				uploadOptions.tags.push(keystone.get('cloudinary prefix'));
			}

			if (keystone.get('env') !== 'production') {
				uploadOptions.tags.push(tp + 'dev');
			}

			async.each(files, function (file, next) {

				if (!file.size) return next();

				if (field.options.filenameAsPublicID) {
					uploadOptions.public_id = file.originalname.substring(0, file.originalname.lastIndexOf('.'));
				}

				cloudinary.uploader.upload(file.path, function (result) {
					if (result.error) {
						return next(result.error);
					} else {
						item.get(field.path).push(result);
						return next();
					}
				}, uploadOptions);

			}, function (err) {
				return callback(err);
			});
		} else {
			return callback();
		}
	};
};

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 */
cloudinaryimages.prototype.handleRequest = function (item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
};

/*!
 * Export class
 */
module.exports = cloudinaryimages;
<|endoftext|>


<commit_before>var _ = require('lodash');
var assign = require('object-assign');
var async = require('async');
var FieldType = require('../Type');
var keystone = require('../../../');
var util = require('util');
var utils = require('keystone-utils');

function getEmptyValue () {
	return {
		public_id: '',
		version: 0,
		signature: '',
		format: '',
		resource_type: '',
		url: '',
		width: 0,
		height: 0,
		secure_url: '',
	};
}

function truthy (value) {
	return value;
}

/**
 * CloudinaryImages FieldType Constructor
 */
function cloudinaryimages (list, path, options) {
	this._underscoreMethods = ['format'];
	this._fixedSize = 'full';
	this._properties = ['select', 'selectPrefix', 'autoCleanup', 'publicID', 'folder', 'filenameAsPublicID'];

	// TODO: implement filtering, usage disabled for now
	options.nofilter = true;

	cloudinaryimages.super_.call(this, list, path, options);

	// validate cloudinary config
	if (!keystone.get('cloudinary config')) {
		throw new Error('Invalid Configuration\n\n'
			+ 'CloudinaryImages fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n'
			+ 'See http://keystonejs.com/docs/configuration/#services-cloudinary for more information.\n');
	}
}
cloudinaryimages.properName = 'CloudinaryImages';
util.inherits(cloudinaryimages, FieldType);

/**
 * Gets the folder for images in this field
 */
cloudinaryimages.prototype.getFolder = function () {
	var folder = null;

	if (keystone.get('cloudinary folders') || this.options.folder) {
		if (typeof this.options.folder === 'string') {
			folder = this.options.folder;
		} else {
			var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
			folderList.push(this.list.path);
			folderList.push(this.path);
			folder = folderList.join('/');
		}
	}

	return folder;
};

/**
 * Registers the field on the List's Mongoose Schema.
 */
cloudinaryimages.prototype.addToSchema = function (schema) {

	var cloudinary = require('cloudinary');
	var mongoose = keystone.mongoose;
	var field = this;

	this.paths = {
		// virtuals
		folder: this._path.append('.folder'),
		// form paths
		upload: this._path.append('_upload'),
		uploads: this._path.append('_uploads'),
		action: this._path.append('_action'),
		order: this._path.append('_order'),
	};

	var ImageSchema = new mongoose.Schema({
		public_id: String,
		version: Number,
		signature: String,
		format: String,
		resource_type: String,
		url: String,
		width: Number,
		height: Number,
		secure_url: String,
	});

	// Generate cloudinary folder used to upload/select images
	var folder = function (item) { // eslint-disable-line no-unused-vars
		var folderValue = '';

		if (keystone.get('cloudinary folders')) {
			if (field.options.folder) {
				folderValue = field.options.folder;
			} else {
				var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
				folderList.push(field.list.path);
				folderList.push(field.path);
				folderValue = folderList.join('/');
			}
		}

		return folderValue;
	};

	// The .folder virtual returns the cloudinary folder used to upload/select images
	schema.virtual(field.paths.folder).get(function () {
		return folder(this);
	});

	var src = function (img, options) {
		if (keystone.get('cloudinary secure')) {
			options = options || {};
			options.secure = true;
		}
		options.format = options.format || img.format;
		return img.public_id ? cloudinary.url(img.public_id, options) : '';
	};

	var addSize = function (options, width, height, other) {
		if (width) options.width = width;
		if (height) options.height = height;
		if (typeof other === 'object') {
			assign(options, other);
		}
		return options;
	};
	ImageSchema.method('src', function (options) {
		return src(this, options);
	});
	ImageSchema.method('scale', function (width, height, options) {
		return src(this, addSize({ crop: 'scale' }, width, height, options));
	});
	ImageSchema.method('fill', function (width, height, options) {
		return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('lfill', function (width, height, options) {
		return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('fit', function (width, height, options) {
		return src(this, addSize({ crop: 'fit' }, width, height, options));
	});
	ImageSchema.method('limit', function (width, height, options) {
		return src(this, addSize({ crop: 'limit' }, width, height, options));
	});
	ImageSchema.method('pad', function (width, height, options) {
		return src(this, addSize({ crop: 'pad' }, width, height, options));
	});
	ImageSchema.method('lpad', function (width, height, options) {
		return src(this, addSize({ crop: 'lpad' }, width, height, options));
	});
	ImageSchema.method('crop', function (width, height, options) {
		return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('thumbnail', function (width, height, options) {
		return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height, options));
	});

	schema.add(this._path.addTo({}, [ImageSchema]));

	this.removeImage = function (item, id, method, callback) {
		var images = item.get(field.path);
		if (typeof id !== 'number') {
			for (var i = 0; i < images.length; i++) {
				if (images[i].public_id === id) {
					id = i;
					break;
				}
			}
		}
		var img = images[id];
		if (!img) return;
		if (method === 'delete') {
			cloudinary.uploader.destroy(img.public_id, function () {});
		}
		images.splice(id, 1);
		if (callback) {
			item.save((typeof callback !== 'function') ? callback : undefined);
		}
	};
	this.underscoreMethod('remove', function (id, callback) {
		field.removeImage(this, id, 'remove', callback);
	});
	this.underscoreMethod('delete', function (id, callback) {
		field.removeImage(this, id, 'delete', callback);
	});
	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 */
cloudinaryimages.prototype.format = function (item) {
	return _.map(item.get(this.path), function (img) {
		return img.src();
	}).join(', ');
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * Deprecated
 */
cloudinaryimages.prototype.inputIsValid = function (data) { // eslint-disable-line no-unused-vars
	// TODO - how should image field input be validated?
	return true;
};

/**
 * Updates the value for this field in the item from a data object
 */
cloudinaryimages.prototype.updateItem = function (item, data, callback) {

	var cloudinary = require('cloudinary');
	var field = this;
	var values = this.getValueFromData(data);

	// Early exit path: reset value when falsy, or bail if no value was provided
	if (!values) {
		if (values !== undefined) {
			item.set(field.path, []);
		}
		return process.nextTick(callback);
	}

	// When the value exists, but isn't an array, turn it into one (this just
	// means a single field was submitted in the formdata)
	if (!Array.isArray(values)) {
		values = [values];
	}

	// We cache options to avoid recalculating them on each iteration in the map below
	var cachedUploadOptions;
	function getUploadOptions () {
		if (cachedUploadOptions) {
			return cachedUploadOptions;
		}
		var tagPrefix = keystone.get('cloudinary prefix') || '';
		var uploadOptions = {
			tags: [],
		};
		if (tagPrefix.length) {
			uploadOptions.tags.push(tagPrefix);
			tagPrefix += '_';
		}
		uploadOptions.tags.push(tagPrefix + field.list.path + '_' + field.path);
		if (keystone.get('env') !== 'production') {
			uploadOptions.tags.push(tagPrefix + 'dev');
		}
		var folder = this.getFolder();
		if (folder) {
			uploadOptions.folder = folder;
		}
		cachedUploadOptions = uploadOptions;
		return uploadOptions;
	}

	async.map(values, function (value, next) {
		// TODO: Do we actually want to support JSON encoded data here? Seems messy

		// When the value is a string, it may be JSON serialised data. If so, parse
		// it. Otherwiser, we assume it's base64 data or a remote URL and upload it
		// to cloudinary as a file path. More logic could be added here to
		// detect/prevent invalid uploads (as per cloudinaryimage field)
		if (typeof value === 'string') {
			if (value.charAt(0) === '{' && value.charAt(value.length - 1) === '}') {
				try {
					value = JSON.parse(value);
				} catch (e) {
					// value isn't JSON
				}
			} else {
				value = { path: value };
			}
		}
		if (typeof value === 'object' && value.uploadFromKey) {
			value = data[value.uploadFromKey];
		}
		if (typeof value === 'object' && 'public_id' in value) {
			// Cloudinary Image data provided
			if (value.public_id) {
				var v = assign(getEmptyValue(), value);
				return next(null, v);
			} else {
				return next(null);
			}
		} else if (typeof value === 'object' && value.path) {
			// File provided - upload it
			var uploadOptions = getUploadOptions();
			// NOTE: field.options.publicID has been deprecated (tbc)
			if (field.options.filenameAsPublicID && value.originalname && typeof value.originalname === 'string') {
				uploadOptions = assign({}, uploadOptions, {
					public_id: value.originalname.substring(0, value.originalname.lastIndexOf('.')),
				});
			}
			// TODO: implement autoCleanup; should delete existing images before uploading
			cloudinary.uploader.upload(value.path, function (result) {
				if (result.error) {
					next(result.error);
				} else {
					next(null, result);
				}
			}, uploadOptions);
		} else {
			// Nothing to do
			return next();
		}
	}, function (err, result) {
		if (err) return callback(err);
		result = result.filter(truthy);
		item.set(field.path, result);
		return callback();
	});
};

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` in syntax `delete:public_id,public_id|remove:public_id,public_id`
 * - `field.paths.upload` in `req.files` (uploads the images to cloudinary)
 */
cloudinaryimages.prototype.getRequestHandler = function (item, req, paths, callback) {

	var cloudinary = require('cloudinary');
	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function () {};

	return function () {

		// Order
		if (req.body[paths.order]) {
			var images = item.get(field.path);
			var newOrder = req.body[paths.order].split(',');

			images.sort(function (a, b) {
				return (newOrder.indexOf(a.public_id) > newOrder.indexOf(b.public_id)) ? 1 : -1;
			});
		}

		// Removals & Deletes
		if (req.body && req.body[paths.action]) {
			var actions = req.body[paths.action].split('|');

			actions.forEach(function (action) {
				action = action.split(':');
				var method = action[0];
				var ids = action[1];

				if (!method.match(/^(remove|delete)$/) || !ids) return;

				ids.split(',').forEach(function (id) {
					field.removeImage(item, id, method);
				});
			});
		}

		// Upload References (direct uploading)
		if (req.body[paths.uploads]) {
			var uploads = JSON.parse(req.body[paths.uploads]);

			uploads.forEach(function (file) {
				item.get(field.path).push(file);
			});
		}

		// Upload Data (form submissions)
		if (req.files && req.files[paths.upload]) {
			var files = [].concat(req.files[paths.upload]);

			var tp = keystone.get('cloudinary prefix') || '';

			if (tp.length) {
				tp += '_';
			}

			var uploadOptions = {
				tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id],
			};

			if (keystone.get('cloudinary folders')) {
				uploadOptions.folder = item.get(paths.folder);
			}

			if (keystone.get('cloudinary prefix')) {
				uploadOptions.tags.push(keystone.get('cloudinary prefix'));
			}

			if (keystone.get('env') !== 'production') {
				uploadOptions.tags.push(tp + 'dev');
			}

			async.each(files, function (file, next) {

				if (!file.size) return next();

				if (field.options.filenameAsPublicID) {
					uploadOptions.public_id = file.originalname.substring(0, file.originalname.lastIndexOf('.'));
				}

				cloudinary.uploader.upload(file.path, function (result) {
					if (result.error) {
						return next(result.error);
					} else {
						item.get(field.path).push(result);
						return next();
					}
				}, uploadOptions);

			}, function (err) {
				return callback(err);
			});
		} else {
			return callback();
		}
	};
};

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 */
cloudinaryimages.prototype.handleRequest = function (item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
};

/*!
 * Export class
 */
module.exports = cloudinaryimages;
<commit_msg>Removing `cloudinary prefix` option from the default cloudinary folders<commit_after>var _ = require('lodash');
var assign = require('object-assign');
var async = require('async');
var FieldType = require('../Type');
var keystone = require('../../../');
var util = require('util');
var utils = require('keystone-utils');

function getEmptyValue () {
	return {
		public_id: '',
		version: 0,
		signature: '',
		format: '',
		resource_type: '',
		url: '',
		width: 0,
		height: 0,
		secure_url: '',
	};
}

function truthy (value) {
	return value;
}

/**
 * CloudinaryImages FieldType Constructor
 */
function cloudinaryimages (list, path, options) {
	this._underscoreMethods = ['format'];
	this._fixedSize = 'full';
	this._properties = ['select', 'selectPrefix', 'autoCleanup', 'publicID', 'folder', 'filenameAsPublicID'];

	// TODO: implement filtering, usage disabled for now
	options.nofilter = true;

	cloudinaryimages.super_.call(this, list, path, options);

	// validate cloudinary config
	if (!keystone.get('cloudinary config')) {
		throw new Error('Invalid Configuration\n\n'
			+ 'CloudinaryImages fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n'
			+ 'See http://keystonejs.com/docs/configuration/#services-cloudinary for more information.\n');
	}
}
cloudinaryimages.properName = 'CloudinaryImages';
util.inherits(cloudinaryimages, FieldType);

/**
 * Gets the folder for images in this field
 */
cloudinaryimages.prototype.getFolder = function () {
	var folder = null;

	if (keystone.get('cloudinary folders') || this.options.folder) {
		if (typeof this.options.folder === 'string') {
			folder = this.options.folder;
		} else {
			folder = this.list.path + '/' + this.path;
		}
	}

	return folder;
};

/**
 * Registers the field on the List's Mongoose Schema.
 */
cloudinaryimages.prototype.addToSchema = function (schema) {

	var cloudinary = require('cloudinary');
	var mongoose = keystone.mongoose;
	var field = this;

	this.paths = {
		// virtuals
		folder: this._path.append('.folder'),
		// form paths
		upload: this._path.append('_upload'),
		uploads: this._path.append('_uploads'),
		action: this._path.append('_action'),
		order: this._path.append('_order'),
	};

	var ImageSchema = new mongoose.Schema({
		public_id: String,
		version: Number,
		signature: String,
		format: String,
		resource_type: String,
		url: String,
		width: Number,
		height: Number,
		secure_url: String,
	});

	// Generate cloudinary folder used to upload/select images
	var folder = function (item) { // eslint-disable-line no-unused-vars
		var folderValue = '';

		if (keystone.get('cloudinary folders')) {
			if (field.options.folder) {
				folderValue = field.options.folder;
			} else {
				var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
				folderList.push(field.list.path);
				folderList.push(field.path);
				folderValue = folderList.join('/');
			}
		}

		return folderValue;
	};

	// The .folder virtual returns the cloudinary folder used to upload/select images
	schema.virtual(field.paths.folder).get(function () {
		return folder(this);
	});

	var src = function (img, options) {
		if (keystone.get('cloudinary secure')) {
			options = options || {};
			options.secure = true;
		}
		options.format = options.format || img.format;
		return img.public_id ? cloudinary.url(img.public_id, options) : '';
	};

	var addSize = function (options, width, height, other) {
		if (width) options.width = width;
		if (height) options.height = height;
		if (typeof other === 'object') {
			assign(options, other);
		}
		return options;
	};
	ImageSchema.method('src', function (options) {
		return src(this, options);
	});
	ImageSchema.method('scale', function (width, height, options) {
		return src(this, addSize({ crop: 'scale' }, width, height, options));
	});
	ImageSchema.method('fill', function (width, height, options) {
		return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('lfill', function (width, height, options) {
		return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('fit', function (width, height, options) {
		return src(this, addSize({ crop: 'fit' }, width, height, options));
	});
	ImageSchema.method('limit', function (width, height, options) {
		return src(this, addSize({ crop: 'limit' }, width, height, options));
	});
	ImageSchema.method('pad', function (width, height, options) {
		return src(this, addSize({ crop: 'pad' }, width, height, options));
	});
	ImageSchema.method('lpad', function (width, height, options) {
		return src(this, addSize({ crop: 'lpad' }, width, height, options));
	});
	ImageSchema.method('crop', function (width, height, options) {
		return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height, options));
	});
	ImageSchema.method('thumbnail', function (width, height, options) {
		return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height, options));
	});

	schema.add(this._path.addTo({}, [ImageSchema]));

	this.removeImage = function (item, id, method, callback) {
		var images = item.get(field.path);
		if (typeof id !== 'number') {
			for (var i = 0; i < images.length; i++) {
				if (images[i].public_id === id) {
					id = i;
					break;
				}
			}
		}
		var img = images[id];
		if (!img) return;
		if (method === 'delete') {
			cloudinary.uploader.destroy(img.public_id, function () {});
		}
		images.splice(id, 1);
		if (callback) {
			item.save((typeof callback !== 'function') ? callback : undefined);
		}
	};
	this.underscoreMethod('remove', function (id, callback) {
		field.removeImage(this, id, 'remove', callback);
	});
	this.underscoreMethod('delete', function (id, callback) {
		field.removeImage(this, id, 'delete', callback);
	});
	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 */
cloudinaryimages.prototype.format = function (item) {
	return _.map(item.get(this.path), function (img) {
		return img.src();
	}).join(', ');
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * Deprecated
 */
cloudinaryimages.prototype.inputIsValid = function (data) { // eslint-disable-line no-unused-vars
	// TODO - how should image field input be validated?
	return true;
};

/**
 * Updates the value for this field in the item from a data object
 */
cloudinaryimages.prototype.updateItem = function (item, data, callback) {

	var cloudinary = require('cloudinary');
	var field = this;
	var values = this.getValueFromData(data);

	// Early exit path: reset value when falsy, or bail if no value was provided
	if (!values) {
		if (values !== undefined) {
			item.set(field.path, []);
		}
		return process.nextTick(callback);
	}

	// When the value exists, but isn't an array, turn it into one (this just
	// means a single field was submitted in the formdata)
	if (!Array.isArray(values)) {
		values = [values];
	}

	// We cache options to avoid recalculating them on each iteration in the map below
	var cachedUploadOptions;
	function getUploadOptions () {
		if (cachedUploadOptions) {
			return cachedUploadOptions;
		}
		var tagPrefix = keystone.get('cloudinary prefix') || '';
		var uploadOptions = {
			tags: [],
		};
		if (tagPrefix.length) {
			uploadOptions.tags.push(tagPrefix);
			tagPrefix += '_';
		}
		uploadOptions.tags.push(tagPrefix + field.list.path + '_' + field.path);
		if (keystone.get('env') !== 'production') {
			uploadOptions.tags.push(tagPrefix + 'dev');
		}
		var folder = this.getFolder();
		if (folder) {
			uploadOptions.folder = folder;
		}
		cachedUploadOptions = uploadOptions;
		return uploadOptions;
	}

	async.map(values, function (value, next) {
		// TODO: Do we actually want to support JSON encoded data here? Seems messy

		// When the value is a string, it may be JSON serialised data. If so, parse
		// it. Otherwiser, we assume it's base64 data or a remote URL and upload it
		// to cloudinary as a file path. More logic could be added here to
		// detect/prevent invalid uploads (as per cloudinaryimage field)
		if (typeof value === 'string') {
			if (value.charAt(0) === '{' && value.charAt(value.length - 1) === '}') {
				try {
					value = JSON.parse(value);
				} catch (e) {
					// value isn't JSON
				}
			} else {
				value = { path: value };
			}
		}
		if (typeof value === 'object' && value.uploadFromKey) {
			value = data[value.uploadFromKey];
		}
		if (typeof value === 'object' && 'public_id' in value) {
			// Cloudinary Image data provided
			if (value.public_id) {
				var v = assign(getEmptyValue(), value);
				return next(null, v);
			} else {
				return next(null);
			}
		} else if (typeof value === 'object' && value.path) {
			// File provided - upload it
			var uploadOptions = getUploadOptions();
			// NOTE: field.options.publicID has been deprecated (tbc)
			if (field.options.filenameAsPublicID && value.originalname && typeof value.originalname === 'string') {
				uploadOptions = assign({}, uploadOptions, {
					public_id: value.originalname.substring(0, value.originalname.lastIndexOf('.')),
				});
			}
			// TODO: implement autoCleanup; should delete existing images before uploading
			cloudinary.uploader.upload(value.path, function (result) {
				if (result.error) {
					next(result.error);
				} else {
					next(null, result);
				}
			}, uploadOptions);
		} else {
			// Nothing to do
			return next();
		}
	}, function (err, result) {
		if (err) return callback(err);
		result = result.filter(truthy);
		item.set(field.path, result);
		return callback();
	});
};

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` in syntax `delete:public_id,public_id|remove:public_id,public_id`
 * - `field.paths.upload` in `req.files` (uploads the images to cloudinary)
 */
cloudinaryimages.prototype.getRequestHandler = function (item, req, paths, callback) {

	var cloudinary = require('cloudinary');
	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function () {};

	return function () {

		// Order
		if (req.body[paths.order]) {
			var images = item.get(field.path);
			var newOrder = req.body[paths.order].split(',');

			images.sort(function (a, b) {
				return (newOrder.indexOf(a.public_id) > newOrder.indexOf(b.public_id)) ? 1 : -1;
			});
		}

		// Removals & Deletes
		if (req.body && req.body[paths.action]) {
			var actions = req.body[paths.action].split('|');

			actions.forEach(function (action) {
				action = action.split(':');
				var method = action[0];
				var ids = action[1];

				if (!method.match(/^(remove|delete)$/) || !ids) return;

				ids.split(',').forEach(function (id) {
					field.removeImage(item, id, method);
				});
			});
		}

		// Upload References (direct uploading)
		if (req.body[paths.uploads]) {
			var uploads = JSON.parse(req.body[paths.uploads]);

			uploads.forEach(function (file) {
				item.get(field.path).push(file);
			});
		}

		// Upload Data (form submissions)
		if (req.files && req.files[paths.upload]) {
			var files = [].concat(req.files[paths.upload]);

			var tp = keystone.get('cloudinary prefix') || '';

			if (tp.length) {
				tp += '_';
			}

			var uploadOptions = {
				tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id],
			};

			if (keystone.get('cloudinary folders')) {
				uploadOptions.folder = item.get(paths.folder);
			}

			if (keystone.get('cloudinary prefix')) {
				uploadOptions.tags.push(keystone.get('cloudinary prefix'));
			}

			if (keystone.get('env') !== 'production') {
				uploadOptions.tags.push(tp + 'dev');
			}

			async.each(files, function (file, next) {

				if (!file.size) return next();

				if (field.options.filenameAsPublicID) {
					uploadOptions.public_id = file.originalname.substring(0, file.originalname.lastIndexOf('.'));
				}

				cloudinary.uploader.upload(file.path, function (result) {
					if (result.error) {
						return next(result.error);
					} else {
						item.get(field.path).push(result);
						return next();
					}
				}, uploadOptions);

			}, function (err) {
				return callback(err);
			});
		} else {
			return callback();
		}
	};
};

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 */
cloudinaryimages.prototype.handleRequest = function (item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
};

/*!
 * Export class
 */
module.exports = cloudinaryimages;
<|endoftext|>