<commit_before>// ==UserScript==
// @name         Stack Overflow Extras (SOX)
// @namespace    https://github.com/soscripted/sox
// @homepage     https://github.com/soscripted/sox
// @homepageURL  https://github.com/soscripted/sox
// @version      2.7.12 DEV
// @description  Extra optional features for Stack Overflow and Stack Exchange sites
// @contributor  ᴉʞuǝ (https://stackoverflow.com/users/1454538/, https://github.com/mezmi)
// @contributor  ᔕᖺᘎᕊ (https://stackexchange.com/users/4337810/, https://github.com/shu8)
// @contributor  Sir-Cumference (https://stackexchange.com/users/4119142/, https://github.com/Sir-Cumference)
// @contributor  GaurangTandon (https://github.com/GaurangTandon)
// @contributor  double-beep (https://stackexchange.com/users/14688437/double-beep, https://github.com/double-beep)
// @updateURL    https://cdn.jsdelivr.net/gh/soscripted/sox@dev/sox.user.js

// @match        https://*.stackoverflow.com/*
// @match        https://*.stackexchange.com/*
// @match        https://*.superuser.com/*
// @match        https://*.serverfault.com/*
// @match        https://*.askubuntu.com/*
// @match        https://*.stackapps.com/*
// @match        https://*.mathoverflow.net/*
// @match        *://github.com/soscripted/*
// @match        *://soscripted.github.io/sox/*

// @exclude      *://data.stackexchange.com/*
// @exclude      *://api.stackexchange.com/*
// @exclude      *://stackoverflow.com/c/*

// @require      https://code.jquery.com/jquery-3.3.1.min.js
// @require      https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @require      https://api.stackexchange.com/js/2.0/all.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.5.3/jquery.timeago.min.js

// @require      sox.common.js
// @require      sox.github.js
// @require      sox.dialog.js
// @require      sox.features.js

