<commit_before>/* jshint maxlen:false */

var config = { // eslint-disable-line no-unused-vars
//    configLocation: './config.json', // see ./modules/HttpConfigFetch.js
    hosts: {
        domain: 'jitsi-meet.example.com',
        //anonymousdomain: 'guest.example.com',
        //authdomain: 'jitsi-meet.example.com',  // defaults to <domain>
        muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030
        //jirecon: 'jirecon.jitsi-meet.example.com',
        //call_control: 'callcontrol.jitsi-meet.example.com',
        //focus: 'focus.jitsi-meet.example.com', // defaults to 'focus.jitsi-meet.example.com'
    },
//  getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
//  useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
//  useIPv6: true, // ipv6 support. use at your own risk
    useNicks: false,
    bosh: '//jitsi-meet.example.com/http-bind', // FIXME: use xep-0156 for that
    clientNode: 'http://jitsi.org/jitsimeet', // The name of client node advertised in XEP-0115 'c' stanza
    //focusUserJid: 'focus@auth.jitsi-meet.example.com', // The real JID of focus participant - can be overridden here
    //defaultSipNumber: '', // Default SIP number

    // The ID of the jidesha extension for Chrome.
    desktopSharingChromeExtId: null,
    // Whether desktop sharing should be disabled on Chrome.
    desktopSharingChromeDisabled: true,
    // The media sources to use when using screen sharing with the Chrome
    // extension.
    desktopSharingChromeSources: ['screen', 'window', 'tab'],
    // Required version of Chrome extension
    desktopSharingChromeMinExtVersion: '0.1',

    // The ID of the jidesha extension for Firefox. If null, we assume that no
    // extension is required.
    desktopSharingFirefoxExtId: null,
    // Whether desktop sharing should be disabled on Firefox.
    desktopSharingFirefoxDisabled: true,
    // The maximum version of Firefox which requires a jidesha extension.
    // Example: if set to 41, we will require the extension for Firefox versions
    // up to and including 41. On Firefox 42 and higher, we will run without the
    // extension.
    // If set to -1, an extension will be required for all versions of Firefox.
    desktopSharingFirefoxMaxVersionExtRequired: -1,
    // The URL to the Firefox extension for desktop sharing.
    desktopSharingFirefoxExtensionURL: null,

    // Disables ICE/UDP by filtering out local and remote UDP candidates in signalling.
    webrtcIceUdpDisable: false,
    // Disables ICE/TCP by filtering out local and remote TCP candidates in signalling.
    webrtcIceTcpDisable: false,

    openSctp: true, // Toggle to enable/disable SCTP channels
    disableStats: false,
    disableAudioLevels: false,
    channelLastN: -1, // The default value of the channel attribute last-n.
    //disableAdaptiveSimulcast: false,
    enableRecording: false,
    enableWelcomePage: true,
    //enableClosePage: false, // enabling the close page will ignore the welcome
                              // page redirection when call is hangup
    disableSimulcast: false,
    logStats: false, // Enable logging of PeerConnection stats via the focus
//    requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room.
//    startAudioMuted: 10, // every participant after the Nth will start audio muted
//    startVideoMuted: 10, // every participant after the Nth will start video muted
//    defaultLanguage: "en",
// To enable sending statistics to callstats.io you should provide Applicaiton ID and Secret.
//    callStatsID: "", // Application ID for callstats.io API
//    callStatsSecret: "", // Secret for callstats.io API
    /*noticeMessage: 'Service update is scheduled for 16th March 2015. ' +
    'During that time service will not be available. ' +
    'Apologise for inconvenience.',*/
    disableThirdPartyRequests: false,
    minHDHeight: 540,
    // If true - all users without token will be considered guests and all users
    // with token will be considered non-guests. Only guests will be allowed to
    // edit their profile.
    enableUserRolesBasedOnToken: false,
    // Suspending video might cause problems with audio playback. Disabling until these are fixed.
    disableSuspendVideo: true,
    // disables or enables RTX (RFC 4588).
    disableRtx: true,
    // Sets the preferred resolution (height) for local video. Defaults to 360.
    resolution: 720
};
<commit_msg>cleanup: remove old adaptive simulcast config option<commit_after>/* jshint maxlen:false */

var config = { // eslint-disable-line no-unused-vars
//    configLocation: './config.json', // see ./modules/HttpConfigFetch.js
    hosts: {
        domain: 'jitsi-meet.example.com',
        //anonymousdomain: 'guest.example.com',
        //authdomain: 'jitsi-meet.example.com',  // defaults to <domain>
        muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030
        //jirecon: 'jirecon.jitsi-meet.example.com',
        //call_control: 'callcontrol.jitsi-meet.example.com',
        //focus: 'focus.jitsi-meet.example.com', // defaults to 'focus.jitsi-meet.example.com'
    },
//  getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
//  useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
//  useIPv6: true, // ipv6 support. use at your own risk
    useNicks: false,
    bosh: '//jitsi-meet.example.com/http-bind', // FIXME: use xep-0156 for that
    clientNode: 'http://jitsi.org/jitsimeet', // The name of client node advertised in XEP-0115 'c' stanza
    //focusUserJid: 'focus@auth.jitsi-meet.example.com', // The real JID of focus participant - can be overridden here
    //defaultSipNumber: '', // Default SIP number

    // The ID of the jidesha extension for Chrome.
    desktopSharingChromeExtId: null,
    // Whether desktop sharing should be disabled on Chrome.
    desktopSharingChromeDisabled: true,
    // The media sources to use when using screen sharing with the Chrome
    // extension.
    desktopSharingChromeSources: ['screen', 'window', 'tab'],
    // Required version of Chrome extension
    desktopSharingChromeMinExtVersion: '0.1',

    // The ID of the jidesha extension for Firefox. If null, we assume that no
    // extension is required.
    desktopSharingFirefoxExtId: null,
    // Whether desktop sharing should be disabled on Firefox.
    desktopSharingFirefoxDisabled: true,
    // The maximum version of Firefox which requires a jidesha extension.
    // Example: if set to 41, we will require the extension for Firefox versions
    // up to and including 41. On Firefox 42 and higher, we will run without the
    // extension.
    // If set to -1, an extension will be required for all versions of Firefox.
    desktopSharingFirefoxMaxVersionExtRequired: -1,
    // The URL to the Firefox extension for desktop sharing.
    desktopSharingFirefoxExtensionURL: null,

    // Disables ICE/UDP by filtering out local and remote UDP candidates in signalling.
    webrtcIceUdpDisable: false,
    // Disables ICE/TCP by filtering out local and remote TCP candidates in signalling.
    webrtcIceTcpDisable: false,

    openSctp: true, // Toggle to enable/disable SCTP channels
    disableStats: false,
    disableAudioLevels: false,
    channelLastN: -1, // The default value of the channel attribute last-n.
    enableRecording: false,
    enableWelcomePage: true,
    //enableClosePage: false, // enabling the close page will ignore the welcome
                              // page redirection when call is hangup
    disableSimulcast: false,
    logStats: false, // Enable logging of PeerConnection stats via the focus
//    requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room.
//    startAudioMuted: 10, // every participant after the Nth will start audio muted
//    startVideoMuted: 10, // every participant after the Nth will start video muted
//    defaultLanguage: "en",
// To enable sending statistics to callstats.io you should provide Applicaiton ID and Secret.
//    callStatsID: "", // Application ID for callstats.io API
//    callStatsSecret: "", // Secret for callstats.io API
    /*noticeMessage: 'Service update is scheduled for 16th March 2015. ' +
    'During that time service will not be available. ' +
    'Apologise for inconvenience.',*/
    disableThirdPartyRequests: false,
    minHDHeight: 540,
    // If true - all users without token will be considered guests and all users
    // with token will be considered non-guests. Only guests will be allowed to
    // edit their profile.
    enableUserRolesBasedOnToken: false,
    // Suspending video might cause problems with audio playback. Disabling until these are fixed.
    disableSuspendVideo: true,
    // disables or enables RTX (RFC 4588).
    disableRtx: true,
    // Sets the preferred resolution (height) for local video. Defaults to 360.
    resolution: 720
};
<|endoftext|>


<commit_before>/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

const config = require('config');
const find = require('find');
const fs = require('fs');
const pkg = require('../package.json');
const path = require('path');
const semver = require('semver');
const UserProfile = require('./user-profile');
const Utils = require('./utils');

const API_VERSION = 2;
const MANIFEST_VERSION = 1;

/**
 * Verify one level of an object, recursing as required.
 *
 * @param {string} prefix - Prefix to use for keys, e.g. level1.level2.
 * @param {object} object - Object to validate
 * @param {object} template - Template to validate against
 *
 * @returns {string?} Error string, or undefined if no error.
 */
function validateObject(prefix, object, template) {
  for (const key in template) {
    if (key in object) {
      const objectVal = object[key];
      const templateVal = template[key];

      if (typeof objectVal !== typeof templateVal) {
        return `Expecting ${prefix}${key} to have type: ${
          typeof templateVal}, found: ${typeof objectVal}`;
      }

      if (typeof objectVal === 'object') {
        if (Array.isArray(objectVal)) {
          if (templateVal.length > 0) {
            const expectedType = typeof templateVal[0];
            for (const val of objectVal) {
              if (typeof val !== expectedType) {
                return `Expecting all values in ${prefix}${key} to be of ` +
                  `type ${expectedType}`;
              }
            }
          }
        } else {
          const err = validateObject(
            `${prefix + key}.`,
            objectVal,
            templateVal
          );
          if (err) {
            return err;
          }
        }
      }
    } else {
      return `Manifest is missing: ${prefix}${key}`;
    }
  }
}

/**
 * Verify that a package.json looks valid. We only need to validate fields
 * which we actually use.
 *
 * @param {object} manifest - The parsed package.json manifest
 *
 * @returns {string?} Error string, or undefined if no error.
 */
function validatePackageJson(manifest) {
  const manifestTemplate = {
    name: '',
    display_name: '',
    description: '',
    version: '',
    files: [''],
    moziot: {
      api: {
        min: 0,
        max: 0,
      },
    },
  };

  if (config.get('ipc.protocol') !== 'inproc') {
    // If we're not using in-process plugins, then we also need the exec
    // keyword to exist.
    manifestTemplate.moziot.exec = '';
  }

  return validateObject('', manifest, manifestTemplate);
}

