<commit_before>/**
 * Dependencies
 */
const {JSONSchema, JSONDocument} = require('json-document')
const AuthenticationRequest = require('./AuthenticationRequest')
const AuthenticationResponse = require('./AuthenticationResponse')
// const Session = require('./Session')

/**
 * RelyingParty Schema
 */
const schema = new JSONSchema({
  type: 'object',
  properties: {
    issuer: {
      type: 'string',
      format: 'uri'
    },
    client_id: {
      type: 'string'
    },
    response_type: {
      type: 'string',
      default: 'id_token token',
      enum: ['id_token token']
    },
    display: {
      type: 'string',
      default: 'page',
      enum: ['page', 'popup']
    },
    scope: {
      type: ['string', 'array'],
      default: ['openid']
    }
  },
  required: ['issuer']
})

/**
 * RelyingParty
 *
 * @class
 * Client interface for OpenID Connect Relying Party.
 *
 * @example
 *  let client = RelyingParty({
 *    issuer: 'https://issuer.com',
 *    client_id: 'uuid'
 *    response_type: 'id_token',
 *    display: 'popup',
 *    scope: 'openid profile email'
 *  })
 *
 *  client.discover() => Promise
 *  client.keys() => Promise
 *  client.authenticate()
 *  client.authenticateUri()
 *  client.validateResponse(uri) => Promise
 *  client.userinfo() => Promise
 *  client.logout()
 */
class RelyingParty extends JSONDocument {
  /**
   * fromProvider
   *
   * @description
   * Promises a RelyingParty instance with discovery and JWK Set
   * data.
   *
   * @param {Object} options
   * @returns Promise
   */
  static fromProvider (options) {
    return Promise
      .resolve(new RelyingParty(options))
      .then(client => client.discover())
      .then(client => client.jwks())
      .then(client => client)
      .catch(/* err => {} */)
  }

  /**
   * Constructor
   */
  constructor (options = {}) {
    super()
    if (!options.issuer) {
      throw new Error('RelyingParty must have an issuer')
    }
    this.issuer = options.issuer
    this.client_id = options.client_id
    this.persistence = options.persistence || RelyingParty.defaultStore()
  }

  /**
   * Discover
   *
   * @description Promises the issuer's OpenID Configuration.
   * @returns {Promise}
   */
  discover () {}

  /**
   * Register
   *
   * @description Register's a client with provider as a Relying Party
   * @returns {Promise}
   */
  register () {}

  /**
   * Keys
   *
   * @description Promises the issuer's JWK Set.
   * @returns {Promise}
   */
  keys () {}

  /**
   * Authenticate
   *
   * @description
   * Initiates OpenID Connect implicit authentication flow by, directing the
   * User-Agent to the issuer's authorize endpoint, either by changing the
   * window location or by opening a popup window.
   *
   * @param {Object} options
   * @returns Promise
   */
  authenticate (options) {
    return AuthenticationRequest.send(this)
  }

  /**
   * Authenticate URI
   *
   * @description
   * Constructs a URI that can be employed in a hyperlink to initiate an
   * authentication flow with the configured OpenID Connect provider (issuer)..
   *
   * @param {Object} options
   * @returns string
   */
  authenticateUri (options) {
    return AuthenticationRequest.uri(this)
  }

  /**
   * Validate Response
   */
  validateResponse (uri) {
    AuthenticationResponse.validate(uri)
  }

  /**
   * UserInfo
   */
  userinfo () {}

  /**
   * Is Authenticated
   */
  isAuthenticated () {}

  /**
   * Logout
   */
  logout () {}

  /**
   * Schema
   */
  static get schema () {
    return schema
  }
}

module.exports = RelyingParty
<commit_msg>Reorder - put static methods on top<commit_after>/**
 * Dependencies
 */
const {JSONSchema, JSONDocument} = require('json-document')
const AuthenticationRequest = require('./AuthenticationRequest')
const AuthenticationResponse = require('./AuthenticationResponse')
// const Session = require('./Session')

/**
 * RelyingParty Schema
 */
const schema = new JSONSchema({
  type: 'object',
  properties: {
    issuer: {
      type: 'string',
      format: 'uri'
    },
    client_id: {
      type: 'string'
    },
    response_type: {
      type: 'string',
      default: 'id_token token',
      enum: ['id_token token']
    },
    display: {
      type: 'string',
      default: 'page',
      enum: ['page', 'popup']
    },
    scope: {
      type: ['string', 'array'],
      default: ['openid']
    }
  },
  required: ['issuer']
})

/**
 * RelyingParty
 *
 * @class
 * Client interface for OpenID Connect Relying Party.
 *
 * @example
 *  let client = RelyingParty({
 *    issuer: 'https://issuer.com',
 *    client_id: 'uuid'
 *    response_type: 'id_token',
 *    display: 'popup',
 *    scope: 'openid profile email'
 *  })
 *
 *  client.discover() => Promise
 *  client.keys() => Promise
 *  client.authenticate()
 *  client.authenticateUri()
 *  client.validateResponse(uri) => Promise
 *  client.userinfo() => Promise
 *  client.logout()
 */
class RelyingParty extends JSONDocument {
  /**
   * Constructor
   */
  constructor (options = {}) {
    super()
    if (!options.issuer) {
      throw new Error('RelyingParty must have an issuer')
    }
    this.issuer = options.issuer
    this.client_id = options.client_id
    this.persistence = options.persistence || RelyingParty.defaultStore()
  }

  /**
   * fromProvider
   *
   * @description
   * Promises a RelyingParty instance with discovery and JWK Set
   * data.
   *
   * @param {Object} options
   * @returns Promise
   */
  static fromProvider (options) {
    return Promise
      .resolve(new RelyingParty(options))
      .then(client => client.discover())
      .then(client => client.jwks())
      .then(client => client)
      .catch(/* err => {} */)
  }

  /**
   * Schema
   */
  static get schema () {
    return schema
  }

  /**
   * Discover
   *
   * @description Promises the issuer's OpenID Configuration.
   * @returns {Promise}
   */
  discover () {}

  /**
   * Register
   *
   * @description Register's a client with provider as a Relying Party
   * @returns {Promise}
   */
  register () {}

  /**
   * Keys
   *
   * @description Promises the issuer's JWK Set.
   * @returns {Promise}
   */
  keys () {}

  /**
   * Authenticate
   *
   * @description
   * Initiates OpenID Connect implicit authentication flow by, directing the
   * User-Agent to the issuer's authorize endpoint, either by changing the
   * window location or by opening a popup window.
   *
   * @param {Object} options
   * @returns Promise
   */
  authenticate (options) {
    return AuthenticationRequest.send(this)
  }

  /**
   * Authenticate URI
   *
   * @description
   * Constructs a URI that can be employed in a hyperlink to initiate an
   * authentication flow with the configured OpenID Connect provider (issuer)..
   *
   * @param {Object} options
   * @returns string
   */
  authenticateUri (options) {
    return AuthenticationRequest.uri(this)
  }

  /**
   * Validate Response
   */
  validateResponse (uri) {
    AuthenticationResponse.validate(uri)
  }

  /**
   * UserInfo
   */
  userinfo () {}

  /**
   * Is Authenticated
   */
  isAuthenticated () {}

  /**
   * Logout
   */
  logout () {}
}

module.exports = RelyingParty
<|endoftext|>


<commit_before>/**

  Utils.js

  Various helper functions.

  • getRegExpMatches
  • isIndexFile
  • isProduction
  • makePathAbsolute
  • makeRoutes
  • mapFiles
  • readDirectory
  • readFileHead
  • sequence
  • uniqueExtensions
  • watch

**/

//
//  Dependencies
//

var fs       = require("fs"),
    url      = require("url"),
    path     = require("path"),
    chokidar = require("chokidar"),
    Promise  = require("bluebird"),
    reorg    = require("reorg"),
    zlib     = require("zlib"),
    Utils;

module.exports = Utils = {};

/**

  Makes a path absolute (if not already)

  @param {String} p Path
  @returns {String} Absolute path version of a string

**/

Utils.makePathAbsolute = function(p) {

  if (path.isAbsolute(p))
    return p;
  else
    return path.resolve(process.cwd(), p || "");

};

/**

  Tests to see if a file is of form "index*"

  @param {Stats} file File as returned by fs.lstat
  @returns {Boolean} true if a file is index-like

*/

Utils.isIndexFile = function(file) {
  return /^index[^\/]*$/i.test(file.name);
};

/**

  Retrieves all unique file extensions from a directory

  @param {String} p Directory path
  @returns {Array} Array of strings of extensions

**/

Utils.uniqueExtensions = function(p) {
  var exts = {};
  Utils.readDirectory(p).forEach(function(file) {
    if (file.ext && "" !== file.ext) exts[file.ext.toLowerCase()] = 1;
  });
  return Object.keys(exts);
};

/**

  Finds index files and marks as "bundles", then removes sibling and
  sub-diretories

  @param {String} p Absolute path
  @param {Array} files Array of file stats

**/

Utils.flagBundles = function(p, files, ext) {

  var indices = [],
      i, j, n;

  // Filter out index files (outside of master directory)
  i = files.length;
  while (i-- > 0)
    if ((p !== files[i].dir) && Utils.isIndexFile(files[i]))
      indices.push(files.splice(i, 1)[0]);

  // Remove indices that are subdirectories of each other
  i = indices.length;
  while (i-- > 0)
    for (j = 0, n = indices.length; j < n; j++)
      if (i !== j && 0 === indices[i].path.indexOf(indices[j].dir)) {
        indices.splice(i, 1);
        break;
      }

  // Remove files in subdirectories of indices
  i = files.length;
  while (i-- > 0)
    for (j = 0, n = indices.length; j < n; j++)
      if (0 === files[i].path.indexOf(indices[j].dir)) {
        files.splice(i, 1);
        break;
      }

  // Denote indices as bundles
  indices.forEach(function(file) {
    file.bundle = true;
    file.ignored = [
      path.join(file.dir, "*"),
      file.dir + "." + ext
    ];
  });

  // Rejoin indices back into files
  return files.concat(indices);

};

/**

  Sorts and preprocesses files in a directory. Adds absolute and relative paths.

  @param {String} p Path to map
  @param {Object} [options.recursive] If true, processes recursively
  @param {Boolean} [options.bundleFolders] If true, automatically bundles appropriate folders
  @param {String} [options.type] The file type of the bundles (e.g. js)

**/

