<commit_before>var tswlairmgr = tswlairmgr || {};
tswlairmgr.core = tswlairmgr.core || {};

tswlairmgr.core.persistentstate = new function() {
	this._coreStateData = {
		activeLocalization: null,
		activeModule: null
	};
	this._moduleStateStruct = {
		v: tswlairmgr.core.info.version,
		m: {}
	};

	this.observables = {
		hashLoaded: new tswlairmgr.core.helpers.Observable(this)
	};

	this._loadStateFromHash = function(hash) {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadStateFromHash called");

		var data = (hash.charAt(0) == "#") ? hash.substring(1) : hash;

		var blocks = data.split(":");

		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadStateFromHash: trying to set localization to <"+blocks[0]+">...");
		tswlairmgr.modules.setInterfaceAndDataLocalizationById(blocks[0]);

		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadStateFromHash: trying to set active module to <"+blocks[1]+">...");
		tswlairmgr.modules.setActiveModuleById(blocks[1]);

		this._loadModuleStateFromHash(blocks[2]);

		this._stateChanged();

		this.observables.hashLoaded.notify({});
	};

	this._loadModuleStateFromHash = function(packedModuleState) {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash called");

		if(!packedModuleState || packedModuleState.length === 0 || packedModuleState.indexOf(".") === -1)
		{
			if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: warning: empty or invalid module state hash!");
			return;
		}

		var parts = packedModuleState.split(".");
		var encodedCompressedStringifiedData = parts[0];
		var claimedChecksum = parts[1];

		var compressedStringifiedData = tswlairmgr.core.helpers.Base64URL.decode(encodedCompressedStringifiedData);

		var actualChecksum = tswlairmgr.core.helpers.CRC32.textChecksum(compressedStringifiedData);
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: data checksum is <"+actualChecksum+">");

		var parsed;

		if(actualChecksum !== claimedChecksum)
		{
			if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: warning: invalid checksum <"+claimedChecksum+">!");
		}
		else
		{
			if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: trying to decompress and parse JSON...");
			try
			{
				parsed = JSON.parse(lzw_decode(compressedStringifiedData));
				/*if(parsed.v === tswlairmgr.core.info.version)
				{*/
					if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: data loaded.");
					this._moduleStateStruct.m = parsed.m;
				/*}
				else
				{
					if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: warning: version <"+parsed.v+"> does not match expected <"+tswlairmgr.core.info.version+">!");
				}*/
			}
			catch(e)
			{
				if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: warning: invalid data!");
			}
		}
	};

	this.getModuleState = function(module) {
		var moduleId = module.id;

		return this._moduleStateStruct.m[moduleId];
	};

	this.updateActiveLocalizationId = function(newId) {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: updateActiveLocalizationId: to <"+newId+">");
		this._coreStateData.activeLocalization = newId;
		this._stateChanged();
	};

	this.updateActiveModuleId = function(newId) {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: updateActiveModuleId: to <"+newId+">");
		this._coreStateData.activeModule = newId;
		this._stateChanged();
	};

	this.updateModuleState = function(module, newState) {
		var moduleId = module.id;
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: updateModuleState called from <"+moduleId+">");

		this._moduleStateStruct.m[moduleId] = newState;
		this._stateChanged();
	};

	this._stateChanged = function() {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: stateChanged called");
		var newHash = this.hashify();
		this._setHash(newHash);
	};

	this._setHash = function(string) {
		window.location.hash = string;

		// Bugfix for FF: Disappearing favicon on window.location change
		$("link[rel*=icon]", $("head")).detach().appendTo($("head"));
	};

	this.hashify = function() {
		var hash = this._coreStateData.activeLocalization + ":" +
			this._coreStateData.activeModule + ":" +
			this.hashifyModuleState();

		return hash;
	};

	this.hashifyModuleState = function() {
		var compressedStringifiedData = lzw_encode(JSON.stringify(this._moduleStateStruct));
		var textChecksum = tswlairmgr.core.helpers.CRC32.textChecksum(compressedStringifiedData);

		var encodedCompressedStringifiedData = tswlairmgr.core.helpers.Base64URL.encode(compressedStringifiedData);
		var packedDataWithChecksum = encodedCompressedStringifiedData + "." + textChecksum;

		return packedDataWithChecksum;
	};

	this._initPersistentStateController = function() {

	};
};<commit_msg>Missing additional logic for previous bugfix<commit_after>var tswlairmgr = tswlairmgr || {};
tswlairmgr.core = tswlairmgr.core || {};

tswlairmgr.core.persistentstate = new function() {
	this._coreStateData = {
		activeLocalization: null,
		activeModule: null
	};
	this._moduleStateStruct = {
		v: tswlairmgr.core.info.version,
		m: {}
	};

	this.observables = {
		hashLoaded: new tswlairmgr.core.helpers.Observable(this)
	};

	this._loadStateFromHash = function(hash) {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadStateFromHash called");

		var data = (hash.charAt(0) == "#") ? hash.substring(1) : hash;

		var blocks = data.split(":");

		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadStateFromHash: trying to set localization to <"+blocks[0]+">...");
		tswlairmgr.modules.setInterfaceAndDataLocalizationById(blocks[0]);
		tswlairmgr.modules._redrawLocalizationMenu();

		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadStateFromHash: trying to set active module to <"+blocks[1]+">...");
		tswlairmgr.modules.setActiveModuleById(blocks[1]);

		this._loadModuleStateFromHash(blocks[2]);

		this._stateChanged();

		this.observables.hashLoaded.notify({});
	};

	this._loadModuleStateFromHash = function(packedModuleState) {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash called");

		if(!packedModuleState || packedModuleState.length === 0 || packedModuleState.indexOf(".") === -1)
		{
			if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: warning: empty or invalid module state hash!");
			return;
		}

		var parts = packedModuleState.split(".");
		var encodedCompressedStringifiedData = parts[0];
		var claimedChecksum = parts[1];

		var compressedStringifiedData = tswlairmgr.core.helpers.Base64URL.decode(encodedCompressedStringifiedData);

		var actualChecksum = tswlairmgr.core.helpers.CRC32.textChecksum(compressedStringifiedData);
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: data checksum is <"+actualChecksum+">");

		var parsed;

		if(actualChecksum !== claimedChecksum)
		{
			if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: warning: invalid checksum <"+claimedChecksum+">!");
		}
		else
		{
			if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: trying to decompress and parse JSON...");
			try
			{
				parsed = JSON.parse(lzw_decode(compressedStringifiedData));

				if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: data loaded.");
				this._moduleStateStruct.m = parsed.m;
			}
			catch(e)
			{
				if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: loadModuleStateFromHash: warning: invalid data!");
			}
		}
	};

	this.getModuleState = function(module) {
		var moduleId = module.id;

		return this._moduleStateStruct.m[moduleId];
	};

	this.updateActiveLocalizationId = function(newId) {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: updateActiveLocalizationId: to <"+newId+">");
		this._coreStateData.activeLocalization = newId;
		this._stateChanged();
	};

	this.updateActiveModuleId = function(newId) {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: updateActiveModuleId: to <"+newId+">");
		this._coreStateData.activeModule = newId;
		this._stateChanged();
	};

	this.updateModuleState = function(module, newState) {
		var moduleId = module.id;
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: updateModuleState called from <"+moduleId+">");

		this._moduleStateStruct.m[moduleId] = newState;
		this._stateChanged();
	};

	this._stateChanged = function() {
		if(tswlairmgr.core.config.debug) console.log("<tswlairmgr.core.persistentstate>: stateChanged called");
		var newHash = this.hashify();
		this._setHash(newHash);
	};

	this._setHash = function(string) {
		window.location.hash = string;

		// Bugfix for FF: Disappearing favicon on window.location change
		$("link[rel*=icon]", $("head")).detach().appendTo($("head"));
	};

	this.hashify = function() {
		var hash = this._coreStateData.activeLocalization + ":" +
			this._coreStateData.activeModule + ":" +
			this.hashifyModuleState();

		return hash;
	};

	this.hashifyModuleState = function() {
		var compressedStringifiedData = lzw_encode(JSON.stringify(this._moduleStateStruct));
		var textChecksum = tswlairmgr.core.helpers.CRC32.textChecksum(compressedStringifiedData);

		var encodedCompressedStringifiedData = tswlairmgr.core.helpers.Base64URL.encode(compressedStringifiedData);
		var packedDataWithChecksum = encodedCompressedStringifiedData + "." + textChecksum;

		return packedDataWithChecksum;
	};

	this._initPersistentStateController = function() {

	};
};<|endoftext|>


<commit_before>var request = require('request');
var separator = require('simple-random-id')();

var page = "The_Wall_Street_Journal";
var apiURL = "http://en.wikipedia.org/w/api.php?format=json&action=query&prop=revisions&rvprop=content&titles=" + page;
var wikiURL = "http://en.wikipedia.org/wiki/";