/**
 * Verify that a manifest.json looks valid. We only need to validate fields
 * which we actually use.
 *
 * @param {object} manifest - The parsed manifest.json manifest
 *
 * @returns {string?} Error string, or undefined if no error.
 */
function validateManifestJson(manifest) {
  const manifestTemplate = {
    author: '',
    description: '',
    gateway_specific_settings: {
      webthings: {
        primary_type: '',
      },
    },
    homepage_url: '',
    id: '',
    license: '',
    manifest_version: 0,
    name: '',
    version: '',
  };

  if (config.get('ipc.protocol') !== 'inproc') {
    // If we're not using in-process plugins, then we also need the exec
    // keyword to exist.
    manifestTemplate.gateway_specific_settings.webthings.exec = '';
  }

  return validateObject('', manifest, manifestTemplate);
}

/**
 * Load an add-on manifest from a legacy package.json file.
 *
 * @param {string} packageId - The ID of the package, e.g. example-adapter
 *
 * @returns {object[]} 2-value array containing a parsed manifest and a default
 *                     config object.
 */
function loadPackageJson(packageId) {
  const addonPath = path.join(UserProfile.addonsDir, packageId);

  // Read the package.json file.
  let data;
  try {
    data = fs.readFileSync(path.join(addonPath, 'package.json'));
  } catch (e) {
    throw new Error(
      `Failed to read package.json for add-on ${packageId}: ${e}`
    );
  }

  // Parse as JSON
  let manifest;
  try {
    manifest = JSON.parse(data);
  } catch (e) {
    throw new Error(
      `Failed to parse package.json for add-on: ${packageId}: ${e}`
    );
  }

  // Verify that the name in the package matches the packageId
  if (manifest.name != packageId) {
    const err = `Name from package.json "${manifest.name}" doesn't ` +
                `match the ID from list.json "${packageId}"`;
    throw new Error(err);
  }

  // Verify the files list in the package.
  if (!manifest.hasOwnProperty('files') || manifest.files.length === 0) {
    throw new Error(`files property missing for add-on ${packageId}`);
  }

  // If the add-on is a git repository, skip checking the SHA256SUMS file.
  if (fs.existsSync(path.join(addonPath, '.git'))) {
    const sha256SumsIndex = manifest.files.indexOf('SHA256SUMS');
    if (sha256SumsIndex >= 0) {
      manifest.files.splice(sha256SumsIndex, 1);
      console.log(
        `Not checking SHA256SUMS file for ${packageId} since a .git`,
        'directory was detected'
      );
    }
  }

  // Verify that all files in the files list exist.
  for (let fname of manifest.files) {
    fname = path.join(addonPath, fname);
    if (!fs.existsSync(fname)) {
      throw new Error(`Add-on ${packageId} is missing file: ${fname}`);
    }
  }

  // If a SHA256SUMS file is present, verify it. This file is of the format:
  // <checksum> <filename>
  //
  // To generate a file of this type, you can use:
  //   `rm -f SHA256SUMS && sha256sum file1 file2 ... > SHA256SUMS`
  // To verify, use:
  //   `sha256sum --check SHA256SUMS`
  if (manifest.files.includes('SHA256SUMS')) {
    const sumsFile = path.join(addonPath, 'SHA256SUMS');
    try {
      const data = fs.readFileSync(sumsFile, 'utf8');
      const lines = data.trim().split(/\r?\n/);
      for (const line of lines) {
        const checksum = line.slice(0, 64);
        let filename = line.slice(64).trimLeft();

        if (filename.startsWith('*')) {
          filename = filename.substring(1);
        }

        if (Utils.hashFile(path.join(addonPath, filename)) !== checksum) {
          throw new Error(
            `Checksum failed in add-on ${packageId}: ${filename}`
          );
        }
      }
    } catch (e) {
      throw new Error(
        `Failed to read SHA256SUMS for add-on ${packageId}: ${e}`
      );
    }
  }

  // Verify that important fields exist in the manifest
  const err = validatePackageJson(manifest);
  if (err) {
    throw new Error(
      `Error found in manifest for add-on ${packageId}: ${err}`
    );
  }

  // Verify API version.
  if (manifest.moziot.api.min > API_VERSION ||
      manifest.moziot.api.max < API_VERSION) {
    const err = `API mismatch for add-on ${packageId}: Current: ${
      API_VERSION} Supported: ${manifest.moziot.api.min}-${
      manifest.moziot.api.max}`;
    throw new Error(err);
  }

  const obj = {
    id: manifest.name,
    name: manifest.display_name,
    description: manifest.description,
    homepage_url: manifest.homepage,
    version: manifest.version,
    primary_type: manifest.moziot.type || 'adapter',
    exec: manifest.moziot.exec,
    enabled: false,
  };

  if (typeof manifest.author === 'object') {
    obj.author = manifest.author.name;
  } else if (typeof manifest.author === 'string') {
    obj.author = manifest.author.split('<')[0].trim();
  }

  if (manifest.moziot.hasOwnProperty('schema')) {
    obj.schema = manifest.moziot.schema;
  }

  if (manifest.moziot.enabled) {
    obj.enabled = true;
  }

  return [
    obj,
    manifest.moziot.config || {},
  ];
}

/**
 * Load an add-on manifest from a manifest.json file.
 *
 * @param {string} packageId - The ID of the package, e.g. example-adapter
 *
 * @returns {object[]} 2-value array containing a parsed manifest and a default
 *                     config object.
 */
function loadManifestJson(packageId) {
  const addonPath = path.join(UserProfile.addonsDir, packageId);

  // Read the package.json file.
  let data;
  try {
    data = fs.readFileSync(path.join(addonPath, 'manifest.json'));
  } catch (e) {
    throw new Error(
      `Failed to read manifest.json for add-on ${packageId}: ${e}`
    );
  }

  // Parse as JSON
  let manifest;
  try {
    manifest = JSON.parse(data);
  } catch (e) {
    throw new Error(
      `Failed to parse manifest.json for add-on: ${packageId}: ${e}`
    );
  }

  // First, verify manifest version.
  if (manifest.manifest_version !== MANIFEST_VERSION) {
    throw new Error(
      `Manifest version ${manifest.manifest_version} for add-on ${packageId
      } does not match expected version ${MANIFEST_VERSION}`
    );
  }

  // Verify that the id in the package matches the packageId
  if (manifest.id != packageId) {
    const err = `ID from manifest .json "${manifest.id}" doesn't ` +
                `match the ID from list.json "${packageId}"`;
    throw new Error(err);
  }

  // If the add-on is not a git repository, check the SHA256SUMS file.
  if (!fs.existsSync(path.join(addonPath, '.git'))) {
    const sumsFile = path.resolve(path.join(addonPath, 'SHA256SUMS'));

    if (fs.existsSync(sumsFile)) {
      const sums = new Map();

      try {
        const data = fs.readFileSync(sumsFile, 'utf8');
        const lines = data.trim().split(/\r?\n/);
        for (const line of lines) {
          const checksum = line.slice(0, 64);
          let filename = line.slice(64).trimLeft();

          if (filename.startsWith('*')) {
            filename = filename.substring(1);
          }

          filename = path.resolve(path.join(addonPath, filename));
          if (!fs.existsSync(filename)) {
            throw new Error(
              `File ${filename} missing for add-on ${packageId}`
            );
          }

          sums.set(filename, checksum);
        }
      } catch (e) {
        throw new Error(
          `Failed to read SHA256SUMS for add-on ${packageId}: ${e}`
        );
      }

      find.fileSync(addonPath).forEach((fname) => {
        fname = path.resolve(fname);

        if (fname === sumsFile) {
          return;
        }

        if (!sums.has(fname)) {
          throw new Error(
            `No checksum found for file ${fname} in add-on ${packageId}`
          );
        }

        if (Utils.hashFile(fname) !== sums.get(fname)) {
          throw new Error(
            `Checksum failed for file ${fname} in add-on ${packageId}`
          );
        }
      });
    } else if (process.env.NODE_ENV !== 'test') {
      throw new Error(`SHA256SUMS file missing for add-on ${packageId}`);
    }
  }

  // Verify that important fields exist in the manifest
  const err = validateManifestJson(manifest);
  if (err) {
    throw new Error(
      `Error found in manifest for add-on ${packageId}: ${err}`
    );
  }

  // Verify gateway version.
  let min = manifest.gateway_specific_settings.webthings.strict_min_version;
  let max = manifest.gateway_specific_settings.webthings.strict_max_version;

  if (typeof min === 'string' && min !== '*') {
    min = semver.coerce(min);
    if (semver.lt(pkg.version, min)) {
      throw new Error(
        `Gateway version ${pkg.version} is lower than minimum version ${min
        } supported by add-on ${packageId}`
      );
    }
  }
  if (typeof max === 'string' && max !== '*') {
    max = semver.coerce(max);
    if (semver.gt(pkg.version, max)) {
      throw new Error(
        `Gateway version ${pkg.version} is higher than maximum version ${max
        } supported by add-on ${packageId}`
      );
    }
  }

  const obj = {
    id: manifest.id,
    author: manifest.author,
    name: manifest.name,
    description: manifest.description,
    homepage_url: manifest.homepage_url,
    version: manifest.version,
    primary_type: manifest.gateway_specific_settings.webthings.primary_type,
    exec: manifest.gateway_specific_settings.webthings.exec,
    enabled: false,
  };

  let cfg = {};
  if (manifest.hasOwnProperty('options')) {
    if (manifest.options.hasOwnProperty('default')) {
      cfg = manifest.options.default;
    }

    if (manifest.options.hasOwnProperty('schema')) {
      obj.schema = manifest.options.schema;
    }
  }

  if (manifest.gateway_specific_settings.webthings.enabled) {
    obj.enabled = true;
  }

  return [
    obj,
    cfg,
  ];
}

/**
 * Load the manifest for a given package.
 *
 * @param {string} packageId - The ID of the package, e.g. example-adapter
 *
 * @returns {object[]} 2-value array containing a parsed manifest and a default
 *                     config object.
 */
function loadManifest(packageId) {
  const addonPath = path.join(UserProfile.addonsDir, packageId);

  if (fs.existsSync(path.join(addonPath, 'manifest.json'))) {
    return loadManifestJson(packageId);
  }

  if (fs.existsSync(path.join(addonPath, 'package.json'))) {
    console.warn(`manifest.json did not exist for add-on ${packageId}`);
    return loadPackageJson(packageId);
  }

  throw new Error(`No manifest found for add-on ${packageId}`);
}