Utils.mapFiles = function(p, options) {

  var files, i, indices, results = [];
  options = options || {};

  // Make path absolute before call (for comparison after)
  p = Utils.makePathAbsolute(p);
  files = Utils.readDirectory(p, options.recursive, p);

  // Sort in reverse order so that directory calls come last
  files.sort(function(a, b) { return (a.path < b.path) ? -1 : (a.path > b.path) ? 1 : 0; }).reverse();

  // Move "index" to end so that routes are from specific to unspecific
  i = files.length;
  while (i-- > 0)
    if (Utils.isIndexFile(files[i]))
      Array.prototype.push.apply(files, files.splice(i, 1));

  // Remove subdirectory files if index.* is encountered
  if (options.bundleFolders) files = Utils.flagBundles(p, files, options.type);

  return files;

};

/**

  Parses a file name and adds additional information

**/

Utils.parse = function(p, base) {

  var parsed;

  p = Utils.makePathAbsolute(p);
  parsed = path.parse(p);

  // Get rid of leading dot for an extension
  parsed.ext = parsed.ext.replace(/^\./, "");
  parsed.path = p;

  if (base) {
    base = Utils.makePathAbsolute(base);
    parsed.basePath = base;
    parsed.folder = path.relative(base, parsed.path.replace(new RegExp(parsed.base + "$"), ""));
  }

  return parsed;

};

/**

  Creates appropriate routes for a file: for example, /about/index.html needs
  /about/, /about/index, and /about/index.html.

  Files named "template" are special cases and serve as wildcard route handlers.

  @param {String} baseRoute Base route from which to build
  @param {Stat} file File object (as returned from fs.lStat)
  @param {Object} options Options
  @param {String} [options.type] Default file type (e.g. js for coffee)
  @returns {Array} Array of route strings

**/