// @resource     css sox.css
// @resource     dialog sox.dialog.html
// @resource     featuresJSON sox.features.info.json
// @resource     common sox.common.info.json
// @resource     sprites sox.sprites.svg

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @grant        GM_info
// @grant        GM_setClipboard
// ==/UserScript==
/*jshint loopfunc: true*/
(function(sox, $) {
  'use strict';

  function runFeatures(settings, featureInfo) {
    // Execute features
    performance.mark('allFeatures-start');
    for (let i = 0; i < settings.length; ++i) {
      const category = settings[i].split('-')[0];
      const featureId = settings[i].split('-')[1];

      if (!(category in featureInfo.categories)) { //if we ever rename a category
        sox.loginfo('Deleting feature "' + settings[i] + '" (category rename?)');
        settings.splice(i, 1);
        sox.settings.save(settings);
        continue;
      }

      const feature = featureInfo.categories[category].filter(obj => {
        return obj.name == featureId;
      })[0];

      let runFeature = true;
      try {
        //NOTE: there is no else if() because it is possible to have both match and exclude patterns..
        //which could have minor exceptions making it neccessary to check both
        if (feature.match !== '') {
          const sites = feature.match.split(',');

          for (let pattern = 0; pattern < sites.length; pattern++) {
            if (!sox.location.matchWithPattern(sites[pattern])) {
              runFeature = false; //none of the patterns match the current site.. yet.
            } else {
              runFeature = true;
              break; //if it does match, then stop looping; we want the feature to run
            }
          }
        }
        if (feature.exclude !== '') {
          const sites = feature.exclude.split(',');

          for (let pattern = 0; pattern < sites.length; pattern++) {
            if (sox.location.matchWithPattern(sites[pattern])) { //if current site is in list, DON'T run feature
              runFeature = false; //don't run feature
              break; //no need to keep on looping
            }
          }
        }
        if (runFeature) {
          sox.debug('running ' + featureId);
          performance.mark(`${featureId}-start`);
          if (feature.settings) {
            const settingsToPass = GM_getValue('SOX-' + featureId + '-settings') ? JSON.parse(GM_getValue('SOX-' + featureId + '-settings')) : {};
            sox.features[featureId](settingsToPass); //run the feature if match and exclude conditions are met, pass on settings object
          } else {
            sox.features[featureId](); //run the feature if match and exclude conditions are met
          }
          performance.mark(`${featureId}-end`);
          performance.measure(featureId, `${featureId}-start`, `${featureId}-end`);
        }
      } catch (err) {
        if (!sox.features[featureId] || !feature) { //remove deprecated/'corrupt' feature IDs from saved settings
          sox.loginfo('Deleting feature "' + settings[i] + '" (feature not found)');
          settings.splice(i, 1);
          sox.settings.save(settings);
          $('#sox-settings-dialog-features').find('#' + settings[i].split('-')[1]).parent().parent().remove();
        } else {
          $('#sox-settings-dialog-features').find('#' + settings[i].split('-')[1]).parent().css('color', 'red').attr('title', 'There was an error loading this feature. Please raise an issue on GitHub.');
          sox.error('There was an error loading the feature "' + settings[i] + '". Please raise an issue on GitHub, and copy the following error log:\n' + err);
        }
      }
    }
    performance.mark('allFeatures-end');
    performance.measure('allFeatures', 'allFeatures-start', 'allFeatures-end');
    sox.debug('Performance Data', performance.getEntriesByType('measure'));
  }

  function init() {
    if (sox.location.on('github.com/soscripted')) {
      try {
        sox.github.init(sox.info.version, sox.info.handler);
      } catch (e) {
        throw ('SOX: There was an error while attempting to initialize the sox.github.js file, please report this on GitHub.\n' + e);
      }
      return;
    }

    if (sox.location.on('soscripted.github.io/sox/#access_token')) { //save access token
      try {
        const access_token = window.location.href.split('=')[1].split('&')[0];
        sox.loginfo('ACCESS TOKEN: ', access_token);
        GM_setValue('SOX-accessToken', access_token);
        alert('Access token successfully saved! You can close this window :)');
      } catch (e) {
        throw ('SOX: There was an error saving your access token');
      }
      return;
    }
    if (sox.info.debugging) {
      sox.debug('DEBUGGING SOX VERSION ' + sox.info.version);
      sox.debug('----------------saved variables---------------------');
      sox.settings.writeToConsole(true); //true => hide access token
      sox.debug('----------------end saved variables---------------------');
    }

    const spritesDiv = $('<div/>', { html: GM_getResourceText('sprites') });
    $('head').append(spritesDiv);

    GM_addStyle(GM_getResourceText('css'));

    const settings = sox.settings.load();
    //returns undefined if not set

    const featureInfo = JSON.parse(GM_getResourceText('featuresJSON'));

    try {
      sox.debug('SOX object', sox);
      sox.dialog.init({
        version: sox.info.version,
        features: featureInfo,
        settings: settings,
        lastVersionInstalled: sox.info.lastVersionInstalled,
      });
    } catch (e) {
      throw ('SOX: There was an error while attempting to initialize the SOX Settings Dialog, please report this on GitHub.\n' + e);
    }

    window.onload = () => {
      if (sox.settings.available) {
        if (document.hasFocus && document.hasFocus()) {
          runFeatures(settings, featureInfo);
        } else {
          window.addEventListener('focus', () => runFeatures(settings, featureInfo), { once: true });
        }
      }
    }

    //custom events....
    sox.helpers.observe([...document.getElementsByClassName('post-layout')], '.new_comment, .comment, .comments, .comment-text', node => {
      sox.debug('sox-new-comment event triggered');
      $(document).trigger('sox-new-comment', [node]);
    });

    sox.helpers.observe(document.body, 'textarea[id^="wmd-input"]', node => {
      sox.debug('sox-edit-window event triggered');
      $(document).trigger('sox-edit-window', [node]);
    });

    sox.helpers.observe(document.body, '.reviewable-post, .review-content', node => {
      sox.debug('sox-new-review-post-appeared event triggered');
      $(document).trigger('sox-new-review-post-appeared', [node]);
    });

    const chatBody = document.getElementById('chat-body');
    if (chatBody) {
      sox.helpers.observe(chatBody, '.user-popup', node => {
        sox.debug('sox-chat-user-popup event triggered');
        $(document).trigger('sox-chat-user-popup', [node]);
      });
    }

    if (GM_getValue('SOX-accessToken', -1) == -1) { //set access token
      //This was originally a series of IIFEs appended to the head which used the SE API JS SDK but
      //it was very uncertain and often caused issues, especially in FF
      //it now uses a Github page to show the access token
      //and detects that page and saves it automatically.
      //this seems to be a much cleaner and easier-to-debug method!
      GM_setValue('SOX-accessToken', -2); //once we ask the user once, don't ask them again: set the value to -2 so this IF never evaluates to true
      const askUserToAuthorise = window.confirm('To get the most out of SOX, you should get an access token! Please press "OK" to continue and follow the instructions in the window that opens. NOTE: this message will not appear again; if you choose not to, you can click the key at the bottom of the settings dialog at anytime to get one.');
      if (askUserToAuthorise) window.open('https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
      sox.warn('Please go to the following URL to get your access token for certain SOX features', 'https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
    }
  }
  sox.ready(init);
})(window.sox = window.sox || {}, jQuery);
<commit_msg>Bump version number<commit_after>// ==UserScript==
// @name         Stack Overflow Extras (SOX)
// @namespace    https://github.com/soscripted/sox
// @homepage     https://github.com/soscripted/sox
// @homepageURL  https://github.com/soscripted/sox
// @version      2.7.13 DEV
// @description  Extra optional features for Stack Overflow and Stack Exchange sites
// @contributor  ᴉʞuǝ (https://stackoverflow.com/users/1454538/, https://github.com/mezmi)
// @contributor  ᔕᖺᘎᕊ (https://stackexchange.com/users/4337810/, https://github.com/shu8)
// @contributor  Sir-Cumference (https://stackexchange.com/users/4119142/, https://github.com/Sir-Cumference)
// @contributor  GaurangTandon (https://github.com/GaurangTandon)
// @contributor  double-beep (https://stackexchange.com/users/14688437/double-beep, https://github.com/double-beep)
// @updateURL    https://cdn.jsdelivr.net/gh/soscripted/sox@dev/sox.user.js

// @match        https://*.stackoverflow.com/*
// @match        https://*.stackexchange.com/*
// @match        https://*.superuser.com/*
// @match        https://*.serverfault.com/*
// @match        https://*.askubuntu.com/*
// @match        https://*.stackapps.com/*
// @match        https://*.mathoverflow.net/*
// @match        *://github.com/soscripted/*
// @match        *://soscripted.github.io/sox/*

// @exclude      *://data.stackexchange.com/*
// @exclude      *://api.stackexchange.com/*
// @exclude      *://stackoverflow.com/c/*

// @require      https://code.jquery.com/jquery-3.3.1.min.js
// @require      https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @require      https://api.stackexchange.com/js/2.0/all.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.5.3/jquery.timeago.min.js

// @require      sox.common.js
// @require      sox.github.js
// @require      sox.dialog.js
// @require      sox.features.js

// @resource     css sox.css
// @resource     dialog sox.dialog.html
// @resource     featuresJSON sox.features.info.json
// @resource     common sox.common.info.json
// @resource     sprites sox.sprites.svg

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @grant        GM_info
// @grant        GM_setClipboard
// ==/UserScript==
/*jshint loopfunc: true*/
(function(sox, $) {
  'use strict';

  function runFeatures(settings, featureInfo) {
    // Execute features
    performance.mark('allFeatures-start');
    for (let i = 0; i < settings.length; ++i) {
      const category = settings[i].split('-')[0];
      const featureId = settings[i].split('-')[1];

      if (!(category in featureInfo.categories)) { //if we ever rename a category
        sox.loginfo('Deleting feature "' + settings[i] + '" (category rename?)');
        settings.splice(i, 1);
        sox.settings.save(settings);
        continue;
      }

      const feature = featureInfo.categories[category].filter(obj => {
        return obj.name == featureId;
      })[0];

      let runFeature = true;
      try {
        //NOTE: there is no else if() because it is possible to have both match and exclude patterns..
        //which could have minor exceptions making it neccessary to check both
        if (feature.match !== '') {
          const sites = feature.match.split(',');

          for (let pattern = 0; pattern < sites.length; pattern++) {
            if (!sox.location.matchWithPattern(sites[pattern])) {
              runFeature = false; //none of the patterns match the current site.. yet.
            } else {
              runFeature = true;
              break; //if it does match, then stop looping; we want the feature to run
            }
          }
        }
        if (feature.exclude !== '') {
          const sites = feature.exclude.split(',');

          for (let pattern = 0; pattern < sites.length; pattern++) {
            if (sox.location.matchWithPattern(sites[pattern])) { //if current site is in list, DON'T run feature
              runFeature = false; //don't run feature
              break; //no need to keep on looping
            }
          }
        }
        if (runFeature) {
          sox.debug('running ' + featureId);
          performance.mark(`${featureId}-start`);
          if (feature.settings) {
            const settingsToPass = GM_getValue('SOX-' + featureId + '-settings') ? JSON.parse(GM_getValue('SOX-' + featureId + '-settings')) : {};
            sox.features[featureId](settingsToPass); //run the feature if match and exclude conditions are met, pass on settings object
          } else {
            sox.features[featureId](); //run the feature if match and exclude conditions are met
          }
          performance.mark(`${featureId}-end`);
          performance.measure(featureId, `${featureId}-start`, `${featureId}-end`);
        }
      } catch (err) {
        if (!sox.features[featureId] || !feature) { //remove deprecated/'corrupt' feature IDs from saved settings
          sox.loginfo('Deleting feature "' + settings[i] + '" (feature not found)');
          settings.splice(i, 1);
          sox.settings.save(settings);
          $('#sox-settings-dialog-features').find('#' + settings[i].split('-')[1]).parent().parent().remove();
        } else {
          $('#sox-settings-dialog-features').find('#' + settings[i].split('-')[1]).parent().css('color', 'red').attr('title', 'There was an error loading this feature. Please raise an issue on GitHub.');
          sox.error('There was an error loading the feature "' + settings[i] + '". Please raise an issue on GitHub, and copy the following error log:\n' + err);
        }
      }
    }
    performance.mark('allFeatures-end');
    performance.measure('allFeatures', 'allFeatures-start', 'allFeatures-end');
    sox.debug('Performance Data', performance.getEntriesByType('measure'));
  }

  function init() {
    if (sox.location.on('github.com/soscripted')) {
      try {
        sox.github.init(sox.info.version, sox.info.handler);
      } catch (e) {
        throw ('SOX: There was an error while attempting to initialize the sox.github.js file, please report this on GitHub.\n' + e);
      }
      return;
    }

    if (sox.location.on('soscripted.github.io/sox/#access_token')) { //save access token
      try {
        const access_token = window.location.href.split('=')[1].split('&')[0];
        sox.loginfo('ACCESS TOKEN: ', access_token);
        GM_setValue('SOX-accessToken', access_token);
        alert('Access token successfully saved! You can close this window :)');
      } catch (e) {
        throw ('SOX: There was an error saving your access token');
      }
      return;
    }
    if (sox.info.debugging) {
      sox.debug('DEBUGGING SOX VERSION ' + sox.info.version);
      sox.debug('----------------saved variables---------------------');
      sox.settings.writeToConsole(true); //true => hide access token
      sox.debug('----------------end saved variables---------------------');
    }

    const spritesDiv = $('<div/>', { html: GM_getResourceText('sprites') });
    $('head').append(spritesDiv);

    GM_addStyle(GM_getResourceText('css'));

    const settings = sox.settings.load();
    //returns undefined if not set

    const featureInfo = JSON.parse(GM_getResourceText('featuresJSON'));

    try {
      sox.debug('SOX object', sox);
      sox.dialog.init({
        version: sox.info.version,
        features: featureInfo,
        settings: settings,
        lastVersionInstalled: sox.info.lastVersionInstalled,
      });
    } catch (e) {
      throw ('SOX: There was an error while attempting to initialize the SOX Settings Dialog, please report this on GitHub.\n' + e);
    }

    window.onload = () => {
      if (sox.settings.available) {
        if (document.hasFocus && document.hasFocus()) {
          runFeatures(settings, featureInfo);
        } else {
          window.addEventListener('focus', () => runFeatures(settings, featureInfo), { once: true });
        }
      }
    }

    //custom events....
    sox.helpers.observe([...document.getElementsByClassName('post-layout')], '.new_comment, .comment, .comments, .comment-text', node => {
      sox.debug('sox-new-comment event triggered');
      $(document).trigger('sox-new-comment', [node]);
    });

    sox.helpers.observe(document.body, 'textarea[id^="wmd-input"]', node => {
      sox.debug('sox-edit-window event triggered');
      $(document).trigger('sox-edit-window', [node]);
    });

    sox.helpers.observe(document.body, '.reviewable-post, .review-content', node => {
      sox.debug('sox-new-review-post-appeared event triggered');
      $(document).trigger('sox-new-review-post-appeared', [node]);
    });

    const chatBody = document.getElementById('chat-body');
    if (chatBody) {
      sox.helpers.observe(chatBody, '.user-popup', node => {
        sox.debug('sox-chat-user-popup event triggered');
        $(document).trigger('sox-chat-user-popup', [node]);
      });
    }

    if (GM_getValue('SOX-accessToken', -1) == -1) { //set access token
      //This was originally a series of IIFEs appended to the head which used the SE API JS SDK but
      //it was very uncertain and often caused issues, especially in FF
      //it now uses a Github page to show the access token
      //and detects that page and saves it automatically.
      //this seems to be a much cleaner and easier-to-debug method!
      GM_setValue('SOX-accessToken', -2); //once we ask the user once, don't ask them again: set the value to -2 so this IF never evaluates to true
      const askUserToAuthorise = window.confirm('To get the most out of SOX, you should get an access token! Please press "OK" to continue and follow the instructions in the window that opens. NOTE: this message will not appear again; if you choose not to, you can click the key at the bottom of the settings dialog at anytime to get one.');
      if (askUserToAuthorise) window.open('https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
      sox.warn('Please go to the following URL to get your access token for certain SOX features', 'https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
    }
  }
  sox.ready(init);
})(window.sox = window.sox || {}, jQuery);
<|endoftext|>


<commit_before>// This script was started by copying MonoDevelop's, available at
// https://github.com/mono/monodevelop/tree/master/setup/WixSetup

// To run: just open a Windows command line (cmd.exe) and invoke
// it like if it was a .bat/.cmd file (do not simply double-click)

// HEAT manual: http://wix.sourceforge.net/manual-wix3/heat.htm

var bin = '..\\..\\bin';
var sh = new ActiveXObject("WScript.Shell");
var fs = new ActiveXObject("Scripting.FileSystemObject");
var env = sh.Environment("Process");
var heat = "\"" + env("WIX") + "bin\\heat.exe\"";

// Look for msbuild.exe
if (fs.FileExists (env("windir") + "\\Microsoft.NET\\Framework\\v4.0.30319\\msbuild.exe") == 1) {
  var msbuild = env("windir") + "\\Microsoft.NET\\Framework\\v4.0.30319\\msbuild.exe"
} else if (fs.FileExists(env("windir") + "\\Microsoft.NET\\Framework\\v3.5\\msbuild.exe") == 1) {
  var msbuild = env("windir") + "\\Microsoft.NET\\Framework\\v3.5\\msbuild.exe"
} else {
  WScript.Echo ('Build failed: Microsoft.NET MSBuild \(msbuild.exe\) not found');
  WScript.Quit (1);
}

// Could build Banshee from here, but atm at least I prefer to assume it's already built
//build ("..\\..\\Banshee.sln");

// Delete some files that might be created by running uninstalled
if (fs.FileExists (bin + "\\bin\\registry.bin")) fs.DeleteFile (bin + "\\bin\\registry.bin");
if (fs.FolderExists (bin + "\\bin\\addin-db-001")) fs.DeleteFolder (bin + "\\bin\\addin-db-001");

// We can't just heat the entire dir b/c it would include the .git/ directory
heatDir ("bin");
heatDir ("etc");
heatDir ("lib");
heatDir ("share\\banshee");
heatDir ("share\\icons");
heatDir ("share\\locale");
heatDir ("share\\themes");

// Create the installer, will be outputted to Banshee-1.9.6.msi in build/windows/
build ("Installer.wixproj")
WScript.Echo ("Setup successfully generated");

function heatDir (dir)
{
  var wxi_name = dir.replace (/\\/, '_');
  var params = ' -cg ' + dir + ' -scom -sreg -ag -sfrag -indent 2 -var var.' + wxi_name + 'Dir';

  if (wxi_name.indexOf ("share") == 0) {
	  params += ' -dr SHARELOCATION ';
  } else {
	  params += ' -dr INSTALLLOCATION ';
  }

  if (dir == 'bin') {
    // Do not auto-generate ids for files in the bin directory
    params += '-suid '
  }
  // Generate the list of binary files (managed and native .dlls and .pdb and .config files)
  run (heat + ' dir ..\\..\\bin\\' + dir + params + ' -out obj\\generated_'+wxi_name+'.wxi');

  // Heat has no option to output Include (wxi) files instead of Wix (wxs) ones, so do a little regex
  regexreplace ('obj\\generated_'+wxi_name+'.wxi', /Wix xmlns/, 'Include xmlns');
  regexreplace ('obj\\generated_'+wxi_name+'.wxi', /Wix>/, 'Include>');
}

function run (cmd)
{
  if (sh.run (cmd, 5, true) != 0) {
    WScript.Echo ("Failed to run cmd:\n" + cmd);
    WScript.Quit (1);
  }
}

function build (file)
{
  if (sh.run (msbuild + " " + file, 5, true) != 0) {
    WScript.Echo ("Build failed");
    WScript.Quit (1);
  }
}

function regexreplace (file, regex, replacement)
{
  var f = fs.OpenTextFile (file, 1);
  var content = f.ReadAll ();
  f.Close ();
  content = content.replace (regex, replacement);
  f = fs.CreateTextFile (file, true);
  f.Write (content);
  f.Close ();
}
<commit_msg>build: give a clearer error when not finding WIX env var (windows)<commit_after>// This script was started by copying MonoDevelop's, available at
// https://github.com/mono/monodevelop/tree/master/setup/WixSetup

// To run: just open a Windows command line (cmd.exe) and invoke
// it like if it was a .bat/.cmd file (do not simply double-click)

// HEAT manual: http://wix.sourceforge.net/manual-wix3/heat.htm

var bin = '..\\..\\bin';
var sh = new ActiveXObject("WScript.Shell");
var fs = new ActiveXObject("Scripting.FileSystemObject");
var env = sh.Environment("Process");
var wix_path = env("WIX");
if (wix_path == "") {
  WScript.Echo ('WIX environment variable not set, did you install WiX 3.5?');
  WScript.Quit (1);
}
var heat = "\"" + wix_path + "bin\\heat.exe\"";

// Look for msbuild.exe
if (fs.FileExists (env("windir") + "\\Microsoft.NET\\Framework\\v4.0.30319\\msbuild.exe") == 1) {
  var msbuild = env("windir") + "\\Microsoft.NET\\Framework\\v4.0.30319\\msbuild.exe"
} else if (fs.FileExists(env("windir") + "\\Microsoft.NET\\Framework\\v3.5\\msbuild.exe") == 1) {
  var msbuild = env("windir") + "\\Microsoft.NET\\Framework\\v3.5\\msbuild.exe"
} else {
  WScript.Echo ('Build failed: Microsoft.NET MSBuild \(msbuild.exe\) not found');
  WScript.Quit (1);
}

// Could build Banshee from here, but atm at least I prefer to assume it's already built
//build ("..\\..\\Banshee.sln");

// Delete some files that might be created by running uninstalled
if (fs.FileExists (bin + "\\bin\\registry.bin")) fs.DeleteFile (bin + "\\bin\\registry.bin");
if (fs.FolderExists (bin + "\\bin\\addin-db-001")) fs.DeleteFolder (bin + "\\bin\\addin-db-001");

// We can't just heat the entire dir b/c it would include the .git/ directory
heatDir ("bin");
heatDir ("etc");
heatDir ("lib");
heatDir ("share\\banshee");
heatDir ("share\\icons");
heatDir ("share\\locale");
heatDir ("share\\themes");

// Create the installer, will be outputted to Banshee-1.9.6.msi in build/windows/
build ("Installer.wixproj")
WScript.Echo ("Setup successfully generated");

function heatDir (dir)
{
  var wxi_name = dir.replace (/\\/, '_');
  var params = ' -cg ' + dir + ' -scom -sreg -ag -sfrag -indent 2 -var var.' + wxi_name + 'Dir';

  if (wxi_name.indexOf ("share") == 0) {
	  params += ' -dr SHARELOCATION ';
  } else {
	  params += ' -dr INSTALLLOCATION ';
  }

  if (dir == 'bin') {
    // Do not auto-generate ids for files in the bin directory
    params += '-suid '
  }
  // Generate the list of binary files (managed and native .dlls and .pdb and .config files)
  run (heat + ' dir ..\\..\\bin\\' + dir + params + ' -out obj\\generated_'+wxi_name+'.wxi');

  // Heat has no option to output Include (wxi) files instead of Wix (wxs) ones, so do a little regex
  regexreplace ('obj\\generated_'+wxi_name+'.wxi', /Wix xmlns/, 'Include xmlns');
  regexreplace ('obj\\generated_'+wxi_name+'.wxi', /Wix>/, 'Include>');
}

function run (cmd)
{
  if (sh.run (cmd, 5, true) != 0) {
    WScript.Echo ("Failed to run cmd:\n" + cmd);
    WScript.Quit (1);
  }
}

function build (file)
{
  if (sh.run (msbuild + " " + file, 5, true) != 0) {
    WScript.Echo ("Build failed");
    WScript.Quit (1);
  }
}

function regexreplace (file, regex, replacement)
{
  var f = fs.OpenTextFile (file, 1);
  var content = f.ReadAll ();
  f.Close ();
  content = content.replace (regex, replacement);
  f = fs.CreateTextFile (file, true);
  f.Write (content);
  f.Close ();
}
<|endoftext|>


<commit_before>/**
 * This object serves as a small model containing the currently logged-in user's preferences state.
 *
 * @class PreferencesModel
 * @param {Object} [serverDispatchedObject]
 * @constructor
 */
function PreferencesModel (serverDispatchedObject) {

    /**
     * @property canArchiveProgramYears
     * @type {Boolean}
     */
    this.canArchiveProgramYears = false;

    /**
     * @property canArchiveCourses
     * @type {Boolean}
     * @default false
     */
    this.canArchiveCourses = false;

    /**
     * @property canRolloverCourses
     * @type {Boolean}
     * @default false
     */
    this.canRolloverCourses = false;

    if (typeof serverDispatchedObject !== 'undefined') {
        this.updateWithServerDispatchedObject(serverDispatchedObject);
    }
}
/**
 * @method showCourseArchiving
 * @return {Boolean}
 * @default false
 */
PreferencesModel.prototype.showCourseArchiving = function () {
    return this.canArchiveCourses;
};

/**
 * @method showProgramYearArchiving
 * @return {Boolean}
 */
PreferencesModel.prototype.showProgramYearArchiving = function () {
    return this.canArchiveProgramYears;
};

/**
 * @method showCourseRollover
 * @return {Boolean}
 */
PreferencesModel.prototype.showCourseRollover = function () {
    return this.canRolloverCourses;
};

/**
 * @method updateWithServerDispatchedObject
 * @param {Object} serverDispatchedObject
 *   @param {String} serverDispatchedObject.py_archiving
 *   @param {String} serverDispatchedObject.course_archiving
 *   @param {String} serverDispatchedObject.course_rollover
 */
PreferencesModel.prototype.updateWithServerDispatchedObject = function (serverDispatchedObject) {
    this.canArchiveProgramYears = (serverDispatchedObject.py_archiving === 'true');
    this.canArchiveCourses = (serverDispatchedObject.course_archiving === 'true');
    this.canRolloverCourses = (serverDispatchedObject.course_rollover === 'true');
};
<commit_msg>annotation fix.<commit_after>/**
 * This object serves as a small model containing the currently logged-in user's preferences state.
 *
 * @class PreferencesModel
 * @param {Object} [serverDispatchedObject]
 * @constructor
 */
function PreferencesModel (serverDispatchedObject) {

    /**
     * @property canArchiveProgramYears
     * @type {Boolean}
     * @default false
     */
    this.canArchiveProgramYears = false;

    /**
     * @property canArchiveCourses
     * @type {Boolean}
     * @default false
     */
    this.canArchiveCourses = false;

    /**
     * @property canRolloverCourses
     * @type {Boolean}
     * @default false
     */
    this.canRolloverCourses = false;

    if (typeof serverDispatchedObject !== 'undefined') {
        this.updateWithServerDispatchedObject(serverDispatchedObject);
    }
}
/**
 * @method showCourseArchiving
 * @return {Boolean}
 */
PreferencesModel.prototype.showCourseArchiving = function () {
    return this.canArchiveCourses;
};

/**
 * @method showProgramYearArchiving
 * @return {Boolean}
 */
PreferencesModel.prototype.showProgramYearArchiving = function () {
    return this.canArchiveProgramYears;
};

/**
 * @method showCourseRollover
 * @return {Boolean}
 */
PreferencesModel.prototype.showCourseRollover = function () {
    return this.canRolloverCourses;
};

/**
 * @method updateWithServerDispatchedObject
 * @param {Object} serverDispatchedObject
 *   @param {String} serverDispatchedObject.py_archiving
 *   @param {String} serverDispatchedObject.course_archiving
 *   @param {String} serverDispatchedObject.course_rollover
 */
PreferencesModel.prototype.updateWithServerDispatchedObject = function (serverDispatchedObject) {
    this.canArchiveProgramYears = (serverDispatchedObject.py_archiving === 'true');
    this.canArchiveCourses = (serverDispatchedObject.course_archiving === 'true');
    this.canRolloverCourses = (serverDispatchedObject.course_rollover === 'true');
};
<|endoftext|>


<commit_before>/*
 *  Override for form fields
 *  @author: Prakash Paudel
 *
 */

/**
 * Fix for the checkbox focus visible
 */
Ext.override(Ext.form.Checkbox, {
	onFocus: function(){
		var wrap = this.wrap;
		if(!wrap) return
		wrap.setStyle("float","left")
		wrap.setStyle("height","auto")
		wrap.setStyle("border","1px solid #7eadd9")
		Ext.DomHelper.insertAfter(wrap,{tag:'div',style:'clear:both'})

	},
	onBlur: function(){
		var wrap = this.wrap;
		if(!wrap) return
		wrap.setStyle("border","0px solid #7eadd9")
	}
});

/**
 * Fix for the radio focus visible
 */
Ext.override(Ext.form.Radio, {
	onFocus: function(){
		var wrap = this.wrap;
		if(!wrap) return
		wrap.setStyle("float","left")
		wrap.setStyle("height","auto")
		wrap.setStyle("border","1px solid #7eadd9")
		Ext.DomHelper.insertAfter(wrap,{tag:'div',style:'clear:both'})

	},
	onBlur: function(){
		var wrap = this.wrap;
		if(!wrap) return
		wrap.setStyle("border","0px solid #7eadd9")
	}
});

/**
 * Fix for the button focus visible, looks like mouse overed when focused
 */
Ext.override(Ext.Button, {
    onFocus: function(){
		this.addClass("x-btn-over")
    },
    onBlur: function(){
    	this.removeClass("x-btn-over")
    }
});

/**
 * Fix for enter key press form submit
 */
Ext.override(Ext.form.Field,{
	fireKey : function(e) {
	    if(((Ext.isIE && e.type == 'keydown') || e.type == 'keypress') && e.isSpecialKey()) {
	    	if(e.getKey() == e.ENTER){
	    		var form = this.findParentByType('form');
	    		Ext.each(form.buttons,function(button){
	    			if(button.url && button.url == form.url){
	    				button.handler.call(button.scope);
	    			}
	    		})
	    	}
	    }
	},
	initEvents : function() {
		//this.el.on(Ext.isIE ? "keydown" : "keypress", this.fireKey,  this);
	    this.el.on("focus", this.onFocus,  this);
	    this.el.on("blur", this.onBlur,  this);
	    this.el.on("keydown", this.fireKey, this);
	    this.el.on("keypress", this.fireKey, this);
	    this.el.on("keyup", this.fireKey, this);
	}
})

<commit_msg>updates<commit_after>/*
 *  Override for form fields
 *  @author: Prakash Paudel
 *
 */

/**
 * Fix for the checkbox focus visible
 */
Ext.override(Ext.form.Checkbox, {
	onFocus: function(){
		var wrap = this.wrap;
		if(!wrap) return
		wrap.setStyle("float","left")
		wrap.setStyle("height","auto")
		wrap.setStyle("border","1px solid #7eadd9")
		Ext.DomHelper.insertAfter(wrap,{tag:'div',style:'clear:both'})

	},
	onBlur: function(){
		var wrap = this.wrap;
		if(!wrap) return
		wrap.setStyle("border","0px solid #7eadd9")
	}
});

/**
 * Fix for the radio focus visible
 */
Ext.override(Ext.form.Radio, {
	onFocus: function(){
		var wrap = this.wrap;
		if(!wrap) return
		wrap.setStyle("float","left")
		wrap.setStyle("height","auto")
		wrap.setStyle("border","1px solid #7eadd9")
		Ext.DomHelper.insertAfter(wrap,{tag:'div',style:'clear:both'})

	},
	onBlur: function(){
		var wrap = this.wrap;
		if(!wrap) return
		wrap.setStyle("border","0px solid #7eadd9")
	}
});

/**
 * Fix for the button focus visible, looks like mouse overed when focused
 */
Ext.override(Ext.Button, {
    onFocus: function(){
		this.addClass("x-btn-over")
    },
    onBlur: function(){
    	this.removeClass("x-btn-over")
    }
});

/**
 * Fix for enter key press form submit
 */
Ext.override(Ext.form.Field,{
	fireKeys : function(e) {
	    if(((Ext.isIE && e.type == 'keydown') || e.type == 'keypress') && e.isSpecialKey()) {
	    	if(e.getKey() == e.ENTER){
	    		if(this.getXType() != "textarea"){
		    		var form = this.findParentByType('form');
		    		this.fireEvent("specialkey",this);
		    		if(form){
			    		Ext.each(form.buttons,function(button){
			    			if(button.url && button.url == form.url){
			    				button.handler.call(button.scope);
			    			}
			    		})
		    		}
	    		}
	    	}
	    }
	},
	initEvents : function() {
	    this.el.on("keydown", this.fireKeys, this);
	    this.el.on("keypress", this.fireKeys, this);
	    this.el.on("keyup", this.fireKeys, this);
	}
})

<|endoftext|>


<commit_before>const _ = require('lodash');
const debug = require('@tryghost/debug');
const logging = require('@tryghost/logging');
const mailgun = require('mailgun-js');

module.exports.BATCH_SIZE = 1000;

module.exports = class MailgunClient {
    #config;
    #settings;

    constructor({config, settings}) {
        this.#config = config;
        this.#settings = settings;
    }

    /**
     * Creates the data payload and sends to Mailgun
     *
     * @param {Object} message
     * @param {Object} recipientData
     * @param {Array<Object>} replacements
     *
     * recipientData format:
     * {
     *     'test@example.com': {
     *         name: 'Test User',
     *         unique_id: '12345abcde',
     *         unsubscribe_url: 'https://example.com/unsub/me'
     *     }
     * }
     */
    send(message, recipientData, replacements) {
        if (recipientData.length > module.exports.BATCH_SIZE) {
            // err - too many recipients
        }

        const mailgunInstance = this.getInstance();
        if (!mailgunInstance) {
            logging.warn(`Mailgun is not configured`);
            return;
        }

        let messageData = {};

        try {
            const bulkEmailConfig = this.#config.get('bulkEmail');
            const messageContent = _.pick(message, 'subject', 'html', 'plaintext');

            // update content to use Mailgun variable syntax for replacements
            replacements.forEach((replacement) => {
                messageContent[replacement.format] = messageContent[replacement.format].replace(
                    replacement.match,
                    `%recipient.${replacement.id}%`
                );
            });

            messageData = {
                to: Object.keys(recipientData),
                from: message.from,
                'h:Reply-To': message.replyTo || message.reply_to,
                subject: messageContent.subject,
                html: messageContent.html,
                text: messageContent.plaintext,
                'recipient-variables': recipientData
            };

            // add a reference to the original email record for easier mapping of mailgun event -> email
            if (message.id) {
                messageData['v:email-id'] = message.id;
            }

            const tags = ['bulk-email'];
            if (bulkEmailConfig && bulkEmailConfig.mailgun && bulkEmailConfig.mailgun.tag) {
                tags.push(bulkEmailConfig.mailgun.tag);
            }
            messageData['o:tag'] = tags;

            if (bulkEmailConfig && bulkEmailConfig.mailgun && bulkEmailConfig.mailgun.testmode) {
                messageData['o:testmode'] = true;
            }

            // enable tracking if turned on for this email
            if (message.track_opens) {
                messageData['o:tracking-opens'] = true;
            }

            return new Promise((resolve, reject) => {
                mailgunInstance.messages().send(messageData, (error, body) => {
                    if (error || !body) {
                        return reject(error);
                    }

                    return resolve({
                        id: body.id
                    });
                });
            });
        } catch (error) {
            return Promise.reject({error, messageData});
        }
    }

    async fetchEvents(mailgunOptions, batchHandler, {maxEvents = Infinity} = {}) {
        let result = [];

        const mailgunInstance = this.getInstance();
        if (!mailgunInstance) {
            logging.warn(`Mailgun is not configured`);
            return result;
        }

        debug(`fetchEvents: starting fetching first events page`);
        let page = await mailgunInstance.events().get(mailgunOptions);
        let events = page && page.items && page.items.map(this.normalizeEvent) || [];
        debug(`fetchEvents: finished fetching first page with ${events.length} events`);

        let eventCount = 0;

        pagesLoop:
        while (events.length !== 0) {
            const batchResult = await batchHandler(events);

            result = result.concat(batchResult);
            eventCount += events.length;

            if (eventCount >= maxEvents) {
                break pagesLoop;
            }

            const nextPageUrl = page.paging.next.replace(/https:\/\/api\.(eu\.)?mailgun\.net\/v3/, '');
            debug(`fetchEvents: starting fetching next page ${nextPageUrl}`);
            page = await mailgunInstance.get(nextPageUrl);
            events = page && page.items && page.items.map(this.normalizeEvent) || [];
            debug(`fetchEvents: finished fetching next page with ${events.length} events`);
        }

        return result;
    }

    normalizeEvent(event) {
        const providerId = event?.message?.headers['message-id'];

        return {
            type: event.event,
            severity: event.severity,
            recipientEmail: event.recipient,
            emailId: event['user-variables'] && event['user-variables']['email-id'],
            providerId: providerId,
            timestamp: new Date(event.timestamp * 1000)
        };
    }

    #getConfig() {
        const bulkEmailConfig = this.#config.get('bulkEmail');
        const bulkEmailSetting = {
            apiKey: this.#settings.get('mailgun_api_key'),
            domain: this.#settings.get('mailgun_domain'),
            baseUrl: this.#settings.get('mailgun_base_url')
        };

        const hasMailgunConfig = !!(bulkEmailConfig && bulkEmailConfig.mailgun);
        const hasMailgunSetting = !!(bulkEmailSetting && bulkEmailSetting.apiKey && bulkEmailSetting.baseUrl && bulkEmailSetting.domain);

        if (!hasMailgunConfig && !hasMailgunSetting) {
            return null;
        }

        const mailgunConfig = hasMailgunConfig ? bulkEmailConfig.mailgun : bulkEmailSetting;
        return mailgunConfig;
    }

    /**
     * Returns an instance of the Mailgun client based upon the config or settings values
     *
     * We don't cache the instance so we can always get a fresh one based upon changed settings
     * or config values over time
     *
     * Note: if the credentials are not configure, this method returns `null` and it is down to the
     * consumer to act upon this/log this out
     *
     * @returns {import('mailgun-js').Mailgun} the Mailgun client instance
     */
    getInstance() {
        const mailgunConfig = this.#getConfig();
        if (!mailgunConfig) {
            return null;
        }

        const baseUrl = new URL(mailgunConfig.baseUrl);

        return mailgun({
            apiKey: mailgunConfig.apiKey,
            domain: mailgunConfig.domain,
            protocol: baseUrl.protocol,
            host: baseUrl.hostname,
            port: baseUrl.port,
            endpoint: baseUrl.pathname,
            retry: 5
        });
    }

    /**
     * Returns whether the Mailgun instance is configured via config/settings
     *
     * @returns {boolean}
     */
    isConfigured() {
        const instance = this.getInstance();
        return !!instance;
    }
};
<commit_msg>Added optional chaining to `mailgun-client` package<commit_after>const _ = require('lodash');
const debug = require('@tryghost/debug');
const logging = require('@tryghost/logging');
const mailgun = require('mailgun-js');

module.exports.BATCH_SIZE = 1000;

module.exports = class MailgunClient {
    #config;
    #settings;

    constructor({config, settings}) {
        this.#config = config;
        this.#settings = settings;
    }

    /**
     * Creates the data payload and sends to Mailgun
     *
     * @param {Object} message
     * @param {Object} recipientData
     * @param {Array<Object>} replacements
     *
     * recipientData format:
     * {
     *     'test@example.com': {
     *         name: 'Test User',
     *         unique_id: '12345abcde',
     *         unsubscribe_url: 'https://example.com/unsub/me'
     *     }
     * }
     */
    send(message, recipientData, replacements) {
        if (recipientData.length > module.exports.BATCH_SIZE) {
            // err - too many recipients
        }

        const mailgunInstance = this.getInstance();
        if (!mailgunInstance) {
            logging.warn(`Mailgun is not configured`);
            return;
        }

        let messageData = {};

        try {
            const bulkEmailConfig = this.#config.get('bulkEmail');
            const messageContent = _.pick(message, 'subject', 'html', 'plaintext');

            // update content to use Mailgun variable syntax for replacements
            replacements.forEach((replacement) => {
                messageContent[replacement.format] = messageContent[replacement.format].replace(
                    replacement.match,
                    `%recipient.${replacement.id}%`
                );
            });

            messageData = {
                to: Object.keys(recipientData),
                from: message.from,
                'h:Reply-To': message.replyTo || message.reply_to,
                subject: messageContent.subject,
                html: messageContent.html,
                text: messageContent.plaintext,
                'recipient-variables': recipientData
            };

            // add a reference to the original email record for easier mapping of mailgun event -> email
            if (message.id) {
                messageData['v:email-id'] = message.id;
            }

            const tags = ['bulk-email'];
            if (bulkEmailConfig?.mailgun?.tag) {
                tags.push(bulkEmailConfig.mailgun.tag);
            }
            messageData['o:tag'] = tags;

            if (bulkEmailConfig?.mailgun?.testmode) {
                messageData['o:testmode'] = true;
            }

            // enable tracking if turned on for this email
            if (message.track_opens) {
                messageData['o:tracking-opens'] = true;
            }

            return new Promise((resolve, reject) => {
                mailgunInstance.messages().send(messageData, (error, body) => {
                    if (error || !body) {
                        return reject(error);
                    }

                    return resolve({
                        id: body.id
                    });
                });
            });
        } catch (error) {
            return Promise.reject({error, messageData});
        }
    }

    async fetchEvents(mailgunOptions, batchHandler, {maxEvents = Infinity} = {}) {
        let result = [];

        const mailgunInstance = this.getInstance();
        if (!mailgunInstance) {
            logging.warn(`Mailgun is not configured`);
            return result;
        }

        debug(`fetchEvents: starting fetching first events page`);
        let page = await mailgunInstance.events().get(mailgunOptions);
        let events = page?.items?.map(this.normalizeEvent) || [];
        debug(`fetchEvents: finished fetching first page with ${events.length} events`);

        let eventCount = 0;

        pagesLoop:
        while (events.length !== 0) {
            const batchResult = await batchHandler(events);

            result = result.concat(batchResult);
            eventCount += events.length;

            if (eventCount >= maxEvents) {
                break pagesLoop;
            }

            const nextPageUrl = page.paging.next.replace(/https:\/\/api\.(eu\.)?mailgun\.net\/v3/, '');
            debug(`fetchEvents: starting fetching next page ${nextPageUrl}`);
            page = await mailgunInstance.get(nextPageUrl);
            events = page?.items?.map(this.normalizeEvent) || [];
            debug(`fetchEvents: finished fetching next page with ${events.length} events`);
        }

        return result;
    }

    normalizeEvent(event) {
        const providerId = event?.message?.headers['message-id'];

        return {
            type: event.event,
            severity: event.severity,
            recipientEmail: event.recipient,
            emailId: event['user-variables'] && event['user-variables']['email-id'],
            providerId: providerId,
            timestamp: new Date(event.timestamp * 1000)
        };
    }

    #getConfig() {
        const bulkEmailConfig = this.#config.get('bulkEmail');
        const bulkEmailSetting = {
            apiKey: this.#settings.get('mailgun_api_key'),
            domain: this.#settings.get('mailgun_domain'),
            baseUrl: this.#settings.get('mailgun_base_url')
        };

        const hasMailgunConfig = !!(bulkEmailConfig?.mailgun);
        const hasMailgunSetting = !!(bulkEmailSetting && bulkEmailSetting.apiKey && bulkEmailSetting.baseUrl && bulkEmailSetting.domain);

        if (!hasMailgunConfig && !hasMailgunSetting) {
            return null;
        }

        const mailgunConfig = hasMailgunConfig ? bulkEmailConfig.mailgun : bulkEmailSetting;
        return mailgunConfig;
    }

    /**
     * Returns an instance of the Mailgun client based upon the config or settings values
     *
     * We don't cache the instance so we can always get a fresh one based upon changed settings
     * or config values over time
     *
     * Note: if the credentials are not configure, this method returns `null` and it is down to the
     * consumer to act upon this/log this out
     *
     * @returns {import('mailgun-js').Mailgun} the Mailgun client instance
     */
    getInstance() {
        const mailgunConfig = this.#getConfig();
        if (!mailgunConfig) {
            return null;
        }

        const baseUrl = new URL(mailgunConfig.baseUrl);

        return mailgun({
            apiKey: mailgunConfig.apiKey,
            domain: mailgunConfig.domain,
            protocol: baseUrl.protocol,
            host: baseUrl.hostname,
            port: baseUrl.port,
            endpoint: baseUrl.pathname,
            retry: 5
        });
    }

    /**
     * Returns whether the Mailgun instance is configured via config/settings
     *
     * @returns {boolean}
     */
    isConfigured() {
        const instance = this.getInstance();
        return !!instance;
    }
};
<|endoftext|>


<commit_before>/*
 * Copyright 2017 Hewlett Packard Enterprise Development Company, L.P.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

define([
    'find/app/model/geography-model',
    'find/app/configuration'
], function(GeographyModel, configuration) {
    'use strict';

    function resetLocationFields() {
        GeographyModel.LocationFields.length = 0;
        _.each(GeographyModel.LocationFieldsById, function(val, key){
            delete GeographyModel.LocationFieldsById[key];
        });
    }

    describe('Geography Model', function() {
        beforeEach(function() {
            resetLocationFields();

            GeographyModel.parseConfiguration({
                map: {
                    "enabled" : true,
                    "locationFields" : [
                        {
                            "id": "DefaultLocation",
                            "displayName": "Default Location",
                            "latitudeField": "latitude",
                            "longitudeField": "longitude",
                            "iconName": null,
                            "iconColor": null,
                            "markerColor": null
                        },
                        {
                            "id": "OGLocation",
                            "displayName": "OG Location",
                            "latitudeField": "oglatitude",
                            "longitudeField": "oglongitude",
                            "iconName": "hp-pin",
                            "iconColor": "blue",
                            "markerColor": "orange"
                        }
                    ]
                },
                fieldsInfo: {
                    "latitude": {
                        "names": [
                            "NODE_PLACE/LAT",
                            "LAT"
                        ],
                        "type": "NUMBER",
                        "advanced": true
                    },
                    "longitude": {
                        "names": [
                            "NODE_PLACE/LON",
                            "LON"
                        ],
                        "type": "NUMBER",
                        "advanced": true
                    },
                    "oglatitude": {
                        "names": [
                            "OG_LATITUDE"
                        ],
                        "type": "NUMBER",
                        "advanced": true
                    },
                    "oglongitude": {
                        "names": [
                            "OG_LONGITUDE"
                        ],
                        "type": "NUMBER",
                        "advanced": true
                    }
                }
            })

            this.model = new GeographyModel({});
        });

        afterEach(function(){
            resetLocationFields();
            GeographyModel.parseConfiguration(configuration());
        })

        describe('toFieldText function', function() {
            it('returns null when no shapes are applied', function() {
                const fieldtext = this.model.toFieldText();
                expect(fieldtext).toBeNull();
            });

            it('returns null when shapes list is empty', function() {
                this.model.set('OGLocation', [])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext).toBeNull();
            });

            it('returns DISTSPHERICAL for a circle', function() {
                this.model.set('OGLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual('DISTSPHERICAL{-7.013,-193.007,3512}:OG_LATITUDE:OG_LONGITUDE');
            });

            it('returns three POLYGON fieldtext (with ±360° offsets) for a polygon', function() {
                this.model.set('OGLocation', [
                    {"type":"polygon","points":[[-12.76,-206.71],[-5.09,-170.51],[-27.21,-168.75],[-29.07,-200.12]]}
                ])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'POLYGON{-12.76,-206.71,-5.09,-170.51,-27.21,-168.75,-29.07,-200.12}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,153.29,-5.09,189.49,-27.21,191.25,-29.07,159.88}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-566.71,-5.09,-530.51,-27.21,-528.75,-29.07,-560.12}:OG_LATITUDE:OG_LONGITUDE');
            });

            it('returns NOT DISTSPHERICAL for a circle exclusion', function() {
                this.model.set('OGLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726,"NOT":true}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual('NOT DISTSPHERICAL{-7.013,-193.007,3512}:OG_LATITUDE:OG_LONGITUDE');
            });

            it('returns NOT POLYGON for a polygon exclusion', function() {
                this.model.set('OGLocation', [
                    {"type":"polygon","points":[[-12.76,-206.71],[-5.09,-170.51],[-27.21,-168.75],[-29.07,-200.12]],"NOT":true}
                ])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'NOT (' +
                    'POLYGON{-12.76,-206.71,-5.09,-170.51,-27.21,-168.75,-29.07,-200.12}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,153.29,-5.09,189.49,-27.21,191.25,-29.07,159.88}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-566.71,-5.09,-530.51,-27.21,-528.75,-29.07,-560.12}:OG_LATITUDE:OG_LONGITUDE' +
                    ')'
                );
            });

            it('returns DISTSPHERICAL OR POLYGON for a circle + polygon', function() {
                this.model.set('OGLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726},
                    {"type":"polygon","points":[[-12.76,-206.71],[-5.09,-170.51],[-27.21,-168.75],[-29.07,-200.12]]}
                ])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'DISTSPHERICAL{-7.013,-193.007,3512}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-206.71,-5.09,-170.51,-27.21,-168.75,-29.07,-200.12}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,153.29,-5.09,189.49,-27.21,191.25,-29.07,159.88}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-566.71,-5.09,-530.51,-27.21,-528.75,-29.07,-560.12}:OG_LATITUDE:OG_LONGITUDE'
                );
            });

            it('returns one DISTSPHERICAL per IDOL field for documents with multiple fields', function() {
                this.model.set('DefaultLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'DISTSPHERICAL{-7.013,-193.007,3512}:NODE_PLACE/LAT:NODE_PLACE/LON OR ' +
                    'DISTSPHERICAL{-7.013,-193.007,3512}:LAT:LON'
                );
            });

            it('returns AND-ed geographic restrictions if multiple fields have geographic restrictions', function() {
                this.model.set('DefaultLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726}]
                )
                this.model.set('OGLocation', [
                    {"type":"circle","center":[40.123,60.321],"radius":123456.1}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    '(' +
                    'DISTSPHERICAL{-7.013,-193.007,3512}:NODE_PLACE/LAT:NODE_PLACE/LON OR ' +
                    'DISTSPHERICAL{-7.013,-193.007,3512}:LAT:LON' +
                    ') AND ' +
                    'DISTSPHERICAL{40.123,60.321,123}:OG_LATITUDE:OG_LONGITUDE'
                );
            });

            it('ignores geographic restrictions from fields which are not configured (e.g. deleted fields in a saved search)', function() {
                this.model.set('NoSuchField', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726}]
                )
                this.model.set('OGLocation', [
                    {"type":"circle","center":[40.123,60.321],"radius":123456.1}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'DISTSPHERICAL{40.123,60.321,123}:OG_LATITUDE:OG_LONGITUDE'
                );
            });
        });
    });
});
<commit_msg>Tests for multiple inclusion and exclusion filters<commit_after>/*
 * Copyright 2017 Hewlett Packard Enterprise Development Company, L.P.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

define([
    'find/app/model/geography-model',
    'find/app/configuration'
], function(GeographyModel, configuration) {
    'use strict';

    function resetLocationFields() {
        GeographyModel.LocationFields.length = 0;
        _.each(GeographyModel.LocationFieldsById, function(val, key){
            delete GeographyModel.LocationFieldsById[key];
        });
    }

    describe('Geography Model', function() {
        beforeEach(function() {
            resetLocationFields();

            GeographyModel.parseConfiguration({
                map: {
                    "enabled" : true,
                    "locationFields" : [
                        {
                            "id": "DefaultLocation",
                            "displayName": "Default Location",
                            "latitudeField": "latitude",
                            "longitudeField": "longitude",
                            "iconName": null,
                            "iconColor": null,
                            "markerColor": null
                        },
                        {
                            "id": "OGLocation",
                            "displayName": "OG Location",
                            "latitudeField": "oglatitude",
                            "longitudeField": "oglongitude",
                            "iconName": "hp-pin",
                            "iconColor": "blue",
                            "markerColor": "orange"
                        }
                    ]
                },
                fieldsInfo: {
                    "latitude": {
                        "names": [
                            "NODE_PLACE/LAT",
                            "LAT"
                        ],
                        "type": "NUMBER",
                        "advanced": true
                    },
                    "longitude": {
                        "names": [
                            "NODE_PLACE/LON",
                            "LON"
                        ],
                        "type": "NUMBER",
                        "advanced": true
                    },
                    "oglatitude": {
                        "names": [
                            "OG_LATITUDE"
                        ],
                        "type": "NUMBER",
                        "advanced": true
                    },
                    "oglongitude": {
                        "names": [
                            "OG_LONGITUDE"
                        ],
                        "type": "NUMBER",
                        "advanced": true
                    }
                }
            })

            this.model = new GeographyModel({});
        });

        afterEach(function(){
            resetLocationFields();
            GeographyModel.parseConfiguration(configuration());
        })

        describe('toFieldText function', function() {
            it('returns null when no shapes are applied', function() {
                const fieldtext = this.model.toFieldText();
                expect(fieldtext).toBeNull();
            });

            it('returns null when shapes list is empty', function() {
                this.model.set('OGLocation', [])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext).toBeNull();
            });

            it('returns DISTSPHERICAL for a circle', function() {
                this.model.set('OGLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual('DISTSPHERICAL{-7.013,-193.007,3512}:OG_LATITUDE:OG_LONGITUDE');
            });

            it('returns three POLYGON fieldtext (with ±360° offsets) for a polygon', function() {
                this.model.set('OGLocation', [
                    {"type":"polygon","points":[[-12.76,-206.71],[-5.09,-170.51],[-27.21,-168.75],[-29.07,-200.12]]}
                ])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'POLYGON{-12.76,-206.71,-5.09,-170.51,-27.21,-168.75,-29.07,-200.12}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,153.29,-5.09,189.49,-27.21,191.25,-29.07,159.88}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-566.71,-5.09,-530.51,-27.21,-528.75,-29.07,-560.12}:OG_LATITUDE:OG_LONGITUDE');
            });

            it('returns NOT DISTSPHERICAL for a circle exclusion', function() {
                this.model.set('OGLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726,"NOT":true}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual('NOT DISTSPHERICAL{-7.013,-193.007,3512}:OG_LATITUDE:OG_LONGITUDE');
            });

            it('returns NOT POLYGON for a polygon exclusion', function() {
                this.model.set('OGLocation', [
                    {"type":"polygon","points":[[-12.76,-206.71],[-5.09,-170.51],[-27.21,-168.75],[-29.07,-200.12]],"NOT":true}
                ])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'NOT (' +
                    'POLYGON{-12.76,-206.71,-5.09,-170.51,-27.21,-168.75,-29.07,-200.12}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,153.29,-5.09,189.49,-27.21,191.25,-29.07,159.88}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-566.71,-5.09,-530.51,-27.21,-528.75,-29.07,-560.12}:OG_LATITUDE:OG_LONGITUDE' +
                    ')'
                );
            });

            it('returns DISTSPHERICAL OR POLYGON for a circle + polygon', function() {
                this.model.set('OGLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726},
                    {"type":"polygon","points":[[-12.76,-206.71],[-5.09,-170.51],[-27.21,-168.75],[-29.07,-200.12]]}
                ])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'DISTSPHERICAL{-7.013,-193.007,3512}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-206.71,-5.09,-170.51,-27.21,-168.75,-29.07,-200.12}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,153.29,-5.09,189.49,-27.21,191.25,-29.07,159.88}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-566.71,-5.09,-530.51,-27.21,-528.75,-29.07,-560.12}:OG_LATITUDE:OG_LONGITUDE'
                );
            });

            it('allow mixing multiple inclusion and exclusion filters', function() {
                this.model.set('OGLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726},
                    {"type":"polygon","points":[[-12.76,-206.71],[-5.09,-170.51],[-27.21,-168.75],[-29.07,-200.12]]},
                    {"type":"circle","center":[40.123,60.321],"radius":123456.1,"NOT":true},
                    {"type":"polygon","points":[[50.76,-206.71],[11.12,-170.51],[17,-168.75]],"NOT":true}
                ])

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    '(' +
                    'DISTSPHERICAL{-7.013,-193.007,3512}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-206.71,-5.09,-170.51,-27.21,-168.75,-29.07,-200.12}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,153.29,-5.09,189.49,-27.21,191.25,-29.07,159.88}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{-12.76,-566.71,-5.09,-530.51,-27.21,-528.75,-29.07,-560.12}:OG_LATITUDE:OG_LONGITUDE' +
                    ') AND NOT (' +
                    'DISTSPHERICAL{40.123,60.321,123}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{50.76,-206.71,11.12,-170.51,17,-168.75}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{50.76,153.29,11.12,189.49,17,191.25}:OG_LATITUDE:OG_LONGITUDE OR ' +
                    'POLYGON{50.76,-566.71,11.12,-530.51,17,-528.75}:OG_LATITUDE:OG_LONGITUDE' +
                    ')'
                );
            });

            it('returns one DISTSPHERICAL per IDOL field for documents with multiple fields', function() {
                this.model.set('DefaultLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'DISTSPHERICAL{-7.013,-193.007,3512}:NODE_PLACE/LAT:NODE_PLACE/LON OR ' +
                    'DISTSPHERICAL{-7.013,-193.007,3512}:LAT:LON'
                );
            });

            it('returns AND-ed geographic restrictions if multiple fields have geographic restrictions', function() {
                this.model.set('DefaultLocation', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726}]
                )
                this.model.set('OGLocation', [
                    {"type":"circle","center":[40.123,60.321],"radius":123456.1}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    '(' +
                    'DISTSPHERICAL{-7.013,-193.007,3512}:NODE_PLACE/LAT:NODE_PLACE/LON OR ' +
                    'DISTSPHERICAL{-7.013,-193.007,3512}:LAT:LON' +
                    ') AND ' +
                    'DISTSPHERICAL{40.123,60.321,123}:OG_LATITUDE:OG_LONGITUDE'
                );
            });

            it('ignores geographic restrictions from fields which are not configured (e.g. deleted fields in a saved search)', function() {
                this.model.set('NoSuchField', [
                    {"type":"circle","center":[-7.013,-193.007],"radius":3511716.726}]
                )
                this.model.set('OGLocation', [
                    {"type":"circle","center":[40.123,60.321],"radius":123456.1}]
                )

                const fieldtext = this.model.toFieldText();
                expect(fieldtext.toString()).toEqual(
                    'DISTSPHERICAL{40.123,60.321,123}:OG_LATITUDE:OG_LONGITUDE'
                );
            });
        });
    });
});
<|endoftext|>


<commit_before>var wd = require('wd');
var finalhandler = require('finalhandler');
var http = require('http');
var serveStatic = require('serve-static');
var childProcess = require('child_process');

// double click is not 'natively' supported, so we need to send the
// event direct to the element see:
// http://stackoverflow.com/questions/3982442/selenium-2-webdriver-how-to-double-click-a-table-row-which-opens-a-new-window
var doubleClickScript = 'var evt = document.createEvent("MouseEvents");' +
  'evt.initMouseEvent("dblclick",true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0,null);' +
  'document.querySelectorAll("#todo-list li label")[arguments[0]].dispatchEvent(evt);';

// Serve up public/ftp folder
var serve = serveStatic('..', {});

// Create server
var server = http.createServer(function (req, res) {
  var done = finalhandler(req, res);
  serve(req, res, done);
});

// Listen
console.log('starting server on port 8000');
server.listen(8000);

// http configuration, not needed for simple runs
wd.configureHttp({
  timeout: 60000,
  retryDelay: 15000,
  retries: 5
});

var webdriverProcess = undefined;

var createBrowser = function () {
  var browser = wd.promiseChainRemote('http://localhost:9515');
  if (process.env.VERBOSE || true) {
    // optional logging
    browser.on('status', function (info) {
      console.log(info.cyan);
    });
    browser.on('command', function (meth, path, data) {
      console.log(' > ' + meth.yellow, path.grey, data || '');
    });
  }
  var initBrowser = function () {
    return browser
      .init()
      .setAsyncScriptTimeout(3000)
      .then(function () {
        return browser;
      });
  };
  return new Promise(function (resolve, reject) {
    console.log('Starting selenium');
    var chromedriverBinPath = require('chromedriver').path;
    console.log('Starting '+ chromedriverBinPath);
    var webdriverProcess = childProcess.spawn(chromedriverBinPath, [], {});
    var resolved = false;
    webdriverProcess.on('close', function(code) {
      webdriverProcess = undefined;
      if (!resolved) {
        console.log('chromedriver exited with code ' + code);
      } else {
        reject('chromedriver exited with code ' + code)
      }
    });
    webdriverProcess.stdout.on('data', function(data) {
      console.log('> ' + data.toString());
      if (!resolved) {
        resolved = true;
        resolve(initBrowser().catch(function(err) { webdriverProcess.kill(); throw err;}));
      }
    });
    webdriverProcess.stderr.on('data', function(data) {
      console.log('! ' + data);
    });
  });
};

var quitBrowser = function (browser, allPassed) {
  if (browser) {
    browser = browser.quit();
    if (webdriverProcess) {
      browser.then(function () {
        console.log('Killing webdriver process');
        webdriverProcess.kill();
      });
    }
  }
  return browser;
};

var setup = {
  rootUrl: 'http://localhost:8000',
  server: server,
  browserCapabilities: { browserName: 'chrome' },
  sauce: false,
  createBrowser: createBrowser, // returns a promise for a browser
  quitBrowser: quitBrowser
};

module.exports = setup;
<commit_msg>closing down browser tests properly<commit_after>var wd = require('wd');
var finalhandler = require('finalhandler');
var http = require('http');
var serveStatic = require('serve-static');
var childProcess = require('child_process');

// double click is not 'natively' supported, so we need to send the
// event direct to the element see:
// http://stackoverflow.com/questions/3982442/selenium-2-webdriver-how-to-double-click-a-table-row-which-opens-a-new-window
var doubleClickScript = 'var evt = document.createEvent("MouseEvents");' +
  'evt.initMouseEvent("dblclick",true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0,null);' +
  'document.querySelectorAll("#todo-list li label")[arguments[0]].dispatchEvent(evt);';

// Serve up public/ftp folder
var serve = serveStatic('..', {});

// Create server
var server = http.createServer(function (req, res) {
  var done = finalhandler(req, res);
  serve(req, res, done);
});

// Listen
console.log('starting server on port 8000');
server.listen(8000);

// http configuration, not needed for simple runs
wd.configureHttp({
  timeout: 60000,
  retryDelay: 15000,
  retries: 5
});

var webdriverProcess = undefined;

var createBrowser = function () {
  var browser = wd.promiseChainRemote('http://localhost:9515');
  if (process.env.VERBOSE || true) {
    // optional logging
    browser.on('status', function (info) {
      console.log(info.cyan);
    });
    browser.on('command', function (meth, path, data) {
      console.log(' > ' + meth.yellow, path.grey, data || '');
    });
  }
  var initBrowser = function () {
    return browser
      .init()
      .setAsyncScriptTimeout(3000)
      .then(function () {
        return browser;
      });
  };
  return new Promise(function (resolve, reject) {
    console.log('Starting selenium');
    var chromedriverBinPath = require('chromedriver').path;
    console.log('Starting '+ chromedriverBinPath);
    webdriverProcess = childProcess.spawn(chromedriverBinPath, [], {});
    var resolved = false;
    webdriverProcess.on('close', function(code) {
      webdriverProcess = undefined;
      if (!resolved) {
        console.log('chromedriver exited with code ' + code);
      } else {
        reject('chromedriver exited with code ' + code)
      }
    });
    webdriverProcess.stdout.on('data', function(data) {
      console.log('> ' + data.toString());
      if (!resolved) {
        resolved = true;
        resolve(initBrowser().catch(function(err) { webdriverProcess.kill(); throw err;}));
      }
    });
    webdriverProcess.stderr.on('data', function(data) {
      console.log('! ' + data);
    });
  });
};

var quitBrowser = function (browser, allPassed) {
  if (browser) {
    browser = browser.quit();
    if (webdriverProcess) {
      browser.then(function () {
        console.log('Killing webdriver process');
        webdriverProcess.kill();
      });
    }
    if (server) {
      server.close();
    }
  }
  return browser;
};

var setup = {
  rootUrl: 'http://localhost:8000',
  server: server,
  browserCapabilities: { browserName: 'chrome' },
  sauce: false,
  createBrowser: createBrowser, // returns a promise for a browser
  quitBrowser: quitBrowser
};

module.exports = setup;
<|endoftext|>


<commit_before>odoo.define('website_product_configurator.config_form', function (require) {
    'use strict';

    var ajax = require('web.ajax');

	$(document).ready(function () {
	    var config_form = $("#product_config_form");

	 	/* Monitor input changes in the configuration form and call the backend onchange method*/
	 	config_form.find('.config_attribute').change(function(ev) {
			ajax.jsonRpc("/website_product_configurator/onchange", 'call', {
                form_values: config_form.serializeArray(),
                field_name: $(this)[0].name,
            }).then(function(data) {
                if (data.error) {
                    alert(data.error);
                };
                var values = data.value;
                var domains = data.domain;
                var open_cfg_step_lines = data.open_cfg_step_lines;

                _applyDomainOnValues(domains);
                _handleOpenSteps(open_cfg_step_lines)

            });
            if ($(ev.currentTarget.selectedOptions[0]).hasClass('custom_config_attr_value')) {
                config_form.find('.custom_config_value').parent().removeClass('hidden');
            }
		});

        function _handleOpenSteps(open_cfg_step_lines) {
            var $steps = config_form.find('.config_step');
            _.each($steps, function (step) {
                step = $(step);
                var step_id = step.attr('data-step-id');
                if ($.inArray(parseInt(step_id), open_cfg_step_lines) < 0) {
                    if (!step.hasClass('hidden')) {
                        step.addClass('hidden');
                    };
                } else {
                    if (step.hasClass('hidden')) {
                        step.removeClass('hidden');
                    };
                };
            });
        }

        function _applyDomainOnValues(domains) {
            _.each(domains, function (domain, attr_id) {
                var $selection = config_form.find('#' + attr_id);
                var $options = $selection.find('.config_attr_value');
                _.each($options, function (option) {
                    var condition = domain[0][1];
                    if (condition == 'in' || config_form == '=') {
                        if ($.inArray(parseInt(option.value), domain[0][2]) < 0) {
                            $(option).attr('disabled', true);
                            if (option.selected) {
                                option.selected = false;
                            };
                        } else {
                            $(option).attr('disabled', false);
                        };
                    } else if (condition == 'not in' || config_form == '!=') {
                        if ($.inArray(parseInt(option.value), domain[0][2]) < 0) {
                            $(option).attr('disabled', false);
                        } else {
                            $(option).attr('disabled', true);
                            if (option.selected) {
                                option.selected = false;
                            };
                        };
                    };
                });
            });
        }

        function _onChangeConfigStep(event, next_step) {
            var flag = _checkRequiredFields(event)
            var config_step_header = config_form.find('.nav.nav-tabs');
            var current_config_step = config_step_header.find('.nav-item.config_step.active').attr('data-step-id');
            if (flag) {
                return ajax.jsonRpc("/website_product_configurator/save_configuration", 'call', {
                    form_values: config_form.serializeArray(),
                    next_step: next_step || false,
                    current_step: current_config_step || false,
                }).then(function(data) {
                    if (data.error) {
                        alert(data.error);
                    };
                    return data;
                });
            } else {
                return false;
            }
        };

        function _displayTooltip(config_attribut) {
            $(config_attribut).focus();
            $(config_attribut).tooltip({
                title: "Please select an item in the list.",
                placement: "bottom",
                trigger: "manual",
                delay: {show: 500, hide: 500},
                template: "

"
            }).tooltip('show');
            setTimeout(function(){
                $(config_attribut).tooltip('hide');
            }, 2000);
        };

        function _checkRequiredFields(event) {
            var active_step = config_form.find('.tab-content').find('.tab-pane.active.in');
            var config_attr = active_step.find('.form-control.config_attribute');
            var flag = true;
            for (var i = 0; i < config_attr.length; i++) {
               if ($(config_attr[i]).hasClass('required_config_attrib') && !config_attr[i].value) {
                    flag = false;
                    _displayTooltip(config_attr[i]);
                    break;
               };
            };
            return flag;
        };

        config_form.find('.config_step').click(function (event) {
            var next_step = event.currentTarget.getAttribute('data-step-id');
            var result = _onChangeConfigStep(event, next_step);
            if (!result) {
                event.preventDefault();
                event.stopPropagation();
            };
        });

        function _openNextStep(step) {
            var config_step_header = config_form.find('.nav.nav-tabs');
            var config_step = config_step_header.find('.nav-item.config_step.active');
            if (config_step.length) {
                config_step.removeClass('active');
            }
            var active_step = config_form.find('.tab-content').find('.tab-pane.active.in');
            active_step.removeClass('active in');

            var next_step = config_step_header.find('.nav-item.config_step[data-step-id=' + step + ']');
            if (next_step.length) {
                next_step.addClass('active');
                var selector = next_step.find('a:first-child').attr('href');
                var step_to_active = config_form.find('.tab-content').find(selector);
                step_to_active.addClass('active in');
            }
        };

        function addRequiredAttr(config_step) {
            config_step = config_form.find('.tab-content').find('tab-pane container[data-step-id=' + config_step + ']');
            _.each(config_step.find('.form-control.config_attribute'), function(attribute_field) {
                $(attribute_field).attr('required', true);
            });
        };

        config_form.submit(function (event) {
            event.preventDefault();
            event.stopPropagation();

            var result = _onChangeConfigStep(event);
            if (result) {
                result.then(function (data) {
                    if (data) {
                        if (data.next_step) {
                            _openNextStep(data.next_step);
                        };
                        if (data.redirect_url) {
                            window.location = data.redirect_url;
                        };
                    };
                });
            }
        });
	 });

});
<commit_msg>[IMP]website_product_configurator : check required on custom attribute<commit_after>odoo.define('website_product_configurator.config_form', function (require) {
    'use strict';

    var ajax = require('web.ajax');

	$(document).ready(function () {
	    var config_form = $("#product_config_form");

	 	/* Monitor input changes in the configuration form and call the backend onchange method*/
	 	config_form.find('.config_attribute').change(function(ev) {
			ajax.jsonRpc("/website_product_configurator/onchange", 'call', {
                form_values: config_form.serializeArray(),
                field_name: $(this)[0].name,
            }).then(function(data) {
                if (data.error) {
                    alert(data.error);
                };
                var values = data.value;
                var domains = data.domain;
                var open_cfg_step_lines = data.open_cfg_step_lines;

                _applyDomainOnValues(domains);
                _handleOpenSteps(open_cfg_step_lines)

            });
            _handleCustomAttribute(ev)
		});

        function _handleCustomAttribute(event) {
            var container = $(event.currentTarget).closest('.tab-pane.container');
            var attribute_id = $(event.currentTarget).attr('data-oe-id');
            var custom_value = container.find('.custom_config_value[data-oe-id=' + attribute_id + ']');
            var custom_value_container = custom_value.parent();
            if ($(event.currentTarget.selectedOptions[0]).hasClass('custom_config_attr_value') && custom_value_container.hasClass('hidden')) {
                custom_value_container.removeClass('hidden');
                var is_required = $(event.currentTarget).hasClass('required_config_attrib');
                if (is_required) {
                    custom_value.addClass('required_config_attrib');
                }
            } else if (!custom_value_container.hasClass('hidden')){
                custom_value_container.addClass('hidden');
                if (custom_value.hasClass('required_config_attrib')) {
                    custom_value.removeClass('required_config_attrib');
                }
            }
        }

        function _handleOpenSteps(open_cfg_step_lines) {
            var $steps = config_form.find('.config_step');
            _.each($steps, function (step) {
                step = $(step);
                var step_id = step.attr('data-step-id');
                if ($.inArray(parseInt(step_id), open_cfg_step_lines) < 0) {
                    if (!step.hasClass('hidden')) {
                        step.addClass('hidden');
                    };
                } else {
                    if (step.hasClass('hidden')) {
                        step.removeClass('hidden');
                    };
                };
            });
        }

        function _applyDomainOnValues(domains) {
            _.each(domains, function (domain, attr_id) {
                var $selection = config_form.find('#' + attr_id);
                var $options = $selection.find('.config_attr_value');
                _.each($options, function (option) {
                    var condition = domain[0][1];
                    if (condition == 'in' || config_form == '=') {
                        if ($.inArray(parseInt(option.value), domain[0][2]) < 0) {
                            $(option).attr('disabled', true);
                            if (option.selected) {
                                option.selected = false;
                            };
                        } else {
                            $(option).attr('disabled', false);
                        };
                    } else if (condition == 'not in' || config_form == '!=') {
                        if ($.inArray(parseInt(option.value), domain[0][2]) < 0) {
                            $(option).attr('disabled', false);
                        } else {
                            $(option).attr('disabled', true);
                            if (option.selected) {
                                option.selected = false;
                            };
                        };
                    };
                });
            });
        }

        function _onChangeConfigStep(event, next_step) {
            var flag = _checkRequiredFields(event)
            var config_step_header = config_form.find('.nav.nav-tabs');
            var current_config_step = config_step_header.find('.nav-item.config_step.active').attr('data-step-id');
            if (flag) {
                return ajax.jsonRpc("/website_product_configurator/save_configuration", 'call', {
                    form_values: config_form.serializeArray(),
                    next_step: next_step || false,
                    current_step: current_config_step || false,
                }).then(function(data) {
                    if (data.error) {
                        alert(data.error);
                    };
                    return data;
                });
            } else {
                return false;
            }
        };

        function _displayTooltip(config_attribut) {
            $(config_attribut).focus();
            $(config_attribut).tooltip({
                title: "Please select an item in the list.",
                placement: "bottom",
                trigger: "manual",
                delay: {show: 500, hide: 500},
                template: "

"
            }).tooltip('show');
            setTimeout(function(){
                $(config_attribut).tooltip('hide');
            }, 2000);
        };

        function _checkRequiredFields(event) {
            var active_step = config_form.find('.tab-content').find('.tab-pane.active.in');
            var config_attr = active_step.find('.form-control.required_config_attrib');
            var flag = true;
            for (var i = 0; i < config_attr.length; i++) {
               if (!config_attr[i].value) {
                    flag = false;
                    _displayTooltip(config_attr[i]);
                    break;
               };
            };
            return flag;
        };

        config_form.find('.config_step').click(function (event) {
            var next_step = event.currentTarget.getAttribute('data-step-id');
            var result = _onChangeConfigStep(event, next_step);
            if (!result) {
                event.preventDefault();
                event.stopPropagation();
            };
        });

        function _openNextStep(step) {
            var config_step_header = config_form.find('.nav.nav-tabs');
            var config_step = config_step_header.find('.nav-item.config_step.active');
            if (config_step.length) {
                config_step.removeClass('active');
            }
            var active_step = config_form.find('.tab-content').find('.tab-pane.active.in');
            active_step.removeClass('active in');

            var next_step = config_step_header.find('.nav-item.config_step[data-step-id=' + step + ']');
            if (next_step.length) {
                next_step.addClass('active');
                var selector = next_step.find('a:first-child').attr('href');
                var step_to_active = config_form.find('.tab-content').find(selector);
                step_to_active.addClass('active in');
            }
        };

        function addRequiredAttr(config_step) {
            config_step = config_form.find('.tab-content').find('tab-pane container[data-step-id=' + config_step + ']');
            _.each(config_step.find('.form-control.config_attribute'), function(attribute_field) {
                $(attribute_field).attr('required', true);
            });
        };

        config_form.submit(function (event) {
            event.preventDefault();
            event.stopPropagation();

            var result = _onChangeConfigStep(event);
            if (result) {
                result.then(function (data) {
                    if (data) {
                        if (data.next_step) {
                            _openNextStep(data.next_step);
                        };
                        if (data.redirect_url) {
                            window.location = data.redirect_url;
                        };
                    };
                });
            }
        });
	 });

});
<|endoftext|>