module.exports = {
  loadManifest,
};
<commit_msg>Loosen checksum verification enforcement. (#2156)<commit_after>/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

const config = require('config');
const find = require('find');
const fs = require('fs');
const pkg = require('../package.json');
const path = require('path');
const semver = require('semver');
const UserProfile = require('./user-profile');
const Utils = require('./utils');

const API_VERSION = 2;
const MANIFEST_VERSION = 1;

// Setting this flag will force every file present in an add-on's directory to
// have a checksum in the SHA256SUMS file. However, several add-ons currently
// write files directly into their directories. When we resolve all of those
// issues, this flag can be flipped.
const ENFORCE_STRICT_SHA_CHECK = false;

/**
 * Verify one level of an object, recursing as required.
 *
 * @param {string} prefix - Prefix to use for keys, e.g. level1.level2.
 * @param {object} object - Object to validate
 * @param {object} template - Template to validate against
 *
 * @returns {string?} Error string, or undefined if no error.
 */
function validateObject(prefix, object, template) {
  for (const key in template) {
    if (key in object) {
      const objectVal = object[key];
      const templateVal = template[key];

      if (typeof objectVal !== typeof templateVal) {
        return `Expecting ${prefix}${key} to have type: ${
          typeof templateVal}, found: ${typeof objectVal}`;
      }

      if (typeof objectVal === 'object') {
        if (Array.isArray(objectVal)) {
          if (templateVal.length > 0) {
            const expectedType = typeof templateVal[0];
            for (const val of objectVal) {
              if (typeof val !== expectedType) {
                return `Expecting all values in ${prefix}${key} to be of ` +
                  `type ${expectedType}`;
              }
            }
          }
        } else {
          const err = validateObject(
            `${prefix + key}.`,
            objectVal,
            templateVal
          );
          if (err) {
            return err;
          }
        }
      }
    } else {
      return `Manifest is missing: ${prefix}${key}`;
    }
  }
}

/**
 * Verify that a package.json looks valid. We only need to validate fields
 * which we actually use.
 *
 * @param {object} manifest - The parsed package.json manifest
 *
 * @returns {string?} Error string, or undefined if no error.
 */
function validatePackageJson(manifest) {
  const manifestTemplate = {
    name: '',
    display_name: '',
    description: '',
    version: '',
    files: [''],
    moziot: {
      api: {
        min: 0,
        max: 0,
      },
    },
  };

  if (config.get('ipc.protocol') !== 'inproc') {
    // If we're not using in-process plugins, then we also need the exec
    // keyword to exist.
    manifestTemplate.moziot.exec = '';
  }

  return validateObject('', manifest, manifestTemplate);
}

/**
 * Verify that a manifest.json looks valid. We only need to validate fields
 * which we actually use.
 *
 * @param {object} manifest - The parsed manifest.json manifest
 *
 * @returns {string?} Error string, or undefined if no error.
 */
function validateManifestJson(manifest) {
  const manifestTemplate = {
    author: '',
    description: '',
    gateway_specific_settings: {
      webthings: {
        primary_type: '',
      },
    },
    homepage_url: '',
    id: '',
    license: '',
    manifest_version: 0,
    name: '',
    version: '',
  };

  if (config.get('ipc.protocol') !== 'inproc') {
    // If we're not using in-process plugins, then we also need the exec
    // keyword to exist.
    manifestTemplate.gateway_specific_settings.webthings.exec = '';
  }

  return validateObject('', manifest, manifestTemplate);
}

/**
 * Load an add-on manifest from a legacy package.json file.
 *
 * @param {string} packageId - The ID of the package, e.g. example-adapter
 *
 * @returns {object[]} 2-value array containing a parsed manifest and a default
 *                     config object.
 */
function loadPackageJson(packageId) {
  const addonPath = path.join(UserProfile.addonsDir, packageId);

  // Read the package.json file.
  let data;
  try {
    data = fs.readFileSync(path.join(addonPath, 'package.json'));
  } catch (e) {
    throw new Error(
      `Failed to read package.json for add-on ${packageId}: ${e}`
    );
  }

  // Parse as JSON
  let manifest;
  try {
    manifest = JSON.parse(data);
  } catch (e) {
    throw new Error(
      `Failed to parse package.json for add-on: ${packageId}: ${e}`
    );
  }

  // Verify that the name in the package matches the packageId
  if (manifest.name != packageId) {
    const err = `Name from package.json "${manifest.name}" doesn't ` +
                `match the ID from list.json "${packageId}"`;
    throw new Error(err);
  }

  // Verify the files list in the package.
  if (!manifest.hasOwnProperty('files') || manifest.files.length === 0) {
    throw new Error(`files property missing for add-on ${packageId}`);
  }

  // If the add-on is a git repository, skip checking the SHA256SUMS file.
  if (fs.existsSync(path.join(addonPath, '.git'))) {
    const sha256SumsIndex = manifest.files.indexOf('SHA256SUMS');
    if (sha256SumsIndex >= 0) {
      manifest.files.splice(sha256SumsIndex, 1);
      console.log(
        `Not checking SHA256SUMS file for ${packageId} since a .git`,
        'directory was detected'
      );
    }
  }

  // Verify that all files in the files list exist.
  for (let fname of manifest.files) {
    fname = path.join(addonPath, fname);
    if (!fs.existsSync(fname)) {
      throw new Error(`Add-on ${packageId} is missing file: ${fname}`);
    }
  }

  // If a SHA256SUMS file is present, verify it. This file is of the format:
  // <checksum> <filename>
  //
  // To generate a file of this type, you can use:
  //   `rm -f SHA256SUMS && sha256sum file1 file2 ... > SHA256SUMS`
  // To verify, use:
  //   `sha256sum --check SHA256SUMS`
  if (manifest.files.includes('SHA256SUMS')) {
    const sumsFile = path.join(addonPath, 'SHA256SUMS');
    try {
      const data = fs.readFileSync(sumsFile, 'utf8');
      const lines = data.trim().split(/\r?\n/);
      for (const line of lines) {
        const checksum = line.slice(0, 64);
        let filename = line.slice(64).trimLeft();

        if (filename.startsWith('*')) {
          filename = filename.substring(1);
        }

        if (Utils.hashFile(path.join(addonPath, filename)) !== checksum) {
          throw new Error(
            `Checksum failed in add-on ${packageId}: ${filename}`
          );
        }
      }
    } catch (e) {
      throw new Error(
        `Failed to read SHA256SUMS for add-on ${packageId}: ${e}`
      );
    }
  }

  // Verify that important fields exist in the manifest
  const err = validatePackageJson(manifest);
  if (err) {
    throw new Error(
      `Error found in manifest for add-on ${packageId}: ${err}`
    );
  }

  // Verify API version.
  if (manifest.moziot.api.min > API_VERSION ||
      manifest.moziot.api.max < API_VERSION) {
    const err = `API mismatch for add-on ${packageId}: Current: ${
      API_VERSION} Supported: ${manifest.moziot.api.min}-${
      manifest.moziot.api.max}`;
    throw new Error(err);
  }

  const obj = {
    id: manifest.name,
    name: manifest.display_name,
    description: manifest.description,
    homepage_url: manifest.homepage,
    version: manifest.version,
    primary_type: manifest.moziot.type || 'adapter',
    exec: manifest.moziot.exec,
    enabled: false,
  };

  if (typeof manifest.author === 'object') {
    obj.author = manifest.author.name;
  } else if (typeof manifest.author === 'string') {
    obj.author = manifest.author.split('<')[0].trim();
  }

  if (manifest.moziot.hasOwnProperty('schema')) {
    obj.schema = manifest.moziot.schema;
  }

  if (manifest.moziot.enabled) {
    obj.enabled = true;
  }

  return [
    obj,
    manifest.moziot.config || {},
  ];
}

/**
 * Load an add-on manifest from a manifest.json file.
 *
 * @param {string} packageId - The ID of the package, e.g. example-adapter
 *
 * @returns {object[]} 2-value array containing a parsed manifest and a default
 *                     config object.
 */
function loadManifestJson(packageId) {
  const addonPath = path.join(UserProfile.addonsDir, packageId);

  // Read the package.json file.
  let data;
  try {
    data = fs.readFileSync(path.join(addonPath, 'manifest.json'));
  } catch (e) {
    throw new Error(
      `Failed to read manifest.json for add-on ${packageId}: ${e}`
    );
  }

  // Parse as JSON
  let manifest;
  try {
    manifest = JSON.parse(data);
  } catch (e) {
    throw new Error(
      `Failed to parse manifest.json for add-on: ${packageId}: ${e}`
    );
  }

  // First, verify manifest version.
  if (manifest.manifest_version !== MANIFEST_VERSION) {
    throw new Error(
      `Manifest version ${manifest.manifest_version} for add-on ${packageId
      } does not match expected version ${MANIFEST_VERSION}`
    );
  }

  // Verify that the id in the package matches the packageId
  if (manifest.id != packageId) {
    const err = `ID from manifest .json "${manifest.id}" doesn't ` +
                `match the ID from list.json "${packageId}"`;
    throw new Error(err);
  }

  // If the add-on is not a git repository, check the SHA256SUMS file.
  if (!fs.existsSync(path.join(addonPath, '.git'))) {
    const sumsFile = path.resolve(path.join(addonPath, 'SHA256SUMS'));

    if (fs.existsSync(sumsFile)) {
      const sums = new Map();

      try {
        const data = fs.readFileSync(sumsFile, 'utf8');
        const lines = data.trim().split(/\r?\n/);
        for (const line of lines) {
          const checksum = line.slice(0, 64);
          let filename = line.slice(64).trimLeft();

          if (filename.startsWith('*')) {
            filename = filename.substring(1);
          }

          filename = path.resolve(path.join(addonPath, filename));
          if (!fs.existsSync(filename)) {
            throw new Error(
              `File ${filename} missing for add-on ${packageId}`
            );
          }

          sums.set(filename, checksum);
        }
      } catch (e) {
        throw new Error(
          `Failed to read SHA256SUMS for add-on ${packageId}: ${e}`
        );
      }

      find.fileSync(addonPath).forEach((fname) => {
        fname = path.resolve(fname);

        if (fname === sumsFile) {
          return;
        }

        if (!sums.has(fname)) {
          if (ENFORCE_STRICT_SHA_CHECK) {
            throw new Error(
              `No checksum found for file ${fname} in add-on ${packageId}`
            );
          } else {
            return;
          }
        }

        if (Utils.hashFile(fname) !== sums.get(fname)) {
          throw new Error(
            `Checksum failed for file ${fname} in add-on ${packageId}`
          );
        }
      });
    } else if (process.env.NODE_ENV !== 'test') {
      throw new Error(`SHA256SUMS file missing for add-on ${packageId}`);
    }
  }

  // Verify that important fields exist in the manifest
  const err = validateManifestJson(manifest);
  if (err) {
    throw new Error(
      `Error found in manifest for add-on ${packageId}: ${err}`
    );
  }

  // Verify gateway version.
  let min = manifest.gateway_specific_settings.webthings.strict_min_version;
  let max = manifest.gateway_specific_settings.webthings.strict_max_version;

  if (typeof min === 'string' && min !== '*') {
    min = semver.coerce(min);
    if (semver.lt(pkg.version, min)) {
      throw new Error(
        `Gateway version ${pkg.version} is lower than minimum version ${min
        } supported by add-on ${packageId}`
      );
    }
  }
  if (typeof max === 'string' && max !== '*') {
    max = semver.coerce(max);
    if (semver.gt(pkg.version, max)) {
      throw new Error(
        `Gateway version ${pkg.version} is higher than maximum version ${max
        } supported by add-on ${packageId}`
      );
    }
  }

  const obj = {
    id: manifest.id,
    author: manifest.author,
    name: manifest.name,
    description: manifest.description,
    homepage_url: manifest.homepage_url,
    version: manifest.version,
    primary_type: manifest.gateway_specific_settings.webthings.primary_type,
    exec: manifest.gateway_specific_settings.webthings.exec,
    enabled: false,
  };

  let cfg = {};
  if (manifest.hasOwnProperty('options')) {
    if (manifest.options.hasOwnProperty('default')) {
      cfg = manifest.options.default;
    }

    if (manifest.options.hasOwnProperty('schema')) {
      obj.schema = manifest.options.schema;
    }
  }

  if (manifest.gateway_specific_settings.webthings.enabled) {
    obj.enabled = true;
  }

  return [
    obj,
    cfg,
  ];
}

/**
 * Load the manifest for a given package.
 *
 * @param {string} packageId - The ID of the package, e.g. example-adapter
 *
 * @returns {object[]} 2-value array containing a parsed manifest and a default
 *                     config object.
 */
function loadManifest(packageId) {
  const addonPath = path.join(UserProfile.addonsDir, packageId);

  if (fs.existsSync(path.join(addonPath, 'manifest.json'))) {
    return loadManifestJson(packageId);
  }

  if (fs.existsSync(path.join(addonPath, 'package.json'))) {
    console.warn(`manifest.json did not exist for add-on ${packageId}`);
    return loadPackageJson(packageId);
  }

  throw new Error(`No manifest found for add-on ${packageId}`);
}

module.exports = {
  loadManifest,
};
<|endoftext|>


<commit_before>// # Ghost Configuration
// Setup your Ghost install for various [environments](http://support.ghost.org/config/#about-environments).

// Ghost runs in `development` mode by default. Full documentation can be found at http://support.ghost.org/config/

var path = require('path'),
    config;

//Function for setting default ENV variables.
function CheckEnvVar(varname, defaultvalue)
{
    var result = process.env[varname];
    if(result!=undefined)
        return result;
    else
        return defaultvalue;
}

var devDomain = CheckEnvVar('DEV_DOMAIN', 'http://localhost:2368');
var prodDomain = CheckEnvVar('PROD_DOMAIN', 'http://example.com');

config = {
    // ### Production
    // When running Ghost in the wild, use the production environment.
    // Configure your URL and mail settings here
    production: {
        url: prodDomain,
        mail: {},
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(process.env.GHOST_CONTENT, '/data/ghost.db')
            },
            debug: false
        },

        server: {
            host: '0.0.0.0',
            port: '2368'
        }
        // #### Paths
        // Specify where your content directory lives
        paths: {
            contentPath: path.join(process.env.GHOST_CONTENT, '/')
        }
    },

    // ### Development **(default)**
    development: {
        // The url to use when providing links to the site, E.g. in RSS and email.
        // Change this to your Ghost blog's published URL.
        url: devDomain,

        // Example mail config
        // Visit http://support.ghost.org/mail for instructions
        // ```
        //  mail: {
        //      transport: 'SMTP',
        //      options: {
        //          service: 'Mailgun',
        //          auth: {
        //              user: '', // mailgun username
        //              pass: ''  // mailgun password
        //          }
        //      }
        //  },
        // ```

        // #### Database
        // Ghost supports sqlite3 (default), MySQL & PostgreSQL
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(process.env.GHOST_CONTENT, '/data/ghost-dev.db')
            },
            debug: false
        },
        // #### Server
        // Can be host & port (default), or socket
        server: {
            // Host to be passed to node's `net.Server#listen()`
            host: '0.0.0.0',
            // Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
            port: '2368'
        },
        // #### Paths
        // Specify where your content directory lives
        paths: {
            contentPath: path.join(process.env.GHOST_CONTENT, '/')
        }
    },

    // **Developers only need to edit below here**

    // ### Testing
    // Used when developing Ghost to run tests and check the health of Ghost
    // Uses a different port number
    testing: {
        url: 'http://0.0.0.0:2369',
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(process.env.GHOST_CONTENT, '/data/ghost-test.db')
            },
            pool: {
                afterCreate: function (conn, done) {
                    conn.run('PRAGMA synchronous=OFF;' +
                    'PRAGMA journal_mode=MEMORY;' +
                    'PRAGMA locking_mode=EXCLUSIVE;' +
                    'BEGIN EXCLUSIVE; COMMIT;', done);
                }
            }
        },
        server: {
            host: '0.0.0.0',
            port: '2369'
        },
        logging: false
    },

    // ### Testing MySQL
    // Used by Travis - Automated testing run through GitHub
    'testing-mysql': {
        url: 'http://0.0.0.0:2369',
        database: {
            client: 'mysql',
            connection: {
                host     : '0.0.0.0',
                user     : 'root',
                password : '',
                database : 'ghost_testing',
                charset  : 'utf8'
            }
        },
        server: {
            host: '0.0.0.0',
            port: '2369'
        },
        logging: false
    },

    // ### Testing pg
    // Used by Travis - Automated testing run through GitHub
    'testing-pg': {
        url: 'http://0.0.0.0:2369',
        database: {
            client: 'pg',
            connection: {
                host     : '0.0.0.0',
                user     : 'postgres',
                password : '',
                database : 'ghost_testing',
                charset  : 'utf8'
            }
        },
        server: {
            host: '0.0.0.0',
            port: '2369'
        },
        logging: false
    }
};