request.get(apiURL, function(error, data, body){
  if (error) {
    console.log('ERROR: ', error);
    return;
  }

  var content = JSON.parse(body);
  content = content.query.pages;

  var page = Object.keys(content);
  content = content[page].revisions[0]['*'];

  var startingPointRegex = /\{\{\s*[Ii]nfobox/;

  var macz = content.match(startingPointRegex).index;

  var end = parse(content.substr(macz, content.length));

  content = content.substr(macz+2, end);

  content = content.replace(/\n/g, ' ');
  var result = content.match(/\[\[(.+?)\]\]|\{\{(.+?)\}\}/ig);

  result.forEach(function(link) {
    content = content.replace(link, link.replace(/\|/g, separator));
  });

  content = content.split('|');
  content.shift();
  var output = {};
  content.forEach(function(element) {
    var splited = element.split('=');
    splited = splited.map(function(el) {
      return el.trim();
    });

    // This catch is needed because sometimes Infobox ends with |}} what is
    // not right (think of it as of additional coma at the end of the array)
    // I know it's not the best way to fix it, but it's the fastest.
    try {

      output[splited[0]] = stringToObject(splited[0], splited[1].replace(new RegExp(separator, 'g'), '|'));

    } catch(e) {
      console.log('Error, could not parse: ', element);
    }

  });

  console.log(output);

});

var stringToObject = function(name, value) {
  //var match = link.match(/\[\[(.*)\]\]/);
  var matches = [];
  value.replace(/\[\[(.*?)\]\]/g, function(g0,g1){matches.push(g1);})
  if (matches.length > 0) {
    var results = [];
    var obj = {
      type: "link"
    };
    matches.forEach(function(matchElement) {
      obj = {
        type: "link"
      }

      if (matchElement.indexOf('File:') > -1 || matchElement.indexOf('Image:') > -1) {
        obj.type = "image";
      }
      matchElement = matchElement.split('|');
      if (matchElement.length > 1) {
        obj.text = matchElement[1];
        obj.url = wikiURL + matchElement[0];
      } else {
        obj.text = matchElement[0];
        obj.url = wikiURL + matchElement[0];
      }
      results.push(obj);
    });

    if (results.length === 1) {
      results = results.pop();
    }
    return results;

  } else {
    return value;
  }
}

var parse = function(text) {
  var brackets = 0;

  for (var i=0, l=text.length; i<l; i++) {
    if (text.charAt(i) === '{') {
      brackets++;
    } else if (text.charAt(i) === '}') {
      brackets--;
    }

    if (brackets === 0 && i > 0) {
      return i-1;
    }
  }
}
<commit_msg>[WIKI-INFOBOX] - :corn: introduced language variable<commit_after>var request = require('request');
var separator = require('simple-random-id')();

var page = "The_Wall_Street_Journal";
var language = "en";
var apiURL = "http://"+ language + ".wikipedia.org/w/api.php?format=json&action=query&prop=revisions&rvprop=content&titles=" + page;
var wikiURL = "http://" + language +".wikipedia.org/wiki/";

request.get(apiURL, function(error, data, body){
  if (error) {
    console.log('ERROR: ', error);
    return;
  }

  var content = JSON.parse(body);
  content = content.query.pages;

  var page = Object.keys(content);
  content = content[page].revisions[0]['*'];

  var startingPointRegex = /\{\{\s*[Ii]nfobox/;

  var macz = content.match(startingPointRegex).index;

  var end = parse(content.substr(macz, content.length));

  content = content.substr(macz+2, end);

  content = content.replace(/\n/g, ' ');
  var result = content.match(/\[\[(.+?)\]\]|\{\{(.+?)\}\}/ig);

  result.forEach(function(link) {
    content = content.replace(link, link.replace(/\|/g, separator));
  });

  content = content.split('|');
  content.shift();
  var output = {};
  content.forEach(function(element) {
    var splited = element.split('=');
    splited = splited.map(function(el) {
      return el.trim();
    });

    // This catch is needed because sometimes Infobox ends with |}} what is
    // not right (think of it as of additional coma at the end of the array)
    // I know it's not the best way to fix it, but it's the fastest.
    try {

      output[splited[0]] = stringToObject(splited[0], splited[1].replace(new RegExp(separator, 'g'), '|'));

    } catch(e) {
      console.log('Error, could not parse: ', element);
    }

  });

  console.log(output);

});

var stringToObject = function(name, value) {
  //var match = link.match(/\[\[(.*)\]\]/);
  var matches = [];
  value.replace(/\[\[(.*?)\]\]/g, function(g0,g1){matches.push(g1);})
  if (matches.length > 0) {
    var results = [];
    var obj = {
      type: "link"
    };
    matches.forEach(function(matchElement) {
      obj = {
        type: "link"
      }

      if (matchElement.indexOf('File:') > -1 || matchElement.indexOf('Image:') > -1) {
        obj.type = "image";
      }
      matchElement = matchElement.split('|');
      if (matchElement.length > 1) {
        obj.text = matchElement[1];
        obj.url = wikiURL + matchElement[0];
      } else {
        obj.text = matchElement[0];
        obj.url = wikiURL + matchElement[0];
      }
      results.push(obj);
    });

    if (results.length === 1) {
      results = results.pop();
    }
    return results;

  } else {
    return value;
  }
}

var parse = function(text) {
  var brackets = 0;

  for (var i=0, l=text.length; i<l; i++) {
    if (text.charAt(i) === '{') {
      brackets++;
    } else if (text.charAt(i) === '}') {
      brackets--;
    }

    if (brackets === 0 && i > 0) {
      return i-1;
    }
  }
}
<|endoftext|>


<commit_before>module.exports = function (debug) {
    'use strict';

    var fs = require('fs-extra'),
        path = require('path'),
        Stylco;

    /**
     * Initialize new instance of Stylco
     * @param {boolean} debug Set true to enable debug mode
     * @constructor
     */
    Stylco = function (debug) {
        this._init(debug);
    };

    /**
     * Name of the configuration file (both module-level defaults and user-level overrides)
     * @type {string}
     */
    Stylco.prototype.CONFIG_FILE_NAME = '.stylcorc';

    /**
     * Module configuration options
     * @type {StylcoConfig}
     * @private
     */
    Stylco.prototype._config = undefined;

    /**
     * Indicates whether debug mode is enabled
     * @type {boolean}
     * @private
     */
    Stylco.prototype._debug = undefined;

    /**
     * Create Stylus component file/directory structure
     * @param {string} destination Destination directory (relative to `basedir` config param)
     * @param {string} [extend_from] Directory of component to extend from (relative to `basedir` config param)
     */
    Stylco.prototype.create = function (destination, extend_from) {
        var _this = this,
            _config = this._config,
            component_name = path.basename(destination),
            destination_dir = path.join(this._config.basedir, destination),
            stats,
            extend_from_relative,
            files_create_result,
            buildfile_path,
            buildfile_existed,
            buildfile_line,
            buildfile_content_existing,
            buildfile_content_appended;

        // Check there is no existing directory of file
        try {
            stats = fs.statSync(destination_dir);
            if (stats.isDirectory()) {
                console.log('[Error!] Directory exists, stopping to prevent overwrite: ' + destination_dir);
                return false;
            } else if (stats.isFile()) {
                console.log('[Error!] Path exists and appears to be a file: ' + destination_dir);
                return false;
            }
        } catch (ex) {
            // Assume directory doesn't exist, everything alright
        }

        // Create component directory
        try {
            fs.ensureDirSync(destination_dir);
            console.log('Directory created: ' + destination_dir);
        } catch (ex) {
            console.log('[Error!] Cannot create component directory: ' + destination_dir);
            if (_this._debug) {
                console.error(ex);
            }
            return false;
        }

        // Get relative path to component extending from
        if (extend_from) {
            extend_from_relative = path.relative(destination_dir, path.join(this._config.basedir, extend_from));
        }

        // Create component files
        files_create_result = Object.keys(_config.files).every(function (file_id) {
            var filename = _this._getFilePath(component_name, file_id, destination_dir);
            try {
                var content = _this._getFileContent(component_name, file_id, extend_from_relative);
                fs.writeFileSync(filename, content, _config.file_write_options);
                console.log('File created: ' + filename);
                return true;
            } catch (ex) {
                console.log('[Error!] Cannot create file: ' + filename);
                if (_this._debug) {
                    console.error(ex);
                }
                return false;
            }
        });
        if (!files_create_result) {
            return false;
        }

        // Append component entry to build file
        // @todo recursively create buildfiles for each level?
        if (_config.append_to_buildfile) {
            buildfile_path = path.join(destination_dir, '..') + this._config.file_ext;
            if (_config.allow_buildfile_outside_basedir || !_isPathOutsideDir(_config.basedir, buildfile_path)) {
                // Check whether buildfile exists, and if it has any content
                try {
                    buildfile_content_existing = fs.readFileSync(buildfile_path);
                    buildfile_existed = true;
                    console.log('Buildfile found: ' + buildfile_path);
                } catch (ex) {
                    buildfile_existed = false;
                    console.log('Buildfile doesn\'t exist, creating: ' + buildfile_path);
                }

                // Build up import line and combine it with newline, if necessary
                buildfile_line = this._getImportLine(
                    path.join(path.parse(buildfile_path)['name'], component_name), true);
                if (_config.ensure_trailing_newline) {
                    buildfile_content_appended = buildfile_line + _config.newline;
                } else if (buildfile_existed && buildfile_content_existing.length) {
                    buildfile_content_appended = _config.newline + buildfile_line;
                } else {
                    buildfile_content_appended = buildfile_line;
                }

                // Append component import line to buildfile, creating buildfile if not exists
                try {
                    fs.appendFileSync(buildfile_path, buildfile_content_appended, _config.file_write_options);
                    if (!buildfile_existed) {
                        console.log('Buildfile created: ' + buildfile_path);
                    }
                    console.log('Import line appended to buildfile: ' + buildfile_line);
                } catch (ex) {
                    if (buildfile_existed) {
                        console.log('[Error!] Cannot append line to buildfile: ' + buildfile_path);
                    } else {
                        console.log('[Error!] Cannot create buildfile: ' + buildfile_path);
                    }
                    if (_this._debug) {
                        console.error(ex);
                    }
                    return false;
                }
            }
        }

        // Success
        return true;
    };

    /**
     * Build up content for given file type of the component
     * @param {string} component_name Component name
     * @param {string} file_id File ID in `files` config key
     * @param {string} [extend_from_relative] Relative path to component extend if performed from
     * @returns {string}
     * @private
     */
    Stylco.prototype._getFileContent = function (component_name, file_id, extend_from_relative) {
        var _this = this,
            imports,
            lines = [];

        // If appliable, add import for the same-named file within the component extending from
        if (extend_from_relative && this._config.files[file_id].import_extend_source) {
            lines.push(this._getImportLine(extend_from_relative));
        }

        // Append other imports
        imports = this._config.files[file_id].imports;
        if (typeof imports === 'string') {
            imports = [imports];
        }
        if (imports && (imports instanceof Array)) {
            imports.forEach(function (file_id) {
                lines.push(_this._getImportLine(_this._getFileName(component_name, file_id)));
            });
        }

        // Append any custom content, applying values interpolation
        if (this._config.files[file_id].content) {
            lines.push(_replaceVars(this._config.files[file_id].content, {
                'NAME': component_name,
                'IMPORT': this._config.import_rule,
                'SEMICOLON': this._getSemicolonChar(),
                'QUOTE': this._config.quote,
                'NEWLINE': this._config.newline,
                'INDENT': this._config.indent
            }));
        }

        // Append trailing newline
        if (this._config.ensure_trailing_newline) {
            lines.push('');
        }

        // Join lines and return results
        return lines.join(this._config.newline);
    };

    /**
     * Build up file name usable for import rule
     * @param {string} component_name Component name
     * @param {string} file_id File ID in `files` config key
     * @returns {string}
     * @private
     */
    Stylco.prototype._getFileName = function (component_name, file_id) {
        return _replaceVars(this._config.files[file_id].filename, {'NAME': component_name});
    };

    /**
     * Build up path to file usable for reading file content
     * @param {string} component_name Component name
     * @param {string} file_id File ID in `files` config key
     * @param {string} dir File directory
     * @returns {string}
     * @private
     */
    Stylco.prototype._getFilePath = function (component_name, file_id, dir) {
        return path.join(dir, this._getFileName(component_name, file_id) + this._config.file_ext);
    };

    /**
     * Build up import line for the given component
     * @param {string} import_path Path to component
     * @param {boolean} [is_dir] Explicitly specify path to be directory (not file)
     * @returns {string}
     * @private
     */
    Stylco.prototype._getImportLine = function (import_path, is_dir) {
        var _config = this._config;
        import_path = _unixSlashesPath(import_path);
        if (!is_dir && _config.import_with_ext) {
            import_path += _config.file_ext;
        }
        return _config.import_rule + ' ' + _config.quote + import_path + _config.quote + this._getSemicolonChar();
    };

    /**
     * Return semicolon or empty string, respecting current configuration settings
     * @returns {string}
     * @private
     */
    Stylco.prototype._getSemicolonChar = function () {
        return this._config.use_semicolons ? ';' : '';
    };

    /**
     * Get and merge module-level config defaults and user-level config overrides
     * If value exists in user config, it is overriden entirely
     * Also apply debug mode value
     * @private
     */
    Stylco.prototype._init = function (debug) {
        var _config,
            config_defaults,
            user_config,
            user_config_file,
            user_config_content,
            option;

        // Set debug mode value
        this._debug = !!debug;
        if (this._debug) {
            console.log('Debug mode is enabled\n');
        }

        // Read config defaults
        config_defaults = JSON.parse(fs.readFileSync(path.join(__dirname, '..', this.CONFIG_FILE_NAME)));

        // Try to read and parse user config
        user_config_file = path.join(process.cwd(), this.CONFIG_FILE_NAME);
        user_config = {};
        try {
            user_config_content = fs.readFileSync(user_config_file);
            try {
                user_config = JSON.parse(user_config_content);
            } catch (ex) {
                console.log('[Warning!] `' + user_config_file + '` content is not valid JSON; using config defaults.')
            }
        } catch (ex) {
        }

        // Fill instance config with option values
        // If user config doesn't contain an option, default value is used
        // Otherwise, user value overrides default value entirely (no merge for arrays etc.)
        _config = {};
        for (option in config_defaults) {
            if (!config_defaults.hasOwnProperty(option)) {
                continue;
            }
            _config[option] = user_config.hasOwnProperty(option) ? user_config[option] : config_defaults[option];
        }

        // JSON cannot store octal numbers, so file write mode may need type conversion (e.g. '0o644' => 0o644)
        if ((typeof _config.file_write_options === 'object') && (typeof _config.file_write_options.mode === 'string')) {
            _config.file_write_options.mode = Number(_config.file_write_options.mode); // parseInt is bugs, so Number()
        }

        // Apply final config values
        this._config = _config
    };

    /**
     * @typedef {object} StylcoConfig
     * @property {string} basedir
     * @property {string} file_ext
     * @property {string} import_rule
     * @property {string} import_with_ext
     * @property {boolean} append_to_buildfile
     * @property {boolean} allow_buildfile_outside_basedir
     * @property {string} quote
     * @property {boolean} use_semicolons
     * @property {string} indent
     * @property {string} newline
     * @property {boolean} ensure_trailing_newline
     * @property {FileWriteOptions} file_write_options
     * @property {{string: StylcoConfigFileItem}} files
     */

    /**
     * @typedef {object} StylcoConfigFileItem
     * @property {string} filename
     * @property {string|string[]} imports
     * @property {boolean} [import_extend_source]
     * @property {string} [content]
     */

    /**
     * @typedef {object|string} FileWriteOptions
     * @property {string} [encoding]
     * @property {number} [mode]
     * @property {string} [flag]
     */

    /**
     * Check if provided path is outside the reference directory
     * @param {string} test_path Tested path
     * @param {string} dir Reference directory
     * @returns {boolean}
     * @private
     */
    function _isPathOutsideDir(test_path, dir) {
        var relative = path.relative(dir, test_path);
        return (relative.indexOf('..') !== 0);
    }

    /**
     * Reformat path with coerced forward slashes (Unix-style)
     * @param {string} input_path Input path (may be \\-slashed in Windows-style)
     * @returns {string}
     * @private
     */
    function _unixSlashesPath(input_path) {
        return input_path.split('\\').join('/');
    }

    /**
     * Replace template variables with values (e.g. '{{NAME}}' => 'My name') in provided string
     * @param {string} string Input string
     * @param {{string: string}} values Name-value dictionary; name should be provided WITHOUT curly braces
     * @returns {string}
     * @private
     */
    function _replaceVars(string, values) {
        var varname,
            search;
        for (varname in values) {
            if (!values.hasOwnProperty(varname)) {
                continue;
            }
            search = new RegExp('{{' + varname + '}}', 'g');
            string = string.replace(search, values[varname]);
        }
        return string;
    }

    // Create and return Stylco instance
    return new Stylco(debug);
};
<commit_msg>Fix jsdoc for hash objects<commit_after>module.exports = function (debug) {
    'use strict';

    var fs = require('fs-extra'),
        path = require('path'),
        Stylco;

    /**
     * Initialize new instance of Stylco
     * @param {boolean} debug Set true to enable debug mode
     * @constructor
     */
    Stylco = function (debug) {
        this._init(debug);
    };

    /**
     * Name of the configuration file (both module-level defaults and user-level overrides)
     * @type {string}
     */
    Stylco.prototype.CONFIG_FILE_NAME = '.stylcorc';

    /**
     * Module configuration options
     * @type {StylcoConfig}
     * @private
     */
    Stylco.prototype._config = undefined;

    /**
     * Indicates whether debug mode is enabled
     * @type {boolean}
     * @private
     */
    Stylco.prototype._debug = undefined;

    /**
     * Create Stylus component file/directory structure
     * @param {string} destination Destination directory (relative to `basedir` config param)
     * @param {string} [extend_from] Directory of component to extend from (relative to `basedir` config param)
     */
    Stylco.prototype.create = function (destination, extend_from) {
        var _this = this,
            _config = this._config,
            component_name = path.basename(destination),
            destination_dir = path.join(this._config.basedir, destination),
            stats,
            extend_from_relative,
            files_create_result,
            buildfile_path,
            buildfile_existed,
            buildfile_line,
            buildfile_content_existing,
            buildfile_content_appended;

        // Check there is no existing directory of file
        try {
            stats = fs.statSync(destination_dir);
            if (stats.isDirectory()) {
                console.log('[Error!] Directory exists, stopping to prevent overwrite: ' + destination_dir);
                return false;
            } else if (stats.isFile()) {
                console.log('[Error!] Path exists and appears to be a file: ' + destination_dir);
                return false;
            }
        } catch (ex) {
            // Assume directory doesn't exist, everything alright
        }

        // Create component directory
        try {
            fs.ensureDirSync(destination_dir);
            console.log('Directory created: ' + destination_dir);
        } catch (ex) {
            console.log('[Error!] Cannot create component directory: ' + destination_dir);
            if (_this._debug) {
                console.error(ex);
            }
            return false;
        }

        // Get relative path to component extending from
        if (extend_from) {
            extend_from_relative = path.relative(destination_dir, path.join(this._config.basedir, extend_from));
        }

        // Create component files
        files_create_result = Object.keys(_config.files).every(function (file_id) {
            var filename = _this._getFilePath(component_name, file_id, destination_dir);
            try {
                var content = _this._getFileContent(component_name, file_id, extend_from_relative);
                fs.writeFileSync(filename, content, _config.file_write_options);
                console.log('File created: ' + filename);
                return true;
            } catch (ex) {
                console.log('[Error!] Cannot create file: ' + filename);
                if (_this._debug) {
                    console.error(ex);
                }
                return false;
            }
        });
        if (!files_create_result) {
            return false;
        }

        // Append component entry to build file
        // @todo recursively create buildfiles for each level?
        if (_config.append_to_buildfile) {
            buildfile_path = path.join(destination_dir, '..') + this._config.file_ext;
            if (_config.allow_buildfile_outside_basedir || !_isPathOutsideDir(_config.basedir, buildfile_path)) {
                // Check whether buildfile exists, and if it has any content
                try {
                    buildfile_content_existing = fs.readFileSync(buildfile_path);
                    buildfile_existed = true;
                    console.log('Buildfile found: ' + buildfile_path);
                } catch (ex) {
                    buildfile_existed = false;
                    console.log('Buildfile doesn\'t exist, creating: ' + buildfile_path);
                }

                // Build up import line and combine it with newline, if necessary
                buildfile_line = this._getImportLine(
                    path.join(path.parse(buildfile_path)['name'], component_name), true);
                if (_config.ensure_trailing_newline) {
                    buildfile_content_appended = buildfile_line + _config.newline;
                } else if (buildfile_existed && buildfile_content_existing.length) {
                    buildfile_content_appended = _config.newline + buildfile_line;
                } else {
                    buildfile_content_appended = buildfile_line;
                }

                // Append component import line to buildfile, creating buildfile if not exists
                try {
                    fs.appendFileSync(buildfile_path, buildfile_content_appended, _config.file_write_options);
                    if (!buildfile_existed) {
                        console.log('Buildfile created: ' + buildfile_path);
                    }
                    console.log('Import line appended to buildfile: ' + buildfile_line);
                } catch (ex) {
                    if (buildfile_existed) {
                        console.log('[Error!] Cannot append line to buildfile: ' + buildfile_path);
                    } else {
                        console.log('[Error!] Cannot create buildfile: ' + buildfile_path);
                    }
                    if (_this._debug) {
                        console.error(ex);
                    }
                    return false;
                }
            }
        }

        // Success
        return true;
    };

    /**
     * Build up content for given file type of the component
     * @param {string} component_name Component name
     * @param {string} file_id File ID in `files` config key
     * @param {string} [extend_from_relative] Relative path to component extend if performed from
     * @returns {string}
     * @private
     */
    Stylco.prototype._getFileContent = function (component_name, file_id, extend_from_relative) {
        var _this = this,
            imports,
            lines = [];

        // If appliable, add import for the same-named file within the component extending from
        if (extend_from_relative && this._config.files[file_id].import_extend_source) {
            lines.push(this._getImportLine(extend_from_relative));
        }

        // Append other imports
        imports = this._config.files[file_id].imports;
        if (typeof imports === 'string') {
            imports = [imports];
        }
        if (imports && (imports instanceof Array)) {
            imports.forEach(function (file_id) {
                lines.push(_this._getImportLine(_this._getFileName(component_name, file_id)));
            });
        }

        // Append any custom content, applying values interpolation
        if (this._config.files[file_id].content) {
            lines.push(_replaceVars(this._config.files[file_id].content, {
                'NAME': component_name,
                'IMPORT': this._config.import_rule,
                'SEMICOLON': this._getSemicolonChar(),
                'QUOTE': this._config.quote,
                'NEWLINE': this._config.newline,
                'INDENT': this._config.indent
            }));
        }

        // Append trailing newline
        if (this._config.ensure_trailing_newline) {
            lines.push('');
        }

        // Join lines and return results
        return lines.join(this._config.newline);
    };

    /**
     * Build up file name usable for import rule
     * @param {string} component_name Component name
     * @param {string} file_id File ID in `files` config key
     * @returns {string}
     * @private
     */
    Stylco.prototype._getFileName = function (component_name, file_id) {
        return _replaceVars(this._config.files[file_id].filename, {'NAME': component_name});
    };

    /**
     * Build up path to file usable for reading file content
     * @param {string} component_name Component name
     * @param {string} file_id File ID in `files` config key
     * @param {string} dir File directory
     * @returns {string}
     * @private
     */
    Stylco.prototype._getFilePath = function (component_name, file_id, dir) {
        return path.join(dir, this._getFileName(component_name, file_id) + this._config.file_ext);
    };

    /**
     * Build up import line for the given component
     * @param {string} import_path Path to component
     * @param {boolean} [is_dir] Explicitly specify path to be directory (not file)
     * @returns {string}
     * @private
     */
    Stylco.prototype._getImportLine = function (import_path, is_dir) {
        var _config = this._config;
        import_path = _unixSlashesPath(import_path);
        if (!is_dir && _config.import_with_ext) {
            import_path += _config.file_ext;
        }
        return _config.import_rule + ' ' + _config.quote + import_path + _config.quote + this._getSemicolonChar();
    };

    /**
     * Return semicolon or empty string, respecting current configuration settings
     * @returns {string}
     * @private
     */
    Stylco.prototype._getSemicolonChar = function () {
        return this._config.use_semicolons ? ';' : '';
    };

    /**
     * Get and merge module-level config defaults and user-level config overrides
     * If value exists in user config, it is overriden entirely
     * Also apply debug mode value
     * @private
     */
    Stylco.prototype._init = function (debug) {
        var _config,
            config_defaults,
            user_config,
            user_config_file,
            user_config_content,
            option;

        // Set debug mode value
        this._debug = !!debug;
        if (this._debug) {
            console.log('Debug mode is enabled\n');
        }

        // Read config defaults
        config_defaults = JSON.parse(fs.readFileSync(path.join(__dirname, '..', this.CONFIG_FILE_NAME)));

        // Try to read and parse user config
        user_config_file = path.join(process.cwd(), this.CONFIG_FILE_NAME);
        user_config = {};
        try {
            user_config_content = fs.readFileSync(user_config_file);
            try {
                user_config = JSON.parse(user_config_content);
            } catch (ex) {
                console.log('[Warning!] `' + user_config_file + '` content is not valid JSON; using config defaults.')
            }
        } catch (ex) {
        }

        // Fill instance config with option values
        // If user config doesn't contain an option, default value is used
        // Otherwise, user value overrides default value entirely (no merge for arrays etc.)
        _config = {};
        for (option in config_defaults) {
            if (!config_defaults.hasOwnProperty(option)) {
                continue;
            }
            _config[option] = user_config.hasOwnProperty(option) ? user_config[option] : config_defaults[option];
        }

        // JSON cannot store octal numbers, so file write mode may need type conversion (e.g. '0o644' => 0o644)
        if ((typeof _config.file_write_options === 'object') && (typeof _config.file_write_options.mode === 'string')) {
            _config.file_write_options.mode = Number(_config.file_write_options.mode); // parseInt is bugs, so Number()
        }

        // Apply final config values
        this._config = _config
    };

    /**
     * @typedef {object} StylcoConfig
     * @property {string} basedir
     * @property {string} file_ext
     * @property {string} import_rule
     * @property {string} import_with_ext
     * @property {boolean} append_to_buildfile
     * @property {boolean} allow_buildfile_outside_basedir
     * @property {string} quote
     * @property {boolean} use_semicolons
     * @property {string} indent
     * @property {string} newline
     * @property {boolean} ensure_trailing_newline
     * @property {FileWriteOptions} file_write_options
     * @property {object<string, StylcoConfigFileItem>} files
     */

    /**
     * @typedef {object} StylcoConfigFileItem
     * @property {string} filename
     * @property {string|string[]} imports
     * @property {boolean} [import_extend_source]
     * @property {string} [content]
     */

    /**
     * @typedef {object|string} FileWriteOptions
     * @property {string} [encoding]
     * @property {number} [mode]
     * @property {string} [flag]
     */

    /**
     * Check if provided path is outside the reference directory
     * @param {string} test_path Tested path
     * @param {string} dir Reference directory
     * @returns {boolean}
     * @private
     */
    function _isPathOutsideDir(test_path, dir) {
        var relative = path.relative(dir, test_path);
        return (relative.indexOf('..') !== 0);
    }

    /**
     * Reformat path with coerced forward slashes (Unix-style)
     * @param {string} input_path Input path (may be \\-slashed in Windows-style)
     * @returns {string}
     * @private
     */
    function _unixSlashesPath(input_path) {
        return input_path.split('\\').join('/');
    }

    /**
     * Replace template variables with values (e.g. '{{NAME}}' => 'My name') in provided string
     * @param {string} string Input string
     * @param {object<string, string>} values Name-value dictionary; name should be provided WITHOUT curly braces
     * @returns {string}
     * @private
     */
    function _replaceVars(string, values) {
        var varname,
            search;
        for (varname in values) {
            if (!values.hasOwnProperty(varname)) {
                continue;
            }
            search = new RegExp('{{' + varname + '}}', 'g');
            string = string.replace(search, values[varname]);
        }
        return string;
    }

    // Create and return Stylco instance
    return new Stylco(debug);
};
<|endoftext|>


<commit_before>/**
 * `core/user` data store: permission scopes.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { CORE_USER, PERMISSION_READ_SHARED_MODULE_DATA } from './constants';
import { CORE_MODULES } from '../../modules/datastore/constants';
import { getMetaCapabilityPropertyName } from '../util/permissions';
const { createRegistrySelector } = Data;

// Actions
const CLEAR_PERMISSION_SCOPE_ERROR = 'CLEAR_PERMISSION_SCOPE_ERROR';
const SET_PERMISSION_SCOPE_ERROR = 'SET_PERMISSION_SCOPE_ERROR';
const RECEIVE_CAPABILITIES = 'RECEIVE_CAPABILITIES';

export const initialState = {
	permissionError: null,
	capabilities: undefined,
};

export const actions = {
	/**
	 * Clears the permission scope error, if one was previously set.
	 *
	 * @since 1.9.0
	 * @private
	 *
	 * @return {Object} Redux-style action.
	 */
	clearPermissionScopeError() {
		return {
			payload: {},
			type: CLEAR_PERMISSION_SCOPE_ERROR,
		};
	},

	/**
	 * Sets a permission error encountered by an API request.
	 *
	 * @since 1.9.0
	 * @private
	 *
	 * @param {Object} permissionError Permissions error object.
	 * @return {Object} Redux-style action.
	 */
	setPermissionScopeError( permissionError ) {
		invariant( permissionError, 'permissionError is required.' );

		return {
			payload: { permissionError },
			type: SET_PERMISSION_SCOPE_ERROR,
		};
	},

	/**
	 * Sets user capabilities.
	 *
	 * @since 1.13.0
	 * @private
	 *
	 * @param {Object} capabilities User capabilities.
	 * @return {Object} Redux-style action.
	 */
	receiveCapabilities( capabilities ) {
		return {
			type: RECEIVE_CAPABILITIES,
			payload: { capabilities },
		};
	},

	/**
	 * Refreshs user capabilities.
	 *
	 * @since n.e.x.t
	 *
	 * @return {Object} Redux-style action.
	 */
	*refreshCapabilities() {
		const { dispatch } = yield Data.commonActions.getRegistry();

		const newCapabilities = yield API.get(
			'core',
			'user',
			'permissions',
			undefined,
			{ useCache: false }
		);

		global._googlesitekitUserData.permissions = newCapabilities;

		return dispatch( CORE_USER ).receiveCapabilities( newCapabilities );
	},
};

export const controls = {};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case CLEAR_PERMISSION_SCOPE_ERROR: {
			return {
				...state,
				permissionError: null,
			};
		}

		case SET_PERMISSION_SCOPE_ERROR: {
			const { permissionError } = payload;

			return {
				...state,
				permissionError,
			};
		}

		case RECEIVE_CAPABILITIES: {
			const { capabilities } = payload;

			return {
				...state,
				capabilities,
			};
		}

		default: {
			return state;
		}
	}
};

export const resolvers = {
	*getCapabilities() {
		const registry = yield Data.commonActions.getRegistry();

		if ( registry.select( CORE_USER ).getCapabilities() ) {
			return;
		}

		if ( ! global._googlesitekitUserData?.permissions ) {
			global.console.error( 'Could not load core/user permissions.' );
		}

		yield actions.receiveCapabilities(
			global._googlesitekitUserData?.permissions
		);
	},
};

export const selectors = {
	/**
	 * Gets the most recent permission error encountered by this user.
	 *
	 * @since 1.9.0
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|null)} Permission scope errors. Returns `null` if no error exists.
	 */
	getPermissionScopeError( state ) {
		const { permissionError } = state;
		return permissionError;
	},

	/**
	 * Gets capabilities of the current user.
	 *
	 * @since 1.13.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} Capabilities object. Returns undefined if it is not loaded yet.
	 */
	getCapabilities( state ) {
		const { capabilities } = state;
		return capabilities;
	},

	/**
	 * Gets viewable module slugs of the current user.
	 *
	 * @since 1.72.0
	 *
	 * @return {(Array|undefined)} An array of viewable module slugs. `undefined` if `modules` are not loaded yet.
	 */
	getViewableModules: createRegistrySelector( ( select ) => () => {
		const modules = select( CORE_MODULES ).getModules();

		if ( modules === undefined ) {
			return undefined;
		}

		// Return an array of module slugs for modules that are
		// shareable and the user has the "read shared module data"
		// capability for.
		return Object.values( modules ).reduce( ( moduleSlugs, module ) => {
			const hasCapability = select( CORE_USER ).hasCapability(
				PERMISSION_READ_SHARED_MODULE_DATA,
				module.slug
			);

			if ( module.shareable && hasCapability ) {
				return [ ...moduleSlugs, module.slug ];
			}

			return moduleSlugs;
		}, [] );
	} ),

	/**
	 * Checks if the current user has the specified capability or not.
	 *
	 * @since 1.13.0
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} capability Capability name to check.
	 * @param {Array}  args       List of rest of the arguments. Specifically one or many module slugs.
	 * @return {(boolean|undefined)} TRUE if the current user has this capability, otherwise FALSE. If capabilities ain't loaded yet, returns undefined.
	 */
	hasCapability: createRegistrySelector(
		( select ) => ( state, capability, ...args ) => {
			const capabilities = select( CORE_USER ).getCapabilities();

			if ( args.length > 0 ) {
				capability = getMetaCapabilityPropertyName(
					capability,
					...args
				);
			}

			if ( capabilities ) {
				return !! capabilities[ capability ];
			}

			return undefined;
		}
	),

	/**
	 * Checks if the specified module is shareable and viewable by the current user.
	 *
	 * @since 1.77.0
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} moduleSlug Module slug to check.
	 * @return {(boolean|undefined)} `true` if the module is shareable and viewable by the current user. `false` if the module does not exist, is not shareable or not viewable by the current user. `undefined` if state is not loaded yet.
	 */
	canViewSharedModule: createRegistrySelector(
		( select ) => ( state, moduleSlug ) => {
			const module = select( CORE_MODULES ).getModule( moduleSlug );

			if ( module === undefined ) {
				return undefined;
			}

			if ( module === null || ! module.shareable ) {
				return false;
			}

			return select( CORE_USER ).hasCapability(
				PERMISSION_READ_SHARED_MODULE_DATA,
				module.slug
			);
		}
	),
};

export default {
	initialState,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Use a fetch store for getting capabilities.<commit_after>/**
 * `core/user` data store: permission scopes.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { CORE_USER, PERMISSION_READ_SHARED_MODULE_DATA } from './constants';
import { CORE_MODULES } from '../../modules/datastore/constants';
import { getMetaCapabilityPropertyName } from '../util/permissions';
import { createFetchStore } from '../../data/create-fetch-store';
const { createRegistrySelector } = Data;

// Actions
const CLEAR_PERMISSION_SCOPE_ERROR = 'CLEAR_PERMISSION_SCOPE_ERROR';
const SET_PERMISSION_SCOPE_ERROR = 'SET_PERMISSION_SCOPE_ERROR';
const RECEIVE_CAPABILITIES = 'RECEIVE_CAPABILITIES';

const fetchRefreshCapabilitiesStore = createFetchStore( {
	baseName: 'refreshCapabilities',
	controlCallback: () => {
		return API.get( 'core', 'user', 'permissions', undefined, {
			useCache: false,
		} );
	},
} );

const baseInitialState = {
	permissionError: null,
	capabilities: undefined,
};

const baseActions = {
	/**
	 * Clears the permission scope error, if one was previously set.
	 *
	 * @since 1.9.0
	 * @private
	 *
	 * @return {Object} Redux-style action.
	 */
	clearPermissionScopeError() {
		return {
			payload: {},
			type: CLEAR_PERMISSION_SCOPE_ERROR,
		};
	},

	/**
	 * Sets a permission error encountered by an API request.
	 *
	 * @since 1.9.0
	 * @private
	 *
	 * @param {Object} permissionError Permissions error object.
	 * @return {Object} Redux-style action.
	 */
	setPermissionScopeError( permissionError ) {
		invariant( permissionError, 'permissionError is required.' );

		return {
			payload: { permissionError },
			type: SET_PERMISSION_SCOPE_ERROR,
		};
	},

	/**
	 * Sets user capabilities.
	 *
	 * @since 1.13.0
	 * @private
	 *
	 * @param {Object} capabilities User capabilities.
	 * @return {Object} Redux-style action.
	 */
	receiveCapabilities( capabilities ) {
		return {
			type: RECEIVE_CAPABILITIES,
			payload: { capabilities },
		};
	},

	/**
	 * Refreshs user capabilities.
	 *
	 * @since n.e.x.t
	 *
	 * @return {Object} Redux-style action.
	 */
	*refreshCapabilities() {
		const { dispatch } = yield Data.commonActions.getRegistry();

		const {
			response,
		} = yield fetchRefreshCapabilitiesStore.actions.fetchRefreshCapabilities();

		global._googlesitekitUserData = {
			permissions: response,
		};

		return dispatch( CORE_USER ).receiveCapabilities( response );
	},
};

const baseControls = {};

const baseReducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case CLEAR_PERMISSION_SCOPE_ERROR: {
			return {
				...state,
				permissionError: null,
			};
		}

		case SET_PERMISSION_SCOPE_ERROR: {
			const { permissionError } = payload;

			return {
				...state,
				permissionError,
			};
		}

		case RECEIVE_CAPABILITIES: {
			const { capabilities } = payload;

			return {
				...state,
				capabilities,
			};
		}

		default: {
			return state;
		}
	}
};