<commit_before>function andify (array, separator = ', ') {
  if (array.length === 1) {
    return array
  } else if (array.length === 2) {
    return [array[0], {text: ' and '}, array[1]]
  } else {
    return _.chain(array)
      .slice(0, -2)
      .map(item => [item, {text: separator}])
      .flatten()
      .concat(_.slice(array, -2, -1)[0])
      .concat({text: `${separator}and `})
      .concat(_.slice(array, -1)[0])
      .value()
  }
}

function colorizeOpen (obj) {
  if (obj.name) {
    return {text: obj.name, argument: obj.type}
  } else if (obj.url) {
    return {text: obj.url, argument: 'url'}
  } else if (obj.path) {
    return {text: obj.path, argument: 'path'}
  }
}

function outputify (objs) {
  const outputs = _.map(objs, colorizeOpen)
  return andify(outputs)
}

function outputifyOpen (objs) {
  const groups = _.groupBy(objs, obj => obj.type || Object.keys(obj)[0])
  const message = []

  if (groups.application) {
    message.push(_.flatten([{text: 'launch ', category: 'action'}, outputify(groups.application)]))
  }
  if (groups.url) {
    message.push(_.flatten([{text: 'load ', category: 'action'}, outputify(groups.url), {text: ' in '}, {text: 'the default browser', argument: 'application'}]))
  }
  if (groups.path) {
    message.push(_.flatten([{text: 'open ', category: 'action'}, outputify(groups.path), {text: ' in '}, {text: 'the default application', argument: 'application'}]))
  }
  if (groups['preference-pane']) {
    message.push(_.flatten([{text: 'open ', category: 'action'}, {text: ' the '}, outputify(groups['preference-pane']), {text: ' system preference pane'}]))
  }

  return _.flatten(andify(message, '; '))
}