module.exports = config;
<commit_msg>fixed typo in config.js<commit_after>// # Ghost Configuration
// Setup your Ghost install for various [environments](http://support.ghost.org/config/#about-environments).

// Ghost runs in `development` mode by default. Full documentation can be found at http://support.ghost.org/config/

var path = require('path'),
    config;

//Function for setting default ENV variables.
function CheckEnvVar(varname, defaultvalue)
{
    var result = process.env[varname];
    if(result!=undefined)
        return result;
    else
        return defaultvalue;
}

var devDomain = CheckEnvVar('DEV_DOMAIN', 'http://localhost:2368');
var prodDomain = CheckEnvVar('PROD_DOMAIN', 'http://example.com');

config = {
    // ### Production
    // When running Ghost in the wild, use the production environment.
    // Configure your URL and mail settings here
    production: {
        url: prodDomain,
        mail: {},
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(process.env.GHOST_CONTENT, '/data/ghost.db')
            },
            debug: false
        },

        server: {
            host: '0.0.0.0',
            port: '2368'
        },
        // #### Paths
        // Specify where your content directory lives
        paths: {
            contentPath: path.join(process.env.GHOST_CONTENT, '/')
        }
    },

    // ### Development **(default)**
    development: {
        // The url to use when providing links to the site, E.g. in RSS and email.
        // Change this to your Ghost blog's published URL.
        url: devDomain,

        // Example mail config
        // Visit http://support.ghost.org/mail for instructions
        // ```
        //  mail: {
        //      transport: 'SMTP',
        //      options: {
        //          service: 'Mailgun',
        //          auth: {
        //              user: '', // mailgun username
        //              pass: ''  // mailgun password
        //          }
        //      }
        //  },
        // ```

        // #### Database
        // Ghost supports sqlite3 (default), MySQL & PostgreSQL
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(process.env.GHOST_CONTENT, '/data/ghost-dev.db')
            },
            debug: false
        },
        // #### Server
        // Can be host & port (default), or socket
        server: {
            // Host to be passed to node's `net.Server#listen()`
            host: '0.0.0.0',
            // Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
            port: '2368'
        },
        // #### Paths
        // Specify where your content directory lives
        paths: {
            contentPath: path.join(process.env.GHOST_CONTENT, '/')
        }
    },

    // **Developers only need to edit below here**

    // ### Testing
    // Used when developing Ghost to run tests and check the health of Ghost
    // Uses a different port number
    testing: {
        url: 'http://0.0.0.0:2369',
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(process.env.GHOST_CONTENT, '/data/ghost-test.db')
            },
            pool: {
                afterCreate: function (conn, done) {
                    conn.run('PRAGMA synchronous=OFF;' +
                    'PRAGMA journal_mode=MEMORY;' +
                    'PRAGMA locking_mode=EXCLUSIVE;' +
                    'BEGIN EXCLUSIVE; COMMIT;', done);
                }
            }
        },
        server: {
            host: '0.0.0.0',
            port: '2369'
        },
        logging: false
    },

    // ### Testing MySQL
    // Used by Travis - Automated testing run through GitHub
    'testing-mysql': {
        url: 'http://0.0.0.0:2369',
        database: {
            client: 'mysql',
            connection: {
                host     : '0.0.0.0',
                user     : 'root',
                password : '',
                database : 'ghost_testing',
                charset  : 'utf8'
            }
        },
        server: {
            host: '0.0.0.0',
            port: '2369'
        },
        logging: false
    },

    // ### Testing pg
    // Used by Travis - Automated testing run through GitHub
    'testing-pg': {
        url: 'http://0.0.0.0:2369',
        database: {
            client: 'pg',
            connection: {
                host     : '0.0.0.0',
                user     : 'postgres',
                password : '',
                database : 'ghost_testing',
                charset  : 'utf8'
            }
        },
        server: {
            host: '0.0.0.0',
            port: '2369'
        },
        logging: false
    }
};

