corpus/CorpusMask.js

var Confidential = require("./../confidentiality_encryption/Confidential").Confidential;
var Activities = require("./../activity/Activities").Activities;
var Database = require("./Database").Database;
var Connection = require("./Connection").Connection;
var DatumFields = require("./../datum/DatumFields").DatumFields;
var DatumStates = require("./../datum/DatumStates").DatumStates;
var DatumTags = require("./../datum/DatumTags").DatumTags;
var Comments = require("./../comment/Comments").Comments;
var FieldDBObject = require("./../FieldDBObject").FieldDBObject;
var Sessions = require("./../datum/DocumentCollection").DocumentCollection;
var DataLists = require("./../datum/DocumentCollection").DocumentCollection;
var TeamPreference = require("./../user/UserPreference").UserPreference;
var Team = require("./../user/Team").Team;
var Permissions = require("./../permission/Permissions").Permissions;
var Q = require("q");

var DEFAULT_CORPUS_MODEL = require("./corpus_mask.json");
/**
 * @class The CorpusMask is saved as corpus in the Couch repository,
 *        it is the publicly visible version of a corpus. By default it just says "This
 *        corpus is private," when users decide to make some aspects of their corpsu
 *        public they can customize the fields in their "corpus" object to display
 *        only certain sorts of data, even though the corpus is publicly discoverable.
 *
 * @property {String} title This is the title of the corpus, as set by the corpus
 *           team. It can contain any UTF-8 character.
 * @property {String} titleAsUrl This is what appears in the url on the main website.
 *           It is based on the title of the corpus and can be changed and looks
 *           nicer than the dbname which cannot be changed. eg
 *           http://fieldlinguist.com/LingLlama/SampleFieldLinguisticsCorpus
 * @property {String} description This is a short description that
 *           appears on the corpus details page
 * @property {Object} termsOfUse Terms of use set by the corpus team, includes
 *           a field for humanReadable terms which are displayed on the corpus
 *           and included in corpus exports.
 * @property {Object} license License set by the corpus team, includes a field
 *           for humanReadable terms which are displayed on the corpus and
 *           included in corpus exports, as well as a link to the license, imageUrl
 *           for the image/logo of the license for easy recognition and
 *           title of the license.
 * @property {Object} copyright Who owns the copyright to the corpus,
 *           by default it is set to the corpus team"s name but teams can customize
 *           it for example to make the corpus copyright of the language community
 *           or speakers who contributed to the corpus.
 * @property {Object} location GPS location of the corpus (longitude, latitude and accuracy)
 *           The corpus can be plotted on a map using the accuracy as a radius
 *           of roughly where the data is from.
 * @property {String} remote The url of the remote eg:
 *           git@fieldlinguist.com:LingLlama/SampleFieldLinguisticsCorpus.git
 * @property {Array} connection The url of remote server(s) where the
 *           corpus is replicated or backed up as well as other connection it is connected to.
 *
 * @property {Array} members Collection of public browsable/search engine
 *           discoverable members associated to the corpus
 * @property {Array} datumstates Collection of datum states for which data are
 *           will be public browsable/search engine discoverable in the corpus. This
 *           can be used to only show polished data or "checked" data on the public
 *           page for example.
 * @property {Array} datumfields Collection of datum fields which will be
 *           public browsable/search engine discoverable  on public datum in the corpus
 * @property {Array} sessions Collection of public browsable/search engine
 *           discoverable sessions that belong to the corpus
 * @property {Array} datalists Collection of public browsable/search engine
 *           discoverable data lists created under the corpus
 *
 * @extends Database
 * @tutorial tests/CorpusMaskTest.js
 */
var CorpusMask = function CorpusMask(options) {
  if (!this._fieldDBtype) {
    this._fieldDBtype = "CorpusMask";
  }
  this.debug(options);
  Database.apply(this, arguments);
};