export default function demoExecute (result) {
  if (result.verb === 'open') {
    if (result.apps) {
      return _.flatten([
        {text: 'open ', category: 'action'},
        outputify(result.items),
        {text: ' in '},
        outputify(result.apps),
      ])
    } else {
      return outputifyOpen(result.items)
    }
  } else if (result.verb === 'switch') {
    return _.flatten([
      {text: 'switch focus to ', category: 'action'},
      colorizeOpen(result.item)
    ])
  } else if (result.verb === 'close') {
    return _.flatten([
      {text: 'close every window of ', category: 'action'},
      outputify(result.items)
    ])
  } else if (result.verb === 'quit') {
    return _.flatten([
      {text: 'quit ', category: 'action'},
      outputify(result.items)
    ])
  } else if (result.verb === 'kill') {
    return _.flatten([
      {text: 'kill all ', category: 'action'},
      outputify(result.items),
      {text: ' processes'}
    ])
  } else if (result.verb === 'eject') {
    return _.flatten([
      {text: 'eject ', category: 'action'},
      outputify(result.items)
    ])
  }
}
<commit_msg>fix demo<commit_after>function andify (array, separator = ', ') {
  if (array.length === 1) {
    return array
  } else if (array.length === 2) {
    return [array[0], {text: ' and '}, array[1]]
  } else {
    return _.chain(array)
      .slice(0, -2)
      .map(item => [item, {text: separator}])
      .flatten()
      .concat(_.slice(array, -2, -1)[0])
      .concat({text: `${separator}and `})
      .concat(_.slice(array, -1)[0])
      .value()
  }
}