module.exports = config;
<|endoftext|>


<commit_before>var mongoose = require('mongoose');

var elementSchema = mongoose.Schema({
    name: {type: String, default: ''},
    modifiedAt: {type: Date}
});

elementSchema.pre('save', function(next){
    this.modifiedAt = new Date();
    next();
});

module.exports = mongoose.model('Element', elementSchema);<commit_msg>removed unused hook<commit_after>var mongoose = require('mongoose');

var elementSchema = mongoose.Schema({
    name: {type: String, default: ''},
    modifiedAt: {type: Date}
});

module.exports = mongoose.model('Element', elementSchema);<|endoftext|>


<commit_before>/* global exports, process, require */
/* jshint unused: false */

var config = exports
  , system = null
  , env    = null
  , sugar  = null;

try {
    system = require('system');
    env = system.env;
} catch (ex) {
    env = process.env;
    sugar = require('sugar');
}

function getEnv(value, defaultValue) {
    return (typeof value !== 'undefined' && value !== '') ? value : defaultValue;
}

// GOD
config.GOD_ADDRESS = getEnv(env.GOD_ADDRESS, "5.101.106.121");
config.GOD_PORT = getEnv(env.GOD_PORT, 7331);
config.GOD_BACK_CHANNEL_PORT = getEnv(env.GOD_BACK_CHANNEL_PORT, 3000);

// SERAPH TODO its seraph singular and seraphim plural !
config.SERAPH_ADDRESS = getEnv(env.SERAPH_ADDRESS, "127.0.0.1");
config.SERAPH_PORT = getEnv(env.SERAPH_PORT, 8421);
config.SERAPH_CONFIG_PATH = getEnv(env.SERAPH_CONFIG_PATH, '/tmp/seraphConfig.json');
config.ANGEL_START_PORT = getEnv(env.ANGEL_START_PORT, 55550);
config.ANGEL_PORT_COUNT = getEnv(env.ANGEL_PORT_COUNT, 5);
try {
    config.ANGEL_PORTS = Number.range(config.ANGEL_START_PORT, config.ANGEL_START_PORT + config.ANGEL_PORT_COUNT - 1).every();
} catch (ex) {
}<commit_msg>Don't use deployment constants<commit_after>/* global exports, process, require */
/* jshint unused: false */

var config = exports
  , system = null
  , env    = null
  , sugar  = null;

try {
    system = require('system');
    env = system.env;
} catch (ex) {
    env = process.env;
    sugar = require('sugar');
}

function getEnv(value, defaultValue) {
    return (typeof value !== 'undefined' && value !== '') ? value : defaultValue;
}

// GOD
config.GOD_ADDRESS = getEnv(env.GOD_ADDRESS, "127.0.0.1");
config.GOD_PORT = getEnv(env.GOD_PORT, 7331);
config.GOD_BACK_CHANNEL_PORT = getEnv(env.GOD_BACK_CHANNEL_PORT, 3000);

// SERAPH TODO its seraph singular and seraphim plural !
config.SERAPH_ADDRESS = getEnv(env.SERAPH_ADDRESS, "127.0.0.1");
config.SERAPH_PORT = getEnv(env.SERAPH_PORT, 8421);
config.SERAPH_CONFIG_PATH = getEnv(env.SERAPH_CONFIG_PATH, '/tmp/seraphConfig.json');
config.ANGEL_START_PORT = getEnv(env.ANGEL_START_PORT, 55550);
config.ANGEL_PORT_COUNT = getEnv(env.ANGEL_PORT_COUNT, 5);
try {
    config.ANGEL_PORTS = Number.range(config.ANGEL_START_PORT, config.ANGEL_START_PORT + config.ANGEL_PORT_COUNT - 1).every();
} catch (ex) {
}<|endoftext|>


<commit_before>$.getJSON( "./data.json", function( data ) {
  for (var i = data.length - 1; i >= 0; i--) {
    var txtAppend = getHTMLContent(data[i].name, data[i].videoURL, data[i].platform, data[i].description, data[i].buttons);
    $("#content").append(txtAppend);
  };
});

function getHTMLContent(name, videoURL, platform, description, buttons) {
  var buttonsHTML = "";
  for (var i = buttons.length - 1; i >= 0; i--) {
    buttonsHTML += ''+buttons[i].text+' ';
  }
  return '

        <video style=" width:700px; height:300px;" src = "'+videoURL+'" controls> </video>

'+name+'

'+platform+'

'+description+'
      '+buttonsHTML+'

';
}<commit_msg>update<commit_after>var idF=0;
$.getJSON( "./data.json", function( data ) {
  for (var i = data.length - 1; i >= 0; i--) {
    var txtAppend = getHTMLContent(data[i].name, data[i].videoURL, data[i].platform, data[i].description, data[i].buttons);
    $("#content").append(txtAppend);
  };
});

function getHTMLContent(name, videoURL, platform, description, buttons) {
  idF ++;
  var buttonsHTML = "";
  for (var i = buttons.length - 1; i >= 0; i--) {
    buttonsHTML += ''+buttons[i].text+' ';
  }
  var strReturn = '

'+name+'

'+platform+'

'+description+'
      '+buttonsHTML+'

';
  strReturn += '';
  return strReturn;
}<|endoftext|>


<commit_before>'use strict';

var _                 = require('mano').i18n.bind('View: Statistics')
  , db                = require('../db')
  , uncapitalize      = require('es5-ext/string/#/uncapitalize')
  , location          = require('mano/lib/client/location')
  , queryHandlerConf  = require('../apps/statistics/flow-query-conf')
  , setupQueryHandler = require('../utils/setup-client-query-handler')
  , copy              = require('es5-ext/object/copy')
  , ObservableValue   = require('observable-value')
  , assign            = require('es5-ext/object/assign')
  , Pagination        = require('./components/pagination')
  , modes             = require('../utils/statistics-flow-group-modes')
  , selectService     = require('./components/filter-bar/select-service')
  , selectCertificate = require('./components/filter-bar/select-certificate')
  , selectPeriodMode  = require('./components/filter-bar/select-period-mode')
  , itemsPerPage      = require('../conf/objects-list-items-per-page')
  , serviceQuery      = require('../apps-common/query-conf/service')
  , certificateQuery  = require('../apps-common/query-conf/certificate')
  , stepStatusQuery   = require('../apps-common/query-conf/processing-step-status')
  , pageQuery         = require('../utils/query/date-constrained-page')
  , copyDbDate        = require('../utils/copy-db-date')
  , queryServer       = require('./utils/statistics-flow-query-server')
  , processingSteps   = require('../processing-steps-meta')
  , getDynamicUrl     = require('./utils/get-dynamic-url')
  , getStepLabelByShortPath = require('../utils/get-step-label-by-short-path')
  , incrementDateByTimeUnit = require('../utils/increment-date-by-time-unit')
  , floorToTimeUnit         = require('../utils/floor-to-time-unit')
  , calculateDurationByMode = require('../utils/calculate-duration-by-mode')
  , dateFromToBlock         = require('./components/filter-bar/select-date-range-safe-fallback')
  , reduceResult            = require('../utils/statistics-flow-reduce-processing-step')
  , filterData              = require('../utils/statistics-flow-roles-filter-result');

exports._parent        = require('./statistics-flow');
exports._customFilters = Function.prototype;

exports['flow-nav']                = { class: { 'submitted-menu-item-active': true } };
exports['flow-by-role-nav'] = { class: { 'pills-nav-active': true } };

var serviceToCertLegacyMatch = { '': [] };

var getServiceName = function (ServiceType) {
	return uncapitalize.call(
		ServiceType.__id__.slice('BusinessProcess'.length)
	);
};

db.BusinessProcess.extensions.forEach(function (ServiceType) {
	ServiceType.prototype.certificates.map.forEach(function (certificate) {
		if (!serviceToCertLegacyMatch[getServiceName(ServiceType)]) {
			serviceToCertLegacyMatch[getServiceName(ServiceType)] = [];
		}
		serviceToCertLegacyMatch[getServiceName(ServiceType)].push('certificate-' + certificate.key);
		serviceToCertLegacyMatch[''].push('certificate-' + certificate.key);
	});
});

var stepStatuses = {};
['approved', 'sentBack', 'rejected', 'paused'].forEach(function (stepName) {
	// sanity check
	if (!db.ProcessingStepStatus.members.has(stepName)) {
		return;
	}
	stepStatuses[stepName] = db.ProcessingStepStatus.meta[stepName];
});