CorpusMask.prototype = Object.create(Database.prototype, /** @lends CorpusMask.prototype */ {
  constructor: {
    value: CorpusMask
  },

  id: {
    get: function() {
      this._id = "corpus";
      return this._id;
    },
    set: function(value) {
      if (value === this._id) {
        return;
      }
      this.warn("CorpusMask id cannot be set, it is \"corpus\" by default." + value);
      value = "corpus";
      this._id = value;
    }
  },

  api: {
    value: "corpora"
  },

  defaults: {
    get: function() {
      var filteredCorpus = JSON.parse(JSON.stringify(DEFAULT_CORPUS_MODEL));
      return filteredCorpus;
    }
  },

  INTERNAL_MODELS: {
    value: {
      _id: FieldDBObject.DEFAULT_STRING,
      _rev: FieldDBObject.DEFAULT_STRING,
      dbname: FieldDBObject.DEFAULT_STRING,
      version: FieldDBObject.DEFAULT_STRING,
      dateCreated: FieldDBObject.DEFAULT_DATE,
      dateModified: FieldDBObject.DEFAULT_DATE,
      comments: Comments,
      sessions: Sessions,
      datalists: DataLists,

      title: FieldDBObject.DEFAULT_STRING,
      titleAsUrl: FieldDBObject.DEFAULT_STRING,
      description: FieldDBObject.DEFAULT_STRING,
      termsOfUse: FieldDBObject.DEFAULT_OBJECT,
      license: FieldDBObject.DEFAULT_OBJECT,
      copyright: FieldDBObject.DEFAULT_STRING,
      connection: Connection,
      activityConnection: Activities,
      publicCorpus: FieldDBObject.DEFAULT_STRING,
      confidential: Confidential,

      validationStati: DatumStates,
      tags: DatumTags,

      fields: DatumFields,
      datumFields: DatumFields,
      speakerFields: DatumFields,
      participantFields: DatumFields,
      conversationFields: DatumFields,
      sessionFields: DatumFields,

      prefs: TeamPreference,
      team: Team,
      permissions: Permissions
    }
  },

  titleAsUrl: {
    get: function() {
      if (!this._titleAsUrl && this.title) {
        this._titleAsUrl = this.sanitizeStringForFileSystem(this._title, "_").toLowerCase();
      }
      return this._titleAsUrl;
    },
    set: function(value) {
      if (value === this._titleAsUrl) {
        return;
      }
      // If an app is explicity trying to overwrite a titleAsUrl, complain.
      if (this._titleAsUrl) {
        this.warn("titleAsUrl cannot be set directly, setting the title will cause it to be set.");
      }
    }
  },

  title: {
    get: function() {
      return this._title || FieldDBObject.DEFAULT_STRING;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("title", value);
      if (this._title) {
        this._titleAsUrl = this.sanitizeStringForFileSystem(this._title, "_").toLowerCase();
      }
    }
  },

  description: {
    get: function() {
      return this._description || FieldDBObject.DEFAULT_STRING;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("description", value);
    }
  },

  /**
   * TODO decide if we want to fetch these from the server, and keep a fossil in the object?
   * @type {Object}
   */
  team: {
    get: function() {
      if (!this._team) {
        this.team = {
          gravatar: "",
          username: "",
          _id: "team"
        };
        if (this.dbname) {
          this._team.gravatar = this._team.gravatar || this.gravatar || this._team.buildGravatar(this.dbname);
          this._team.username = this._team.username || this.dbname.split("-")[0];
        }
        if (this.id && this.rev) {
          this._team.fetch();
        }
      }
      return this._team;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("team", value);
    }
  },

  activityConnection: {
    get: function() {
      this.debug("getting activityConnection");
      if (!this._activityConnection && this.rev && this._connection) {
        this.warn("This corpus is missing its activity connection for some reason.");
        var copy = this._connection.toJSON();
        copy.dbname = copy.dbname + "-activity_feed";
        this.activityConnection = copy;
      }
      if (this._activityConnection && this._activityConnection.parent !== this) {
        this._activityConnection.parent = this;
      }
      return this._activityConnection;
    },
    set: function(value) {
      if (value) {
        value.parent = this;
        if (!value.confidential && this.confidential) {
          value.confidential = this.confidential;
        }
      }
      this.ensureSetViaAppropriateType("activityConnection", value);
    }
  },

  replicatedCorpusUrls: {
    get: function() {
      if (this._connection && this._connection.replicatedCorpusUrls) {
        return this._connection.replicatedCorpusUrls;
      }
      return FieldDBObject.DEFAULT_COLLECTION;
    },
    set: function(value) {
      if (this._connection) {
        this._connection.replicatedCorpusUrls = value;
      }
    }
  },

  olacExportConnections: {
    get: function() {
      if (this._connection && this._connection.olacExportConnections) {
        return this._connection.olacExportConnections;
      }
      return FieldDBObject.DEFAULT_COLLECTION;
    },
    set: function(value) {
      if (this._connection) {
        this._connection.olacExportConnections = value;
      }
    }
  },

  termsOfUse: {
    get: function() {
      return this._termsOfUse || FieldDBObject.DEFAULT_OBJECT;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("termsOfUse", value);
    }
  },

  license: {
    get: function() {
      return this._license || {};
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("license", value);
    }
  },

  copyright: {
    get: function() {
      return this._copyright || FieldDBObject.DEFAULT_STRING;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("copyright", value);
    }
  },

  startYear: {
    get: function() {
      if (this.parent && this.parent.dateCreated && this.parent._rev && this.parent._fieldDBtype === "Corpus") {
        this.dateCreated = this.parent.dateCreated;
      }
      if (this.dateCreated) {
        var year = new Date(this.dateCreated).getFullYear();
        if (year < new Date().getFullYear()) {
          this._startYear = " " + year + " - ";
        }
      }
      return this._startYear;
    },
    set: function(value) {
      if (value === this._startYear) {
        return;
      }
      if (!value) {
        // delete this._startYear;
        return;
      }
      this._startYear = value;
    }
  },

  // unserializedSessions: {
  //   value: null
  // },
  sessions: {
    get: function() {
      return this.unserializedSessions || FieldDBObject.DEFAULT_COLLECTION;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("sessions", value, "unserializedSessions");
    }
  },

  // unserializedDatalists: {
  //   value: null
  // },
  datalists: {
    get: function() {
      return this.unserializedDatalists || FieldDBObject.DEFAULT_COLLECTION;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("datalists", value, "unserializedDatalists");
    }
  },

  permissions: {
    get: function() {
      if (!this._permissions) {
        this.permissions = {
          dbname: this.dbname,
          parent: this
        };
      }
      return this._permissions || FieldDBObject.DEFAULT_COLLECTION;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("permissions", value);
    }
  },

  loadPermissions: {
    value: function(dataToPost) {
      var deferred = Q.defer(),
        self = this;

      Q.nextTick(function() {

        if (!self.permissions || !(self.permissions instanceof Permissions)) {
          self.permissions = new Permissions(self.permissions);
        }
        if (!self.permissions.dbname) {
          self.permissions.dbname = self.dbname;
        }
        self.permissions.parent = self;
        self.permissions.fetch(dataToPost)
          .then(deferred.resolve, deferred.reject)
          .fail(function(error) {
            console.error(error.stack, self);
            deferred.reject(error);
          });

      });
      return deferred.promise;
    }
  },

  datumFields: {
    get: function() {
      this.debug("getting datumFields");
      return this._datumFields;
    },
    set: function(value) {
      if (value && !value.confidential && this.confidential) {
        value.confidential = this.confidential;
      }
      this.ensureSetViaAppropriateType("datumFields", value);
    }
  },

  sessionFields: {
    get: function() {
      this.debug("getting sessionFields");
      return this._sessionFields;
    },
    set: function(value) {
      if (value && !value.confidential && this.confidential) {
        value.confidential = this.confidential;
      }
      this.ensureSetViaAppropriateType("sessionFields", value);
    }
  },

  speakerFields: {
    get: function() {
      this.debug("getting speakerFields");
      return this._speakerFields;
    },
    set: function(value) {
      if (value && !value.confidential && this.confidential) {
        value.confidential = this.confidential;
      }
      this.ensureSetViaAppropriateType("speakerFields", value);
    }
  },

  participantFields: {
    get: function() {
      this.debug("getting participantFields");
      return this._participantFields;
    },
    set: function(value) {
      if (value && !value.confidential && this.confidential) {
        value.confidential = this.confidential;
      }
      this.ensureSetViaAppropriateType("participantFields", value);
    }
  },

  conversationFields: {
    get: function() {
      this.debug("getting conversationFields");
      return this._conversationFields;
    },
    set: function(value) {
      if (value && !value.confidential && this.confidential) {
        value.confidential = this.confidential;
      }
      this.ensureSetViaAppropriateType("conversationFields", value);
    }
  },

  fields: {
    get: function() {
      this.debug("getting fields");
      return this._fields;
    },
    set: function(value) {
      if (value && !value.confidential && this.confidential) {
        value.confidential = this.confidential;
      }
      this.ensureSetViaAppropriateType("fields", value);
    }
  },

  prefs: {
    get: function() {
      if (!this._prefs && this.INTERNAL_MODELS && this.INTERNAL_MODELS.prefs && this.INTERNAL_MODELS.prefs.prototype && this.INTERNAL_MODELS.prefs.prototype.defaults) {
        this.prefs = this.INTERNAL_MODELS.prefs.prototype.defaults;
      }
      return this._prefs;
    },
    set: function(value) {
      this.ensureSetViaAppropriateType("prefs", value);
    }
  },

  preferredDatumTemplate: {
    get: function() {
      if (this.prefs && this.prefs.preferredDatumTemplate) {
        var upgradeSucess = this.upgradeCorpusFieldsToMatchDatumTemplate(this.prefs.preferredDatumTemplate);
        if (!upgradeSucess) {
          return this.prefs.preferredDatumTemplate;
        } else {
          this.warn("preferredDatumTemplate is no longer needed");
        }
      }
    },
    set: function(value) {
      if (this.prefs && this.prefs.preferredDatumTemplate && value === this.prefs.preferredDatumTemplate) {
        return;
      }
      if (!value || value === "default") {
        if (this.prefs && this.prefs.preferredDatumTemplate) {
          delete this.prefs.preferredDatumTemplate;
        }
        return;
      }
      this.prefs = this.prefs || {};
      var upgradeSucess = this.upgradeCorpusFieldsToMatchDatumTemplate(value.trim());
      if (!upgradeSucess) {
        this.prefs.preferredDatumTemplate = value.trim();
      } else {
        this.warn("preferredDatumTemplate is no longer needed");
      }
    }
  },

  upgradeCorpusFieldsToMatchDatumTemplate: {
    value: function(value) {
      if (this.preferredDatumTemplateAtVersion) {
        return true;
      }
      if (!this.datumFields.reorder) {
        this.warn("could not upgrade corpus fields order to match data entry template for the spreadsheet app ");
        return false;
      }
      this.preferredDatumTemplateAtVersion = this.version;
      this.preferredDatumTemplate = null;
      this.prefs = this.prefs || {};
      this.prefs.debug(" this.prefs.preferredSpreadsheetShape.rows ", this.prefs.preferredSpreadsheetShape.rows);
      var order = ["judgement", "utterance", "morphemes", "gloss", "translation", "context", "documentation"];
      if (value === "compacttemplate") {
        order = ["judgement", "utterance", "morphemes", "gloss", "translation"];

      } else if (value === "fulltemplate") {
        order = ["judgement", "utterance", "morphemes", "gloss", "translation", "context", "documentation"];

      } else if (value === "mcgillfieldmethodsspring2014template") {
        order = ["judgement", "utterance", "morphemes", "gloss", "translation", "validationStatus", "tags"];

      } else if (value === "mcgillfieldmethodsfall2014template") {
        order = ["judgement", "utterance", "morphemes", "gloss", "translation", "phonetic", "notes"];

      } else if (value === "yalefieldmethodsspring2014template") {
        order = ["judgement", "orthography", "utterance", "morphemes", "gloss", "translation", "spanish", "Housekeeping", "tags"];
        this.prefs.debug("setting to yalefieldmethodsspring2014template");
        this.prefs.preferredSpreadsheetShape = this.prefs.preferredSpreadsheetShape;
        this.prefs.preferredSpreadsheetShape.rows = 4;
      }

      var fieldTemplate = {
        "label": "",
        "shouldBeEncrypted": false,
        "showToUserTypes": "all",
        "defaultfield": false,
        "value": "",
        "mask": "",
        "json": {},
        "help": "You can add help/conventions here which explain what this data entry field is for your teammates."
      };
      for (var expectedPosition = 0; expectedPosition < order.length; expectedPosition++) {
        var currentPosition = this.datumFields.indexOf(order[expectedPosition]);
        if (currentPosition === -1) {
          var newField = JSON.parse(JSON.stringify(fieldTemplate));
          // newField.id = order[expectedPosition];
          newField.label = order[expectedPosition];
          this.datumFields.add(newField);
          currentPosition = this.datumFields.length - 1;
        }
        if (currentPosition === expectedPosition) {
          this.debug(order[expectedPosition] + " Field was in the correct order. ");
        } else {
          this.debug(currentPosition + " moving to" + expectedPosition);

          this.datumFields.reorder(currentPosition, expectedPosition);
        }
      }
      var resultingOrder = this.datumFields.map(function(field) {
        return field.id;
      });
      this.warn("reordered fields to " + resultingOrder);

      return true;
    }
  },

  preferredLocale: {
    get: function() {
      if (this.prefs && this.prefs.preferredLocale) {
        return this.prefs.preferredLocale;
      }
    },
    set: function(value) {
      if (this.prefs && this.prefs.preferredLocale && value === this.prefs.preferredLocale) {
        return;
      }
      if (!value || value === "default") {
        if (this.prefs && this.prefs.preferredLocale) {
          delete this.prefs.preferredLocale;
        }
        return;
      }
      this.prefs = this.prefs || {};
      this.prefs.preferredLocale = value.trim();
    }
  },

  preferredDashboardLayout: {
    get: function() {
      if (this.prefs && this.prefs.preferredDashboardLayout) {
        return this.prefs.preferredDashboardLayout;
      }
    },
    set: function(value) {
      if (this.prefs && this.prefs.preferredDashboardLayout && value === this.prefs.preferredDashboardLayout) {
        return;
      }
      if (!value || value === "default") {
        if (this.prefs && this.prefs.preferredDashboardLayout) {
          delete this.prefs.preferredDashboardLayout;
        }
        return;
      }
      this.prefs = this.prefs || {};
      this.prefs.preferredDashboardLayout = value.trim();
    }
  },

  preferredTemplate: {
    get: function() {
      this.warn("preferredTemplate is deprecated, use preferredDatumTemplate instead.");
      return this.preferredDatumTemplate;
    },
    set: function(value) {
      this.warn("preferredTemplate is deprecated, please use preferredDatumTemplate instead.");
      this.preferredDatumTemplate = value;
    }
  }

});
exports.CorpusMask = CorpusMask;