Utils.makeRoutes = function(baseRoute, file, options) {

  var re = /\/\/+$/,
      route,
      routes,
      ext;

  options = options || {};

  // By defaulting to file type, we address templating and transpiling. We can
  // resolve coffee files to js, etc. We also remove leading dot.
  ext = options.type || file.ext || "";
  ext = ext.replace(/^\./, "");

  // Create the file's route by appending the folder to the base route. This
  // process is prone to double "//", which must be removed.
  route = (url.resolve((baseRoute + "/").replace(re, "/"), file.folder) + "/").replace(re, "/");

  // HTML-like files named "template" pull their route from the parent directory
  // and pass the subsequent URL on as a "query" parameter. Note that $ are not
  // allowed in params.
  if (Utils.isTemplate(file, ext)) {
    route = route.split("/").slice(0, -1).join("/");
    return [route + "/" + (options.params || ":slug").replace(/^\//, "")];
  }

  // Add file and default extension
  routes = [url.resolve(route, file.name + "." + ext)];

  // HTML files are special: not only can they be accessed sans extension, if
  // they are named "index", we allow access via a folder.
  if ("html" === ext) {
    routes.push(url.resolve(route, file.name));
    if (Utils.isIndexFile(file)) routes.push(route);
  }

  return routes;

};

/**

  Reads all files in a directory

  @param {String} p Path to read
  @param {Boolean} recursive Whether to read recursively (default: true)
  @param [String] base Optional base path from which to parse

**/

Utils.readDirectory = function(p, recursive, base) {

  var results = [];

  // Set defaults
  if ("undefined" == typeof recursive) recursive = true;
  p = Utils.makePathAbsolute(p);

  // If directory doesn't exist, return nothing
  try {
    fs.lstatSync(p);
  } catch (err) {
    return results;
  }

  // Read in files
  fs.readdirSync(p).forEach(function(file) {

    var stats, parsed;

    // Read file and stats
    file = path.join(p, file);
    stats = fs.lstatSync(file);

    // Recursively walk directory, or add file
    if (stats.isDirectory()) {
      if (recursive)
        results = results.concat(Utils.readDirectory(file, true, base));
    } else {
      results.push(Utils.parse(file, base || p));
    }

  });

  return results;

};

/**

 Reads a limited number of characters from a file.

 @param {String} path Path to file
 @param {Number} chars Number of chars (defaults to 500)
 @returns {String} First characters for a file

**/

Utils.readFileHead = function(path, chars) {

  var buffer = new Buffer(chars || 500),
      file   = fs.openSync(path, "r"),
      str,
      len;

  // Read in file
  len = fs.readSync(file, buffer, 0, chars, 0);

  // Convert to lines, and discard any fragments
  // (so that we can use template engines without through errors)
  str = buffer.slice(0, len).toString().split("\n");

  return str.slice(0, str.length - 1).join("\n");

};

/**

  Returns all regex matches for an expression

  @param {String} str String to search
  @param {RegExp} pattern Pattern to search with
  @param {Number} [idx] Search component to use
  @returns {Array} Array of strings with matches

**/

Utils.getRegExpMatches = function(str, pattern, idx) {

  var match,
      matches = [];

  // Clone the RE and add global flag (otherwise has endless loop)
  pattern = new RegExp(pattern);
  pattern.global = true;

  // Set default idx
  idx = idx || 0;

  while ((match = pattern.exec(str)))
    matches.push(match[idx]);

  return matches;

};

/**

  Watches a directory via chokidar

  @param {String} sourceDir The source directory (without wildcards)
  @param {String} sourceExt The source extension (defaults to "*")
  @param {Object} [options.chokidar] Options to pass to chokidar
  @param {Function} [options.*] Event handlers (file, compiled) where * is name of event

**/

Utils.watch = reorg(function(sourceDir, sourceExt, options) {

  var p;

  // Don't watch in production
  if (Utils.isProduction()) return;

  // For source extension, if not wildcard, convert to "*.ext"
  if ("*" !== sourceExt) sourceExt = "*." + sourceExt.replace(/^[\*\.]+/g, "");

  // Ensure we have chokidar options and that chokidar's cwd is root
  p = Utils.makePathAbsolute(path.join(sourceDir, "**", sourceExt));
  options.chokidar = options.chokidar || {};
  options.chokidar.cwd = path.parse(p).root;

  chokidar.watch(p, options.chokidar).on("all", function(event, file) {

    var parsed, compiled;

    if (options[event] || options.all) {

      // File is missing root directory
      file = path.join(options.chokidar.cwd, file);

      // Map file to compiled version
      if (options.type) {
        parsed = path.parse(file);
        compiled = path.join(parsed.root, parsed.dir, parsed.name + "." + options.type);
      }

      // Callback
      if (options[event]) options[event](file, compiled);
      if (options.all) options.all(file, compiled);

    }

  });

}, "string!", ["string", "*"], "object");

/**

  Determines whether environment is production

  @returns {Boolean} True if is a production environment

**/

Utils.isProduction = function() {
  return /^prod/i.test(process.env.NODE_ENV);
};

/**

  Runs a sequence of promises, similar to when.js

  @param {Array} tasks Array of "tasks" (functions that return promises)
  @param {*} initial Initial value to call task with

**/

Utils.sequence = function(tasks, initial) {
  return Promise.reduce(tasks || [], function(val, task) {
    return task(val);
  }, initial);
};

/**

  Tests whether a file is a template (HTML-like and named properly)

  @param {Stats} file File as returned by fs.lstat
  @param {String} type The type of file extension
  @returns {Boolean} Returns true is is a template

**/

Utils.isTemplate = function(file, type) {
  return ("template" === file.name) && Utils.isHTML(type);
};

/**

  Tests whether a type is HTML-like

  @param {String} type Type of file
  @returns {Boolean} Returns true if is HTML-like

**/

Utils.isHTML = function(type) {
  return /^\*?\.?html?$/i.test(type);
};

/**

  Tests that an input is a non-array object.

  @param {*} input The item to check
  @returns {Boolean} Returns true if is an object, but not an array

**/

Utils.isPlainObject = function(input) {
  return ("object" == typeof input) && !Array.isArray(input);
};

/**

  Tests for an array of type

  @param {*} input The item to check
  @param {String} type The requisite type
  @returns {Boolean} Returns true if is an array of strings

**/

Utils.isArrayOfType = function(input, type) {
  if (!Array.isArray(input)) return false;
  for (var i = 0, n = input.length; i < n; i++)
    if (type !== typeof input[i]) return false;
  return true;
};

/**

  Traverses a JSON-object and calls function on each end point.

**/

Utils.traverse = function(obj, fn) {

  var key, val;

  for (key in obj) {
    val = obj[key];
    if ("string" === typeof val)
      fn(obj, key, val);
    else if (Utils.isPlainObject(val))
      Utils.traverse(val, fn);
  }

};

/**

  Escapes regex. From lodash.com by John Dalton.

  @param {String} str The string to escape

**/

Utils.escapeRegex = function(str) {
  return str.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
};

/**

  Compresses a string.

  @param {String} method The compression method
  @param {String} str The string to compress
  @param {Boolean} maxCompress If true, maximum compression level
  @param {Function} next Callback of form (err, compressed)

**/

Utils.compress = function(method, str, level, next) {

  var zip = ("gzip" === method) ? zlib.gzip : zlib.deflate;

  level = level ? zlib.Z_BEST_COMPRESSION : zlib.Z_DEFAULT_COMPRESSION;
  zip(str, { level : level }, next);

};

/**

  Sends a compressed or uncompressed response.

  @param {Response} res The response
  @param {String} type The file type
  @param {Buffer} buf The buffer to send
  @param {String} enc The encoding to use
  @param {Number} [cache] Cache-control setting

**/

Utils.send = function(res, type, buf, enc, cache) {

  // File type
  res.type(type);

  // Cache control
  if (undefined !== cache)
    res.setHeader("Cache-Control", cache);

  // Raw versus compressed
  if ("identity" === enc || !enc) {
    res.send(buf);
  } else {
    res.setHeader("Content-Encoding", enc);
    res.removeHeader("Content-Length");
    res.send(buf);
  }

};

/**

  Attaches middleware to server, or ignores if blank.

  @param {Express} server Server instance
  @param {Middleware} library (req, res)-style middleware
  @param {Boolean} [skipCheck] If true, ignores errors (defaults to false)

**/

Utils.useMiddleware = function(server, library, skipCheck) {
  if (!library || Array.isArray(library) && !library.length) {
    if (skipCheck) return;
    return new Error("No middleware added");
  }
  server.use(library);
};
<commit_msg>Added: Utils#makeQueries function (abstracted from data-store)<commit_after>/**

  Utils.js

  Various helper functions.

  • getRegExpMatches
  • isIndexFile
  • isProduction
  • makePathAbsolute
  • makeRoutes
  • mapFiles
  • readDirectory
  • readFileHead
  • sequence
  • uniqueExtensions
  • watch

**/

//
//  Dependencies
//

var fs       = require("fs"),
    url      = require("url"),
    path     = require("path"),
    chokidar = require("chokidar"),
    Promise  = require("bluebird"),
    reorg    = require("reorg"),
    pair     = require("lodash/fromPairs"),
    zlib     = require("zlib"),
    Utils;

module.exports = Utils = {};

/**

  Makes a path absolute (if not already)

  @param {String} p Path
  @returns {String} Absolute path version of a string

**/

Utils.makePathAbsolute = function(p) {

  if (path.isAbsolute(p))
    return p;
  else
    return path.resolve(process.cwd(), p || "");

};

/**

  Tests to see if a file is of form "index*"

  @param {Stats} file File as returned by fs.lstat
  @returns {Boolean} true if a file is index-like

*/

Utils.isIndexFile = function(file) {
  return /^index[^\/]*$/i.test(file.name);
};

/**

  Retrieves all unique file extensions from a directory

  @param {String} p Directory path
  @returns {Array} Array of strings of extensions

**/

Utils.uniqueExtensions = function(p) {
  var exts = {};
  Utils.readDirectory(p).forEach(function(file) {
    if (file.ext && "" !== file.ext) exts[file.ext.toLowerCase()] = 1;
  });
  return Object.keys(exts);
};

/**

  Finds index files and marks as "bundles", then removes sibling and
  sub-diretories

  @param {String} p Absolute path
  @param {Array} files Array of file stats

**/

Utils.flagBundles = function(p, files, ext) {

  var indices = [],
      i, j, n;

  // Filter out index files (outside of master directory)
  i = files.length;
  while (i-- > 0)
    if ((p !== files[i].dir) && Utils.isIndexFile(files[i]))
      indices.push(files.splice(i, 1)[0]);

  // Remove indices that are subdirectories of each other
  i = indices.length;
  while (i-- > 0)
    for (j = 0, n = indices.length; j < n; j++)
      if (i !== j && 0 === indices[i].path.indexOf(indices[j].dir)) {
        indices.splice(i, 1);
        break;
      }

  // Remove files in subdirectories of indices
  i = files.length;
  while (i-- > 0)
    for (j = 0, n = indices.length; j < n; j++)
      if (0 === files[i].path.indexOf(indices[j].dir)) {
        files.splice(i, 1);
        break;
      }

  // Denote indices as bundles
  indices.forEach(function(file) {
    file.bundle = true;
    file.ignored = [
      path.join(file.dir, "*"),
      file.dir + "." + ext
    ];
  });

  // Rejoin indices back into files
  return files.concat(indices);

};

/**

  Sorts and preprocesses files in a directory. Adds absolute and relative paths.

  @param {String} p Path to map
  @param {Object} [options.recursive] If true, processes recursively
  @param {Boolean} [options.bundleFolders] If true, automatically bundles appropriate folders
  @param {String} [options.type] The file type of the bundles (e.g. js)

**/

Utils.mapFiles = function(p, options) {

  var files, i, indices, results = [];
  options = options || {};

  // Make path absolute before call (for comparison after)
  p = Utils.makePathAbsolute(p);
  files = Utils.readDirectory(p, options.recursive, p);

  // Sort in reverse order so that directory calls come last
  files.sort(function(a, b) { return (a.path < b.path) ? -1 : (a.path > b.path) ? 1 : 0; }).reverse();

  // Move "index" to end so that routes are from specific to unspecific
  i = files.length;
  while (i-- > 0)
    if (Utils.isIndexFile(files[i]))
      Array.prototype.push.apply(files, files.splice(i, 1));

  // Remove subdirectory files if index.* is encountered
  if (options.bundleFolders) files = Utils.flagBundles(p, files, options.type);

  return files;

};

/**

  Parses a file name and adds additional information

**/

Utils.parse = function(p, base) {

  var parsed;

  p = Utils.makePathAbsolute(p);
  parsed = path.parse(p);

  // Get rid of leading dot for an extension
  parsed.ext = parsed.ext.replace(/^\./, "");
  parsed.path = p;

  if (base) {
    base = Utils.makePathAbsolute(base);
    parsed.basePath = base;
    parsed.folder = path.relative(base, parsed.path.replace(new RegExp(parsed.base + "$"), ""));
  }

  return parsed;

};

/**

  Creates appropriate routes for a file: for example, /about/index.html needs
  /about/, /about/index, and /about/index.html.

  Files named "template" are special cases and serve as wildcard route handlers.

  @param {String} baseRoute Base route from which to build
  @param {Stat} file File object (as returned from fs.lStat)
  @param {Object} options Options
  @param {String} [options.type] Default file type (e.g. js for coffee)
  @returns {Array} Array of route strings

**/

Utils.makeRoutes = function(baseRoute, file, options) {

  var re = /\/\/+$/,
      route,
      routes,
      ext;

  options = options || {};

  // By defaulting to file type, we address templating and transpiling. We can
  // resolve coffee files to js, etc. We also remove leading dot.
  ext = options.type || file.ext || "";
  ext = ext.replace(/^\./, "");

  // Create the file's route by appending the folder to the base route. This
  // process is prone to double "//", which must be removed.
  route = (url.resolve((baseRoute + "/").replace(re, "/"), file.folder) + "/").replace(re, "/");

  // HTML-like files named "template" pull their route from the parent directory
  // and pass the subsequent URL on as a "query" parameter. Note that $ are not
  // allowed in params.
  if (Utils.isTemplate(file, ext)) {
    route = route.split("/").slice(0, -1).join("/");
    return [route + "/" + (options.params || ":slug").replace(/^\//, "")];
  }

  // Add file and default extension
  routes = [url.resolve(route, file.name + "." + ext)];

  // HTML files are special: not only can they be accessed sans extension, if
  // they are named "index", we allow access via a folder.
  if ("html" === ext) {
    routes.push(url.resolve(route, file.name));
    if (Utils.isIndexFile(file)) routes.push(route);
  }

  return routes;

};

/**

  Reads all files in a directory

  @param {String} p Path to read
  @param {Boolean} recursive Whether to read recursively (default: true)
  @param [String] base Optional base path from which to parse

**/

Utils.readDirectory = function(p, recursive, base) {

  var results = [];

  // Set defaults
  if ("undefined" == typeof recursive) recursive = true;
  p = Utils.makePathAbsolute(p);

  // If directory doesn't exist, return nothing
  try {
    fs.lstatSync(p);
  } catch (err) {
    return results;
  }

  // Read in files
  fs.readdirSync(p).forEach(function(file) {

    var stats, parsed;

    // Read file and stats
    file = path.join(p, file);
    stats = fs.lstatSync(file);

    // Recursively walk directory, or add file
    if (stats.isDirectory()) {
      if (recursive)
        results = results.concat(Utils.readDirectory(file, true, base));
    } else {
      results.push(Utils.parse(file, base || p));
    }

  });

  return results;

};

/**

 Reads a limited number of characters from a file.

 @param {String} path Path to file
 @param {Number} chars Number of chars (defaults to 500)
 @returns {String} First characters for a file

**/

Utils.readFileHead = function(path, chars) {

  var buffer = new Buffer(chars || 500),
      file   = fs.openSync(path, "r"),
      str,
      len;

  // Read in file
  len = fs.readSync(file, buffer, 0, chars, 0);

  // Convert to lines, and discard any fragments
  // (so that we can use template engines without through errors)
  str = buffer.slice(0, len).toString().split("\n");

  return str.slice(0, str.length - 1).join("\n");

};

/**

  Returns all regex matches for an expression

  @param {String} str String to search
  @param {RegExp} pattern Pattern to search with
  @param {Number} [idx] Search component to use
  @returns {Array} Array of strings with matches

**/

Utils.getRegExpMatches = function(str, pattern, idx) {

  var match,
      matches = [];

  // Clone the RE and add global flag (otherwise has endless loop)
  pattern = new RegExp(pattern);
  pattern.global = true;

  // Set default idx
  idx = idx || 0;

  while ((match = pattern.exec(str)))
    matches.push(match[idx]);

  return matches;

};

/**

  Watches a directory via chokidar

  @param {String} sourceDir The source directory (without wildcards)
  @param {String} sourceExt The source extension (defaults to "*")
  @param {Object} [options.chokidar] Options to pass to chokidar
  @param {Function} [options.*] Event handlers (file, compiled) where * is name of event

**/

Utils.watch = reorg(function(sourceDir, sourceExt, options) {

  var p;

  // Don't watch in production
  if (Utils.isProduction()) return;

  // For source extension, if not wildcard, convert to "*.ext"
  if ("*" !== sourceExt) sourceExt = "*." + sourceExt.replace(/^[\*\.]+/g, "");

  // Ensure we have chokidar options and that chokidar's cwd is root
  p = Utils.makePathAbsolute(path.join(sourceDir, "**", sourceExt));
  options.chokidar = options.chokidar || {};
  options.chokidar.cwd = path.parse(p).root;

  chokidar.watch(p, options.chokidar).on("all", function(event, file) {

    var parsed, compiled;

    if (options[event] || options.all) {

      // File is missing root directory
      file = path.join(options.chokidar.cwd, file);

      // Map file to compiled version
      if (options.type) {
        parsed = path.parse(file);
        compiled = path.join(parsed.root, parsed.dir, parsed.name + "." + options.type);
      }

      // Callback
      if (options[event]) options[event](file, compiled);
      if (options.all) options.all(file, compiled);

    }

  });

}, "string!", ["string", "*"], "object");

/**

  Determines whether environment is production

  @returns {Boolean} True if is a production environment

**/

Utils.isProduction = function() {
  return /^prod/i.test(process.env.NODE_ENV);
};

/**

  Runs a sequence of promises, similar to when.js

  @param {Array} tasks Array of "tasks" (functions that return promises)
  @param {*} initial Initial value to call task with

**/

Utils.sequence = function(tasks, initial) {
  return Promise.reduce(tasks || [], function(val, task) {
    return task(val);
  }, initial);
};

/**

  Tests whether a file is a template (HTML-like and named properly)

  @param {Stats} file File as returned by fs.lstat
  @param {String} type The type of file extension
  @returns {Boolean} Returns true is is a template

**/

Utils.isTemplate = function(file, type) {
  return ("template" === file.name) && Utils.isHTML(type);
};

/**

  Tests whether a type is HTML-like

  @param {String} type Type of file
  @returns {Boolean} Returns true if is HTML-like

**/

Utils.isHTML = function(type) {
  return /^\*?\.?html?$/i.test(type);
};

/**

  Tests that an input is a non-array object.

  @param {*} input The item to check
  @returns {Boolean} Returns true if is an object, but not an array

**/

Utils.isPlainObject = function(input) {
  return ("object" == typeof input) && !Array.isArray(input);
};

/**

  Tests for an array of type

  @param {*} input The item to check
  @param {String} type The requisite type
  @returns {Boolean} Returns true if is an array of strings

**/

Utils.isArrayOfType = function(input, type) {
  if (!Array.isArray(input)) return false;
  for (var i = 0, n = input.length; i < n; i++)
    if (type !== typeof input[i]) return false;
  return true;
};

/**

  Traverses a JSON-object and calls function on each end point.

**/

Utils.traverse = function(obj, fn) {

  var key, val;

  for (key in obj) {
    val = obj[key];
    if ("string" === typeof val)
      fn(obj, key, val);
    else if (Utils.isPlainObject(val))
      Utils.traverse(val, fn);
  }

};

/**

  Escapes regex. From lodash.com by John Dalton.

  @param {String} str The string to escape

**/

Utils.escapeRegex = function(str) {
  return str.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
};

/**

  Compresses a string.

  @param {String} method The compression method
  @param {String} str The string to compress
  @param {Boolean} maxCompress If true, maximum compression level
  @param {Function} next Callback of form (err, compressed)

**/

Utils.compress = function(method, str, level, next) {

  var zip = ("gzip" === method) ? zlib.gzip : zlib.deflate;

  level = level ? zlib.Z_BEST_COMPRESSION : zlib.Z_DEFAULT_COMPRESSION;
  zip(str, { level : level }, next);

};

/**

  Sends a compressed or uncompressed response.

  @param {Response} res The response
  @param {String} type The file type
  @param {Buffer} buf The buffer to send
  @param {String} enc The encoding to use
  @param {Number} [cache] Cache-control setting

**/

Utils.send = function(res, type, buf, enc, cache) {

  // File type
  res.type(type);

  // Cache control
  if (undefined !== cache)
    res.setHeader("Cache-Control", cache);

  // Raw versus compressed
  if ("identity" === enc || !enc) {
    res.send(buf);
  } else {
    res.setHeader("Content-Encoding", enc);
    res.removeHeader("Content-Length");
    res.send(buf);
  }

};

/**

  Attaches middleware to server, or ignores if blank.

  @param {Express} server Server instance
  @param {Middleware} library (req, res)-style middleware
  @param {Boolean} [skipCheck] If true, ignores errors (defaults to false)

**/

Utils.useMiddleware = function(server, library, skipCheck) {
  if (!library || Array.isArray(library) && !library.length) {
    if (skipCheck) return;
    return new Error("No middleware added");
  }
  server.use(library);
};

/**

  Executes multiple queries and combines into a single object

  @param {Array} queries Array of UniversQL queries
  @param {Object} context Templating variables to insert into the query
  @param {Function} next Callback with results

**/

Utils.makeQueries = function(queries, context, next) {

  var results = {},
      remaining;

  // Ensure that queries is an array
  queries = queries || [];
  if (!Array.isArray(queries)) queries = [queries];

  remaining = queries.length;
  if (!remaining) return next(null, {});

  queries.forEach(function(query) {
    global.db.query(query.query, context, function(err, result) {

      if ("undefined" !== typeof query.idx) {
        if (!result.length) return next(new Error("Result not found"), null);
        result = result[query.idx];
      }

      results = Object.assign(results, (query.key) ? pair([[query.key, result]]) : result);
      if (--remaining === 0) next(err, results);

    });
  });

};
<|endoftext|>


<commit_before>'use strict'

var ui = require('../utils/ui')

var $ = window.jQuery
var tinymce = window.tinymce

module.exports = HeadFoot

/**
 * Abstract class to inherit Header and Footer sub classes from.
 * @constructor
 * @param {Editor} editor The current editor
 * @param {DOMElement} documentBody The document body for this documentBody
 * @param {DOMNode} [existingElement] The optional existing element that constitute a header of a footer and should be loaded from it
 * @property {Editor} _editor The current editor
 * @property {DOMElement}  _documentBody The body element of the current document
 * @property {DOMNode} node The header/footer's node element
 */
function HeadFoot (editor, documentBody, existingElement) {
  // bind useful vars
  var that = this
  this._editor = editor
  this._documentBody = documentBody
  this.pluginPaginate = editor.plugins.paginate

  // load the existing element if it exists or create a new one.
  if (existingElement) {
    this.node = existingElement
  } else {
    this._createNode()
  }

  var $thisNode = $(this.node)
  // live the node and implements the double click handler to switch the contentEditable mode.
  this.isActive = false
  this.liveNode()
  $thisNode.dblclick(this.enterNode.bind(this))
  $(this._documentBody).on('EnterNode', function (evt) {
    if (that.node !== evt.target) {
      that.liveNode()
    }
  })
}

/**
 * Create a new node for an header or a footer.
 * @private
 * @method
 */
HeadFoot.prototype._createNode = function () {
  this.node = $('
')
  .attr('data-headfoot', true)
  .attr('data-headfoot-pristine', true)[0]
  this.setPlaceholder()
}

/**
 * Disable the page edition and enable the edition for the header or the footers
 * @method
 * @returns void
 */
HeadFoot.prototype.enterNode = function () {
  if (!this.isActive) {
    // var that = this
    // var currentPageContent
    var headfootContent
    var $thisNode = $(this.node)

    this.isActive = true
    $thisNode.trigger('EnterNode', this.node)

    // disable paginator watching
    // if (this.pluginPaginate) {
    //   this.pluginPaginate.disableWatchPage()
    //
    //   // toggle elements states (contentEditable or not)
    //   $.each(this.pluginPaginate.paginator.getPages(), function () {
    //     ui.lockNode.call(this)
    //   })
    // }

    ui.unlockNode.call(this.node)

    // select the unlocked node content or not
    headfootContent = this.node.firstChild
    if (!headfootContent) {
      throw new Error('no child is not allowed in a headfoot')
    }
    this._editor.selection.select(headfootContent)
    this._editor.selection.collapse(true)
    if ($thisNode.attr('data-headfoot-pristine')) {
      $thisNode.removeAttr('data-headfoot-pristine')
    }

    }
    // if (this.pluginPaginate) {
    //   // bind a click handler to the current page to toggle contentEditable state between header/footer and the page
    //   currentPageContent = this.pluginPaginate.getCurrentPage().content()
    //   $(currentPageContent).click(that.liveNode.bind(that))
    // }
  }
}

/**
 * Do the inverse of .enterNode(). Disable edition for the header or footer, and re-enable it for the current page.
 * @method
 * @returns void
 */
HeadFoot.prototype.liveNode = function () {
  this.isActive = false
  $(this.node).trigger('LiveNode', this.node)
  if (this.pluginPaginate) {
    this.pluginPaginate.enableWatchPage()
    $.each(this.pluginPaginate.paginator.getPages(), function () {
      ui.unlockNode.call(this)
    })
  }
  // var sectionChildNodes = this.node.childNodes
  // if (sectionChildNodes) {
  //   if (
  //     !sectionChildNodes.length ||
  //     sectionChildNodes.length === 1 && !sectionChildNodes.item(0).childNodes.length ||
  //     sectionChildNodes.length === 1 && sectionChildNodes.item(0).childNodes.length === 1 && sectionChildNodes.item(0).childNodes.item(0) === 'BR'
  //   ) {
  //     this.setPlaceholder()
  //   }
  // } else {
  //   this.setPlaceholder()
  // }
  ui.lockNode.call(this.node)
}

HeadFoot.prototype.setPlaceholder = function () {
  var $placeholderSpan = $('').css({ 'font-family': 'calibri', 'font-size': '12pt' })
  var $placeholder = $('
').append($placeholderSpan)
  var translatedLabel = tinymce.i18n.translate('Double-click to edit this content')
  $placeholderSpan.html(translatedLabel)
  $(this.node).empty().append($placeholder)

HeadFoot.prototype.initParagraph = function () {
  var $span = $('').css({ 'font-family': 'calibri', 'font-size': '12pt' })
  var $p = $('
').append($span)
  $span.html('
')
  $(this.node).removeAttr('data-headfoot-pristine').empty().append($p)
  return $p
}

}
<commit_msg>create pristine()<commit_after>'use strict'

var ui = require('../utils/ui')

var $ = window.jQuery
var tinymce = window.tinymce

module.exports = HeadFoot

/**
 * Abstract class to inherit Header and Footer sub classes from.
 * @constructor
 * @param {Editor} editor The current editor
 * @param {DOMElement} documentBody The document body for this documentBody
 * @param {DOMNode} [existingElement] The optional existing element that constitute a header of a footer and should be loaded from it
 * @property {Editor} _editor The current editor
 * @property {DOMElement}  _documentBody The body element of the current document
 * @property {DOMNode} node The header/footer's node element
 */
function HeadFoot (editor, documentBody, existingElement) {
  // bind useful vars
  var that = this
  this._editor = editor
  this._documentBody = documentBody
  this.pluginPaginate = editor.plugins.paginate

  // load the existing element if it exists or create a new one.
  if (existingElement) {
    this.node = existingElement
  } else {
    this._createNode()
  }

  var $thisNode = $(this.node)
  // live the node and implements the double click handler to switch the contentEditable mode.
  this.isActive = false
  this.liveNode()
  $thisNode.dblclick(this.enterNode.bind(this))
  $(this._documentBody).on('EnterNode', function (evt) {
    if (that.node !== evt.target) {
      that.liveNode()
    }
  })
}

/**
 * Create a new node for an header or a footer.
 * @private
 * @method
 */
HeadFoot.prototype._createNode = function () {
  this.node = $('
')
  .attr('data-headfoot', true)
  .attr('data-headfoot-pristine', true)[0]
  this.setPlaceholder()
}

/**
 * Disable the page edition and enable the edition for the header or the footers
 * @method
 * @returns void
 */
HeadFoot.prototype.enterNode = function () {
  if (!this.isActive) {
    // var that = this
    // var currentPageContent
    var headfootContent
    var $thisNode = $(this.node)

    this.isActive = true
    $thisNode.trigger('EnterNode', this.node)

    // disable paginator watching
    // if (this.pluginPaginate) {
    //   this.pluginPaginate.disableWatchPage()
    //
    //   // toggle elements states (contentEditable or not)
    //   $.each(this.pluginPaginate.paginator.getPages(), function () {
    //     ui.lockNode.call(this)
    //   })
    // }

    ui.unlockNode.call(this.node)

    // select the unlocked node content or not
    headfootContent = this.node.firstChild
    if (!headfootContent) {
      throw new Error('no child is not allowed in a headfoot')
    }
    this._editor.selection.select(headfootContent)
    this._editor.selection.collapse(true)
    if ($thisNode.attr('data-headfoot-pristine')) {
      $thisNode.removeAttr('data-headfoot-pristine')
    }

    }
    // if (this.pluginPaginate) {
    //   // bind a click handler to the current page to toggle contentEditable state between header/footer and the page
    //   currentPageContent = this.pluginPaginate.getCurrentPage().content()
    //   $(currentPageContent).click(that.liveNode.bind(that))
    // }
  }
}

/**
 * Do the inverse of .enterNode(). Disable edition for the header or footer, and re-enable it for the current page.
 * @method
 * @returns void
 */
HeadFoot.prototype.liveNode = function () {
  this.isActive = false
  $(this.node).trigger('LiveNode', this.node)
  if (this.pluginPaginate) {
    this.pluginPaginate.enableWatchPage()
    $.each(this.pluginPaginate.paginator.getPages(), function () {
      ui.unlockNode.call(this)
    })
  }
  // var sectionChildNodes = this.node.childNodes
  // if (sectionChildNodes) {
  //   if (
  //     !sectionChildNodes.length ||
  //     sectionChildNodes.length === 1 && !sectionChildNodes.item(0).childNodes.length ||
  //     sectionChildNodes.length === 1 && sectionChildNodes.item(0).childNodes.length === 1 && sectionChildNodes.item(0).childNodes.item(0) === 'BR'
  //   ) {
  //     this.setPlaceholder()
  //   }
  // } else {
  //   this.setPlaceholder()
  // }
  ui.lockNode.call(this.node)
}

HeadFoot.prototype.setPlaceholder = function () {
  var $placeholderSpan = $('').css({ 'font-family': 'calibri', 'font-size': '12pt' })
  var $placeholder = $('
').append($placeholderSpan)
  var translatedLabel = tinymce.i18n.translate('Double-click to edit this content')
  $placeholderSpan.html(translatedLabel)
  $(this.node).empty().append($placeholder)

HeadFoot.prototype.initParagraph = function () {
  var $span = $('').css({ 'font-family': 'calibri', 'font-size': '12pt' })
  var $p = $('
').append($span)
  $span.html('
')
  $(this.node).removeAttr('data-headfoot-pristine').empty().append($p)
  return $p
}

/**
 * [Getter/Setter] Get or set the pristine state of the headfoot node
 * @method
 * @param {Boolean} [b] If defined, the value to set
 * @returns {Boolean|undefined} - The pristine value if no argument is given.
 * @throws {Error} - if this.node is unset an error is thrown
 */
HeadFoot.prototype.pristine = function (b) {
  if (!this.node || !this.node.nodeType) {
    throw new Error('Missing node can not be pristine or not.')
  }
  var attr = 'data-headfoot-pristine'
  if (b === undefined) {
    return this.node.getAttribute(attr) === 'true'
  } else {
    this.node.setAttribute(attr, !!b)
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
 * directory of this distribution and at
 * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
 */
;
(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define(factory);
    } else if (typeof exports === "object") {
        module.exports = factory();
    } else {
        root.ResizeSensor = factory();
    }
}(this, function () {

    // Make sure it does not throw in a SSR (Server Side Rendering) situation
    if (typeof window === "undefined") {
        return null;
    }
    // Only used for the dirty checking, so the event callback count is limited to max 1 call per fps per sensor.
    // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
    // would generate too many unnecessary events.
    var requestAnimationFrame = window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (fn) {
            return window.setTimeout(fn, 20);
        };

    /**
     * Iterate over each of the provided element(s).
     *
     * @param {HTMLElement|HTMLElement[]} elements
     * @param {Function}                  callback
     */
    function forEachElement(elements, callback){
        var elementsType = Object.prototype.toString.call(elements);
        var isCollectionTyped = ('[object Array]' === elementsType
            || ('[object NodeList]' === elementsType)
            || ('[object HTMLCollection]' === elementsType)
            || ('[object Object]' === elementsType)
            || ('undefined' !== typeof jQuery && elements instanceof jQuery) //jquery
            || ('undefined' !== typeof Elements && elements instanceof Elements) //mootools
        );
        var i = 0, j = elements.length;
        if (isCollectionTyped) {
            for (; i < j; i++) {
                callback(elements[i]);
            }
        } else {
            callback(elements);
        }
    }

    /**
     * Class for dimension change detection.
     *
     * @param {Element|Element[]|Elements|jQuery} element
     * @param {Function} callback
     *
     * @constructor
     */
    var ResizeSensor = function(element, callback) {
        /**
         *
         * @constructor
         */
        function EventQueue() {
            var q = [];
            this.add = function(ev) {
                q.push(ev);
            };

            var i, j;
            this.call = function() {
                for (i = 0, j = q.length; i < j; i++) {
                    q[i].call();
                }
            };

            this.remove = function(ev) {
                var newQueue = [];
                for(i = 0, j = q.length; i < j; i++) {
                    if(q[i] !== ev) newQueue.push(q[i]);
                }
                q = newQueue;
            }

            this.length = function() {
                return q.length;
            }
        }

        /**
         * @param {HTMLElement} element
         * @param {String}      prop
         * @returns {String|Number}
         */
        function getComputedStyle(element, prop) {
            if (element.currentStyle) {
                return element.currentStyle[prop];
            }
            if (window.getComputedStyle) {
                return window.getComputedStyle(element, null).getPropertyValue(prop);
            }

            return element.style[prop];
        }

        /**
         *
         * @param {HTMLElement} element
         * @param {Function}    resized
         */
        function attachResizeEvent(element, resized) {
            if (!element) return;
            if (element.resizedAttached) {
                element.resizedAttached.add(resized);
                return;
            }

            element.resizedAttached = new EventQueue();
            element.resizedAttached.add(resized);

            element.resizeSensor = document.createElement('div');
            element.resizeSensor.className = 'resize-sensor';
            var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
            var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';

            element.resizeSensor.style.cssText = style;
            element.resizeSensor.innerHTML =
                '
' +
                    '

' +
                '
' +
                '
' +
                    '

' +
                '
';
            element.appendChild(element.resizeSensor);

            if (getComputedStyle(element, 'position') == 'static') {
                element.style.position = 'relative';
            }

            var expand = element.resizeSensor.childNodes[0];
            var expandChild = expand.childNodes[0];
            var shrink = element.resizeSensor.childNodes[1];
            var dirty, rafId, newWidth, newHeight;
            var lastWidth = element.offsetWidth;
            var lastHeight = element.offsetHeight;

            var reset = function() {
                expandChild.style.width = '100000px';
                expandChild.style.height = '100000px';

                expand.scrollLeft = 100000;
                expand.scrollTop = 100000;

                shrink.scrollLeft = 100000;
                shrink.scrollTop = 100000;
            };

            reset();

            var onResized = function() {
                rafId = 0;

                if (!dirty) return;

                lastWidth = newWidth;
                lastHeight = newHeight;

                if (element.resizedAttached) {
                    element.resizedAttached.call();
                }
            };

            var onScroll = function() {
                newWidth = element.offsetWidth;
                newHeight = element.offsetHeight;
                dirty = newWidth != lastWidth || newHeight != lastHeight;

                if (dirty && !rafId) {
                    rafId = requestAnimationFrame(onResized);
                }

                reset();
            };

            var addEvent = function(el, name, cb) {
                if (el.attachEvent) {
                    el.attachEvent('on' + name, cb);
                } else {
                    el.addEventListener(name, cb);
                }
            };

            addEvent(expand, 'scroll', onScroll);
            addEvent(shrink, 'scroll', onScroll);
        }

        forEachElement(element, function(elem){
            attachResizeEvent(elem, callback);
        });

        this.detach = function(ev) {
            ResizeSensor.detach(element, ev);
        };
    };

    ResizeSensor.detach = function(element, ev) {
        forEachElement(element, function(elem){
            if (!elem) return
            if(elem.resizedAttached && typeof ev == "function"){
                elem.resizedAttached.remove(ev);
                if(elem.resizedAttached.length()) return;
            }
            if (elem.resizeSensor) {
                if (elem.contains(elem.resizeSensor)) {
                    elem.removeChild(elem.resizeSensor);
                }
                delete elem.resizeSensor;
                delete elem.resizedAttached;
            }
        });
    };

    return ResizeSensor;

}));
<commit_msg>ResizeSensor.js optimization (#172)<commit_after>/**
 * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
 * directory of this distribution and at
 * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
 */
;
(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define(factory);
    } else if (typeof exports === "object") {
        module.exports = factory();
    } else {
        root.ResizeSensor = factory();
    }
}(this, function () {

    // Make sure it does not throw in a SSR (Server Side Rendering) situation
    if (typeof window === "undefined") {
        return null;
    }
    // Only used for the dirty checking, so the event callback count is limited to max 1 call per fps per sensor.
    // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
    // would generate too many unnecessary events.
    var requestAnimationFrame = window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (fn) {
            return window.setTimeout(fn, 20);
        };

    /**
     * Iterate over each of the provided element(s).
     *
     * @param {HTMLElement|HTMLElement[]} elements
     * @param {Function}                  callback
     */
    function forEachElement(elements, callback){
        var elementsType = Object.prototype.toString.call(elements);
        var isCollectionTyped = ('[object Array]' === elementsType
            || ('[object NodeList]' === elementsType)
            || ('[object HTMLCollection]' === elementsType)
            || ('[object Object]' === elementsType)
            || ('undefined' !== typeof jQuery && elements instanceof jQuery) //jquery
            || ('undefined' !== typeof Elements && elements instanceof Elements) //mootools
        );
        var i = 0, j = elements.length;
        if (isCollectionTyped) {
            for (; i < j; i++) {
                callback(elements[i]);
            }
        } else {
            callback(elements);
        }
    }

    /**
     * Class for dimension change detection.
     *
     * @param {Element|Element[]|Elements|jQuery} element
     * @param {Function} callback
     *
     * @constructor
     */
    var ResizeSensor = function(element, callback) {
        /**
         *
         * @constructor
         */
        function EventQueue() {
            var q = [];
            this.add = function(ev) {
                q.push(ev);
            };

            var i, j;
            this.call = function() {
                for (i = 0, j = q.length; i < j; i++) {
                    q[i].call();
                }
            };

            this.remove = function(ev) {
                var newQueue = [];
                for(i = 0, j = q.length; i < j; i++) {
                    if(q[i] !== ev) newQueue.push(q[i]);
                }
                q = newQueue;
            }

            this.length = function() {
                return q.length;
            }
        }

        /**
         *
         * @param {HTMLElement} element
         * @param {Function}    resized
         */
        function attachResizeEvent(element, resized) {
            if (!element) return;
            if (element.resizedAttached) {
                element.resizedAttached.add(resized);
                return;
            }

            element.resizedAttached = new EventQueue();
            element.resizedAttached.add(resized);

            element.resizeSensor = document.createElement('div');
            element.resizeSensor.className = 'resize-sensor';
            var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
            var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';

            element.resizeSensor.style.cssText = style;
            element.resizeSensor.innerHTML =
                '
' +
                    '

' +
                '
' +
                '
' +
                    '

' +
                '
';
            element.appendChild(element.resizeSensor);

            if (element.resizeSensor.offsetParent !== element) {
                element.style.position = 'relative';
            }

            var expand = element.resizeSensor.childNodes[0];
            var expandChild = expand.childNodes[0];
            var shrink = element.resizeSensor.childNodes[1];
            var dirty, rafId, newWidth, newHeight;
            var lastWidth = element.offsetWidth;
            var lastHeight = element.offsetHeight;

            var reset = function() {
                expandChild.style.width = '100000px';
                expandChild.style.height = '100000px';

                expand.scrollLeft = 100000;
                expand.scrollTop = 100000;

                shrink.scrollLeft = 100000;
                shrink.scrollTop = 100000;
            };

            reset();

            var onResized = function() {
                rafId = 0;

                if (!dirty) return;

                lastWidth = newWidth;
                lastHeight = newHeight;

                if (element.resizedAttached) {
                    element.resizedAttached.call();
                }
            };

            var onScroll = function() {
                newWidth = element.offsetWidth;
                newHeight = element.offsetHeight;
                dirty = newWidth != lastWidth || newHeight != lastHeight;

                if (dirty && !rafId) {
                    rafId = requestAnimationFrame(onResized);
                }

                reset();
            };

            var addEvent = function(el, name, cb) {
                if (el.attachEvent) {
                    el.attachEvent('on' + name, cb);
                } else {
                    el.addEventListener(name, cb);
                }
            };

            addEvent(expand, 'scroll', onScroll);
            addEvent(shrink, 'scroll', onScroll);
        }

        forEachElement(element, function(elem){
            attachResizeEvent(elem, callback);
        });

        this.detach = function(ev) {
            ResizeSensor.detach(element, ev);
        };
    };

    ResizeSensor.detach = function(element, ev) {
        forEachElement(element, function(elem){
            if (!elem) return
            if(elem.resizedAttached && typeof ev == "function"){
                elem.resizedAttached.remove(ev);
                if(elem.resizedAttached.length()) return;
            }
            if (elem.resizeSensor) {
                if (elem.contains(elem.resizeSensor)) {
                    elem.removeChild(elem.resizeSensor);
                }
                delete elem.resizeSensor;
                delete elem.resizedAttached;
            }
        });
    };

    return ResizeSensor;

}));
<|endoftext|>


<commit_before>'use strict';

angular.module('arachne.controllers')

    .controller('EntityController', ['$rootScope', '$stateParams', 'searchService', '$scope', '$uibModal', 'Entity', '$location', 'arachneSettings', 'Catalog', 'CatalogEntry', 'authService', 'categoryService', 'Query', 'messageService',
        function ($rootScope, $stateParams, searchService, $scope, $uibModal, Entity, $location, arachneSettings, Catalog, CatalogEntry, authService, categoryService, Query, messages) {

            $rootScope.hideFooter = false;

            $scope.user = authService.getUser();
            $scope.serverUri = "http://" + document.location.host + document.getElementById('baseLink').getAttribute("href");

            categoryService.getCategoriesAsync().then(function (categories) {
                $scope.categories = categories;
            });

            $scope.currentQuery = searchService.currentQuery();

            $scope.goToResultIndex = function (resultIndex) {
                if (resultIndex > 0 && resultIndex <= $scope.resultSize) {
                    $location.url('/entity/' + $scope.currentQuery.setParam('resultIndex',resultIndex).toString());
                }
            };

            // if no id given, but query get id from search and reload
            if (!$stateParams.id && $scope.currentQuery.hasOwnProperty('resultIndex')) {

                var resultIndex = parseInt($scope.currentQuery.resultIndex);
                searchService.getEntity(resultIndex).then(function (entity) {
                    $location.url('entity/' + entity.entityId + $scope.currentQuery.toString());
                    $location.replace();
                });

            } else {

                var live = $location.search()["live"] == "true";

                Entity.get({id: $stateParams.id, live: live}, function (data) {

                    $scope.entity = data;
                    // very ugly exception for Berliner Skulpturennetzwerk
                    // hide all data not in datenblatt_berlin
                    if (data.sections[0].label == 'Datenblatt Berlin') {
                        data.sections = data.sections.slice(0,1);
                    }
                    categoryService.getCategoryHref($scope.entity.type).then(function (categoryHref) {
                        $scope.entity.categoryHref = categoryHref;
                    });
                    categoryService.getCategoryKey($scope.entity.type).then(function (key) {
                        $scope.entity.categoryKey = key;
                    });

                    /**
                     * Hide map widget if no marker coordinates are provided
                     * Jan G. Wieners
                     */
                    if (data.places !== undefined) {

                        var cur, locationsExist = false, len = data.places.length;
                        for (var j = len; j--;) {

                            cur = data.places[j].location;
                            if (cur && cur.lat && cur.lon) {
                                locationsExist = true;
                                break;
                            }
                        }
                    }
                    if (!locationsExist) {
                        $scope.entity.places = false;
                    }

                    $scope.entity.lastModified = new Date(data.lastModified).toISOString();

                    document.title = $scope.entity.title + " | Arachne";

                }, function (response) {
                    $scope.error = true;
                    messages.add("entity_" + response.status);
                });

                $scope.contextQuery = new Query();
                $scope.contextQuery.label = "Mit " + $stateParams.id + " verknüpfte Objekte";
                $scope.contextQuery.q = "connectedEntities:" + $stateParams.id;
                $scope.contextQuery.limit = 0;

                if ($scope.currentQuery.hasOwnProperty('resultIndex')) {

                    $scope.resultIndex = parseInt($scope.currentQuery.resultIndex);
                    $scope.resultIndexInput = $scope.resultIndex;
                    searchService.getCurrentPage().then(function (results) {
                        $scope.searchresults = results;
                        $scope.resultSize = searchService.getSize();
                    }, function (response) {
                        $scope.searchresults = {size: 0};
                        messages.add('search_' + response.status);
                    });

                    var prevIndex = $scope.resultIndex - 1;
                    $scope.prevEntity = searchService.getEntity(prevIndex).then(function (entity) {
                        $scope.prevEntity = entity;
                    }, function () {
                        $scope.prevEntity = false;
                    });
                    var nextIndex = $scope.resultIndex + 1;
                    $scope.nextEntity = searchService.getEntity(nextIndex).then(function (entity) {
                        $scope.nextEntity = entity;
                    }, function () {
                        $scope.prevEntity = false;
                    });

                }
            }

        }
    ]);
<commit_msg>Check for undefined last modified date.<commit_after>'use strict';

angular.module('arachne.controllers')

    .controller('EntityController', ['$rootScope', '$stateParams', 'searchService', '$scope', '$uibModal', 'Entity', '$location', 'arachneSettings', 'Catalog', 'CatalogEntry', 'authService', 'categoryService', 'Query', 'messageService',
        function ($rootScope, $stateParams, searchService, $scope, $uibModal, Entity, $location, arachneSettings, Catalog, CatalogEntry, authService, categoryService, Query, messages) {

            $rootScope.hideFooter = false;

            $scope.user = authService.getUser();
            $scope.serverUri = "http://" + document.location.host + document.getElementById('baseLink').getAttribute("href");

            categoryService.getCategoriesAsync().then(function (categories) {
                $scope.categories = categories;
            });

            $scope.currentQuery = searchService.currentQuery();

            $scope.goToResultIndex = function (resultIndex) {
                if (resultIndex > 0 && resultIndex <= $scope.resultSize) {
                    $location.url('/entity/' + $scope.currentQuery.setParam('resultIndex',resultIndex).toString());
                }
            };

            // if no id given, but query get id from search and reload
            if (!$stateParams.id && $scope.currentQuery.hasOwnProperty('resultIndex')) {

                var resultIndex = parseInt($scope.currentQuery.resultIndex);
                searchService.getEntity(resultIndex).then(function (entity) {
                    $location.url('entity/' + entity.entityId + $scope.currentQuery.toString());
                    $location.replace();
                });

            } else {

                var live = $location.search()["live"] == "true";

                Entity.get({id: $stateParams.id, live: live}, function (data) {

                    $scope.entity = data;
                    // very ugly exception for Berliner Skulpturennetzwerk
                    // hide all data not in datenblatt_berlin
                    if (data.sections[0].label == 'Datenblatt Berlin') {
                        data.sections = data.sections.slice(0,1);
                    }
                    categoryService.getCategoryHref($scope.entity.type).then(function (categoryHref) {
                        $scope.entity.categoryHref = categoryHref;
                    });
                    categoryService.getCategoryKey($scope.entity.type).then(function (key) {
                        $scope.entity.categoryKey = key;
                    });

                    /**
                     * Hide map widget if no marker coordinates are provided
                     * Jan G. Wieners
                     */
                    if (data.places !== undefined) {

                        var cur, locationsExist = false, len = data.places.length;
                        for (var j = len; j--;) {

                            cur = data.places[j].location;
                            if (cur && cur.lat && cur.lon) {
                                locationsExist = true;
                                break;
                            }
                        }
                    }
                    if (!locationsExist) {
                        $scope.entity.places = false;
                    }

                    if (data.lastModified) {
                        $scope.entity.lastModified = new Date(data.lastModified).toISOString();
                    } else {
                        $scope.entity.lastModified = "";
                    }

                    document.title = $scope.entity.title + " | Arachne";

                }, function (response) {
                    $scope.error = true;
                    messages.add("entity_" + response.status);
                });

                $scope.contextQuery = new Query();
                $scope.contextQuery.label = "Mit " + $stateParams.id + " verknüpfte Objekte";
                $scope.contextQuery.q = "connectedEntities:" + $stateParams.id;
                $scope.contextQuery.limit = 0;

                if ($scope.currentQuery.hasOwnProperty('resultIndex')) {

                    $scope.resultIndex = parseInt($scope.currentQuery.resultIndex);
                    $scope.resultIndexInput = $scope.resultIndex;
                    searchService.getCurrentPage().then(function (results) {
                        $scope.searchresults = results;
                        $scope.resultSize = searchService.getSize();
                    }, function (response) {
                        $scope.searchresults = {size: 0};
                        messages.add('search_' + response.status);
                    });

                    var prevIndex = $scope.resultIndex - 1;
                    $scope.prevEntity = searchService.getEntity(prevIndex).then(function (entity) {
                        $scope.prevEntity = entity;
                    }, function () {
                        $scope.prevEntity = false;
                    });
                    var nextIndex = $scope.resultIndex + 1;
                    $scope.nextEntity = searchService.getEntity(nextIndex).then(function (entity) {
                        $scope.nextEntity = entity;
                    }, function () {
                        $scope.prevEntity = false;
                    });

                }
            }

        }
    ]);
<|endoftext|>


<commit_before>
'use strict';

var nconf = require('nconf'),
    q = require('q'),
    fs = require('fs'),
    mv = require('mv'),
    https = require('https'),
    pki = require('node-forge').pki,
    agentSchema = require('../schemata/agent');

nconf.file({ file: 'gebo.json' });

/**
 * Mongo naming restriction constants
 */
exports.constants = {
    at: '_at_',
    backslash: '_backslash_',
    colon: '_colon_',
    dollarSign: '_dollarsign_',
    dot: '_dot_',
    doubleQuotes: '_doublequotes_',
    greaterThan: '_greaterthan_',
    lessThan: '_lessthan_',
    noCollection: 'No collection',
    pipe: '_pipe_',
    questionMark: '_questionmark_',
    slash: '_slash_',
    space: '_space_',
    star: '_star_',
  };

/**
 * Return a unique identifier with the given `len`.
 *
 *     utils.uid(10);
 *     // => "FDaS435D2z"
 *
 * @param {Number} len
 * @return {String}
 * @api private
 */

/**
 * Return a random int, used by `utils.uid()`
 *
 * @param {Number} min
 * @param {Number} max
 * @return {Number}
 * @api private
 */
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }

exports.uid = function (len) {
    var buf = [],
        chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
        charlen = chars.length;

    for (var i = 0; i < len; ++i) {
      buf.push(chars[getRandomInt(0, charlen - 1)]);
    }

    return buf.join('');
  };

/**
 * Make the gebo user's email address suitable for naming
 * a database... and more!
 *
 * Mongo does not allow these characters: /\. "*<>:|?
 * (http://docs.mongodb.org/manual/reference/limits/)
 *
 * This function sanitizes @s as well, though it is not
 * required by Mongo.
 *
 * @param string
 *
 * @return string
 */
function _getMongoDbName(str) {
    if (!/[/\. "*<>:|?@]/.test(str)) {
      return str;
    }

    str = str.replace(/\//g, exports.constants.slash);
    str = str.replace(/\\/g, exports.constants.backslash);
    str = str.replace(/\./g, exports.constants.dot);
    str = str.replace(/ /g, exports.constants.space);
    str = str.replace(/"/g, exports.constants.doubleQuotes);
    str = str.replace(/\*/g, exports.constants.star);
    str = str.replace(/</g, exports.constants.lessThan);
    str = str.replace(/>/g, exports.constants.greaterThan);
    str = str.replace(/:/g, exports.constants.colon);
    str = str.replace(/\|/g, exports.constants.pipe);
    str = str.replace(/\?/g, exports.constants.questionMark);
    str = str.replace(/@/g, exports.constants.at);

    return str;
  };
exports.getMongoDbName = _getMongoDbName;

/**
 * Sanitize a mongo collection name. Find restrictions
 * on collections names here:
 *
 * http://docs.mongodb.org/manual/reference/limits/
 *
 * @param string
 *
 * @return string
 */
exports.getMongoCollectionName = function(str) {
    if (str === undefined ||
        str === null || str.length === 0) {
      return exports.constants.noCollection;
    }

    str = str.replace(/\$/g, exports.constants.dollarSign);

    if (/^system\.|^[^A-Za-z_]/.test(str)) {
      str = '_' + str;
    }

    return str;
  };

/**
 * Sanitize a mongo field name. Find restrictions
 * on collections names here:
 *
 * http://docs.mongodb.org/manual/reference/limits/
 *
 * @param string
 *
 * @return string
 */
exports.getMongoFieldName = function(str) {
    if (str === undefined ||
        str === null || str.length === 0) {
      return exports.constants.noCollection;
    }

    str = str.replace(/\$/g, exports.constants.dollarSign);
    str = str.replace(/\./g, exports.constants.dot);
    return str;
  };

/**
 * Given an flat object, return an URL-friendly query string.  Note
 * that for a given object, the return value may be.
 *
 * @example
 *

 *    // returns 'color=red&size=large'
 *    _objectToQueryString({color: 'red', size: 'large'})
 *

 *
 * @param {Object} A flat object containing keys for a query string.
 *
 * @returns {string} An URL-friendly query string.
 */
exports.objectToQueryString = function(obj) {
    var str = [];
    for (var key in obj) {
      str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
    }
    return str.join('&');
  };

/**
 * Take an email address and turn it into a mongo-friendly
 * database name
 *
 * @param string
 *
 * @return string
 */
exports.ensureDbName = function(email) {
//    if (!email) {
//      email = nconf.get('email');
//    }
    return _getMongoDbName(email);
  };

/**
 * Recursively save a batch of files
 *
 * @param Object - req.files
 * @param Object - verified user object
 * @param array - keys in the req.files object
 * @param index - the current index in the array
 * @param array - file objects
 *
 * @return promise
 */
function _saveFilesToAgentDirectory(files, verified, keys, index, fileObjects) {
   var deferred = q.defer();

    if (!files) {
      deferred.resolve();
      return deferred.promise;
    }

    if (keys === undefined) {
      keys = Object.keys(files);
    }

    if (index === undefined) {
      index = keys.length;
    }

    if (fileObjects === undefined) {
      fileObjects = [];
    }

    var dir = nconf.get('docs') + '/' + verified.dbName + '/' + verified.collectionName;

    if (index > 0) {
      _saveFilesToAgentDirectory(files, verified, keys, --index, fileObjects).
        then(function() {
                _getSafeFileName(files[keys[index]].name, dir).
                    then(function(filename) {

                        // Make file document for DB
                        var db = new agentSchema(verified.dbName);
                        var file = new db.fileModel({
                                name: filename,
                                collectionName: verified.collectionName,
                                type: files[keys[index]].type,
                                size: files[keys[index]].size,
                            });

                        file.save(function(err, file) {
                            fileObjects.push(file)
                            db.connection.db.close();
                            if (err) {
                             deferred.reject(err);
                            }
                            else {
                              mv(files[keys[index]].path, dir + '/' + filename, { mkdirp: true },
                                  function(err) {
                                      if (err) {
                                        deferred.reject(err);
                                      }
                                      else {
                                        deferred.resolve(fileObjects);
                                      }
                                    });
                            }
                          });
                      }).
                    catch(function(err) {
                        deferred.reject(err);
                      });
          });
    }
    else {
      deferred.resolve(fileObjects);
    }

    return deferred.promise;
  };
exports.saveFilesToAgentDirectory = _saveFilesToAgentDirectory;

/**
 * Append a copy number to a filename if a
 * file by that same name already exists
 *
 * @param string
 * @param string
 *
 * @return promise
 */
function _getSafeFileName(filename, directory) {
    var deferred = q.defer();

    fs.readdir(directory, function(err, files) {

        if(err) {
          // This means the directory doesn't exist
          if (err.errno === 34 && err.code === 'ENOENT') {
            deferred.resolve(filename);
          }
          else {
            deferred.reject(err);
          }
        }
        else {
          var index = files.indexOf(filename);
          if (index > -1) {

            // Get filename and extension (if it has one)
            var name, extension;
            var splitString = filename.split('.');

            // Has extension
            if (splitString.length > 1) {
              extension = splitString.pop();
              name = splitString.join('.');

              // Wait! Is this a hidden file?
              if (!name) {
                name = '.' + extension;
                extension = '';
              }
            }
            // No extension
            else {
              name = filename;
              extension = '';
            }

            // Get the copy number appended to the name and increment, if any
            var matches = name.match(/\((\d+)\)$/);
            if (matches) {
              var copyNumber = Number(matches[1]) + 1;
              name = name.replace(/\((\d+)\)$/, '(' + copyNumber + ')');
            }
            else {
              name += '(1)';
            }

            // Assemble new filename
            if (extension) {
              filename = name + '.' + extension;
            }
            else {
              filename = name;
            }
			deferred.resolve(_getSafeFileName(filename, directory));
          }
		  else {
          	deferred.resolve(filename);
		  }
        }
      });

    return deferred.promise;
  };
exports.getSafeFileName = _getSafeFileName;

/**
 * Get the index of the first object containing the matching
 * key-value pair
 *
 * @param Array of Objects
 * @param string
 * @param string, number
 *
 * @return int
 */
exports.getIndexOfObject = function(array, key, value) {
    for (var i = 0; i < array.length; i++) {
      if (array[i][key] === value) {
        return i;
      }
    }
    return -1;
  };

/**
 * Generate a private key and self-signed certificate
 * in PEM format
 *
 * @return Object
 */
exports.getPrivateKeyAndCertificate = function() {
    var deferred = q.defer();
    pki.rsa.generateKeyPair(512, function(err, keys) {
        if (err) {
          deferred.reject(err);
        }
        else {
          var cert = pki.createCertificate();
          cert.publicKey = keys.publicKey;
          cert.sign(keys.privateKey);
          cert.validity.notAfter = new Date() + 3600*24*365*10;

          deferred.resolve({
                  privateKey: pki.privateKeyToPem(keys.privateKey),
                  certificate: pki.certificateToPem(cert)
              });
        }

      });
    return deferred.promise;

  };

/**
 * Get the domain and HTTPS port set in local.json
 *
 * @return string
 */
exports.getDefaultDomain = function() {
    var host = nconf.get('domain');
    if (nconf.get('httpsPort')) {
      host += ':' + nconf.get('httpsPort');
    }
    return host;
  };
<commit_msg>Address current nconf problem (not loading file in time)<commit_after>
'use strict';

var nconf = require('nconf'),
    q = require('q'),
    fs = require('fs'),
    mv = require('mv'),
    https = require('https'),
    pki = require('node-forge').pki,
    agentSchema = require('../schemata/agent');

/**
 * 2014-4-17
 */
//nconf.file({ file: 'gebo.json' });

/**
 * Mongo naming restriction constants
 */
exports.constants = {
    at: '_at_',
    backslash: '_backslash_',
    colon: '_colon_',
    dollarSign: '_dollarsign_',
    dot: '_dot_',
    doubleQuotes: '_doublequotes_',
    greaterThan: '_greaterthan_',
    lessThan: '_lessthan_',
    noCollection: 'No collection',
    pipe: '_pipe_',
    questionMark: '_questionmark_',
    slash: '_slash_',
    space: '_space_',
    star: '_star_',
  };

/**
 * Return a unique identifier with the given `len`.
 *
 *     utils.uid(10);
 *     // => "FDaS435D2z"
 *
 * @param {Number} len
 * @return {String}
 * @api private
 */

/**
 * Return a random int, used by `utils.uid()`
 *
 * @param {Number} min
 * @param {Number} max
 * @return {Number}
 * @api private
 */
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }

exports.uid = function (len) {
    var buf = [],
        chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
        charlen = chars.length;

    for (var i = 0; i < len; ++i) {
      buf.push(chars[getRandomInt(0, charlen - 1)]);
    }

    return buf.join('');
  };

/**
 * Make the gebo user's email address suitable for naming
 * a database... and more!
 *
 * Mongo does not allow these characters: /\. "*<>:|?
 * (http://docs.mongodb.org/manual/reference/limits/)
 *
 * This function sanitizes @s as well, though it is not
 * required by Mongo.
 *
 * @param string
 *
 * @return string
 */
function _getMongoDbName(str) {
    if (!/[/\. "*<>:|?@]/.test(str)) {
      return str;
    }

    str = str.replace(/\//g, exports.constants.slash);
    str = str.replace(/\\/g, exports.constants.backslash);
    str = str.replace(/\./g, exports.constants.dot);
    str = str.replace(/ /g, exports.constants.space);
    str = str.replace(/"/g, exports.constants.doubleQuotes);
    str = str.replace(/\*/g, exports.constants.star);
    str = str.replace(/</g, exports.constants.lessThan);
    str = str.replace(/>/g, exports.constants.greaterThan);
    str = str.replace(/:/g, exports.constants.colon);
    str = str.replace(/\|/g, exports.constants.pipe);
    str = str.replace(/\?/g, exports.constants.questionMark);
    str = str.replace(/@/g, exports.constants.at);

    return str;
  };
exports.getMongoDbName = _getMongoDbName;

/**
 * Sanitize a mongo collection name. Find restrictions
 * on collections names here:
 *
 * http://docs.mongodb.org/manual/reference/limits/
 *
 * @param string
 *
 * @return string
 */
exports.getMongoCollectionName = function(str) {
    if (str === undefined ||
        str === null || str.length === 0) {
      return exports.constants.noCollection;
    }

    str = str.replace(/\$/g, exports.constants.dollarSign);

    if (/^system\.|^[^A-Za-z_]/.test(str)) {
      str = '_' + str;
    }

    return str;
  };

/**
 * Sanitize a mongo field name. Find restrictions
 * on collections names here:
 *
 * http://docs.mongodb.org/manual/reference/limits/
 *
 * @param string
 *
 * @return string
 */
exports.getMongoFieldName = function(str) {
    if (str === undefined ||
        str === null || str.length === 0) {
      return exports.constants.noCollection;
    }

    str = str.replace(/\$/g, exports.constants.dollarSign);
    str = str.replace(/\./g, exports.constants.dot);
    return str;
  };

/**
 * Given an flat object, return an URL-friendly query string.  Note
 * that for a given object, the return value may be.
 *
 * @example
 *

 *    // returns 'color=red&size=large'
 *    _objectToQueryString({color: 'red', size: 'large'})
 *

 *
 * @param {Object} A flat object containing keys for a query string.
 *
 * @returns {string} An URL-friendly query string.
 */
exports.objectToQueryString = function(obj) {
    var str = [];
    for (var key in obj) {
      str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
    }
    return str.join('&');
  };

/**
 * Take an email address and turn it into a mongo-friendly
 * database name
 *
 * @param string
 *
 * @return string
 */
exports.ensureDbName = function(email) {
//    if (!email) {
//      email = nconf.get('email');
//    }
    return _getMongoDbName(email);
  };

/**
 * Recursively save a batch of files
 *
 * @param Object - req.files
 * @param Object - verified user object
 * @param array - keys in the req.files object
 * @param index - the current index in the array
 * @param array - file objects
 *
 * @return promise
 */
function _saveFilesToAgentDirectory(files, verified, keys, index, fileObjects) {
   var deferred = q.defer();

    if (!files) {
      deferred.resolve();
      return deferred.promise;
    }

    if (keys === undefined) {
      keys = Object.keys(files);
    }

    if (index === undefined) {
      index = keys.length;
    }

    if (fileObjects === undefined) {
      fileObjects = [];
    }

    /**
     * 2014-4-17
     * Why does this need to be placed here? Previously,
     * I could load the file at the top of the page (see
     * commented above), and everything would work fine
     */
    nconf.file({ file: 'gebo.json' });
    var dir = nconf.get('docs') + '/' + verified.dbName + '/' + verified.collectionName;

    if (index > 0) {
      _saveFilesToAgentDirectory(files, verified, keys, --index, fileObjects).
        then(function() {
                _getSafeFileName(files[keys[index]].name, dir).
                    then(function(filename) {

                        // Make file document for DB
                        var db = new agentSchema(verified.dbName);
                        var file = new db.fileModel({
                                name: filename,
                                collectionName: verified.collectionName,
                                type: files[keys[index]].type,
                                size: files[keys[index]].size,
                            });

                        file.save(function(err, file) {
                            fileObjects.push(file)
                            db.connection.db.close();
                            if (err) {
                             deferred.reject(err);
                            }
                            else {
                              mv(files[keys[index]].path, dir + '/' + filename, { mkdirp: true },
                                  function(err) {
                                      if (err) {
                                        deferred.reject(err);
                                      }
                                      else {
                                        deferred.resolve(fileObjects);
                                      }
                                    });
                            }
                          });
                      }).
                    catch(function(err) {
                        deferred.reject(err);
                      });
          });
    }
    else {
      deferred.resolve(fileObjects);
    }

    return deferred.promise;
  };
exports.saveFilesToAgentDirectory = _saveFilesToAgentDirectory;

/**
 * Append a copy number to a filename if a
 * file by that same name already exists
 *
 * @param string
 * @param string
 *
 * @return promise
 */
function _getSafeFileName(filename, directory) {
    var deferred = q.defer();

    fs.readdir(directory, function(err, files) {

        if(err) {
          // This means the directory doesn't exist
          if (err.errno === 34 && err.code === 'ENOENT') {
            deferred.resolve(filename);
          }
          else {
            deferred.reject(err);
          }
        }
        else {
          var index = files.indexOf(filename);
          if (index > -1) {

            // Get filename and extension (if it has one)
            var name, extension;
            var splitString = filename.split('.');

            // Has extension
            if (splitString.length > 1) {
              extension = splitString.pop();
              name = splitString.join('.');

              // Wait! Is this a hidden file?
              if (!name) {
                name = '.' + extension;
                extension = '';
              }
            }
            // No extension
            else {
              name = filename;
              extension = '';
            }

            // Get the copy number appended to the name and increment, if any
            var matches = name.match(/\((\d+)\)$/);
            if (matches) {
              var copyNumber = Number(matches[1]) + 1;
              name = name.replace(/\((\d+)\)$/, '(' + copyNumber + ')');
            }
            else {
              name += '(1)';
            }

            // Assemble new filename
            if (extension) {
              filename = name + '.' + extension;
            }
            else {
              filename = name;
            }
			deferred.resolve(_getSafeFileName(filename, directory));
          }
		  else {
          	deferred.resolve(filename);
		  }
        }
      });

    return deferred.promise;
  };
exports.getSafeFileName = _getSafeFileName;

/**
 * Get the index of the first object containing the matching
 * key-value pair
 *
 * @param Array of Objects
 * @param string
 * @param string, number
 *
 * @return int
 */
exports.getIndexOfObject = function(array, key, value) {
    for (var i = 0; i < array.length; i++) {
      if (array[i][key] === value) {
        return i;
      }
    }
    return -1;
  };

/**
 * Generate a private key and self-signed certificate
 * in PEM format
 *
 * @return Object
 */
exports.getPrivateKeyAndCertificate = function() {
    var deferred = q.defer();
    pki.rsa.generateKeyPair(512, function(err, keys) {
        if (err) {
          deferred.reject(err);
        }
        else {
          var cert = pki.createCertificate();
          cert.publicKey = keys.publicKey;
          cert.sign(keys.privateKey);
          cert.validity.notAfter = new Date() + 3600*24*365*10;

          deferred.resolve({
                  privateKey: pki.privateKeyToPem(keys.privateKey),
                  certificate: pki.certificateToPem(cert)
              });
        }

      });
    return deferred.promise;

  };

/**
 * Get the domain and HTTPS port set in local.json
 *
 * @return string
 */
exports.getDefaultDomain = function() {
    var host = nconf.get('domain');
    if (nconf.get('httpsPort')) {
      host += ':' + nconf.get('httpsPort');
    }
    return host;
  };
<|endoftext|>


<commit_before>// This is the core script of the SkyX engine. It will be served by the extension's main server.
// This architecture will allow us not only to update extensions, but to update the SkyX core remotely.
// Its purposes:
//
// 1. Load all extensions and run them.
// 2. Check for plugin updates from their servers
// 3. Prodive a GeoFS API for adding UI elements and modifying base GeoFS methods.
// 4. Add a UI to manage extensions (add, remove, update, etc.)
//
// This is the release version of the file.
// (Add if I missed something, Ron)
//

(function() {
	SkyXDebug.log("Core has been loaded");
});

// Adding some lines to trigger WebHook
<commit_msg>Version 0.0.2<commit_after>// This is the core script of the SkyX engine. It will be served by the extension's main server.
// This architecture will allow us not only to update extensions, but to update the SkyX core remotely.
// Its purposes:
//
// 1. Load all extensions and run them.
// 2. Check for plugin updates from their servers
// 3. Prodive a GeoFS API for adding UI elements and modifying base GeoFS methods.
// 4. Add a UI to manage extensions (add, remove, update, etc.)
//
// This is the release version of the file.
// (Add if I missed something, Ron)
//

(function() {
	var CORE_VERSION = "0.1.0";
	SkyXDebug.log("Core has been loaded");
});

// Adding some lines to trigger WebHook
<|endoftext|>