<commit_before>const JDLParser = require('./parser').JDLParser;
const _ = require('lodash');

function buildAst(cst) {
  // eslint-disable-next-line no-use-before-define
  const astBuilderVisitor = new JDLAstBuilderVisitor();
  return astBuilderVisitor.visit(cst);
}

const BaseJDLCSTVisitor = new JDLParser().getBaseCstVisitorConstructor();

/**
 * Note that the logic here assumes the input CST is valid.
 * To make this work with partially formed CST created during automatic error recovery
 * would require refactoring for greater robustness.
 * Meaning we can never assume at least one element exists in a ctx child array
 * e.g:
 * 1. ctx.NAME[0].image --> ctx.NAME[0] ? ctx.NAME[0].image : "???"
 */
class JDLAstBuilderVisitor extends BaseJDLCSTVisitor {
  // constructor() {
  //   super();
  //   // TODO: commented out because the validation would fail as we are missing methods
  //   // due to new grammar rules.
  //   // this.validateVisitor();
  // }

  prog(ctx) {
    return {
      entities: _.map(ctx.entityDeclaration, element => this.visit(element)),
      constants: _.map(ctx.constantDeclaration, element => this.visit(element))
    };
  }

  constantDeclaration(ctx) {
    return {
      name: ctx.NAME[0].image,
      value: parseInt(ctx.INTEGER[0].image, 10)
    };
  }

  entityDeclaration(ctx) {
    return {
      name: ctx.NAME[0].image,
      // ctx.entityTableNameDeclaration is optional which means
      // either an empty array or an array of a single element
      // the "this.visit" API will handle this transparently and return
      // undefined in the case of empty array.
      tableName: this.visit(ctx.entityTableNameDeclaration),
      fields: this.visit(ctx.entityBody)
    };
  }

  entityTableNameDeclaration(ctx) {
    return ctx.NAME[0].image;
  }

  entityBody(ctx) {
    return _.map(ctx.fieldDeclaration, element => this.visit(element));
  }

  fieldDeclaration(ctx) {
    return {
      name: ctx.NAME[0].image,
      // ctx.type is an array with a single item.
      // in that case:
      // this.visit(ctx.type) is equivalent to this.visit(ctx.type[0])
      type: this.visit(ctx.type),
      validations: _.map(ctx.validation, element => this.visit(element))
    };
  }

  type(ctx) {
    return ctx.NAME[0].image;
  }

  validation(ctx) {
    // only one of these alternatives can exist at the same time.
    if (!_.isEmpty(ctx.REQUIRED)) {
      return {
        validationType: 'required'
      };
    } else if (!_.isEmpty(ctx.minMaxValidation)) {
      return this.visit(ctx.minMaxValidation);
    }
    return this.visit(ctx.pattern);
  }

  minMaxValidation(ctx) {
    return {
      validationType: ctx.MIN_MAX_KEYWORD[0].image,
      limit: _.isEmpty(ctx.NAME) ?
        parseInt(ctx.INTEGER[0].image, 10) :
        ctx.NAME[0].image
    };
  }

  pattern(ctx) {
    return {
      validationType: 'pattern',
      pattern: ctx.REGEX[0].image
    };
  }
}

module.exports = {
  buildAst
};
<commit_msg>Added missing ast visitor methods. - empty implementation.<commit_after>const JDLParser = require('./parser').JDLParser;
const _ = require('lodash');

function buildAst(cst) {
  // eslint-disable-next-line no-use-before-define
  const astBuilderVisitor = new JDLAstBuilderVisitor();
  return astBuilderVisitor.visit(cst);
}

const BaseJDLCSTVisitor = new JDLParser().getBaseCstVisitorConstructor();

/**
 * Note that the logic here assumes the input CST is valid.
 * To make this work with partially formed CST created during automatic error recovery
 * would require refactoring for greater robustness.
 * Meaning we can never assume at least one element exists in a ctx child array
 * e.g:
 * 1. ctx.NAME[0].image --> ctx.NAME[0] ? ctx.NAME[0].image : "???"
 */
class JDLAstBuilderVisitor extends BaseJDLCSTVisitor {
  constructor() {
    super();
    this.validateVisitor();
  }

  prog(ctx) {
    return {
      entities: _.map(ctx.entityDeclaration, element => this.visit(element)),
      constants: _.map(ctx.constantDeclaration, element => this.visit(element))
    };
  }

  constantDeclaration(ctx) {
    return {
      name: ctx.NAME[0].image,
      value: parseInt(ctx.INTEGER[0].image, 10)
    };
  }

  entityDeclaration(ctx) {
    return {
      name: ctx.NAME[0].image,
      // ctx.entityTableNameDeclaration is optional which means
      // either an empty array or an array of a single element
      // the "this.visit" API will handle this transparently and return
      // undefined in the case of empty array.
      tableName: this.visit(ctx.entityTableNameDeclaration),
      fields: this.visit(ctx.entityBody)
    };
  }

  entityTableNameDeclaration(ctx) {
    return ctx.NAME[0].image;
  }

  entityBody(ctx) {
    return _.map(ctx.fieldDeclaration, element => this.visit(element));
  }

  fieldDeclaration(ctx) {
    return {
      name: ctx.NAME[0].image,
      // ctx.type is an array with a single item.
      // in that case:
      // this.visit(ctx.type) is equivalent to this.visit(ctx.type[0])
      type: this.visit(ctx.type),
      validations: _.map(ctx.validation, element => this.visit(element))
    };
  }

  type(ctx) {
    return ctx.NAME[0].image;
  }

  validation(ctx) {
    // only one of these alternatives can exist at the same time.
    if (!_.isEmpty(ctx.REQUIRED)) {
      return {
        validationType: 'required'
      };
    } else if (!_.isEmpty(ctx.minMaxValidation)) {
      return this.visit(ctx.minMaxValidation);
    }
    return this.visit(ctx.pattern);
  }

  minMaxValidation(ctx) {
    return {
      validationType: ctx.MIN_MAX_KEYWORD[0].image,
      limit: _.isEmpty(ctx.NAME) ?
        parseInt(ctx.INTEGER[0].image, 10) :
        ctx.NAME[0].image
    };
  }

  pattern(ctx) {
    return {
      validationType: 'pattern',
      pattern: ctx.REGEX[0].image
    };
  }

  relationDecl(ctx) {
    // TODO: implement
  }

  relationshipType(ctx) {
    // TODO: implement
  }

  relationshipBody(ctx) {
    // TODO: implement
  }

  relationshipSide(ctx) {
    // TODO: implement
  }

  enumDecl(ctx) {
    // TODO: implement
  }

  enumPropList(ctx) {
    // TODO: implement
  }

  dtoDecl(ctx) {
    // TODO: implement
  }

  entityList(ctx) {
    // TODO: implement
  }

  method(ctx) {
    // TODO: implement
  }

  exclusion(ctx) {
    // TODO: implement
  }

  paginationDecl(ctx) {
    // TODO: implement
  }

  serviceDecl(ctx) {
    // TODO: implement
  }

  microserviceDecl1(ctx) {
    // TODO: implement
  }

  searchEngineDecl(ctx) {
    // TODO: implement
  }

  noClientDecl(ctx) {
    // TODO: implement
  }

  noServerDecl(ctx) {
    // TODO: implement
  }

  noFluentMethod(ctx) {
    // TODO: implement
  }

  filterDecl(ctx) {
    // TODO: implement
  }

  negativeForDecl(ctx) {
    // TODO: implement
  }

  angularSuffixDecl(ctx) {
    // TODO: implement
  }

  comment(ctx) {
    // TODO: implement
  }
}

module.exports = {
  buildAst
};
<|endoftext|>


<commit_before>var fs = require('fs'),
    path = require('path'),
    flatiron = require('../../flatiron'),
    common = flatiron.common,
    async = common.async,
    directories = common.directories,
    cpr = common.cpr,
    app = flatiron.app;

module.exports = function create(name, callback) {
  name = name || 'flatiron-app';

  var root = path.join(process.cwd(), name),
      type = app.argv.cli ? 'cli' : 'http',
      scaffold = path.join(__dirname, '..', '..', '..', 'scaffolds', type);

  //
  // Creates directories specified in `/scaffolds/:type/directories.json`.
  //
  function createDirs(next) {
    var dirs = directories.normalize(root,
      JSON.parse(fs.readFileSync(path.join(scaffold, 'directories.json'), 'utf8'))
    );

    Object.keys(dirs).forEach(function (name) {
      app.log.info('Creating directory ' + name.grey);
    });

    directories.create(dirs, next);
  }

  //
  // Creates a templated package.json from `/scaffolds/:type/package.json`.
  //
  function createPackage(next) {
    var pkg = JSON.parse(fs.readFileSync(path.join(scaffold, 'package.json'), 'utf8'));

    pkg.name = name;
    pkg.dependencies.flatiron = flatiron.version;

    app.log.info('Writing ' + 'package.json'.grey);
    fs.writeFile(path.join(root, 'package.json'), JSON.stringify(pkg, null, 2) + '\n', next);
  }

  //
  // Writes the `app.js`
  //
  function writeApp(next) {
    app.log.info('Writing ' + 'app.js'.grey);
    cpr(path.join(scaffold, 'app.js'), path.join(root, 'app.js'), next);
  }

  //
  // Writes the top-level include for the app
  //
  function writeMain(next) {
    app.log.info('Writing ' + ('lib/index.js').grey);
    fs.writeFile(path.join(root, 'lib', 'index.js'), '', next);
  }

  app.log.info('Creating application ' + name.magenta)
  app.log.info('Using ' + type.yellow + ' scaffold.');
  async.series([
    createDirs,
    createPackage,
    writeApp,
    writeMain
  ], function onComplete(next) {
      app.log.info('Application ' + name.magenta + ' is now ready');
      callback();
    }
  );
}

module.exports.usage = [
  'Generates a flatiron skeleton application. By default',
  'an HTTP application will be created.',
  '',
  'create <app-name>',
  '',
  'Command options:',
  '  --cli   Creates a CLI application'
];
<commit_msg>[api] Change `flatiron create` syntax to `flatiron create <app-name> <type>`<commit_after>var fs = require('fs'),
    path = require('path'),
    flatiron = require('../../flatiron'),
    common = flatiron.common,
    async = common.async,
    directories = common.directories,
    cpr = common.cpr,
    app = flatiron.app;