exports['statistics-main'] = function () {
	var queryHandler, data = new ObservableValue({})
	  , pagination = new Pagination('/flow/by-role/'), handlerConf, params;

	handlerConf = queryHandlerConf.slice(0);
	handlerConf.push(pageQuery, serviceQuery, certificateQuery, stepStatusQuery);
	queryHandler = setupQueryHandler(handlerConf,
		location, '/flow/by-role/');

	params = queryHandler._handlers.map(function (handler) {
		return handler.name;
	});

	queryHandler.on('query', function (query) {
		var serverQuery = copy(query), dateFrom, dateTo, mode
		  , currentDate, offset, timeUnitsCount = 0, durationInTimeUnits, page;
		dateFrom = query.dateFrom;
		dateTo   = query.dateTo || new db.Date();
		mode     = query.mode;
		page     = query.page;

		durationInTimeUnits = calculateDurationByMode(dateFrom, dateTo, mode);
		if (query.pageCount > 1) {
			offset = { from: ((page - 1) * itemsPerPage) };
			offset.to = offset.from;
			if ((durationInTimeUnits - offset.from) < itemsPerPage) {
				offset.to += durationInTimeUnits - offset.from;
			} else {
				offset.to += itemsPerPage;
			}
			offset.to -= 1;

			currentDate = copyDbDate(dateFrom);
			floorToTimeUnit(currentDate, mode);
			while (timeUnitsCount <= offset.to) {
				if (timeUnitsCount === offset.from && query.page > 1) {
					dateFrom = copyDbDate(currentDate);
				}
				if (timeUnitsCount === offset.to && query.page < query.pageCount) {
					dateTo = incrementDateByTimeUnit(copyDbDate(currentDate), mode);
					dateTo.setUTCDate(dateTo.getUTCDate() - 1);
				}
				timeUnitsCount++;
				incrementDateByTimeUnit(currentDate, mode);
			}
		}
		serverQuery.dateFrom = dateFrom.toJSON();
		serverQuery.dateTo = dateTo.toJSON();

		delete serverQuery.service;
		delete serverQuery.page;
		delete serverQuery.certificate;
		delete serverQuery.status;
		delete serverQuery.pageCount;

		queryServer(serverQuery).done(function (responseData) {
			data.value = filterData(reduceResult(responseData),
				assign(query, { dateFrom: dateFrom, dateTo: dateTo }));
			pagination.count.value   = query.pageCount;
			pagination.current.value = query.page;
		});
	});

	div({ class: 'block-pull-up' },
		form({ action: '/flow/by-role/', autoSubmit: true },
			section({ class: 'date-period-selector-positioned-on-submenu' }, dateFromToBlock()),
			section({ class: 'section-primary users-table-filter-bar display-flex flex-wrap' },
				div(
					div({ class: 'users-table-filter-bar-status width-30' },
						selectService({ label: _("All services") })),
					div({ class: 'users-table-filter-bar-status width-30' },
						selectCertificate(),
						legacy('selectMatch', 'service-select', serviceToCertLegacyMatch)),
					div({ class: 'users-table-filter-bar-status width-30' },
						select(
							{ id: 'step-status', name: 'status' },
							list(Object.keys(stepStatuses), function (status) {
								return option({
									id: 'status-' + status,
									value: status,
									selected: location.query.get('status').map(function (value) {
										var selected = (status ? (value === status) : (value == null));
										return selected ? 'selected' : null;
									})
								}, stepStatuses[status].label);
							})
						))
				),
				selectPeriodMode(),
				div(
					div(
						a({
							class: 'users-table-filter-bar-print',
							href: getDynamicUrl('/flow-roles-data.pdf', { only: params }),
							target: '_blank'
						}, span({ class: 'fa fa-print' }), " ", _("Print pdf"))
					),
					div(
						a({
							class: 'users-table-filter-bar-print',
							href: getDynamicUrl('/flow-roles-data.csv', { only: params }),
							target: '_blank'
						}, span({ class: 'fa fa-print' }), " ", _("Print csv"))
					)
				),
				p({ class: 'submit' }, input({ type: 'submit' })))),

		section({ class: 'pad-if-pagination' }, pagination),
		br(),
			data.map(function (result) {
			var mode = modes.get(location.query.mode || 'daily');
			return div({ class: 'overflow-x-scroll width-1050px' },
					table({ class: 'submitted-user-data-table statistics-table' },
					thead(
						th({ class: 'statistics-table-number' }, mode.labelNoun),
						list(Object.keys(processingSteps), function (shortStepPath) {
							return th({ class: 'statistics-table-number' },
								getStepLabelByShortPath(shortStepPath));
						})
					),
					tbody({
						onEmpty: tr(td({ class: 'empty', colspan: Object.keys(processingSteps).length },
							_("No data for this criteria")))
					}, Object.keys(result), function (key) {
						return tr(
							td(key),
							Object.keys(result[key]).length ?
									list(Object.keys(result[key]), function (step) {
										return td({ class: 'statistics-table-number' },
											result[key][step] == null ? _("N/A") : result[key][step]);
									}) : td({
								class: 'statistics-table-info',
								colspan: Object.keys(processingSteps).length
							}, _("Nothing to report for this period"))
						);
					})));
		}));
};
<commit_msg>fix incorrect class name<commit_after>'use strict';

var _                 = require('mano').i18n.bind('View: Statistics')
  , db                = require('../db')
  , uncapitalize      = require('es5-ext/string/#/uncapitalize')
  , location          = require('mano/lib/client/location')
  , queryHandlerConf  = require('../apps/statistics/flow-query-conf')
  , setupQueryHandler = require('../utils/setup-client-query-handler')
  , copy              = require('es5-ext/object/copy')
  , ObservableValue   = require('observable-value')
  , assign            = require('es5-ext/object/assign')
  , Pagination        = require('./components/pagination')
  , modes             = require('../utils/statistics-flow-group-modes')
  , selectService     = require('./components/filter-bar/select-service')
  , selectCertificate = require('./components/filter-bar/select-certificate')
  , selectPeriodMode  = require('./components/filter-bar/select-period-mode')
  , itemsPerPage      = require('../conf/objects-list-items-per-page')
  , serviceQuery      = require('../apps-common/query-conf/service')
  , certificateQuery  = require('../apps-common/query-conf/certificate')
  , stepStatusQuery   = require('../apps-common/query-conf/processing-step-status')
  , pageQuery         = require('../utils/query/date-constrained-page')
  , copyDbDate        = require('../utils/copy-db-date')
  , queryServer       = require('./utils/statistics-flow-query-server')
  , processingSteps   = require('../processing-steps-meta')
  , getDynamicUrl     = require('./utils/get-dynamic-url')
  , getStepLabelByShortPath = require('../utils/get-step-label-by-short-path')
  , incrementDateByTimeUnit = require('../utils/increment-date-by-time-unit')
  , floorToTimeUnit         = require('../utils/floor-to-time-unit')
  , calculateDurationByMode = require('../utils/calculate-duration-by-mode')
  , dateFromToBlock         = require('./components/filter-bar/select-date-range-safe-fallback')
  , reduceResult            = require('../utils/statistics-flow-reduce-processing-step')
  , filterData              = require('../utils/statistics-flow-roles-filter-result');

exports._parent        = require('./statistics-flow');
exports._customFilters = Function.prototype;

exports['flow-nav']                = { class: { 'submitted-menu-item-active': true } };
exports['flow-by-role-nav'] = { class: { 'pills-nav-active': true } };

var serviceToCertLegacyMatch = { '': [] };

var getServiceName = function (ServiceType) {
	return uncapitalize.call(
		ServiceType.__id__.slice('BusinessProcess'.length)
	);
};

db.BusinessProcess.extensions.forEach(function (ServiceType) {
	ServiceType.prototype.certificates.map.forEach(function (certificate) {
		if (!serviceToCertLegacyMatch[getServiceName(ServiceType)]) {
			serviceToCertLegacyMatch[getServiceName(ServiceType)] = [];
		}
		serviceToCertLegacyMatch[getServiceName(ServiceType)].push('certificate-' + certificate.key);
		serviceToCertLegacyMatch[''].push('certificate-' + certificate.key);
	});
});

var stepStatuses = {};
['approved', 'sentBack', 'rejected', 'paused'].forEach(function (stepName) {
	// sanity check
	if (!db.ProcessingStepStatus.members.has(stepName)) {
		return;
	}
	stepStatuses[stepName] = db.ProcessingStepStatus.meta[stepName];
});

exports['statistics-main'] = function () {
	var queryHandler, data = new ObservableValue({})
	  , pagination = new Pagination('/flow/by-role/'), handlerConf, params;

	handlerConf = queryHandlerConf.slice(0);
	handlerConf.push(pageQuery, serviceQuery, certificateQuery, stepStatusQuery);
	queryHandler = setupQueryHandler(handlerConf,
		location, '/flow/by-role/');

	params = queryHandler._handlers.map(function (handler) {
		return handler.name;
	});

	queryHandler.on('query', function (query) {
		var serverQuery = copy(query), dateFrom, dateTo, mode
		  , currentDate, offset, timeUnitsCount = 0, durationInTimeUnits, page;
		dateFrom = query.dateFrom;
		dateTo   = query.dateTo || new db.Date();
		mode     = query.mode;
		page     = query.page;

		durationInTimeUnits = calculateDurationByMode(dateFrom, dateTo, mode);
		if (query.pageCount > 1) {
			offset = { from: ((page - 1) * itemsPerPage) };
			offset.to = offset.from;
			if ((durationInTimeUnits - offset.from) < itemsPerPage) {
				offset.to += durationInTimeUnits - offset.from;
			} else {
				offset.to += itemsPerPage;
			}
			offset.to -= 1;

			currentDate = copyDbDate(dateFrom);
			floorToTimeUnit(currentDate, mode);
			while (timeUnitsCount <= offset.to) {
				if (timeUnitsCount === offset.from && query.page > 1) {
					dateFrom = copyDbDate(currentDate);
				}
				if (timeUnitsCount === offset.to && query.page < query.pageCount) {
					dateTo = incrementDateByTimeUnit(copyDbDate(currentDate), mode);
					dateTo.setUTCDate(dateTo.getUTCDate() - 1);
				}
				timeUnitsCount++;
				incrementDateByTimeUnit(currentDate, mode);
			}
		}
		serverQuery.dateFrom = dateFrom.toJSON();
		serverQuery.dateTo = dateTo.toJSON();

		delete serverQuery.service;
		delete serverQuery.page;
		delete serverQuery.certificate;
		delete serverQuery.status;
		delete serverQuery.pageCount;

		queryServer(serverQuery).done(function (responseData) {
			data.value = filterData(reduceResult(responseData),
				assign(query, { dateFrom: dateFrom, dateTo: dateTo }));
			pagination.count.value   = query.pageCount;
			pagination.current.value = query.page;
		});
	});

	div({ class: 'block-pull-up' },
		form({ action: '/flow/by-role/', autoSubmit: true },
			section({ class: 'date-period-selector-positioned-on-submenu' }, dateFromToBlock()),
			section({ class: 'section-primary users-table-filter-bar display-flex flex-wrap' },
				div(
					div({ class: 'users-table-filter-bar-status width-30' },
						selectService({ label: _("All services") })),
					div({ class: 'users-table-filter-bar-status width-30' },
						selectCertificate(),
						legacy('selectMatch', 'service-select', serviceToCertLegacyMatch)),
					div({ class: 'users-table-filter-bar-status width-30' },
						select(
							{ id: 'step-status', name: 'status' },
							list(Object.keys(stepStatuses), function (status) {
								return option({
									id: 'status-' + status,
									value: status,
									selected: location.query.get('status').map(function (value) {
										var selected = (status ? (value === status) : (value == null));
										return selected ? 'selected' : null;
									})
								}, stepStatuses[status].label);
							})
						))
				),
				selectPeriodMode(),
				div(
					div(
						a({
							class: 'users-table-filter-bar-print',
							href: getDynamicUrl('/flow-roles-data.pdf', { only: params }),
							target: '_blank'
						}, span({ class: 'fa fa-print' }), " ", _("Print pdf"))
					),
					div(
						a({
							class: 'users-table-filter-bar-print',
							href: getDynamicUrl('/flow-roles-data.csv', { only: params }),
							target: '_blank'
						}, span({ class: 'fa fa-print' }), " ", _("Print csv"))
					)
				),
				p({ class: 'submit' }, input({ type: 'submit' })))),

		section({ class: 'pad-if-pagination' }, pagination),
		br(),
			data.map(function (result) {
			var mode = modes.get(location.query.mode || 'daily');
			return div({ class: 'overflow-x width-1050px' },
					table({ class: 'submitted-user-data-table statistics-table' },
					thead(
						th({ class: 'statistics-table-number' }, mode.labelNoun),
						list(Object.keys(processingSteps), function (shortStepPath) {
							return th({ class: 'statistics-table-number' },
								getStepLabelByShortPath(shortStepPath));
						})
					),
					tbody({
						onEmpty: tr(td({ class: 'empty', colspan: Object.keys(processingSteps).length },
							_("No data for this criteria")))
					}, Object.keys(result), function (key) {
						return tr(
							td(key),
							Object.keys(result[key]).length ?
									list(Object.keys(result[key]), function (step) {
										return td({ class: 'statistics-table-number' },
											result[key][step] == null ? _("N/A") : result[key][step]);
									}) : td({
								class: 'statistics-table-info',
								colspan: Object.keys(processingSteps).length
							}, _("Nothing to report for this period"))
						);
					})));
		}));
};
<|endoftext|>