const baseResolvers = {
	*getCapabilities() {
		const registry = yield Data.commonActions.getRegistry();

		if ( registry.select( CORE_USER ).getCapabilities() ) {
			return;
		}

		if ( ! global._googlesitekitUserData?.permissions ) {
			global.console.error( 'Could not load core/user permissions.' );
		}

		yield actions.receiveCapabilities(
			global._googlesitekitUserData?.permissions
		);
	},
};

const baseSelectors = {
	/**
	 * Gets the most recent permission error encountered by this user.
	 *
	 * @since 1.9.0
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|null)} Permission scope errors. Returns `null` if no error exists.
	 */
	getPermissionScopeError( state ) {
		const { permissionError } = state;
		return permissionError;
	},

	/**
	 * Gets capabilities of the current user.
	 *
	 * @since 1.13.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} Capabilities object. Returns undefined if it is not loaded yet.
	 */
	getCapabilities( state ) {
		const { capabilities } = state;
		return capabilities;
	},

	/**
	 * Gets viewable module slugs of the current user.
	 *
	 * @since 1.72.0
	 *
	 * @return {(Array|undefined)} An array of viewable module slugs. `undefined` if `modules` are not loaded yet.
	 */
	getViewableModules: createRegistrySelector( ( select ) => () => {
		const modules = select( CORE_MODULES ).getModules();

		if ( modules === undefined ) {
			return undefined;
		}

		// Return an array of module slugs for modules that are
		// shareable and the user has the "read shared module data"
		// capability for.
		return Object.values( modules ).reduce( ( moduleSlugs, module ) => {
			const hasCapability = select( CORE_USER ).hasCapability(
				PERMISSION_READ_SHARED_MODULE_DATA,
				module.slug
			);

			if ( module.shareable && hasCapability ) {
				return [ ...moduleSlugs, module.slug ];
			}

			return moduleSlugs;
		}, [] );
	} ),

	/**
	 * Checks if the current user has the specified capability or not.
	 *
	 * @since 1.13.0
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} capability Capability name to check.
	 * @param {Array}  args       List of rest of the arguments. Specifically one or many module slugs.
	 * @return {(boolean|undefined)} TRUE if the current user has this capability, otherwise FALSE. If capabilities ain't loaded yet, returns undefined.
	 */
	hasCapability: createRegistrySelector(
		( select ) => ( state, capability, ...args ) => {
			const capabilities = select( CORE_USER ).getCapabilities();

			if ( args.length > 0 ) {
				capability = getMetaCapabilityPropertyName(
					capability,
					...args
				);
			}

			if ( capabilities ) {
				return !! capabilities[ capability ];
			}

			return undefined;
		}
	),

	/**
	 * Checks if the specified module is shareable and viewable by the current user.
	 *
	 * @since 1.77.0
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} moduleSlug Module slug to check.
	 * @return {(boolean|undefined)} `true` if the module is shareable and viewable by the current user. `false` if the module does not exist, is not shareable or not viewable by the current user. `undefined` if state is not loaded yet.
	 */
	canViewSharedModule: createRegistrySelector(
		( select ) => ( state, moduleSlug ) => {
			const module = select( CORE_MODULES ).getModule( moduleSlug );

			if ( module === undefined ) {
				return undefined;
			}

			if ( module === null || ! module.shareable ) {
				return false;
			}

			return select( CORE_USER ).hasCapability(
				PERMISSION_READ_SHARED_MODULE_DATA,
				module.slug
			);
		}
	),
};

