'use strict'; /** * Module dependencies. */ var mongoose = require('mongoose'), Schema = mongoose.Schema, pdfFiller = require('pdffiller'), _ = require('lodash'), config = require('../../config/config'), path = require('path'), mUtilities = require('mongoose-utilities'), fs = require('fs-extra'), async = require('async'), util = require('util'); //Mongoose Models var FieldSchema = require('./form_field.server.model.js'); var Field = mongoose.model('Field'); var FormSubmissionSchema = require('./form_submission.server.model.js'), FormSubmission = mongoose.model('FormSubmission', FormSubmissionSchema); var ButtonSchema = new Schema({ url: { type: String, match: [/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/], }, action: String, text: String, bgColor: { type: String, match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/], default: '#5bc0de' }, color: { type: String, match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/], default: '#ffffff' } }); /** * Form Schema */ var FormSchema = new Schema({ title: { type: String, trim: true, required: 'Form Title cannot be blank', }, language: { type: String, enum: ['english', 'french', 'spanish'], required: 'Form must have a language', default: 'english' }, description: { type: String, default: '' }, form_fields: { type: [FieldSchema] }, submissions: [{ type: Schema.Types.ObjectId, ref: 'FormSubmission' }], admin: { type: Schema.Types.ObjectId, ref: 'User', required: 'Form must have an Admin' }, pdf: { type: Schema.Types.Mixed }, pdfFieldMap: { type: Schema.Types.Mixed }, startPage: { showStart:{ type: Boolean, default: false, }, introTitle:{ type: String, default: 'Welcome to Form' }, introParagraph:{ type: String }, introButtonText:{ type: String, default: 'Start' }, buttons:[ButtonSchema] }, hideFooter: { type: Boolean, default: false }, isGenerated: { type: Boolean, default: false }, isLive: { type: Boolean, default: false }, autofillPDFs: { type: Boolean, default: false }, design: { colors:{ backgroundColor: { type: String, match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/], default: '#fff' }, questionColor: { type: String, match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/], default: '#333' }, answerColor: { type: String, match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/], default: '#333' }, buttonColor: { type: String, match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/], default: '#fff' }, buttonTextColor: { type: String, match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/], default: '#333' }, }, font: String, backgroundImage: { type: Schema.Types.Mixed } }, plugins: { oscarhost: { baseUrl: { type: String, }, settings: { lookupField: { type: Schema.Types.ObjectId, ref: 'Field' }, updateType: { type: String, enum: ['upsert', 'force_add', 'force_update', 'fetch'], }, fieldMap: { type: Schema.Types.Mixed, }, validUpdateTypes: { type: [String] }, validFields : { type: [String], default: [ 'address', 'city', 'email', 'firstName', 'hin', 'lastName', 'phone', 'postal', 'province', 'sex', 'spokenLanguage', 'title', 'DOB'] } }, auth: { user: { type: String, }, pass: { type: String, } } } } }); FormSchema.plugin(mUtilities.timestamp, { createdPath: 'created', modifiedPath: 'lastModified', useVirtual: false }); //DAVID: TODO: Make this so we don't have to update the validFields property ever save FormSchema.pre('save', function (next) { if(this.plugins.oscarhost.hasOwnProperty('baseUrl')){ var validUpdateTypes= mongoose.model('Form').schema.path('plugins.oscarhost.settings.updateType').enumValues; this.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes; } next(); }); //Delete template PDF of current Form FormSchema.pre('remove', function (next) { if(this.pdf && process.env.NODE_ENV === 'development'){ //Delete template form fs.unlink(this.pdf.path, function(err){ if (err) throw err; console.log('successfully deleted', this.pdf.path); }); } }); var _original; //Set _original FormSchema.pre('save', function (next) { this.constructor .findOne({_id: this._id}).exec(function(err, original){ if(err) { console.log(err); next(err); } else { _original = original; //console.log('_original'); // console.log(_original); next(); } }); }); function getDeletedIndexes(needle, haystack){ var deletedIndexes = []; if(haystack.length > 0){ for(var i = 0; i < needle.length; i++){ if(haystack.indexOf(needle[i]) === -1){ deletedIndexes.push(i); } } } return deletedIndexes; } //Move PDF to permanent location after new template is uploaded FormSchema.pre('save', function (next) { if(this.pdf){ var that = this; async.series([ function(callback){ if(that.isModified('pdf') && that.pdf.path){ var new_filename = that.title.replace(/ /g,'')+'_template.pdf'; var newDestination = path.join(config.pdfUploadPath, that.admin.username.replace(/ /g,''), that.title.replace(/ /g,'')), stat = null; try { stat = fs.statSync(newDestination); } catch (err) { fs.mkdirSync(newDestination); } if (stat && !stat.isDirectory()) { return callback( new Error('Directory cannot be created because an inode of a different type exists at "' + config.pdfUploadPath + '"'), null); } var old_path = that.pdf.path; fs.move(old_path, path.join(newDestination, new_filename), {clobber: true}, function (err) { if (err) { console.error(err); callback( new Error(err.message), 'task1'); }else { that.pdf.path = path.join(newDestination, new_filename); that.pdf.name = new_filename; callback(null,'task1'); } }); }else { callback(null,'task1'); } }, function(callback){ if(that.isGenerated){ that.pdf.path = path.join(config.pdfUploadPath, that.admin.username.replace(/ /g,''), that.title.replace(/ /g,''), that.title.replace(/ /g,'')+'_template.pdf'); that.pdf.name = that.title.replace(/ /g,'')+'_template.pdf'; var _typeConvMap = { 'Multiline': 'textarea', 'Text': 'textfield', 'Button': 'checkbox', 'Choice': 'radio', 'Password': 'password', 'FileSelect': 'filefield', 'Radio': 'radio' }; // console.log('autogenerating form'); // console.log(that.pdf.path); pdfFiller.generateFieldJson(that.pdf.path, function(err, _form_fields){ if(err){ callback( new Error(err.message), null); }else if(!_form_fields.length || _form_fields === undefined || _form_fields === null){ callback( new Error('Generated formfields is empty'), null); } //Map PDF field names to FormField field names for(var i = 0; i < _form_fields.length; i++){ var field = _form_fields[i]; //Convert types from FDF to 'FormField' types if(_typeConvMap[ field.fieldType+'' ]){ field.fieldType = _typeConvMap[ field.fieldType+'' ]; } // field = new Field(field); field.required = false; _form_fields[i] = field; } // console.log('NEW FORM_FIELDS: '); // console.log(_form_fields); that.form_fields = that.form_fields.concat(_form_fields); // console.log('\n\nOLD FORM_FIELDS: '); // console.log(that.form_fields); that.isGenerated = false; callback(null, 'task2'); }); }else{ callback(null, 'task2'); } } ], function(err, results) { if(err){ next(new Error({ message: err.message })); } console.log('ending form save'); next(); }); }else if(_original){ if(_original.hasOwnProperty('pdf')){ fs.remove(_original.pdf.path, function (err) { if(err) next(err); console.log('file at '+_original.pdf.path+' successfully deleted'); next(); }); } } next(); }); FormSchema.pre('save', function (next) { // console.log('_original\n------------'); // console.log(_original); //console.log('field has been deleted: '); //console.log(this.isModified('form_fields') && !!this.form_fields && !!_original); if(this.isModified('form_fields') && this.form_fields && _original){ var old_form_fields = _original.form_fields, new_ids = _.map(_.pluck(this.form_fields, '_id'), function(id){ return ''+id;}), old_ids = _.map(_.pluck(old_form_fields, '_id'), function(id){ return ''+id;}), deletedIds = getDeletedIndexes(old_ids, new_ids), that = this; // console.log('deletedId Indexes\n--------'); // console.log(deletedIds); // console.log('old_ids\n--------'); // console.log(old_ids); // console.log('new_ids\n--------'); // console.log(new_ids); //Preserve fields that have at least one submission if( deletedIds.length > 0 ){ var modifiedSubmissions = []; async.forEachOfSeries(deletedIds, function (deletedIdIndex, key, callback) { var deleted_id = old_ids[deletedIdIndex]; //Find FormSubmissions that contain field with _id equal to 'deleted_id' FormSubmission. find({ form: that._id, admin: that.admin, form_fields: {$elemMatch: {_id: deleted_id} } }). exec(function(err, submissions){ if(err){ console.error(err); return callback(err); } //Delete field if there are no submission(s) found if(submissions.length) { // console.log('adding submissions'); // console.log(submissions); //Add submissions modifiedSubmissions.push.apply(modifiedSubmissions, submissions); } callback(null); }); // } }, function (err) { if(err){ console.error(err.message); next(err); } // console.log('modifiedSubmissions\n---------\n\n'); // console.log(modifiedSubmissions); //Iterate through all submissions with modified form_fields async.forEachOfSeries(modifiedSubmissions, function (submission, key, callback) { //Iterate through ids of deleted fields for(var i = 0; i < deletedIds.length; i++){ var index = _.findIndex(submission.form_fields, function(field){ var tmp_id = field._id+''; return tmp_id === old_ids[ deletedIds[i] ]; } ); var deletedField = submission.form_fields[index]; //Hide field if it exists if(deletedField){ // console.log('deletedField\n-------\n\n'); // console.log(deletedField); //Delete old form_field submission.form_fields.splice(index, 1); deletedField.deletePreserved = true; //Move deleted form_field to start submission.form_fields.unshift(deletedField); that.form_fields.unshift(deletedField); // console.log('form.form_fields\n--------\n\n'); // console.log(that.form_fields); } } submission.save(function (err) { if(err) callback(err); else callback(null); }); }, function (err) { if(err){ console.error(err.message); next(err); } // console.log('form.form_fields\n--------\n\n'); // console.log(that.form_fields); next(); }); } ); }else { next(); } }else { next(); } }); mongoose.model('Form', FormSchema);