module.exports = function create(name, type, callback) {
  name = name || 'flatiron-app';
  type = type || 'http';

  var root = path.join(process.cwd(), name),
      scaffold = path.join(__dirname, '..', '..', '..', 'scaffolds', type);

  //
  // Creates directories specified in `/scaffolds/:type/directories.json`.
  //
  function createDirs(next) {
    var dirs = directories.normalize(root,
      JSON.parse(fs.readFileSync(path.join(scaffold, 'directories.json'), 'utf8'))
    );

    Object.keys(dirs).forEach(function (name) {
      app.log.info('Creating directory ' + name.grey);
    });

    directories.create(dirs, next);
  }

  //
  // Creates a templated package.json from `/scaffolds/:type/package.json`.
  //
  function createPackage(next) {
    var pkg = JSON.parse(fs.readFileSync(path.join(scaffold, 'package.json'), 'utf8'));

    pkg.name = name;
    pkg.dependencies.flatiron = flatiron.version;

    app.log.info('Writing ' + 'package.json'.grey);
    fs.writeFile(path.join(root, 'package.json'), JSON.stringify(pkg, null, 2) + '\n', next);
  }

  //
  // Writes the `app.js`
  //
  function writeApp(next) {
    app.log.info('Writing ' + 'app.js'.grey);
    cpr(path.join(scaffold, 'app.js'), path.join(root, 'app.js'), next);
  }

  //
  // Writes the top-level include for the app
  //
  function writeMain(next) {
    app.log.info('Writing ' + ('lib/index.js').grey);
    fs.writeFile(path.join(root, 'lib', 'index.js'), '', next);
  }

  app.log.info('Creating application ' + name.magenta)
  app.log.info('Using ' + type.yellow + ' scaffold.');
  async.series([
    createDirs,
    createPackage,
    writeApp,
    writeMain
  ], function onComplete(next) {
      app.log.info('Application ' + name.magenta + ' is now ready');
      callback();
    }
  );
}

module.exports.usage = [
  'Generates a flatiron skeleton application. If no <type>',
  'is specified an HTTP application will be created.',
  '',
  'create <app-name> <type>',
];
<|endoftext|>


<commit_before>/**
 * handlebars-xgettext
 * Extract translatable strings from Handlebars templates.
 *
 * \@todo Parse output PO file to look for duplicates with extracted strings.
 * \@todo Update API to be able to call from node files.
 * \@todo Check multilines strings.
 * \@todo Add source file, line number and comments.
 * \@todo Parse ngettext and other gettext functions (dgettext, npgettext...).
 * \@todo Use a parser/compiler (See https://github.com/duaneg/ajs-xgettext).
 * \@todo Handle mustache tpl as well? e.g. `{{#i}}Translatable string{{/i}}`
 */
var fs = require('fs'),
  readdirp = require('readdirp'),
  _ = require('underscore'),
  Parser = require('./parser'),
  Gettext = require('node-gettext');

/**
 * Parse the templates from a directory and save the i18n strings to a PO file.
 *
 * @param {Object} options A configuration object containing cli options.
 * @param {Function} callback An optional callback method.
 */
function parse(options, callback) {
  if (options === undefined ||
    (typeof options === 'string' && arguments.length < 2)) {
    throw new Error('Missing parameters');
  }

  // handle legacy arguments (dir, dstFile)
  if (typeof options === 'string') {
    // convert arguments to argv style options
    options = {
      _: [options, callback]
    };
  }

  options = _.extend({
    'from-code': 'utf8',
    'join-existing': false
  }, options);

  var gettext = new Gettext(),
    dir = options._[0],
    dstFile = options._[1],
    keys = Object.create(null),
    parser = new Parser(options.keyword);

  dir = dir.replace(/\/+$/, ''); // Remove trailing slash.

  readdirp({root: dir}, function(err, res) {
    if (err) {
      throw err;
    }

    var files = res.files.map(function (file) {
        return file.fullPath;
      }),
      newKeys,
      data;

    // We need to sort out files to keep the extracted strings order.
    files.sort();

    files.forEach(function (file) {
      data = fs.readFileSync(file, options['from-code']);

      newKeys = parser.parse(data);

      for (var i in newKeys) {
        keys[i] = newKeys[i];
      }
    });

    gettext.addTextdomain(null);

    for (var key in keys) {
      gettext.setTranslation(null, null, keys[key]);
    }

    fs.writeFile(dstFile, gettext.compilePO(), callback);
  });
}

module.exports = parse;
<commit_msg>moved todo's to github issues<commit_after>/**
 * handlebars-xgettext
 * Extract translatable strings from Handlebars templates.
 */

var fs = require('fs'),
  readdirp = require('readdirp'),
  _ = require('underscore'),
  Parser = require('./parser'),
  Gettext = require('node-gettext');

/**
 * Parse the templates from a directory and save the i18n strings to a PO file.
 *
 * @param {Object} options A configuration object containing cli options.
 * @param {Function} callback An optional callback method.
 */
function parse(options, callback) {
  if (options === undefined ||
    (typeof options === 'string' && arguments.length < 2)) {
    throw new Error('Missing parameters');
  }

  // handle legacy arguments (dir, dstFile)
  if (typeof options === 'string') {
    // convert arguments to argv style options
    options = {
      _: [options, callback]
    };
  }

  options = _.extend({
    'from-code': 'utf8',
    'join-existing': false
  }, options);

  var gettext = new Gettext(),
    dir = options._[0],
    dstFile = options._[1],
    keys = Object.create(null),
    parser = new Parser(options.keyword);

  dir = dir.replace(/\/+$/, ''); // Remove trailing slash.

  readdirp({root: dir}, function(err, res) {
    if (err) {
      throw err;
    }

    var files = res.files.map(function (file) {
        return file.fullPath;
      }),
      newKeys,
      data;

    // We need to sort out files to keep the extracted strings order.
    files.sort();

    files.forEach(function (file) {
      data = fs.readFileSync(file, options['from-code']);

      newKeys = parser.parse(data);

      for (var i in newKeys) {
        keys[i] = newKeys[i];
      }
    });

    gettext.addTextdomain(null);

    for (var key in keys) {
      gettext.setTranslation(null, null, keys[key]);
    }

    fs.writeFile(dstFile, gettext.compilePO(), callback);
  });
}

module.exports = parse;
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';
import { Email } from 'meteor/email';

const { siteName, accountsEmail, eventYear, siteURL } = Meteor.settings.public;

const USER_FIELDS = ["createdAt", "firstname", "lastname", "email", "accountType", "paid", "ticketUsed", "lookingForTeam", "age", "city", "state", "address", "zip", "phone", "ecName", "ecRelationship", "ecPhone", "ecEmail", "photoPermission", "holdHarmless"];
const TX_FIELDS = ["createdAt", "tx", "name", "email", "studentTickets", "nonStudentTickets"]
const GEAR_FIELDS = ["createdAt", "tx", "email", "itemcode", "color", "logoColor", "size", "qty", "amount"];

function buildReport(name, collection, fields) {
  let csv = fields.join(",") + "\n";
  collection.find().forEach((item) => {
    csv += fields.map((field) => `"${item[field] || ''}"`).join(",") + "\n"
  });
  return {
    filename: `${name}.csv`,
    content: csv,
    encoding: 'utf-8',
  };
}

function buildUsersAndTeams() {
  const users = Meteor.users.find({}).fetch();
  const teams = Teams.find({}).fetch().reduce((acc, team) => {
    acc[team._id] = team;
    return acc;
  }, {});

  const usersWithTeams = users.map((user) => {
    if (user.teamId) {
      user.teamName = teams[user.teamId].name;
      user.teamDivision = teams[user.teamId].division;
    }
    return user;
  });

  const fields = ["createdAt", "firstname", "lastname", "email", "accountType", "paid", "ticketUsed", "lookingForTeam", "age", "city", "state", "address", "zip", "phone", "ecName", "ecRelationship", "ecPhone", "ecEmail", "photoPermission", "holdHarmless", 'teamName', 'teamDivision'];
  let csv = fields.join(",") + "\n";
  usersWithTeams.forEach((user) => {
    csv += fields.map((field) => `"${user[field] || ''}"`).join(",") + "\n";
  });

  return {
    filename: `users_and_teams.csv`,
    content: csv,
    encoding: 'utf-8',
  };
}

export function sendReports(to = null) {
  if (Meteor.isClient) return;
  const now = new Date();
  const sendTo = to || Gamestate.findOne().sendReportsTo;
  const subject = `GPH ${eventYear} Admin Reports ${now}`;
  const html = `

Admin,

Attached are the admin reports for users, transactions, and gear orders.

Cheers,
${siteName}
`;

  Meteor.logger.info(`Sending reports to: ${sendTo}`);

  return Email.send({
    from: accountsEmail,
    to: sendTo,
    subject,
    html,
    attachments: [
      buildReport("users", Meteor.users, USER_FIELDS),
      buildReport("transactions", Transactions, TX_FIELDS),
      buildReport("gear_orders", GearOrders, GEAR_FIELDS),
    ]
  });
};

export function sendUsersAndTeams(to = null) {
  if (Meteor.isClient) return;
  const now = new Date();
  const sendTo = to || Gamestate.findOne().sendReportsTo;
  const subject = `GPH ${eventYear} Admin Report: Users and Teams ${now}`;
  const html = `

Admin,

Attached are the users and teams!

Cheers,
${siteName}
`;

  Meteor.logger.info(`Sending reports to: ${sendTo}`);

  return Email.send({
    from: accountsEmail,
    to: sendTo,
    subject,
    html,
    attachments: [
      buildUsersAndTeams(),
    ]
  });
};
<commit_msg>Simplify user and teams report<commit_after>import { Meteor } from 'meteor/meteor';
import { Email } from 'meteor/email';
import { sortBy } from 'lodash';

const { siteName, accountsEmail, eventYear, siteURL } = Meteor.settings.public;

const USER_FIELDS = ["createdAt", "firstname", "lastname", "email", "accountType", "paid", "ticketUsed", "lookingForTeam", "age", "city", "state", "address", "zip", "phone", "ecName", "ecRelationship", "ecPhone", "ecEmail", "photoPermission", "holdHarmless"];
const TX_FIELDS = ["createdAt", "tx", "name", "email", "studentTickets", "nonStudentTickets"]
const GEAR_FIELDS = ["createdAt", "tx", "email", "itemcode", "color", "logoColor", "size", "qty", "amount"];

function buildReport(name, collection, fields) {
  let csv = fields.join(",") + "\n";
  collection.find().forEach((item) => {
    csv += fields.map((field) => `"${item[field] || ''}"`).join(",") + "\n"
  });
  return {
    filename: `${name}.csv`,
    content: csv,
    encoding: 'utf-8',
  };
}