const store = Data.combineStores( fetchRefreshCapabilitiesStore, {
	initialState: baseInitialState,
	actions: baseActions,
	controls: baseControls,
	reducer: baseReducer,
	resolvers: baseResolvers,
	selectors: baseSelectors,
} );

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export default store;
<|endoftext|>


<commit_before>define(['angular'],function(angular) {

  var app = angular.module('Leaf');

  app.register.controller('signupController',['$rootScope','$scope','$http',function($rootScope,$scope,$http) {

    // Registra un nuovo utente.
    $scope.signup = function() {
      const f = $scope.form;
      if (f.fullname.$error.required) $rootScope.alert('danger','
The full name is empty.
');
      else if (f.email.$error.required || f.email.$error.email) $rootScope.alert('danger','
The e-mail address is invalid.
');
      else if (f.password.$error.required || f.password.$error.minlength) $rootScope.alert('danger','
The password must be at least 8 characters long.
');
      else {

      }
    };

  }]);

});<commit_msg>Delete signup.js<commit_after><|endoftext|>


<commit_before>/*
  Author: Joe Choi <joechoi910@gmail.com>
*/
'use strict';
var _ = require('underscore'),
  request = require('request'),
  clientId,
  clientSecret,
  restEndpoint,
  lists,
  campaigns,
  accessToken,
  leadId

/*
  init(options, callback);
  Initiates Marketo API interface.

  Example:
  var options = {
    'clientId': '',
    'clientSecret': '',
    'restEndpoint': '',
    'lists': {
      'listname_1': listid_1,
      'listname_2': listid_2,
      ...
    },
    'campaigns': {
      'campaignname_1': campaignid_1,
      'campaignname_2': campaignid_2,
      ...
    },
  };
  marketo.init(options, function(response){
    console.log(response);
  });

  Success:
  { 'success': true, 'message': 'Marketo API variable are loaded OK and authenticated.', 'token': 'aaaaaa-bbbbbb-cccccc' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.init = function (options, callback) {
  if(options.debug) console.log(options);
  clientId = options.clientId;
  clientSecret = options.clientSecret;
  restEndpoint = options.restEndpoint;
  lists = options.lists;
  campaigns = options.campaigns;
  authenticate(processInit, options, callback);
};

/*
  syncLead(options, callback);
  Sync leads on Marketo lead database and associates the lead to Marketo list.

  Example:
  var options: {
    'process': 'add' || 'update' || 'remove',
    'list': 'listname from 'lists':{...} in init()',
    'input': {
      'email': 'user@email.com',
      'firstName': 'John',
      'lastName': 'Doe',
      'title': 'Techie',
      'phone': '1112223333',
      'company': 'John Doe Company',
      'custom_field_1': 'custom_value_1',
      'custom_field_2': 'custom_value_2',
      ...
    }
  }
  marketo.syncLead(options, function(response){
    console.log(response);
  });

  Success:
  { 'success':true, 'message':'Lead Id ___ is successfully added to Marketo.' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.syncLead = function (options, callback) {
  if(options.debug) console.log(options);
  authenticate(processSyncLead, options, callback);
};

/*
  sendEmail(options, callback);
  Schedules Marketo to send an email campaign to selected user.

  Example:
  var options: {
    'email': 'user@email.com',
    'campaign': 'campaignname from 'campaigns':{...} in init()',
    'tokens': [
      {'name': '{{token1_name}}', 'value': 'some value'},
      {'name': '{{token2_name}}', 'value': 'some value'},
      ...
    ]}
  }
  marketo.sendEmail(options, function(response){
    console.log(response);
  });

  Success:
  { 'success':true, 'message':'"confirm" email is sent to user@email.com.' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.sendEmail = function (options, callback) {
  if(options.debug) console.log(options);
  authenticate(processSendEmail, options, callback);
};

/*
  After Marketo API variables are loaded, verify authentication and return token if authenticated.
*/
var processInit = function (options, callback){
  callback({'success':true, 'message':'Marketo API variable are loaded OK and authenticated.', 'token':accessToken});
};

/*
  After authenticated with Marketo API, execute lead sync job using Marketo API.

  For add & update lead,
    1) add or update lead
    2) add lead to list
  For remove lead,
    1) get lead id
    2) remove lead
    3) remove lead from list
*/
var processSyncLead = function (options, callback){
  var listId = '';
  if(options.list && lists[options.list]) {
    listId = lists[options.list];
  }
  switch(options.process.toLowerCase()){
    case 'add':
    case 'update':
      // 1) add or update lead
      addOrUpdateLead(options, function(response){
        if(options.debug) console.log('addOrUpdateLead:' + response.success);
        if(response.success === true){

          // 2) associate lead to list
          var leadId = response.leadId;
          var message = (options.process.toLowerCase() === 'add') ? 'Lead Id ' + leadId + ' is successfully added to Marketo.' : 'Lead Id ' + leadId + ' is successfully updated in Marketo.';
          if(listId !== '') {
            addLeadToList(leadId, listId, function(response){
              if(options.debug) console.log('addLeadToList:' + response.success);
              if(response.success === true){
                callback({'success':true, 'message':message});
              }
              else{
                callback({'success':false, 'error':response.error});
              }
            });
          }
          else{
            callback({'success':true, 'message':message});
          }
        }
        else{
          callback({'success':false, 'error':response.error});
        }
      });
      break;

    case 'remove':
      // 1) get lead id
      getLeadId(options.email, function(response){
        if(options.debug) console.log('getLeadId:' + response.success);
        if(response.success === true){

          // 2) remove lead
          var leadId = response.leadId;
          removeLead(leadId, function(response){
            if(options.debug) console.log('removeLead:' + response.success);
            if(response.success === true){

              // 3) remove lead from list
              if(listId !== '') {
                removeLeadFromList(leadId, listId, function(response){
                  if(options.debug) console.log('removeLeadFromList:' + response.success);
                  if(response.success === true){
                    response.message='Lead Id ' + leadId + ' is successfully removed from Marketo.';
                    callback(response);
                  } else{
                    response.success=false;
                    callback(response);
                  }
                });
              } else{
                response.message='Lead Id ' + leadId + ' is successfully removed from Marketo.';
                callback(response);
              }
            } else{
              response.success=false;
              callback(response);
            }
          });
        } else{
          response.success=false;
          callback(response);
        }
      });
      break;
  }
};

/*
  After authenticated with Marketo API, execute schedule campaign API call.
  1) get lead id
  2) request campaign
*/
var processSendEmail = function (options, callback){
  var campaignId = campaigns[options.campaign];
  if (typeof campaignId === 'undefined'){
    callback({'success':false, 'error':'campaignId is not found.'});
    return;
  }
  // 1) get lead id
  getLeadId(options.email, function(response){
    if(options.debug) console.log('getLeadId:' + response.success);
    if(response.success === true){

      // 2) request campaign
      var leadId = response.leadId;
      requestCampaign(campaignId, leadId, options.tokens, function(response){
        if(options.debug) console.log('requestCampaign:' + response.success);
        if(response.success === true){
          callback({'success':true, 'message':'"' + options.campaign + '" email has been sent to ' + options.email + '.'});
        }
        else{
          callback({'success':false, 'error':response.error});
        }
      });
    }
    else{
      callback({'success':false, 'error':response.error});
    }
  });
};

/*
  Authenticate to Marketo and store access token.
  Access token will expire every 1 hour by default.
  http://developers.marketo.com/documentation/rest/authentication/
*/
var authenticate = function (authCallback, options, callback) {
  var url = restEndpoint + 'identity/oauth/token?grant_type=client_credentials' + "&client_id=" + clientId + "&client_secret=" + clientSecret;
  request({
      method: 'GET',
      url: url,
      json: true
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.access_token) {
        accessToken = body.access_token;
        authCallback(options, callback);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo authentication failed.';
        callback(response);
      }
  });
};

/*
  Returns request header for Marketo API.
*/
var header = function () {
  var header = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + accessToken,
  };
  return header;
}

/*
  Returns lead id by matching email.
  http://developers.marketo.com/documentation/rest/get-multiple-leads-by-filter-type/
*/
var getLeadId = function (email, callback) {
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken + '&filterType=email&filterValues=' + _.escape(email);
  request({
      method: 'GET',
      headers: header(),
      url: url,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true) {
        response.success = true;
        response.leadId = body.result[0].id;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Get Lead Id by Email API failed.';
        callback(response);
      }
  });
}