<commit_before>const FooterPun = require("./models/footerPun");
const BreakInfo = require("./models/breakInfo");

module.exports = {
    settings: {
        useRandomFooterPuns: true,
        defaultFooterPun: "heart",
        winterTheme: false,
        nodePort: 3000,
    },
    onBreak: {
        //The dates are only rendered, never used for checking if closed!
        // mensa: new BreakInfo("Sommerpause", "Die Mensa macht Pause.", "31.7", "3.9", "fa-sun-o")
    },
    cache: {
        // Time in miliseconds -> 20 min.
        intervall: 1200000,
        overallVisitorKey: "overallVisitors",
        dailyVisitorKey: "dailyVisitors"
    },
    scraper: {
        mensaUrl: "http://menu.mensen.at/index/index/locid/45",
        uniwirtUrl: "http://www.uniwirt.at",
        mittagstischUrl: "http://www.lakeside-scitec.com/services/gastronomie/mittagstisch/",
        unipizzeriaUrl: "http://www.uni-pizzeria.at/speisen/mittagsteller.html",
        lapastaUrl: "https://lapasta.at/wochenmenue.html",
        princsUrl: "http://www.princs.com/"
    },
    userFriendlyUrls: {
        //Usually the same as the scraper ones
        mensa: "http://menu.mensen.at/index/index/locid/45",
        uniwirt: "http://www.uniwirt.at/wp/home/mittag/",
        mittagstisch: "http://www.lakeside-scitec.com/services/gastronomie/mittagstisch/",
        "uni-pizzeria": "http://www.uni-pizzeria.at/speisen/mittagsteller.html",
        "la-pasta": "https://lapasta.at/wochenmenue.html",
        princs: "http://www.princs.com/"
    },
    cookie: {
        // 30 days in milliseconds
        maxAge: 2592000000
    },
    externalApis: {
        paramKey: "${param}",
        numbersApi: "http://numbersapi.com/${param}",
        catFactsApi: "https://catfact.ninja/fact",
        placeKittenApi: "https://placekitten.com/${param}/200",
        placeKittenWidth: 700,
        placeKittenWidthSpan: 200
    },
    footerPuns: [
        new FooterPun("heart", "Crafted with", "fa-heart"),
        new FooterPun("empire", "Constructed for the", "fa-empire", "http://starwars.wikia.com/wiki/Galactic_Empire"),
        new FooterPun("rebellion", "Join the", "fa-rebel", "http://starwars.wikia.com/wiki/Alliance_to_Restore_the_Republic"),
        new FooterPun("lizardpaper", ' eats', "fa-hand-paper-o"),
        new FooterPun("print", "Now also printable!", "fa-print", "/print"),
        new FooterPun("got", "Winter is coming", "fa-snowflake-o", "http://gameofthrones.wikia.com/wiki/House_Stark"),
        /*new FooterPun("christmas", "Frohe Weihnachten!", "fa-snowflake-o"),
         new FooterPun("star", "Hey look!", "fa-star fa-spin", null, " It's spinning!"),
         new FooterPun("keyboard", "Use your ", "fa-keyboard-o", null, " arrows on desktop!"),
         new FooterPun("infinity", "To infinity ", "fa-space-shuttle", null, " and beyond!")*/
    ],
    snowFall: {
        particles: 50
    }
};
<commit_msg>mensa: sommerpause<commit_after>const FooterPun = require("./models/footerPun");
const BreakInfo = require("./models/breakInfo");

module.exports = {
    settings: {
        useRandomFooterPuns: true,
        defaultFooterPun: "heart",
        winterTheme: false,
        nodePort: 3000,
    },
    onBreak: {
        //The dates are only rendered, never used for checking if closed!
        mensa: new BreakInfo("Sommerpause", "Die Mensa macht Pause.", "28.7", "2.9", "fa-sun-o")
    },
    cache: {
        // Time in miliseconds -> 20 min.
        intervall: 1200000,
        overallVisitorKey: "overallVisitors",
        dailyVisitorKey: "dailyVisitors"
    },
    scraper: {
        mensaUrl: "http://menu.mensen.at/index/index/locid/45",
        uniwirtUrl: "http://www.uniwirt.at",
        mittagstischUrl: "http://www.lakeside-scitec.com/services/gastronomie/mittagstisch/",
        unipizzeriaUrl: "http://www.uni-pizzeria.at/speisen/mittagsteller.html",
        lapastaUrl: "https://lapasta.at/wochenmenue.html",
        princsUrl: "http://www.princs.com/"
    },
    userFriendlyUrls: {
        //Usually the same as the scraper ones
        mensa: "http://menu.mensen.at/index/index/locid/45",
        uniwirt: "http://www.uniwirt.at/wp/home/mittag/",
        mittagstisch: "http://www.lakeside-scitec.com/services/gastronomie/mittagstisch/",
        "uni-pizzeria": "http://www.uni-pizzeria.at/speisen/mittagsteller.html",
        "la-pasta": "https://lapasta.at/wochenmenue.html",
        princs: "http://www.princs.com/"
    },
    cookie: {
        // 30 days in milliseconds
        maxAge: 2592000000
    },
    externalApis: {
        paramKey: "${param}",
        numbersApi: "http://numbersapi.com/${param}",
        catFactsApi: "https://catfact.ninja/fact",
        placeKittenApi: "https://placekitten.com/${param}/200",
        placeKittenWidth: 700,
        placeKittenWidthSpan: 200
    },
    footerPuns: [
        new FooterPun("heart", "Crafted with", "fa-heart"),
        new FooterPun("empire", "Constructed for the", "fa-empire", "http://starwars.wikia.com/wiki/Galactic_Empire"),
        new FooterPun("rebellion", "Join the", "fa-rebel", "http://starwars.wikia.com/wiki/Alliance_to_Restore_the_Republic"),
        new FooterPun("lizardpaper", ' eats', "fa-hand-paper-o"),
        new FooterPun("print", "Now also printable!", "fa-print", "/print"),
        new FooterPun("got", "Winter is coming", "fa-snowflake-o", "http://gameofthrones.wikia.com/wiki/House_Stark"),
        /*new FooterPun("christmas", "Frohe Weihnachten!", "fa-snowflake-o"),
         new FooterPun("star", "Hey look!", "fa-star fa-spin", null, " It's spinning!"),
         new FooterPun("keyboard", "Use your ", "fa-keyboard-o", null, " arrows on desktop!"),
         new FooterPun("infinity", "To infinity ", "fa-space-shuttle", null, " and beyond!")*/
    ],
    snowFall: {
        particles: 50
    }
};
<|endoftext|>


<commit_before>'use strict';

/**
 * The default settings
 * @private
 * @type {Object}
 */
const defaultConfig = {
	homePlugin: 'Overview',
	siadAddress: 'http://localhost:9980',
	width: 800,
	height: 600,
	xPosition: 0,
	yPosition: 0
};

/**
 * The config object derived from the config.json file used to store UI settings
 * @typedef {Object} config
 * @property {string} homePlugin - The name of the default plugin, usually 'Overview'
 * @property {string} siadAddress - Usually 'http://localhost:9980'
 * @property {string} siadCommand - The command to run siad
 * @property {number} zoom - The zoom factor for the UI
 */

/**
 * Holds all config.json related logic for the UI
 * @module UIConfig
 */
module.exports = {
	/**
	 * Writes the current config to defaultConfigPath
	 * @param {config} config - config in memory
	 * @param {string} path - UI's defaultConfigPath
	 */
	save: function(config, path) {
		if (config !== undefined) {
			Fs.writeFile(path, JSON.stringify(config, null, '\t'), function(err) {
				if (err) {
					window.alert(err);
				}
			});
		}
	},

	/**
	 * Finds if a config file exists and uses default if not
	 * @param {string} path - UI's defaultConfigPath
	 * @param {callback} callback
	 */
	load: function(path, callback) {
		Fs.readFile(path, function(err, data) {
			if (err || data === 'undefined') {
				// no file found, use default config
				callback(defaultConfig);
			} else {
				// found config, use it
				callback(JSON.parse(data));
			}
		});
	},

	/**
	 * returns the defaultConfig
	 * @param {callback} callback
	 */
	reset: function(callback) {
		callback(defaultConfig);
	},
};
<commit_msg>Can load json files with require<commit_after>'use strict';

/**
 * The default settings
 * @private
 * @type {Object}
 */
const defaultConfig = {
	homePlugin: 'Overview',
	siadAddress: 'http://localhost:9980',
	width: 800,
	height: 600,
	xPosition: 0,
	yPosition: 0
};