function buildUsersAndTeams() {
  const users = Meteor.users.find({}).fetch();
  const teams = Teams.find({}).fetch().reduce((acc, team) => {
    acc[team._id] = team;
    return acc;
  }, {});

  const usersWithTeams = sortBy(users.map((user) => {
      if (user.teamId) {
        const team = teams[user.teamId];
        user.teamDivision = team.division;
        user.teamName = team.name;
        user.teamIsPlaying = team.hasBegun;
        user.teamSize = team.members.length

        user.checkedIn = Boolean(user.checkedIn);
      }
      return user;
    }), ['division', 'teamName', 'firstname', 'lastname']);

  const fields = ["teamDivision", "teamName", "teamIsPlaying", "checkedIn", "firstname", "lastname", "phone"];
  let csv = fields.join(",") + "\n";
  usersWithTeams.forEach((user) => {
    csv += fields.map((field) => `"${user[field] || ''}"`).join(",") + "\n";
  });

  return {
    filename: `users_and_teams.csv`,
    content: csv,
    encoding: 'utf-8',
  };
}

export function sendReports(to = null) {
  if (Meteor.isClient) return;
  const now = new Date();
  const sendTo = to || Gamestate.findOne().sendReportsTo;
  const subject = `GPH ${eventYear} Admin Reports ${now}`;
  const html = `

Admin,

Attached are the admin reports for users, transactions, and gear orders.

Cheers,
${siteName}
`;

  Meteor.logger.info(`Sending reports to: ${sendTo}`);

  return Email.send({
    from: accountsEmail,
    to: sendTo,
    subject,
    html,
    attachments: [
      buildReport("users", Meteor.users, USER_FIELDS),
      buildReport("transactions", Transactions, TX_FIELDS),
      buildReport("gear_orders", GearOrders, GEAR_FIELDS),
    ]
  });
};

export function sendUsersAndTeams(to = null) {
  if (Meteor.isClient) return;
  const now = new Date();
  const sendTo = to || Gamestate.findOne().sendReportsTo;
  const subject = `GPH ${eventYear} Admin Report: Users and Teams ${now}`;
  const html = `

Admin,

Attached are the users and teams!

Cheers,
${siteName}
`;

  Meteor.logger.info(`Sending reports to: ${sendTo}`);

  return Email.send({
    from: accountsEmail,
    to: sendTo,
    subject,
    html,
    attachments: [
      buildUsersAndTeams(),
    ]
  });
};
<|endoftext|>


<commit_before>/* (c) Copyright 2002-2016 by authors of the Tiki Wiki CMS Groupware Project
 *
 * All Rights Reserved. See copyright.txt for details and a complete list of authors.
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
 *
 * $Id$
 *
 * Rewritten for bootstrap tiki 15.x 2015-2016
 * Based on work by Jobi Carter keacarterdev@gmail.com
 */