/*
  Adds Marketo lead to Marketo list.
  http://developers.marketo.com/documentation/rest/add-leads-to-list/
*/

var addLeadToList = function (leadId, listId, callback) {
  var url = restEndpoint + 'rest/v1/lists/' + listId + '/leads.json?access_token=' + accessToken;
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        callback({'success':true});
      }
      else{
        callback({'success':false, 'error':'Marketo Add Lead to List API failed. with error: ' + error});
      }
  });
};

/*
  Removes Marketo lead from Marketo list.
  http://developers.marketo.com/documentation/rest/remove-leads-from-list/
*/
var removeLeadFromList = function (leadId, listId, callback) {
  var url = restEndpoint + 'rest/v1/lists/' + listId + '/leads.json?access_token=' + accessToken + '&_method=DELETE';
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Remove Lead from List API failed.'
        callback(response);
      }
  });
};

/*
  Adds or updates lead from Marketo.
  http://developers.marketo.com/documentation/rest/createupdate-leads/
*/

var addOrUpdateLead = function (options, callback){
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken;
  var data = {
    'action': 'createOrUpdate',
    'lookupField': 'email',
    'input': [options.input]
  };
  data.input[0].email = options.email;
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        callback({'success':true, 'leadId':body.result[0].id});
      }
      else{
        callback({'success':false, 'error':'Marketo Add & Edit Lead API failed.'});
      }
  });
};