function colorizeOpen (obj) {
  if (obj.name) {
    return {text: obj.name, argument: obj.type}
  } else if (obj.url) {
    return {text: obj.url, argument: 'url'}
  } else if (obj.path) {
    return {text: obj.path, argument: 'path'}
  }
}

function outputify (objs) {
  const outputs = _.map(objs, colorizeOpen)
  return andify(outputs)
}

function outputifyOpen (objs) {
  const groups = _.groupBy(objs, obj => obj.type || Object.keys(obj)[0])
  const message = []

  if (groups.application) {
    message.push(_.flatten([{text: 'launch ', category: 'action'}, outputify(groups.application)]))
  }
  if (groups.url) {
    message.push(_.flatten([{text: 'load ', category: 'action'}, outputify(groups.url), {text: ' in '}, {text: 'the default browser', argument: 'application'}]))
  }
  if (groups.path) {
    message.push(_.flatten([{text: 'open ', category: 'action'}, outputify(groups.path), {text: ' in '}, {text: 'the default application', argument: 'application'}]))
  }
  if (groups['preference pane']) {
    message.push(_.flatten([{text: 'open ', category: 'action'}, {text: ' the '}, outputify(groups['preference pane']), {text: ' system preference pane'}]))
  }

  return _.flatten(andify(message, '; '))
}

export default function demoExecute (result) {
  if (result.verb === 'open') {
    if (result.apps) {
      return _.flatten([
        {text: 'open ', category: 'action'},
        outputify(result.items),
        {text: ' in '},
        outputify(result.apps),
      ])
    } else {
      return outputifyOpen(result.items)
    }
  } else if (result.verb === 'switch') {
    return _.flatten([
      {text: 'switch focus to ', category: 'action'},
      colorizeOpen(result.item)
    ])
  } else if (result.verb === 'close') {
    return _.flatten([
      {text: 'close every window of ', category: 'action'},
      outputify(result.items)
    ])
  } else if (result.verb === 'quit') {
    return _.flatten([
      {text: 'quit ', category: 'action'},
      outputify(result.items)
    ])
  } else if (result.verb === 'kill') {
    return _.flatten([
      {text: 'kill all ', category: 'action'},
      outputify(result.items),
      {text: ' processes'}
    ])
  } else if (result.verb === 'eject') {
    return _.flatten([
      {text: 'eject ', category: 'action'},
      outputify(result.items)
    ])
  }
}
<|endoftext|>