$.buildAutoToc = function () {
	var $page = $("body"),
		$top = $("#top");

	//if a wiki page, and if there is no toc-off div and not printing
	if ($top.length && !$("#toc-off").length && location.href.indexOf("tiki-print.php") == -1) {

		var $headers = $("h1.pagetitle", "#col1").add($(":header", $top)).not("#toctitle > h3");

		//if there are more than one $headers on the page
		if ($headers.length > 1) {

			var $tocDiv = $("
");

			//create object to store processed IDs.
			var processedId = {};

			//function to process header Id generation. If an ID which has been processed is generated again and passed in again, the id name will be incremented to id_[1*]
			function processId(id) {
				if (id in processedId) {
					//if processed before
					//iterate count for header with this ane
					processedId[id] += 1;
					//set the new id to id plus count for header
					var newId = id + "_" + processedId[id];
				} else {
					//if not processed before
					//add to "dictionary' with count of 0
					processedId[id] = 0;
					//return id passed in
					newId = id;
				}
				return newId;
			}

			// open HTML $list
			var $list = $("
"),
					$currentList = $list,		// pointer to where to add items
					headerLevel,				// how deep we are
					previousHeaderLevel = 0; 	//start from indentation level 0, with header one as base

			//Iterate over the $headers
			$headers.each(function () {

				//get header level for header to see if this header should be processed or not
				var $this = $(this);
				headerLevel = parseInt($this.prop("tagName").substring(1));

				//grab the whole header element to show as the anchor text
				var aText = $.trim($this.text());

				//generate and set id if necessary (if element does not already have an id, create one)
				var id = $this.attr("id");
				if (!id){
					// Set the id to the the inner text of the header, with underscores instead of spaces (" ").
					// processId checks if the ID has been assigned yet, and if so, increments the Id with a number at the end of the id name
					id = processId(aText.replace(/\W/g, "_"));

				} else {
					id = id.replace(":", "\\:").replace(".", "\\.").replace("#", "\\#");
				}
				//set the element's id to the constructed ID
				$this.attr("id", id);
				//construct the anchor URL with chars jquery doesn't like escaped
				var url = "#" + id;

				//create the HTML anchor item with the text from the header and pointing to baseurl#divId
				var $item = $("
" + aText + "
");

				if (previousHeaderLevel && headerLevel > previousHeaderLevel) {	// deeper level

					//open a new sublist for each level of difference
					var $lastItem = $("li:last", $currentList).append($("
"));
					$currentList = $("ul:last", $lastItem);

				} else if (headerLevel < previousHeaderLevel) {					// up some levels

					$currentList = $($currentList.parents("ul")[previousHeaderLevel - headerLevel - 1]);

				}

				$currentList.append($item);

				//set current header level to previous header level for next iteration
				previousHeaderLevel = headerLevel;
			});

			// append the $list
			if (!jqueryTiki.autoToc_inline) {
				$("#page-data").addClass("col-sm-9");
				if (jqueryTiki.autoToc_pos === "left" || jqueryTiki.autoToc_pos === "top") {
					$tocDiv.append($list).css('clear','left').insertBefore('#page-data');
				} else {
					$tocDiv.append($list).css('clear','right').insertAfter('#page-data');
				}

				//Fix fixed position and width and left offset when resize window
				$(window).resize(function() {
					leftoffset = $('#autotoc').offset().left;
					$("> .nav", "#autotoc").css('left',leftoffset);
					$('#autotoc .nav').width($('#autotoc').width());
					affix();
				}).resize();

				// trigger the bootstrap affix and scrollspy
				$page.scrollspy({ target: "#autotoc" , offset: jqueryTiki.autoToc_offset});

				function affix() {
					$("> .nav", "#autotoc").affix({
						offset: {
							top: function () {
								return (this.top = $('#page-data').offset().top - jqueryTiki.autoToc_offset)
							},
							bottom: function () {
								return (this.bottom = $(document).height() - $('#page-data').offset().top - $('#page-data').height() + 10)
							}
						}
					})
					.on('affix.bs.affix', function (e) {
						$(e.target).width($('#autotoc').width());
					});
				}
				//$("> .nav", "#autotoc").affix('checkPosition');//Prevent bottom overlap if page loaded in max-bottom of page (not working)
			} else {
				$tocDiv.prepend($list).prependTo($top);
			}
		}
	}
};

$(document).ready(function () {
	$.buildAutoToc();
});<commit_msg>[FIX] change behaviour of autotoc when there is a sticky header<commit_after>/* (c) Copyright 2002-2016 by authors of the Tiki Wiki CMS Groupware Project
 *
 * All Rights Reserved. See copyright.txt for details and a complete list of authors.
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
 *
 * $Id$
 *
 * Rewritten for bootstrap tiki 15.x 2015-2016
 * Based on work by Jobi Carter keacarterdev@gmail.com
 */

$.buildAutoToc = function () {
	var $page = $("body"),
		$top = $("#top");

	//if a wiki page, and if there is no toc-off div and not printing
	if ($top.length && !$("#toc-off").length && location.href.indexOf("tiki-print.php") == -1) {

		var $headers = $("h1.pagetitle", "#col1").add($(":header", $top)).not("#toctitle > h3");

		//if there are more than one $headers on the page
		if ($headers.length > 1) {

			var $tocDiv = $("
");

			//create object to store processed IDs.
			var processedId = {};

			//function to process header Id generation. If an ID which has been processed is generated again and passed in again, the id name will be incremented to id_[1*]
			function processId(id) {
				if (id in processedId) {
					//if processed before
					//iterate count for header with this ane
					processedId[id] += 1;
					//set the new id to id plus count for header
					var newId = id + "_" + processedId[id];
				} else {
					//if not processed before
					//add to "dictionary' with count of 0
					processedId[id] = 0;
					//return id passed in
					newId = id;
				}
				return newId;
			}

			// open HTML $list
			var $list = $("
"),
					$currentList = $list,		// pointer to where to add items
					headerLevel,				// how deep we are
					previousHeaderLevel = 0; 	//start from indentation level 0, with header one as base

			//Iterate over the $headers
			$headers.each(function () {

				//get header level for header to see if this header should be processed or not
				var $this = $(this);
				headerLevel = parseInt($this.prop("tagName").substring(1));

				//grab the whole header element to show as the anchor text
				var aText = $.trim($this.text());

				//generate and set id if necessary (if element does not already have an id, create one)
				var id = $this.attr("id");
				if (!id){
					// Set the id to the the inner text of the header, with underscores instead of spaces (" ").
					// processId checks if the ID has been assigned yet, and if so, increments the Id with a number at the end of the id name
					id = processId(aText.replace(/\W/g, "_"));

				} else {
					id = id.replace(":", "\\:").replace(".", "\\.").replace("#", "\\#");
				}
				//set the element's id to the constructed ID
				$this.attr("id", id);
				//construct the anchor URL with chars jquery doesn't like escaped
				var url = "#" + id;

				//create the HTML anchor item with the text from the header and pointing to baseurl#divId
				var $item = $("
" + aText + "
");

				if (previousHeaderLevel && headerLevel > previousHeaderLevel) {	// deeper level

					//open a new sublist for each level of difference
					var $lastItem = $("li:last", $currentList).append($("
"));
					$currentList = $("ul:last", $lastItem);

				} else if (headerLevel < previousHeaderLevel) {					// up some levels

					$currentList = $($currentList.parents("ul")[previousHeaderLevel - headerLevel - 1]);

				}

				$currentList.append($item);

				//set current header level to previous header level for next iteration
				previousHeaderLevel = headerLevel;
			});

			// append the $list
			if (!jqueryTiki.autoToc_inline) {
				$("#page-data").addClass("col-sm-9");
				if (jqueryTiki.autoToc_pos === "left" || jqueryTiki.autoToc_pos === "top") {
					$tocDiv.append($list).css('clear','left').insertBefore('#page-data');
				} else {
					$tocDiv.append($list).css('clear','right').insertAfter('#page-data');
				}

				//change behaviour if header is fixed
				var fixed_height = 0;
				if ($('#header_outer, #navbar-fixed-top').css('position') === 'fixed') {
					fixed_height = $('#header_outer, #navbar-fixed-top').height() + 10;
					$('').appendTo('head');
					$('#autotoc li a').click(function(e) {
						var href = $(this).attr('href');
						anchorOffset = $(href).offset().top - fixed_height;
						$('html, body').animate({scrollTop: anchorOffset});
					});
				}

				//Fix fixed position and width and left offset when resize window
				$(window).resize(function() {
					leftoffset = $('#autotoc').offset().left;
					$("> .nav", "#autotoc").css('left',leftoffset);
					$('#autotoc .nav').width($('#autotoc').width());
					affix();
				}).resize();

				// trigger the bootstrap affix and scrollspy
				$page.scrollspy({ target: "#autotoc" , offset: (jqueryTiki.autoToc_offset + fixed_height)});

				function affix() {
					$("> .nav", "#autotoc").affix({
						offset: {
							top: function () {
								return (this.top = $('#page-data').offset().top - (jqueryTiki.autoToc_offset + fixed_height))
							},
							bottom: function () {
								return (this.bottom = $(document).height() - $('#page-data').offset().top - $('#page-data').height() + 10)
							}
						}
					})
					.on('affix.bs.affix', function (e) {
						$(e.target).width($('#autotoc').width());
					});
				}
				//$("> .nav", "#autotoc").affix('checkPosition');//Prevent bottom overlap if page loaded in max-bottom of page (not working)
			} else {
				$tocDiv.prepend($list).prependTo($top);
			}
		}
	}
};

$(document).ready(function () {
	$.buildAutoToc();
});<|endoftext|>


<commit_before>/**
 * @author Sam Thompson
 *
 * Provides basic functionality of a panel
 */
$( function()
{
	"use strict";
	//noinspection JSUnresolvedVariable
	$.widget( 'jqueryDash.panel', $.jqueryDash.localStorageObject, {

		_mode: 'init',
		_modeEdit: 'edit',
		_modeView: 'view',

		options: {
			id: 0,
			mode: 'view',
			minWidth: 200,
			minHeight: 200,
			position: {
				x: 8,
				y: 9
			},
			_editorFields: {},
			_editorFieldCount: 0,
			ordering: null,
			refreshable: false,
			refreshInterval: 300
		},

		/**
		 * Create a new panel widget
		 *
		 * @private
		 */
		_create: function()
		{
			var
				initialPos,
				initialWidth,
				initialHeight;

			this.element.data( 'panelType', this.widgetName );

			// Load the saved panel position
			initialPos = ( this.isSetting( 'posX' ) && this.isSetting( 'posY' ) ) ?
						 { 'x': this.setting( 'posX' ), 'y': this.setting( 'posY' ) } :
						 { 'x': this.options.position.x, 'y': this.options.position.y };

			// Load the saved panel size
			initialWidth = ( this.isSetting( 'width' ) ) ? this.setting( 'width' ) : this.options.minWidth;
			initialHeight = ( this.isSetting( 'height' ) ) ? this.setting( 'height' ) : this.options.minHeight;

			this.element
				.width( initialWidth )
				.height( initialHeight )
				.css( {
					'position': 'absolute',
					'top': initialPos.y,
					'left': initialPos.x
				} )
				.addClass( this.widgetName );

			this._createEditorFields();

			this._readSettings();

			// Set the default mode
			this.mode( this.options.mode );

			// Set the ordering if one is specified
			if( this.options.ordering !== null )
			{
				this.setOrdering( this.options.ordering );
			}
			else
			{
				// Load the ordering setting
				if( this.isSetting( 'ordering' ) )
				{
					this.element.zIndex( this.setting( 'ordering' ) );
				}
			}
		},

		/**
		 * Name of the storage object for this widget
		 *
		 * @returns {number}
		 * @private
		 */
		_getStorageNamespace: function()
		{
			return this.options.id;
		},

		/**
		 * Gets or sets the mode of this panel
		 *
		 * @param {string} [mode] Acts as setter if provided
		 * @returns {string} Current mode
		 */
		mode: function( mode )
		{
			// Set the mode if provided
			if( mode !== undefined && mode !== this._mode )
			{

				if( mode === this._modeEdit )
				{
					// destroy view mode if required
					if( this._mode === this._modeView )
					{
						this._destroyViewPanel();
					}
					this._drawEditPanel();
					this._mode = this._modeEdit;
				}
				else
				{
					// destroy edit mode if required
					if( this._mode === this._modeEdit )
					{
						this._destroyEditPanel();
					}
					// open view
					this._drawViewPanel();
					this._mode = this._modeView;
				}
			}

			// Return the mode - allows this method to be used as a getter if no mode is passed
			return this._mode;
		},

		/**
		 * Completely removes this panel and all storage records associated with it
		 *
		 * @private
		 */
		_destroy: function()
		{

			// destroy anything specific to whatever mode we are in
			switch( this._mode )
			{
				case this._modeEdit:
					this._destroyEditPanel();
					break;
				case this._modeView:
					this._destroyViewPanel();
					break;
			}

			// Destroy this widget
			this._super();

			// Remove settings for this object from the controller's local storage and remove the element from the DOM
			this.element.parent().trigger( 'deletePanel', this.options.id ).end().remove();
		},

		/**
		 * Creates the editor interface for this panel
		 *
		 * @private
		 */
		_drawEditPanel: function()
		{
			var self = this;
			this.element
				.addClass( 'edit' )
				.html( $( '<div>', { 'class': 'content', 'text': 'content' } ) )
				// Set the panel to be movable
				.draggable( {
					containment: 'parent',
					grid: [50, 50],
					'handle': '.move-panel',
					start: function()
					{
						self._bringToFront();
					},
					stop: function()
					{
						var position = $( this ).position();
						self.setting( 'posX', position.left );
						self.setting( 'posY', position.top );
					}
				} )
				// Set the panel to be resizable
				.resizable( {
					minWidth: this.options.minWidth,
					minHeight: this.options.minHeight,
					handles: 'n,s,e,w,se,sw,nw',
					grid: 50,
					containment: 'parent',
					start: function()
					{
						self._bringToFront();
					},
					stop: function()
					{
						self.setting( 'width', $( this ).width() );
						self.setting( 'height', $( this ).height() );
					}
				} )
				.append(
					$( '<a>', {'class': 'button delete-panel', 'text': 'x', 'title': 'Delete Panel'} ).click( function()
					{
						if( confirm( 'Are you sure you want to delete this panel?' ) )
						{
							self._destroy();
						}
					} ),
					$( '<a>', {'class': 'move-panel', 'html': '≡', 'title': 'Move Panel'} )
				)
				.click( function()
				{
					self._bringToFront();
				} );

			if( this.options._editorFieldCount > 0 )
			{
				this._drawEditorFields();
			}

			$( 'body' ).disableSelection();
		},

		/**
		 * Destroys the editor interface for this panel
		 *
		 * @private
		 */
		_destroyEditPanel: function()
		{
			var parent = this.element.parent();

			if( this.options._editorFieldCount > 0 )
			{
				this._saveEditorFieldValues();
			}

			this.element
				.detach()
				.removeClass( 'edit' )
				.draggable( 'destroy' )
				.resizable( 'destroy' )
				.unbind( 'click' )
				.html( '' )
				.appendTo( parent );
			$( 'body' ).removeClass( 'viewing' );
		},

		/**
		 * Creates the view interface for this panel
		 *
		 * @private
		 */
		_drawViewPanel: function()
		{
			this.element
				.addClass( 'view' )
				.html( $( '<div>', { 'class': 'content', 'text': 'content' } ) );
			if( this.options.refreshable )
			{
				this.element.addClass( 'refresh' );
				this.refreshContent();
			}
		},

		/**
		 * Destroys the view interface for this panel
		 *
		 * @private
		 */
		_destroyViewPanel: function()
		{
			this.element.removeClass( 'view' );
			if( this.options.refreshable )
			{
				this.element.removeClass( 'refresh' );
			}
		},

		/**
		 * Called by the _create method or when panels are reorder by the Panels controller
		 *
		 * @param order New z-index for this panel
		 */
		setOrdering: function( order )
		{
			this.element.zIndex( order );
			this.setting( 'ordering', order );
		},

		/**
		 * Brings this panel to the front
		 *
		 * @private
		 */
		_bringToFront: function()
		{
			this.element.parent().panels( 'setFrontPanel', this );
		},

		/**
		 * Override this method and call this._addEditorField as required in here
		 *
		 * @private
		 */
		_createEditorFields: function()
		{
			if( this.options.refreshable )
			{
				this._addEditorField(
					'select',
					'refreshInterval',
					this.option.refreshInterval,
					'Check for new data',
					{ 'options': [
						{'value': 60, 'display': 'every minute'},
						{'value': 300, 'display': 'every 5 minutes'},
						{'value': 1800, 'display': 'every half hour'},
						{'value': 3600, 'display': 'once an hour'}
					] } );
			}
		},

		/**
		 * Adds an editor field
		 *
		 * @param {String} fieldType Currently supports: bool
		 * @param {String} settingName Name of the setting to be modified by this field
		 * @param {*} defaultValue Default value of the setting
		 * @param {String} [fieldLabel] Label to use for the input. Setting name is used if nothing is provided
		 * @param {Object} [additionalData] additional data required for the display of this input (eg drop down options)
		 * @private
		 */
		_addEditorField: function( fieldType, settingName, defaultValue, fieldLabel, additionalData )
		{
			// Default the field label to settingName if none was provided
			if( fieldLabel === undefined )
			{
				fieldLabel = settingName;
			}

			if( additionalData === undefined )
			{
				additionalData = {};
			}

			// Create the editor fields object
			this.options._editorFields[ settingName ] = {
				'type': fieldType,
				'label': fieldLabel,
				'editorId': 'ef' + (this.options._editorFieldCount = this.options._editorFieldCount + 1),
				'additionalData': additionalData
			};

			// Set the default if it isn't set
			if( this.isSetting( settingName ) === false )
			{
				this.setting( settingName, defaultValue );
			}
		},

		/**
		 * Adds the editor fields to the editor view
		 *
		 * @param {Boolean} [clearContent] Content div will be cleared if TRUE, default: TRUE
		 * @private
		 */
		_drawEditorFields: function( clearContent )
		{
			var
				fieldSetting,
				fieldSettings,
				container = this.element.find( 'div.content' ),
				input,
				inputValue,
				iteratorLimit,
				iterator,
				iteratedValue;

			if( clearContent === undefined || clearContent )
			{
				container.html( '' );
			}
			for( fieldSetting in this.options._editorFields )
			{
				if( this.options._editorFields.hasOwnProperty( fieldSetting ) )
				{
					fieldSettings = this.options._editorFields[ fieldSetting ];
					inputValue = this.setting( fieldSetting );

					switch( fieldSettings.type )
					{
						case 'bool':
							input =
							$( '<input>', { 'type': 'checkbox', 'checked': parseInt( inputValue, 10 ) === 1 } );
							break;
						case 'text':
							input = $( '<input>', { 'type': 'text', 'value': inputValue } );
							break;
						case 'select':
							input = $( '<select/>' );
							iteratorLimit = fieldSettings.additionalData.options.length;
							for( iterator = 0; iterator < iteratorLimit; iterator++ )
							{
								iteratedValue = fieldSettings.additionalData.options[ iterator ];
								input.append( $( '<option/>',
									{
										'value': iteratedValue.value,
										'text': (
											iteratedValue.display !== undefined
												? iteratedValue.display
												: iteratedValue.value
											),
										'selected': iteratedValue.value.toString() === inputValue
									}
								) );
							}
							break;
						default:
							input = $( '<span>', {'text': 'Unsupported input type: ' + fieldSettings.type} );
							break;
					}
					container.append(
						$( '<label>', { 'text': fieldSettings.label } ).append( input.addClass( fieldSettings.editorId ) )
					);
				}
			}
		},

		/**
		 * Save editor field values to settings
		 *
		 * @private
		 */
		_saveEditorFieldValues: function()
		{
			var
				fieldSetting,
				fieldSettings,
				fieldInput,
				settingValue,
				container = this.element.find( 'div.content' );

			// For each editor field, retrieve the field's value and store it in the appropriate setting
			for( fieldSetting in this.options._editorFields )
			{
				if( this.options._editorFields.hasOwnProperty( fieldSetting ) )
				{
					fieldSettings = this.options._editorFields[ fieldSetting ];
					fieldInput = container.find( '.' + fieldSettings.editorId );
					// Retrieve the value from the input
					switch( fieldSettings.type )
					{
						case 'bool':
							settingValue = fieldInput.is( ':checked' ) ? 1 : 0;
							break;
						case 'text':
						case 'select':
							settingValue = fieldInput.val();
							break;
						default:
							settingValue = undefined;
							break;
					}

					// If a valid value has been retrieved, set the setting
					if( settingValue !== undefined )
					{
						this.setting( fieldSetting, settingValue );
					}
				}
			}
			this._readSettings();
		},

		/**
		 * Used to read settings into properties of widgets. Useful for setting flags etc
		 *
		 * Triggered on creation of the widget and when settings are updated
		 *
		 * @private
		 */
		_readSettings: function()
		{
			if( this.options.refreshable )
			{
				this.options.refreshInterval = parseInt( this.setting( 'refreshInterval' ), 10 );
			}
		},

		/**
		 * Hashes the passed string
		 *
		 * @param string
		 * @returns {Object}
		 */
		hash: function( string )
		{
			// JSLint really doesn't like bitwise operations. But they are totally fine here. Probably
			return string.split( "" ).reduce( function( a, b )
				//noinspection JSLint
			{
				a = ((a << 5) - a) + b.charCodeAt( 0 );
				//noinspection JSLint
				return (a & a);
			}, 0 );
		},

		/**
		 * Updates a panel's content after a specified time interval
		 *
		 * @returns {boolean} Indicates if this panel should use refreshing (is refreshable and has a refresh interval)
		 */
		refreshContent: function()
		{
			if( this.options.refreshable )
			{
				this.element.data( 'nextRefresh', (new Date()).getTime() + ( this.options.refreshInterval * 1000 ) );

				return this.options.refreshInterval > 0;
			}

			return false;
		}

	} );
} );
<commit_msg>jslint suppression<commit_after>/**
 * @author Sam Thompson
 *
 * Provides basic functionality of a panel
 */
$( function()
{
	"use strict";
	//noinspection JSUnresolvedVariable
	$.widget( 'jqueryDash.panel', $.jqueryDash.localStorageObject, {

		_mode: 'init',
		_modeEdit: 'edit',
		_modeView: 'view',

		options: {
			id: 0,
			mode: 'view',
			minWidth: 200,
			minHeight: 200,
			position: {
				x: 8,
				y: 9
			},
			_editorFields: {},
			_editorFieldCount: 0,
			ordering: null,
			refreshable: false,
			refreshInterval: 300
		},

		/**
		 * Create a new panel widget
		 *
		 * @private
		 */
		_create: function()
		{
			var
				initialPos,
				initialWidth,
				initialHeight;

			this.element.data( 'panelType', this.widgetName );

			// Load the saved panel position
			initialPos = ( this.isSetting( 'posX' ) && this.isSetting( 'posY' ) ) ?
						 { 'x': this.setting( 'posX' ), 'y': this.setting( 'posY' ) } :
						 { 'x': this.options.position.x, 'y': this.options.position.y };

			// Load the saved panel size
			initialWidth = ( this.isSetting( 'width' ) ) ? this.setting( 'width' ) : this.options.minWidth;
			initialHeight = ( this.isSetting( 'height' ) ) ? this.setting( 'height' ) : this.options.minHeight;

			this.element
				.width( initialWidth )
				.height( initialHeight )
				.css( {
					'position': 'absolute',
					'top': initialPos.y,
					'left': initialPos.x
				} )
				.addClass( this.widgetName );

			this._createEditorFields();

			this._readSettings();

			// Set the default mode
			this.mode( this.options.mode );

			// Set the ordering if one is specified
			if( this.options.ordering !== null )
			{
				this.setOrdering( this.options.ordering );
			}
			else
			{
				// Load the ordering setting
				if( this.isSetting( 'ordering' ) )
				{
					this.element.zIndex( this.setting( 'ordering' ) );
				}
			}
		},

		/**
		 * Name of the storage object for this widget
		 *
		 * @returns {number}
		 * @private
		 */
		_getStorageNamespace: function()
		{
			return this.options.id;
		},

		/**
		 * Gets or sets the mode of this panel
		 *
		 * @param {string} [mode] Acts as setter if provided
		 * @returns {string} Current mode
		 */
		mode: function( mode )
		{
			// Set the mode if provided
			if( mode !== undefined && mode !== this._mode )
			{

				if( mode === this._modeEdit )
				{
					// destroy view mode if required
					if( this._mode === this._modeView )
					{
						this._destroyViewPanel();
					}
					this._drawEditPanel();
					this._mode = this._modeEdit;
				}
				else
				{
					// destroy edit mode if required
					if( this._mode === this._modeEdit )
					{
						this._destroyEditPanel();
					}
					// open view
					this._drawViewPanel();
					this._mode = this._modeView;
				}
			}

			// Return the mode - allows this method to be used as a getter if no mode is passed
			return this._mode;
		},

		/**
		 * Completely removes this panel and all storage records associated with it
		 *
		 * @private
		 */
		_destroy: function()
		{

			// destroy anything specific to whatever mode we are in
			switch( this._mode )
			{
				case this._modeEdit:
					this._destroyEditPanel();
					break;
				case this._modeView:
					this._destroyViewPanel();
					break;
			}

			// Destroy this widget
			this._super();

			// Remove settings for this object from the controller's local storage and remove the element from the DOM
			this.element.parent().trigger( 'deletePanel', this.options.id ).end().remove();
		},

		/**
		 * Creates the editor interface for this panel
		 *
		 * @private
		 */
		_drawEditPanel: function()
		{
			var self = this;
			this.element
				.addClass( 'edit' )
				.html( $( '<div>', { 'class': 'content', 'text': 'content' } ) )
				// Set the panel to be movable
				.draggable( {
					containment: 'parent',
					grid: [50, 50],
					'handle': '.move-panel',
					start: function()
					{
						self._bringToFront();
					},
					stop: function()
					{
						var position = $( this ).position();
						self.setting( 'posX', position.left );
						self.setting( 'posY', position.top );
					}
				} )
				// Set the panel to be resizable
				.resizable( {
					minWidth: this.options.minWidth,
					minHeight: this.options.minHeight,
					handles: 'n,s,e,w,se,sw,nw',
					grid: 50,
					containment: 'parent',
					start: function()
					{
						self._bringToFront();
					},
					stop: function()
					{
						self.setting( 'width', $( this ).width() );
						self.setting( 'height', $( this ).height() );
					}
				} )
				.append(
					$( '<a>', {'class': 'button delete-panel', 'text': 'x', 'title': 'Delete Panel'} ).click( function()
					{
						if( confirm( 'Are you sure you want to delete this panel?' ) )
						{
							self._destroy();
						}
					} ),
					$( '<a>', {'class': 'move-panel', 'html': '≡', 'title': 'Move Panel'} )
				)
				.click( function()
				{
					self._bringToFront();
				} );

			if( this.options._editorFieldCount > 0 )
			{
				this._drawEditorFields();
			}

			$( 'body' ).disableSelection();
		},

		/**
		 * Destroys the editor interface for this panel
		 *
		 * @private
		 */
		_destroyEditPanel: function()
		{
			var parent = this.element.parent();

			if( this.options._editorFieldCount > 0 )
			{
				this._saveEditorFieldValues();
			}

			this.element
				.detach()
				.removeClass( 'edit' )
				.draggable( 'destroy' )
				.resizable( 'destroy' )
				.unbind( 'click' )
				.html( '' )
				.appendTo( parent );
			$( 'body' ).removeClass( 'viewing' );
		},

		/**
		 * Creates the view interface for this panel
		 *
		 * @private
		 */
		_drawViewPanel: function()
		{
			this.element
				.addClass( 'view' )
				.html( $( '<div>', { 'class': 'content', 'text': 'content' } ) );
			if( this.options.refreshable )
			{
				this.element.addClass( 'refresh' );
				this.refreshContent();
			}
		},

		/**
		 * Destroys the view interface for this panel
		 *
		 * @private
		 */
		_destroyViewPanel: function()
		{
			this.element.removeClass( 'view' );
			if( this.options.refreshable )
			{
				this.element.removeClass( 'refresh' );
			}
		},

		/**
		 * Called by the _create method or when panels are reorder by the Panels controller
		 *
		 * @param order New z-index for this panel
		 */
		setOrdering: function( order )
		{
			this.element.zIndex( order );
			this.setting( 'ordering', order );
		},

		/**
		 * Brings this panel to the front
		 *
		 * @private
		 */
		_bringToFront: function()
		{
			this.element.parent().panels( 'setFrontPanel', this );
		},

		/**
		 * Override this method and call this._addEditorField as required in here
		 *
		 * @private
		 */
		_createEditorFields: function()
		{
			if( this.options.refreshable )
			{
				this._addEditorField(
					'select',
					'refreshInterval',
					this.option.refreshInterval,
					'Check for new data',
					{ 'options': [
						{'value': 60, 'display': 'every minute'},
						{'value': 300, 'display': 'every 5 minutes'},
						{'value': 1800, 'display': 'every half hour'},
						{'value': 3600, 'display': 'once an hour'}
					] } );
			}
		},

		/**
		 * Adds an editor field
		 *
		 * @param {String} fieldType Currently supports: bool
		 * @param {String} settingName Name of the setting to be modified by this field
		 * @param {*} defaultValue Default value of the setting
		 * @param {String} [fieldLabel] Label to use for the input. Setting name is used if nothing is provided
		 * @param {Object} [additionalData] additional data required for the display of this input (eg drop down options)
		 * @private
		 */
		_addEditorField: function( fieldType, settingName, defaultValue, fieldLabel, additionalData )
		{
			// Default the field label to settingName if none was provided
			if( fieldLabel === undefined )
			{
				fieldLabel = settingName;
			}

			if( additionalData === undefined )
			{
				additionalData = {};
			}

			// Create the editor fields object
			this.options._editorFields[ settingName ] = {
				'type': fieldType,
				'label': fieldLabel,
				'editorId': 'ef' + (this.options._editorFieldCount = this.options._editorFieldCount + 1),
				'additionalData': additionalData
			};

			// Set the default if it isn't set
			if( this.isSetting( settingName ) === false )
			{
				this.setting( settingName, defaultValue );
			}
		},

		/**
		 * Adds the editor fields to the editor view
		 *
		 * @param {Boolean} [clearContent] Content div will be cleared if TRUE, default: TRUE
		 * @private
		 */
		_drawEditorFields: function( clearContent )
		{
			var
				fieldSetting,
				fieldSettings,
				container = this.element.find( 'div.content' ),
				input,
				inputValue,
				iteratorLimit,
				iterator,
				iteratedValue;

			if( clearContent === undefined || clearContent )
			{
				container.html( '' );
			}
			for( fieldSetting in this.options._editorFields )
			{
				if( this.options._editorFields.hasOwnProperty( fieldSetting ) )
				{
					fieldSettings = this.options._editorFields[ fieldSetting ];
					inputValue = this.setting( fieldSetting );

					switch( fieldSettings.type )
					{
						case 'bool':
							input =
							$( '<input>', { 'type': 'checkbox', 'checked': parseInt( inputValue, 10 ) === 1 } );
							break;
						case 'text':
							input = $( '<input>', { 'type': 'text', 'value': inputValue } );
							break;
						case 'select':
							input = $( '<select/>' );
							iteratorLimit = fieldSettings.additionalData.options.length;
							//noinspection JSLint
							for( iterator = 0; iterator < iteratorLimit; iterator++ )
							{
								iteratedValue = fieldSettings.additionalData.options[ iterator ];
								input.append( $( '<option/>',
									{
										'value': iteratedValue.value,
										'text': (
											iteratedValue.display !== undefined
												? iteratedValue.display
												: iteratedValue.value
											),
										'selected': iteratedValue.value.toString() === inputValue
									}
								) );
							}
							break;
						default:
							input = $( '<span>', {'text': 'Unsupported input type: ' + fieldSettings.type} );
							break;
					}
					container.append(
						$( '<label>', { 'text': fieldSettings.label } ).append( input.addClass( fieldSettings.editorId ) )
					);
				}
			}
		},

		/**
		 * Save editor field values to settings
		 *
		 * @private
		 */
		_saveEditorFieldValues: function()
		{
			var
				fieldSetting,
				fieldSettings,
				fieldInput,
				settingValue,
				container = this.element.find( 'div.content' );

			// For each editor field, retrieve the field's value and store it in the appropriate setting
			for( fieldSetting in this.options._editorFields )
			{
				if( this.options._editorFields.hasOwnProperty( fieldSetting ) )
				{
					fieldSettings = this.options._editorFields[ fieldSetting ];
					fieldInput = container.find( '.' + fieldSettings.editorId );
					// Retrieve the value from the input
					switch( fieldSettings.type )
					{
						case 'bool':
							settingValue = fieldInput.is( ':checked' ) ? 1 : 0;
							break;
						case 'text':
						case 'select':
							settingValue = fieldInput.val();
							break;
						default:
							settingValue = undefined;
							break;
					}

					// If a valid value has been retrieved, set the setting
					if( settingValue !== undefined )
					{
						this.setting( fieldSetting, settingValue );
					}
				}
			}
			this._readSettings();
		},

		/**
		 * Used to read settings into properties of widgets. Useful for setting flags etc
		 *
		 * Triggered on creation of the widget and when settings are updated
		 *
		 * @private
		 */
		_readSettings: function()
		{
			if( this.options.refreshable )
			{
				this.options.refreshInterval = parseInt( this.setting( 'refreshInterval' ), 10 );
			}
		},

		/**
		 * Hashes the passed string
		 *
		 * @param string
		 * @returns {Object}
		 */
		hash: function( string )
		{
			// JSLint really doesn't like bitwise operations. But they are totally fine here. Probably
			return string.split( "" ).reduce( function( a, b )
				//noinspection JSLint
			{
				a = ((a << 5) - a) + b.charCodeAt( 0 );
				//noinspection JSLint
				return (a & a);
			}, 0 );
		},

		/**
		 * Updates a panel's content after a specified time interval
		 *
		 * @returns {boolean} Indicates if this panel should use refreshing (is refreshable and has a refresh interval)
		 */
		refreshContent: function()
		{
			if( this.options.refreshable )
			{
				this.element.data( 'nextRefresh', (new Date()).getTime() + ( this.options.refreshInterval * 1000 ) );

				return this.options.refreshInterval > 0;
			}

			return false;
		}

	} );
} );
<|endoftext|>


<commit_before>var page = require('webpage').create(),
    system = require('system'),
    fs = require('fs');

var userAgent = "Grabshot";

var url  = system.args[1];
var format = system.args[2] || "PNG";
var width = parseInt(system.args[3]) || 1280;
var height = parseInt(system.args[4]); // default: fit content
var crop = true;

// NOTE: This does not work as "window" size, so height will
// not do as expected here.
//
// See https://github.com/ariya/phantomjs/issues/10619
page.viewportSize = {
  width: width,
  height: height
};

function render() {
  var result = page.evaluate(function () {
    return {
      title: document.title,
      width: document.body.clientWidth,
      height: document.body.clientHeight,
    };
  });

  if(crop && height && result.height > height) {
    // Cropping isn't exactly what we want, but PhantomJS does
    // not yet have a "window size" concept (See NOTE above).
    page.clipRect = {
      top: 0,
      left: 0,
      width: width,
      height: height
    };

    result.height = height;
    result.width  = width;
  }

  result.imageData = page.renderBase64(format);
  result.format = format;

  console.log(JSON.stringify(result));
  phantom.exit();
}

page.onError = phantom.onError = function() {
  console.log("PhantomJS error :(");
  phantom.exit(1);
};

page.onLoadFinished = function (status) {
  if (status !== 'success') {
    phantom.exit(1);
  }

  setTimeout(render, 300);
};

page.open(url);
<commit_msg>Fix typekit font support<commit_after>var page = require('webpage').create(),
    system = require('system'),
    fs = require('fs');

var userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17';

var url  = system.args[1];
var format = system.args[2] || "PNG";
var width = parseInt(system.args[3]) || 1280;
var height = parseInt(system.args[4]); // default: fit content
var crop = true;

// NOTE: This does not work as "window" size, so height will
// not do as expected here.
//
// See https://github.com/ariya/phantomjs/issues/10619
page.viewportSize = {
  width: width,
  height: height
};

page.customHeaders = {
  Referer: url
};

page.settings.userAgent = userAgent;

function render() {
  var result = page.evaluate(function () {
    return {
      title: document.title,
      width: document.body.clientWidth,
      height: document.body.clientHeight,
    };
  });

  if(crop && height && result.height > height) {
    // Cropping isn't exactly what we want, but PhantomJS does
    // not yet have a "window size" concept (See NOTE above).
    page.clipRect = {
      top: 0,
      left: 0,
      width: width,
      height: height
    };

    result.height = height;
    result.width  = width;
  }

  result.imageData = page.renderBase64(format);
  result.format = format;

  console.log(JSON.stringify(result));
  phantom.exit();
}

page.onError = phantom.onError = function() {
  console.log("PhantomJS error :(");
  phantom.exit(1);
};

page.onLoadFinished = function (status) {
  if (status !== 'success') {
    phantom.exit(1);
  }

  setTimeout(render, 300);
};

page.open(url);
<|endoftext|>


<commit_before>'use strict';

const assert = require('assert');
const Router = require('./index');
const middleware = require('storj-service-middleware');
const rawbody = middleware.rawbody;
const log = require('../../logger');
const errors = require('storj-service-error-types');
const merge = require('merge');
const inherits = require('util').inherits;
const authenticate = middleware.authenticate;
const crypto = require('crypto');
const storj = require('storj-lib');
const analytics = require('../../vendor/analytics');

/**
 * Handles endpoints for all user related operations
 * @constructor
 * @extends {Router}
 */
function UsersRouter(options) {
  if (!(this instanceof UsersRouter)) {
    return new UsersRouter(options);
  }

  Router.apply(this, arguments);

  this._verify = authenticate(this.storage);
}

inherits(UsersRouter, Router);

/**
 * Sends a user activation email
 * @private
 */
UsersRouter.prototype._dispatchActivationEmail = function(user, redir, cb) {
  let self = this;
  let profile = self.config.server.public || self.config.server;
  let host = profile.host;
  let callback = cb || storj.utils.noop;
  let port = [443, 80].indexOf(profile.port) === -1 ?
             ':' + profile.port :
             '';
  let proto = self.config.server.ssl &&
              self.config.server.ssl.cert &&
              self.config.server.ssl.key ?
              'https:' :
              'http:';

  self.mailer.dispatch(user.email, 'confirm', {
    token: user.activator,
    redirect: redir,
    url: proto + '//' + host + port
  }, function(err) {
    if (err) {
      log.error('failed to send activation email, reason: %s', err.message);
      callback(err);
    } else {
      callback(null);
    }
  });
};

/**
 * Registers a new user
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.createUser = function(req, res, next) {
  const self = this;
  const User = this.storage.models.User;
  const PublicKey = this.storage.models.PublicKey;

  log.info('registering user account for %s', req.body.email);

  User.create(req.body.email, req.body.password, function(err, user) {
    if (err) {
      return next(err);
    }

    analytics.identify(req.headers.dnt, {
      userID: user.uuid,
      traits: {
        activated: false
      }
    });
    analytics.track(req.headers.dnt, {
      userID: user.uuid,
      event: 'User Created'
    });

    if (!req.body.pubkey) {
      self._dispatchActivationEmail(user, req.body.redirect);
      return res.status(201).send(user.toObject());
    }

    PublicKey.create(user, req.body.pubkey, function(err, pubkey) {
      if (err) {
        user.remove();
        return next(new errors.BadRequestError(err.message));
      }

      self._dispatchActivationEmail(user, req.body.redirect);
      res.status(201).send(merge(user.toObject(), {
        pubkey: pubkey.key
      }));
    });
  });
};

/**
 * Confirms a user account
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.confirmActivateUser = function(req, res, next) {
  const User = this.storage.models.User;

  log.info('activating user with token %s', req.params.token);

  User.findOne({
    activator: req.params.token
  }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.BadRequestError('Invalid activation token'));
    }

    user.activate(function(err) {
      if (err) {
        return next(new errors.InternalError(err.message));
      }

      analytics.track(req.headers.dnt, {
        userID: user.uuid,
        event: 'User Activated'
      });
      analytics.identify(req.headers.dnt, {
        userID: user.uuid,
        traits: {
          activated: true
        }
      });

      if (req.query.redirect) {
        res.redirect(req.query.redirect);
      } else {
        res.send(user.toObject());
      }
    });
  });
};

/**
 * Reactivates a user account
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.reactivateUser = function(req, res, next) {
  const self = this;
  const User = this.storage.models.User;

  log.info('sending account reactivation email to %s', req.body.email);

  User.findOne({
    _id: req.body.email
  }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.NotFoundError('User not found'));
    }

    if (user.activated) {
      return next(new errors.BadRequestError('User is already activated'));
    }

    self._dispatchActivationEmail(user, req.body.redirect);
    res.status(201).send(user.toObject());
  });
};

/**
 * Deactivates a user account
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.destroyUser = function(req, res, next) {
  const self = this;
  const User = this.storage.models.User;

  log.info('creating user deactivation token');

  User.findOne({ _id: req.params.id }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.NotFoundError('User not found'));
    }

    if (req.user._id !== user._id) {
      return next(new errors.NotAuthorizedError());
    }

    user.deactivator = crypto.randomBytes(256).toString('hex');

    let profile = self.config.server.public || self.config.server;
    let host = profile.host;
    let port = [443, 80].indexOf(profile.port) === -1 ?
               ':' + profile.port :
               '';
    let proto = self.config.server.ssl &&
                self.config.server.ssl.cert &&
                self.config.server.ssl.key ?
                'https:' :
                'http:';

    self.mailer.dispatch(user.email, 'delete', {
      token: user.deactivator,
      redirect: req.body.redirect,
      url: proto + '//' + host + port
    }, function(err) {
      if (err) {
        log.error('failed to send deactivation email, reason: %s', err.message);
        return next(new errors.InternalError(err.message));
      }

      user.save(function(err) {
        if (err) {
          return next(new errors.InternalError(err.message));
        }

        res.status(200).send(user.toObject());
      });
    });
  });
};

/**
 * Confirms the deletion of a user account
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.confirmDestroyUser = function(req, res, next) {
  const User = this.storage.models.User;

  log.info('deactivating user account with token %s', req.params.token);

  User.findOne({ deactivator: req.params.token }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.NotFoundError('User not found'));
    }

    user.deactivate(function(err) {
      if (err) {
        return next(new errors.InternalError(err.message));
      }

      if (req.query.redirect) {
        res.redirect(req.query.redirect);
      } else {
        res.status(200).send(user.toObject());
      }
    });
  });
};

/**
 * Creates a password reset token
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.createPasswordResetToken = function(req, res, next) {
  const self = this;
  const User = this.storage.models.User;

  try {
    assert(Buffer(req.body.password, 'hex').length * 8 === 256);
  } catch (err) {
    return next(new errors.BadRequestError(
      'Password must be hex encoded SHA-256 hash'
    ));
  }

  User.findOne({ _id: req.params.id }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.NotFoundError('User not found'));
    }

    log.info('creating password reset token for %s', user._id);

    user.resetter = crypto.randomBytes(256).toString('hex');
    user.pendingHashPass = crypto.createHash('sha256').update(req.body.password).digest('hex');

    user.save(function(err) {
      if (err) {
        return next(new errors.InternalError(err.message));
      }

      let profile = self.config.server.public || self.config.server;
      let host = profile.host;
      let port = [443, 80].indexOf(profile.port) === -1 ?
                 ':' + profile.port :
                 '';
      let proto = self.config.server.ssl &&
                  self.config.server.ssl.cert &&
                  self.config.server.ssl.key ?
                  'https:' :
                  'http:';

      self.mailer.dispatch(user.email, 'reset', {
        token: user.resetter,
        redirect: req.body.redirect,
        url: proto + '//' + host + port
      }, function(err) {
        if (err) {
          log.error('failed to send reset email, reason: %s', err.message);
          return next(new errors.InternalError(err.message));
        }

        res.status(200).send(user.toObject());
      });
    });
  });
};

/**
 * Confirms and applies the password reset
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.confirmPasswordReset = function(req, res, next) {
  const User = this.storage.models.User;

  // NB: Mitigate timing attack
  // NB: - attempt to make non-lookup-responses take similar
  // NB: - amounts of time as lookup-responses
  User.count({}, function(err, count) {
    let rand = Math.floor(Math.random() * count);

    function nextPlusTime(value) {
      User.findOne({}).skip(rand).exec(function() { // do nothing with this record
        next(value);
      });
    }

    try {
      assert(Buffer(req.params.token, 'hex').length === 256);
    } catch (err) {
      return nextPlusTime(new Error('Resetter must be hex encoded 256 byte string'));
    }

    User.findOne({ resetter: req.params.token }, function(err, user) {
      if (err) {
        return next(new errors.InternalError(err.message));
      }

      if (!user) {
        return next(new errors.NotFoundError('User not found'));
      }

      user.hashpass = user.pendingHashPass;
      user.pendingHashPass = null;
      user.resetter = null;

      user.save(function(err) {
        if (err) {
          return next(new errors.InternalError(err.message));
        }

        if (req.query.redirect) {
          res.redirect(req.query.redirect);
        } else {
          res.send(user.toObject());
        }
      });
    });
  });
};

/**
 * Export definitions
 * @private
 */
UsersRouter.prototype._definitions = function() {
  return [
    ['POST', '/users', rawbody, this.createUser],
    ['POST', '/activations', rawbody, this.reactivateUser],
    ['GET', '/activations/:token', this.confirmActivateUser],
    ['DELETE', '/users/:id', this._verify, this.destroyUser],
    ['GET', '/deactivations/:token', this.confirmDestroyUser],
    ['PATCH', '/users/:id', rawbody, this.createPasswordResetToken],
    ['GET', '/resets/:token', this.confirmPasswordReset]
  ];
};

module.exports = UsersRouter;
<commit_msg>add obfuscated email to identify call<commit_after>'use strict';

const assert = require('assert');
const Router = require('./index');
const middleware = require('storj-service-middleware');
const rawbody = middleware.rawbody;
const log = require('../../logger');
const errors = require('storj-service-error-types');
const merge = require('merge');
const inherits = require('util').inherits;
const authenticate = middleware.authenticate;
const crypto = require('crypto');
const storj = require('storj-lib');
const analytics = require('../../vendor/analytics');

/**
 * Handles endpoints for all user related operations
 * @constructor
 * @extends {Router}
 */
function UsersRouter(options) {
  if (!(this instanceof UsersRouter)) {
    return new UsersRouter(options);
  }

  Router.apply(this, arguments);

  this._verify = authenticate(this.storage);
}

inherits(UsersRouter, Router);

/**
 * Sends a user activation email
 * @private
 */
UsersRouter.prototype._dispatchActivationEmail = function(user, redir, cb) {
  let self = this;
  let profile = self.config.server.public || self.config.server;
  let host = profile.host;
  let callback = cb || storj.utils.noop;
  let port = [443, 80].indexOf(profile.port) === -1 ?
             ':' + profile.port :
             '';
  let proto = self.config.server.ssl &&
              self.config.server.ssl.cert &&
              self.config.server.ssl.key ?
              'https:' :
              'http:';

  self.mailer.dispatch(user.email, 'confirm', {
    token: user.activator,
    redirect: redir,
    url: proto + '//' + host + port
  }, function(err) {
    if (err) {
      log.error('failed to send activation email, reason: %s', err.message);
      callback(err);
    } else {
      callback(null);
    }
  });
};

/**
 * Registers a new user
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.createUser = function(req, res, next) {
  const self = this;
  const User = this.storage.models.User;
  const PublicKey = this.storage.models.PublicKey;

  log.info('registering user account for %s', req.body.email);

  User.create(req.body.email, req.body.password, function(err, user) {
    if (err) {
      return next(err);
    }

    analytics.identify(req.headers.dnt, {
      userID: user.uuid,
      traits: {
        activated: false,
        email: user.uuid + '@user.storj.io'
      }
    });
    analytics.track(req.headers.dnt, {
      userID: user.uuid,
      event: 'User Created'
    });

    if (!req.body.pubkey) {
      self._dispatchActivationEmail(user, req.body.redirect);
      return res.status(201).send(user.toObject());
    }

    PublicKey.create(user, req.body.pubkey, function(err, pubkey) {
      if (err) {
        user.remove();
        return next(new errors.BadRequestError(err.message));
      }

      self._dispatchActivationEmail(user, req.body.redirect);
      res.status(201).send(merge(user.toObject(), {
        pubkey: pubkey.key
      }));
    });
  });
};

/**
 * Confirms a user account
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.confirmActivateUser = function(req, res, next) {
  const User = this.storage.models.User;

  log.info('activating user with token %s', req.params.token);

  User.findOne({
    activator: req.params.token
  }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.BadRequestError('Invalid activation token'));
    }

    user.activate(function(err) {
      if (err) {
        return next(new errors.InternalError(err.message));
      }

      analytics.track(req.headers.dnt, {
        userID: user.uuid,
        event: 'User Activated'
      });
      analytics.identify(req.headers.dnt, {
        userID: user.uuid,
        traits: {
          activated: true
        }
      });

      if (req.query.redirect) {
        res.redirect(req.query.redirect);
      } else {
        res.send(user.toObject());
      }
    });
  });
};

/**
 * Reactivates a user account
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.reactivateUser = function(req, res, next) {
  const self = this;
  const User = this.storage.models.User;

  log.info('sending account reactivation email to %s', req.body.email);

  User.findOne({
    _id: req.body.email
  }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.NotFoundError('User not found'));
    }

    if (user.activated) {
      return next(new errors.BadRequestError('User is already activated'));
    }

    self._dispatchActivationEmail(user, req.body.redirect);
    res.status(201).send(user.toObject());
  });
};

/**
 * Deactivates a user account
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.destroyUser = function(req, res, next) {
  const self = this;
  const User = this.storage.models.User;

  log.info('creating user deactivation token');

  User.findOne({ _id: req.params.id }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.NotFoundError('User not found'));
    }

    if (req.user._id !== user._id) {
      return next(new errors.NotAuthorizedError());
    }

    user.deactivator = crypto.randomBytes(256).toString('hex');

    let profile = self.config.server.public || self.config.server;
    let host = profile.host;
    let port = [443, 80].indexOf(profile.port) === -1 ?
               ':' + profile.port :
               '';
    let proto = self.config.server.ssl &&
                self.config.server.ssl.cert &&
                self.config.server.ssl.key ?
                'https:' :
                'http:';

    self.mailer.dispatch(user.email, 'delete', {
      token: user.deactivator,
      redirect: req.body.redirect,
      url: proto + '//' + host + port
    }, function(err) {
      if (err) {
        log.error('failed to send deactivation email, reason: %s', err.message);
        return next(new errors.InternalError(err.message));
      }

      user.save(function(err) {
        if (err) {
          return next(new errors.InternalError(err.message));
        }

        res.status(200).send(user.toObject());
      });
    });
  });
};

/**
 * Confirms the deletion of a user account
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.confirmDestroyUser = function(req, res, next) {
  const User = this.storage.models.User;

  log.info('deactivating user account with token %s', req.params.token);

  User.findOne({ deactivator: req.params.token }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.NotFoundError('User not found'));
    }

    user.deactivate(function(err) {
      if (err) {
        return next(new errors.InternalError(err.message));
      }

      if (req.query.redirect) {
        res.redirect(req.query.redirect);
      } else {
        res.status(200).send(user.toObject());
      }
    });
  });
};

/**
 * Creates a password reset token
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.createPasswordResetToken = function(req, res, next) {
  const self = this;
  const User = this.storage.models.User;

  try {
    assert(Buffer(req.body.password, 'hex').length * 8 === 256);
  } catch (err) {
    return next(new errors.BadRequestError(
      'Password must be hex encoded SHA-256 hash'
    ));
  }

  User.findOne({ _id: req.params.id }, function(err, user) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!user) {
      return next(new errors.NotFoundError('User not found'));
    }

    log.info('creating password reset token for %s', user._id);

    user.resetter = crypto.randomBytes(256).toString('hex');
    user.pendingHashPass = crypto.createHash('sha256').update(req.body.password).digest('hex');

    user.save(function(err) {
      if (err) {
        return next(new errors.InternalError(err.message));
      }

      let profile = self.config.server.public || self.config.server;
      let host = profile.host;
      let port = [443, 80].indexOf(profile.port) === -1 ?
                 ':' + profile.port :
                 '';
      let proto = self.config.server.ssl &&
                  self.config.server.ssl.cert &&
                  self.config.server.ssl.key ?
                  'https:' :
                  'http:';

      self.mailer.dispatch(user.email, 'reset', {
        token: user.resetter,
        redirect: req.body.redirect,
        url: proto + '//' + host + port
      }, function(err) {
        if (err) {
          log.error('failed to send reset email, reason: %s', err.message);
          return next(new errors.InternalError(err.message));
        }

        res.status(200).send(user.toObject());
      });
    });
  });
};

/**
 * Confirms and applies the password reset
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
UsersRouter.prototype.confirmPasswordReset = function(req, res, next) {
  const User = this.storage.models.User;

  // NB: Mitigate timing attack
  // NB: - attempt to make non-lookup-responses take similar
  // NB: - amounts of time as lookup-responses
  User.count({}, function(err, count) {
    let rand = Math.floor(Math.random() * count);

    function nextPlusTime(value) {
      User.findOne({}).skip(rand).exec(function() { // do nothing with this record
        next(value);
      });
    }

    try {
      assert(Buffer(req.params.token, 'hex').length === 256);
    } catch (err) {
      return nextPlusTime(new Error('Resetter must be hex encoded 256 byte string'));
    }

    User.findOne({ resetter: req.params.token }, function(err, user) {
      if (err) {
        return next(new errors.InternalError(err.message));
      }

      if (!user) {
        return next(new errors.NotFoundError('User not found'));
      }

      user.hashpass = user.pendingHashPass;
      user.pendingHashPass = null;
      user.resetter = null;

      user.save(function(err) {
        if (err) {
          return next(new errors.InternalError(err.message));
        }

        if (req.query.redirect) {
          res.redirect(req.query.redirect);
        } else {
          res.send(user.toObject());
        }
      });
    });
  });
};

/**
 * Export definitions
 * @private
 */
UsersRouter.prototype._definitions = function() {
  return [
    ['POST', '/users', rawbody, this.createUser],
    ['POST', '/activations', rawbody, this.reactivateUser],
    ['GET', '/activations/:token', this.confirmActivateUser],
    ['DELETE', '/users/:id', this._verify, this.destroyUser],
    ['GET', '/deactivations/:token', this.confirmDestroyUser],
    ['PATCH', '/users/:id', rawbody, this.createPasswordResetToken],
    ['GET', '/resets/:token', this.confirmPasswordReset]
  ];
};

module.exports = UsersRouter;
<|endoftext|>


<commit_before>#!/usr/bin/env node

var vsprintf = require("sprintf-js").vsprintf,
    stats = require('stats-lite'),
    _ = require('underscore'),
    db = require('./db'),
    data = {},
    contributorTypes;

db.select('office', 'contributions.committee_name', 'candidate_name')
    .count('* as contributions')
    .sum('amount as amount')
    .from('contributions')
    .innerJoin('committees', 'contributions.committee_name', 'committees.committee_name')
    .groupBy('office', 'contributions.committee_name', 'candidate_name')
    .orderBy('office')
    .orderBy('candidate_name')
    .then(function (rows) {
        rows.forEach(function (row) {
            console.log(row.committee_name);
            row.amountList = [];
            row.dcAmount = 0;
            row.amountByType = {};
            data[row.committee_name] = row;
        });
        getStats();
    });

function getStats() {
    getContributorTypes()
        .then(function () {
            db.select('committee_name', 'normalized', 'contributor_state')
                .sum('amount as subtotal')
                .from('contributions')
                .groupBy('committee_name', 'normalized')
                .having('subtotal', '>', 0)
                .then(function (rows) {
                    var prevOffice = '',
                        headers = 'Candidate            ' +
                            'Contributions  Contributors       Amount     Mean   Median  %Ind  %DC',
                        format = '%-20s %13d %13d  %11.2f  %7.2f  %7.2f  %4.0f  %3.0f';
                    console.log(headers);
                    rows.forEach(function (row) {
                        data[row.committee_name].amountList.push(row.subtotal);
                        if (row.contributor_state === 'DC') {
                            data[row.committee_name].dcAmount += row.subtotal;
                        }
                    });
                    _.each(data, function (c) {
                        var values = [
                                c.candidate_name,
                                c.contributions,
                                c.amountList.length,
                                c.amount
                            ];
                        c.amountList = c.amountList.sort(function (a, b) { return a - b; });
                        values.push(
                            stats.mean(c.amountList),
                            stats.median(c.amountList),
                            100 * ((c.amountByType['Individual'] || 0) + (c.amountByType['Candidate'] || 0)) / c.amount,
                            100 * ((c.dcAmount || 0)) / c.amount
                            //c.amountList[0],
                            //c.amountList[c.amountList.length - 1]
                        );
                        if (c.office !== prevOffice) {
                            console.log(c.office.toUpperCase());
                        }
                        console.log(vsprintf(format, values));
                        prevOffice = c.office;
                    });
                });
        });
}

function getContributorTypes() {
    return db.distinct('contributor_type')
        .from('contributions')
        .orderBy('contributor_type')
        .then(function (rows) {
            contributorTypes = _.pluck(rows, 'contributor_type');
            console.log(contributorTypes);
            return db.select('committee_name', 'contributor_type')
                .sum('amount as amount')
                .from('contributions')
                .groupBy('committee_name', 'contributor_type')
                .then(function (rows) {
                    rows.forEach(function (c) {
                        data[c.committee_name].amountByType[c.contributor_type] = c.amount;
                    });
                });
        });
}
<commit_msg>add DC individual amount to report<commit_after>#!/usr/bin/env node

var vsprintf = require("sprintf-js").vsprintf,
    stats = require('stats-lite'),
    _ = require('underscore'),
    db = require('./db'),
    data = {},
    contributorTypes;

db.select(
        'office',
        'contributions.committee_name',
        'candidate_name',
        db.raw("sum(case when contributor_state = 'DC' then amount else 0 end) as dc_amount"),
        db.raw("sum(case when contributor_type in ('Individual', 'Candidate') then amount else 0 end) as ind_amount"),
        db.raw("sum(case when contributor_state = 'DC' and contributor_type in ('Individual', 'Candidate') then amount else 0 end) as dc_ind_amount")
    )
    .count('* as contributions')
    .sum('amount as amount')
    .from('contributions')
    .innerJoin('committees', 'contributions.committee_name', 'committees.committee_name')
    .groupBy('office', 'contributions.committee_name', 'candidate_name')
    .orderBy('office')
    .orderBy('candidate_name')
    .then(function (rows) {
        rows.forEach(function (row) {
            console.log(row.committee_name);
            row.amountList = [];
            row.amountByType = {};
            data[row.committee_name] = row;
        });
        getStats();
    });

function getStats() {
    getContributorTypes()
        .then(function () {
            db.select('committee_name', 'normalized')
                .sum('amount as subtotal')
                .from('contributions')
                .groupBy('committee_name', 'normalized')
                .having('subtotal', '>', 0)
                .then(function (rows) {
                    var prevOffice = '',
                        headers = 'Candidate         ' +
                            'Contributions  Contributors       Amount     Mean   Median  %Ind %DC %DCInd',
                        format = '%-20s %10d %13d  %11.2f  %7.2f  %7.2f  %4.0f %3.0f %6.0f';
                    console.log(headers);
                    rows.forEach(function (row) {
                        data[row.committee_name].amountList.push(row.subtotal);
                    });
                    _.each(data, function (c) {
                        var values = [
                                c.candidate_name,
                                c.contributions,
                                c.amountList.length,
                                c.amount
                            ];
                        c.amountList = c.amountList.sort(function (a, b) { return a - b; });
                        values.push(
                            stats.mean(c.amountList),
                            stats.median(c.amountList),
                            100 * c.ind_amount / c.amount,
                            100 * c.dc_amount / c.amount,
                            100 * c.dc_ind_amount / c.amount,
                            //c.amountList[0],
                            //c.amountList[c.amountList.length - 1]
                        );
                        if (c.office !== prevOffice) {
                            console.log(c.office.toUpperCase());
                        }
                        console.log(vsprintf(format, values));
                        prevOffice = c.office;
                    });
                });
        });
}

function getContributorTypes() {
    return db.distinct('contributor_type')
        .from('contributions')
        .orderBy('contributor_type')
        .then(function (rows) {
            contributorTypes = _.pluck(rows, 'contributor_type');
            console.log(contributorTypes);
            return db.select('committee_name', 'contributor_type')
                .sum('amount as amount')
                .from('contributions')
                .groupBy('committee_name', 'contributor_type')
                .then(function (rows) {
                    rows.forEach(function (c) {
                        data[c.committee_name].amountByType[c.contributor_type] = c.amount;
                    });
                });
        });
}
<|endoftext|>


<commit_before><commit_msg>remove reveal.js<commit_after><|endoftext|>