/*
  Removes lead from Marketo.
  http://developers.marketo.com/documentation/rest/delete-lead/
*/
var removeLead = function (leadId, callback){
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken;
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'DELETE',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Remove Lead API failed.';
        callback(response);
      }
  });
};

/*
  Associates lead with marketo cookie
  http://developers.marketo.com/documentation/rest/associate-lead/
*/
exports.associateLeadWithCookie = function (leadId, cookieId, callback) {
  var token = '?access_token=' + accessToken;
  var cookie = '&cookie=' + cookieId.replace('&', '%46');
  var url = restEndpoint + 'rest/v1/leads/' + leadId + '/associate.json' + token + cookie;
  request({
    'method': 'POST',
    'headers': header(),
    'url': url,
    'data': {}
  }, function (error, response, body) {
    if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Associate Lead API failed.';
        callback(response);
      }
  });
}

/*
  Requests campaign in Marketo.
  http://developers.marketo.com/documentation/rest/request-campaign/
*/
var requestCampaign = function (campaignId, leadId, tokens, callback){
  var url = restEndpoint + 'rest/v1/campaigns/' + campaignId + '/trigger.json?access_token=' + accessToken;
  var data = {
    'input': {
      'leads': [{
        'id': leadId
      }],
    }
  };
  // add tokens if exists
  if (tokens && tokens.length > 0){
    data.input.tokens = tokens;
  }
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        response.campaignId = body.result[0].id;
        callback({'success':true, 'campaignId':response});
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Request Campaign API failed.';
        callback(response);
      }
  });
};