/**
 * The config object derived from the config.json file used to store UI settings
 * @typedef {Object} config
 * @property {string} homePlugin - The name of the default plugin, usually 'Overview'
 * @property {string} siadAddress - Usually 'http://localhost:9980'
 * @property {string} siadCommand - The command to run siad
 * @property {number} zoom - The zoom factor for the UI
 */

/**
 * Holds all config.json related logic for the UI
 * @module UIConfig
 */
module.exports = {
	/**
	 * Writes the current config to defaultConfigPath
	 * @param {config} config - config in memory
	 * @param {string} path - UI's defaultConfigPath
	 */
	save: function(config, path) {
		if (config !== undefined) {
			Fs.writeFile(path, JSON.stringify(config, null, '\t'), function(err) {
				if (err) {
					window.alert(err);
				}
			});
		}
	},

	/**
	 * Finds if a config file exists and uses default if not
	 * @param {string} path - UI's defaultConfigPath
	 * @param {callback} callback
	 */
	load: function(path, callback) {
		try {
			callback(require(path));
		} catch (err) {
			callback(defaultConfig);
		}
	},

	/**
	 * returns the defaultConfig
	 * @param {callback} callback
	 */
	reset: function(callback) {
		callback(defaultConfig);
	},
};
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs'),
    yaml = require('js-yaml');

function readFile(path, callback) {
    if (!path || typeof path !== 'string'){
        return callback(new Error('Path must be a string'));
    }
    fs.readFile(path, (err, data) => {
        if (err) {
            return callback(err);
        }
        if (data.length >= 3 && data[0] === 0xef &&
            data[1] === 0xbb && data[2] === 0xbf) {
            data = data.slice(3);
        }
        try {
            callback(null, yaml.safeLoad(data));
        } catch (e) {
            callback(e);
        }
    });
}

/* istanbul ignore else */
if (typeof GLOBAL.describe === 'function') {
    exports.internals = {
        readFile: readFile
    };
}
<commit_msg>add initial documentation to config functions<commit_after>'use strict';

const fs = require('fs'),
    yaml = require('js-yaml');

/**
 * Read and parse configuration File from disc
 *
 * @param {string} path File Path of file to read
 * @param {readComplete} callback Completion callback
 */
function readFile(path, callback) {
    if (!path || typeof path !== 'string'){
        return callback(new Error('Path must be a string'));
    }
    fs.readFile(path, (err, data) => {
        if (err) {
            return callback(err);
        }
        // Remove UTF-8 BOM iof present
        if (data.length >= 3 && data[0] === 0xef &&
            data[1] === 0xbb && data[2] === 0xbf) {
            data = data.slice(3);
        }
        try {
            callback(null, yaml.safeLoad(data));
        } catch (e) {
            callback(e);
        }
    });
}

/**
 * Read File Callback
 *
 * @param {Exception} [err=null] Error encountered processing request
 * @param {Object} body YAML parsed response body. If invalid YAML will be `undefined`
 */
function readComplete(err, config) {} //eslint-disable-line handle-callback-err, no-unused-vars

/* istanbul ignore else */
if (typeof GLOBAL.describe === 'function') {
    exports.internals = {
        readFile: readFile
    };
    exports.stubs = {

    }
}
<|endoftext|>


<commit_before>/* eslint-env jquery */

/*eslint-disable no-unused-vars*/
var addHeadingAnchors = {
  /*eslint-enable no-unused-vars*/
  TOOLTIP_TTL: 500,

  init: function (selector, popoverContainer) {
    this.popoverContainer = popoverContainer || "body";
    this.target = document.querySelector(selector);
    if (this.target) {
      this.addAnchorsToHeadings();
      this.registerClipboardHandlers();
      this.registerDismissPopoverHandler();
      this.registerJumpPreventer();
    }
  },

  addAnchorsToHeadings: function () {
    var selectorString = "h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]";
    var headings = this.target.querySelectorAll(selectorString);

    Array.prototype.forEach.call(headings, function (heading) {
      var id = heading.id;
      // only add if no existing anchor
      if (!heading.querySelector("a.headerLink")) {
        var anchor = this.createAnchor(id);
        heading.appendChild(anchor);
        this.createPopover(anchor, id);
        this.createSuccessTooltip(anchor);
      }
    }.bind(this));
  },

  createAnchor: function (id) {
    var anchor = document.createElement("a");
    var icon = document.createElement("i");
    var attributes = {
      href: "#" + id,
      class: "headerLink"
    };

    icon.setAttribute("class", "icon-fixed-width icon-share");

    anchor.appendChild(icon);

    for (var attr in attributes) {
      anchor.setAttribute(attr, attributes[attr]);
    }

    // there might be a browser quirk here
    anchor.setAttribute("data-clipboard-text", anchor.href);

    return anchor;
  },

  createPopover: function (anchor, headingId) {
    var popover;
    var inputId = "popover-" + headingId;
    popover = $(anchor).popover({
      container: this.popoverContainer,
      title: "Share a link to this section",
      content: function () {
        return "<input readonly id='" + inputId + "' value='" + anchor.href + "'>";
      },
      html: true,
      trigger: "manual" // this disables it for clicks
    });

    popover.on("shown", function () {
      // warning:  due to a BS2 implementation detail, this handler ALSO fires
      // when success tooltip is shown

      // Hide all other popovers
      // `this` is the anchor that triggered the shown event.
      $("a.headerLink").not(this).popover("hide");
      // the contents of popover are lazy-created, so this unfortunately needs to go here.
      var input = document.getElementById(inputId);

      if (input) {
        input.focus();
        input.select();
      }
    });
  },

  createSuccessTooltip: function (anchor) {
    $(anchor).tooltip({
      title: "Direct link to this section copied",
      trigger: "manual"
    });
  },

  registerClipboardHandlers: function () {
    var TOOLTIP_TTL = this.TOOLTIP_TTL;
    var clipboardErrorHandler = function (e) {
      $(e.trigger).popover("show");
    };

    var ensureSuccessHandler = function (e) {
      var originalText = e.text;
      if (window.clipboardData) {
        // IE only
        var clipboardContent = window.clipboardData.getData("Text");
        if (originalText !== clipboardContent) {
          clipboardErrorHandler(e);
          return;
        }
      }
      $(e.trigger).tooltip("show");
      setTimeout(function () {
        $(e.trigger).tooltip("hide");
      }, TOOLTIP_TTL);
    };

    if (!this.handler) {
      this.handler = new Clipboard("a.headerLink");
      this.handler.on("error", clipboardErrorHandler);
      this.handler.on("success", ensureSuccessHandler);
    }
  },

  registerDismissPopoverHandler: function () {
    $(this.target).click(function (e) {
      // if clicking a non-popover-ed element
      if ($("a.headerLink").has(e.target).length === 0) {
        $("a.headerLink").popover("hide");
      }
    });
  }

};
<commit_msg>don't need to register it anymore<commit_after>/* eslint-env jquery */

/*eslint-disable no-unused-vars*/
var addHeadingAnchors = {
  /*eslint-enable no-unused-vars*/
  TOOLTIP_TTL: 500,

  init: function (selector, popoverContainer) {
    this.popoverContainer = popoverContainer || "body";
    this.target = document.querySelector(selector);
    if (this.target) {
      this.addAnchorsToHeadings();
      this.registerClipboardHandlers();
      this.registerDismissPopoverHandler();
    }
  },

  addAnchorsToHeadings: function () {
    var selectorString = "h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]";
    var headings = this.target.querySelectorAll(selectorString);

    Array.prototype.forEach.call(headings, function (heading) {
      var id = heading.id;
      // only add if no existing anchor
      if (!heading.querySelector("a.headerLink")) {
        var anchor = this.createAnchor(id);
        heading.appendChild(anchor);
        this.createPopover(anchor, id);
        this.createSuccessTooltip(anchor);
      }
    }.bind(this));
  },

  createAnchor: function (id) {
    var anchor = document.createElement("a");
    var icon = document.createElement("i");
    var attributes = {
      href: "#" + id,
      class: "headerLink"
    };

    icon.setAttribute("class", "icon-fixed-width icon-share");

    anchor.appendChild(icon);

    for (var attr in attributes) {
      anchor.setAttribute(attr, attributes[attr]);
    }

    // there might be a browser quirk here
    anchor.setAttribute("data-clipboard-text", anchor.href);

    return anchor;
  },

  createPopover: function (anchor, headingId) {
    var popover;
    var inputId = "popover-" + headingId;
    popover = $(anchor).popover({
      container: this.popoverContainer,
      title: "Share a link to this section",
      content: function () {
        return "<input readonly id='" + inputId + "' value='" + anchor.href + "'>";
      },
      html: true,
      trigger: "manual" // this disables it for clicks
    });

    popover.on("shown", function () {
      // warning:  due to a BS2 implementation detail, this handler ALSO fires
      // when success tooltip is shown

      // Hide all other popovers
      // `this` is the anchor that triggered the shown event.
      $("a.headerLink").not(this).popover("hide");
      // the contents of popover are lazy-created, so this unfortunately needs to go here.
      var input = document.getElementById(inputId);

      if (input) {
        input.focus();
        input.select();
      }
    });
  },

  createSuccessTooltip: function (anchor) {
    $(anchor).tooltip({
      title: "Direct link to this section copied",
      trigger: "manual"
    });
  },

  registerClipboardHandlers: function () {
    var TOOLTIP_TTL = this.TOOLTIP_TTL;
    var clipboardErrorHandler = function (e) {
      $(e.trigger).popover("show");
    };

    var ensureSuccessHandler = function (e) {
      var originalText = e.text;
      if (window.clipboardData) {
        // IE only
        var clipboardContent = window.clipboardData.getData("Text");
        if (originalText !== clipboardContent) {
          clipboardErrorHandler(e);
          return;
        }
      }
      $(e.trigger).tooltip("show");
      setTimeout(function () {
        $(e.trigger).tooltip("hide");
      }, TOOLTIP_TTL);
    };

    if (!this.handler) {
      this.handler = new Clipboard("a.headerLink");
      this.handler.on("error", clipboardErrorHandler);
      this.handler.on("success", ensureSuccessHandler);
    }
  },

  registerDismissPopoverHandler: function () {
    $(this.target).click(function (e) {
      // if clicking a non-popover-ed element
      if ($("a.headerLink").has(e.target).length === 0) {
        $("a.headerLink").popover("hide");
      }
    });
  }

};
<|endoftext|>