<commit_msg>reverted changes<commit_after>/*
  Author: Joe Choi <joechoi910@gmail.com>
*/
'use strict';
var _ = require('underscore'),
  request = require('request'),
  clientId,
  clientSecret,
  restEndpoint,
  lists,
  campaigns,
  accessToken,
  leadId;

/*
  init(options, callback);
  Initiates Marketo API interface.

  Example:
  var options = {
    'clientId': '',
    'clientSecret': '',
    'restEndpoint': '',
    'lists': {
      'listname_1': listid_1,
      'listname_2': listid_2,
      ...
    },
    'campaigns': {
      'campaignname_1': campaignid_1,
      'campaignname_2': campaignid_2,
      ...
    },
  };
  marketo.init(options, function(response){
    console.log(response);
  });

  Success:
  { 'success': true, 'message': 'Marketo API variable are loaded OK and authenticated.', 'token': 'aaaaaa-bbbbbb-cccccc' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.init = function (options, callback) {
  if(options.debug) console.log(options);
  clientId = options.clientId;
  clientSecret = options.clientSecret;
  restEndpoint = options.restEndpoint;
  lists = options.lists;
  campaigns = options.campaigns;
  authenticate(processInit, options, callback);
};

/*
  syncLead(options, callback);
  Sync leads on Marketo lead database and associates the lead to Marketo list.

  Example:
  var options: {
    'process': 'add' || 'update' || 'remove',
    'list': 'listname from 'lists':{...} in init()',
    'input': {
      'email': 'user@email.com',
      'firstName': 'John',
      'lastName': 'Doe',
      'title': 'Techie',
      'phone': '1112223333',
      'company': 'John Doe Company',
      'custom_field_1': 'custom_value_1',
      'custom_field_2': 'custom_value_2',
      ...
    }
  }
  marketo.syncLead(options, function(response){
    console.log(response);
  });

  Success:
  { 'success':true, 'message':'Lead Id ___ is successfully added to Marketo.' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.syncLead = function (options, callback) {
  if(options.debug) console.log(options);
  authenticate(processSyncLead, options, callback);
};

/*
  sendEmail(options, callback);
  Schedules Marketo to send an email campaign to selected user.

  Example:
  var options: {
    'email': 'user@email.com',
    'campaign': 'campaignname from 'campaigns':{...} in init()',
    'tokens': [
      {'name': '{{token1_name}}', 'value': 'some value'},
      {'name': '{{token2_name}}', 'value': 'some value'},
      ...
    ]}
  }
  marketo.sendEmail(options, function(response){
    console.log(response);
  });

  Success:
  { 'success':true, 'message':'"confirm" email is sent to user@email.com.' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.sendEmail = function (options, callback) {
  if(options.debug) console.log(options);
  authenticate(processSendEmail, options, callback);
};

/*
  After Marketo API variables are loaded, verify authentication and return token if authenticated.
*/
var processInit = function (options, callback){
  callback({'success':true, 'message':'Marketo API variable are loaded OK and authenticated.', 'token':accessToken});
};

/*
  After authenticated with Marketo API, execute lead sync job using Marketo API.

  For add & update lead,
    1) add or update lead
    2) add lead to list
  For remove lead,
    1) get lead id
    2) remove lead
    3) remove lead from list
*/
var processSyncLead = function (options, callback){
  var listId = '';
  if(options.list && lists[options.list]) {
    listId = lists[options.list];
  }
  switch(options.process.toLowerCase()){
    case 'add':
    case 'update':
      // 1) add or update lead
      addOrUpdateLead(options, function(response){
        if(options.debug) console.log('addOrUpdateLead:' + response.success);
        if(response.success === true){

          // 2) associate lead to list
          var leadId = response.leadId;
          var message = (options.process.toLowerCase() === 'add') ? 'Lead Id ' + leadId + ' is successfully added to Marketo.' : 'Lead Id ' + leadId + ' is successfully updated in Marketo.';
          if(listId !== '') {
            addLeadToList(leadId, listId, function(response){
              if(options.debug) console.log('addLeadToList:' + response.success);
              response.leadId = leadId;
              if(response.success === true){
                callback(response);
              }
              else{
                callback(response);
              }
            });
          }
          else{
            callback(response);
          }
        }
        else{
          callback(response);
        }
      });
      break;

    case 'remove':
      // 1) get lead id
      getLeadId(options.email, function(response){
        if(options.debug) console.log('getLeadId:' + response.success);
        if(response.success === true){

          // 2) remove lead
          var leadId = response.leadId;
          removeLead(leadId, function(response){
            if(options.debug) console.log('removeLead:' + response.success);
            if(response.success === true){

              // 3) remove lead from list
              if(listId !== '') {
                removeLeadFromList(leadId, listId, function(response){
                  if(options.debug) console.log('removeLeadFromList:' + response.success);
                  if(response.success === true){
                    response.message='Lead Id ' + leadId + ' is successfully removed from Marketo.';
                    callback(response);
                  } else{
                    response.success=false;
                    callback(response);
                  }
                });
              } else{
                response.message='Lead Id ' + leadId + ' is successfully removed from Marketo.';
                callback(response);
              }
            } else{
              response.success=false;
              callback(response);
            }
          });
        } else{
          response.success=false;
          callback(response);
        }
      });
      break;
  }
};

/*
  After authenticated with Marketo API, execute schedule campaign API call.
  1) get lead id
  2) request campaign
*/
var processSendEmail = function (options, callback){
  var campaignId = campaigns[options.campaign];
  if (typeof campaignId === 'undefined'){
    callback({'success':false, 'error':'campaignId is not found.'});
    return;
  }
  // 1) get lead id
  getLeadId(options.email, function(response){
    if(options.debug) console.log('getLeadId:' + response.success);
    if(response.success === true){

      // 2) request campaign
      var leadId = response.leadId;
      requestCampaign(campaignId, leadId, options.tokens, function(response){
        if(options.debug) console.log('requestCampaign:' + response.success);
        if(response.success === true){
          callback({'success':true, 'message':'"' + options.campaign + '" email has been sent to ' + options.email + '.'});
        }
        else{
          callback({'success':false, 'error':response.error});
        }
      });
    }
    else{
      callback({'success':false, 'error':response.error});
    }
  });
};

/*
  Authenticate to Marketo and store access token.
  Access token will expire every 1 hour by default.
  http://developers.marketo.com/documentation/rest/authentication/
*/
var authenticate = function (authCallback, options, callback) {
  var url = restEndpoint + 'identity/oauth/token?grant_type=client_credentials' + "&client_id=" + clientId + "&client_secret=" + clientSecret;
  request({
      method: 'GET',
      url: url,
      json: true
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.access_token) {
        accessToken = body.access_token;
        authCallback(options, callback);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo authentication failed.';
        callback(response);
      }
  });
};

/*
  Returns request header for Marketo API.
*/
var header = function () {
  var header = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + accessToken,
  };
  return header;
}

/*
  Returns lead id by matching email.
  http://developers.marketo.com/documentation/rest/get-multiple-leads-by-filter-type/
*/
var getLeadId = function (email, callback) {
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken + '&filterType=email&filterValues=' + _.escape(email);
  request({
      method: 'GET',
      headers: header(),
      url: url,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true) {
        response.success = true;
        response.leadId = body.result[0].id;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Get Lead Id by Email API failed.';
        callback(response);
      }
  });
}

/*
  Adds Marketo lead to Marketo list.
  http://developers.marketo.com/documentation/rest/add-leads-to-list/
*/
var addLeadToList = function (leadId, listId, callback) {
  var url = restEndpoint + 'rest/v1/lists/' + listId + '/leads.json?access_token=' + accessToken;
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        response.success = true
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Add Lead to List API failed.';
        callback(response);
      }
  });
};

/*
  Removes Marketo lead from Marketo list.
  http://developers.marketo.com/documentation/rest/remove-leads-from-list/
*/
var removeLeadFromList = function (leadId, listId, callback) {
  var url = restEndpoint + 'rest/v1/lists/' + listId + '/leads.json?access_token=' + accessToken + '&_method=DELETE';
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Remove Lead from List API failed.'
        callback(response);
      }
  });
};

/*
  Adds or updates lead from Marketo.
  http://developers.marketo.com/documentation/rest/createupdate-leads/
*/
var addOrUpdateLead = function (options, callback){
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken;
  var data = {
    'action': 'createOrUpdate',
    'lookupField': 'email',
    'input': [options.input]
  };
  data.input[0].email = options.email;

  request({
    method: 'POST',
    headers: header(),
    url: url,
    body: data,
    json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        response.success = true;
        response.leadId = body.result[0].id;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Add & Edit Lead API failed.';
        callback(response);
      }
  });
};

/*
  Removes lead from Marketo.
  http://developers.marketo.com/documentation/rest/delete-lead/
*/
var removeLead = function (leadId, callback){
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken;
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'DELETE',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Remove Lead API failed.';
        callback(response);
      }
  });
};

/*
  Associates lead with marketo cookie
  http://developers.marketo.com/documentation/rest/associate-lead/
*/
exports.associateLeadWithCookie = function (leadId, cookieId, callback) {
  var token = '?access_token=' + accessToken;
  var cookie = '&cookie=' + cookieId.replace('&', '%46');
  var url = restEndpoint + 'rest/v1/leads/' + leadId + '/associate.json' + token + cookie;
  request({
    'method': 'POST',
    'headers': header(),
    'url': url,
    'data': {}
  }, function (error, response, body) {
    if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Associate Lead API failed.';
        callback(response);
      }
  });
}

/*
  Requests campaign in Marketo.
  http://developers.marketo.com/documentation/rest/request-campaign/
*/
var requestCampaign = function (campaignId, leadId, tokens, callback){
  var url = restEndpoint + 'rest/v1/campaigns/' + campaignId + '/trigger.json?access_token=' + accessToken;
  var data = {
    'input': {
      'leads': [{
        'id': leadId
      }],
    }
  };
  // add tokens if exists
  if (tokens && tokens.length > 0){
    data.input.tokens = tokens;
  }
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        response.campaignId = body.result[0].id;
        callback({'success':true, 'campaignId':response});
      }
      else{
        response = response || {};
        response.success = false;
        response.error = error;
        response.message = 'Marketo Request Campaign API failed.';
        callback(response);
      }
  });
};

<|endoftext|>