(function () {
/*
# cjt/core.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
// -----------------------------------------------------------------------
// DEVELOPER NOTES:
// -----------------------------------------------------------------------
/* global define: false, PAGE: true */
define('cjt/core',[],function() {
"use strict";
// ---------------------------
// Constants
// ---------------------------
var PERIOD = ".";
// ---------------------------
// State
// ---------------------------
var instance = null;
// Testing shim
var locationService = {
get_pathname: function() {
return window.location.pathname;
},
get_port: function() {
return window.location.port;
},
get_hostname: function() {
return window.location.hostname;
},
get_protocol: function() {
return window.location.protocol;
}
};
// Testing shim
var pageService = {
get_configuration: function() {
return PAGE;
},
};
function CJT() {
if (instance !== null) {
throw new Error("Cannot instantiate more then one CJT, use CJT.getInstance()");
}
this.initialize(locationService, pageService);
}
// To make unit test happy
if (window) {
window.PAGE = window.PAGE || {};
}
/**
* Check if the protocol is https.
* @param {String} protocol
* @return {Boolean} true if its https: in any case, false otherwise.
*/
function isHttps(protocol) {
return (/^https:$/i).test(protocol);
}
/**
* Check if the protocol is http.
* @param {String} protocol
* @return {Boolean} true if its http: in any case, false otherwise.
*/
function isHttp(protocol) {
return (/^http:$/i).test(protocol);
}
/**
* Strip any trailing slashes from a string.
*
* @method stripTrailingSlash
* @param {String} path The path string to process.
* @return {String} The path string without a trailing slash.
*/
function stripTrailingSlash(path) {
return path && path.replace(/\/?$/, "");
}
/**
* Add a trailing slashes to a string if it doesn't have one.
*
* @method ensureTrailingSlash
* @param {String} path The path string to process.
* @return {String} The path string with a guaranteed trailing slash.
*/
function ensureTrailingSlash(path) {
return path && path.replace(/\/?$/, "/");
}
CJT.prototype = {
name: "CJT",
description: "cPanel Common JavaScript Library",
version: "2.0.0.1",
/**
* List of application name constants
* @name KNOWN_APPLICATIONS
* @type {Object}
*/
KNOWN_APPLICATIONS: {
WHM: "whostmgr",
CPANEL: "cpanel",
WEBMAIL: "webmail",
UNPROTECTED: "unprotected",
},
/**
* Initialize the state for CJT
*/
initialize: function(locationService, pageService) {
var config = pageService.get_configuration();
/**
* List of common application configuration settings
* @name config
* @type {Object}
*/
this.config = {
/**
* Name of the actual mode the framework is running under
* @type {String} 'release' or 'debug'
*/
mode: config.MODE,
/**
* Turn on/off debug features of the framework
* @name config.debug
* @type {Boolean}
*/
debug: config.MODE === "debug",
/**
* Alternate base path for looking up the theme. Only used when the application
* is "other or unprotected". Defaults to the root of the site.
* @type {String}
*/
themePath: ensureTrailingSlash(config.THEME_PATH) || "/",
/**
* Indicates the application is running under a proxy sub domain.
* @type {Boolean}
*/
isProxy: config.IS_PROXY || false,
applicationName: config.APP_NAME || "",
/**
* Indicates the application is running in an end to end testing environment.
* These environments use tools like Selenium and Protractor and are sensitive
* to timing issues in the UI. Applications and components should use this flag
* to turn off animations and other interactions that may complicate writing of
* the end to end tests.
* @type {[type]}
*/
e2e: config.IS_E2E || false
};
/**
* @property {String} [protocol] Protocol used to access the page.
*/
this.protocol = locationService.get_protocol();
var port = locationService.get_port();
if (!port) {
// Since some browsers wont fill this in, we have to derive it from
// the protocol if its not provided in the window.location object.
if (isHttps(this.protocol)) {
port = "443";
} else if (isHttp(this.protocol)) {
port = "80";
}
}
// This will work in any context except a proxy URL to cpanel or webmail
// that accesses a URL outside /frontend (cpanel) or /webmail (webmail),
// but URLs like that are non-production by defintion.
var port_path_app = {
80: "other",
443: "other",
2082: "cpanel",
2083: "cpanel",
2086: "whostmgr",
2087: "whostmgr",
2095: "webmail",
2096: "webmail",
9876: "unittest",
9877: "unittest",
9878: "unittest",
9879: "unittest",
frontend: "cpanel",
webmail: "webmail"
};
// State variables
this._url_path = locationService.get_pathname();
// ---------------------------------------------
// Gets the session token from the url
// ---------------------------------------------
var path_match = (this._url_path.match(/((?:\/cpsess\d+)?)(?:\/([^\/]+))?/) || []);
/**
* @property {String} [domain] Domain used to access the page.
*/
this.domain = locationService.get_hostname();
/**
* @property {Number} [port] Port used to access the product.
*/
this.port = parseInt(port, 10);
/**
* @property {String} [applicationName] Name of the application
*/
if (this.config.applicationName) {
this.applicationName = this.config.applicationName;
} else {
if (!this.config.isProxy) {
this.applicationName = port_path_app[port] || port_path_app[path_match[2]] || "whostmgr";
} else {
// For proxy subdomains, we look at the first subdomain to identify the application.
if (/^whm\./.test(this.domain)) {
this.applicationName = port_path_app[2087];
} else if (/^cpanel\./.test(this.domain)) {
this.applicationName = port_path_app[2083];
} else if (/^webmail\./.test(this.domain)) {
this.applicationName = port_path_app[2095];
}
}
}
/**
* @property {String} [session] Session token
*/
this.securityToken = path_match[1] || "";
this.applicationPath = this.securityToken ? this._url_path.replace(this.securityToken, "") : this._url_path;
this.theme = "";
this.themePath = "";
},
/**
* Return whether we are running inside some other framework or application
*
* @method isUnitTest
* @return {Boolean} true if this is an unrecognized application or framework; false otherwise
*/
isOther: function() {
return (/other/i).test(this.applicationName);
},
/**
* Return whether we are running inside the unit test framework
*
* @method isUnitTest
* @return {Boolean} true if this is unittest; false otherwise
*/
isUnitTest: function() {
return (/unittest/i).test(this.applicationName) || Boolean(window.__karma__);
},
/**
* Return whether we are running inside an unprotected path
*
* @method isUnprotected
* @return {Boolean} true if this is unprotected; false otherwise
*/
isUnprotected: function() {
return !this.securityToken && this.unprotected_paths.indexOf( stripTrailingSlash(this.applicationPath) ) !== -1;
},
unprotected_paths: ["/resetpass", "/invitation"],
/**
* Return whether we are running inside cpanel or something else (e.g., WHM)
*
* @method isCpanel
* @return {Boolean} true if this is cpanel; false otherwise
*/
isCpanel: function() {
return (/cpanel/i).test(this.applicationName);
},
/**
* Return whether we are running inside WHM or something else (e.g., whm)
*
* @method isWhm
* @return {Boolean} true if this is whm; false otherwise
*/
isWhm: function() {
return (/whostmgr/i).test(this.applicationName);
},
/**
* Check if the framework is running in an e2e test.
*
* @method isE2E
* @return {Boolean} true if this is running in an e2e test run, false otherwise.
*/
isE2E: function(e2e) {
return this.e2e;
},
/**
* Set the value for end to end testing mode.
*
* @method setE2E
* @param {Boolean} e2e true if this is an e2e test run, false otherwise.
*/
setE2E: function(e2e) {
if (typeof (e2e) === "undefined") {
throw "Parameter e2e must be boolean.";
} else if (typeof (e2e) !== "boolean") {
throw "Parameter e2e must be boolean.";
}
this.e2e = e2e;
window.__isE2E = e2e; // Publish this so the global onerror code can see it.
this._updateE2EHandlers();
},
/**
* Add any calls here that will change on enabling or disabling end-to-end testing.
*
* @method _updateE2EHandlers
*/
_updateE2EHandlers: function() {
// Add tasks and mods here a we find them.
// TODO: Disable angular animations when e2e is enabled.
// ...
},
/**
* Return whether we are running inside WHM or something else (e.g., whm)
*
* @method isWebmail
* @return {Boolean} true if this is webmail; false otherwise
*/
isWebmail: function() {
return (/webmail/i).test(this.applicationName);
},
/**
* Get the name of the theme from the URL if applicable.
*
* @method getTheme
* @return {String} Name of the theme from the url
*/
getTheme: function() {
var theme = this.theme;
if (!theme) {
if (!this.isUnprotected() && ( this.isCpanel() || this.isWebmail() )) {
var folders = this._url_path.split("/");
this.theme = theme = folders[3];
}
}
return theme;
},
/**
* Get the path up to the theme if applicable to the current application.
*
* @method getThemePath
* @return {String} Path including theme if applicable for the application.
*/
getThemePath: function() {
var themePath = this.themePath;
if (!themePath) {
themePath = this.securityToken + "/";
if ( this.isUnprotected() ) {
themePath = this.config.themePath;
} else if ( this.isCpanel() ) {
themePath += "frontend/" + this.getTheme() + "/";
} else if ( this.isWebmail() ) {
themePath += "webmail/" + this.getTheme() + "/";
} else if ( this.isUnitTest() ) {
// Unit tests
themePath = "/";
} else if ( this.isOther() || PAGE.customThemePath) {
// For unrecognized applications, use the path passed in PAGE.THEME_PATH
themePath = this.config.themePath;
}
this.themePath = themePath;
}
return themePath;
},
/**
* Get the domain relative path for the relative url path.
*
* @method buildPath
* @return {String} Domain relative url path including theme if applicable for the application to the file.
*/
buildPath: function(relative) {
return this.getThemePath() + relative;
},
/**
* Get the full url path for the relative url path.
*
* @method buildFullPath
* @return {String} Full url path including theme if applicable for the application to the file.
*/
buildFullPath: function(relative) {
return this.protocol + "//" + this.domain + ":" + this.port + this.buildPath(relative);
},
/**
* Get the url for the login ui.
*
* @method getLoginPath
* @return {String} Url that will trigger a login.
*/
getLoginPath: function() {
return this.getRootPath();
// TODO: Add redirect url once its supported by cpsvrd
// return this.getRootPath() + "?redir=" + this.applicationPath;
},
/**
* Get the url for the root of the application.
*
* @method getRootPath
* @return {String} Url that will trigger a login.
*/
getRootPath: function() {
return this.protocol + "//" + this.domain + ":" + this.port;
},
/**
* State management for the Unique Id generator.
*
* @private
* @type {Object} - Contains the counters for the Unique Id generator.
*/
_uniqueSets: {},
/**
* Generate a unique id based on the prefix.
*
* @param {string} [prefix] Optional prefix for the id. Will default to id.
* @param {number} [start] Optional start number. Defaults to 1.
* @return {string} Id that is unique within the context.
*/
generateUniqueId: function(prefix, start) {
prefix = prefix || "id";
var id = start || 1;
if (!this._uniqueSets[prefix]) {
this._uniqueSets[prefix] = id;
} else {
id = ++this._uniqueSets[prefix];
}
return prefix + id;
},
/**
* Conditionally log a message only in debug mode
*
* @method debug
* @param {String} msg
*/
debug: function(msg) {
if (this.config.debug && window && window.console) {
window.console.log(msg);
}
},
/**
* Log a message
*
* @method debug
* @param {String} msg
*/
log: function(msg) {
if (window && window.console) {
window.console.log(msg);
}
},
/**
Cloned from YUI 3.8.1 implementation so the CPANEL namespace can emulate
the behavior of YAHOO namespace.
Utility method for safely creating namespaces if they don't already exist.
May be called statically on the CPANEL global object.
When called statically, a namespace will be created on the CPANEL global
object:
// Create `CPANEL.your.namespace.here` as nested objects, preserving any
// objects that already exist instead of overwriting them.
CPANEL.namespace('your.namespace.here');
Dots in the input string cause `namespace` to create nested objects for each
token. If any part of the requested namespace already exists, the current
object will be left in place and will not be overwritten. This allows
multiple calls to `namespace` to preserve existing namespaced properties.
If the first token in the namespace string is "CPANEL", that token is
discarded.
Be careful with namespace tokens. Reserved words may work in some browsers
and not others. For instance, the following will fail in some browsers
because the supported version of JavaScript reserves the word "long":
CPANEL.namespace('really.long.nested.namespace');
Note: If you pass multiple arguments to create multiple namespaces, only the
last one created is returned from this function.
@method namespace
@param {String} namespace* One or more namespaces to create.
@return {Object} Reference to the last namespace object created.
**/
namespace: function() {
var a = arguments,
o, i = 0,
j, d, arg;
for (; i < a.length; i++) {
o = this; // Reset base object per argument or it will get reused from the last
arg = a[i];
if (arg.indexOf(PERIOD) > -1) { // Skip this if no "." is present
d = arg.split(PERIOD);
/* TODO: Figure out how to remove the linter issue */
if (d[0] === "CPANEL") {
if (d[1] === "v2") {
j = 2;
} else {
j = 1;
}
}
for (; j < d.length; j++) {
o[d[j]] = o[d[j]] || {};
o = o[d[j]];
}
} else {
o[arg] = o[arg] || {};
o = o[arg]; // Reset base object to the new object so it's returned
}
}
return o;
}
};
/**
* Singleton Constructor
* @return {Object} Returns the CJT singleton.
*/
CJT.getInstance = function() {
if (instance === null) {
instance = new CJT();
}
return instance;
};
// The one global so qa tests can easily poke this.
window.__CJT2 = CJT.getInstance();
return CJT.getInstance();
});
/*
* cjt/bootstrap.js Copyright(c) 2020 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* DEVELOPERS NOTES:
* This is the common bootstrap routine used by all applications.
*/
/* global define: false */
define('cjt/bootstrap',[
"angular",
"cjt/core"
], function(
angular,
CJT
) {
"use strict";
return function(bootElement, applicationName) {
var bootEl = bootElement || "#content";
applicationName = applicationName || "App";
if (CJT.applicationName === "cpanel" && bootEl !== "#content") {
console.debug("Apps in cPanel that utilize the breadcrumbs need to bootstrap to #content to include that."); // eslint-disable-line no-console
}
if (angular.isString(bootEl)) {
var els = angular.element(bootEl);
if (els && els.length) {
bootEl = els[0];
} else {
throw "Can not start up angular application since we can not find the element: " + bootElement;
}
}
if (bootEl) {
angular.bootstrap(bootEl, [applicationName]);
} else {
throw "Can not start up angular application since the element was not passed or is undefined";
}
};
}
);
/*
# cjt/util/locale.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:false, console:false, global:false */
/* --------------------------*/
/* eslint camelcase: 0 */
// TODO: Add tests for these
/**
*
* @module cjt/util/locale
* @example
* define(
* [
* "cjt/util/locale",
* ],
* function(LOCALE) {
* LOCALE.numf(100.1);
* });
*
* @example
*
* The main use case is to get the current selected locale. If you need access to other locales, you
* must set them up manually by doing the following in a module that brings in cjt2/util/locale. With
* these you can generate new locales, modify existing locale, etc, though there should be very limited
* need
*
* define(
* [
* "cjt/util/locale",
* ],
* function(LOCALE) {
* var manager = LOCALE.get_manager();
* if (!manager.has_locale("fr")) {
* manager.generateClassFromCldr("fr", {
* misc_info: {
* delimiters: {
* quotation_start: "<<",
* quotation_end: ">>"
* }
* }
* });
* }
* }
*
*/
define('cjt/util/locale',["lodash"], function(_) {
"use strict";
/**
* Utility module for building localized strings.
*
* @static
* @public
* @class locale
*/
var DEFAULT_ELLIPSIS = {
initial: "…{0}",
medial: "{0}…{1}",
"final": "{0}…"
};
var html_apos = _.escape("'");
var html_quot = _.escape("\"");
var html_amp = _.escape("&");
/**
* JS getUTCDay() starts from Sunday, but CLDR starts from Monday.
* @param {[type]} the_date [description]
* @return {[type]} [description]
*/
var get_cldr_day = function(the_date) {
var num = the_date.getUTCDay() - 1;
return (num < 0) ? 6 : num;
};
function Locale() {}
Locale._locales = {};
Locale._currentLocaleTag = "";
/**
* Get the current locale tag name
* @return {String} Locale tag name in ISO ??? format.
*/
Locale.getCurrentLocale = function() {
return Locale._currentLocaleTag;
};
/**
* Sets the current locale by its tag name.
* @param {String} tag Locale tag name in ISO ??? format.
*/
Locale.setCurrentLocale = function(tag) {
if (tag in Locale._locales.keys()) {
Locale._currentLocaleTag = tag;
window.LOCALE = Locale._locales[tag];
} else {
// eslint-disable-next-line no-console
console.log("Failed to locate the requested locale " + tag + " in the loaded locales.");
}
return window.LOCALE;
};
/**
* Add a locale to the system
* @param {String} tag Locale tag name in ISO ??? format.
* @param {Function} construc Constructor function for the CLDR instance for this locale.
*/
Locale.add_locale = function(tag, construc) {
Locale._locales[tag] = construc;
Locale._currentLocaleTag = tag; // Assume the last one set is the current locale.
construc.prototype._locale_tag = tag;
};
/**
* Remove a local from the system
* @param {String} tag Locale tag name in ISO ??? format.
*/
Locale.remove_locale = function(tag) { // For testing
return delete Locale._locales[tag];
};
/**
* Remove all locales
*/
Locale.clear_locales = function(tag) { // For testing
Locale._locales = {};
};
/**
* Test if the locale generator or instance exists
* @param {String} tag Locale tag name in ISO format.
*/
Locale.has_locale = function(tag) {
return !!Locale._locales[tag];
};
/**
* Get a handle to the specified locale, processing the arguments until one is found.
* You can provide one or more instances tags to attempt. This method will search until
* if finds one or will return the first one if it cant find any of the ones you passed,
* or you didn't pass a tag.
* @param {String...} tag name of the locale to lookup.
* @return {Object} Locale object containing the CLDR knowledge and string management
* logic for a given locale.
*/
Locale.get_handle = function() {
var cur_arg;
var arg_count = arguments.length;
for (var a = 0; a < arg_count; a++) {
cur_arg = arguments[a];
if ( cur_arg in Locale._locales ) {
return new Locale._locales[cur_arg]();
}
}
// We didn't find anything from the given arguments, so check _locales.
// We can't trust JS's iteration order, so grab keys and take the first one.
var locale_tags = Object.keys(Locale._locales);
var loc = locale_tags.length ? locale_tags[0] : false;
var loc_obj = (loc && Locale._locales[loc] ? new Locale._locales[loc]() : new Locale());
loc_obj.get_manager = function() {
return Locale;
};
return loc_obj;
};
// ymd_string_to_date will be smarter once case 52389 is done.
// For now, we need the ymd order from the server.
Locale.ymd = null;
/**
* Convert a YYMMDD string to a date object
* @param {String} str [description]
* @return {Date} [description]
* @????
*/
Locale.ymd_string_to_date = function(str) {
var str_split = str.split(/\D+/);
var ymd = this.ymd || "mdy"; // U.S. English;
var day = str_split[ ymd.indexOf("d") ];
var month = str_split[ ymd.indexOf("m") ];
var year = str_split[ ymd.indexOf("y") ];
// It seems unlikely that we'd care about ancient times.
if ( year && (year.length < 4) ) {
var deficit = 4 - year.length;
year = String((new Date()).getFullYear()).substr(0, deficit) + year;
}
var date = new Date( year, month - 1, day );
return isNaN(date.getTime()) ? undefined : date;
};
// temporary, until case 52389 is in
Locale.date_template = null;
/**
* Convert a date into a YYMMDD string
* @param {Date} date [description]
* @return {String} [description]
*/
Locale.to_ymd_string = function(date) {
var template = Locale.date_template || "{month}/{day}/{year}"; // U.S. English
return template.replace(/\{(?:month|day|year)\}/g, function(subst) {
switch (subst) {
case "{day}":
return date.getDate();
case "{month}":
return date.getMonth() + 1;
case "{year}":
return date.getFullYear();
}
} );
};
var bracket_re = /([^~\[\]]+|~.|\[|\]|~)/g;
// cf. Locale::Maketext re DEL
var faux_comma = "\x07";
var faux_comma_re = new RegExp( faux_comma, "g" );
// For outside a bracket group
var tilde_chars = { "[": 1, "]": 1, "~": 1 };
var underscore_digit_re = /^_(\d+)$/;
var func_substitutions = {
"#": "numf",
"*": "quant"
};
// NOTE: There is no widely accepted consensus of exactly how to measure data
// and which units to use for it. For example, some bodies define "B" to mean
// bytes, while others don't. (NB: SI defines "B" to mean bels.) Some folks
// use k for kilo; others use K. Some say kilo should be 1,024; others say
// it's 1,000 (and "kibi" would be 1,024). What we do here is at least in
// longstanding use at cPanel.
var data_abbreviations = [ "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
// NOTE: args *must* be a list, not an Array object (as is permissible with
// most other functions in this module).
/**
* Internal implementation
* @param {String} str Source string.
* @return {String} Interpolated string.
*/
var _maketext = function(str /* , args list */) { // ## no extract maketext
if (!str) {
return;
}
str = this.LEXICON && this.LEXICON[str] || str;
if ( str.indexOf("[") === -1 ) {
return String(str);
}
var assembled = [];
var pieces = str.match(bracket_re);
var pieces_length = pieces.length;
var in_group = false;
var bracket_args = "";
var p, cur_p, a;
PIECE:
for (p = 0; p < pieces_length; p++) {
cur_p = pieces[p];
if ((cur_p === "[")) {
if (in_group) {
throw "Invalid maketext string: " + str; // ## no extract maketext
}
in_group = true;
} else if ( cur_p === "]" ) {
if (!in_group || !bracket_args) {
throw "Invalid maketext string: " + str; // ## no extract maketext
}
in_group = false;
var real_args = bracket_args.split(",");
var len = real_args.length;
var func;
if ( len === 1 ) {
var arg = real_args[0].match(underscore_digit_re);
if ( !arg ) {
throw "Invalid maketext string: " + str; // ## no extract maketext
}
var looked_up = arguments[arg[1]];
if ( typeof looked_up === "undefined" ) {
throw "Invalid argument \"" + arg[1] + "\" passed to maketext string: " + str; // ## no extract maketext
} else {
bracket_args = "";
assembled.push(looked_up);
continue PIECE;
}
} else {
func = real_args.shift();
len -= 1;
func = func_substitutions[func] || func;
if ( typeof this[func] !== "function" ) {
throw "Invalid function \"" + func + "\" in maketext string: " + str; // ## no extract maketext
}
}
if ( bracket_args.indexOf(faux_comma) !== -1 ) {
for (a = 0; a < len; a++) {
real_args[a] = real_args[a].replace(faux_comma_re, ",");
}
}
var cur_arg, alen;
for (a = 0; a < len; a++) {
cur_arg = real_args[a];
if ( cur_arg.charAt(0) === "_" ) {
if ( cur_arg === "_*" ) {
real_args.splice( a, 1 );
for (a = 1, alen = arguments.length; a < alen; a++) {
real_args.push( arguments[a] );
}
} else {
var arg_num = cur_arg.match(underscore_digit_re);
if ( arg_num ) {
if ( arg_num[1] in arguments ) {
real_args[a] = arguments[arg_num[1]];
} else {
throw "Invalid variable \"" + arg_num[1] + "\" in maketext string: " + str; // ## no extract maketext
}
} else {
throw "Invalid maketext string: " + str; // ## no extract maketext
}
}
}
}
bracket_args = "";
assembled.push( this[func].apply(this, real_args) );
} else if ( cur_p.charAt(0) === "~" ) {
var real_char = cur_p.charAt(1) || "~";
if ( in_group ) {
if (real_char === ",") {
bracket_args += faux_comma;
} else {
bracket_args += real_char;
}
} else if ( real_char in tilde_chars ) {
assembled.push(real_char);
} else {
assembled.push(cur_p);
}
} else if (in_group) {
bracket_args += cur_p;
} else {
assembled.push(cur_p);
}
}
if (in_group) {
throw "Invalid maketext string: " + str; // ## no extract maketext
}
return assembled.join("");
};
// Most of what is here ports functionality from CPAN Locale::Maketext::Utils.
Locale.prototype = Object.create({
/**
* Initialized with the value stored in window.LEXICON if there is anything.
* @type {Object} - where each key is the untranslated string and the value is the translated version
* of that string in the current users selected locale.
*/
LEXICON: (typeof window === "undefined") ?
global.LEXICON || (global.LEXICON = {}) :
window.LEXICON || (window.LEXICON = {}),
/**
* Use this method to localize a static string. These strings are harvested normally.
*
* @method maketext // ## no extract maketext
* @param {String} template Template to process.
* @param {...*} [args] Optional replacement arguments for the template.
* @return {String}
*/
maketext: _maketext, // ## no extract maketext
/**
* Like maketext() but does not lookup the phrase in the lexicon and compiles the phrase exactly as given. // ## no extract maketext
*
* @note In the current implementation this works just like maketext, but will need to be modified once we // ## no extract maketext
* start doing lexicon lookups.
*
* @method maketext // ## no extract maketext
* @param {String} template Template to process.
* @param {...*} [args] Optional replacement arguments for the template.
* @return {String}
*/
makethis: _maketext, // ## no extract maketext
/**
* Use this method instead of maketext if you are passing a variable that contains the maketext template. // ## no extract maketext
*
* @method makevar
* @param {String} template Template to process.
* @param {...*} [args] Optional replacement arguments for the template.
* @return {String}
* @example
*
* var translatable = LOCALE.translatable; // ## no extract maketext
* var template = translatable("What is this [numf,_1] thing."); // ## no extract maketext
* ...
* var localized = LOCALE.makevar(template)
*
* or
*
* var template = LOCALE.translatable("What is this [numf,_1] thing."); // ## no extract maketext
* ...
* var localized = LOCALE.makevar(template)
*
*/
makevar: _maketext, // this is a marker method that is ignored in phrase harvesting, but is functionally equivalent to maketext otherwise. // ## no extract maketext
/**
* Marks the phrase as translatable for the harvester. // ## no extract maketext
*
* @method translatable // ## no extract maketext
* @param {String} str Translatable string
* @return {Strung} Same string, this is just a marker function for the harvester
*/
translatable: function(str) { // ## no extract maketext
return str;
},
_locale_tag: null,
/**
* Get the language tag for this locale.
* @return {[type]} [description]
*/
get_language_tag: function() {
return this._locale_tag;
},
/**
* Returns an instance of Intl.Collator for the current locale.
* @return {[type]} [description]
*/
getCollator: function getCollator() {
if (!this._collator) {
var localeTag = this.get_language_tag();
try {
this._collator = new Intl.Collator(localeTag);
} catch (e) {
// eslint-disable-next-line no-console
console.info("Failed to create collator for locale: " + localeTag + "; falling back to “en”", e);
this._collator = new Intl.Collator("en");
}
}
return this._collator;
},
// These methods are locale-independent and should not need overrides.
/**
* Join a list with the provided separator.
* @param {[type]} sep [description]
* @param {[type]} list [description]
* @return {[type]} [description]
* @example
*
* LOCALE.maketext("We have [join,,,_1].", ["fruit", "nuts", "raisins"]); // ## no extract maketext
*/
join: function(sep, list) {
sep = String(sep);
if ( typeof list === "object" ) {
return list.join(sep);
} else {
var str = String(arguments[1]);
for (var a = 2; a < arguments.length; a++) {
str += sep + arguments[a];
}
return str;
}
},
/**
* Boolean operator.
* Perl has undef, but JavaScript has both null *and* undefined.
* Let's treat null as undefined since JSON doesn't know what
* undefined is, so serializers use null instead.
* @param {Boolean} condition [description]
* @param {String} when_true String to write when condition is true
* @param {String} when_false String to write when condition is false
* @param {String} [when_null] String to write when condition is null or undefined
* @return {String}
* @example
*
* LOCALE.maketext("We have [boolean,_1,a,no] banana.", bananas); // ## no extract maketext
*/
"boolean": function(condition, when_true, when_false, when_null) {
if (condition) {
return "" + when_true;
}
if ( ((arguments.length > 3) && (condition === null || condition === undefined)) ) {
return "" + when_null;
}
return "" + when_false;
},
/**
* Comment operator
* @return {String} Returns nothing
* @example
*
* LOCALE.maketext("We have only bananas. [comment,This does nothing]"); // ## no extract maketext
*/
comment: function() {
return "";
},
//
/**
* Output operator for bracket notation. Acts as "dispatch" function for the
* output_* methods below.
* @param {String} sub_func Name of the output function to process
* @param {String} str String to pass to the output function
* @return {String} Processed output.
*/
output: function( sub_func, str ) {
var that = this;
var sub_args = Array.prototype.concat.apply([], arguments).slice(1);
// Implementation of the chr() and amp() embeddable methods
if ( sub_args && typeof sub_args[0] === "string" ) {
sub_args[0] = sub_args[0].replace(/chr\((\d+|\S)\)/g, function(str, p1) {
return that.output_chr(p1);
});
sub_args[0] = sub_args[0].replace(/amp\(\)/g, function(str) {
return that.output_amp();
});
}
var func_name = "output_" + sub_func;
if ( typeof this[func_name] === "function" ) {
return this[func_name].apply(this, sub_args);
} else {
if (window.console) {
window.console.warn("Locale output function \"" + sub_func + "\" is not implemented.");
}
return str;
}
},
/**
* Output an HTML safe apostrophe
* @return {String}
*/
output_apos: function() {
return html_apos;
},
/**
* Output an HTML safe quote mark
* @return {String}
*/
output_quot: function() {
return html_quot;
},
// TODO: Implement embeddable methods described at
// https://metacpan.org/pod/Locale::Maketext::Utils#asis()
output_asis: String,
asis: String,
/**
* Output the string wrapped in a <u> HTML tag
* @param {String} str
* @return {String}
*/
output_underline: function(str) {
return "<u>" + str + "</u>";
},
/**
* Output the string wrapped in a <strong> HTML tag
* @param {String} str
* @return {String}
*/
output_strong: function(str) {
return "<strong>" + str + "</strong>";
},
/**
* Output the string wrapped in a <em> HTML tag
* @param {String} str
* @return {String}
*/
output_em: function(str) {
return "<em>" + str + "</em>";
},
/**
* Output the string wrapped in a <abbr> HTML tag
* @param {String} abbr Abbreviation
* @param {String} full Full version of the abbreviation
* @return {String}
*/
output_abbr: function(abbr, full) {
return "<abbr title=\"__FULL__\">".replace(/__FULL__/, full) + abbr + "</abbr>";
},
/**
* Output the string wrapped in a <abbr> HTML tag with special markings
* @param {String} abbr Acronym
* @param {String} full Full version of the acronym
* @return {String}
*/
output_acronym: function(abbr, full) {
// TODO: Is this still right with bootstrap???
return this.output_abbr(abbr, full).replace(/^(<[a-z]+)/i, "$1 class=\"initialism\"");
},
/**
* Output the string wrapped in a <span> HTML tag with the provided classes
* @param {String} str String to embed in the span.
* @param {String...} list of classes as arguments.
* @return {[type]} [description]
*/
output_class: function(str) {
var classes = Array.prototype.slice.call( arguments, 1 );
return "<span class=\"" + classes.join(" ") + "\">" + str + "</span>";
},
/**
* Output the requested character encoded as an HTML character.
* @param {Number} num Character code to output.
* @return {String}
*/
output_chr: function(num) {
return isNaN(+num) ? String(num) : _.escape(String.fromCharCode(num));
},
/**
* Output the HTML escaped version of an ampersand.
* @return {String}
*/
output_amp: function() {
return html_amp;
},
/**
* Output a url from the input. There are multiple forms possible:
* A) output_url( dest, text, [ config_obj ] )
* B) output_url( dest, text, [ key1, val1, [...] ] )
* C) output_url( dest, [ config_obj ] )
* D) output_url( dest, [ key1, val1, [...] ] )
* @param {String} dest Url to link the results too.
* @return {String}
*/
output_url: function(dest) {
var
args_length = arguments.length,
config = arguments[args_length - 1],
text,
key,
value,
start_i,
a,
len;
// object properties hash, form A or C
if ( typeof config === "object" ) {
text = (args_length === 3) ? arguments[1] : (config.html || dest);
// Go ahead and clobber other stuff.
if ( "_type" in config && config._type === "offsite" ) {
config["class"] = "offsite";
config.target = "_blank";
delete config._type;
}
} else {
config = {};
if (args_length % 2) {
start_i = 1;
} else {
text = arguments[1];
start_i = 2;
}
a = start_i;
len = arguments.length;
while ( a < len ) {
key = arguments[a];
value = arguments[++a];
if (key === "_type" && value === "offsite") {
config.target = "_blank";
config["class"] = "offsite";
} else {
config[key] = value;
}
a++;
}
if (!text) {
text = config.html || dest;
}
}
var html = "<a href=\"" + dest + "\"";
if ( typeof config === "object" ) {
for (key in config) {
if (config.hasOwnProperty(key)) {
html += " " + key + "=\"" + config[key] + "\"";
}
}
}
html += ">" + text + "</a>";
return html;
},
// Flattening argument lists in JS is much hairier than in Perl,
// so this doesn't flatten array objects. Hopefully CLDR will soon
// implement list_or; then we could deprecate this function.
// cf. http://unicode.org/cldr/trac/ticket/4051
list_separator: ", ",
oxford_separator: ",",
list_default_and: "&",
/**
* Maketext list operator
* @param {String} word Type of list to process. Examples are 'and' and 'or'.
* @return {[type]} [description]
*/
list: function(word /* , [foo,bar,...] | foo, bar, ... */) {
if (!word) {
word = this.list_default_and; // copying our Perl
}
var list_sep = this.list_separator;
var oxford_sep = this.oxford_separator;
var the_list;
if (typeof arguments[1] === "object" && arguments[1] instanceof Array) {
the_list = arguments[1];
} else {
the_list = Array.prototype.concat.apply([], arguments).slice(1);
}
var len = the_list.length;
if (!len) {
return "";
}
if (len === 1) {
return String(the_list[0]);
} else if (len === 2) {
return (the_list[0] + " " + word + " " + the_list[1]);
} else {
// Use slice() here to avoid altering the array
// since it may have been passed in as an object.
return (the_list.slice(0, -1).join(list_sep) + [oxford_sep, word, the_list.slice(-1)].join(" "));
}
},
/**
* Formats bytes with the specific decimal places.
* This depends on locale-specific overrides of base functionality
* but should not itself need an override.
* @param {[type]} bytes [description]
* @param {[type]} decimal_places [description]
* @return {[type]} [description]
*/
format_bytes: function(bytes, decimal_places) {
if ( decimal_places === undefined ) {
decimal_places = 2;
}
bytes = Number(bytes);
var exponent = bytes && Math.min( Math.floor( Math.log(bytes) / Math.log(1024) ), data_abbreviations.length );
if ( !exponent ) {
// This is a special, internal-to-format_bytes, phrase: developers will not have to deal with this phrase directly.
return this.maketext( "[quant,_1,%s byte,%s bytes]", bytes ); // the space between the '%s' and the 'b' is a non-break space (e.g. option-spacebar, not spacebar)
// We do not use or \u00a0 since:
// * parsers would need to know how to interpolate them in order to work with the phrase in the context of the system
// * the non-breaking space character behaves as you'd expect its various representations to.
// Should a second instance of this sort of thing happen we can revisit the idea of adding [comment] in the phrase itself or perhaps supporting an embedded call to [output,nbsp].
} else {
// We use \u00a0 here because it won't affect lookup since it is not
// being used in a source phrase and we don't want to worry about
// whether an entity is going to be interpreted or not.
return this.numf(bytes / Math.pow(1024, exponent), decimal_places) + "\u00a0" + data_abbreviations[exponent - 1];
}
},
// CLDR-informed functions
/**
* Maketext numerate operator
* @param {Number} num Quantity to numerate.
* @return {String}
*/
numerate: function(num) {
if ( this.get_plural_form ) { // from CPAN Locales
var numerated = this.get_plural_form.apply(this, arguments)[0];
if (numerated === undefined) {
numerated = arguments[arguments.length - 1];
}
return numerated;
} else {
// English-language logic, in the absence of CLDR
// The -1 case here is debatable.
// cf. http://unicode.org/cldr/trac/ticket/4049
var abs = Math.abs(num);
if (abs === 1) {
return "" + arguments[1];
} else if (abs === 0) {
return "" + arguments[ arguments.length - 1 ];
} else {
return "" + arguments[2];
}
}
},
/**
* Maketext quant operator..
* @param {Number} num Quantity on which the output depends.
* @return {[type]} [description]
*/
quant: function(num) {
var numerated,
is_special_zero,
decimal_places = 3;
if ( num instanceof Array ) {
decimal_places = num[1];
num = num[0];
}
if ( this.get_plural_form ) {
// from CPAN Locales
var gpf = this.get_plural_form.apply(this, arguments);
numerated = gpf[0];
// If there's a mismatch between the actual number of forms
// (singular, plural, etc.) and the real number, this can be
// undefined, which can break code. We pick the rightmost, or
// "most plural," form as a fallback.
if (numerated === undefined) {
numerated = arguments[arguments.length - 1];
}
is_special_zero = gpf[1];
} else {
// no CLDR, fall back to English
numerated = this.numerate.apply(this, arguments);
// Check: num is 0, we gave a special_zero value, and that numerate() gave it
is_special_zero = (parseInt(num, 10) === 0) &&
( arguments.length > 3 ) &&
( numerated === String(arguments[3]) )
;
}
var formatted = this.numf(num, decimal_places);
if (numerated.indexOf("%s") !== -1) {
return numerated.replace(/%s/g, formatted);
}
if (is_special_zero) {
return numerated;
}
return this.is_rtl() ? (numerated + " " + formatted) : (formatted + " " + numerated);
},
_max_decimal_places: 6,
/**
* [numf description]
* @param {[type]} num [description]
* @param {[type]} decimal_places [description]
* @return {[type]} [description]
*/
numf: function(num, decimal_places) {
if ( decimal_places === undefined ) {
decimal_places = this._max_decimal_places;
}
// exponential -> don't know how to deal
if (/e/.test(num)) {
return String(num);
}
var cldr, decimal_format, decimal_group, decimal_decimal;
try {
cldr = this.get_cldr("misc_info").cldr_formats;
decimal_format = cldr.decimal;
decimal_group = cldr._decimal_format_group;
decimal_decimal = cldr._decimal_format_decimal;
} catch (e) {}
// No CLDR, so fall back to hard-coded English values.
if (!decimal_format || !decimal_group || !decimal_decimal) {
decimal_format = "#,##0.###";
decimal_group = ",";
decimal_decimal = ".";
}
var is_negative = num < 0;
num = Math.abs(num);
// Trim the decimal part to 6 digits and round
var whole = Math.floor(num);
var normalized, fraction;
if ( /(?!')\.(?!')/.test(num) ) {
// This weirdness is necessary to avoid floating-point
// errors that can crop up with large-ish numbers.
// Convert to a simple fraction.
fraction = String(num).replace(/^[^.]+/, "0");
// Now round to the desired precision.
fraction = Number(fraction).toFixed(decimal_places);
// e.g., 1.9999 when only 3 decimal places are desired.
if (/^1/.test(fraction)) {
whole++;
num = whole;
fraction = undefined;
normalized = num;
} else {
fraction = fraction.replace(/^.*\./, "").replace(/0+$/, "");
normalized = Number( whole + "." + fraction );
}
} else {
normalized = num;
}
var pattern_with_outside_symbols;
if ( /(?!');(?!')/.test(decimal_format) ) {
pattern_with_outside_symbols = decimal_format.split(/(?!');(?!')/)[ is_negative ? 1 : 0 ];
} else {
pattern_with_outside_symbols = (is_negative ? "-" : "") + decimal_format;
}
var inner_pattern = pattern_with_outside_symbols.match(/[0#].*[0#]/)[0];
// Applying the integer part of the pattern is much easier if it's
// done with the strings reversed.
var pattern_split = inner_pattern.split(/(?!')\.(?!')/);
var int_pattern_split = pattern_split[0].split("").reverse().join("").split(/(?!'),(?!')/);
// If there is only one part of the int pattern, then set the "joiner"
// to empty string. (http://unicode.org/cldr/trac/ticket/4094)
var group_joiner;
if (int_pattern_split.length === 1) {
group_joiner = "";
} else {
// Most patterns look like #,##0.###, for which the leftmost # is
// just a placeholder so we know where to put the group separator.
int_pattern_split.pop();
group_joiner = decimal_group;
}
var whole_reverse = String(whole).split("").reverse();
var whole_assembled = []; // reversed
var pattern;
var replacer = function(chr) {
switch (chr) {
case "#":
return whole_reverse.shift() || "";
case "0":
return whole_reverse.shift() || "0";
}
};
while ( whole_reverse.length ) {
if ( int_pattern_split.length ) {
pattern = int_pattern_split.shift();
}
// Since this is reversed, we can just replace a character
// at a time, in regular forward order. Make sure we leave quoted
// stuff alone while paying attention to stuff *by* quoted stuff.
var assemble_chunk = pattern
.replace(/(?!')[0#]|[0#](?!')/g, replacer )
.replace(/'([.,0#;¤%E])'$/, "")
.replace(/'([.,0#;¤%E])'/, "$1")
;
whole_assembled.push(assemble_chunk);
}
var formatted_num = whole_assembled.join(group_joiner).split("").reverse().join("") + ( fraction ? decimal_decimal + fraction : "" );
return pattern_with_outside_symbols.replace(/[0#].*[0#]/, formatted_num);
},
// This *may* be useful publicly.
_quote: function(str) {
var delimiters;
try {
delimiters = this.get_cldr("misc_info").delimiters;
} catch (e) {
delimiters = {
quotation_start: "“",
quotation_end: "”"
};
}
return delimiters["quotation_start"] + str + delimiters["quotation_end"];
},
/**
* Quotes each value and then returns a localized “and”-list of them.
*
* Accepts either a list of arguments or a single array of arguments.
*
* @return {String} The localized list of quoted items.
*/
list_and_quoted: function() {
return this._list_quoted("list_and", arguments);
},
/**
* Quotes each value and then returns a localized “and”-list of them.
*
* Accepts either a list of arguments or a single array of arguments.
*
* @return {String} The localized list of quoted items.
*/
list_or_quoted: function() {
return this._list_quoted("list_or", arguments);
},
_list_quoted: function(join_fn, args) {
var the_list;
if (typeof (args[0]) === "object") {
if (args[0] instanceof Array) {
// slice() so that we don’t change the caller’s data
the_list = args[0].slice();
} else {
throw ( "Unrecognized list_and_quoted() argument: " + args[0].toString() );
}
} else {
the_list = Array.prototype.slice.apply(args);
}
// Emulate Locales.pm _quote_get_list_items() list_quote_mode 'all'.
// list_or(), currently not implemented in JS (no reason for it not to be), will need to behave the same
if (the_list === undefined || the_list.length === 0) {
the_list = [""]; // disambiguate no args
}
// The CJT1 code actually writes out the list_and() logic again.
// There doesn’t seem to be a good reason for that … ??
return this[join_fn](the_list.map( _.bind(this._quote, this) ) );
},
/**
* [list_and description]
* @return {[type]} [description]
*/
list_and: function() {
return this._list_join_cldr("list", arguments);
},
list_or: function() {
return this._list_join_cldr("list_or", arguments);
},
_list_join_cldr: function(templates_name, args) {
var the_list;
if ( (typeof args[0] === "object") && args[0] instanceof Array ) {
the_list = args[0];
} else {
the_list = args;
}
var cldr_list;
var len = the_list.length;
var pattern;
var text;
try {
cldr_list = this.get_cldr("misc_info").cldr_formats[templates_name];
} catch (e) {
// Use hard-coded English below if we don't have CLDR.
var conjunction = (templates_name === "list_or") ? "or" : "and";
cldr_list = {
2: "{0} " + conjunction + " {1}",
start: "{0}, {1}",
middle: "{0}, {1}",
end: "{0}, " + conjunction + " {1}",
};
}
var replacer = function(str, p1) {
switch (p1) {
case "0":
return text;
case "1":
return the_list[i++];
}
};
switch (len) {
case 0:
return;
case 1:
return String(the_list[0]);
default:
if ( len === 2 ) {
text = cldr_list["2"];
} else {
text = cldr_list.start;
}
text = text.replace(/\{([01])\}/g, function(all, bit) {
return the_list[bit];
});
if (len === 2) {
return text;
}
var i = 2;
while ( i < len ) {
pattern = cldr_list[ (i === len - 1) ? "end" : "middle" ];
text = pattern.replace(/\{([01])\}/g, replacer );
}
return text;
}
},
/**
* [_apply_quote_types description]
* @param {[type]} quotee [description]
* @param {[type]} starttype [description]
* @param {[type]} endtype [description]
* @return {[type]} [description]
*/
_apply_quote_types: function( quotee, starttype, endtype ) {
if (quotee === undefined) {
return;
}
var delimiters = this.get_cldr().misc_info.delimiters;
return delimiters[starttype] + quotee + delimiters[endtype];
},
/**
* [quote description]
* @param {[type]} quotee [description]
* @return {[type]} [description]
*/
quote: function( quotee ) {
return this._apply_quote_types( quotee, "quotation_start", "quotation_end" );
},
/**
* [alt_quote description]
* @param {[type]} quotee [description]
* @return {[type]} [description]
*/
alt_quote: function( quotee ) {
return this._apply_quote_types( quotee, "alternate_quotation_start", "alternate_quotation_end" );
},
/**
* [_quote_list description]
* @param {[type]} quote_method [description]
* @param {[type]} list_method [description]
* @param {[type]} items_obj [description]
* @return {[type]} [description]
*/
_quote_list: function( quote_method, list_method, items_obj ) {
if ( (typeof items_obj[0] === "object") && (items_obj[0] instanceof Array) ) {
items_obj = items_obj[0];
}
var quoted = [];
for (var i = items_obj.length - 1; i >= 0; i--) {
quoted[i] = this[quote_method]( items_obj[i] );
}
return this[list_method]( quoted );
},
/**
* [quote_list_and description]
* @return {[type]} [description]
*/
quote_list_and: function() {
return this._quote_list( "quote", "list_and", arguments );
},
/**
* [alt_quote_list_and description]
* @return {[type]} [description]
*/
alt_quote_list_and: function() {
return this._quote_list( "alt_quote", "list_and", arguments );
},
/* NOT IMPLEMENTED, pending a list_or() implementation
quote_list_or : function( items ) {
return this._quote_list( "quote", "list_or", items );
},
alt_quote_list_or : function( items ) {
return this._quote_list( "alt_quote", "list_or", items );
},
*/
/**
* [local_datetime description]
* @param {[type]} my_date [description]
* @param {[type]} format_string [description]
* @return {[type]} [description]
*/
local_datetime: function( my_date, format_string ) {
if (!this._cldr) {
return this.datetime.apply(this, arguments);
}
if ( my_date instanceof Date ) {
my_date = new Date(my_date);
} else if ( /^-?\d+$/.test(my_date) ) {
my_date = new Date(my_date * 1000);
} else {
my_date = new Date();
}
var tz_offset = my_date.getTimezoneOffset();
my_date.setMinutes( my_date.getMinutes() - tz_offset );
var non_utc = this.datetime( my_date, format_string );
// This is really hackish...but should be safe.
if ( non_utc.indexOf("UTC") > -1 ) {
var hours = (tz_offset > 0) ? "-" : "+";
hours += _.padStart(Math.floor(Math.abs(tz_offset) / 60).toString(), 2, "0");
var minutes = _.padStart((tz_offset % 60).toString(), 2, "0");
non_utc = non_utc.replace("UTC", "GMT" + hours + minutes);
}
return non_utc;
},
// time can be either epoch seconds or a JS Date object
// format_string can match the regexp below or be a [ date, time ] suffix pair
// (e.g., [ "medium", "short" ] -> "Aug 30, 2011 5:12 PM")
/**
* Format dates according to CLDR.
* This logic should stay in sync with Cpanel::Date::Format in Perl.
*
* @param {[type]} my_date [description]
* @param {[type]} format_string [description]
* @return {[type]} [description]
*/
datetime: function datetime( my_date, format_string ) {
if ( !my_date && (my_date !== 0) ) {
my_date = new Date();
} else if ( !(my_date instanceof Date) ) {
my_date = new Date(my_date * 1000);
}
var loc_strs = this.get_cldr("datetime");
if ( !loc_strs ) {
return my_date.toString();
}
if ( format_string ) {
// Make sure we don't just grab any random CLDR datetime key.
if ( /^(?:date|time|datetime|special)_format_/.test(format_string) ) {
format_string = loc_strs[format_string];
}
} else {
format_string = loc_strs.date_format_long;
}
/**
* [substituter description]
* @return {[type]} [description]
*/
var substituter = function() {
// Check for quoted strings
if (arguments[1]) {
return arguments[1].substr( 1, arguments[1].length - 2 );
}
// No quoted string, eh? OK, let’s check for a known pattern.
var key = arguments[2];
var xformed = ( function() {
switch (key) {
case "yy":
return Math.abs(my_date.getUTCFullYear()).toString().slice(-2);
case "y":
case "yyy":
case "yyyy":
return Math.abs(my_date.getUTCFullYear());
case "MMMMM":
return loc_strs.month_format_narrow[my_date.getUTCMonth()];
case "LLLLL":
return loc_strs.month_stand_alone_narrow[my_date.getUTCMonth()];
case "MMMM":
return loc_strs.month_format_wide[my_date.getUTCMonth()];
case "LLLL":
return loc_strs.month_stand_alone_wide[my_date.getUTCMonth()];
case "MMM":
return loc_strs.month_format_abbreviated[my_date.getUTCMonth()];
case "LLL":
return loc_strs.month_stand_alone_abbreviated[my_date.getUTCMonth()];
case "MM":
case "LL":
return _.padStart((my_date.getUTCMonth() + 1).toString(), 2, "0");
case "M":
case "L":
return my_date.getUTCMonth() + 1;
case "EEEE":
return loc_strs.day_format_wide[ get_cldr_day(my_date) ];
case "EEE":
case "EE":
case "E":
return loc_strs.day_format_abbreviated[ get_cldr_day(my_date) ];
case "EEEEE":
return loc_strs.day_format_narrow[ get_cldr_day(my_date) ];
case "cccc":
return loc_strs.day_stand_alone_wide[ get_cldr_day(my_date) ];
case "ccc":
case "cc":
case "c":
return loc_strs.day_stand_alone_abbreviated[ get_cldr_day(my_date) ];
case "ccccc":
return loc_strs.day_stand_alone_narrow[ get_cldr_day(my_date) ];
case "dd":
return _.padStart(my_date.getUTCDate().toString(), 2, "0");
case "d":
return my_date.getUTCDate();
case "h":
case "hh":
var twelve_hours = my_date.getUTCHours();
if ( twelve_hours > 12 ) {
twelve_hours -= 12;
}
if ( twelve_hours === 0 ) {
twelve_hours = 12;
}
return ( key === "hh" ) ? _.padStart(twelve_hours.toString(), 2, "0") : twelve_hours;
case "H":
return my_date.getUTCHours();
case "HH":
return _.padStart(my_date.getUTCHours().toString(), 2, "0");
case "m":
return my_date.getUTCMinutes();
case "mm":
return _.padStart(my_date.getUTCMinutes().toString(), 2, "0");
case "s":
return my_date.getUTCSeconds();
case "ss":
return _.padStart(my_date.getUTCSeconds().toString(), 2, "0");
case "a":
var hours = my_date.getUTCHours();
if (hours < 12) {
return loc_strs.am_pm_abbreviated[0];
} else if ( hours > 12 ) {
return loc_strs.am_pm_abbreviated[1];
}
// CLDR defines "noon", but CPAN DateTime::Locale doesn't have it.
return loc_strs.am_pm_abbreviated[1];
case "z":
case "zzzz":
case "v":
case "vvvv":
return "UTC";
case "G":
case "GG":
case "GGG":
return loc_strs.era_abbreviated[ my_date.getUTCFullYear() < 0 ? 0 : 1 ];
case "GGGGG":
return loc_strs.era_narrow[ my_date.getUTCFullYear() < 0 ? 0 : 1 ];
case "GGGG":
return loc_strs.era_wide[ my_date.getUTCFullYear() < 0 ? 0 : 1 ];
}
if (window.console) {
// eslint-disable-next-line no-console
console.warn("Unknown CLDR date/time pattern: " + key + " (" + format_string + ")" );
}
return key;
} )();
return xformed;
};
return format_string.replace(
/('[^']+')|(([a-zA-Z])\3*)/g,
substituter
);
},
/**
* [is_rtl description]
* @return {Boolean} [description]
*/
is_rtl: function() {
try {
return this.get_cldr("misc_info").orientation.characters === "right-to-left";
} catch (e) {
return false;
}
},
/**
* Shorten a string into one or two end fragments, using CLDR formatting.
*
* ex.: elide( "123456", 2 ) //"12…"
* ex.: elide( "123456", 2, 2 ) //"12…56"
* ex.: elide( "123456", 0, 2 ) //"…56"
*
* @param str {String} The actual string to shorten.
* @param start_length {Number} How many initial characters to put into the result.
* @param end_length {Number} How many final characters to put into the result. (optional)
* @return {String} The processed string.
*/
elide: function(str, start_length, end_length) {
start_length = start_length || 0;
end_length = end_length || 0;
if (str.length <= (start_length + end_length)) {
return str;
}
var template, substring0, substring1;
if (start_length) {
if (end_length) {
template = "medial";
substring0 = str.substr(0, start_length);
substring1 = str.substr( str.length - end_length );
} else {
template = "final";
substring0 = str.substr(0, start_length);
}
} else if (end_length) {
template = "initial";
substring0 = str.substr( str.length - end_length );
} else {
return "";
}
try {
template = this._cldr.misc_info.cldr_formats.ellipsis[template]; // JS reserved word
} catch (e) {
template = DEFAULT_ELLIPSIS[template];
}
if (substring1) { // medial
return template
.replace( "{0}", substring0 )
.replace( "{1}", substring1 )
;
}
return template.replace( "{0}", substring0 );
},
/**
* [get_first_day_of_week description]
* @return {[type]} [description]
*/
get_first_day_of_week: function() {
var fd = Number(this.get_cldr("datetime").first_day_of_week) + 1;
return (fd === 8) ? 0 : fd;
},
/**
* [set_cldr description]
* @param {[type]} cldr [description]
*/
set_cldr: function(cldr) {
var cldr_obj = this._cldr;
if ( !cldr_obj ) {
cldr_obj = this._cldr = {};
}
for (var key in cldr) {
if (cldr.hasOwnProperty(key)) {
cldr_obj[key] = cldr[key];
}
}
},
/**
* [get_cldr description]
* @param {[type]} key [description]
* @return {[type]} [description]
*/
get_cldr: function(key) {
if ( !this._cldr ) {
return;
}
if ((typeof key === "object") && (key instanceof Array)) {
return key.map(this.get_cldr, this);
} else {
return key ? this._cldr[key] : this._cldr;
}
},
/**
* For testing. Don't "delete" since this will cause prototype traversal.
* @return {[type]} [description]
*/
reset_cldr: function() {
this._cldr = undefined;
},
_cldr: null
});
// Annotate the class hierarchy for introspection.
Locale.prototype.constructor = Locale;
Locale.prototype.parent = Object;
Locale.generateClassFromCldr = function(tag, cldr) {
// Create a custom class for the locale generated from the CLDR data.
var GeneratedLocale = function() {
GeneratedLocale.prototype.parent.apply(this, arguments);
this.set_cldr( { datetime: cldr.datetime_info } );
this.set_cldr( { misc_info: cldr.misc_info } );
};
GeneratedLocale.prototype = new Locale();
// Annotate the class hierarchy for introspection.
GeneratedLocale.prototype.constructor = GeneratedLocale;
GeneratedLocale.prototype.parent = Locale;
// Mix in the the CLDR locale functions into the new class.
for (var key in cldr.functions) {
if ( cldr.functions.hasOwnProperty(key)) {
GeneratedLocale.prototype[key] = cldr.functions[key];
}
}
// Add the new locale class to the collection
Locale.add_locale(tag, GeneratedLocale);
};
var tag;
if (window.CJT2_loader && window.CJT2_loader.CLDR && window.CJT2_loader.current_locale) {
tag = window.CJT2_loader.current_locale;
Locale.generateClassFromCldr(tag, window.CJT2_loader.CLDR[tag]);
}
return Locale.get_handle(tag);
});
/*
# cjt/config/componentConfiguration.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/config/componentConfiguration',[
// Libraries
"angular",
"cjt/core",
"cjt/util/locale"
],
function(angular, CJT, LOCALE) {
"use strict";
/**
* @typedef {Object} ComponentConfiguration
* @description Base class for all configuration objects.
* @property {String} component Name of the component associated with the configuration
*/
/**
* @typedef {ComponentConfiguration} AlertListConfiguration
* @property {string} position - one of the following:
* top-right
* top-middle
* top-left
* bottom-right
* bottom-middle
* bottom-left
* middle-right
* middle-middle
* middle-left
* @property {boolean} inline - if false, the alert list will use the position rule, otherwise, it will appear where it naturally flows in the html.
*/
/**
* @typedef {Object} ComponentConfigurations
* @property {AlertListConfiguration} alertList
*/
/**
* Generates the default render rules which are used if the user
* has not set a preference about where their alerts render, or if
* there is an error retrieving the user's render rules.
*
* @method _defaultComponentConfiguration
* @private
* @return {ComponentConfiguration} default display properties for the various UI components.
*/
function _defaultComponentConfiguration() {
return {
alertList: {
component: "alertList",
position: !LOCALE.is_rtl() ? "top-right" : "top-left",
inline: false
}
};
}
/**
* Get the component's configuration
* @method _getComponent
* @private
* @param {ComponentConfigurations} config Configuration for all components
* @param {String} [component] Name of the component.
* @return {ComponentConfiguration|ComponentConfigurations}
*/
function _getComponent(config, component) {
if (!component) {
return config;
} else if (component in config) {
return config[component];
} else {
throw new Error("The component " + component + " is not available in the configuration.");
}
}
/**
* Set the value of a component's configuration. Only components defined in
* the default configuration may be set.
*
* @method _setComponent
* @param {ComponentConfigurations} config Configuration for all components
* @param {String} component Name of the component.
* @param {ComponentConfiguration} value
*/
function _setComponent(config, component, value) {
if (!component) {
throw new Error("You must provide a component name when setting a component");
}
var defaultConfig = _defaultComponentConfiguration();
if (component in defaultConfig) {
if (!value) {
config[component] = defaultConfig[component];
} else {
value.component = component;
config[component] = value;
}
} else {
throw new Error("The component " + component + " is not available in the configuration.");
}
}
var module = angular.module("cjt2.config.componentConfiguration", []);
module.provider("componentConfiguration", function() {
var config = _defaultComponentConfiguration();
return {
/**
* Get a named component of the display configuration.
*
* @method getComponent
* @param {String} [component] Name of the component. If not provided the whole configuration is returned
* @return {Any} Data for the component. Varies depending on what component was requested. If no component is passed, the whole configuration is returned.
* @throws {Error} If the component name is not available in the configuration.
*/
getComponent: function(component) {
return _getComponent(config, component);
},
/**
* Sets the value for a component for the display configurtion
*
* @method setComponent
* @param {String} component Name of the component.
* @param {Any} value Data for the component. Varies depending on what component was requested.
*/
setComponent: function(component, value) {
_setComponent(config, component, value);
},
/**
* Gets the complete display configuration.
*
* @method get
* @return {Object} Object whose properties are the display configuration properties for various components.
*/
get: function() {
return config;
},
/**
* Sets the complete display configuration.
*
* @method set
* @param {Object} value An object whose keys represent the various component display properties.
*/
set: function(value) {
config = value;
},
/**
* @method $get
* @return {componentConfigurationService} [description]
*/
$get: function() {
/**
* @classdesc The display properties used by shared components.
* @name componentConfigurationService
* @class
*/
return {
/**
* Gets a specific component from the configuration
*
* @method getComponent
* @param {String} [component] Component name, if not passed, will get the whole configuration.
* @return {ComponentConfiguration|AlertListConfiguration} The configuration data for the component or the whole configuration.
*/
getComponent: function(component) {
return _getComponent(config, component);
},
/**
* Gets the complete configuration.
*
* @method get
* @return {ComponentConfiguration} The configuration data for the component or the whole configuration.
*/
get: function() {
return config;
},
/**
* Sets the value for a component for the display configurtion
*
* @method setComponent
* @param {String} component Name of the component.
* @param {Any} value Data for the component. Varies depending on what component was requested.
* @throws Will throw an error if component name is not provided OR if component is not available in the configuration.
*/
setComponent: function(component, value) {
return _setComponent(config, component, value);
},
/**
* Gets the default configuration so callers can see what changed in the component setup.
*
* @method getDefaults
* @return {ComponentConfigurations} All default component configurations
*/
getDefaults: function() {
var config = _defaultComponentConfiguration();
/**
* Gets a specific component from the default configuration
*
* @method getComponent
* @param {String} [component] Component name, if not passed, will get the whole configuration.
* @return {ComponentConfiguration|AlertListConfiguration} The configuration data for the component or the whole configuration.
*/
config.getComponent = function(component) {
return _getComponent(config, component);
};
/**
* Gets the complete default configuration.
*
* @method get
* @return {ComponentConfiguration} The configuration data for the component or the whole configuration.
*/
config.get = function() {
return config;
};
return config;
}
};
}
};
});
}
);
/*
* cjt/config/componentConfigurationLoader.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define('cjt/config/componentConfigurationLoader',[
"cjt/util/locale",
],
function(
LOCALE
) {
"use strict";
var COMPONENT_LIST = [
"common-alertList",
];
return function(provider, nvDataService, $window, $log) {
if (!provider) {
throw new Error(LOCALE.maketext("You must specify the [_1] argument.", "provider"));
}
if (!nvDataService) {
throw new Error(LOCALE.maketext("You must specify the [_1] argument.", "nvDataService"));
}
if (!$window) {
throw new Error(LOCALE.maketext("You must specify the [_1] argument.", "$window"));
}
if (!$log) {
throw new Error(LOCALE.maketext("You must specify the [_1] argument.", $log));
}
// Handle prefetch first
var needed = [];
if ($window.PAGE && $window.PAGE.COMPONENT_SETTINGS) {
COMPONENT_LIST.forEach(function(fullComponentName) {
var parts = fullComponentName.split("-");
var componentName = parts[1];
if ($window.PAGE.COMPONENT_SETTINGS.hasOwnProperty(fullComponentName)) {
provider.setComponent(componentName, $window.PAGE.COMPONENT_SETTINGS[fullComponentName]);
} else {
needed.push(fullComponentName);
}
});
} else {
needed = COMPONENT_LIST;
}
if (!needed.length) {
return;
}
// Handle fetching any properties missing from the prefetch
nvDataService.getObject(needed).then(
function(nvdata) {
needed.forEach(function(fullComponentName) {
var componentSettings = nvdata[fullComponentName];
// Parse the nvdata's value to json object before using it.
if (typeof componentSettings === "string") {
try {
componentSettings = JSON.parse(componentSettings);
} catch (e) {
componentSettings = null;
}
}
var parts = fullComponentName.split("-");
var componentName = parts[1];
provider.setComponent(componentName, componentSettings);
});
},
function(error) {
$log.error(LOCALE.maketext("The system failed to retrieve the account-wide personalization preferences with the error: [_1]", error));
}
);
};
});
/*
# cjt/util/query.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* jshint -W089 */
/* --------------------------*/
// TODO: Add tests for these
/**
*
* @module cjt/util/query
* @example
*
*/
define('cjt/util/query',["lodash"], function(_) {
"use strict";
/**
* Utility module for parsing and building querystrings.
*
* @static
* @public
* @class query
*/
var query = {
// Converts
//
// { foo: [ 1, 2, 3] }
//
// to:
//
// {
// foo: 1,
// "foo-1": 2,
// "foo-2": 3
// }
//
// This is useful for interacting with cPanel’s API.
expand_arrays_for_cpanel_api: function( data ) {
var my_args = {};
for ( var key in data ) {
if (Array.isArray(data[key])) {
my_args[key] = data[key][0];
for ( var v = 1; v < data[key].length; v++ ) {
my_args[key + "-" + v] = data[key][v];
}
} else {
my_args[key] = data[key];
}
}
return my_args;
},
// creates an HTTP query string from a JavaScript object
// For convenience when assembling the data, we make null and undefined
// values not be part of the query string.
make_query_string: function( data ) {
var query_string_parts = [];
for ( var key in data ) {
if ( data.hasOwnProperty(key) ) {
var value = data[key];
if ((value !== null) && (value !== undefined)) {
var encoded_key = encodeURIComponent(key);
if ( _.isArray( value ) ) {
for ( var cv = 0; cv < value.length; cv++ ) {
query_string_parts.push( encoded_key + "=" + encodeURIComponent(value[cv]) );
}
} else {
query_string_parts.push( encoded_key + "=" + encodeURIComponent(value) );
}
}
}
}
return query_string_parts.join("&");
},
// parses a given query string, or location.search if none is given
// returns an object corresponding to those values
parse_query_string: function( qstr ) {
if ( qstr === undefined ) {
qstr = location.search.replace(/^\?/, "");
}
var parsed = {};
if (qstr) {
// This rejects invalid stuff
var pairs = qstr.match(/([^=&]*=[^=&]*)/g);
var plen = pairs.length;
if ( pairs && pairs.length ) {
for (var p = 0; p < plen; p++) {
var key_val = _.map(pairs[p].split(/=/), decodeURIComponent);
var key = key_val[0].replace(/\+/g, " ");
if ( key in parsed ) {
if ( typeof parsed[key] !== "string" ) {
parsed[key].push(key_val[1].replace(/\+/g, " "));
} else {
parsed[key] = [ parsed[key_val[0]], key_val[1].replace(/\+/g, " ") ];
}
} else {
parsed[key] = key_val[1].replace(/\+/g, " ");
}
}
}
}
return parsed;
}
};
return query;
});
/*
# cjt/io/api.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true, require:true, console: true */
/* --------------------------*/
/**
*
* @module cjt/io/api
* @example
*
* require(["cjt/io/api"], function(API) {
* return API.promise({
* module: "SetLang",
* func: "setlocale",
* data: { locale: "en-US" },
* callback: {
* failure: function(tId, res, args) {
* // We only get here for api failures
*
* // Do something for failure
* var error = String(res.error || res.cpanel_error || res);
* if (!error) {
* }
* },
* success: function(tId, res, args) {
* // Do something for success
* }
* }
* } );
*/
/* eslint-disable */
define('cjt/io/api',[
"cjt/core",
"jquery",
"cjt/util/query",
"cjt/util/locale"
],
function(CJT, $, QUERY, LOCALE) {
"use strict";
//------------------------------
// Module
//------------------------------
var MODULE_NAME = "cjt/io/api"; // requirejs(["cjt/io/api"], function(api) {}); -> cjt/io/api.js || cjt/io/api.debug.js
var MODULE_DESC = "";
var MODULE_VERSION = 2.0;
/* Each API is implemented in a module that constructs a static object with the
following interface. Implementers must provide a complete set of these methods
on their API driver object for it to function in the API system.
var IAPIDriver = {
parse_response : function(resp) {
},
find_messages : function(resp) {
},
find_status : function(resp) {
},
get_data : function(resp) {
},
get_meta : function(resp) {
},
build_query : function(api_version, args_obj) {
},
get_url : function(token, args_obj) {
}
}
*/
/**
* Wrapper method to make localization to an alternative AJAX model a bit more
* transparent.
*
* @method _ajax
* @private
* @param {String} method POST or GET
* @param {String} url The url for the request
* @param {Object} args The arguments object to pass to the api call.
* @param {Function} filter
* @param {Boolean} json If true, send application/json as the Content-Type
* @return {Promise} A Promise object that when resolved indicates the api was run,
* and when rejected indicates the api was not run or failed.
*/
var _ajax = function(method, url, args, filter, json) {
// Setting traditional to true so query params will look like:
// field=value1&field=value2
// instead of:
// field[]=value1&field[]=value2
var ajaxArgs = {
type: method || "POST",
url: url,
data: args,
traditional: true
};
if (filter) {
ajaxArgs.dataFilter = filter;
ajaxArgs.converters = {
"text json": function(data) {
// Replace the internal converter so we dont double parse.
return data;
}
};
}
if (json) {
ajaxArgs.contentType = "application/json";
}
return $.ajax(ajaxArgs);
};
/**
* Normalized the application detection
*
* @method _find_is_whm
* @private
* @param {Object} args_obj Arguments to the api call.
* {String} args_obj.application Optional name of the application.
* @return {Boolean}
*/
var _find_is_whm = function(args_obj) {
return (
args_obj.application === CJT.KNOWN_APPLICATIONS.WHM
|| CJT.isWhm()
|| CJT.isUnitTest() && require("karmaHelpers").isWhmUnitTest()
);
};
/**
* Normalized the version information for the application.
*
* @name _find_api_version
* @private
* @param {Object} args_obj Arguments to the api call.
* @return {Number} Version number of the invoked api call.
*/
var _find_api_version = function(args_obj) {
var version;
if ("version" in args_obj) {
version = args_obj.version;
} else if ("api_data" in args_obj &&
"version" in args_obj.api_data) {
version = args_obj.api_data.version;
} else {
// Since we didn't pass it, we have to guess
if (_find_is_whm(args_obj)) {
// We are in WHM so default to WHM v1.
version = 1;
} else {
// We are in CPANEL so default to API2.
// CONSIDER: This assumption will have to be evaluated once UAPI is
// more widely implemented at which time this would be change to 3.
version = 2;
}
}
return parseInt(version, 10);
};
/**
* Retrieves the driver for the specified interface and version
*
* @method _get_api_driver
* @private
* @param {Boolean} is_whm true if we are in WHM, false otherwise.
* @param {Number} api_version Version of the api: Should be 1, 2 or 3.
* @return {Object} Driver object for the requested interface and version supporting the IAPIDriver interface.
*/
var _get_api_driver = function(is_whm, api_version) {
var api_driver;
if (is_whm) {
switch (api_version) {
case 1:
api_driver = require("cjt/io/whm-v1");
break;
case 3:
// NOTE: This is future proofing, but not actually supported until UAPI is ported to WHM.
api_driver = require("cjt/io/uapi");
break;
default:
// WHM defaults to WHM v1
api_driver = require("cjt/io/whm-v1");
break;
}
} else {
switch (api_version) {
case 1:
api_driver = require("cjt/io/api1");
break;
case 2:
api_driver = require("cjt/io/api2");
break;
case 3:
api_driver = require("cjt/io/uapi");
break;
default:
// CPANEL defaults to API2
api_driver = require("cjt/io/api2");
break;
}
}
return api_driver;
};
/**
* Construct the api call query string
*
* @method construct_api_query
* @private
* @param {Number} api_version Version number
* @param {Object} args_obj Arguments to the call.
* @param {Object} driver Driver supporting the IAPIDriver interface.
* @return {String} Url to call the api call.
*/
var _construct_api_query = function(args_obj, driver) {
var api_call = driver.build_query(args_obj);
if (args_obj.json){
return JSON.stringify(api_call);
}
// TODO: See if we can use Y.QueryString.stringify() here.
return QUERY.make_query_string(api_call);
};
/**
* Build the data filter from the driver.
*
* @method _build_data_filter
* @private
* @param {Number} api_version Version of the api: Should be 1, 2 or 3.
* @param {Object} args_obj Arguments to the call.
* @param {Object} driver Driver supporting the IAPIDriver interface.
* @return {Object} Object containing the callbacks for the call
* adjusted into a standard format.
*/
var _build_data_filter = function(api_version, args_obj, driver) {
var dataFilter = function(data, type) {
var parse_response = driver.parse_response;
if (!parse_response) {
var msg = "No parser for the API version requested:" + CJT.applicationName + " " + api_version;
console.log(msg, "error", MODULE_NAME);
throw msg;
}
return parse_response(data, type, args_obj);
};
return dataFilter;
};
//CPANEL.api()
//
//Normalize interactions with cPanel and WHM's APIs.
//
//This checks for API failures as well as HTTP failures; both are
//routed to the "failure" callback.
//
//"failure" callbacks receive the same argument as with YUI
//asyncRequest, but with additional properties as given in api_base._parse_response below.
//
//NOTE: WHM API v1 responses are normalized to a list if the API return
//is a hash with only 1 value, and that value is a list.
//
//
//This function takes a single object as its argument with these keys:
// module (not needed in WHM API v1)
// func
// callback (cf. YUI 2 asyncRequest)
// data (goes to the API call itself)
// api_data (see below)
//
//Sort, filter, and pagination are passed in as api_data.
//They are formatted thus:
//
//sort: [ "foo", "!bar", ["baz","numeric"] ]
// "foo" is sorted normally, "bar" is descending, then "baz" with method "numeric"
// NB: "normally" means that the API determines the sort method to use.
//
//filter: [ ["foo","contains","whatsit"], ["baz","gt",2], ["*","contains","bar"]
// each [] is column,type,term
// column of "*" is a wild-card search (only does "contains")
//
//paginate: { start: 12, size: 20 }
// gets 20 records starting at index 12 (0-indexed)
//
//NOTE: sorting, filtering, and paginating do NOT work with cPanel API 1!
var api = {
MODULE_NAME: MODULE_NAME,
MODULE_DESC: MODULE_DESC,
MODULE_VERSION: MODULE_VERSION,
/**
* Setup the api call promise
*
* @method promise
* @static
* @param {Object} args_obj Object containing the arguments for the call
* @return {Promise} A promise that encapsulates the request.
*/
promise: function(args_obj) {
if(typeof args_obj === "undefined"){
throw new Error("Parameter args_obj does not exist.");
}
var is_whm = _find_is_whm(args_obj);
var api_version = _find_api_version(args_obj);
// Retrieve the driver for the selected api
var api_driver = _get_api_driver(is_whm, api_version);
if (!api_driver) {
var msg = "Could not find the driver for the API version requested:" + CJT.applicationName + " " + api_version;
throw msg;
}
// Fix up the filter callback
var filter = _build_data_filter(api_version, args_obj, api_driver);
// Get the url from the driver
var url = api_driver.get_url(CJT.securityToken, args_obj);
var query = _construct_api_query(args_obj, api_driver);
var method = (args_obj.args && typeof args_obj.args.method !== "undefined") ? args_obj.args.method : "POST";
// Start the request
var req_obj = _ajax(
method,
url,
query,
filter,
args_obj.json
);
return req_obj;
}
};
return api;
}
);
/* eslint-enable */
;
/*
# cpanel - share/libraries/cjt2/src/util/analytics.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global window: true, define: false */
(function() {
"use strict";
/**
* @typedef {Object} AnalyticsState
* @property {Boolean} enable - true to enable analytics logging, false otherwise.
*
* @function AnalyticsState
* @constructor
* @param {Object} [options] Optional set of initializaton options.
* @param {Boolean} [options.enable] If true, analytics logging will be enabled.
*/
function AnalyticsState(options) {
if( !(this instanceof AnalyticsState) ) {
return new AnalyticsState(options);
}
this.update(this.DEFAULTS);
this.update(options);
}
// Only properties that exist on this default object can be set via update()
AnalyticsState.prototype.DEFAULTS = {
enable: true,
};
/**
* Given an object containing valid AnalyticsState properties, this method will
* update the values of the AnalyticsState instance to the values provided by
* said object.
*
* @method update
* @param {Object} options The options and values to change.
* @return {AnalyticsState} The updated AnalyticsState instance.
*/
AnalyticsState.prototype.update = function(options) {
var self = this;
options = _parseOptions(options);
// Only update properties that are present in DEFAULTS
var propNames = Object.keys( self.DEFAULTS );
propNames.forEach(function(propName) {
if(options.hasOwnProperty(propName)) {
self[propName] = options[propName];
}
});
return self;
};
/**
* Serializes the AnalyticsState object into a JSON blob.
*
* @method serialize
* @return {String} A JSON string representing the analytics state.
*/
AnalyticsState.prototype.serialize = function() {
return JSON.stringify( this );
};
function _parseOptions(options) {
if(!options) {
return {};
}
if(options.hasOwnProperty("enable")) {
options.enable = Boolean(options.enable);
}
return options;
}
// This is the actual return value
var Analytics = {
_constructor: AnalyticsState, // For testing purposes
/**
* Create an analytics state instance that carries the current client state.
*
* @method create
* @static
* @param {Object} [options] Optional set of initialization options.
* @return {AnalyticsState} A new analytics state object.
*/
create: function create(options) {
return new AnalyticsState(options);
},
/**
* Test whether an object was created by the create method.
*
* @method isAnalyticsStateInstance
* @static
* @param {Object} thingToTest The object to test.
* @return {Boolean} True if it is an instance of AnalyticsState.
*/
isAnalyticsStateInstance: function(thingToTest) {
return (thingToTest instanceof AnalyticsState);
},
};
if ( typeof define === "function" && define.amd ) {
define('cjt/util/analytics',[], function() {
return Analytics;
});
} else {
if (!window.CPANEL) {
window.CPANEL = {};
}
window.CPANEL.Analytics = Analytics;
}
}());
/*
# cjt/io/request.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* eslint-env amd */
/* --------------------------*/
// -----------------------------------------------------------------------
// DEVELOPER NOTES:
// -----------------------------------------------------------------------
// TODO: Change api2 to use this base class.
// -----------------------------------------------------------------------
/**
* This is a base class factory for request objects so that the common part of their implementation
* can be shared between concrete classes.
*
* @module cjt/io/request
* @see module:cjt/io/request:Request
* @example
*
* require(["cjt/io/request"], function(generateRequestClass) {
* var BaseClass = generateRequestClass(1, function { ... });
* var CustomRequest = function() {
* Base.call(this);
* };
* CustomRequest.prototype = Object.create(Base.prototype, {
* method: {
* value: function() {
* ...
* }
* },
* booleanProperty: {
* value: false
* },
* stringProperty: {
* value: "custom"
* },
* ...
* });
* CustomRequest.prototype.constructor = CustomRequest;
*
* var request = new CustomRequest();
* ...
* });
*/
define('cjt/io/request',[
"lodash",
"cjt/util/analytics",
], function(_, ANALYTICS) {
"use strict";
/**
* Factory method that generates a Request base class based on the parameters passed.
*
* @param {Number} version
* @param {Function} getEmptyMeta Used to generate the meta data for a specific API version.
* @return {Function} Constructor function for the specific Request class.
*/
return function generateRequestClass(version, getEmptyMeta) {
/**
* Base class for all requests
*
* @class
* @exports module:cjt/io/request:Request
*/
var Request = function() {
/**
* API version number to use for UAPI.
*
* @property {Number} version
* @instance
*/
this.version = version;
/**
* API module name for UAPI call. Represents the module in <Module>::<Func>() for
* UAPI. Optional for WHM APIv1. If provided in WHM APIv1, it will append to the func
* name as: <Module>_<Func>.
*
* @property {String} module
* @instance
*/
this.module = "";
/**
* API function name to call. Represents the call in <Module>::<Func>() for
* UAPI or the method to lookup in WHM API 1 from the dispatch tables.
*
* @property {String} func
* @instance
*/
this.func = "";
/**
* Collection of arguments for the API call.
*
* @property {Object} args
* @instance
*/
this.args = {};
/**
* Whether to send the request as JSON.
*
* For backward compatibility with code already using uapi-request or whm-v1-request, this
* defaults to false. To enable JSON requests, set this to true or use the opts parameter
* in the initialize() method to enable json.
*
* @property {Boolean} json
* @instance
*/
this.json = false;
/**
* API call meta data for the UAPI or WHM API v1 call. Should be an object with names for
* each meta data parameter passed to the UAPI call. This data is used to
* control properties such as sorting, filters and paging.
*
* @property {Object} meta
* @instance
*/
this.meta = getEmptyMeta();
/**
* An object of auto-increment counters that will keep track of the numeric
* suffixes appended to arguments when the 'auto' option is set to true.
*
* @property {Number} autoCounter
* @instance
*/
this.autoCounter = {
__startVal: 1,
};
};
Request.prototype = {
/**
* @global
* @typedef RequestOptions
* @type {Object}
* @property {Boolean} [json] Use JSON request formatting. With this, the request arguments are sent to the
* server as JSON in the body of the request and the Content-Type header is set to 'application/json'.
* @property {Boolean} [realNamespaces] When true, treats the module as a real namespace, defaults to false. Only applicable to whm-v1.
*/
/**
* Initialize the request
*
* @method initialize
* @instance
* @param {String} module Name of the module where the API function exists.
* @param {String} func Name of the function to call.
* @param {Object} data Data for the function call.
* @param {Object} meta Meta-data such as sorting, filtering, paging and similar data for the API call.
* @param {RequestOptions} opts Use to set additional options for the request.
* @return {Request} The current instance of the request so calls can be chained.
* @throws When data is not an object or undefined or null.
*/
initialize: function(module, func, data, meta, opts) {
this.module = module;
this.func = func;
this.setArguments(data || {});
this.meta = meta || {};
this.json = opts && opts.json ? true : false;
return this; // for chaining
},
/**
* Sets the args property of the request. This can be used in lieu of addArgument when
* using JSON API Requests so you can set the arguments to any arbitrary object.
*
* @method setArguments
* @instance
* @param {Object} args The data must be serializable as JSON
* @throws When the args parameter is not an object.
* @return {Request} The current instance of the request so calls can be chained.
* @throws When args is not an object
*/
setArguments: function(args) {
if (typeof args !== "object") {
throw new TypeError("args parameter for 'setArgumetnObject' method must be an Object");
}
this.args = args;
return this;
},
/**
* Adds an argument
*
* @method addArgument
* @instance
* @param {String} name Name of the argument to add.
* @param {Object} value Value of the argument.
* @param {Boolean} [isAuto] Optional. If true, an automatic numeric suffix will be
* appended to the argument name.
* @return {Request} The current instance of the request so calls can be chained.
* @throws When this.args is not an object.
*/
addArgument: function(name, value, isAuto) {
this.validateArgs("addArgument");
if (!isAuto) {
this.args[name] = value;
} else {
this.args[name + this.getAutoSuffix(name)] = value;
this.incrementAuto(name);
}
return this;
},
/**
* Removes an argument
*
* @method removeArgument
* @instance
* @param {String} name Name of the argument to remove.
* @param {Boolean} [isAuto] Optional. If true, the last auto-incremented argument
* with the same base name will be removed.
* @return {Request} The current instance of the request so calls can be chained.
* @throws When this.args is not an object.
*/
removeArgument: function(name, isAuto) {
this.validateArgs("removeArgument");
name = isAuto ? (name + this.decrementAuto(name)) : name;
this.args[name] = null;
delete this.args[name];
return this;
},
/**
* Clears the arguments
*
* @method clearArguments
* @instance
* @param {String} name Name of the argument
* @param {Object} value Value of the argument
* @return {Request} The current instance of the request so calls can be chained.
*/
clearArguments: function() {
this.args = {};
this.autoCounter = {
__startVal: this.autoCounter.__startVal,
};
return this;
},
/**
* Get the current auto increment value for the given property.
*
* @method getAutoSuffix
* @instance
* @param {String} name The name of the property that will be incremented.
* @return {Number} New value of the auto increment for the property.
*/
getAutoSuffix: function(name) {
if (_.isUndefined( this.autoCounter[name] ) ) {
this.autoCounter[name] = this.autoCounter.__startVal;
}
return this.autoCounter[name];
},
/**
* Increment the suffix counter for a given argument name.
*
* @method incrementAuto
* @instance
* @param {String} name The name of the property that will be incremented.
* @return {Number} New value of the auto increment for the property.
*/
incrementAuto: function(name) {
return (this.autoCounter[name] = this.getAutoSuffix(name) + 1);
},
/**
* Decrement the argument suffix counter for a given argument name. The
* counter will never go below the __startVal.
*
* @method decrementAuto
* @instance
* @param {String} name The name of the property that will be decremented.
* @return {Number} New value of the auto increment for the property.
*/
decrementAuto: function(name) {
var currVal = this.getAutoSuffix(name);
if (currVal > this.autoCounter.__startVal) {
this.autoCounter[name] = currVal - 1;
}
return this.autoCounter[name];
},
/**
* Add analytics metadata to the API request. If the AnalyticsState object has already
* been instantiated, any options passed will update the instance with those key/value
* pairs while leaving omitted properties intact. If you wish to remove properties you
* must reset the analytics metadata using clearAnaltyics() first.
*
* @method addAnalytics
* @instance
* @param {Object} [options] Optional hash of options to pass to the AnalyticsState
* constructor. See cjt/util/analytics for details.
* @return {Request} The current instance of the request so calls can be chained.
*/
addAnalytics: function(options) {
if (!this.analytics) {
this.analytics = ANALYTICS.create(options);
} else {
this.analytics.update(options);
}
return this;
},
/**
* Clears the analytics metadata from this API request.
*
* @method clearAnalytics
* @instance
* @return {Request} The current instance of the request so calls can be chained.
*/
clearAnalytics: function() {
delete this.analytics;
return this;
},
/**
* @global
* @typedef RunArguments
* @type {Object}
* @property {Object} [analytics] Request analytics for this API call.
* @property {Object} args Argument to the API call.
* @property {String} func Name of the API call.
* @property {Boolean} json When true, send request as application/json, otherwise, send request as application/x-www-form-urlencoded
* @property {Object} meta Filter, sort and paging rules for API call.
* @property {String} [module] Name of the module where the API call exists. Required for UAPI. Optional for WHM API v1.
* @property {Number} [version] Version of the API.
*/
/**
* Build the UAPI call data structure.
*
* @method getRunArguments
* @instance
* @return {RunArguments} Packages up an API call based on collected information.
*/
getRunArguments: function() {
return {
version: this.version,
module: this.module,
func: this.func,
meta: this.meta,
args: this.args,
analytics: this.analytics,
json: this.json
};
},
/**
* Validate that the args property is an object. Used by other methods that depend on this precondition.
*
* @protected
* @param {String} method Name of the method being attempted.
* @throws When this.args is not an object.
*/
validateArgs: function(method) {
if (typeof this.args !== "object") {
throw new Error("You can not call '" + method + "'' if you the args property to something other than an Object");
}
},
/**
* Validate that the meta property is an object. Used by other methods that depend on this precondition.
*
* @protected
* @param {String} method Name of the method being attempted.
* @throws When this.meta is not an object.
*/
validateMeta: function(method) {
if (typeof this.meta !== "object") {
throw new Error("You can not call '" + method + "'' if you the meta property to something other than an Object");
}
}
};
return Request;
};
});
/*
# cjt/io/uapi-request.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* eslint-env amd */
/* --------------------------*/
// -----------------------------------------------------------------------
// DEVELOPER NOTES:
// -----------------------------------------------------------------------
// TODO: Add better argument checking:
// * no negative pages
// * no sort field provided
//
// See the xit() tests...
// -----------------------------------------------------------------------
/**
* Helper module to build requests for UAPI.
*
* @module cjt/io/uapi-request
* @example
*
* require(["cjt/io/uapi-request"], function(REQUEST) {
* var request = new REQUEST.Class();
* request.initialize("module-name", "function-name");
* request.addArgument("arg1", "value1");
* request.addArgument("arg2", "value2");
* request.addPaging(1, 10);
* request.addFilter("column1", "contains", "a");
* request.addSorting("columnt1", REQUEST.sort.ASCENDING);
*
* var callObject = requets.getRunArguments();
* });
*/
define('cjt/io/uapi-request',[
"lodash",
"cjt/io/request",
], function(_, generateRequestClass) {
"use strict";
// ------------------------------
// Module
// ------------------------------
var MODULE_NAME = "cjt/io/uapi-request";
var MODULE_DESC = "Contains a helper object used to build UAPI call parameters.";
var MODULE_VERSION = 2.0;
// ------------------------------
// Constants
// ------------------------------
var DEFAULT_PAGE_SIZE = 10;
var ASCENDING = 0;
var DESCENDING = 1;
/**
* Creates an empty metadata object
* @method _getEmptyRequestMeta
* @private
* @return {Object} Initialized abstract request metadata object
*/
var _getEmptyRequestMeta = function() {
return {
paginate: {
start: 0,
start_record: 0,
size: DEFAULT_PAGE_SIZE
},
filter: [],
sort: []
};
};
var Base = generateRequestClass(3, _getEmptyRequestMeta);
/**
* Helper class for generating UAPI requests.
* @class
* @augments module:cjt/io/request:Request
* @exports module:cjt/io/uapi-request:UapiRequest
*/
var UapiRequest = function() {
Base.call(this);
};
/**
* @static
* @property {enum} [sort] Contains sorting constants
*/
UapiRequest.sort = {
ASCENDING: ASCENDING,
DESCENDING: DESCENDING
};
UapiRequest.prototype = Object.create(Base.prototype, {
/**
* Adds the paging meta data to the run parameters in UAPI format
*
* @method addPaging
* @instance
* @param {Number} startPage Start page
* @param {Number} page_size Optional page size, inherits from previous initialization.
* @returns {UapiRequest} The current instance of the request so calls can be chained.
* @throws When this.meta is not an object.
*/
addPaging: {
value: function(startPage, pageSize) {
this.validateMeta("addPaging");
this.meta.paginate = this.meta.paginate || {};
// if the size is equal to the "All" page size sentinel value,
// abort pagination
if (pageSize === -1 || this.meta.paginate.size === -1) {
return;
}
pageSize = pageSize || this.meta.paginate.size || DEFAULT_PAGE_SIZE;
this.meta.paginate.start = ((startPage - 1) * pageSize) + 1;
this.meta.paginate.size = pageSize;
return this;
}
},
/**
* Clears the paging rules from the meta data.
*
* @method clearPaging
* @instance
* @returns {UapiRequest} The current instance of the request so calls can be chained.
* @throws When this.meta is not an object.
*/
clearPaging: {
value: function() {
this.validateMeta("clearPaging");
delete this.meta.paginate;
return this;
}
},
/**
* Add sorting rules meta data to the run parameters in API2 format.
*
* @method addSorting
* @instance
* @param {String} field Name of the field to sort on.
* @param {String} direction asc or dsc. Defaults to asc.
* @param {String} type Sort types supported by the API. Defaults to
* equality
* @returns {UapiRequest} The current instance of the request so calls can be chained.
* @throws When this.meta is not an object.
*/
addSorting: {
value: function(field, direction, type) {
this.validateMeta("addSorting");
var sortField = field;
var sortDir = direction || "asc";
var sortType = type || "";
if (sortField) {
var uapiSortRule = (sortDir === "asc" ? "" : "!") + sortField;
// If we have a type, the we need the complex array format
if (sortType !== "") {
uapiSortRule = [uapiSortRule, sortType];
}
// Store the rule.
if (this.meta.sort) {
this.meta.sort.push(uapiSortRule);
} else {
this.meta.sort = [uapiSortRule];
}
}
return this;
}
},
/**
* Clear the sorting rules meta data.
*
* @method clearSorting
* @instance
* @returns {UapiRequest} The current instance of the request so calls can be chained.
* @throws When this.meta is not an object.
*/
clearSorting: {
value: function() {
this.validateMeta("clearSorting");
delete this.meta.sort;
return this;
}
},
/**
* Add filter rules meta data to the run parameters in UAPI format.
*
* @method addFilter
* @instance
* @param {String|Array} key Field name or array of three elements [key, operator, value].
* @param {String} [operator] Comparison operator to use. Optional if the first parameter is an array.
* @param {String} [value] Value to compare the field with. Optional if the first parameter is an array.
* @returns {UapiRequest} The current instance of the request so calls can be chained.
* @throws When this.meta is not an object.
*/
addFilter: {
value: function(key, operator, value) {
this.validateMeta("addFilter");
var filter;
if (_.isArray(key)) {
filter = key;
} else {
filter = [key, operator, value];
}
// ---------------------------------------------
// Building a structure that looks like this:
//
// [
// [ key1 , operator1, value1 ],
// [ key2 , operator2, value2 ],
// ...
// ]
// ---------------------------------------------
if (filter) {
if (this.meta.filter) {
this.meta.filter.push(filter);
} else {
this.meta.filter = [filter];
}
}
return this;
}
},
/**
* Clear the meta data for the filter.
*
* @method clearFilter
* @instance
* @returns {UapiRequest} The current instance of the request so calls can be chained.
* @throws When this.meta is not an object.
*/
clearFilter: {
value: function() {
this.validateMeta("clearFilter");
delete this.meta.filter;
return this;
}
}
});
UapiRequest.prototype.constructor = UapiRequest; // Repair the constructor.
// Publish the component
return {
MODULE_NAME: MODULE_NAME,
MODULE_DESC: MODULE_DESC,
MODULE_VERSION: MODULE_VERSION,
Class: UapiRequest
};
});
/*
# cjt/services/nvDataServiceFactory.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
/**
* This module returns a factory function to generate NVDataService instances
* scoped to a specific application: whostmgr, cpanel, webmail. To work in
* an application, the application must expose an API for Personalization::get
* and Personalization::set. See the relevant back-end API modules in:
*
* Whostmgr/API/1/Personalization.pm
* Cpanel/API/Personalization.pm
*
* Instances created by this factory are available in cjt2 in for whostmgr,
* cpanel and webmail in:
*
* [NVDataService for cpanel and webmail]{@link module:cjt/services/cpanel/NVDataService}
* [NVDataService for whostmgr]{@link module:cjt/services/whm/NVDataService}
*
* @module cjt/services/nvDataServiceFactory
*/
define('cjt/services/nvDataServiceFactory',[
// Libraries
"angular"
],
function(angular) {
"use strict";
/**
* Factory method to generate specific NVDataServices that work in one of the
* applications: whostmgr, cpanel, webmail depending on the arguments passed.
*
* @function module:cjt/services/nvDataServiceFactory
* @param {module:cjt/io/request:Request} APIREQUEST
* @param {module:cjt/service/APIService:APIService} APIService
* @return {module:cjt/services/nvDataServiceFactory:NVDataService} Instance of the NVDataService for the specific application.
*/
return function(APIREQUEST, APIService) {
/**
* NVDataService for a specific application environment.
*
* @class
* @exports module:cjt/services/nvDataServiceFactory:NVDataService
*/
var NVDataService = function() {};
NVDataService.prototype = new APIService();
// Extend the prototype with any class-specific functionality
angular.extend(NVDataService.prototype, {
/**
* @global
* @typedef {Object} NameValuePair
* @property {String} name Name of the pair
* @property {?String} value Value of the named item
*/
/**
* @global
* @typedef {Object} SavedNameValuePair
* @property {String} name Name of the pair
* @property {Any} [value] Value of the named item if saved correctly
* @property {String} [error] Problem if the named item could not be saved.
*/
/**
* Gets one or more user preferences (nvdata) data elements
*
* @example "xmainrollstatus|xmaingroupsorder"
*
* @method get
* @instance
* @async
* @param {String|String[]} names one of the following:
* * name of one nvdata element as a string
* * an array of one or more nvdata names
* @return {Promise.<NameValuePair[]>} Promise that will fulfill the request.
*/
get: function(names) {
if (!angular.isArray(names)) {
names = [names];
}
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Personalization", "get", null, null, { json: true });
apiCall.addArgument("names", names);
return this.deferred(apiCall, {
apiSuccess: function(response, deferred) {
var list = [];
var personalization = response.data.personalization;
names.forEach(function(name) {
list.push({ name: name, value: personalization[name].value } );
});
deferred.resolve(list);
}
}).promise;
},
/**
* Set a single name/value pair on the server
*
* @method set
* @instance
* @async
* @param {String} name Name of the property to store
* @param {Any} value Value to store for the name
* @returns {Promise.<SavedNameValuePair>}
*/
set: function(name, value) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Personalization", "set", null, null, { json: true });
var pairs = {};
pairs[name] = value;
apiCall.setArguments({
personalization: pairs
});
return this.deferred(apiCall, {
apiSuccess: function(response, deferred) {
var personalization = response.data.personalization;
var pair = personalization[name];
var ret = { set: name, value: pair.value };
if (!pair.success) {
ret.error = pair.reason || "Unknown failure.";
}
deferred.resolve(ret);
}
}).promise;
},
/**
* Builds the NVData name/value pairs in a single object
* for the passed in names.
*
* @method getObject
* @instance
* @async
* @param {String|String[]} names one of the following:
* * name of one nvdata element as a string
* * an array of one or more nvdata names
* @return {Promise.<Object.<string, value>>} Promise that will fulfill the request.
* @example
* var names = [
* "xmainrollstatus",
* "xmaingroupsorder"
* ];
*
* nvDataService.getObject(names).then(function(data){
* console.log(data);
* });
*
* In this example, the following will be printed to the console:
*
* {
* xmainrollstatus: "databases=0|domains=0",
* xmaingroupsorder: "databases|files|domains|email"
* }
*/
getObject: function(names) {
if (!angular.isArray(names)) {
names = [names];
}
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Personalization", "get", null, null, { json: true });
apiCall.addArgument("names", names);
return this.deferred(apiCall, {
apiSuccess: function(response, deferred) {
var personalization = response.data.personalization;
var xform = {};
Object.keys(personalization).forEach(function(name) {
xform[name] = personalization[name].value;
});
deferred.resolve(xform);
}
}).promise;
},
/**
* Sets NVData values passed as key value pairs in an object
*
* @example
* {
* xmainrollstatus: "databases=0|domains=0",
* xmaingroupsorder: "databases|files|domains|email"
* }
*
* @method setObject
* @param {Object.<string,string>} data NVData pairs to be set where each property
* is the name of the pair and each property value is the value
* of the pair.
* @return {Promise.<SavedNameValuePair[]>} Promise that will fulfill the request.
*/
setObject: function(data) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Personalization", "set", null, null, { json: true });
apiCall.setArguments({
personalization: data
});
return this.deferred(apiCall, {
apiSuccess: function(response, deferred) {
var list = [];
var personalization = response.data.personalization;
Object.keys(personalization).forEach(function(name) {
var pair = personalization[name];
var ret = { set: name, value: pair.value };
if (!pair.success) {
ret.error = pair.reason || "Unknown failure.";
}
list.push(ret);
});
deferred.resolve(list);
}
}).promise;
}
});
return new NVDataService();
};
});
/*
# api/io/base.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
// TODO: Add tests for these
/**
* Contain the IAPI Driver implementation used to process cpanel api2
* request/response messages.
* @module cjt/io/api2
* @example
*
*/
define('cjt/io/base',["lodash", "cjt/util/locale"], function(_, LOCALE) {
"use strict";
// ------------------------------
// Module
// ------------------------------
var MODULE_NAME = "cjt/io/base"; // requirejs(["cjt/io/base"], function(base) {}); -> cjt/io/base.js || cjt/io/base.debug.js
var MODULE_DESC = "Contains helper methods reused by all the api drivers.";
var MODULE_VERSION = 2.0;
// ------------------------------
// State
// ------------------------------
var _transaction_args = {};
/**
* This static class contains various helper methods that are used by drivers.
* @class api_base
* @static
* @type {Object}
*/
var base = {
MODULE_NAME: MODULE_NAME,
MODULE_DESC: MODULE_DESC,
MODULE_VERSION: MODULE_VERSION,
/**
* It is useful for error reporting to show a failed transaction's arguments,
* so CPANEL.api stores these internally for later reporting.
*
* @method get_transaction_args
* @public
* @static
* @param {number} t_id The transaction ID (as given by YUI 2 asyncRequest)
* @return {object} A copy of the "arguments" object
*/
get_transaction_args: function(t_id) {
var args = _transaction_args[t_id];
return args && _.extend({}, args); // shallow copy
},
/**
* Generates an unknown error message.
*
* @method _unknown_error_msg
* @protected
* @static
* @return {String} Localized erroror message.
*/
_unknown_error_msg: function() {
return LOCALE.maketext("An unknown error occurred.");
},
/**
* Parse the response object and normalize it.
*
* @method _parse_response
* @protected
* @static
* @param {Function} status_finder Function that extracts the status from the object representation
* @param {Function} message_finder Function that extracts the message collection from the object representation
* @param {Function} data_getter Function that extracts the data from the object representation
* @param {Function} meta_getter Function that extracts the meta data from the object representation
* @param {String} response Raw JSON response from the io system
* @return {Function} Parsed response
*/
_parse_response: function(iapi_module, response) {
var error = null;
if (_.isString(response)) {
try {
response = JSON.parse(response);
} catch (e) {
if (window.console) {
window.console.log("Could not parse the response string: " + response + "\n" + e);
}
error = LOCALE.maketext("The API response could not be parsed.");
response = null;
}
}
return base._parse_response_object(iapi_module, response, error);
},
/**
* Parse the response object and normalize it.
*
* @method _parse_response_object
* @protected
* @static
* @param {Function} status_finder Function that extracts the status from the object representation
* @param {Function} message_finder Function that extracts the message collection from the object representation
* @param {Function} data_getter Function that extracts the data from the object representation
* @param {Function} meta_getter Function that extracts the meta data from the object representation
* @param {Object} response Raw JSON response from the io system
* @return {Function} [description]
*/
_parse_response_object: function(iapi_module, response, error) {
var status_finder = iapi_module.find_status;
var message_finder = iapi_module.find_messages;
var data_getter = iapi_module.get_data;
var meta_getter = iapi_module.get_meta;
var data = null,
meta = null,
status = false,
messages = null;
try {
data = data_getter(response);
if (_.isUndefined(data)) {
data = null;
}
} catch (e) {
if (window.console) {
window.console.log("Failed to extract the data from the response: ", response, e);
}
}
try {
meta = meta_getter(response);
if (_.isUndefined(meta)) {
meta = null;
}
} catch (e) {
if (window.console) {
window.console.log("Failed to extract the metadata from the response: ", response, e);
}
}
messages = message_finder(response);
status = status_finder(response);
// We can't depend on the first message being an error.
var errors = messages.filter(function(m) {
return m.level === "error";
});
if (errors && errors.length) {
error = errors[0].content;
} else if (!status) {
error = LOCALE.maketext("No specific error was returned with the failed API call.");
}
var warnings = response.warnings && response.warnings.length ? response.warnings : null;
// We include this here because the response from this function
// should be agnostic as to the specific API version that we called.
// If we don’t reduce the batch data now, then the caller will see
// “data” as a list of structures that are specific to the API
// version being called. Since the parsing logic is (appropriately)
// housed in the IAPI module, we want to use that here.
var is_batch = iapi_module.is_batch_response && iapi_module.is_batch_response(response);
if (is_batch && Array.isArray(data)) {
data = data.map( function(d) {
return base._parse_response_object(iapi_module, d);
} );
}
return {
parsedResponse: {
is_batch: is_batch, // a convenience
status: status,
raw: response,
data: data,
meta: meta,
error: error,
messages: messages,
warnings: warnings,
messagesAreHtml: iapi_module.HTML_ESCAPES_MESSAGES,
}
};
}
};
return base;
});
/*
# cjt/util/test.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
// TODO: Add tests for these
/**
*
* @module cjt/util/test
* @example
require(["cjt/util/test"], function(TEST){
var foo = {
a : 100,
b : {
c : {
e: 105
},
d : "ninja"
}
};
if (TEST.objectHasPath(foo, "a")) {
// Should succeed.
}
if (TEST.objectHasPath(foo, "b.c.e")) {
// Should succeed.
}
if (TEST.objectHasPath(foo, "a.b.c.e")) {
// Should fail.
}
});
*/
define('cjt/util/test',["lodash"], function(_) {
/**
* Validate that the root object contains the structure defined by the path.
*
* @public
* @static
* @method objectHasPath
* @param {Object} root Object to check for the given structure.
* @param {String} path String with all the properties required separated by "."s
* @return {Boolean} true if the structure exists, false otherwise.
*/
var objectHasPath = function(root, path) {
var parts = path.split(".");
var context = root;
if (!context) {
return false;
}
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
if (!(part in context)) {
return false;
}
context = context[part];
}
return true;
};
/**
* Validate that the root object contains the structure and the leaf is a function.
*
* @public
* @static
* @method objectHasFunction
* @param {Object} root Object to check for the given structure.
* @param {String} path String with all the properties required separated by "."s
* @return {Boolean} true if the structure exists and leaf is a function, false otherwise.
*/
var objectHasFunction = function(root, path) {
var parts = path.split(".");
var context = root;
if (!context) {
return false;
}
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
if (!(part in context)) {
return false;
}
context = context[part];
}
return _.isFunction(context);
};
/**
* Test if the object is a promise. This is an approximate
* process since promise do not have a instance type. We
* test for a then() method and then assume its a PROMISE A at
* least.
* @public
* @static
* @method isPromise
* @param {Object} obj Object to test.
* @return {Boolean} true if this looks like a promise, false otherwise.
*/
var isPromise = function(obj) {
return obj && obj.then && _.isFunction(obj.then);
};
/**
* Test if the object is a $q promise. This is an approximate
* process since promise do not have a instance type. We
* test for a then() and a finally() method and then assume its
* a $q from angular.
* @public
* @static
* @method isQPromise
* @param {Object} obj Object to test
* @return {Boolean} true if this looks like a promise, false otherwise.
*/
var isQPromise = function(obj) {
return isPromise(obj) && obj["finally"] && _.isFunction(obj["finally"]);
};
/**
* Provides various static utility testing functions.
*
* @class test
* @static
*/
// Publish the component
var test = {
objectHasPath: objectHasPath,
objectHasFunction: objectHasFunction,
isPromise: isPromise,
isQPromise: isQPromise
};
return test;
});
/*
# cjt/util/parse.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
// TODO: Add tests for these
/**
*
* @module cjt/util/parse
* @example
*
*/
define('cjt/util/parse',["lodash"], function(_) {
"use strict";
var booleanMap = {
"no": false,
"false": false,
"yes": true,
"true": true,
"1": true,
"0": false
};
function _parseBoolean( string ) {
if (_.isUndefined(string) || _.isNull(string)) {
return false;
}
string = (string + "").toLowerCase();
return ( string in booleanMap && booleanMap.hasOwnProperty(string)) ? booleanMap[ string ] : !!string;
}
/**
* Utility module for parsing various string representation into native types.
*
* @static
* @public
* @class parse
*/
var parse = {
/**
* Parse a boolean using the lookup system.
* @param {String} string Input string to evaluate.
* @return {Boolean} true or false.
*/
parseBoolean: _parseBoolean,
/**
* Parse a perl generated boolean.
* @param {String} string Input string to evaluate.
* @return {Boolean} true or false.
*/
parsePerlBoolean: function( string ) {
if (_.isUndefined(string) || _.isNull(string)) {
return false;
}
if (string === "") {
return false;
}
return _parseBoolean(string);
},
/**
* Parse a string into a number
* @param {String} string Input string to evaluate
* @param {Number} defaultValue Default value to use if the string is undefined, null, empty or NaN.
* @return {Number}
*/
parseNumber: function(string, defaultValue) {
if (_.isUndefined(string) || _.isNull(string) || string === "") {
return defaultValue;
}
var number = Number(string);
if (isNaN(number)) {
return defaultValue;
}
return number;
},
/**
* Parse a string into a integer
* @param {String} string Input string to evaluate
* @param {Number} defaultValue Default value to use if the string is undefined, null, empty or NaN.
* @param {Number} [base] Optional base for the parsing. Defaults to 10.
* @return {Number}
*/
parseInteger: function(string, defaultValue, base) {
if (!base) {
base = 10;
}
if (_.isUndefined(string) || _.isNull(string) || string === "") {
return defaultValue;
}
var number = parseInt(string, 10);
if (isNaN(number)) {
return defaultValue;
}
return number;
}
};
return parse;
});
/*
# api/io/uapi.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* eslint-env amd */
/* eslint camelcase: "off" */
/* --------------------------*/
// TODO: Add tests for these
/**
* Contain the IAPI Driver implementation used to process cpanel uapi
* request/response messages.
*
* @module cjt2/io/uapi
* @example
*
* require([
* "cjt/io/api",
* "cjt/io/uapi-request",
* "cjt/io/uapi", // IMPORTANT: Load the driver so its ready
* ], function(API, APIREQUEST) {
* return {
* getUsers: function {
* var apiCall = new APIREQUEST.Class();
* apiCall.initialize(UserManager, "list_users");
* return API.promise(apiCall.getRunArguments());
* }
* };
* });
*/
define('cjt/io/uapi',[
"lodash",
"cjt/io/base",
"cjt/util/test",
"cjt/util/parse",
"cjt/util/query"
], function(_, BASE, TEST, PARSE, QUERY) {
"use strict";
// ------------------------------
// Module
// ------------------------------
var MODULE_NAME = "cjt/io/uapi"; // requirejs(["cjt/io/uapi"], function(api) {}); -> cjt/io/uapi.js || cjt/io/uapi.debug.js
var MODULE_DESC = "Contains the unique bits for integration with UAPI calls.";
var MODULE_VERSION = "2.0";
// ------------------------------
// Shortcuts
// ------------------------------
// Since UAPI doesn’t return the information on which API call
// we called, we need to iterate through the response data and supply that.
// Compare to whm-v1.js (cf. is_batch_response() in that module).
function _expand_response_with_module_and_func(response, args_obj) {
// This way we don’t alter anything that was passed in.
var resp = _.assign({}, response);
if ( args_obj.batch ) {
resp.module = "Batch";
// Possibly offer a "loose" mode in the API at some point?
// That will prompt more logic to be created here.
resp.func = "strict";
if (Array.isArray(resp.data)) {
// Unlike WHM API v1, UAPI batching allows nested batches.
// It could be useful if we implement a “loose” batch method.
resp.data = resp.data.map( function( di, idx ) {
return _expand_response_with_module_and_func( di, args_obj.batch[idx] );
} );
}
} else {
resp.module = args_obj.module;
resp.func = args_obj.func;
}
return resp;
}
function _get_module(args_obj) {
return ( args_obj.batch ? "Batch" : args_obj.module );
}
function _get_func(args_obj) {
return ( args_obj.batch ? "strict" : args_obj.func );
}
/**
* IAPIDriver for uapi
*
* @exports module:cjt/io/uapi:UapiDriver
*/
var uapi = {
MODULE_NAME: MODULE_NAME,
MODULE_DESC: MODULE_DESC,
MODULE_VERSION: MODULE_VERSION,
/**
* Parse a YUI asyncRequest response object to extract
* the interesting parts of a cPanel UAPI call response.
*
* @static
* @param {object} response The asyncRequest response object
* @return {object} See api_base._parse_response for the format of this object.
*/
parse_response: function(response, type, args_obj) {
// BASE._parse_response does this for us, but we can’t
// depend on that since we have to twiddle with the response
// to expand any batches.
response = JSON.parse(response);
response = _expand_response_with_module_and_func(response, args_obj);
return BASE._parse_response(uapi, response);
},
/**
* Determine if the response is a batch
*
* @static
* @param {Object} response
* @return {Boolean} true if this is a batch, false otherwise
* @see module:cjt/io/whm-v1
*/
is_batch_response: function(response) {
// We might include other batch functions in here in the future.
return ( (response.module === "Batch") && (response.func === "strict") );
},
/**
* Return a list of messages from a cPanel UAPI response, normalized as a
* list of [ { level:"info|warn|error", content:"..." }, ... ]
*
* @static
* @param {object} response The parsed API JSON response
* @return {array} The messages that the API call returned
*/
find_messages: function(response) {
if (!response ) {
return [{
level: "error",
content: BASE._unknown_error_msg()
}];
}
if ("errors" in response ) {
var err = response.errors;
if ( err ) {
return [{
level: "error",
content: err.length ? _.escape(err.join("\n")) : BASE._unknown_error_msg()
}];
}
}
if ("messages" in response) {
var messages = response.messages;
if ( messages ) {
return [{
level: "msg",
content: messages.length ? _.escape(messages.join("\n")) : BASE._unknown_error_msg()
}];
}
}
return [];
},
/**
* Indicates whether this module’s find_messages() function
* HTML-escapes.
*/
HTML_ESCAPES_MESSAGES: true,
/**
* Return what a cPanel UAPI call says about whether it succeeded or not
*
* @static
* @param {object} response The parsed API JSON response
* @return {boolean} Whether the API call says it succeeded
*/
find_status: function(response) {
try {
var status = false;
if (response) {
if (typeof (response.status) !== "undefined") {
status = PARSE.parsePerlBoolean(response.status);
} else {
if (window.console) {
window.console.log("The response does not conform to UAPI standards: A status field is required.");
}
}
}
return status;
} catch (e) {
return false;
}
},
/**
* Return normalized data from a UAPI call
*
* @static
* @param {object} response The parsed API JSON response
* @return {array} The data that the API returned
*/
get_data: function(response) {
return response.data;
},
/**
* Return normalized data from a cPanel API 2 call
*
* @static
* @param {object} response The parsed API JSON response
* @return {array} The data that the API returned
*/
get_meta: function(response) {
var meta = {
paginate: {
is_paged: false,
total_records: 0,
current_record: 0,
total_pages: 0,
current_page: 0,
page_size: 0
},
filter: {
is_filtered: false,
records_before_filter: NaN,
records_filtered: NaN
}
};
if (TEST.objectHasPath(response, "metadata.paginate")) {
var paginate = meta.paginate;
paginate.is_paged = true;
paginate.total_records = response.metadata.paginate.total_results || paginate.total_records || 0;
paginate.current_record = response.metadata.paginate.start_result || paginate.current_record || 0;
paginate.total_pages = response.metadata.paginate.total_pages || paginate.total_pages || 0;
paginate.current_page = response.metadata.paginate.current_page || paginate.current_page || 0;
paginate.page_size = response.metadata.paginate.results_per_page || paginate.page_size || 0;
}
if (TEST.objectHasPath(response, "metadata.filter")) {
meta.filter.is_filtered = true;
meta.filter.records_before_filter = response.metadata.records_before_filter || 0;
}
// Copy any custom meta data properties.
if (TEST.objectHasPath(response, "metadata")) {
for (var key in response.metadata) {
if (response.metadata.hasOwnProperty(key) &&
(key !== "filter" && key !== "paginate")) {
meta[key] = response.metadata[key];
}
}
}
return meta;
},
_assemble_batch: function(batch_list) {
var commands = batch_list.map( function(b) {
if (b.args) {
b = Object.create(b);
b.args = QUERY.expand_arrays_for_cpanel_api(b.args);
}
return JSON.stringify([
b.module,
b.func,
uapi.build_query(b),
]);
} );
return {
command: commands
};
},
/**
* Build the call structure from the arguments and data.
*
* @static
* @param {Object} args_obj Arguments passed to the call.
* @return {Object} Object representation of the call arguments
*/
build_query: function(args_obj) {
if (args_obj.batch) {
return this._assemble_batch(args_obj.batch);
}
// Utility variables, used in specific contexts below.
var s, cur_sort, f, cur_filter;
var api_prefix = "api.";
var api_call = {};
if (args_obj.args) {
_.extend(api_call, args_obj.args);
}
if (args_obj.meta) {
if (args_obj.meta.sort) {
var sort_count = args_obj.meta.sort.length;
if (sort_count === 1) {
cur_sort = args_obj.meta.sort[0];
if (cur_sort instanceof Array) {
api_call[api_prefix + "sort_method"] = cur_sort[1];
cur_sort = cur_sort[0];
}
if (cur_sort.charAt(0) === "!") {
api_call[api_prefix + "sort_reverse"] = 1;
cur_sort = cur_sort.substr(1);
}
api_call[api_prefix + "sort_column"] = cur_sort;
} else {
for (s = 0; s < sort_count; s++) {
cur_sort = args_obj.meta.sort[s];
if (cur_sort instanceof Array) {
api_call[api_prefix + "sort_method_" + s] = cur_sort[1];
cur_sort = cur_sort[0];
}
if (cur_sort.charAt(0) === "!") {
api_call[api_prefix + "sort_reverse_" + s] = 1;
cur_sort = cur_sort.substr(1);
}
api_call[api_prefix + "sort_column_" + s] = cur_sort;
}
}
}
if (args_obj.meta.filter) {
var filter_count = args_obj.meta.filter.length;
if (filter_count === 1) {
cur_filter = args_obj.meta.filter[0];
api_call[api_prefix + "filter_column"] = cur_filter[0];
api_call[api_prefix + "filter_type"] = cur_filter[1];
api_call[api_prefix + "filter_term"] = cur_filter[2];
} else {
for (f = 0; f < filter_count; f++) {
cur_filter = args_obj.meta.filter[f];
api_call[api_prefix + "filter_column_" + f] = cur_filter[0];
api_call[api_prefix + "filter_type_" + f] = cur_filter[1];
api_call[api_prefix + "filter_term_" + f] = cur_filter[2];
}
}
}
if (args_obj.meta.paginate) {
if ("start" in args_obj.meta.paginate) {
api_call[api_prefix + "paginate_start"] = args_obj.meta.paginate.start;
}
if ("size" in args_obj.meta.paginate) {
api_call[api_prefix + "paginate_size"] = args_obj.meta.paginate.size;
}
}
delete args_obj.meta;
}
if (args_obj.analytics) {
api_call[api_prefix + "analytics"] = args_obj.analytics.serialize();
}
return api_call;
},
/**
* Assemble the url for the request.
*
* @static
* @param {String} token cPanel Security Token
* @param {Object} args_obj Arguments passed to the call.
* @return {String} Url prefix for the call
*/
get_url: function(token, args_obj ) {
return token + [
"",
"execute",
_get_module(args_obj),
_get_func(args_obj),
].map(encodeURIComponent).join("/");
}
};
return uapi;
});
/*
# cjt/util/httpStatus.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define('cjt/util/httpStatus',[
"cjt/util/locale"
],
function(LOCALE) {
return {
/**
* Convert the status code into a human readable string.
*
* @static
* @method convertHttpStatusToReadable
* @param {Numer} status String with html characters that need encoding
* @return {String} Human readable string for the status.
*/
convertHttpStatusToReadable: function(status) {
switch (status) {
case 100:
return LOCALE.maketext("Continue");
case 101:
return LOCALE.maketext("Switching Protocols");
case 200:
return LOCALE.maketext("OK");
case 201:
return LOCALE.maketext("Created");
case 202:
return LOCALE.maketext("Accepted");
case 203:
return LOCALE.maketext("Non-Authoritative Information");
case 204:
return LOCALE.maketext("No Content");
case 205:
return LOCALE.maketext("Reset Content");
case 206:
return LOCALE.maketext("Partial Content");
case 300:
return LOCALE.maketext("Multiple Choices");
case 301:
return LOCALE.maketext("Moved Permanently");
case 302:
return LOCALE.maketext("Found");
case 303:
return LOCALE.maketext("See Other");
case 304:
return LOCALE.maketext("Not Modified");
case 305:
return LOCALE.maketext("Use Proxy");
case 307:
return LOCALE.maketext("Temporary Redirect");
case 400:
return LOCALE.maketext("Bad Request");
case 401:
return LOCALE.maketext("Unauthorized");
case 402:
return LOCALE.maketext("Payment Required");
case 403:
return LOCALE.maketext("Forbidden");
case 404:
return LOCALE.maketext("Not Found");
case 405:
return LOCALE.maketext("Method Not Allowed");
case 406:
return LOCALE.maketext("Not Acceptable");
case 407:
return LOCALE.maketext("Proxy Authentication Required");
case 408:
return LOCALE.maketext("Request Timeout");
case 409:
return LOCALE.maketext("Conflict");
case 410:
return LOCALE.maketext("Gone");
case 411:
return LOCALE.maketext("Length Required");
case 412:
return LOCALE.maketext("Precondition Failed");
case 413:
return LOCALE.maketext("Request Entity Too Large");
case 414:
return LOCALE.maketext("Request-URI Too Long");
case 415:
return LOCALE.maketext("Unsupported Media Type");
case 416:
return LOCALE.maketext("Requested Range Not Satisfiable");
case 417:
return LOCALE.maketext("Expectation Failed");
case 500:
return LOCALE.maketext("Internal Server Error");
case 501:
return LOCALE.maketext("Not Implemented");
case 502:
return LOCALE.maketext("Bad Gateway");
case 503:
return LOCALE.maketext("Service Unavailable");
case 504:
return LOCALE.maketext("Gateway Timeout");
case 505:
return LOCALE.maketext("HTTP Version Not Supported");
default:
return LOCALE.maketext("Unknown Error");
}
}
};
}
);
/*
# cjt/services/APIService.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
// ----------------------------------------------------------------------
// HEY YOU!! Looking for quick-and-simple?
//
// var promise = APIService.promise( apiCall );
//
// ...where apiCall is a “request” object, in the mold of uapi-request.js.
// ----------------------------------------------------------------------
/**
* This module generates an angular.js service that can be used as a
* subclass for your custom services.
*
* @module cjt/services/APIService
*/
define('cjt/services/APIService',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/io/api",
"cjt/util/httpStatus"
],
function(angular, CJT, LOCALE, API, HTTP_STATUS) {
"use strict";
var module = angular.module("cjt2.services.api", []);
function reduceResponse(response) {
var resp = response.parsedResponse;
if (resp && resp.is_batch) {
for (var i = 0; i < resp.data.length; i++) {
resp.data[i] = reduceResponse( resp.data[i] );
}
}
return resp;
}
return module.factory("APIService", ["$q", function($q) {
/**
* Test if the argument is defined and is a function.
*
* @private
* @method _isFunc
* @param {Any} func
* @return {Boolean} true if defined and is a function, false otherwise.
*/
function _isFunc(func) {
return func && angular.isFunction(func);
}
/**
* This is an Angular wrapper for jquery-based XHR request promise.
*
* @private
* @construtor
* @param {RunArguments} apiCall Contains a valid API request object.
* @param {Object} [handlers] Optional. Contains any overridden handlers. See defaultHandlers below for candidate names.
* @param {Deferred} [deferred] Optional. Deferred passed from outer context. Created if not passed.
*/
function AngularAPICall(apiCall, handlers, deferred) {
this.handlers = handlers;
this.deferred = deferred = deferred || $q.defer();
this.jqXHR = API.promise(apiCall.getRunArguments())
.done(function(response) {
handlers.done(response, deferred);
})
.fail(function(xhr, textStatus) {
if (textStatus === "abort") {
handlers.abort(xhr, deferred);
} else {
handlers.fail(xhr, deferred);
}
});
// Since API calls from JS are just HTTP underneath, we can
// expose a means of canceling them. Rather than being called
// something like .cancel(), this has a “namespaced” name
// in order to avoid unintended interactions with any potential
// changes to the underlying deferred/promise stuff.
deferred.promise.cancelCpCall = this.jqXHR.abort.bind(this.jqXHR);
}
/**
* Constructor for an APIService. Sets up the instance's default handler methods.
*
* @class
* @exports module:cjt/io/APIService:APIService
* @param {Object} instanceDefaultHandlers If you would like to override any of the default handlers
* for the instance, pass them here. Otherwise, the preset
* defaults will be used.
*/
function APIService(instanceDefaultHandlers) {
this.defaultHandlers = angular.extend({}, this.presetDefaultHandlers, instanceDefaultHandlers || {});
}
APIService.prototype = {
/**
* Wrap an api call with application standard done and fail code. The caller can override behavior
* via the overrides property which is an object containing the specific parts to override. The
* overrides argument will only pertain to this single API instance. Overrides in the instance
* defaults are next in the hierarchy, followed by the preset defaults for the base API service.
*
* @method deferred
* @instance
* @param {RunArguments} apiCall An api request helper object containing arguments, filters, etc.
* @param {Object} overrides An object of overrides. See getCallHandlers documentation.
* @param {Function} overrides.done Replaces the default jqXHR done handling.
* @param {Function} overrides.fail Replaces the default jqXHR fail handling.
* @param {Function} overrides.apiSuccess Replaces standard api success handling.
* Called when not overridding done.
* @param {Function} overrides.apiFailure Replaces standard api failure handling.
* Called when not overridding done.
* @param {Function} overrides.transformApiSuccess Transforms the response on success. If not provided,
* the default behavior is to return the whole response.
* Called when not overriding apiSuccess.
* @param {Function} overrides.transformApiFailure Transforms the response on failure. If not provided,
* default behavior is to return the whole error.
* Called when not overriding apiFailure.
* @param {Deferred} [deferred] Optional deferred created with $q.defer(). If not passed one will be created internally.
* @return {Deferred} Deferred wrapping the api call.
*/
deferred: function(apiCall, overrides, deferred) {
var handlers = {};
if (overrides) {
// Iterate over the defaultHandlers and see if there are overrides with the same key
angular.forEach(this.defaultHandlers, function(defaultHandler, handlerName) {
if (_isFunc(overrides[handlerName])) {
// If a context is provided, bind the handler to that context
handlers[handlerName] = (angular.isObject(overrides.context) || angular.isFunction(overrides.context)) ?
overrides[handlerName].bind(overrides.context) : overrides[handlerName];
} else {
handlers[handlerName] = defaultHandler;
}
}, this);
} else {
handlers = this.defaultHandlers;
}
return this.sendRequest(apiCall, handlers, deferred);
},
/**
* Generates a new Angular wrapper instance for the API call. This is a separate method
* to present an easy way to mock this step for testing.
*
* @method sendRequest
* @instance
* @param {RunArguments} apiCall See deferred method documentation.
* @param {Object} handlers See deferred method documentation.
* @param {Deferred} deferred See deferred method documentation.
* @return {Deferred} A $q wrapped jqXHR promise.
*/
sendRequest: function(apiCall, handlers, deferred) {
return new AngularAPICall(apiCall, handlers, deferred).deferred;
},
/**
* Since this class is meant to be sub-classed per service, the defaultHandlers are kept
* here so that each service can conveniently overwrite them in one place.
*
* The handlers all run within the context of the handlers object by default, so if you
* override them and need another context, make sure to use Function.bind or use some
* other mechanism to keep access to your scope.
*/
presetDefaultHandlers: {
done: function(response, deferred) {
var toCaller = reduceResponse(response);
if (toCaller && toCaller.status) {
this.apiSuccess(toCaller, deferred);
} else {
this.apiFailure(toCaller, deferred);
}
},
fail: function(xhr, deferred) {
deferred.reject(_requestFailureText(xhr));
},
abort: function(xhr, deferred) {
// Intentionally a no-op. Override this if you want to reject the promise.
},
apiSuccess: function(response, deferred) {
deferred.resolve(this.transformAPISuccess(response));
},
apiFailure: function(response, deferred) {
deferred.reject(this.transformAPIFailure(response));
},
transformAPISuccess: function(response) {
return response;
},
transformAPIFailure: function(response) {
return response.error;
}
}
};
/**
* Generates the error text for when an API request fails.
*
* TODO: This should really only be called when the API doesn’t
* return any useful information other than the HTTP status codes.
* Currently it disregards useful information in the API response,
* e.g., the “reason” given in the JSON response from WHM API v1.
*
* @method _requestFailureText
* @private
* @param {Number|String} status A relevant status code.
* @return {String} The text to be presented to the user.
*/
function _requestFailureText(xhr) {
var status = xhr.status;
var message = LOCALE.maketext("The API request failed with the following error: [_1] - [_2].", status, HTTP_STATUS.convertHttpStatusToReadable(status));
if (status === 401 || status === 403) {
message += " " + LOCALE.maketext("Your session may have expired or you logged out of the system. [output,url,_1,Login] again to continue.", CJT.getLoginPath());
}
// These messages come from cpsrvd itself, not from the the API.
// (API messages don’t produce HTTP-level errors.)
try {
var parsed = JSON.parse(xhr.responseText);
if (parsed.error) {
message += ": " + parsed.error;
}
if (parsed.statusmsg) {
message += ": " + parsed.statusmsg;
}
} catch (e) {
if (xhr.responseText) {
message += ": " + xhr.responseText.substr(0, 1024);
// not json so we show the first 1024
// chars of the message
}
}
return message;
}
APIService.AngularAPICall = AngularAPICall;
var keepFailureObject = { transformAPIFailure: Object };
/**
* Starts an async request with the given request
*
* @static
* @param {RunArguments} apiCall
* @return {Promise}
*/
APIService.promise = function promise(apiCall) {
// The use of “this” as the constructor allows
// this static method to be assigned to subclass constructors,
// and all will work as it should.
return (new this(keepFailureObject)).deferred(apiCall).promise;
};
return APIService;
}]);
}
);
/*
# cjt/services/cpanel/nvDataService.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Provides nvDataService for cpanel. Used to get and set
* name value pairs of personalization data for a user.
*
* @module cjt/services/cpanel/nvDataService
* @ngmodule cjt2.services.cpanel.nvDataService
*/
define('cjt/services/cpanel/nvDataService',[
// Libraries
"angular",
// CJT
"cjt/io/api",
"cjt/io/uapi-request",
"cjt/services/nvDataServiceFactory",
"cjt/io/uapi",
// Angular components
"cjt/services/APIService"
],
function(angular, API, APIREQUEST, NVDATASERVICEFACTORY) {
"use strict";
var module = angular.module("cjt2.services.cpanel.nvdata", ["cjt2.services.api"]);
module.factory("nvDataService", [ "APIService", function(APIService) {
return NVDATASERVICEFACTORY(APIREQUEST, APIService);
}]);
});
/*
* cjt/config/cpanel/configProvider.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* DEVELOPERS NOTES:
* This is a common configuration provider for most pages in cPanel.
*/
/* global define: false */
define('cjt/config/cpanel/configProvider',[
"angular",
"cjt/core",
"cjt/config/componentConfigurationLoader",
"angular-growl",
"cjt/config/componentConfiguration",
"cjt/services/cpanel/nvDataService"
],
function(
angular,
CJT,
loadComponentConfiguration
) {
"use strict";
var _pendingForcePasswordChange;
var _componentConfigurationProvider;
var config = angular.module("cjt2.config.cpanel.configProvider", [
"angular-growl",
"cjt2.config.componentConfiguration",
"cjt2.services.cpanel.nvdata",
]);
config.config([
"$locationProvider",
"$compileProvider",
"growlProvider",
"componentConfigurationProvider",
function(
$locationProvider,
$compileProvider,
growlProvider,
componentConfigurationProvider
) {
if (CJT.config.debug) {
// disable debug data when debugging
$compileProvider.debugInfoEnabled(true);
} else {
// disable debug data for production
$compileProvider.debugInfoEnabled(false);
}
// Setup the growl defaults if the growlProvider is loaded
growlProvider.globalTimeToLive({ success: 5000, warning: -1, info: -1, error: -1 });
growlProvider.globalDisableCountDown(true);
_pendingForcePasswordChange = PAGE.skipNotificationsCheck || false;
_componentConfigurationProvider = componentConfigurationProvider;
}
]);
config.run([
"nvDataService",
"$window",
"$log",
function(
nvDataService,
$window,
$log
) {
if (_pendingForcePasswordChange) return;
if (_componentConfigurationProvider) {
loadComponentConfiguration(_componentConfigurationProvider, nvDataService, $window, $log);
}
}
]);
});
/*
* cjt/config/webmail/configProvider.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* DEVELOPERS NOTES:
* This is a common configuration provider for most pages in cPanel.
*/
/* global define: false */
define('cjt/config/webmail/configProvider',[
"angular",
"cjt/core",
"cjt/config/componentConfigurationLoader",
"angular-growl",
"cjt/config/componentConfiguration",
"cjt/services/cpanel/nvDataService"
],
function(
angular,
CJT,
loadComponentConfiguration
) {
"use strict";
var _componentConfigurationProvider;
var config = angular.module("cjt2.config.webmail.configProvider", [
"angular-growl",
"cjt2.config.componentConfiguration",
"cjt2.services.cpanel.nvdata",
]);
var configureApplication = function(
$compileProvider,
growlProvider,
componentConfigurationProvider
) {
if (CJT.config.debug) {
// disable debug data when debugging
$compileProvider.debugInfoEnabled(true);
} else {
// disable debug data for production
$compileProvider.debugInfoEnabled(false);
}
// Setup the growl defaults if the growlProvider is loaded
growlProvider.globalTimeToLive({ success: 5000, warning: -1, info: -1, error: -1 });
growlProvider.globalDisableCountDown(true);
_componentConfigurationProvider = componentConfigurationProvider;
};
config.config([
"$compileProvider",
"growlProvider",
"componentConfigurationProvider",
configureApplication
]);
var runApplication = function(
nvDataService,
$window,
$log
) {
if (_componentConfigurationProvider) {
loadComponentConfiguration(_componentConfigurationProvider, nvDataService, $window, $log);
}
};
config.run([
"nvDataService",
"$window",
"$log",
runApplication
]);
return {
configureApplication: configureApplication,
runApplication: runApplication,
getComponentConfigurationProvider: function() {
return _componentConfigurationProvider;
}
};
});
/**
* cjt/decorators/$httpDecorator.js Copyright(c) 2016 cPanel, Inc.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/decorators/$httpDecorator',[
"angular",
],
function(angular) {
// Retrieve the module
var module = angular.module("cjt2.decorators.$http", []);
module.run(["$http", function($http) {
$http.postAsForm = function(url, data, config) {
if (typeof url !== "string") {
throw new TypeError("Developer Error: $http.postAsForm requires a \"url\" argument.");
}
if (!angular.isObject(config)) {
config = {};
}
if (data) {
if ("data" in config) {
throw new ReferenceError("Developer Error: $http.postAsForm does not accept a \"config.data\" key when there is a \"data\" argument.");
}
config.data = data;
}
angular.merge(config, {
method: "POST",
url: url,
transformRequest: function(args) {
var uriEncoded = [];
angular.forEach(args, function(val, key) {
uriEncoded.push(encodeURIComponent(key) + "=" + encodeURIComponent(val));
});
return uriEncoded.join("&");
},
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" }
});
return $http(config);
};
}]);
}
);
/*
# cjt/services/APIFailures.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* A service that functions as a custom event for uncaught API failures.
This, in tandem with APICatcher and modules like growlAPIReporter,
can ensure that no API failure goes unreported (or errantly reported).
See APICatcher for more information.
*/
/* global define: false */
define('cjt/services/APIFailures',[
"angular",
],
function(angular) {
var module = angular.module("cjt2.services.apifailures", []);
module.factory( "APIFailures", function() {
var listeners = [];
return {
register: listeners.push.bind(listeners),
emit: function(evt) {
listeners.forEach( function(todo) {
todo(evt);
} );
},
};
} );
}
);
/*
# cjt/services/alertService.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/services/alertService',[
// Libraries
"angular",
"cjt/core"
],
function(angular, CJT) {
"use strict";
var module = angular.module("cjt2.services.alert", []);
/**
* The alert service provides an API for handling a collection of alerts. The alertList directive
* uses the data from this service to render a list of alerts.
*/
module.factory("alertService", function() {
var alerts = {
__DEFAULT: []
},
alertCounter = 0;
/**
* Retrieves the alert list for a particular alert group
*
* @method getAlerts (aliased as bind in the public interface)
* @param {String|Number} group A group name that should correspond to a key in alerts
* @return {Array} alerts
*/
function getAlerts(group) {
if (group) {
group = group.toString();
if (alerts[group]) {
return alerts[group];
} else {
alerts[group] = [];
return (alerts[group]);
}
} else {
return alerts.__DEFAULT;
}
}
/**
* Validate the type passed in to the alerts before adding it.
*
* @method _validateType
* @private
* @param {String} type Type requested from the outside.
* @return {Boolean} true if a recognized type, false otherwise.
*/
function _validateType(type) {
switch (type) {
case "danger":
case "warning":
case "success":
case "info":
return true;
default:
return false;
}
}
function _getAlertObject(alert) {
if (typeof alert === "string") {
alert = {
message: alert
};
} else if (typeof alert === "object" && typeof alert.message !== "string") {
throw new TypeError("alertService: alert.message is expected to be a string");
}
return alert;
}
function success(alert) {
var successDefaults = {
autoClose: 10 * 1000,
type: "success"
};
alert = _getAlertObject(alert);
add(angular.extend(successDefaults, alert));
}
/**
* Add an alert to the alerts service
*
* @method add
* @param {String|Object} alert A warning string or an alert object with the following properties:
* @param {String} message The text to be displayed in the alert
* @param {String} type The type of alert to display: success, info, warning, danger, defaults to warning
* @param {String} id Static id for the alert
* @param {String|Number} group A group name that can refer to related alerts
* @param {Boolean} replace Bypass the default behavior of replacing existing alerts with false
* @param {Number} [autoClose] Number of milliseconds until auto-closes the alert.
*/
function add(alert) {
alert = _getAlertObject(alert);
// Determine proper ID to use
var uniqueID;
var idIsGenerated;
if (typeof alert.id === "string") {
// Use the id provided
uniqueID = alert.id;
} else {
// Generate an id
uniqueID = "alert" + alertCounter++;
idIsGenerated = true;
}
// Check if a group is specified, a valid key, and exists
var group = "__DEFAULT";
if (typeof alert.group === "string" || typeof alert.group === "number") {
group = alert.group.toString();
if (!alerts[group]) {
alerts[group] = [];
}
}
// Stack or replace alerts
if (!alert.hasOwnProperty("replace") || alert.replace) {
clear(void 0, group);
} else if (!idIsGenerated) {
// If we're not replacing the existing alerts, we might want to append a counter to the ID.
// If the ID is generated, then we already have a counter so don't add another one.
// If the ID is not generated, we will add a counter unless alert.counter exists and is falsy.
alert.counter = !alert.hasOwnProperty("counter") || !!alert.counter;
if (alert.counter) {
uniqueID += alertCounter++;
}
}
alerts[group].push({
type: _validateType(alert.type) ? alert.type : "warning",
closeable: angular.isDefined(alert.closeable) ? alert.closeable : alert.type !== "danger",
message: alert.message,
list: alert.list || [],
id: uniqueID,
autoClose: CJT.isE2E() ? false : alert.autoClose,
label: alert.label
});
}
/**
* Remove an alert from the alerts service
*
* @method remove
* @param {String} index The array index of the alert to remove
* @param {String|Number} group A valid identifier that serves as a group name
*/
function remove(index, group) {
var alertGroup = getAlerts(group);
alertGroup.splice(index, 1);
}
/**
* Remove an alert by its unique id
*
* @method removeById
* @param {String} id Id of the alert
* @param {String} group Group name
* @return {Boolean} true if found and removed, false otherwise
*/
function removeById(id, group) {
var alertGroup = getAlerts(group);
for (var i = 0, l = alertGroup.length; i < l; i++) {
if (alertGroup[i].id === id) {
alertGroup.splice(i, 1);
return true;
}
}
return false;
}
/**
* Removes all alerts or all alerts of a specified type
*
* @method clear
* @param {String} type The type of alert to clear from the alert list
*/
function clear(type, group) {
var alertGroup = getAlerts(group);
if (type) {
// Loop over the array and filter out the specified type
// Start iterating from the last element
for (var i = alertGroup.length - 1; i >= 0; i--) {
if (alertGroup[i].type === type) {
alertGroup.splice(i, 1);
}
}
} else {
// Remove all alerts while maintaining the array reference so
// that any bound elements will continue to update properly
alertGroup.splice(0);
}
}
// return the factory interface
return {
getAlerts: getAlerts,
add: add,
success: success,
remove: remove,
removeById: removeById,
clear: clear
};
});
}
);
/*
# cjt/decorators/alertAPIReporter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* One possible display-layer complement to the APICatcher service. This
module will display all failures from APICatcher in the UI via alert
notifications and the browser console.
It is assumed (for now) that this is as simple as just showing the
response’s .error value; for batch responses, we probably want to be
more detailed eventually (TODO).
Ultimately, we’d ideally even create *typed* failure response objects;
these could encapsulate their own logic for generating a string (or
even raw markup??) to report failures.
You’ll need the following somewhere in the ng template where this
logic can find it:
<cp-alert-list></cp-alert-list>
See APICatcher for more information.
*/
/* global define: false */
define(
'cjt/decorators/alertAPIReporter',[
"angular",
"cjt/services/APIFailures",
"cjt/services/alertService",
],
function(angular, _) {
"use strict";
// Set up the module in the cjt2 space
var module = angular.module("cjt2.decorators.alertAPIReporter", ["cjt2.services.alert"]);
module.config(["$provide", function($provide) {
$provide.decorator("alertService", ["$delegate", "APIFailures", "$log", function(alertService, apifail, $log) {
function _reportMessages(messages) {
messages.forEach(function(message) {
$log.warn(message.content);
alertService.add( {
type: message.type,
message: message.content,
replace: false
} );
});
}
apifail.register(_reportMessages);
return alertService;
}]);
}]);
}
);
(function(root) {
define("jquery-chosen", ["jquery"], function() {
return (function() {
/* Chosen v1.5.1 | (c) 2011-2016 by Harvest | MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md */
(function(){var a,AbstractChosen,Chosen,SelectParser,b,c={}.hasOwnProperty,d=function(a,b){function d(){this.constructor=a}for(var e in b)c.call(b,e)&&(a[e]=b[e]);return d.prototype=b.prototype,a.prototype=new d,a.__super__=b.prototype,a};SelectParser=function(){function SelectParser(){this.options_index=0,this.parsed=[]}return SelectParser.prototype.add_node=function(a){return"OPTGROUP"===a.nodeName.toUpperCase()?this.add_group(a):this.add_option(a)},SelectParser.prototype.add_group=function(a){var b,c,d,e,f,g;for(b=this.parsed.length,this.parsed.push({array_index:b,group:!0,label:this.escapeExpression(a.label),title:a.title?a.title:void 0,children:0,disabled:a.disabled,classes:a.className}),f=a.childNodes,g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(this.add_option(c,b,a.disabled));return g},SelectParser.prototype.add_option=function(a,b,c){return"OPTION"===a.nodeName.toUpperCase()?(""!==a.text?(null!=b&&(this.parsed[b].children+=1),this.parsed.push({array_index:this.parsed.length,options_index:this.options_index,value:a.value,text:a.text,html:a.innerHTML,title:a.title?a.title:void 0,selected:a.selected,disabled:c===!0?c:a.disabled,group_array_index:b,group_label:null!=b?this.parsed[b].label:null,classes:a.className,style:a.style.cssText})):this.parsed.push({array_index:this.parsed.length,options_index:this.options_index,empty:!0}),this.options_index+=1):void 0},SelectParser.prototype.escapeExpression=function(a){var b,c;return null==a||a===!1?"":/[\&\<\>\"\'\`]/.test(a)?(b={"<":"<",">":">",'"':""","'":"'","`":"`"},c=/&(?!\w+;)|[\<\>\"\'\`]/g,a.replace(c,function(a){return b[a]||"&"})):a},SelectParser}(),SelectParser.select_to_array=function(a){var b,c,d,e,f;for(c=new SelectParser,f=a.childNodes,d=0,e=f.length;e>d;d++)b=f[d],c.add_node(b);return c.parsed},AbstractChosen=function(){function AbstractChosen(a,b){this.form_field=a,this.options=null!=b?b:{},AbstractChosen.browser_is_supported()&&(this.is_multiple=this.form_field.multiple,this.set_default_text(),this.set_default_values(),this.setup(),this.set_up_html(),this.register_observers(),this.on_ready())}return AbstractChosen.prototype.set_default_values=function(){var a=this;return this.click_test_action=function(b){return a.test_active_click(b)},this.activate_action=function(b){return a.activate_field(b)},this.active_field=!1,this.mouse_on_container=!1,this.results_showing=!1,this.result_highlighted=null,this.allow_single_deselect=null!=this.options.allow_single_deselect&&null!=this.form_field.options[0]&&""===this.form_field.options[0].text?this.options.allow_single_deselect:!1,this.disable_search_threshold=this.options.disable_search_threshold||0,this.disable_search=this.options.disable_search||!1,this.enable_split_word_search=null!=this.options.enable_split_word_search?this.options.enable_split_word_search:!0,this.group_search=null!=this.options.group_search?this.options.group_search:!0,this.search_contains=this.options.search_contains||!1,this.single_backstroke_delete=null!=this.options.single_backstroke_delete?this.options.single_backstroke_delete:!0,this.max_selected_options=this.options.max_selected_options||1/0,this.inherit_select_classes=this.options.inherit_select_classes||!1,this.display_selected_options=null!=this.options.display_selected_options?this.options.display_selected_options:!0,this.display_disabled_options=null!=this.options.display_disabled_options?this.options.display_disabled_options:!0,this.include_group_label_in_selected=this.options.include_group_label_in_selected||!1,this.max_shown_results=this.options.max_shown_results||Number.POSITIVE_INFINITY},AbstractChosen.prototype.set_default_text=function(){return this.form_field.getAttribute("data-placeholder")?this.default_text=this.form_field.getAttribute("data-placeholder"):this.is_multiple?this.default_text=this.options.placeholder_text_multiple||this.options.placeholder_text||AbstractChosen.default_multiple_text:this.default_text=this.options.placeholder_text_single||this.options.placeholder_text||AbstractChosen.default_single_text,this.results_none_found=this.form_field.getAttribute("data-no_results_text")||this.options.no_results_text||AbstractChosen.default_no_result_text},AbstractChosen.prototype.choice_label=function(a){return this.include_group_label_in_selected&&null!=a.group_label?"<b class='group-name'>"+a.group_label+"</b>"+a.html:a.html},AbstractChosen.prototype.mouse_enter=function(){return this.mouse_on_container=!0},AbstractChosen.prototype.mouse_leave=function(){return this.mouse_on_container=!1},AbstractChosen.prototype.input_focus=function(a){var b=this;if(this.is_multiple){if(!this.active_field)return setTimeout(function(){return b.container_mousedown()},50)}else if(!this.active_field)return this.activate_field()},AbstractChosen.prototype.input_blur=function(a){var b=this;return this.mouse_on_container?void 0:(this.active_field=!1,setTimeout(function(){return b.blur_test()},100))},AbstractChosen.prototype.results_option_build=function(a){var b,c,d,e,f,g,h;for(b="",e=0,h=this.results_data,f=0,g=h.length;g>f&&(c=h[f],d="",d=c.group?this.result_add_group(c):this.result_add_option(c),""!==d&&(e++,b+=d),(null!=a?a.first:void 0)&&(c.selected&&this.is_multiple?this.choice_build(c):c.selected&&!this.is_multiple&&this.single_set_selected_text(this.choice_label(c))),!(e>=this.max_shown_results));f++);return b},AbstractChosen.prototype.result_add_option=function(a){var b,c;return a.search_match&&this.include_option_in_results(a)?(b=[],a.disabled||a.selected&&this.is_multiple||b.push("active-result"),!a.disabled||a.selected&&this.is_multiple||b.push("disabled-result"),a.selected&&b.push("result-selected"),null!=a.group_array_index&&b.push("group-option"),""!==a.classes&&b.push(a.classes),c=document.createElement("li"),c.className=b.join(" "),c.style.cssText=a.style,c.setAttribute("data-option-array-index",a.array_index),c.innerHTML=a.search_text,a.title&&(c.title=a.title),this.outerHTML(c)):""},AbstractChosen.prototype.result_add_group=function(a){var b,c;return(a.search_match||a.group_match)&&a.active_options>0?(b=[],b.push("group-result"),a.classes&&b.push(a.classes),c=document.createElement("li"),c.className=b.join(" "),c.innerHTML=a.search_text,a.title&&(c.title=a.title),this.outerHTML(c)):""},AbstractChosen.prototype.results_update_field=function(){return this.set_default_text(),this.is_multiple||this.results_reset_cleanup(),this.result_clear_highlight(),this.results_build(),this.results_showing?this.winnow_results():void 0},AbstractChosen.prototype.reset_single_select_options=function(){var a,b,c,d,e;for(d=this.results_data,e=[],b=0,c=d.length;c>b;b++)a=d[b],a.selected?e.push(a.selected=!1):e.push(void 0);return e},AbstractChosen.prototype.results_toggle=function(){return this.results_showing?this.results_hide():this.results_show()},AbstractChosen.prototype.results_search=function(a){return this.results_showing?this.winnow_results():this.results_show()},AbstractChosen.prototype.winnow_results=function(){var a,b,c,d,e,f,g,h,i,j,k,l;for(this.no_results_clear(),d=0,f=this.get_search_text(),a=f.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),i=new RegExp(a,"i"),c=this.get_search_regex(a),l=this.results_data,j=0,k=l.length;k>j;j++)b=l[j],b.search_match=!1,e=null,this.include_option_in_results(b)&&(b.group&&(b.group_match=!1,b.active_options=0),null!=b.group_array_index&&this.results_data[b.group_array_index]&&(e=this.results_data[b.group_array_index],0===e.active_options&&e.search_match&&(d+=1),e.active_options+=1),b.search_text=b.group?b.label:b.html,(!b.group||this.group_search)&&(b.search_match=this.search_string_match(b.search_text,c),b.search_match&&!b.group&&(d+=1),b.search_match?(f.length&&(g=b.search_text.search(i),h=b.search_text.substr(0,g+f.length)+"</em>"+b.search_text.substr(g+f.length),b.search_text=h.substr(0,g)+"<em>"+h.substr(g)),null!=e&&(e.group_match=!0)):null!=b.group_array_index&&this.results_data[b.group_array_index].search_match&&(b.search_match=!0)));return this.result_clear_highlight(),1>d&&f.length?(this.update_results_content(""),this.no_results(f)):(this.update_results_content(this.results_option_build()),this.winnow_results_set_highlight())},AbstractChosen.prototype.get_search_regex=function(a){var b;return b=this.search_contains?"":"^",new RegExp(b+a,"i")},AbstractChosen.prototype.search_string_match=function(a,b){var c,d,e,f;if(b.test(a))return!0;if(this.enable_split_word_search&&(a.indexOf(" ")>=0||0===a.indexOf("["))&&(d=a.replace(/\[|\]/g,"").split(" "),d.length))for(e=0,f=d.length;f>e;e++)if(c=d[e],b.test(c))return!0},AbstractChosen.prototype.choices_count=function(){var a,b,c,d;if(null!=this.selected_option_count)return this.selected_option_count;for(this.selected_option_count=0,d=this.form_field.options,b=0,c=d.length;c>b;b++)a=d[b],a.selected&&(this.selected_option_count+=1);return this.selected_option_count},AbstractChosen.prototype.choices_click=function(a){return a.preventDefault(),this.results_showing||this.is_disabled?void 0:this.results_show()},AbstractChosen.prototype.keyup_checker=function(a){var b,c;switch(b=null!=(c=a.which)?c:a.keyCode,this.search_field_scale(),b){case 8:if(this.is_multiple&&this.backstroke_length<1&&this.choices_count()>0)return this.keydown_backstroke();if(!this.pending_backstroke)return this.result_clear_highlight(),this.results_search();break;case 13:if(a.preventDefault(),this.results_showing)return this.result_select(a);break;case 27:return this.results_showing&&this.results_hide(),!0;case 9:case 38:case 40:case 16:case 91:case 17:case 18:break;default:return this.results_search()}},AbstractChosen.prototype.clipboard_event_checker=function(a){var b=this;return setTimeout(function(){return b.results_search()},50)},AbstractChosen.prototype.container_width=function(){return null!=this.options.width?this.options.width:""+this.form_field.offsetWidth+"px"},AbstractChosen.prototype.include_option_in_results=function(a){return this.is_multiple&&!this.display_selected_options&&a.selected?!1:!this.display_disabled_options&&a.disabled?!1:a.empty?!1:!0},AbstractChosen.prototype.search_results_touchstart=function(a){return this.touch_started=!0,this.search_results_mouseover(a)},AbstractChosen.prototype.search_results_touchmove=function(a){return this.touch_started=!1,this.search_results_mouseout(a)},AbstractChosen.prototype.search_results_touchend=function(a){return this.touch_started?this.search_results_mouseup(a):void 0},AbstractChosen.prototype.outerHTML=function(a){var b;return a.outerHTML?a.outerHTML:(b=document.createElement("div"),b.appendChild(a),b.innerHTML)},AbstractChosen.browser_is_supported=function(){return/iP(od|hone)/i.test(window.navigator.userAgent)?!1:/Android/i.test(window.navigator.userAgent)&&/Mobile/i.test(window.navigator.userAgent)?!1:/IEMobile/i.test(window.navigator.userAgent)?!1:/Windows Phone/i.test(window.navigator.userAgent)?!1:/BlackBerry/i.test(window.navigator.userAgent)?!1:/BB10/i.test(window.navigator.userAgent)?!1:"Microsoft Internet Explorer"===window.navigator.appName?document.documentMode>=8:!0},AbstractChosen.default_multiple_text="Select Some Options",AbstractChosen.default_single_text="Select an Option",AbstractChosen.default_no_result_text="No results match",AbstractChosen}(),a=jQuery,a.fn.extend({chosen:function(b){return AbstractChosen.browser_is_supported()?this.each(function(c){var d,e;return d=a(this),e=d.data("chosen"),"destroy"===b?void(e instanceof Chosen&&e.destroy()):void(e instanceof Chosen||d.data("chosen",new Chosen(this,b)))}):this}}),Chosen=function(c){function Chosen(){return b=Chosen.__super__.constructor.apply(this,arguments)}return d(Chosen,c),Chosen.prototype.setup=function(){return this.form_field_jq=a(this.form_field),this.current_selectedIndex=this.form_field.selectedIndex,this.is_rtl=this.form_field_jq.hasClass("chosen-rtl")},Chosen.prototype.set_up_html=function(){var b,c;return b=["chosen-container"],b.push("chosen-container-"+(this.is_multiple?"multi":"single")),this.inherit_select_classes&&this.form_field.className&&b.push(this.form_field.className),this.is_rtl&&b.push("chosen-rtl"),c={"class":b.join(" "),style:"width: "+this.container_width()+";",title:this.form_field.title},this.form_field.id.length&&(c.id=this.form_field.id.replace(/[^\w]/g,"_")+"_chosen"),this.container=a("<div />",c),this.is_multiple?this.container.html('<ul class="chosen-choices"><li class="search-field"><input type="text" value="'+this.default_text+'" class="default" autocomplete="off" style="width:25px;" /></li></ul><div class="chosen-drop"><ul class="chosen-results"></ul></div>'):this.container.html('<a class="chosen-single chosen-default"><span>'+this.default_text+'</span><div><b></b></div></a><div class="chosen-drop"><div class="chosen-search"><input type="text" autocomplete="off" /></div><ul class="chosen-results"></ul></div>'),this.form_field_jq.hide().after(this.container),this.dropdown=this.container.find("div.chosen-drop").first(),this.search_field=this.container.find("input").first(),this.search_results=this.container.find("ul.chosen-results").first(),this.search_field_scale(),this.search_no_results=this.container.find("li.no-results").first(),this.is_multiple?(this.search_choices=this.container.find("ul.chosen-choices").first(),this.search_container=this.container.find("li.search-field").first()):(this.search_container=this.container.find("div.chosen-search").first(),this.selected_item=this.container.find(".chosen-single").first()),this.results_build(),this.set_tab_index(),this.set_label_behavior()},Chosen.prototype.on_ready=function(){return this.form_field_jq.trigger("chosen:ready",{chosen:this})},Chosen.prototype.register_observers=function(){var a=this;return this.container.bind("touchstart.chosen",function(b){return a.container_mousedown(b),b.preventDefault()}),this.container.bind("touchend.chosen",function(b){return a.container_mouseup(b),b.preventDefault()}),this.container.bind("mousedown.chosen",function(b){a.container_mousedown(b)}),this.container.bind("mouseup.chosen",function(b){a.container_mouseup(b)}),this.container.bind("mouseenter.chosen",function(b){a.mouse_enter(b)}),this.container.bind("mouseleave.chosen",function(b){a.mouse_leave(b)}),this.search_results.bind("mouseup.chosen",function(b){a.search_results_mouseup(b)}),this.search_results.bind("mouseover.chosen",function(b){a.search_results_mouseover(b)}),this.search_results.bind("mouseout.chosen",function(b){a.search_results_mouseout(b)}),this.search_results.bind("mousewheel.chosen DOMMouseScroll.chosen",function(b){a.search_results_mousewheel(b)}),this.search_results.bind("touchstart.chosen",function(b){a.search_results_touchstart(b)}),this.search_results.bind("touchmove.chosen",function(b){a.search_results_touchmove(b)}),this.search_results.bind("touchend.chosen",function(b){a.search_results_touchend(b)}),this.form_field_jq.bind("chosen:updated.chosen",function(b){a.results_update_field(b)}),this.form_field_jq.bind("chosen:activate.chosen",function(b){a.activate_field(b)}),this.form_field_jq.bind("chosen:open.chosen",function(b){a.container_mousedown(b)}),this.form_field_jq.bind("chosen:close.chosen",function(b){a.input_blur(b)}),this.search_field.bind("blur.chosen",function(b){a.input_blur(b)}),this.search_field.bind("keyup.chosen",function(b){a.keyup_checker(b)}),this.search_field.bind("keydown.chosen",function(b){a.keydown_checker(b)}),this.search_field.bind("focus.chosen",function(b){a.input_focus(b)}),this.search_field.bind("cut.chosen",function(b){a.clipboard_event_checker(b)}),this.search_field.bind("paste.chosen",function(b){a.clipboard_event_checker(b)}),this.is_multiple?this.search_choices.bind("click.chosen",function(b){a.choices_click(b)}):this.container.bind("click.chosen",function(a){a.preventDefault()})},Chosen.prototype.destroy=function(){return a(this.container[0].ownerDocument).unbind("click.chosen",this.click_test_action),this.search_field[0].tabIndex&&(this.form_field_jq[0].tabIndex=this.search_field[0].tabIndex),this.container.remove(),this.form_field_jq.removeData("chosen"),this.form_field_jq.show()},Chosen.prototype.search_field_disabled=function(){return this.is_disabled=this.form_field_jq[0].disabled,this.is_disabled?(this.container.addClass("chosen-disabled"),this.search_field[0].disabled=!0,this.is_multiple||this.selected_item.unbind("focus.chosen",this.activate_action),this.close_field()):(this.container.removeClass("chosen-disabled"),this.search_field[0].disabled=!1,this.is_multiple?void 0:this.selected_item.bind("focus.chosen",this.activate_action))},Chosen.prototype.container_mousedown=function(b){return this.is_disabled||(b&&"mousedown"===b.type&&!this.results_showing&&b.preventDefault(),null!=b&&a(b.target).hasClass("search-choice-close"))?void 0:(this.active_field?this.is_multiple||!b||a(b.target)[0]!==this.selected_item[0]&&!a(b.target).parents("a.chosen-single").length||(b.preventDefault(),this.results_toggle()):(this.is_multiple&&this.search_field.val(""),a(this.container[0].ownerDocument).bind("click.chosen",this.click_test_action),this.results_show()),this.activate_field())},Chosen.prototype.container_mouseup=function(a){return"ABBR"!==a.target.nodeName||this.is_disabled?void 0:this.results_reset(a)},Chosen.prototype.search_results_mousewheel=function(a){var b;return a.originalEvent&&(b=a.originalEvent.deltaY||-a.originalEvent.wheelDelta||a.originalEvent.detail),null!=b?(a.preventDefault(),"DOMMouseScroll"===a.type&&(b=40*b),this.search_results.scrollTop(b+this.search_results.scrollTop())):void 0},Chosen.prototype.blur_test=function(a){return!this.active_field&&this.container.hasClass("chosen-container-active")?this.close_field():void 0},Chosen.prototype.close_field=function(){return a(this.container[0].ownerDocument).unbind("click.chosen",this.click_test_action),this.active_field=!1,this.results_hide(),this.container.removeClass("chosen-container-active"),this.clear_backstroke(),this.show_search_field_default(),this.search_field_scale()},Chosen.prototype.activate_field=function(){return this.container.addClass("chosen-container-active"),this.active_field=!0,this.search_field.val(this.search_field.val()),this.search_field.focus()},Chosen.prototype.test_active_click=function(b){var c;return c=a(b.target).closest(".chosen-container"),c.length&&this.container[0]===c[0]?this.active_field=!0:this.close_field()},Chosen.prototype.results_build=function(){return this.parsing=!0,this.selected_option_count=null,this.results_data=SelectParser.select_to_array(this.form_field),this.is_multiple?this.search_choices.find("li.search-choice").remove():this.is_multiple||(this.single_set_selected_text(),this.disable_search||this.form_field.options.length<=this.disable_search_threshold?(this.search_field[0].readOnly=!0,this.container.addClass("chosen-container-single-nosearch")):(this.search_field[0].readOnly=!1,this.container.removeClass("chosen-container-single-nosearch"))),this.update_results_content(this.results_option_build({first:!0})),this.search_field_disabled(),this.show_search_field_default(),this.search_field_scale(),this.parsing=!1},Chosen.prototype.result_do_highlight=function(a){var b,c,d,e,f;if(a.length){if(this.result_clear_highlight(),this.result_highlight=a,this.result_highlight.addClass("highlighted"),d=parseInt(this.search_results.css("maxHeight"),10),f=this.search_results.scrollTop(),e=d+f,c=this.result_highlight.position().top+this.search_results.scrollTop(),b=c+this.result_highlight.outerHeight(),b>=e)return this.search_results.scrollTop(b-d>0?b-d:0);if(f>c)return this.search_results.scrollTop(c)}},Chosen.prototype.result_clear_highlight=function(){return this.result_highlight&&this.result_highlight.removeClass("highlighted"),this.result_highlight=null},Chosen.prototype.results_show=function(){return this.is_multiple&&this.max_selected_options<=this.choices_count()?(this.form_field_jq.trigger("chosen:maxselected",{chosen:this}),!1):(this.container.addClass("chosen-with-drop"),this.results_showing=!0,this.search_field.focus(),this.search_field.val(this.search_field.val()),this.winnow_results(),this.form_field_jq.trigger("chosen:showing_dropdown",{chosen:this}))},Chosen.prototype.update_results_content=function(a){return this.search_results.html(a)},Chosen.prototype.results_hide=function(){return this.results_showing&&(this.result_clear_highlight(),this.container.removeClass("chosen-with-drop"),this.form_field_jq.trigger("chosen:hiding_dropdown",{chosen:this})),this.results_showing=!1},Chosen.prototype.set_tab_index=function(a){var b;return this.form_field.tabIndex?(b=this.form_field.tabIndex,this.form_field.tabIndex=-1,this.search_field[0].tabIndex=b):void 0},Chosen.prototype.set_label_behavior=function(){var b=this;return this.form_field_label=this.form_field_jq.parents("label"),!this.form_field_label.length&&this.form_field.id.length&&(this.form_field_label=a("label[for='"+this.form_field.id+"']")),this.form_field_label.length>0?this.form_field_label.bind("click.chosen",function(a){return b.is_multiple?b.container_mousedown(a):b.activate_field()}):void 0},Chosen.prototype.show_search_field_default=function(){return this.is_multiple&&this.choices_count()<1&&!this.active_field?(this.search_field.val(this.default_text),this.search_field.addClass("default")):(this.search_field.val(""),this.search_field.removeClass("default"))},Chosen.prototype.search_results_mouseup=function(b){var c;return c=a(b.target).hasClass("active-result")?a(b.target):a(b.target).parents(".active-result").first(),c.length?(this.result_highlight=c,this.result_select(b),this.search_field.focus()):void 0},Chosen.prototype.search_results_mouseover=function(b){var c;return c=a(b.target).hasClass("active-result")?a(b.target):a(b.target).parents(".active-result").first(),c?this.result_do_highlight(c):void 0},Chosen.prototype.search_results_mouseout=function(b){return a(b.target).hasClass("active-result")?this.result_clear_highlight():void 0},Chosen.prototype.choice_build=function(b){var c,d,e=this;return c=a("<li />",{"class":"search-choice"}).html("<span>"+this.choice_label(b)+"</span>"),b.disabled?c.addClass("search-choice-disabled"):(d=a("<a />",{"class":"search-choice-close","data-option-array-index":b.array_index}),d.bind("click.chosen",function(a){return e.choice_destroy_link_click(a)}),c.append(d)),this.search_container.before(c)},Chosen.prototype.choice_destroy_link_click=function(b){return b.preventDefault(),b.stopPropagation(),this.is_disabled?void 0:this.choice_destroy(a(b.target))},Chosen.prototype.choice_destroy=function(a){return this.result_deselect(a[0].getAttribute("data-option-array-index"))?(this.show_search_field_default(),this.is_multiple&&this.choices_count()>0&&this.search_field.val().length<1&&this.results_hide(),a.parents("li").first().remove(),this.search_field_scale()):void 0},Chosen.prototype.results_reset=function(){return this.reset_single_select_options(),this.form_field.options[0].selected=!0,this.single_set_selected_text(),this.show_search_field_default(),this.results_reset_cleanup(),this.form_field_jq.trigger("change"),this.active_field?this.results_hide():void 0},Chosen.prototype.results_reset_cleanup=function(){return this.current_selectedIndex=this.form_field.selectedIndex,this.selected_item.find("abbr").remove()},Chosen.prototype.result_select=function(a){var b,c;return this.result_highlight?(b=this.result_highlight,this.result_clear_highlight(),this.is_multiple&&this.max_selected_options<=this.choices_count()?(this.form_field_jq.trigger("chosen:maxselected",{chosen:this}),!1):(this.is_multiple?b.removeClass("active-result"):this.reset_single_select_options(),b.addClass("result-selected"),c=this.results_data[b[0].getAttribute("data-option-array-index")],c.selected=!0,this.form_field.options[c.options_index].selected=!0,this.selected_option_count=null,this.is_multiple?this.choice_build(c):this.single_set_selected_text(this.choice_label(c)),(a.metaKey||a.ctrlKey)&&this.is_multiple||this.results_hide(),this.show_search_field_default(),(this.is_multiple||this.form_field.selectedIndex!==this.current_selectedIndex)&&this.form_field_jq.trigger("change",{selected:this.form_field.options[c.options_index].value}),this.current_selectedIndex=this.form_field.selectedIndex,a.preventDefault(),this.search_field_scale())):void 0},Chosen.prototype.single_set_selected_text=function(a){return null==a&&(a=this.default_text),a===this.default_text?this.selected_item.addClass("chosen-default"):(this.single_deselect_control_build(),this.selected_item.removeClass("chosen-default")),this.selected_item.find("span").html(a)},Chosen.prototype.result_deselect=function(a){var b;return b=this.results_data[a],this.form_field.options[b.options_index].disabled?!1:(b.selected=!1,this.form_field.options[b.options_index].selected=!1,this.selected_option_count=null,this.result_clear_highlight(),this.results_showing&&this.winnow_results(),this.form_field_jq.trigger("change",{deselected:this.form_field.options[b.options_index].value}),this.search_field_scale(),!0)},Chosen.prototype.single_deselect_control_build=function(){return this.allow_single_deselect?(this.selected_item.find("abbr").length||this.selected_item.find("span").first().after('<abbr class="search-choice-close"></abbr>'),this.selected_item.addClass("chosen-single-with-deselect")):void 0},Chosen.prototype.get_search_text=function(){return a("<div/>").text(a.trim(this.search_field.val())).html()},Chosen.prototype.winnow_results_set_highlight=function(){var a,b;return b=this.is_multiple?[]:this.search_results.find(".result-selected.active-result"),a=b.length?b.first():this.search_results.find(".active-result").first(),null!=a?this.result_do_highlight(a):void 0},Chosen.prototype.no_results=function(b){var c;return c=a('<li class="no-results">'+this.results_none_found+' "<span></span>"</li>'),c.find("span").first().html(b),this.search_results.append(c),this.form_field_jq.trigger("chosen:no_results",{chosen:this})},Chosen.prototype.no_results_clear=function(){return this.search_results.find(".no-results").remove()},Chosen.prototype.keydown_arrow=function(){var a;return this.results_showing&&this.result_highlight?(a=this.result_highlight.nextAll("li.active-result").first())?this.result_do_highlight(a):void 0:this.results_show()},Chosen.prototype.keyup_arrow=function(){var a;return this.results_showing||this.is_multiple?this.result_highlight?(a=this.result_highlight.prevAll("li.active-result"),a.length?this.result_do_highlight(a.first()):(this.choices_count()>0&&this.results_hide(),this.result_clear_highlight())):void 0:this.results_show()},Chosen.prototype.keydown_backstroke=function(){var a;return this.pending_backstroke?(this.choice_destroy(this.pending_backstroke.find("a").first()),this.clear_backstroke()):(a=this.search_container.siblings("li.search-choice").last(),a.length&&!a.hasClass("search-choice-disabled")?(this.pending_backstroke=a,this.single_backstroke_delete?this.keydown_backstroke():this.pending_backstroke.addClass("search-choice-focus")):void 0)},Chosen.prototype.clear_backstroke=function(){return this.pending_backstroke&&this.pending_backstroke.removeClass("search-choice-focus"),this.pending_backstroke=null},Chosen.prototype.keydown_checker=function(a){var b,c;switch(b=null!=(c=a.which)?c:a.keyCode,this.search_field_scale(),8!==b&&this.pending_backstroke&&this.clear_backstroke(),b){case 8:this.backstroke_length=this.search_field.val().length;break;case 9:this.results_showing&&!this.is_multiple&&this.result_select(a),this.mouse_on_container=!1;break;case 13:this.results_showing&&a.preventDefault();break;case 32:this.disable_search&&a.preventDefault();break;case 38:a.preventDefault(),this.keyup_arrow();break;case 40:a.preventDefault(),this.keydown_arrow()}},Chosen.prototype.search_field_scale=function(){var b,c,d,e,f,g,h,i,j;if(this.is_multiple){for(d=0,h=0,f="position:absolute; left: -1000px; top: -1000px; display:none;",g=["font-size","font-style","font-weight","font-family","line-height","text-transform","letter-spacing"],i=0,j=g.length;j>i;i++)e=g[i],f+=e+":"+this.search_field.css(e)+";";return b=a("<div />",{style:f}),b.text(this.search_field.val()),a("body").append(b),h=b.width()+25,b.remove(),c=this.container.outerWidth(),h>c-10&&(h=c-10),this.search_field.css({width:h+"px"})}},Chosen}(AbstractChosen)}).call(this);
}).apply(root, arguments);
});
}(this));
(function(root) {
define("angular-chosen", ["angular","jquery","jquery-chosen"], function() {
return (function() {
/**
* angular-chosen-localytics - Angular Chosen directive is an AngularJS Directive that brings the Chosen jQuery in a Angular way
* @version v1.3.0
* @link http://github.com/leocaseiro/angular-chosen
* @license MIT
*/
(function(){var e=[].indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(t in this&&this[t]===e)return t;return-1};angular.module("localytics.directives",[]),angular.module("localytics.directives").directive("chosen",["$timeout",function(t){var n,r,i,s;return r=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,n=["persistentCreateOption","createOptionText","createOption","skipNoResults","noResultsText","allowSingleDeselect","disableSearchThreshold","disableSearch","enableSplitWordSearch","inheritSelectClasses","maxSelectedOptions","placeholderTextMultiple","placeholderTextSingle","searchContains","singleBackstrokeDelete","displayDisabledOptions","displaySelectedOptions","width","includeGroupLabelInSelected","maxShownResults"],s=function(e){return e.replace(/[A-Z]/g,function(e){return"_"+e.toLowerCase()})},i=function(e){var t;if(angular.isArray(e))return 0===e.length;if(angular.isObject(e))for(t in e)if(e.hasOwnProperty(t))return!1;return!0},{restrict:"A",require:"?ngModel",priority:1,link:function(a,l,o,d){var u,c,f,h,p,g,b,v,S,y,w;return a.disabledValuesHistory=a.disabledValuesHistory?a.disabledValuesHistory:[],l=$(l),l.addClass("localytics-chosen"),p=a.$eval(o.chosen)||{},angular.forEach(o,function(t,r){return e.call(n,r)>=0?o.$observe(r,function(e){return p[s(r)]="{{"===String(l.attr(o.$attr[r])).slice(0,2)?e:a.$eval(e),S()}):void 0}),b=function(){return l.addClass("loading").attr("disabled",!0).trigger("chosen:updated")},v=function(){return l.removeClass("loading"),angular.isDefined(o.disabled)?l.attr("disabled",o.disabled):l.attr("disabled",!1),l.trigger("chosen:updated")},u=null,c=!1,f=function(){var e;return u?l.trigger("chosen:updated"):(t(function(){u=l.chosen(p).data("chosen")}),angular.isObject(u)?e=u.default_text:void 0)},S=function(){return c?l.attr("data-placeholder",u.results_none_found).attr("disabled",!0):l.removeAttr("data-placeholder"),l.trigger("chosen:updated")},d?(g=d.$render,d.$render=function(){return g(),f()},l.on("chosen:hiding_dropdown",function(){return a.$apply(function(){return d.$setTouched()})}),o.multiple&&(w=function(){return d.$viewValue},a.$watch(w,d.$render,!0))):f(),o.$observe("disabled",function(){return l.trigger("chosen:updated")}),o.ngOptions&&d?(h=o.ngOptions.match(r),y=h[7],a.$watchCollection(y,function(e,n){var r;return r=t(function(){return angular.isUndefined(e)?b():(c=i(e),v(),S())})}),a.$on("$destroy",function(e){return"undefined"!=typeof timer&&null!==timer?t.cancel(timer):void 0})):void 0}}}])}).call(this);
}).apply(root, arguments);
});
}(this));
/*
* cjt/decorators/angularChosenDecorator.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* eslint-env amd */
define(
'cjt/decorators/angularChosenDecorator',[
"angular",
"jquery-chosen",
"angular-chosen",
],
function(angular) {
"use strict";
angular
.module("cjt2.decorators.angularChosenDecorator", ["localytics.directives"])
.config(["$provide", function($provide) {
$provide.decorator("chosenDirective", ["$delegate", function($delegate) {
var directive = $delegate[0];
var originalLinkFn = directive.link;
directive.compile = function() {
return function(scope, selectElem) {
originalLinkFn.apply(directive, arguments);
selectElem.on("chosen:ready", function() {
selectElem = selectElem.get(0);
var chosenElem = selectElem.nextElementSibling;
if (!chosenElem || !chosenElem.classList.contains("chosen-container")) {
throw new Error("Developer Error: Chosen has not initialized properly. The .chosen-container element is not next to the select element");
}
var inputElem = chosenElem.querySelector(".chosen-search input");
var labelElem = selectElem.id && document.querySelector("label[for=\"" + selectElem.id + "\"]");
if (inputElem && labelElem && labelElem.id) {
inputElem.setAttribute("aria-labelledby", labelElem.id);
}
});
};
};
return $delegate;
}]);
}]);
}
);
/*
# cjt/decorators/dynamicName.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/decorators/dynamicName',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.decorators.dynamicName", []);
// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
module.config(["$provide", function($provide) {
// Extend the ngModelDirective to interpolate its name attribute
$provide.decorator("ngModelDirective", ["$delegate", function($delegate) {
var ngModel = $delegate[0], controller = ngModel.controller;
ngModel.controller = ["$scope", "$element", "$attrs", "$injector", function(scope, element, attrs, $injector) {
var $interpolate = $injector.get("$interpolate");
attrs.$set("name", $interpolate(attrs.name || "")(scope));
$injector.invoke(controller, this, {
"$scope": scope,
"$element": element,
"$attrs": attrs
});
}];
return $delegate;
}]);
// Extend the formDirective to interpolate its name attribute
$provide.decorator("formDirective", ["$delegate", function($delegate) {
var form = $delegate[0], controller = form.controller;
form.controller = ["$scope", "$element", "$attrs", "$injector", function(scope, element, attrs, $injector) {
var $interpolate = $injector.get("$interpolate");
attrs.$set("name", $interpolate(attrs.name || attrs.ngForm || "")(scope));
$injector.invoke(controller, this, {
"$scope": scope,
"$element": element,
"$attrs": attrs
});
}];
return $delegate;
}]);
}]);
}
);
/*
# cjt/templates.js Copyright 2026 cPanel, Inc.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global angular: false */
define(
'cjt/templates',[
"angular",
],
function(angular) {
"use strict";
angular.module("cjt2.templates", []).run(["$templateCache", function($templateCache) {
$templateCache.put("libraries/cjt2/diag/routeDirective.phtml",
"<div>\n" +
" <hr />\n" +
" <pre>$window.location.path = {{$window.location.path}}</pre>\n" +
" <pre>$window.location.search = {{$window.location.search}}</pre>\n" +
" <pre>$location.path() = {{$location.path()}}</pre>\n" +
" <pre>$location.search() = {{$location.search()}}</pre>\n" +
" <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>\n" +
" <pre>$route.current.params = {{$route.current.params}}</pre>\n" +
" <pre>$route.current.scope = {{$route.current.scope}}</pre>\n" +
" <pre>$routeParams = {{$routeParams}}</pre>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/actionButton.phtml",
"<button class=\"btn {{buttonClass}}\"\n" +
" ng-class=\"ngClass()\"\n" +
" ng-click=\"start()\">\n" +
" <spinner id=\"{{spinnerId}}\" glyph-class=\"fas fa-sync button-loading-indicator\"></spinner>\n" +
" <span class=\"button-label\" ng-transclude></span>\n" +
"</button>\n" +
"");
$templateCache.put("libraries/cjt2/directives/alert.phtml",
"<div>\n" +
" <div ng-show=\"alert.type === 'danger'\" class='alert alert-danger ng-hide' role=\"alert\">\n" +
" <button id=\"{{'btnClose_danger_' + alert.id}}\" type='button'\n" +
" class='close' ng-if='alert.closeable' ng-click='runClose()' aria-label=\"Close\">\n" +
" <span aria-hidden=\"true\">×</span>\n" +
" </button>\n" +
" <button id=\"{{'btnMore_danger_' + alert.id}}\" type='button'\n" +
" class='btn btn-more btn-link pull-right flip' ng-if='hasToggleHandler' ng-click='runToggleMore()'>{{moreLabel}}</button>\n" +
" <span class='glyphicon glyphicon-remove-sign' aria-hidden=\"true\"></span>\n" +
" <div class='alert-message'>\n" +
" <strong class=\"alert-title\" ng-show=\"errorLabel\">{{errorLabel}}</strong>\n" +
" <span class=\"alert-body\"><span id=\"{{'txtMessage_danger_' + alert.id}}\" ng-bind-html=\"alert.message\" ng-if=\"alert && alert.message\"></span></span>\n" +
" <ul ng-if=\"alert.list && alert.list.length\" class=\"alert-list\">\n" +
" <li ng-repeat=\"value in alert.list\">\n" +
" <span id=\"{{'txtList_danger_' + alert.id + '_' + $index}}\" ng-bind-html=\"value\"></span>\n" +
" </li>\n" +
" </ul>\n" +
" </div>\n" +
" </div>\n" +
"\n" +
" <div ng-show=\"alert.type === 'info'\" class='alert alert-info ng-hide' role=\"alert\">\n" +
" <button id=\"{{'btnClose_info_' + alert.id}}\" type='button'\n" +
" class='close' ng-if='alert.closeable' ng-click='runClose()' aria-label=\"Close\">\n" +
" <span aria-hidden=\"true\">×</span>\n" +
" </button>\n" +
" <button id=\"{{'btnMore_info_' + alert.id}}\" type='button' class='btn btn-more btn-link pull-right flip' ng-if='hasToggleHandler' ng-click='runToggleMore()'>{{moreLabel}}</button>\n" +
" <span class='glyphicon glyphicon-info-sign' aria-hidden=\"true\"></span>\n" +
" <div class='alert-message'>\n" +
" <strong class=\"alert-title\" ng-show=\"infoLabel\">{{infoLabel}}</strong>\n" +
" <span class=\"alert-body\"><span id=\"{{'txtMessage_info_' + alert.id}}\" ng-bind-html=\"alert.message\" ng-if=\"alert && alert.message\"></span></span>\n" +
" <ul ng-if=\"alert.list && alert.list.length\" class=\"alert-list\">\n" +
" <li ng-repeat=\"value in alert.list\">\n" +
" <span id=\"{{'txtList_info_' + alert.id + '_' + $index}}\" ng-bind-html=\"value\"></span>\n" +
" </li>\n" +
" </ul>\n" +
" </div>\n" +
" </div>\n" +
"\n" +
" <div ng-show=\"alert.type === 'success'\" class='alert alert-success ng-hide' role=\"alert\">\n" +
" <button id=\"{{'btnClose_success_' + alert.id}}\" type='button'\n" +
" class='close' ng-if='alert.closeable' ng-click='runClose()' aria-label=\"Close\">\n" +
" <span aria-hidden=\"true\">×</span>\n" +
" </button>\n" +
" <button id=\"{{'btnMore_success_' + alert.id}}\" type='button' class='btn btn-more btn-link pull-right flip' ng-if='hasToggleHandler' ng-click='runToggleMore()'>{{moreLabel}}</button>\n" +
" <span class='glyphicon glyphicon-ok-sign' aria-hidden=\"true\"></span>\n" +
" <div class='alert-message'>\n" +
" <strong class=\"alert-title\" ng-show=\"successLabel\">{{successLabel}}</strong>\n" +
" <span class=\"alert-body\"><span id=\"{{'txtMessage_success_' + alert.id}}\" ng-bind-html=\"alert.message\" ng-if=\"alert && alert.message\"></span></span>\n" +
" <ul ng-if=\"alert.list && alert.list.length\" class=\"alert-list\">\n" +
" <li ng-repeat=\"value in alert.list\">\n" +
" <span id=\"{{'txtList_success_' + alert.id + '_' + $index}}\" ng-bind-html=\"value\"></span>\n" +
" </li>\n" +
" </ul>\n" +
" </div>\n" +
" </div>\n" +
"\n" +
" <div ng-show=\"alert.type === 'warning'\" class='alert alert-warning ng-hide' role=\"alert\">\n" +
" <button id=\"{{'btnClose_warning_' + alert.id}}\" type='button'\n" +
" class='close' ng-if='alert.closeable' ng-click='runClose()' aria-label=\"Close\">\n" +
" <span aria-hidden=\"true\">×</span>\n" +
" </button>\n" +
" <button id=\"{{'btnMore_warning_' + alert.id}}\" type='button' class='btn btn-more btn-link pull-right flip' ng-if='hasToggleHandler' ng-click='runToggleMore()'>{{moreLabel}}</button>\n" +
" <span class='glyphicon glyphicon-exclamation-sign' aria-hidden=\"true\"></span>\n" +
" <div class='alert-message'>\n" +
" <strong class=\"alert-title\" ng-show=\"warnLabel\">{{warnLabel}}</strong>\n" +
" <span class=\"alert-body\"><span id=\"{{'txtMessage_warning_' + alert.id}}\" ng-bind-html=\"alert.message\" ng-if=\"alert && alert.message\"></span></span>\n" +
" <ul ng-if=\"alert.list && alert.list.length\" class=\"alert-list\">\n" +
" <li ng-repeat=\"value in alert.list\">\n" +
" <span id=\"{{'txtList_warning_' + alert.id + '_' + $index}}\" ng-bind-html=\"value\"></span>\n" +
" </li>\n" +
" </ul>\n" +
" </div>\n" +
" </div>\n" +
"</div>");
$templateCache.put("libraries/cjt2/directives/alertList.phtml",
"<div>\n" +
" <div class=\"alert-list-container\" ng-class=\"[ getPositionClasses(), {\n" +
" 'show-scroll-bar': needsScrollbar(),\n" +
" }]\">\n" +
" <div class=\"alert-list\"><!-- Used to get the full height of the list, since alert-list-container can be constrained -->\n" +
" <div class=\"alert-container\" ng-repeat=\"alert in alerts\"><!-- Used to smoothly animate the alert margins -->\n" +
" <cp:alert ng-model=\"alert\"\n" +
" id=\"{{ alert.id }}\"\n" +
" auto-close=\"alert.autoClose\">\n" +
" </cp:alert>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"alert-list-backdrop\" ng-if=\"alertsPresent\" ng-class=\"getPositionClasses()\">\n" +
" </div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/breadcrumbs.phtml",
"<ul class=\"breadcrumb\">\n" +
" <li ng-repeat=\"breadcrumb in crumbs\" ng-class=\"{'hidden-xs':!($last-1) && !$first}\">\n" +
" <ng-switch on=\"$last\">\n" +
" <span ng-switch-when=\"true\"><span ng-bind-html=\"breadcrumb.name\"></span></span>\n" +
" <a href=\"javascript:void(0)\" ng-click=\"changeRoute(breadcrumb)\" ng-switch-default>\n" +
" <i class=\"fas fa-chevron-left visible-xs-inline\" ng-if=\"$last-1\"></i>\n" +
" <span ng-bind-html=\"breadcrumb.name\"></span>\n" +
" </a>\n" +
" </ng-switch>\n" +
" </li>\n" +
"</ul>\n" +
"");
$templateCache.put("libraries/cjt2/directives/breadcrumbs.spec.phtml",
"<breadcrumbs id=\"breadcrumbs\" class=\"breadcrumbs\">\n" +
"</breadcrumbs>");
$templateCache.put("libraries/cjt2/directives/bytesInput.phtml",
"<span class=\"input-group bytes-input\">\n" +
" <input type=\"number\" id=\"{{name}}\" name=\"{{name}}\" class=\"form-control {{extraInputClasses}}\" size=\"{{size}}\" ng-model=\"inputValue\"\n" +
" ng-model-options=\"{ debounce: 500 }\"\n" +
" ng-disabled=\"isDisabled\"\n" +
" min=\"{{min}}\">\n" +
" <span class=\"input-group-btn\" uib-dropdown>\n" +
" <button type=\"button\" id=\"{{name}}DropDownButton\" name=\"{{name}}DropDownButton\" uib-dropdown-toggle class=\"btn btn-default dropdown-toggle\" title=\"{{ selectedUnit.full }}\"\n" +
" ng-disabled=\"isDisabled\">\n" +
" {{ selectedUnit.abbr }}\n" +
" <span class=\"fas fa-caret-down\"></span>\n" +
" </button>\n" +
" <ul uib-dropdown-menu role=\"menu\" class=\"dropdown-menu\">\n" +
" <li ng-repeat=\"(key, value) in units track by key\">\n" +
" <a href=\"javascript:void(0)\" ng-click=\"selectUnit(key)\" title=\"{{value.full}}\">{{ value.abbr }}</a>\n" +
" </li>\n" +
" </ul>\n" +
" </span>\n" +
"</span>\n" +
"\n" +
"");
$templateCache.put("libraries/cjt2/directives/callout.phtml",
"<div class=\"callout callout-{{calloutType}}\" aria-label=\"{{calloutType}}\">\n" +
" <button type='button' class='close'\n" +
" ng-if='closeable'\n" +
" ng-click='runClose()'\n" +
" aria-label=\"Close\">\n" +
" <span aria-hidden=\"true\">×</span>\n" +
" </button>\n" +
" <div class=\"callout-heading\" ng-show=\"hasHeading\">{{calloutHeading}}</div>\n" +
" <div ng-transclude></div>\n" +
"</div>");
$templateCache.put("libraries/cjt2/directives/copyField.phtml",
"<div class=\"copy-field-directive\">\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-12\">\n" +
" <div class=\"input-group\">\n" +
" <span class=\"input-group-addon\"><strong ng-bind=\"label\"></strong></span>\n" +
"\n" +
" <!--\n" +
" Use an <input> when there is only one line\n" +
" because a non-resizable, overflow-hidden <textarea>\n" +
" doesn’t allow the user to see the full value.\n" +
" -->\n" +
" <input ng-if=\"!multilineRows || multilineRows < 2\" readonly ng-value=\"text\" placeholder=\"{{placeholderText}}\" class=\"form-control copy-field-data\" id=\"{{ :: copyFieldID }}\" name=\"{{::parentID}}_recordField\">\n" +
"\n" +
" <textarea ng-if=\"multilineRows && multilineRows > 1\" placeholder=\"{{placeholderText}}\" class=\"form-control multi-line copy-field-data\" id=\"{{ :: copyFieldID }}\" name=\"{{::parentID}}_recordField\" readonly rows=\"{{multilineRows}}\" ng-value=\"text\" ng-bind=\"text\"></textarea>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-12 text-right\">\n" +
" <span ng-transclude></span>\n" +
" <a ng-click=\"text && copyToClipboard()\" href=\"javascript:void(0)\" class=\"btn btn-sm btn-link\" ng-disabled=\"!text\">\n" +
" <span ng-bind=\"copyLabel\"></span>\n" +
" <i class=\"fas fa-copy\" aria-hidden=\"true\"></i>\n" +
" </a>\n" +
" </div>\n" +
" </div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/datePicker.phtml",
"<div class=\"date-picker-directive\">\n" +
" <p class=\"input-group\">\n" +
" <input\n" +
" id=\"{{parentID}}_datePicker_input\"\n" +
" type=\"text\"\n" +
" class=\"form-control\"\n" +
" uib-datepicker-popup\n" +
" ng-model=\"selectedDate\"\n" +
" is-open=\"showingPopup\"\n" +
" ng-required=\"true\"\n" +
" ng-change=\"onChange(selectedDate)\"\n" +
" datepicker-options=\"options\"\n" +
" current-text=\"{{::currentTextLabel}}\"\n" +
" clear-text=\"{{::clearTextLabel}}\"\n" +
" close-text=\"{{::closeTextLabel}}\" />\n" +
"\n" +
" <span class=\"input-group-btn\">\n" +
" <button id=\"{{parentID}}_datePicker_btn\" type=\"button\" class=\"btn btn-default\" ng-click=\"showPopup()\">\n" +
" <i class=\"fas fa-calendar\" aria-hidden=\"true\"></i>\n" +
" </button>\n" +
" </span>\n" +
" </p>\n" +
"</div>");
$templateCache.put("libraries/cjt2/directives/displayPasswordStrength.phtml",
"<ul class=\"strength\">\n" +
" <li class=\"point\" data-testid=\"{{testId}}-strength-check-1\"></li>\n" +
" <li class=\"point\" data-testid=\"{{testId}}-strength-check-2\"></li>\n" +
" <li class=\"point\" data-testid=\"{{testId}}-strength-check-3\"></li>\n" +
" <li class=\"point\" data-testid=\"{{testId}}-strength-check-4\"></li>\n" +
" <li class=\"point\" data-testid=\"{{testId}}-strength-check-5\"></li>\n" +
"</ul>");
$templateCache.put("libraries/cjt2/directives/formWaiting.phtml",
"<fieldset class=\"cp-form-waiting-wrapper\" ng-disabled=\"_show_mask\">\n" +
" <div class=\"cp-form-waiting-mask\"></div>\n" +
" <div class=\"cp-form-waiting-spinner\">\n" +
" <i class=\"fas fa-spinner fa-spin fa-{{spinner_size}}x\"></i>\n" +
" </div>\n" +
"\n" +
" <div class=\"cp-form-waiting-transclude\"></div>\n" +
"</fieldset>\n" +
"");
$templateCache.put("libraries/cjt2/directives/growl.phtml",
"<div class=\"growl-container\" ng-class=\"wrapperClasses()\">\n" +
" <div class=\"growl-item alert\" id=\"{{ message.id }}\" ng-repeat=\"message in growlMessages.directives[referenceId].messages\" ng-class=\"alertClasses(message)\" ng-click=\"stopTimeoutClose(message)\">\n" +
" <button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-hidden=\"true\" ng-click=\"growlMessages.deleteMessage(message)\" ng-show=\"!message.disableCloseButton\">×</button>\n" +
" <button type=\"button\" class=\"close\" aria-hidden=\"true\" ng-show=\"showCountDown(message)\">{{message.countdown}}</button>\n" +
" <h4 class=\"growl-title\" ng-show=\"message.title\" ng-bind=\"message.title\"></h4>\n" +
" <div class=\"growl_icon\">\n" +
" <span class=\"glyphicon glyphicon-remove-sign\" ng-if=\"message.severity == 'error'\"></span>\n" +
" <span class=\"glyphicon glyphicon-exclamation-sign\" ng-if=\"message.severity == 'warning' || message.severity == 'warn'\"></span>\n" +
" <span class=\"glyphicon glyphicon-info-sign\" ng-if=\"message.severity == 'info'\"></span>\n" +
" <span class=\"glyphicon glyphicon-ok-sign\" ng-if=\"message.severity == 'success'\"></span>\n" +
" </div>\n" +
" <div class=\"growl-message\" ng-bind-html=\"message.text\"></div>\n" +
" <div class=\"growl_action\" ng-show=\"message.variables.showAction\">\n" +
" <button class=\"btn btn-primary btn-xs\" ng-click=\"$eval(message.variables.action)\">{{message.variables.buttonLabel}}</button>\n" +
" </div>\n" +
" </div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/indeterminateState.spec.phtml",
"<input type=\"checkbox\"\n" +
" name=\"checkAllInList\"\n" +
" id=\"checkAllInList\"\n" +
" aria-label=\"Toggle account selection\"\n" +
" ng-model=\"selectAllState\"\n" +
" ng-change=\"toggleSelectAll()\"\n" +
" indeterminate-state\n" +
" check-state=\"getIndeterminateState()\">\n" +
"");
$templateCache.put("libraries/cjt2/directives/labelSuffix.phtml",
"<span class=\"required-field\" id=\"fieldRequired_{{fieldName}}\" ng-show=\"showAsterisk()\" title=\"{{ text('required') }}\">*</span>\n" +
"<span class=\"fas fa-check field-validation-valid\" id=\"fieldValid_{{fieldName}}\" ng-show=\"isValid()\" title=\"{{ text('valid') }}\"></span>\n" +
"<span class=\"fas fa-times field-validation-invalid\" id=\"fieldInvalid_{{fieldName}}\" ng-show=\"isInvalid()\" title=\"{{ text('invalid') }}\"></span>\n" +
"<span glyph-class=\"fas fa-spinner\" id=\"{{spinnerId}}\" spinner title=\"{{ text('validating') }}\"></span>\n" +
"");
$templateCache.put("libraries/cjt2/directives/loadingPanel.phtml",
"<div class=\"alert alert-info\" id=\"{{id}}\">\n" +
" <span class=\"glyphicon glyphicon-refresh fa-spin\" id=\"{{id}}_loadingSpinner\"></span>\n" +
" <div class=\"alert-message\" id=\"{{id}}_loadingMessage\">\n" +
" <span ng-transclude></span>\n" +
" </div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/multiFieldEditor.phtml",
"<div class=\"multi-field-editor-directive\" id=\"{{ :: parentID }}_multiFieldEditor\">\n" +
" <div class=\"form-group\">\n" +
" <div class=\"row\" ng-show=\"ngModel.length > 49\">\n" +
" <div class=\"col-xs-12 col-md-12\">\n" +
" <a href class=\"btn btn-link\" ng-click=\"addRow()\" id=\"{{ :: parentID }}_multiFieldEditor_addNewButton_top\">\n" +
" <span class='glyphicon glyphicon-plus'></span>\n" +
" {{ :: addNewLabel }}\n" +
" </a>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-12 col-md-12\">\n" +
" <div id=\"{{ :: parentID }}_multiFieldEditor_valueItems\" class=\"mfe-items\" ng-transclude></div>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-12 col-md-12\">\n" +
" <a href class=\"btn btn-link\" ng-click=\"addRow()\" id=\"{{ :: parentID }}_multiFieldEditor_addNewButton_bottom\">\n" +
" <span class='glyphicon glyphicon-plus'></span>\n" +
" {{ :: addNewLabel }}\n" +
" </a>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/multiFieldEditorItem.phtml",
"<div class=\"multi-field-editor-item-directive row\">\n" +
" <div class=\"mfei-content col-xs-12 col-sm-12 col-md-6 col-lg-6\">\n" +
" <div class=\"mfei-label-holder\">\n" +
" <label for=\"{{ :: labelFor }}\" ng-bind=\"label\"></label>\n" +
" </div>\n" +
" <div class=\"mfei-transclude\" ng-transclude></div>\n" +
" <div class=\"mfei-link\">\n" +
" <a href\n" +
" ng-if=\"canRemove\"\n" +
" class=\"btn btn-link\"\n" +
" ng-click=\"remove()\"\n" +
" id=\"{{ :: parentID }}_removeNewButton\">\n" +
" <span class='fas fa-times fa-lg'></span>\n" +
" <span class=\"sr-only\">[% locale.maketext('Delete Item') %]</span>\n" +
" </a>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"col-xs-12 col-sm-12 col-md-6 col-lg-6\">\n" +
" <ul validation-container field-name=\"{{ :: labelFor}}\">\n" +
" <li validation-item\n" +
" field-name=\"{{ :: labelFor}}\"\n" +
" validation-name=\"number\">\n" +
" <span ng-bind=\"numericValueMessage()\"></span>\n" +
" </li>\n" +
" <li validation-item\n" +
" field-name=\"{{ :: labelFor}}\"\n" +
" validation-name=\"required\">\n" +
" <span ng-bind=\"requiredFieldMessage()\"></span>\n" +
" </li>\n" +
" </ul>\n" +
" </div>\n" +
"</div>");
$templateCache.put("libraries/cjt2/directives/pageSizeButtonDirective.phtml",
"<div>\n" +
" <div class=\"btn-group pageSizeButtons\" role=\"group\">\n" +
" <button type=\"button\" id=\"{{size.id}}\" ng-repeat=\"size in options\" class=\"btn btn-default btn-sm no-validation-border\" ng-class=\"{active: $parent.pageSize == '{{size.value}}'}\" ng-model=\"$parent.pageSize\" uib-btn-radio=\"{{size.value}}\" aria-label=\"{{size.description}}\" aria-pressed=\"{{ $parent.pageSize == size.value }}\">{{size.label}}</button>\n" +
" </div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/pageSizeDirective.phtml",
"<div class=\"page-size\">\n" +
" <div ng-hide=\"autoHide && options[0].value >= totalItems\" class=\"form-group\" >\n" +
" <label for=\"{{parentID}}_select\" class=\"title\">{{pageSizeTitle}}</label>\n" +
" <select id=\"{{parentID}}_select\"\n" +
" class=\"form-control\"\n" +
" ng-options=\"size.value as size.label for size in options\"\n" +
" ng-model=\"pageSize\">\n" +
" </select>\n" +
" </div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/pagination.phtml",
"<ul class=\"pagination\" aria-label=\"{{::ariaLabels.title}}\">\n" +
" <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noPrevious()}\">\n" +
" <a id=\"{{::parentId}}_first\" href\n" +
" ng-click=\"selectPage(1)\"\n" +
" aria-label=\"{{::ariaLabels.firstPage}}\">\n" +
" {{getText('first')}}\n" +
" </a>\n" +
" </li>\n" +
" <li ng-if=\"directionLinks\" ng-class=\"{disabled: noPrevious()}\">\n" +
" <a id=\"{{::parentId}}_previous\" href\n" +
" ng-click=\"selectPage(page - 1)\"\n" +
" aria-label=\"{{::ariaLabels.previousPage}}\">\n" +
" {{getText('previous')}}\n" +
" </a>\n" +
" </li>\n" +
" <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active}\" ng-switch=\"page.active\">\n" +
" <a id=\"{{page.id}}\" href\n" +
" ng-click=\"selectPage(page.number)\"\n" +
" aria-label=\"{{page.ariaLabel}}\"\n" +
" aria-current=\"page\"\n" +
" ng-switch-when=\"true\">\n" +
" {{page.text}}\n" +
" </a>\n" +
" <a id=\"{{page.id}}\" href\n" +
" ng-click=\"selectPage(page.number)\"\n" +
" aria-label=\"{{page.ariaLabel}}\"\n" +
" ng-switch-default>\n" +
" {{page.text}}\n" +
" </a>\n" +
" </li>\n" +
" <li ng-if=\"directionLinks\" ng-class=\"{disabled: noNext()}\">\n" +
" <a id=\"{{::parentId}}_next\" href\n" +
" ng-click=\"selectPage(page + 1)\"\n" +
" aria-label=\"{{::ariaLabels.nextPage}}\">\n" +
" {{getText('next')}}\n" +
" </a>\n" +
" </li>\n" +
" <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noNext()}\">\n" +
" <a id=\"{{::parentId}}_last\" href\n" +
" ng-click=\"selectPage(totalPages)\"\n" +
" aria-label=\"{{::ariaLabels.lastPage}}\">\n" +
" {{getText('last')}}\n" +
" </a>\n" +
" </li>\n" +
"</ul>\n" +
"");
$templateCache.put("libraries/cjt2/directives/passwordField.phtml",
"<span class=\"ng-cloak\" ng-cloak>\n" +
" <span class=\"input-group\" ng-class=\"{ 'settings-panel-visible': showSettings }\">\n" +
" <!-- hack to make sure browsers dont auto fill the password field -->\n" +
" <!-- title is required for WAI, but screen readers will not see the element so not translating -->\n" +
" <input type=\"password\" aria-hidden=\"true\" title=\"hidden password field\" id=\"hiddenPasswordField\" autocomplete=\"off\" style=\"display:none\">\n" +
" <input id=\"{{name}}\"\n" +
" type=\"{{show ? 'text' : 'password'}}\"\n" +
" name=\"{{name}}\"\n" +
" data-testid=\"{{testId}}-input\"\n" +
" class=\"form-control field\"\n" +
" ng-class=\"{ 'obscured-field': !show, 'unobscured-field': show}\"\n" +
" placeholder=\"{{placeholder}}\"\n" +
" autocomplete=\"new-password\"\n" +
" ng-model=\"password\"\n" +
" title=\"{{placeholder}}\"\n" +
" ng-trim=\"false\"\n" +
" check-password-strength\n" +
" minimum-password-strength=\"{{minimumStrength}}\"\n" +
" ng-model-options=\"{debounce: 150}\"\n" +
" ng-minlength=\"{{minimumLength}}\"\n" +
" ng-maxlength=\"{{maximumLength}}\">\n" +
" <span class=\"input-group-btn ng-hide\" ng-show=\"showGenerator || showToggleView\">\n" +
" <button id=\"{{name + '_btnToggleView'}}\"\n" +
" data-testid=\"{{testId}}-toggle-visibility-button\"\n" +
" class=\"btn btn-default ng-hide\"\n" +
" ng-click=\"toggle()\"\n" +
" ng-show=\"showGenerator || showToggleView\"\n" +
" type=\"button\"\n" +
" ng-attr-tabindex=\"{{toggleViewButtonTabIndex}}\"\n" +
" title={{toggleViewButtonTitle}}>\n" +
" <i class=\"fas fa-lg fa-fw\" ng-class=\"{ 'fa-eye': show, 'fa-eye-slash': !show }\"></i>\n" +
" </button>\n" +
" <button id=\"{{name + '_btnGenerate'}}\"\n" +
" class=\"btn btn-default ng-hide\"\n" +
" data-testid=\"{{testId}}-generate-button\"\n" +
" ng-click=\"generate()\"\n" +
" ng-show=\"showGenerator\"\n" +
" type=\"button\"\n" +
" ng-attr-tabindex=\"{{generateButtonTabIndex}}\"\n" +
" title=\"{{generateButtonTitle}}\">\n" +
" <span class=\"hidden-xs\">{{generateButtonText}}</span>\n" +
" <i class=\"fas fa-fw fa-key fa-lg fa-rotate-90 hidden-sm hidden-md hidden-lg\" ></i>\n" +
" </button>\n" +
" <button id=\"{{name + '_btnSettings'}}\"\n" +
" class=\"btn btn-default ng-hide\"\n" +
" data-testid=\"{{testId}}-toggle-settings-button\"\n" +
" title=\"{{generateSettingsTitle}}\"\n" +
" ng-click=\"toggleSettings()\"\n" +
" type=\"button\"\n" +
" ng-attr-tabindex=\"{{generateSettingsTabIndex}}\"\n" +
" ng-show=\"showGenerator\">\n" +
" <span class=\"fas fa-fw fa-caret-down\" ng-show=\"!showSettings\"></span>\n" +
" <span class=\"fas fa-fw fa-caret-up\" ng-show=\"showSettings\"></span>\n" +
" </button>\n" +
" </span>\n" +
" </span>\n" +
" <div id=\"{{name + '_pnlSettings'}}\" class=\"panel panel-default ng-hide\" ng-show=\"showSettings\">\n" +
" <div class=\"panel-body\">\n" +
" <p>{{caption}}</p>\n" +
" <div class=\"row\">\n" +
" <span class=\"col-xs-6 col-sm-3\">\n" +
" <span class=\"form-group form-group-length\">\n" +
" <label for=\"{{name + '_txtLength'}}\">\n" +
" {{generateSettingsLengthLabel}}\n" +
" </label>\n" +
" <input id=\"{{name + '_txtLength'}}\"\n" +
" ng-model=\"passwordLength\"\n" +
" type=\"number\"\n" +
" limit-range\n" +
" range-minimum=\"{{generateMinimumLength}}\"\n" +
" range-maximum=\"{{generateMaximumLength}}\"\n" +
" range-default=\"{{defaultLength}}\"\n" +
" class=\"form-control length-field\"\n" +
" min=\"{{generateMinimumLength}}\"\n" +
" max=\"{{generateMaximumLength}}\">\n" +
" </span>\n" +
" </span>\n" +
" <span class=\"col-xs-1 col-xs-offset-4 col-sm-offset-8\">\n" +
" <i ng-click=\"toggleSettings()\" class=\"fas fa-fw fa-times fa-lg pull-right flip\" title=\"Close\" id=\"{{name + '_btnClose'}}\"></i>\n" +
" </span>\n" +
" </div>\n" +
" <div class=\"row\">\n" +
" <span class=\"col-xs-12 col-sm-6\">\n" +
" <span class=\"row\">\n" +
" <span class=\"col-xs-12\">\n" +
" <h4>{{generateSettingsAlphaTitle}}</h4>\n" +
" </span>\n" +
" <span class=\"col-xs-12\">\n" +
" <span class=\"radio\">\n" +
" <label for=\"{{name + '_rdoAlphaBoth'}}\">\n" +
" <input id=\"{{name + '_rdoAlphaBoth'}}\" data-testid=\"{{testId}}-settings-both-letters-radio\" type=\"radio\" ng-model=\"alpha\" value=\"both\" name=\"alpha\">\n" +
" {{generateSettingsAlphaBothLabel}}\n" +
" </label>\n" +
" </span>\n" +
" </span>\n" +
" <span class=\"col-xs-12\">\n" +
" <span class=\"radio\">\n" +
" <label for=\"{{name + '_rdoAlphaLower'}}\">\n" +
" <input id=\"{{name + '_rdoAlphaLower'}}\" data-testid=\"{{testId}}-settings-lc-letters-radio\" type=\"radio\" ng-model=\"alpha\" value=\"lower\" name=\"alpha\">\n" +
" {{generateSettingsAlphaLowerLabel}}\n" +
" </label>\n" +
" </span>\n" +
" </span>\n" +
" <span class=\"col-xs-12\">\n" +
" <span class=\"radio\">\n" +
" <label for=\"{{name + '_rdoAlphaUpper'}}\">\n" +
" <input id=\"{{name + '_rdoAlphaUpper'}}\" data-testid=\"{{testId}}-settings-uc-letters-radio\" type=\"radio\" ng-model=\"alpha\" value=\"upper\" name=\"alpha\">\n" +
" {{generateSettingsAlphaUpperLabel}}\n" +
" </label>\n" +
" </span>\n" +
" </span>\n" +
" </span>\n" +
" </span>\n" +
" <span class=\"col-xs-12 col-sm-6\">\n" +
" <span class=\"row\">\n" +
" <span class=\"col-xs-12\">\n" +
" <h4>{{generateSettingsOtherTitle}}</h4>\n" +
" </span>\n" +
" <span class=\"col-xs-12\">\n" +
" <span class=\"radio\">\n" +
" <label for=\"{{name + '_rdoNonAlphaBoth'}}\">\n" +
" <input id=\"{{name + '_rdoNonAlphaBoth'}}\" type=\"radio\" data-testid=\"{{testId}}-settings-both-symbols-radio\" ng-model=\"nonalpha\" value=\"both\" name=\"nonalpha\">\n" +
" {{generateSettingsBothNumersAndSymbolsLabel}}\n" +
" </label>\n" +
" </span>\n" +
" </span>\n" +
" <span class=\"col-xs-12\">\n" +
" <span class=\"radio\">\n" +
" <label for=\"{{name + '_rdoNonAlphaNumbers'}}\">\n" +
" <input id=\"{{name + '_rdoNonAlphaNumbers'}}\" data-testid=\"{{testId}}-settings-numbers-radio\" type=\"radio\" ng-model=\"nonalpha\" value=\"numbers\" name=\"nonalpha\">\n" +
" {{generateSettingsNumbersLabel}}\n" +
" </label>\n" +
" </span>\n" +
" </span>\n" +
" <span class=\"col-xs-12\">\n" +
" <span class=\"radio\">\n" +
" <label for=\"{{name + '_rdoNonAlphaSymbols'}}\">\n" +
" <input id=\"{{name + '_rdoNonAlphaSymbols'}}\" data-testid=\"{{testId}}-settings-symbols-radio\" type=\"radio\" ng-model=\"nonalpha\" value=\"symbols\" name=\"nonalpha\">\n" +
" {{generateSettingsSymbolsLabel}}\n" +
" </label>\n" +
" </span>\n" +
" </span>\n" +
" </span>\n" +
" </span>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <display-password-strength id=\"{{name + '_strength_meter'}}\" field-id=\"{{name}}\" class=\"ng-hide\" ng-show=\"showMeter\"></display-password-strength>\n" +
" <input id=\"{{name + '_strength'}}\" role=\"presentation\" title=\"\" type=\"hidden\" update-password-strength field-id=\"{{name}}\" ng-model=\"passwordStrength\">\n" +
" <div class=\"ng-hide current-strength-text\" ng-show=\"currentStrengthText\">\n" +
" {{ currentStrengthText }}\n" +
" </div>\n" +
"</span>\n" +
"");
$templateCache.put("libraries/cjt2/directives/processingIcon.phtml",
"<span class=\"fas fa-2x text-muted fa-check\"\n" +
" title=\"{{title}}\"\n" +
" ng-class=\"{'text-muted fa-check': state == 0,\n" +
" 'text-primary fa-spinner fa-spin': state == 1,\n" +
" 'text-success fa-check': state == 2,\n" +
" 'text-danger fa-times': state == 3,\n" +
" 'text-info fa-question': state == 4}\">\n" +
"</span>");
$templateCache.put("libraries/cjt2/directives/quickFilterItem.phtml",
"<li ng-class=\"{ 'active' : quickFilter.active}\">\n" +
" <a href=\"javascript:void(0)\" title=\"{{ :: linkTitle }}\" id=\"{{ :: parentID }}_link\" ng-click=\"selectFilter(quickFilter.value)\" ng-transclude></a>\n" +
"</li>\n" +
"");
$templateCache.put("libraries/cjt2/directives/quickFilters.phtml",
"<div class=\"quick-filters-nav\">\n" +
" <span ng-if=\"title\" class=\"quick-filter-label hidden-xs\" ng-bind=\"title\"></span>\n" +
" <ul class=\"nav nav-pills\" ng-transclude></ul>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/quickFilters.spec.phtml",
"<quick-filters id=\"quickFilters\" title=\"Filter\" active=\"quickFilterValue\" on-filter-change=\"fetch()\">\n" +
" <quick-filter-item id=\"lnkFilterAll\" value=\"all\" title=\"Show all of my accounts.\">All</quick-filter-item>\n" +
" <quick-filter-item id=\"lnkFilterRestricted\" value=\"restricted\" title=\"Show my restricted accounts.\">Restricted</quick-filter-item>\n" +
" <quick-filter-item id=\"lnkFilterDefault\" value=\"default\" title=\"Show my system account.\">System Account</quick-filter-item>\n" +
" <quick-filter-item id=\"lnkFilterOverQuota\" value=\"overUsed\" title=\"Show accounts that have used all of their storage space.\">Exceeded Storage</quick-filter-item>\n" +
"</quick-filters>\n" +
"");
$templateCache.put("libraries/cjt2/directives/responsiveSortInsertDirective.phtml",
"<cp-select-sort label=\"{{ attrs.label }}\"\n" +
" id-suffix=\"{{ attrs.idSuffix }}\"\n" +
" default-field=\"{{ attrs.defaultField }}\"\n" +
" default-dir=\"{{ attrs.defaultDir }}\"\n" +
" sort-meta=\"{{ parsed.sortMeta }}\"\n" +
" sort-fields=\"selectSort.parsed.sortFields\"\n" +
" onsort=\"{{ parsed.onsort }}\">\n" +
"</cp-select-sort>");
$templateCache.put("libraries/cjt2/directives/searchDirective.phtml",
"<div class=\"input-group\">\n" +
" <input type=\"text\"\n" +
" id=\"{{parentID}}_input\"\n" +
" class=\"form-control\"\n" +
" placeholder=\"{{placeholder}}\"\n" +
" title=\"{{title}}\"\n" +
" ng-model=\"filterText\"\n" +
" ng-model-options=\"modelOptions\"\n" +
" ng-keyup=\"clear($event)\"\n" +
" prevent-default-on-enter\n" +
" auto-focus=\"{{autofocus}}\"\n" +
" aria-label=\"{{ariaLabelSearch}}\"/>\n" +
" <span class=\"input-group-btn\">\n" +
" <button id=\"{{parentID}}_submit_btn\"\n" +
" class=\"btn btn-default\"\n" +
" ng-click=\"filterText=''\"\n" +
" type=\"button\"\n" +
" ng-attr-aria-label=\"{{ !filterText ? ariaLabelSearch : ariaLabelClear }}\">\n" +
" <span class=\"glyphicon\"\n" +
" ng-class=\"{ 'glyphicon-search' : !filterText, 'glyphicon-remove' :filterText }\"\n" +
" aria-hidden=\"true\">\n" +
" </span>\n" +
" </button>\n" +
" </span>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/searchSettingsPanel.phtml",
"<div>\n" +
" <div id=\"{{ :: searchSettingsID}}\" ng-show=\"displaySettingsPanel\" class=\"panel ng-hide panel-default form-group\">\n" +
" <div class=\"panel-body\">\n" +
" <div class=\"row\">\n" +
" <div ng-repeat=\"(filterKey, searchFilterOption) in options\" class=\"col-md-3\">\n" +
" <div>\n" +
" <h4>\n" +
" {{ :: searchFilterOption.label }}\n" +
" </h4>\n" +
" </div>\n" +
" <div class=\"checkbox\">\n" +
" <label>\n" +
" <input\n" +
" id=\"{{ :: filterKey }}_all\"\n" +
" type=\"checkbox\"\n" +
" ng-disabled=\"all_checked[filterKey]\"\n" +
" ng-checked=\"all_checked[filterKey]\"\n" +
" title=\"[% locale.maketext('Show All') %]\"\n" +
" ng-click=\"set_search_filter_values(filterKey, true)\">\n" +
" {{ :: all_label }}\n" +
" </label>\n" +
" </div>\n" +
" <div class=\"checkbox\" ng-repeat=\"type in searchFilterOption.options\">\n" +
" <label>\n" +
" <input\n" +
" id=\"{{ :: filterKey }}_{{ :: type.value }}\"\n" +
" type=\"checkbox\"\n" +
" ng-model=\"values[filterKey][type.value]\"\n" +
" name=\"{{ :: filterKey }}\"\n" +
" ng-value=\"type.value\"\n" +
" title=\"{{ :: type.description }}\"\n" +
" ng-change=\"update(filterKey)\">\n" +
" <span ng-bind-html=\"type.label\"></span>\n" +
" </label>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <div ng-transclude></div>\n" +
" </div>\n" +
" </div>\n" +
" <div id=\"{{ :: setValuesID}}\" ng-show=\"filteredItemsToDisplay && displaySetValues && !displaySettingsPanel\" class=\"ng-hide form-group\">\n" +
" <span\n" +
" ng-repeat=\"(filterKey, searchFilterOption) in options\"\n" +
" class=\"label label-default\"\n" +
" style=\"font-weight:normal;font-size:11px;margin-right:5px\"\n" +
" id=\"{{ :: setValuesID}}_{{ :: filterKey }}\"\n" +
" ng-if=\"get_filtered_labels(filterKey).length\">\n" +
" <a style=\"font-weight:inherit;padding:0;margin:0;font-size:inherit;color:inherit;\" href=\"javascript:void(0);\" ng-click=\"open_settings()\">\n" +
" {{ :: searchFilterOption.label }}\n" +
" <span id=\"{{ :: setValuesID}}_{{ :: filterKey }}_{{ :: searchFilterOption.label }}\" ng-bind-html=\"get_filtered_labels(filterKey).join(', ')\"></span>\n" +
" </a>\n" +
" <a style=\"font-weight:inherit;padding:0;margin:0;font-size:inherit;color:inherit;\" href=\"javascript:void(0)\" ng-click=\"set_search_filter_values(filterKey, true)\">\n" +
" <span class='fas fa-times fa-sm '></span>\n" +
" </a>\n" +
" </span>\n" +
" </div>\n" +
"</div>");
$templateCache.put("libraries/cjt2/directives/selectSortDirective.phtml",
"<span class=\"cp-select-sort\">\n" +
" <span>{{ label }}</span>\n" +
" <select id=\"select-sort-dropdown_{{ idSuffix }}\"\n" +
" class=\"form-control\"\n" +
" ng-model=\"sortMeta.sortBy\"\n" +
" ng-options=\"fieldObj.field as fieldObj.label for fieldObj in sortFields\"\n" +
" ng-change=\"sort()\">\n" +
" </select>\n" +
" <i id=\"select-sort-direction_{{ idSuffix }}\"\n" +
" class=\"btn btn-default\"\n" +
" ng-class=\"{ true: 'icon-arrow-up', false: 'icon-arrow-down' }[getDir() == 'asc']\"\n" +
" ng-click=\"sort(true)\"\n" +
" ng-attr-title=\"{{ getTitle() }}\">\n" +
" </i>\n" +
"</span>\n" +
"");
$templateCache.put("libraries/cjt2/directives/spinner.phtml",
"<i ng-class=\"[glyph, animate]\" ng-show=\"display\"></i>");
$templateCache.put("libraries/cjt2/directives/statsDirective.phtml",
"<div class=\"stats-widget\" ng-class=\"{'stats-warning': showWarning}\">\n" +
" <div class=\"stats-widget-body\">\n" +
" <div class=\"stats-item\">\n" +
" <span id=\"{{availableID}}\" class=\"stats-huge\">{{availableValue}}</span>\n" +
" <span class=\"stats-title\">{{availableTitle}}</span>\n" +
" </div>\n" +
" <div class=\"stats-item\">\n" +
" <span id=\"{{usedID}}\" class=\"stats-huge\">{{usedValue}}</span>\n" +
" <span class=\"title\">{{usedTitle}}</span>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"stats-widget-footer\" ng-if=\"showUpgradeLink || showWarning\">\n" +
" <a ng-click=\"showWarningDetails()\"\n" +
" ng-if=\"showWarning\"\n" +
" id=\"statsWarning\"\n" +
" class=\"btn btn-sm btn-link\"\n" +
" title=\"{{detailsTooltip}}\">\n" +
" <i class=\"fas fa-exclamation-triangle\"></i>\n" +
" {{viewDetailsText}}\n" +
" </a>\n" +
" <a id=\"{{upgradeLinkID}}\" ng-href=\"{{upgradeLink}}\"\n" +
" ng-if=\"showUpgradeLink && upgradeLink !== ''\"\n" +
" id=\"statsUpgradeLink\"\n" +
" class=\"btn btn-sm btn-link\"\n" +
" target=\"{{upgradeLinkTarget}}\"\n" +
" title=\"{{upgradeTooltip}}\">\n" +
" {{upgradeLinkText}} <i class=\"far fa-arrow-alt-circle-right\"></i>\n" +
" </a>\n" +
" </div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/statsDirective.spec.phtml",
"<stats\n" +
" id=\"statsWidget\"\n" +
" used-id=\"lblUsed\"\n" +
" ng-model=\"accountStats.used\"\n" +
" available-id=\"lblAvailable\"\n" +
" max=\"accountStats.maximum\"\n" +
" upgrade-link-id=\"lblUpgradeLink\"\n" +
" upgrade-link=\"{{upgradeLink}}\"\n" +
" upgrade-link-text=\"REMEMBER ME\"\n" +
" show-upgrade-link=\"{{showUpgradeLink}}\"\n" +
" upgrade-link-target=\"emailUpgrade\"\n" +
" >\n" +
"</stats>\n" +
"");
$templateCache.put("libraries/cjt2/directives/terminal.phtml",
"<div class=\"cp-terminal\">\n" +
" <div class=\"terminal-loading alert alert-info\" ng-if=\"loading\">\n" +
" <i class=\"fas fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n" +
" <span class=\"terminal-opening\" ng-if=\"opening\">{{::openingString}}</span>\n" +
" <span class=\"terminal-waiting\" ng-if=\"!opening\">{{::waitingString}}</span>\n" +
" </div>\n" +
"\n" +
" <p ng-if=\"closed && !windowIsUnloading()\">\n" +
" <button class=\"terminal-reconnect btn btn-primary\" type=\"button\" ng-click=\"connect()\">{{::reconnectString}}</button>\n" +
" </p>\n" +
"\n" +
" <div class=\"terminal-container\" ng-class=\"{ 'disabled': (closed || loading) }\">\n" +
" <div class=\"terminal-title\" ng-bind=\"title\"></div>\n" +
" <div class=\"terminal-xterm\"></div>\n" +
" </div>\n" +
"\n" +
" <div class=\"terminal-exitcode\" ng-if=\"exitCode\">{{::exitCodeString}}: {{exitCode}}</div>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/timePicker.phtml",
"<div>\n" +
" <uib-timepicker\n" +
" id=\"{{parentID}}_timePicker\"\n" +
" ng-model=\"selectedTime\"\n" +
" min=\"options.min\"\n" +
" hour-step=\"hStep\"\n" +
" minute-step=\"mStep\"\n" +
" ng-change=\"onChange(selectedTime)\"\n" +
" show-meridian=\"showMeridian\" ></uib-timepicker>\n" +
"</div>");
$templateCache.put("libraries/cjt2/directives/toggleLabelInfoDirective.phtml",
"<div class=\"toggle-info-label-container\">\n" +
" <label id=\"{{labelID}}\" for=\"{{for}}\">\n" +
" {{labelText}}\n" +
" <label-suffix for=\"{{for}}\" ng-if=\"includeLabelSuffix\"></label-suffix>\n" +
" </label>\n" +
" <a id=\"{{infoIconID}}\"\n" +
" href=\"javascript:void(0)\"\n" +
" class=\"far fa-question-circle text-primary form-info-sign\"\n" +
" ng-click=\"toggleInfoBlock()\"\n" +
" title=\"{{toggleActionTitle}}\">\n" +
" </a>\n" +
"\n" +
" <span id=\"{{infoBlockID}}\" class=\"info-block\" ng-transclude ng-show=\"showInfoBlock\">\n" +
" </span>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/toggleLabelInfoDirective.spec.phtml",
"<toggle-label-info\n" +
" id=\"lblDomain\"\n" +
" for=\"ddlDomain\"\n" +
" label-text=\"Domain\">Additional information about domain</toggle-label-info>\n" +
"\n" +
"<toggle-label-info\n" +
" id=\"lblEmail\"\n" +
" for=\"txtEmail\"\n" +
" label-text=\"Email Address\"\n" +
" show-info-block=\"true\">Additional information about email address</toggle-label-info>\n" +
"\n" +
"<toggle-label-info\n" +
" id=\"lblIDVerification\"\n" +
" label-id=\"lblVerification\"\n" +
" info-icon-id=\"imgQuestionMark\"\n" +
" info-block-id=\"infoBlockID\"\n" +
" for=\"txtIDVerification\"\n" +
" label-text=\"IDVerification\"\n" +
" show-info-block=\"true\">ID Verification</toggle-label-info>\n" +
"\n" +
"<toggle-label-info\n" +
" id=\"lblToggleEvent\"\n" +
" for=\"txtSomething\"\n" +
" label-text=\"Blah\"\n" +
" on-toggle=\"onToggle(show)\">Barg</toggle-label-info>");
$templateCache.put("libraries/cjt2/directives/toggleSortDirective.phtml",
"<a class=\"sort-link\" ng-click=\"sort(sortValue)\" href=\"javascript:void(0)\">\n" +
" <span ng-transclude></span>\n" +
" <span ng-hide=\"sortMeta.sortBy !== sortField\">\n" +
" <i ng-class=\"{true: 'icon-arrow-up', false: 'icon-arrow-down'}[getDir() == 'asc']\"\n" +
" ng-attr-title=\"{{ getTitle() }}\"></i>\n" +
" </span>\n" +
"</a>");
$templateCache.put("libraries/cjt2/directives/toggleSwitch.phtml",
"<div role=\"switch\"\n" +
" class=\"cjt2-toggle-switch toggle-switch-wrapper\"\n" +
" tabindex=\"0\"\n" +
" aria-checked=\"{{ get_aria_value() }}\"\n" +
" aria-label=\"{{ariaLabel}}\"\n" +
" ng-click=\"toggle_status()\"\n" +
" ng-keyup=\"handle_keyup($event)\"\n" +
" ng-keydown=\"handle_keydown($event)\"\n" +
" ng-class=\"{\n" +
" disabled: (changing_status || isDisabled),\n" +
" 'switch-off': !ngModel,\n" +
" 'switch-on': ngModel,\n" +
" 'no-label': noLabel\n" +
" }\">\n" +
" <spinner\n" +
" ng-if=\"!noSpinner && spinnerPosition === 'left'\"\n" +
" id=\"{{spinnerId}}\"\n" +
" glyph-class=\"fas fa-sync toggle-switch-updating-indicator toggle-switch-updating-indicator-left\">\n" +
" </spinner>\n" +
" <span ng-if=\"labelPosition == 'left' && !changing_status\" class=\"toggle-switch-label toggle-switch-label-left\">\n" +
" {{ ngModel ? enabledLabel : disabledLabel}}\n" +
" </span>\n" +
" <div\n" +
" class=\"toggle-switch\"\n" +
" ng-class=\"{disabled: (changing_status || isDisabled)}\">\n" +
" <div id=\"{{parentID}}_toggle_visual\" class=\"toggle-switch-animate\" ng-class=\"{'switch-off': !ngModel, 'switch-on': ngModel}\">\n" +
" <span class=\"switch-left\"></span>\n" +
" <span class=\"knob\"></span>\n" +
" <span class=\"switch-right\"></span>\n" +
" </div>\n" +
" </div>\n" +
" <span ng-if=\"labelPosition == 'right' && !changing_status\" class=\"toggle-switch-label toggle-switch-label-right\">\n" +
" {{ ngModel ? enabledLabel : disabledLabel}}\n" +
" </span>\n" +
" <spinner\n" +
" ng-if=\"!noSpinner && spinnerPosition === 'right'\"\n" +
" id=\"{{spinnerId}}\"\n" +
" glyph-class=\"fas fa-sync toggle-switch-updating-indicator toggle-switch-updating-indicator-right\">\n" +
" </spinner>\n" +
"</div>\n" +
"");
$templateCache.put("libraries/cjt2/directives/triStateCheckbox.phtml",
"<input type=\"checkbox\" ng-model=\"master\" ng-click=\"masterChange()\">\n" +
"");
$templateCache.put("libraries/cjt2/directives/validationContainer.phtml",
"<ul class=\"validation-container ng-hide\" ng-show=\"canShow()\">\n" +
" <li class=\"validation validation-error\" ng-repeat=\"message in aggregateMessages() track by message.id\" ng-if=\"canShowItem(message.validatorName)\">\n" +
" <i class=\"fas fa-exclamation-circle\"></i>\n" +
" <span id=\"{{ message.id }}\" class=\"validation-message\">\n" +
" <span ng-bind-html=\"message.message\"></span>\n" +
" </span>\n" +
" </li>\n" +
" <li ng-transclude></li>\n" +
"</ul>\n" +
"");
$templateCache.put("libraries/cjt2/directives/validationContainer.spec.phtml",
"<form name=\"theFormStandard\" id=\"theFormStandard\">\n" +
" <input type=\"text\" id=\"theTextStandard\" name=\"theTextStandard\" ng-model=\"mytext\" required ng-pattern=\"/^[A-Za-z]+$/\">\n" +
" <ul validation-container field-name=\"theTextStandard\" role=\"alert\">\n" +
" <li validation-item field-name=\"theTextStandard\" validation-name=\"required\" id=\"valTextRequired\">\n" +
" Please enter some text\n" +
" </li>\n" +
" <li validation-item field-name=\"theTextStandard\" validation-name=\"pattern\" id=\"valTextPattern\">\n" +
" Should only be alphabets\n" +
" </li>\n" +
" </ul>\n" +
"</form>\n" +
"\n" +
"<form name=\"theFormAuto\" id=\"theFormAuto\">\n" +
" <input type=\"text\" id=\"theTextAuto\" name=\"theTextAuto\" ng-model=\"mytext\" length=\"10\">\n" +
" <ul validation-container field-name=\"theTextAuto\" role=\"alert\">\n" +
" </ul>\n" +
"</form>\n" +
"\n" +
"<form name=\"theFormManual\" id=\"theFormManual\">\n" +
" <input type=\"text\" id=\"theTextManual\" name=\"theTextManual\" ng-model=\"mytext\" min-length=\"3\" max-length=\"5\">\n" +
" <ul validation-container field-name=\"theTextManual\" role=\"alert\" manual>\n" +
" <li validation-item field-name=\"theTextManual\" validation-name=\"minLength\" id=\"valTextMinLength\">\n" +
" </li>\n" +
" <li validation-item field-name=\"theTextManual\" validation-name=\"maxLength\" id=\"valTextMaxLength\">\n" +
" </li>\n" +
" </ul>\n" +
"</form>");
$templateCache.put("libraries/cjt2/directives/validationItem.phtml",
"<li class=\"validation validation-error ng-hide\" ng-show=\"canShow()\">\n" +
" <i class=\"fas fa-exclamation-circle\" ng-show=\"showIcon()\"></i>\n" +
" <span id=\"{{id}}\" class=\"validation-message\">\n" +
" <span ng-show=\"!showIcon()\" ng-class=\"[ prefixClass ]\"></span>{{print()}}<span ng-transclude></span>\n" +
" </span>\n" +
"</li>");
$templateCache.put("libraries/cjt2/directives/validationItem.spec.phtml",
"<form name=\"theForm\" id=\"theForm\">\n" +
" <input type=\"text\" id=\"theText\" name=\"theText\" ng-model=\"mytext\" required ng-pattern=\"/^[A-Za-z]+$/\">\n" +
" <ul validation-container field-name=\"theText\" role=\"alert\">\n" +
" <li validation-item field-name=\"theText\" validation-name=\"required\" id=\"valTextRequired\">\n" +
" Please enter some text\n" +
" </li>\n" +
" <li validation-item field-name=\"theText\" validation-name=\"pattern\" id=\"valTextPattern\">\n" +
" Should only be alphabets\n" +
" </li>\n" +
" </ul>\n" +
"</form>\n" +
"\n" +
"<form name=\"theFormNoIcon\" id=\"theFormNoIcon\">\n" +
" <style>\n" +
" .bullet::before {\n" +
" content: \"•\";\n" +
" padding-right: 10px;\n" +
" font-weight: bold;\n" +
" font-size: larger;\n" +
" }\n" +
" </style>\n" +
" <input type=\"text\" id=\"theTextNoIcon\" name=\"theTextNoIcon\" ng-model=\"mytext\" required>\n" +
" <ul validation-container field-name=\"theTextNoIcon\" role=\"alert\">\n" +
" <li validation-item field-name=\"theTextNoIcon\" validation-name=\"required\" id=\"valTextRequiredStandard\">\n" +
" Standard\n" +
" </li>\n" +
" <li validation-item field-name=\"theTextNoIcon\" validation-name=\"required\" id=\"valTextRequiredNoIcon\" no-icon>\n" +
" No Icon\n" +
" </li>\n" +
" <li validation-item field-name=\"theTextNoIcon\" validation-name=\"required\" id=\"valTextRequiredCustomIcon\" no-icon prefix-class=\"bullet\">\n" +
" Change Icon\n" +
" </li>\n" +
" </ul>\n" +
"</form>");
$templateCache.put("libraries/cjt2/directives/whm/userDomainListDirective.phtml",
"<div class=\"row user-domain-list-directive account-selector-area\" ng-class=\"{'user-selected':selectedUser,'user-summary-showing':selectedUser && selectedUserObj.summary}\">\n" +
" <!--ng-form name=\"{{parentID}}Form\" -->\n" +
" <div class=\"col-md-12\">\n" +
" <div class=\"form-group\">\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-12\">\n" +
" <search id=\"{{ :: parentID }}_Search\" title=\"{{ :: getString('Search for a user or a domain.') }}\" ng-model=\"meta.filterValue\" ng-change=\"fetch()\"></search>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"row quick-filter-container\">\n" +
" <div class=\"col-xs-12\">\n" +
" <quick-filters id=\"{{ :: parentID }}_QuickFilters\" title=\"{{ :: getString('Search By:') }}\" active=\"quickFilterValue\" on-filter-change=\"fetch()\">\n" +
" <quick-filter-item id=\"quickFilterItem_all\" title=\"{{ :: getString('Search by both users and domains.') }}\" value=\"\">{{ :: getString('All') }}</quick-filter-item>\n" +
" <quick-filter-item id=\"quickFilterItem_users\" title=\"{{ :: getString('Search by users only.') }}\" value=\"user\">{{ :: getString('Users') }}</quick-filter-item>\n" +
" <quick-filter-item id=\"quickFilterItem_domains\" title=\"{{ :: getString('Search by domains only.') }}\" value=\"domain\">{{ :: getString('Domains') }}</quick-filter-item>\n" +
" </quick-filters>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
"\n" +
" <div class=\"form-group\">\n" +
" <div class=\"row\">\n" +
" <div class=\"col-md-12\">\n" +
" <div class=\"panel panel-default\">\n" +
" <div class=\"panel-body account-table-panel-body\" id=\"{{parentID}}_list\" ui-scroll-viewport >\n" +
" <table class=\"table table-striped fixed-width-table\" summary=\"{{ :: getString('A list of users and domains from which to choose.') }}\">\n" +
" <colgroup>\n" +
" <col style=\"width:30px\">\n" +
" <col >\n" +
" </colgroup>\n" +
" <tbody>\n" +
" <tr id=\"{{ :: parentID }}_Row_{{ :: domain.user }}\" class=\"domain_row no-animate\" ui-scroll=\"domain in datasource\" adapter=\"uiScrollAdapter\" start-index=\"0\" ng-click=\"userSelected(domain)\" ng-class=\"{selected:selectedUser == domain.user, 'callout callout-warning edit-locked text-muted':domain.editLocked}\">\n" +
" <td class=\"account-table-radio text-center\">\n" +
" <input\n" +
" type=\"radio\"\n" +
" id=\"{{ :: parentID }}_Row_{{ :: domain.user }}_radio\"\n" +
" ng-required=\"required\"\n" +
" name=\"user\"\n" +
" ng-disabled=\"domain.editLocked\"\n" +
" aria-label=\"{{ :: getRadioAriaLabel(domain.user, domain.domain) }}\"\n" +
" title=\"{{ :: getRadioAriaLabel(domain.user, domain.domain) }}\"\n" +
" value=\"{{ ::domain.user }}\"\n" +
" ng-model=\"selectedUser\" />\n" +
" </td>\n" +
" <td class=\"account-table-title\">\n" +
" <div>\n" +
" <div class=\"cutoff_with_ellipses\"><label for=\"{{ :: parentID }}_Row_{{ :: domain.user }}_radio\" class=\"left-to-right\" ng-bind-html=\"domain.decoratedTitle\"></label></div>\n" +
" </div>\n" +
" <div ng-if=\"domain.editLocked\">\n" +
" <div>{{ domain.editLockedMessage }}</div>\n" +
" </div>\n" +
" <div ng-if=\"!hideAccountSummary && domain.user !== 'root' && !domain.without_domain && domain.selected\">\n" +
" <table ng-if=\"domain.summary\" class=\"table responsive-table account-summary-table fixed-width-table\" summary=\"{{ :: domain.summaryTableSummary }}\">\n" +
" <thead>\n" +
" <tr>\n" +
" <th>{{ :: getString('[asis,IP] Address') }}</th>\n" +
" <th>{{ :: getString('Owner') }}</th>\n" +
" <th>{{ :: getString('Email Addresses') }}</th>\n" +
" <th>{{ :: getString('Start Date') }}</th>\n" +
" <th>{{ :: getString('Theme') }}</th>\n" +
" <th>{{ :: getString('Package') }}</th>\n" +
" <th>{{ :: getString('Disk Usage') }}</th>\n" +
" </tr>\n" +
" </thead>\n" +
" <tbody>\n" +
" <tr>\n" +
" <td class=\"cutoff_with_ellipses\" data-title=\"{{ :: getString('[asis,IP] Address') }}\">{{ ::domain.summary.ip }}</td>\n" +
" <td class=\"cutoff_with_ellipses\" data-title=\"{{ :: getString('Owner') }}\">{{ ::domain.summary.owner }}</td>\n" +
" <td class=\"cutoff_with_ellipses\" data-title=\"{{ :: getString('Email Addresses') }}\">\n" +
" <div ng-repeat=\"email in domain.summary.emails track by $index\">\n" +
" <a class=\"account-summary-email\" href=\"mailto:{{::email}}\" title=\"{{::email}}\">\n" +
" {{ ::email }} <span class='fas fa-external-link-alt'></span>\n" +
" </a>\n" +
" </div>\n" +
" <span ng-if=\"domain.summary.emails.length == 0\">*{{ :: getString(\"unknown\") }}*</span>\n" +
" </td>\n" +
" <td class=\"cutoff_with_ellipses\" data-title=\"{{ :: getString('Start Date') }}\">{{ ::domain.summary.localStartdate }}</td>\n" +
" <td class=\"cutoff_with_ellipses\" data-title=\"{{ :: getString('Theme') }}\">{{ ::domain.summary.theme }}</td>\n" +
" <td class=\"cutoff_with_ellipses\" data-title=\"{{ :: getString('Package') }}\">\n" +
" <button type=\"button\" class=\"btn btn-link\" ng-click=\"viewPackageDetails(domain)\">\n" +
" {{ ::domain.summary.plan }}\n" +
" <span ng-if=\"domain.show_package\" class='fas fa-eye-slash fa-sm'></span>\n" +
" </button>\n" +
" <span ng-if=\"loadingUserPackage[domain.user]\"><i class=\"fas fa-spinner fa-spin\"></i></span>\n" +
" </td>\n" +
" <td class=\"cutoff_with_ellipses\" data-title=\"{{ :: getString('Disk Usage') }}\">{{ ::domain.diskInfo }}</td>\n" +
" </tr>\n" +
" </tbody>\n" +
" </table>\n" +
" <div ng-if=\"domain.requestingSummary\" class=\"account-summary-loading\">\n" +
" <span><i class=\"fas fa-spinner fa-spin\"></i> {{ :: getString('Loading Account Summary …') }}</span>\n" +
" </div>\n" +
" <div ng-if=\"!domain.requestingSummary && !domain.summary\" class=\"account-summary-error\">\n" +
" {{ :: getString('The system failed to load the account summary.') }}\n" +
" </div>\n" +
" </div>\n" +
" </td>\n" +
" </tr>\n" +
" </tbody>\n" +
" </table>\n" +
" <div ng-if=\"filteredDomains.length === 0\" class=\"callout callout-info no-results-msg\">\n" +
" <span id=\"{{ :: parentID }}_noResultsMessage\" ng-bind=\"noResultsMessage()\"></span>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <ng-transclude></ng-transclude>\n" +
" <!--/ng-form-->\n" +
" <script type=\"text/ng-template\" id=\"package-details.ptt\">\n" +
" <div class=\"modal-header\">\n" +
" <h4 class=\"modal-title\" id=\"modal-title\" ng-bind=\"getTitle()\"></h4>\n" +
" </div>\n" +
" <div class=\"modal-body\" id=\"modal-body\">\n" +
" <table class=\"table table-sm account-summary-table\">\n" +
" <tbody>\n" +
" <tr>\n" +
" <td>{{ :: getString('Bandwidth') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'BWLIMIT') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('CGI Access?') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'CGI') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('cPanel Theme') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'CPMOD') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('Feature List') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'FEATURELIST') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('Shell access?') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'HASSHELL') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('Dedicated IP?') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'IP') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('FTP Accounts') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'MAXFTP') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('Email Lists') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'MAXLST') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('Email Accounts') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'MAXPOP') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('Databases') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'MAXSQL') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('Subdomains') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'MAXSUB') }}</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>{{ :: getString('Quota') }}</td>\n" +
" <td>{{ getPackageValue(packageDetails,'QUOTA') }}</td>\n" +
" </tr>\n" +
" </tbody>\n" +
" </table>\n" +
" </div>\n" +
" <div class=\"modal-footer\">\n" +
" <button class=\"btn btn-primary\" type=\"button\" ng-click=\"$close()\">Done</button>\n" +
" </div>\n" +
" </script>\n" +
"</div>\n" +
"");
}]);
}
);
/*
# cjt/decorators/growlDecorator.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/decorators/growlDecorator',[
"angular",
"cjt/core",
"angular-growl",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT) {
// Set up the module in the cjt2 space
var module = angular.module("cjt2.decorators.growlDecorator", ["angular-growl"]);
module.config(["$provide", function($provide) {
// make the growl use our template
$provide.decorator("growlDirective", ["$delegate", function($delegate) {
var RELATIVE_PATH = "libraries/cjt2/directives/growl.phtml";
var ngGrowlDirective = $delegate[0];
// use our template
ngGrowlDirective.templateUrl = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
return $delegate;
}]);
// add ids to the growl messages
$provide.decorator("growlMessages", ["$delegate", function($delegate) {
var counter = 0;
var PREFIX = "growl_";
// save the original addMessage call
var addMessageFn = $delegate.addMessage;
var addId = function() {
var args = [].slice.call(arguments);
// first arg should have the message object
// add a unique id to the messge object
args[0].id = PREFIX + args[0].referenceId + "_" + (++counter);
// call the original addMessage function and pass the service object as 'this'
return addMessageFn.apply($delegate, args);
};
$delegate.addMessage = addId;
return $delegate;
}]);
}]);
}
);
/*
# cjt/decorators/growlAPIReporter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* One possible display-layer complement to the APICatcher service. This
module will display all failures from APICatcher in the UI via growl
notifications and the browser console.
It is assumed (for now) that this is as simple as just showing the
response’s .error value; for batch responses, we probably want to be
more detailed eventually (TODO).
Ultimately, we’d ideally even create *typed* failure response objects;
these could encapsulate their own logic for generating a string (or
even raw markup??) to report failures.
See APICatcher for more information.
*/
/* global define: false */
define(
'cjt/decorators/growlAPIReporter',[
"angular",
"lodash",
"cjt/services/APIFailures",
"cjt/decorators/growlDecorator",
],
function(angular, _) {
"use strict";
// Set up the module in the cjt2 space
var module = angular.module("cjt2.decorators.growlAPIReporter", ["cjt2.decorators.growlDecorator"]);
module.config(["$provide", function($provide) {
$provide.decorator("growl", ["$delegate", "APIFailures", "$log", function(growl, apifail, $log) {
function _reportMessages(messages) {
messages.forEach(function(message) {
$log.warn(message.content);
if (message.type === "danger") {
return growl.error(_.escape(message.content));
} else if (message.type === "warning") {
return growl.warning(_.escape(message.content));
}
});
}
apifail.register(_reportMessages);
return growl;
}]);
}]);
}
);
/*
# cjt/decorators/paginationDecorator.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/decorators/paginationDecorator',[
"angular",
"cjt/core",
"cjt/util/locale",
"uiBootstrap",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, LOCALE) {
// Set up the module in the cjt2 space
var module = angular.module("cjt2.decorators.paginationDecorator", ["ui.bootstrap.pagination"]);
module.config(["$provide", function($provide) {
// Extend the ngModelDirective to interpolate its name attribute
$provide.decorator("uibPaginationDirective", ["$delegate", function($delegate) {
var RELATIVE_PATH = "libraries/cjt2/directives/pagination.phtml";
var uiPaginationDirective = $delegate[0];
/**
* Update the ids in the page collection
*
* @method updateIds
* @param {Array} pages
* @param {String} id Id of the directive, used as a prefix
*/
var updateIds = function(pages, id) {
if (!pages) {
return;
}
pages.forEach(function(page) {
page.id = id + "_" + page.text;
});
};
var updateAriaLabel = function(pages) {
if (!pages) {
return;
}
pages.forEach(function(page) {
page.ariaLabel = LOCALE.maketext("Go to page “[_1]”.", page.text);
});
};
uiPaginationDirective.templateUrl = CJT.buildFullPath(RELATIVE_PATH);
// Extend the page model with the id field.
var linkFn = uiPaginationDirective.link;
uiPaginationDirective.compile = function() {
return function(scope, element, attrs, ctrls) {
var paginationCtrl = ctrls[0];
linkFn.apply(this, arguments);
scope.parentId = attrs.id;
scope.ariaLabels = {
title: LOCALE.maketext("Pagination"),
firstPage: LOCALE.maketext("Go to first page."),
previousPage: LOCALE.maketext("Go to previous page."),
nextPage: LOCALE.maketext("Go to next page."),
lastPage: LOCALE.maketext("Go to last page."),
};
var render = paginationCtrl.render;
paginationCtrl.render = function() {
render.apply(paginationCtrl);
updateIds(scope.pages, scope.parentId);
updateAriaLabel(scope.pages);
};
};
};
return $delegate;
}]);
}]);
}
);
/*
* cjt/decorators/uibTypeaheadDecorator.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* eslint-env amd */
define(
'cjt/decorators/uibTypeaheadDecorator',[
"angular",
"uiBootstrap",
],
function(angular) {
"use strict";
angular
.module("cjt2.decorators.uibTypeaheadDecorator", ["ui.bootstrap.typeahead"])
.config(["$provide", function($provide) {
$provide.decorator("uibTypeaheadDirective", ["$delegate", function($delegate) {
var directive = $delegate[0];
var originalLinkFn = directive.link;
directive.compile = function() {
return function(scope, elem, attrs) {
originalLinkFn.apply(directive, arguments);
attrs.$set("role", "combobox");
};
};
return $delegate;
}]);
}]);
}
);
/*
# cjt/diag/routeDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/diag/routeDirective',[
"angular",
"cjt/core",
"cjt/templates"
],
function(angular, CJT) {
var RELATIVE_PATH = "libraries/cjt2/diag/routeDirective.phtml";
var module = angular.module("cjt2.diag.route", [
"cjt2.templates"
]);
module.controller("diagRouteController", [
"$scope",
"$routeParams",
"$route",
"$window",
"$location",
function( $scope, $routeParams, $route, $window, $location) {
$scope.$location = $location;
$scope.$window = $window;
$scope.$route = $route;
$scope.$routeParams = $routeParams;
}
]);
module.directive("diagRoute", [ function() {
return {
restrict: "EA",
replace: true,
scope: true,
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
controller: "diagRouteController",
link: function(scope, element, attr) {}
};
}]);
}
);
/*
# cjt/directives/spinnerDirective.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
// ------------------------------------------------------------
// Developer notes:
// ------------------------------------------------------------
// The concept for this construct was derived from:
// angular-spinner version 0.2.1
// License: MIT.
// Copyright (C) 2013, Uri Shaked.
// Sources:
// http://ngmodules.org/modules/angular-spinner
// Used with permission.
// ------------------------------------------------------------
define(
'cjt/directives/spinnerDirective',[
"angular",
"lodash",
"cjt/core",
"cjt/util/parse",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, _, CJT, parse) {
"use strict";
var module = angular.module("cjt2.directives.spinner", [
"cjt2.templates"
]);
/**
* Service that runs the spinners in a user interface.
*
* @example
*
* Start all spinners:
*
* spinnerAPI.start();
*
* Stop all spinners:
*
* spinnerAPI.stop();
*
* Start a spinner by id:
*
* spinnerAPI.start("top");
*
* Stop a spinner by id:
*
* spinnerAPI.stop("top");
*
* Start a spinner by group name:
*
* spinnerAPI.stargGroup("loading");
*
* Stop a spinner by group name:
*
* spinnerAPI.stopGroup("loading");
*/
module.factory("spinnerAPI", function() {
/**
* Collection of active spinners. Must register then with the semi-private
* _add() api.
* @type {Object}
*/
var spinners = {};
/**
* Catchup queue. Actions are added here for spinners that can not be
* found in the spinners collection and then run once those spinners appear.
* @type {Array}
*/
var queue = [];
/**
* Flush out any outstanding actions in the queue.
*
* @private
* @method _flushQueue
* @return {[type]} [description]
*/
var _flushQueue = function() {
queue = [];
};
/**
* Make an action from the arguments
*
* @private
* @_makeAction
* @param {String} fnName Name of the spinner method to call.
* @param {String} id Optional spinner id
* @param {String} group Optional spinner group name
* @param {Array} args Array, usually just the parameters passed to the caller so it a psudo array.
* @return {Object} Packaged action object.
*/
var _makeAction = function(fnName, id, group, args) {
return {
fnName: fnName,
args: args,
id: id,
group: group
};
};
/**
* Runs the requested action
*
* @private
* @_runAction
* @param {Object} action
*/
var _runAction = function(action) {
switch (action.fnName) {
case "start":
_start.apply(null, action.args);
break;
case "startGroup":
_startGroup.apply(null, action.args);
break;
case "stop":
_stop.apply(null, action.args);
break;
case "stopGroup":
_stopGroup.apply(null, action.args);
break;
case "kill":
_kill.apply(null, action.args);
break;
case "killGroup":
_killGroup.apply(null, action.args);
break;
}
};
/**
* Process the queue of pending catchup items
*
* @private
* @_processQueue
*/
var _processQueue = function() {
var action;
while ( ( action = queue.shift() ) ) {
if ( (action.id && _has(action.id) ) ||
(action.group && _hasGroup(action.group))
) {
_runAction(action);
}
}
};
/**
* Enqueue an action for future processing
*
* @private
* @_enqueue
* @param {Object} action Action to perform
*/
var _enqueue = function(action) {
queue.push(action);
};
/**
* Accounting helper to start a spinner.
*
* @method _startSpin
* @private
* @param {Spinner} spinner
* @param {Boolean} show
*/
var _startSpin = function(spinner, show) {
var className = spinner.scope.spinClass;
if (className) {
spinner.element.addClass(className);
}
spinner.scope.display = show;
spinner.scope.running = true;
};
/**
* Accounting helper to stop a spinner.
*
* @method _startSpin
* @private
* @param {Spinner} spinner
* @param {Boolean} show
* @param {String} [className]
*/
var _stopSpin = function(spinner, show) {
var className = spinner.scope.spinClass;
if (className) {
spinner.element.removeClass(className);
}
spinner.scope.display = show;
spinner.scope.running = false;
};
/**
* Test if the spinner is registered with the API.
*
* @method has
* @param {String} [id] Optional Identifier for the spinner. If not passed then reports is any spinners are registered.
* @return {Boolean} true if the spinner exists, false otherwise.
*/
var _has = function(id) {
if (!id) {
return spinners.length > 0;
} else {
return !!spinners[id];
}
};
/**
* Test if a spinner from the group is available.
*
* @method hasGroup
* @param {String} className CSS class to look for...
* @return {Boolean} true if the spinner exists, false otherwise.
*/
var _hasGroup = function(className) {
if (!className) {
return false;
} else {
var keys = _.keys(spinners);
for (var i = keys.length; i > -1; i--) {
var key = keys[i];
if (key) {
var spinner = spinners[key];
if (spinner.element.hasClass(className)) {
return true;
}
}
}
return false;
}
};
/**
* Stop the specified spinner, if id is not passed, stops all spinners.
*
* @method stop
* @param {String} id Identifier for the spinner.
* @param {Boolean} [show] show state after the stop. Defaults to false meaning the element is hidden when stopped.
*/
var _stop = function(id, show) {
show = !_.isUndefined(show) ? show : false;
if (!id) {
angular.forEach(spinners, function(spinner) {
_stopSpin(spinner, show);
});
} else {
var spinner = spinners[id];
if (spinner) {
_stopSpin(spinner, show);
}
}
};
/**
* Stop the group of spinners with the designated CSS class.
*
* @method stopGroup
* @param {String} className CSS class to look for...
* @param {Boolean} [show] show state after the stop. Defaults to false meaning the element is hidden when stopped.
*/
var _stopGroup = function(className, show) {
show = !_.isUndefined(show) ? show : false;
angular.forEach(spinners, function(spinner) {
if (spinner.element.hasClass(className)) {
_stopSpin(spinner, show);
}
});
};
/**
* Starts the specified spinner, if id is not passed, starts all the spinners
*
* @method start
* @param {String} id Identifier for the spinner.
* @param {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
*/
var _start = function(id, show) {
show = !_.isUndefined(show) ? show : true;
if (!id) {
angular.forEach(spinners, function(spinner) {
_startSpin(spinner, true);
});
} else {
var spinner = spinners[id];
if (spinner) {
_startSpin(spinner, true);
}
}
};
/**
* Start the group of spinners with the designated CSS class.
*
* @method startGroup
* @param {String} className CSS class to look for...
* @param {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
*/
var _startGroup = function(className, show) {
show = !_.isUndefined(show) ? show : true;
angular.forEach(spinners, function(spinner) {
if (spinner.element.hasClass(className)) {
_startSpin(spinner, show, "fa-spin");
}
});
};
/**
* Kills the specified spinner, if id is not passed, kills all the spinners
*
* @private
* @method _kill
* @param {String} id Identifier for the spinner.
*/
var _kill = function(id) {
var spinner;
if (!id) {
var keys = _.keys(spinners);
for (var i = keys.length; i > -1; i--) {
var key = keys[i];
if (key) {
_stopSpin(spinners[key], false, "fa-spin");
spinners[key] = null;
delete spinners[key];
}
}
} else {
if (id) {
spinner = spinners[id];
if (spinner) {
_stopSpin(spinner, false, "fa-spin");
spinners[id] = null;
delete spinners[id];
} else {
// check the queue instead
for (var j = queue.length - 1; j >= 0; j--) {
if (queue[j].id === id) {
queue.splice(j, 1);
}
}
}
}
}
};
/**
* Kill a group of spinners by classname
*
* @private
* @method _killGroup
* @param {String} className CSS class to look for...
*/
var _killGroup = function(className) {
var keys = _.keys(spinners);
var found = false;
for (var i = keys.length; i > -1; i--) {
var key = keys[i];
if (key) {
var spinner = spinners[key];
if (spinner.element.hasClass(className)) {
_stopSpin(spinner, false, "fa-spin");
spinners[key] = null;
delete spinners[key];
found = true;
}
}
}
if (!found) {
// Check the queue instead
for (var j = queue.length - 1; j >= 0; j--) {
if (queue[j].className === className) {
queue.splice(j, 1);
}
}
}
};
return {
spinners: spinners,
/**
* Add a spinner management object to the system.
*
* @method add
* @protected
* @param {String} id Identifier for the spinner.
* @param {Element} element Wrapped element.
* @param {Boolean} autoStart Start the animation if true, do not start the animation if false or undefined.
* @param {Boolean} show Show state after the stop. Defaults to false meaning the element is hidden when stopped.
*/
_add: function(id, element, autoStart, show, scope) {
var spinner = spinners[id] = {
id: id,
element: element,
scope: scope
};
if (autoStart) {
_startSpin(spinner, show);
} else {
_stopSpin(spinner, show);
}
// Try to catch up now that there is a new one added
_processQueue();
},
has: _has,
hasGroup: _hasGroup,
/**
* Starts the specified spinner, if id is not passed, starts all the spinners. If id is passed, but the spinner has not
* been added yet, the request will be queued.
*
* @method start
* @param {String} id Identifier for the spinner.
* @param {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
*/
start: function(id, show) {
if (id && !_has(id)) {
_enqueue(_makeAction("start", id, null, arguments));
} else {
_start(id, show);
}
},
/**
* Start the group of spinners with the designated CSS class. If className is passed, but the spinner has not
* been added yet, the request will be queued.
*
* @method startGroup
* @param {String} className CSS class to look for...
* @param {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
*/
startGroup: function(className, show) {
if (className && !_hasGroup(className)) {
_enqueue(_makeAction("startGroup", null, className, arguments));
} else {
_startGroup(className, show);
}
},
/**
* Stop the specified spinner, if id is not passed, stops all spinners. If id is passed, but the spinner has not
* been added yet, the request will be queued.
*
* @method stop
* @param {String} id Identifier for the spinner.
* @param {Boolean} [show] show state after the stop. Defaults to false meaning the element is hidden when stopped.
*/
stop: function(id, hide) {
if (id && !_has(id)) {
_enqueue(_makeAction("stop", id, null, arguments));
} else {
_stop(id, hide);
}
},
/**
* Stop the group of spinners with the designated CSS class.
* Stop the group of spinners with the designated CSS class. If className is passed, but the spinner has not
* been added yet, the request will be queued.
*
* @method startGroup
* @param {String} className CSS class to look for...
* @param {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
*/
stopGroup: function(className, hide) {
if (className && !_hasGroup(className)) {
_enqueue(_makeAction("stopGroup", null, className, arguments));
} else {
_stopGroup(className, hide);
}
},
/**
* Kills the specified spinner, if id is not passed, kills all the spinners. If id is passed, but the spinner has not
* been added yet, the request will be queued.
*
* @method kill
* @param {String} id Identifier for the spinner.
*/
kill: function(id) {
if (id && !_has(id)) {
_enqueue(_makeAction("kill", id, null, arguments));
} else {
_kill(id);
}
},
/**
* Kill a group of spinners by CSS class name. If className is passed, but the spinner has not
* been added yet, the request will be queued.
*
* @method killGroup
* @param {String} className CSS class to look for...
*/
killGroup: function(className) {
if (className && !_hasGroup(className)) {
_enqueue(_makeAction("killGroup", null, className, arguments));
} else {
_killGroup(className);
}
},
/**
* Flush out any outstanding actions in the action queue
*
* @method flushQueue
*/
flush: _flushQueue
};
});
/**
* Directive that generates a spinner in the user interface.
*
* @attribute {Object} spinner configuration for the spinner.
* @attribute {Boolean} [cpAutostart] optional starts the spinner automatically if true, doesn't if false. Defaults to false.
* @attribute {Boolean} [cpShow] optional shows the glyph if true, doesn't if false. Defaults to false.
* @attribute {Boolean} [groupClass] optional css group name for use with startGroup and stopGroup API.
* @attribute {Boolean} [glyphClass] optional css class to define the glyph to use in the directive.
* @attribute {Boolean} [spinClass] optional css class to define how to start the animation.
* @example
* Basic spinner
* <div spinner></div>
*
* Non-auto-start Spinner, invisible while not running:
*
* <div spinner cp-autostart="false"></div>
*
* Visible while not running:
*
* <div spinner cp-show="true"></div>
*
* Making two spinners part of a group:
*
* <div spinner group-class="loading" id="top"></div>
* <div spinner group-class="loading" id="bottom"></div>
*
*/
module.directive("spinner", ["spinnerAPI",
function(spinnerAPI) {
var ct = 0;
var RELATIVE_PATH = "libraries/cjt2/directives/spinner.phtml";
return {
scope: true,
restrict: "EA",
replace: true,
controller: ["$scope",
function($scope) {
$scope.api = spinnerAPI;
$scope.display = false;
$scope.running = false;
}
],
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
compile: function(element, attrs) {
return {
pre: function(scope, element, attrs) {
if (_.isUndefined(attrs.glyphClass)) {
attrs.glyphClass = "fas fa-spinner fa-2x";
}
if (_.isUndefined(attrs.spinClass)) {
attrs.spinClass = "fa-spin";
}
if (_.isUndefined(attrs.id) || attrs.id === "") {
attrs.id = "spinner_" + ct++;
}
},
post: function(scope, element, attrs) {
var show = !_.isUndefined(attrs.cpShow) ? parse.parseBoolean(attrs.cpShow) : false;
var autoStart = !_.isUndefined(attrs.cpAutostart) ? parse.parseBoolean(attrs.cpAutostart) : false;
var id = attrs.id;
element.attr("id", id);
// These are used to group spinners into groups
// for startGroup and stopGroup API calls. Not needed
// if you don't intend to use those api calls.
var groupClass = attrs.groupClass;
if (groupClass) {
if (!element.hasClass(groupClass)) {
element.addClass(groupClass);
}
}
var glyphClass = attrs.glyphClass;
if (glyphClass) {
if (!element.hasClass(glyphClass)) {
element.addClass(glyphClass);
}
}
// Setup the scope
scope.id = id;
scope.spinClass = attrs.spinClass;
scope.$on("$destroy", function() {
scope.api.kill(scope.id);
});
scope.api._add(scope.id, element, autoStart, show, scope);
}
};
}
};
}]);
}
);
/*
# cjt/directives/actionButtonDirective.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/actionButtonDirective',[
"angular",
"cjt/core",
"cjt/util/test",
"cjt/directives/spinnerDirective",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, TEST) {
"use strict";
// Retrieve the application object
var module = angular.module("cjt2.directives.actionButton", [
"cjt2.templates",
"cjt2.directives.spinner"
]);
/**
* Directive that produces a button with an embedded spinner. The spinner starts and the button is
* disabled when the button is clicked. Once the action is done processing, the button is enabled
* and the spinner is stopped and hidden.
*
* @attribute {Function|Promise} cp-action The function call to make after starting the spinner. If a Promise, then handled async, if a function then handled sync.
* @attribute {String} [spinnerId] Optional id for the spinner. You only need to set this is you want to
* have control of the spinner independently of the built in behavior.
*
* @example
*
* Basic usage:
* <button cp-action="takeAction">
*
* Providing a custom spinner id:
* <button spinner-id="ActionButton" action="takeAction">
*
* Synchronous Action:
*
* For a synchronous action, the action should be a function that performs some long running
* task. Once the function returns, the spinner will stop. Note that we pass the function in the
* markup here
*
* <button cp-action="takeAction">
*
* $scope.takeAction = function() {
* // do something long sync process
* }
*
* Synchronous Action with Parameter:
*
* For a synchronous action that needs to pass a parameter, the action should be a function that returns a function that
* performs some long running task. Once the function returns, the spinner will stop. Note that we pass the function in the
* markup here
*
* <button cp-action="takeAction($index)">
*
* $scope.takeAction = function($index) {
* return function() {
* // do something long sync process
* // use the $index somehow
* }
* }
*
* Asynchronous Action:
*
* For a asynchronous action, the action function should be a function that starts an asynchronous
* task and returns a promise. Once the promise resolves or is rejected, the spinner will stop. Note
* that we call the function in the markup here.
*
* <button cp-action="takeAction()">
*
* $scope.takeAction = function() {
* var deferred = $q.defer();
* $timeout(function() {
* deferred.resolve();
* }, 1000);
* return deferred.promise;
* }
*
* Classes:
*
* Because this directive uses replacement, using class attributes on the original element don't always get passed to the
* resulting button the way you'd expect. For this reason, you should use the button-class and button-ng-class attributes
* to style the final button. The "btn" class is always included by default. If you don't provide any button-class or
* button-ng-class attributes then the default classes of "btn btn-primary" will be applied. The button-ng-classes
* attribute will be evaluated against the parent scope so it's pretty flexible.
*
* "btn btn-primary"
* <button cp-action="doSomething()">
*
* "btn btn-warning"
* <button cp-action="doSomething()" button-class="btn-warning">
*
* "btn btn-warning" if isWarning
* "btn btn-danger" if isError
* <button cp-action="doSomething()" button-ng-class="{ 'btn-warning' : isWarning, 'btn-danger' : isError }">
*
* "btn" and whatever classes are provided by getButtonClasses on the parent scope
* <button cp-action="doSomething()" button-ng-class="getButtonClasses()">
*/
module.directive("cpAction", ["spinnerAPI", "$log", function(spinnerAPI, $log) {
var ctr = 0;
var DEFAULT_AUTO_DISABLE = true;
var DEFAULT_CONTROL_NAME = "actionButton";
var DEFAULT_BUTTON_CLASS = "btn-primary";
var RELATIVE_PATH = "libraries/cjt2/directives/actionButton.phtml";
return {
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
restrict: "A",
transclude: true,
replace: true,
priority: 10,
scope: {
// spinnerId: "@spinnerId", // REMOVED: Due to an issue with auto one way binding, seems that if you want defaults to work
// with nested controls, you must set the scope in the pre() method, but if you use the
// isolated scope @, you can't set the default right. Its missing during the critical
// phase when the nested controls need it.
buttonClass: "@buttonClass",
buttonNgClass: "&",
action: "&cpAction",
autoDisable: "@?autoDisable",
actionActive: "@?",
},
/* eslint-disable no-unused-vars */
compile: function(element, attrs) {
/* eslint-enable no-unused-vars */
return {
/* eslint-disable no-unused-vars */
pre: function(scope, element, attrs) {
if (attrs.ngBind) {
$log.error("ngBind is not supported on this directive. It causes the spinner to stop working");
}
// Set the defaults
var id = angular.isDefined(attrs.id) && attrs.id !== "" ? attrs.id : DEFAULT_CONTROL_NAME + ctr++;
attrs.spinnerId = angular.isDefined(attrs.spinnerId) && attrs.spinnerId !== "" ? attrs.spinnerId : id + "_Spinner";
if (!angular.isDefined(attrs.buttonNgClass)) {
attrs.buttonClass = angular.isDefined(attrs.buttonClass) && attrs.buttonClass !== "" ? attrs.buttonClass : DEFAULT_BUTTON_CLASS;
}
// remember, autoDisable is a string because of the "@" isolate scope property
// we need to convert it to a proper boolean
var tmpAutoDisable = attrs.autoDisable;
attrs.autoDisable = DEFAULT_AUTO_DISABLE;
if (angular.isDefined(tmpAutoDisable)) {
if (tmpAutoDisable === "false") {
attrs.autoDisable = false;
} else if (tmpAutoDisable === "true") {
attrs.autoDisable = true;
}
}
// Capture the id so the template can use it.
scope.spinnerId = attrs.spinnerId;
scope.autoDisable = attrs.autoDisable;
},
/* eslint-enable no-unused-vars */
post: function(scope, element, attrs) {
scope.running = false;
/**
* Stop the spinner and enable the button again.
* @method finish
* @private
*/
var finish = function() {
if (scope.autoDisable) {
element.prop("disabled", false);
}
scope.running = false;
spinnerAPI.stop(scope.spinnerId, false);
};
/**
* Starts the action specified by the method property
* @protected
*/
scope.start = function() {
_start();
var action = scope.action();
if (TEST.isQPromise(action)) {
// Async
action.finally(finish);
} else {
// Sync
finish();
}
};
function _start() {
if (scope.autoDisable) {
element.prop("disabled", true);
}
spinnerAPI.start(scope.spinnerId);
scope.running = true;
}
/**
* Combines the button-ng-class values with a default ng-class object that handles the
* loading/process font icon. The ng-class directive will evaluate each item in the array
* separately, so mixed formats (string, object, or array) are fine.
*
* @method ngClass
* @return {Array} An array that will be consumed by the ng-class directive.
*/
scope.ngClass = function() {
var finalNgClass = [{
"button-loading": scope.running
}];
var buttonNgClass = scope.buttonNgClass();
if (buttonNgClass) {
if (angular.isArray(buttonNgClass)) {
finalNgClass = finalNgClass.concat(buttonNgClass);
} else {
finalNgClass.push(buttonNgClass);
}
}
return finalNgClass;
};
/**
* Allows the directive to change states based on a boolean, useful if your page is perfoming an action prior to, or after the button click.
*/
attrs.$observe("actionActive", function(newVal) {
scope.actionActive = attrs.actionActive = (newVal === "true");
if (attrs.actionActive) {
_start();
} else {
finish();
}
});
}
};
}
};
}]);
}
);
/*
# cjt/directives/alert.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/alert',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, LOCALE) {
var module = angular.module("cjt2.directives.alert", [
"cjt2.templates"
]);
/**
* Directive that shows an alert.
* @example
*
* Example of a collection of alerts:
*
* <cp:alert ng-repeat="alert in alerts"
* ng-model="alert"
* on-close="myCloseFn($index)">
* </cp:alert>
*
* Where alert-data is an object in the following the form:
*
* {
* message: {String} The alert message text.
* type: {String} The type of alert.
* closeable: {Boolean} Is the user able to dismiss the alert?
* }
*
* And alerts is an array of alert objects. One could add to the
* list of alerts by simply pushing a new one to the array:
*
* $scope.alerts.push({
* message : message,
* type : type || "info"
* });
*
* Example of an alert with transcluded text
*
* <cp:alert type="danger">
* Something bad happened!!!
* </cp:alert>
*
* Example of an alert with transcluded html with filter
*
* <cp:alert type="danger" ng-init="message='what\nare\nyou looking at.'">
* <span ng-bind-html="message | break"></span>
* </cp:alert>
*
* Example of alert with auto close of 10 seconds.
*
* <cp:alert type="info" auto-close="10000">
* Just letting you know something, but it will go away in 10 seconds.
* </cp:alert>
*
* Example of alert with more link.
*
* scope.showMore = false;
* scope.toggleMore = function(show, id) {
* scope.showMore = show;
* }
*
* <cp:alert type="error" on-toggle-more="toggleMore(show, id)" more-label="More">
* Just letting you know an error at the high level.
* <div class="well ng-hide" ng-show="showMore">
* And here are some more details that you don't need unless you are an expert.
* </div>
* </cp:alert>
*
*/
module.directive("cpAlert", ["$timeout", "$compile",
function($timeout, $compile) {
var _counter = 0;
var ID_DEFAULT_PREFIX = "alert";
var RELATIVE_PATH = "libraries/cjt2/directives/alert.phtml";
var LABELS = [{
name: "errorLabel",
defaultText: LOCALE.maketext("Error:"),
}, {
name: "warnLabel",
defaultText: LOCALE.maketext("Warning:")
}, {
name: "infoLabel",
defaultText: LOCALE.maketext("Information:")
}, {
name: "successLabel",
defaultText: LOCALE.maketext("Success:")
}, {
name: "moreLabel",
defaultText: LOCALE.maketext("What went wrong?")
}];
/**
* Initialize the model state with defaults and other business logic. Model can be
* setup via an optional ng-model or via inline attributes on the directive.
*
* @private
* @method initializeModel
* @param {Boolean} hasModel true indicates that a model is used, false indicates to use attribute rules.
* @param {Array} attrs
* @param {String|Object|Undefined} modelValue
* @return {Object} Fully filled out model for the alert.
*/
var initializeModel = function(hasModel, attrs, modelValue) {
var data = {};
if (hasModel) {
if (angular.isString(modelValue)) {
data.message = modelValue;
} else if (angular.isObject(modelValue)) {
angular.copy(modelValue, data);
} else {
throw new TypeError("ngModel must be a string or object.");
}
}
if (!angular.isDefined(data.type)) {
if (angular.isDefined(attrs.type) && attrs.type) {
data.type = attrs.type;
} else {
data.type = "warning";
}
}
if (angular.isDefined(data.closable)) {
// We don't want users to be able to close errors, only the application
// code can do this. Otherwise, accept the users choices.
data.closable = ((data.type === "danger") ? false : data.closable);
} else if (angular.isDefined(attrs.closable)) {
data.closable = ((data.type === "danger") ? false : true);
} else {
data.closable = false;
}
if (CJT.isE2E()) {
data.autoClose = false;
} else if (angular.isDefined(data.autoClose)) {
// We don't want errors to auto close either.
data.autoClose = ((data.type === "danger") ? false : data.autoClose);
} else if (angular.isDefined(attrs.autoClose)) {
data.autoClose = ((data.type === "danger") ? false : data.autoClose);
} else {
data.autoClose = false;
}
if (!angular.isDefined(data.id)) {
if (!angular.isDefined(attrs.id)) {
// Guarantee we have some kind of id.
data.id = ID_DEFAULT_PREFIX + _counter++;
} else {
// Guarantee we have some kind of id.
data.id = attrs.id;
}
}
if (hasModel && !angular.isDefined(data.message) && !data.message) {
throw new Error("No message provided in the model's message property.");
} // Otherwise, its just transcluded.
return data;
};
/**
* Render the body using the transclusion. Only called if using transclusion!!!
*
* @method renderBody
* @param {Object} scope directive scope
* @param {Object} element directive element
* @param {Function} transclude directive transclude function
*/
var renderBody = function(scope, element, transclude) {
// Process the transclude
var type = scope.alert.type;
var typeBlock = element[0].querySelector(".alert-" + type);
var messageSpan = typeBlock.querySelector(".alert-body");
transclude(function(clone) {
// append the transcluded element.
angular.element(messageSpan).append(clone);
});
};
return {
restrict: "EA",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
transclude: true,
replace: true,
require: "?ngModel",
scope: {
close: "&onClose",
toggleMore: "&onToggleMore",
autoClose: "=",
errorLabel: "@",
warnLabel: "@",
infoLabel: "@",
successLabel: "@",
moreLabel: "@"
},
compile: function(element, attrs) {
// Initialize any labels not provided
LABELS.forEach(function(label) {
if (!angular.isDefined(attrs[label.name])) {
attrs[label.name] = label.defaultText;
}
});
return function(scope, element, attrs, ngModelCtrl, transclude) {
// Prepare the model by adding any missing parts to their appropriate defaults.
if (ngModelCtrl) {
ngModelCtrl.$formatters.push(function(modelValue) {
return initializeModel(true, attrs, modelValue);
});
ngModelCtrl.$render = function() {
scope.alert = ngModelCtrl.$viewValue;
$timeout(function() {
scope.$emit("addAlertCalled");
}, 0);
};
} else {
scope.alert = initializeModel(false, attrs);
renderBody(scope, element, transclude);
}
/**
* Set all of the label attributes to the model's label value, if it exists.
*/
scope.$watch("alert.label", function(newVal) {
if ( angular.isDefined(newVal) ) {
LABELS.forEach(function(label) {
attrs.$set(label.name, newVal);
});
}
});
/**
* Helper method to handle manual closing of an alert.
*/
scope.runClose = function() {
if (scope.timer) {
var timer = scope.timer;
scope.timer = null;
delete scope.timer;
$timeout.cancel(timer);
}
/* for alertList (or anything else that might want to use it) */
scope.$emit("closeAlertCalled", {
id: scope.alert.id
});
scope.close();
};
// Check if autoClose is set and set the close timer if it is
var msecs = scope.autoClose ? parseInt(scope.autoClose, 10) : null;
if (msecs && !isNaN(msecs)) {
scope.timer = $timeout(function() {
scope.runClose();
}, msecs);
}
// Add the toggle more support. What the toggle more button
// does is defined by the user of the directive, probably
// in the body of the alert, but not necessarily. Its designed
// to provide a way to embed technical details into the alert
// but not show them by default.
scope.hasToggleHandler = angular.isDefined(attrs.onToggleMore);
scope.showMore = false;
scope.runToggleMore = function() {
scope.showMore = !scope.showMore;
var e = {
id: scope.alert.id,
show: scope.showMore
};
scope.$emit("toggleMoreAlertCalled", e);
scope.toggleMore(e);
};
};
}
};
}
]);
}
);
/*
# cjt/directives/alertList.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/alertList',[
"angular",
"jquery", // Used for the height/width methods
"cjt/core",
"lodash",
"ngAnimate",
"ngSanitize",
"cjt/directives/alert",
"cjt/config/componentConfiguration",
"cjt/services/alertService",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, $, CJT, _) {
"use strict";
var DEFAULT_INLINE = false;
var RELATIVE_PATH = "libraries/cjt2/directives/alertList.phtml";
var module = angular.module("cjt2.directives.alertList", [
"cjt2.config.componentConfiguration",
"cjt2.templates",
"ngAnimate",
"ngSanitize",
"cjt2.directives.alert",
]);
/**
* We don't want to include the top bar in our calculations of available space
* for the alertList content. These values are duplicated in alertList.less, so
* please update them in both places if they ever change.
*/
var TOP_BAR_OFFSETS = {
whostmgrSm: 120,
cpanelSm: 52,
webmailSm: 52,
whostmgrXs: 70,
cpanelXs: 30,
webmailXs: 30,
};
/**
* Validate a position.
*
* @method _validatePosition
* @param {String} position A potential value.
* @return {Boolean} true if valid, false otherwise.
*/
function _validatePosition(position) {
if (!position) {
return true;
} // It's ok for it not to be set
switch (position) {
case "top-left":
case "top-middle":
case "top-right":
case "bottom-left":
case "bottom-middle":
case "bottom-right":
case "middle-left":
case "middle-middle":
case "middle-right":
return true;
default:
window.console.log("Invalid alertList.position set. It must be one of: top-left, top-middle, top-right, bottom-left, bottom-middle, bottom-right, middle-left, middle-middle, middle-right");
return false;
}
}
/**
* This is a directive that creates a list of alert directives using the alertService.
*
* Basic usage in a template:
* <cp-alert-list></cp-alert-list>
*
* Template usage with a non-default alert group and auto-close:
* <cp-alert-list alert-group="'testGroup'" auto-close="2000"></cp-alert-list>
*
* Please note that quotes are required in the alert-group attribute when
* using a string.
*
* Now to add an alert, you can do something like any of the following:
* alertService.add({
* message: "This is my alert message that is not closeable",
* closeable: false
* });
*
* alertService.add({
* message: "This alert will stack with any alerts already present",
* replace: false
* });
*
* alertService.add({
* message: "This alert specifies the type instead of using the default",
* type: "danger"
* });
*
* alertService.add({
* message: "This alert add to the specified group instead of the default",
* type: "info",
* group: "testGroup"
* });
*
* Please see the alertService documentation for more information.
*/
module.directive("cpAlertList", [
"alertService",
"componentConfiguration",
function(
alertService,
componentConfiguration
) {
return {
restrict: "E",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
replace: true,
scope: {
alertGroup: "=",
autoClose: "=",
position: "=",
},
controller: ["$scope", "$element", "$window", "$attrs",
function($scope, $element, $window, $attrs) {
$scope.rules = {
position: null,
inline: DEFAULT_INLINE
};
$scope.$watch("inline", function() {
if ($scope.rules.inline !== $scope.inline) {
refreshPositionRules();
}
});
$scope.$watch("position", function() {
if ($scope.rules.position !== $scope.position && _validatePosition($scope.position)) {
refreshPositionRules();
}
});
$scope.$watchCollection(function() {
return componentConfiguration.getComponent("alertList");
}, function() {
refreshPositionRules();
});
// NOTE: Due to the dynamic nature of the whm header with breadcrumbs
// causing the header to grow in an unpredictable way, we have to
// dynamically adjust the top from the static value defined above.
// These expensive adjustments are only needed right now for whm.
// All code that uses them will check if the contentContainer is
// defined to make the decisions about applying the expensive extra
// adjustments.
//
// Other solutions I looked at:
// * ResizeObserver - not native except for Chrome
// * MutationObserver - complex to implement
// * ResizeObserverPolyfill - ok, but requires adding an RPM, requires inefficent timers for some browsers.
//
// The current solution just addresses the problem where it exists
// presently without adding a polyfill or providing other signifigant
// cross-browser complex solutions at least for the size testing.
var contentContainer = CJT.isWhm() ? angular.element("#contentContainer") : null;
var contentContainerTop = getDefaultContainerTop();
/**
* Fetch the default top position. This is the hardcoded minimum.
*
* @return {Number} Pixel location to set as the top based only
* on the static minimums defined by them and grid size.
*/
function getDefaultContainerTop() {
var customApplicationWidth = CJT.isWebmail() ? 667 : 768;
// We need a default even for whm or the fixed top will get set to 0 which will
// trigger the hidden top of the application problem.
return $window.innerWidth < customApplicationWidth ?
TOP_BAR_OFFSETS[CJT.applicationName + "Xs"] :
TOP_BAR_OFFSETS[CJT.applicationName + "Sm"];
}
/**
* Update contentContainerTop with the actual top of the contentContainer
* in WHM, in case the contentContainer has been pushed down below its default
* top. For other environments, we will just use the defaults, since their
* contents will always start at the default position for a given width.
*
* In either case, this method has the side effect of setting the alertList's
* top property if it has changed.
*
* @method updateContentContainerTop
*/
function updateContentContainerTop() {
var defaultTopForCurrentWidth = getDefaultContainerTop();
if (contentContainer && contentContainer.length) {
// Handle WHM
var actualContainerTop = contentContainer[0].getBoundingClientRect().top;
if (actualContainerTop !== contentContainerTop) {
contentContainerTop = Math.max(actualContainerTop, defaultTopForCurrentWidth);
$element.css("top", contentContainerTop);
}
} else if (contentContainerTop !== defaultTopForCurrentWidth) {
// Handle everything else, if the default top has changed
contentContainerTop = defaultTopForCurrentWidth;
$element.css("top", contentContainerTop);
}
}
/**
* Calculate the height at which to turn on the scrollbar
* for the alert list.
*
* @method calculateHeightToTurnOnScroll
*/
function calculateHeightToTurnOnScroll() {
var windowHeight = $window.innerHeight;
$scope.heightToTurnOnScroll = windowHeight - contentContainerTop;
}
/**
* Update the position properties
*
* @method updatePosition
* @param {String} position Position of the alertList if inline is false.
* @param {Boolean} inline Inline if true, positioned otherwise.
*/
function updatePosition(position, inline) {
if (position !== null) {
$scope.rules.position = position;
}
if (inline !== null) {
$scope.rules.inline = inline;
}
}
/**
* Refresh the positioning rules from the attributes and
* the componentConfiguration service.
*
* @method refreshPositionRules
*/
function refreshPositionRules() {
var rules = componentConfiguration.getComponent("alertList");
updatePosition(rules.position, rules.inline);
if ($attrs.hasOwnProperty("inline")) {
updatePosition(null, true);
}
if (angular.isDefined($scope.position) && $scope.position && _validatePosition($scope.position)) {
updatePosition($scope.position, null);
}
}
/**
* Handle resize to adjust the heightToTurnOnScroll
* scope variable.
*
* @method onResize
* @private
*/
function onResize() {
updateContentContainerTop();
calculateHeightToTurnOnScroll();
// manual $digest required as resize event
// is outside of angular
$scope.$digest();
}
/**
* This method allows the user to close the alert only when it appears
* in the center of the page, by pressing escape key or by clicking
* anywhere but the alert popup.
*
* @method closeAlerts
* @param {Event} event - The keyboard/mouse event.
*/
function closeAlerts(event) {
if ($scope.rules.inline || $scope.rules.position !== "middle-middle" || !$scope.alertsPresent) {
return;
}
// close all alerts when the ESC key is pressed or when a click is caught outside of the alert itself
// we use closest here to ensure that we are not clicking inside of the alert container
if ((event.type === "keyup" && event.keyCode === 27) ||
(event.type === "click" && angular.element(event.target).closest(".alert-container").length === 0)) {
alertService.clear(null, $scope.alertGroup);
// Unregister the click & key events.
angular.element($window).off("keyup click", closeAlerts);
$scope.$digest();
}
}
var debounceOnResizeFn = _.throttle(onResize, 60);
angular.element($window)
.on("resize", debounceOnResizeFn)
.on("toggle-navigation", onResize);
$scope.$on("$destroy", function() {
angular.element($window)
.off("resize", debounceOnResizeFn)
.off("toggle-navigation", onResize)
.off("keyup click", closeAlerts);
});
// Get the initial UI height.
updateContentContainerTop();
calculateHeightToTurnOnScroll();
refreshPositionRules();
/**
* Gets the position classes to apply to the list container based on the
* user's settings in nvdata:alert-list-rules. If this is an inline list,
* then we don't provide the user's preference. This should only be used
* in special cases where local feedback is required.
*
* @method getPositionClasses
* @scope
* @return {String} css classname list to apply to the alertList class attribute.
*/
$scope.getPositionClasses = function() {
return "position-" + ($scope.rules.inline ? "inline" : $scope.rules.position);
};
/**
* Determines whether or not the alert list has exceeded the viewable
* height of the page.
*
* @return {Boolean} True if the list of alerts is taller than the
* visible space below the top bar.
*/
$scope.needsScrollbar = function() {
if ($scope.rules.inline) {
return false;
}
if (!angular.isDefined($scope.heightToTurnOnScroll)) {
calculateHeightToTurnOnScroll();
}
var listHeight = $element.find(".alert-list").height();
return ($scope.alerts && $scope.alerts.length > 0 && listHeight >= $scope.heightToTurnOnScroll);
};
/**
* Set the height of the directive container equal to the visible space
* below the top bar if we need a scrollbar or the height has changed.
*/
$scope.$watchGroup([
"needsScrollbar()",
"heightToTurnOnScroll",
], function(newVals) {
var needsScrollbar = newVals[0];
var heightToTurnOnScroll = newVals[1];
if (needsScrollbar) {
$element.css("height", heightToTurnOnScroll + "px");
} else {
$element.css("height", "auto");
}
});
// Bind to the alert array from the service
$scope.alerts = alertService.getAlerts($scope.alertGroup);
$scope.$watchCollection("alerts", function(alerts) {
var applyAutoClose = !CJT.isE2E() && $scope.autoClose;
if (alerts.length) {
$scope.alertsPresent = true;
} else {
$scope.alertsPresent = false;
}
alerts.forEach(function(alert) {
// If an autoClose is provided add it to any of the alerts
// that don't define their own auto close.
// NOTE: If we are running in an e2e test, we want to disable
// auto-close.
if (applyAutoClose && !alert.autoClose) {
alert.autoClose = $scope.autoClose;
}
/**
* Add the closable property as true to any non-inline alert
* since these were not originally designed to cover up stuff
* and will now. This means that far less of the code needs to
* be modified to make these alertList changes work.
*/
if (!$scope.rules.inline) {
alert.closeable = true;
}
});
});
/**
* Event addAlertCalled used to register
* click/keypup events for center positioned
* alerts.
*/
$scope.$on("addAlertCalled", function(event) {
angular.element($window).on("keyup click", closeAlerts);
});
/**
* Close the alert
*
* @method closeAlert
* @param {Number} index Position in the list to close.
*/
$scope.$on("closeAlertCalled", function(event, args) {
alertService.removeById(args.id, $scope.alertGroup);
angular.element($window).off("keyup click", closeAlerts);
});
}
]
};
}
]);
module.animation(".alert-container", ["$animateCss", function($animateCss) {
return {
enter: function(elem, done) {
var height = elem[0].offsetHeight;
return $animateCss(elem, {
from: { height: "0" },
to: { height: height + "px" },
duration: 0.3,
easing: "ease-out",
event: "enter",
structural: true
})
.start()
.finally(function() {
elem[0].style.height = "";
done();
});
},
leave: function(elem, done) {
var height = elem[0].offsetHeight;
return $animateCss(elem, {
event: "leave",
structural: true,
from: { opacity: "1" },
to: { opacity: "0", transform: "translateX(50px)" },
duration: 0.3,
easing: "ease-out",
})
.start()
.done(function() {
$animateCss(elem, {
event: "leave",
structural: true,
from: { height: height + "px" },
to: { height: "0" },
duration: 0.3,
easing: "ease-out",
}).start().finally(function() {
done();
});
});
},
};
}]);
}
);
/*
# cjt/directives/autoFocus.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
// ------------------------------------------------------------
// Developer notes:
// ------------------------------------------------------------
// The concept for this construct was derived from:
// http://stackoverflow.com/questions/14859266/input-autofocus-attribute
// http://jsfiddle.net/ANfJZ/39/
// Used with permission.
// ------------------------------------------------------------
define(
'cjt/directives/autoFocus',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.autoFocus", []);
/**
* Directive that triggers the filed to be auto-focused on load.
* @example
*
* Always auto-focus the field:
* <input auto-focus />
*
* Conditionally focus the field based on the state variable.
* <input auto-focus="condition" />
*
* Call lost focus callback on focus lost.
* <input auto-focus onFocusLost="handleFocusLost()" />
*/
module.directive("autoFocus", [ "$timeout", function($timeout) {
return {
link: function( scope, element, attrs ) {
// Watch for changes in the attribute, triggered at view load time too.
scope.$watch( attrs.autoFocus, function( val ) {
// only trigger the autofocus if we have a condition and it's true or if we have no condition at all
var condition_exists_and_is_true = angular.isDefined(val) && val;
if (angular.isDefined(attrs.autoFocus) &&
((attrs.autoFocus === "") || (attrs.autoFocus !== "" && condition_exists_and_is_true)) ) {
$timeout( function() {
element[0].focus();
} );
}
}, true);
element.bind("blur", function() {
if ( angular.isDefined( attrs.onFocusLost ) ) {
scope.$apply( attrs.onFocusLost );
}
});
}
};
}]);
}
);
/*
# cjt/directives/boolToInt.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/boolToInt',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.boolToInt", []);
/**
* The boolToInt directive is used for better Perl to frontend handling
*
* @directive
* @directiveType Attribute
* Angular directive that when attached to an element with an ng-model will render that model as true or false
* but ensure that any changing will result in 1 or 0 values. Necessary because Perl cannot evaluate JavaScript
* true/false when submitted in JSON.
*
* @example
*
* Always auto-focus the field:
* <input auto-focus />
*
* Conditionally focus the field based on the state variable.
* <input auto-focus="condition" />
*
* Call lost focus callback on focus lost.
* <input auto-focus onFocusLost="handleFocusLost()" />
*/
module.directive("boolToInt", [
function() {
return {
restrict: "A",
require: "ngModel",
priority: 99,
link: function(scope, elem, attrs, controller) {
controller.$formatters.push(function(modelValue) {
return !!modelValue;
});
controller.$parsers.push(function(viewValue) {
return viewValue ? 1 : 0;
});
}
};
}
]);
}
);
define(
'cjt/directives/breadcrumbs',[
"angular",
"cjt/core",
"lodash",
"ngSanitize",
"ngRoute",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, _) {
"use strict";
var module = angular.module("cjt2.directives.breadcrumbs", [
"cjt2.templates",
"ngRoute",
"ngSanitize"
]);
module.directive("breadcrumbs", function() {
var RELATIVE_PATH = "libraries/cjt2/directives/breadcrumbs.phtml";
var breadcrumbController = [ "$scope", "$location", "$rootScope", "$route", "$window", function($scope, $location, $rootScope, $route, $window) {
var breadcrumbs = [];
$scope.crumbs = [];
function updateBreadcrumbInfo(routeData) {
$scope.crumbs = [];
if (PAGE.customBreadcrumbs) {
$scope.crumbs = PAGE.customBreadcrumbs;
return true;
}
while (routeData) {
$scope.crumbs.unshift(routeData);
routeData = _.find(breadcrumbs, function(breadcrumb) {
return breadcrumb.id === routeData.parentID;
});
}
}
function buildCrumbs() {
var routes = $route.routes;
angular.forEach(routes, function(config) {
if (config.hasOwnProperty("breadcrumb")) {
var breadcrumb = config.breadcrumb;
breadcrumbs.push(breadcrumb);
}
});
}
function init() {
buildCrumbs();
// Validating based on the path whether the initial load is an existing breadcrumb
var pathElements = $location.path().split("/");
var routePath = pathElements.slice(0, 2).join("/");
var routeData = _.find(breadcrumbs, function(breadcrumb) {
var breadcrumbPath = breadcrumb.path;
// If the breadcrumb.path was specified with a trailing forward slash
// strip it for matching purposes
// but not if it is a root level /
if (breadcrumbPath.length > 1 && breadcrumbPath.charAt(breadcrumbPath.length - 1) === "/") {
breadcrumbPath = breadcrumbPath.substr(0, breadcrumbPath.length - 1);
}
return breadcrumbPath === routePath;
});
updateBreadcrumbInfo(routeData);
}
init();
$rootScope.$on("$routeChangeSuccess", function(event, current) {
buildCrumbs();
updateBreadcrumbInfo(current.breadcrumb);
});
// update route parameter
$scope.changeRoute = function(breadcrumb) {
if (breadcrumb.navigate) {
return $window.location.href = breadcrumb.path;
}
$location.path(breadcrumb.path);
};
}];
return {
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
replace: true,
restrict: "EA",
scope: true,
controller: breadcrumbController
};
});
}
);
/*
# cjt/directives/bytesInput.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/bytesInput',[
"angular",
"lodash",
"cjt/core",
"cjt/util/locale",
"cjt/util/parse",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, _, CJT, LOCALE, PARSE) {
"use strict";
var RELATIVE_PATH = "libraries/cjt2/directives/bytesInput.phtml";
var SI_UNITS = {
B: { abbr: LOCALE.maketext("Bytes"), full: LOCALE.maketext("Bytes"), multiplier: 0 },
KB: { abbr: LOCALE.maketext("KB"), full: LOCALE.maketext("Kilobytes"), multiplier: 1 },
MB: { abbr: LOCALE.maketext("MB"), full: LOCALE.maketext("Megabytes"), multiplier: 2 },
GB: { abbr: LOCALE.maketext("GB"), full: LOCALE.maketext("Gigabytes"), multiplier: 3 },
TB: { abbr: LOCALE.maketext("TB"), full: LOCALE.maketext("Terabytes"), multiplier: 4 },
PB: { abbr: LOCALE.maketext("PB"), full: LOCALE.maketext("Petabytes"), multiplier: 5 },
EB: { abbr: LOCALE.maketext("EB"), full: LOCALE.maketext("Exabytes"), multiplier: 6 },
ZB: { abbr: LOCALE.maketext("ZB"), full: LOCALE.maketext("Zettabytes"), multiplier: 7 },
YB: { abbr: LOCALE.maketext("YB"), full: LOCALE.maketext("Yottabytes"), multiplier: 8 },
};
var BINARY_UNITS = {
B: { abbr: LOCALE.maketext("Bytes"), full: LOCALE.maketext("Bytes"), multiplier: 0 },
KiB: { abbr: LOCALE.maketext("KiB"), full: LOCALE.maketext("Kibibytes"), multiplier: 1 },
MiB: { abbr: LOCALE.maketext("MiB"), full: LOCALE.maketext("Mebibytes"), multiplier: 2 },
GiB: { abbr: LOCALE.maketext("GiB"), full: LOCALE.maketext("Gibibytes"), multiplier: 3 },
TiB: { abbr: LOCALE.maketext("TiB"), full: LOCALE.maketext("Tebibytes"), multiplier: 4 },
PiB: { abbr: LOCALE.maketext("PiB"), full: LOCALE.maketext("Pebibytes"), multiplier: 5 },
EiB: { abbr: LOCALE.maketext("EiB"), full: LOCALE.maketext("Exbibytes"), multiplier: 6 },
ZiB: { abbr: LOCALE.maketext("ZiB"), full: LOCALE.maketext("Zebibytes"), multiplier: 7 },
YiB: { abbr: LOCALE.maketext("YiB"), full: LOCALE.maketext("Yobibytes"), multiplier: 8 },
};
// Retrieve the application object
var module = angular.module("cjt2.directives.bytesInput", [
"cjt2.templates"
]);
/**
* @summary Directive that allows for entering byte sizes while picking units such as MB/GB/TB/etc from a drop-down
*
* @attribute {String} name A name for the component. This will be used to set the name and id attributes
* on the text input field to "{{name}}InputValue" and the name and id attributed
* on the drop-down button to name="{{name}}DropDownButton". Defaults to
* "bytesInput".
*
* @attribute {String} displayFormat Either 'si' or 'binary' to define whether to display SI (KB/MB/GB/etc) or
* binary (KiB/MiB/GiB/etc) units. Defaults to "si".
*
* @attribute {String} valueFormat Either 'si' or 'binary' to define whether to calculate the number of bytes using
* SI (1000/1000000/1000000000/etc) or binary (1024/1048576/1073741824/etc) values.
* Provided because cPanel typically displays SI units but calculates sizes in
* binary. Defaults to "binary".
*
* @attribute {Array[String]} allowedUnits An array of strings indicating what values are selectable from the drop-down
* selector. Each element must be a valid SI or binary unit, depending on the
* displayFormat. Defaults to ["MB", "GB", "TB", "PB"] for the "si" displayFormat
* or ["MiB", "GiB", "TiB", "PiB"] for the binary displayFormat.
*
* @attribute {String} defaultUnit Which value to initially select in the drop-down selector. The value must be a
* valid SI or binary unit, depending on the displayFormat. If not specified, it
* will default to the smallest unit in the allowedUnits.
*
* @attribute {Number} size The size of the input field. This value will be directly applied to the size
* attribute on the <input> element. If not specified, it will default to 10.
*
* @attribute {Number} maxlength The maximum length of the input field. This value will be directly applied to
* the maxlength attribute on the <input> element. If not specified, it will
* default to 10.
*
* @attribute {String} selectedUnit The currently selected unit for the dropdown selector. A two way binding, it
* allows a string to be passed in to be converted to a unit object to be used
* internally by the directive. The string should be equivalent to the en-us
* abbreviation for the unit (e.g. MB, MiB, etc…)
*
* @attribute {Number} bytesInputMax The maximum value of the input field. (optional)
*
* @attribute {Number} bytesInputMin The minimum value of the input field. (optional)
*
* @attribute {Boolean} isDisabled True if the input field and drop-down selector should be disabled, false if
* not. This diverges from the typical use of the plain disabled attribute due
* to issues on IE11 where the descendents of an element can have unexpected
* behavior. See: {@link https://docs.angularjs.org/guide/ie}
*
* @required ngModel This directive requires ngModel be set on the element. The model value will be set to the number of
* bytes specified by the component.
*
* NOTE: This directive is wired to support values up to Yobibytes (2 ^ 80), however the current implementation of Number
* in JavaScript limits the maximum value of an integer to 2 ^ 53 or 9 PiB. This is probably good enough for most
* practical applications, but if a value of greater than 2 ^ 53 is required, this directive will need to be
* updated to use BigInteger implementation. This would only be useful is the API backing the component usage also
* supports BigIntegers.
*
* @example
* Using defaults:
* <bytes-input ng-model="numberOfBytes"></bytes-input>
*
* Specifying all attributes:
* <bytes-input ng-model="numberOfBytes"
* displayFormat="si"
* valueFormat="binary"
* allowedUnits="['MB', 'GB', 'TB']"
* defaultUnit="MB"
* size="5"
* maxlength="5"></bytes-input>
*
*/
module.directive("bytesInput", ["bytesInputConfig", "$timeout", function(bytesInputConfig, $timeout) {
return {
restrict: "E",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
require: "ngModel",
replace: true,
scope: {
displayFormat: "@",
valueFormat: "@",
valueUnit: "@",
allowedUnits: "=",
defaultUnit: "@",
ngFocus: "&",
size: "=",
maxlength: "=",
extraInputClasses: "@",
isDisabled: "=",
selectedUnit: "="
},
link: function(scope, element, attrs, ngModel) {
if ( attrs.disabled !== undefined ) {
throw "Do not use “disabled” on this component, use “isDisabled” instead.";
}
var testViewValue = function(view, testFunc) {
if ( scope.isDisabled ) {
return true;
}
if ( ngModel.$isEmpty(view) ) {
// We have no value, skip this validation and let required take care of it
return true;
} else if ( scope.inputValue && scope.inputValue > 0 && ("" + scope.inputValue).length > scope.maxlength ) {
// Value is over maxlength, we're quietly trimming it, so ignore validation errors for it
// until the trim happens
return true;
} else {
return testFunc(view);
}
};
ngModel.$validators.max = function(model, view) {
return testViewValue(view, function(v) {
return !attrs.bytesInputMax || isNaN(attrs.bytesInputMax) ? true : view <= parseInt(attrs.bytesInputMax, 10);
});
};
ngModel.$validators.min = function(model, view) {
return testViewValue(view, function(v) {
return !attrs.bytesInputMin || isNaN(attrs.bytesInputMin) ? true : view >= parseInt(attrs.bytesInputMin, 10);
});
};
ngModel.$validators.integer = function(model, view) {
return testViewValue(view, function(v) {
var parsed = new Number(v);
// In this case, we really just want to compare value, not type
// eslint-disable-next-line eqeqeq
return parsed == parsed.toFixed(0);
});
};
element.find("input[type=number]").on("focus", function() {
this.select();
});
scope.setUnitFromString = function(str) {
// Set the selected unit to the string if it's provided and valid, otherwise use the smallest allowed
if ( str && scope.units[str] ) {
scope.selectedUnit = scope.units[str];
} else {
scope.selectedUnit = scope.units[Object.keys(scope.units)[0]];
}
};
var inputEl = element.find("input[type='number']");
scope.displayFormat = scope.displayFormat || bytesInputConfig.displayFormat;
scope.valueFormat = scope.valueFormat || bytesInputConfig.valueFormat;
scope.size = scope.size || bytesInputConfig.size;
scope.maxlength = scope.maxlength || bytesInputConfig.size;
scope.isDisabled = scope.isDisabled || false;
scope.required = scope.required || false;
scope.dirty = false;
scope.min = !attrs.bytesInputMin || isNaN(attrs.bytesInputMin) ? 0 : parseInt(attrs.bytesInputMin, 10);
scope.units = [];
scope.name = attrs.name || "bytesInput";
if ( scope.valueFormat === "si" ) {
scope.valueUnitObj = SI_UNITS[scope.valueUnit] || SI_UNITS[bytesInputConfig.valueUnit];
} else if ( scope.valueFormat === "binary" ) {
scope.valueUnitObj = BINARY_UNITS[scope.valueUnit] || BINARY_UNITS[bytesInputConfig.valueUnit];
}
// Check for bad displayFormat values
if ( scope.displayFormat === "si" || scope.displayFormat === "binary" ) {
// Pick out the allowed units from the full list
if ( scope.displayFormat === "si" ) {
scope.units = _.pick(SI_UNITS, scope.allowedUnits || bytesInputConfig.siAllowed);
} else if ( scope.displayFormat === "binary" ) {
scope.units = _.pick(BINARY_UNITS, scope.allowedUnits || bytesInputConfig.binaryAllowed);
}
scope.setUnitFromString(scope.defaultUnit);
}
scope.selectUnit = function(unit) {
if ( scope.units[unit] ) {
scope.selectedUnit = scope.units[unit];
scope.calculateValue();
}
};
scope.calculateValue = function() {
if ( scope.valueFormat !== "si" && scope.valueFormat !== "binary" ) {
ngModel.$setViewValue(undefined);
return;
}
if ( !scope.inputValue || scope.inputValue === "" || isNaN(scope.inputValue) ) {
ngModel.$setViewValue(scope.inputValue);
return;
}
var inputValue = new Number(scope.inputValue);
var inputMultiplier = scope.selectedUnit.multiplier;
var outputMultiplier = scope.valueUnitObj.multiplier;
var base = scope.valueFormat === "si" ? 1000 : 1024;
var value = inputValue * Math.pow(base, inputMultiplier) / Math.pow(base, outputMultiplier);
ngModel.$setViewValue(value);
ngModel.$validate();
};
scope.setFromModel = function() {
if ( !scope.selectedUnit || (scope.valueFormat !== "si" && scope.valueFormat !== "binary") ) {
return;
}
if ( typeof scope.selectedUnit === "string" ) {
scope.setUnitFromString(scope.selectedUnit);
}
var base = scope.valueFormat === "si" ? 1000 : 1024;
var inputMultiplier = scope.selectedUnit.multiplier;
var outputMultiplier = scope.valueUnitObj.multiplier;
if ( !ngModel.$modelValue || isNaN(ngModel.$modelValue) ) {
if ( ngModel.$modelValue === undefined && ngModel.$viewValue !== null && !isNaN(ngModel.$viewValue) ) {
scope.inputValue = ngModel.$viewValue * Math.pow(base, outputMultiplier) / Math.pow(base, inputMultiplier);
} else {
scope.inputValue = ngModel.$modelValue;
}
} else {
var modelValue = new Number(ngModel.$modelValue);
var newValue = parseInt(new Number(modelValue * Math.pow(base, outputMultiplier) / Math.pow(base, inputMultiplier)));
// This is just to prevent us from auto-editing things like 5.0 to 5 so it's not
// weirdly changing things for the user
// eslint-disable-next-line eqeqeq
if ( scope.inputValue != newValue ) {
scope.inputValue = newValue;
}
}
ngModel.$setDirty();
ngModel.$validate();
};
scope.$watch(
function() {
return ngModel.$modelValue;
},
scope.setFromModel
);
scope.$watch("inputValue", scope.calculateValue);
scope.$watch("selectedUnit", function() {
if ( typeof scope.selectedUnit === "string" ) {
scope.setUnitFromString(scope.selectedUnit);
}
});
scope.$watch(
function() {
return element.find("input[type=number]")[0].disabled;
},
function() {
var el = element.find("input[type=number]")[0];
if ( !el.disabled ) {
el.select();
}
}
);
if (scope.maxlength && scope.maxlength > 0) {
inputEl.on("input", function(e) {
var str = ("" + scope.inputValue);
if (str.length > scope.maxlength) {
scope.inputValue = parseInt(str.slice(0, scope.maxlength));
}
});
}
}
};
}]);
module.constant("bytesInputConfig", {
displayFormat: "si",
valueFormat: "binary",
valueUnit: "B",
siAllowed: ["MB", "GB", "TB", "PB"],
binaryAllowed: ["MiB", "GiB", "TiB", "PiB"],
size: 10,
maxlength: 10
});
}
);
/*
# cjt/directives/callout.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/callout',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/templates"
],
function(angular, CJT, LOCALE, TEST) {
"use strict";
var module = angular.module("cjt2.directives.callout", ["cjt2.templates"]);
/**
* Directive that lets users highlight information on a page.
*
* @attribute {String} callout-type Type of callout (warning, info, danger)
* @attribute {String} [callout-heading] Optional heading for callout.
*
* @example
*
* Basic usage with heading:
* <callout callout-type="warning" callout-heading="Heading Text">Call out body text</callout>
* <callout callout-type="info" callout-heading="Heading Text">Call out body text</callout>
* <callout callout-type="danger" callout-heading="Heading Text">Call out body text</callout>
*
* Basic usage without heading
* <callout callout-type="warning">Call out body text</callout>
* <callout callout-type="info">Call out body text</callout>
* <callout callout-type="danger">Call out body text</callout>
*/
module.directive("callout", [function() {
var RELATIVE_PATH = "libraries/cjt2/directives/callout.phtml";
var DEFAULT_CALLOUT_TYPE = "info";
return {
restrict: "EA",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
transclude: true,
scope: {
calloutType: "=calloutType",
calloutHeading: "@calloutHeading",
closeable: "@",
onClose: "&"
},
link: function(scope, element, attr) {
scope.hasHeading = false;
scope.closeText = LOCALE.maketext("Close");
scope.runClose = function() {
scope.onClose();
};
// Handles calloutType enumeration defaulting
if (angular.isDefined(attr.calloutType)) {
switch (attr.calloutType) {
case "warning":
case "danger":
scope.calloutType = attr.calloutType;
break;
default:
scope.calloutType = DEFAULT_CALLOUT_TYPE;
break;
}
} else {
scope.calloutType = DEFAULT_CALLOUT_TYPE;
}
// Handles calloutHeading display
if (angular.isDefined(attr.calloutHeading)) {
scope.hasHeading = true;
}
}
};
}]);
}
);
/*
# cjt/util/performance.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/util/performance',[],function() {
// ------------------------------------------------------------------------------
// Developer Notes:
// ------------------------------------------------------------------------------
// This utility is provided to allow applications to depend on the performance
// even in environments where the API does not exist such as Phantom.js. Note
// if the environment does not have the API, the resolution is reduced
// significantly since the resolution of Date.now() is not very good compared
// to the high resolution time stamp used by the normal browser implementation.
// ------------------------------------------------------------------------------
var _performance;
if (window && !window.performance) {
_performance = {
now: function() {
return Date.now();
}
};
} else {
_performance = window.performance;
}
return _performance;
}
);
/*
# cjt/services/passwordStrengthService.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/services/passwordStrengthService',[
// Libraries
"angular",
"lodash",
// CJT
"cjt/core",
"cjt/util/locale",
"cjt/util/performance"
],
function(angular, _, CJT, LOCALE, performance) {
var module = angular.module("cjt2.services.passwordStrength", []);
var url = CJT.securityToken + (CJT.isUnprotected() ? "/unprotected" : "/backend" ) + "/passwordstrength.cgi";
var lastRequest = null;
var memoizedPasswordStrengths = {};
/**
* Setup the password strength API service
*/
module.factory("passwordStrength", [ "$q", "$http", "$rootScope", function($q, $http, $rootScope) {
/**
* Broadcast the event
*
* @private
* @method _broadcast
* @param {String} id
* @param {String} password
* @param {Number} strength
*/
function _broadcast(id, password, strength) {
$rootScope.$broadcast("passwordStrengthChange", {
// Password field we are validating
id: id,
// For validators to pull apart.
password: password,
// Just let the other listeners know if the password is set
hasPassword: password && password.length > 0 ? true : false,
// Strength returned by the service
strength: strength
});
}
// return the factory interface
return {
/**
* Cancel the last pending request if it exists.
*
* @method cancelLastRequest
*/
cancelLastRequest: function() {
if (lastRequest) {
lastRequest.cancel();
lastRequest.deferred.reject({
canceled: true
});
CJT.debug("Canceled existing request");
lastRequest = null;
}
},
/**
* Checks if there are any pending requests for strength check.
*
* @method hasPendingRequest
* @return {Boolean} true if there are pending requests, false otherwise.
*/
hasPendingRequest: function() {
return lastRequest !== null;
},
/**
* Checks the strength of a password.
*
* @method checkPasswordStrength
* @param {String} id The unique id for the field being evaluated.
* @param {String} password The password to check.
* @return {Promise} A promise that resolves once the password strength is determined
* or rejects if it wasn't determined.
*/
checkPasswordStrength: function(id, password) {
// Cancel the last request if it exists
this.cancelLastRequest();
var deferred, canceler;
// Exit quickly if password is empty or undefined
if (angular.isUndefined(password) || password === "") {
_broadcast(id, password, 0);
deferred = $q.defer();
deferred.resolve({
status: 200,
strength: 0,
password: password,
id: id
});
return deferred.promise;
}
if (memoizedPasswordStrengths.hasOwnProperty(password)) {
_broadcast(id, password, memoizedPasswordStrengths[password]);
deferred = $q.defer();
deferred.resolve({
status: 200,
strength: memoizedPasswordStrengths[password],
password: password,
id: id
});
return deferred.promise;
}
// Setup and stash key items from the last request for cancellation purposes
canceler = $q.defer();
deferred = $q.defer();
lastRequest = {
cancel: function() {
canceler.resolve();
},
deferred: deferred
};
// Prepare the postAsForm arguments
var request = {
url: url,
data: {
password: password
},
config: {
timeout: canceler.promise.then(function() {
var stop = performance.now();
CJT.debug("Call to cgi password strength service canceled " + (stop - start) + " milliseconds.");
})
}
};
var start = performance.now();
var strength = 0;
$http.postAsForm(request.url, request.data, request.config).then(
function(response) {
var stop = performance.now();
CJT.debug("Call to cgi password strength service " + (stop - start) + " milliseconds.");
// We can't resolve the promise if there's no strength returned
if ( !angular.isUndefined(response.data.strength) ) {
memoizedPasswordStrengths[password] = response.data.strength;
strength = response.data.strength;
deferred.resolve({
status: 200,
strength: strength,
password: password,
id: id
});
} else {
deferred.reject({
statusText: "Unspecified API Error", // HTTP status messages aren't localized
status: response.status,
strength: strength,
password: password,
id: id
});
}
},
// HTTP status other than 200
function(error) {
deferred.reject({
statusText: error.statusText,
status: error.status,
strength: strength,
password: password,
id: id
});
}
).finally(function() {
// Done processing so clear the last request
lastRequest = null;
_broadcast(id, password, strength);
});
return deferred.promise;
}
};
}]);
return {
url: url
};
}
);
/*
# cjt/directives/checkStrength.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
// ------------------------------------------------------------
// Developer notes:
// ------------------------------------------------------------
// The concept for this construct was derived from:
// http://blog.brunoscopelliti.com/angularjs-directive-to-test-the-strength-of-a-password
// Used with permission.
// ------------------------------------------------------------
define(
'cjt/directives/checkStrength',[
"angular",
"ngSanitize",
"uiBootstrap",
"cjt/services/passwordStrengthService"
],
function(angular) {
var module = angular.module("cjt2.directives.checkPasswordStrength", [
"ui.bootstrap",
"ngSanitize",
"cjt2.services.passwordStrength"
]);
/**
* Directive that triggers a back-end password strength check on the selected field.
* @example
*/
module.directive("checkPasswordStrength", ["passwordStrength", function(passwordStrength) {
return {
require: "ngModel",
priority: 1000,
restrict: "EACM",
replace: false,
link: function(scope, el, attrs, ngModel) {
ngModel.$asyncValidators.passwordStrength = function(modelVal, viewVal) {
return _checkStrength(modelVal || viewVal);
};
/**
* asyncValidators don't run at all if the synchronous validators don't pass first,
* which means there is no passwordStrength event broadcast from the service if a
* password becomes invalid after being valid. That could cause models that rely on
* that event to go stale so we trigger the check here if we detect that the input
* is invalid.
*/
scope.$watch(function() {
/**
* if the minlength validator has an error, then
* we should remove any displayed strength messages as they will be stale.
*/
if (ngModel.$error.minlength) {
_checkStrength();
}
return ngModel.$invalid;
}, function() {
if (ngModel.$invalid && !ngModel.$error.minimumPasswordStrength) {
_checkStrength();
}
});
/**
* Dispatches the password strength check request through the service.
*
* @param {String} password The password to check.
* @return {Promise} If resolved, the strength check succeeded. If rejected, it did not.
* This directive does not actually check the strength against the
* minimum required strength at this time.
*/
function _checkStrength(password) {
var id = attrs.id || ngModel.$name;
var promise = passwordStrength.checkPasswordStrength(id, password);
return promise;
}
}
};
}]);
}
);
/*
# cjt/util/module.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Provides angular module helper methods used in the construction
* of the bundles for each major application. *
*
* @module cjt/util/module
*/
define(
'cjt/util/module',[
"angular"
],
function(angular) {
"use strict";
/**
* Test if the given module is available to angular.js.
*
* @method isModuleAvailable
* @param {String} name Module name
* @return {Boolean} true if found, false if not found.
*/
function _isModuleAvailable(name) {
var module = null;
try {
module = angular.module(name);
return module !== null;
} catch (e) {
return false;
}
}
return {
/**
* Test if the given module is available to angular.js.
*
* @method isModuleAvailable
* @param {String} name Module name
* @return {Boolean} true if found, false if not found.
*/
isModuleAvailable: _isModuleAvailable,
/**
* Creates a module that depends on a set of other modules. This allows us to
* reference that set of dependencies with a single name.
*
* Ex: Assume we have a set of modules that are commonly used in various parts
* of an application. That set includes module "a", "b", and "c". We can create
* a module package called "myDeps" that depends on those three modules, and in
* our Angular app we can now just use the module dependency "myDeps" instead
* of enumerating "a", "b", and "c".
*
* This method checks for the existence of all dependent modules before adding
* them to the module package since we can't guarantee their existence.
*
* @method createModule
* @private
* @param {String} packageName The name of the resulting module package that will be registered with Angular.
* @param {String[]} moduleList A list of module names that the resulting package will require.
*/
createModule: function(packageName, moduleList) {
var packageDependencies = [];
moduleList.forEach(function(module) {
if (_isModuleAvailable(module)) {
packageDependencies.push(module);
} else if (module) {
window.console.log(module + " not found");
}
});
angular.module(packageName, packageDependencies);
}
};
}
);
/*
# cjt2/directives/datePicker.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/datePicker',[
"angular",
"cjt/util/locale",
"cjt/core"
],
function(angular, LOCALE, CJT) {
"use strict";
/**
* Directive to render a time picker
*
* @module date-picker
* @memberof cjt2.directives
*
* @example
* <date-picker ng-model="myDate"></date-picker>
*
*/
var RELATIVE_PATH = "libraries/cjt2/directives/";
var TEMPLATES_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
var TEMPLATE = TEMPLATES_PATH + "datePicker.phtml";
var MODULE_NAMESPACE = "cjt2.directives.datePicker";
var MODULE_REQUIREMENTS = [];
var module = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
var LINK = function(scope, element, attrs, ngModel) {
var unregister = scope.$watch(function() {
return ngModel.$modelValue;
}, initialize);
function initialize(value) {
ngModel.$setViewValue(value);
scope.selectedDate = value;
}
scope.closeTextLabel = LOCALE.maketext("Close");
scope.currentTextLabel = LOCALE.maketext("Today");
scope.clearTextLabel = LOCALE.maketext("Clear");
scope.showingPopup = false;
scope.showPopup = function() {
scope.showingPopup = true;
};
scope.onChange = function onChange(newDate) {
ngModel.$setViewValue(newDate);
};
scope.$on("$destroy", unregister);
};
var DIRECTIVE_FACTORY = function createDatePickerDirective() {
return {
templateUrl: TEMPLATE,
restrict: "EA",
require: "ngModel",
scope: {
parentID: "@id",
options: "="
},
transclude: true,
link: LINK
};
};
module.directive("datePicker", DIRECTIVE_FACTORY);
return {
"directiveFactory": DIRECTIVE_FACTORY,
"linkController": LINK,
"namespace": MODULE_NAMESPACE,
"template": TEMPLATE
};
}
);
/*
# cjt/directives/deepTriStateCheckbox.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
// ------------------------------------------------------------
// Developer notes:
// ------------------------------------------------------------
// The concept for this construct was derived from:
// https://gist.github.com/arnab-das/6129431
// Used with permission.
// ------------------------------------------------------------
// 1) Consider converting to use ng-model instead of custom
// binding.
// ------------------------------------------------------------
define(
'cjt/directives/deepTriStateCheckbox',[
"angular",
"cjt/core",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT) {
var module = angular.module("cjt2.directives.deepTriStateCheckbox", [
"cjt2.templates"
]);
/**
* The deepTriStateCheckbox is used to create a check all style controller
* check box for a group of check boxes that includes one or more nested child
* collections of check boxes.
*
* @directive
* @directiveType Elements
* @attribute {Binding} checkboxes Dataset controlling the dependent check-boxes
*/
module.directive("deepTriStateCheckbox", function() {
var RELATIVE_PATH = "libraries/cjt2/directives/triStateCheckbox.phtml";
return {
replace: true,
restrict: "E",
scope: {
checkboxes: "="
},
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
controller: ["$scope", "$element", function($scope, $element) {
/**
* Handler method when changes to the master controller occur.
*/
$scope.masterChange = function() {
if ($scope.master) {
angular.forEach($scope.checkboxes, function(cb) {
cb.selected = true;
angular.forEach(cb.children, function(cb) {
cb.selected = true;
});
});
} else {
angular.forEach($scope.checkboxes, function(cb) {
cb.selected = false;
angular.forEach(cb.children, function(cb) {
cb.selected = false;
});
});
}
};
// Watch for changes to the model behind the related checkboxes
$scope.$watch("checkboxes", function() {
var allSet = true,
allClear = true;
angular.forEach($scope.checkboxes, function(cb) {
if (cb.children) {
angular.forEach(cb.children, function(cb) {
if (cb.selected) {
allClear = false;
} else {
allSet = false;
}
});
} else if (cb.selected) {
allClear = false;
} else {
allSet = false;
}
});
if (allSet) {
// Handle all set
$scope.master = true;
$element.prop("indeterminate", false);
} else if (allClear) {
// Handle all clear
$scope.master = false;
$element.prop("indeterminate", false);
} else {
// Handel indeterminate
$scope.master = false;
$element.prop("indeterminate", true);
}
}, true);
}]
};
});
}
);
/*
# cjt/directives/disableAnimations.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/disableAnimations',[
"angular",
"ngAnimate"
],
function(angular) {
var module = angular.module("cjt2.directives.disableAnimations", ["ngAnimate"]);
/**
* Directive that disables animations for the element and all children.
*/
module.directive("disableAnimations", ["$animate", function($animate) {
return {
restrict: "A",
link: function(scope, element, attrs) {
$animate.enabled(element, false);
}
};
}]);
}
);
/*
# cjt/directives/displayPasswordStrength.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/displayPasswordStrength',[
"angular",
"cjt/core",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT) {
var RELATIVE_PATH = "libraries/cjt2/directives/displayPasswordStrength.phtml";
var DEFAULT_STYLES = [
"strength-0",
"strength-1",
"strength-2",
"strength-3",
"strength-4"
];
var module = angular.module("cjt2.directives.displayPasswordStrength", [
"cjt2.templates"
]);
/**
* Directive that shows the password strength in a meter bar.
* @attribute [fieldId] Optional field id, used to correlate the message from the strength service. Not needed
* if only one password strength field is used on the view.
* @attribute [styles] Optional comma delimited list of css class names for the colors. Must be 4 items in the
* list or the directive will use the defaults.
* @attribute [calculateColorBreak] Optional function to process the strength increment process. The function should return
* a structure with the following layout:
* {
* index: <number>, // Number 1 to 5 for the slot to fill too.
* color: <string> // CSS class name to use to do the fill for slots 1 to index.
* }
* If not provided, the built in function uses a step-wise linear algorithm breaking every 20 units for 0 to 100.
* @example
*
* <div displayPasswordStrength></div>
*/
module.directive("displayPasswordStrength", function() {
return {
replace: true,
restrict: "EACM",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
scope: {
fieldId: "@?fieldId",
styles: "@?styles",
calculateColorBreak: "&calculateColorBreak",
testId: "@?testId"
},
compile: function(element, attrs) {
return {
pre: function(scope, element, attrs) {
if (!angular.isUndefined(attrs.styles)) {
var styles = (attrs.styles + "").split(",");
if (styles.length < 5) {
throw "You must provide a list of 5 css class names if you are implementing custom styles";
} else {
scope.styles = styles;
}
}
},
post: function(scope, el, attrs) {
var colors = scope.styles && scope.styles.length === 5 ? scope.styles : DEFAULT_STYLES;
var allClasses = colors.join(" ");
/**
* Get the color related to the indicated password strength. This is the
* default implementation. User can override this behavior with their own
* method.
*
* @private
* @method calculateColorBreak
* @param {Number} strength - current strength
* @param {Array} colors - list of css class names
* @return {Object}
* {Number} index Position to update: 1 to 5.
* {String} color Color to use with this strength.
*/
var _calculateColorBreak = function(strength, colors) {
var index = 0;
if (strength <= 20) {
index = 0;
} else if (strength <= 40) {
index = 1;
} else if (strength <= 60) {
index = 2;
} else if (strength <= 80) {
index = 3;
} else {
index = 4;
}
return {
index: index + 1,
color: colors[index]
};
};
if (scope.calculateColorBreak) {
scope.calculateColorBreak = _calculateColorBreak;
}
// Monitor for the passwordStrengthChange event
scope.$on("passwordStrengthChange", function(evt, result) {
if ( !angular.isUndefined(scope.fieldId) && scope.fieldId !== result.id ) {
// This is not for us since its a
// message not related to our caller.
return;
}
var hasPassword = result.hasPassword;
var strength = result.strength;
if (!hasPassword) {
el.children("li")
.removeClass(allClasses);
} else {
var colorBreak = scope.calculateColorBreak(strength, colors);
el.children("li")
.removeClass(allClasses)
.slice(0, colorBreak.index)
.addClass(colorBreak.color);
}
});
}
};
}
};
});
return {
DEFAULT_STYLES: DEFAULT_STYLES
};
}
);
/*
# validator-utils.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* The module contains utility functions for validators
*
* @module validator-utils
*/
define('cjt/validator/validator-utils',[], function() {
"use strict";
// NOTE: Having results contain multiple messages add a lot of complication to the system since
// the validation system already has a system for registering multiple messages. I think we should
// refactor this to remove this complication as its us is very edge case and makes renders much more
// complicated.
/**
* The results object is used by all of the custom validator to embed response messages generated
* by complex validation logic.
*
* @constructor
*/
var ValidationResult = function() {
this.isValid = true;
this.messages = [];
this.lookup = {};
};
ValidationResult.prototype = {
/**
* Converts the message collection into a delimited string
*
* @method toString
* @param {String} [delimiter] Optional delimiter, defaults to newline.
* @return {String} Message from the has concatenated with delimiter separations.
*/
toString: function(delimiter) {
delimiter = delimiter || "\n";
var message = "";
for (var i = 0, l = this.messages.length; i < l; i++) {
var item = this.messages[i];
if (item && i > 0) {
message += delimiter;
}
message += item.message;
}
return message;
},
/**
* Checks if the message collection contains an item for the given name.
*
* @method hasMessage
* @param {String} name Name of the message set by the validator.
* @return {Boolean} true if there is a message with that name, false otherwise.
*/
hasMessage: function(name) {
var item = this.lookup[name];
return typeof (item) !== "undefined";
},
/**
* Checks if the message collection contains any messages.
*
* @method hasMessages
* @return {Boolean} [description]
*/
hasMessages: function() {
return this.messages.length > 0;
},
/**
* Add a message for this validator
*
* @method add
* @param {String} name A short name for the validation rule that triggered the message
* @param {String} message The actual message text
*/
add: function(name, message) {
var obj = { name: name, message: message };
this.messages.push(obj);
this.lookup[name] = obj; // NOTE: The lookup only supports one message per name. Last message added wins.
return this;
},
/**
* Shortcut for the "add" method when the validation message results from an error.
*
* @method addError
* @param {String} name A short name for the validation rule that triggered the error
* @param {String} message A longer description of the error
* @return {ValidationResult} This object, for chaining
*/
addError: function(name, message) {
this.add(name, message);
this.isValid = false;
return this;
},
/**
* Clear all the messages from the ValidationResult object.
*
* @method clear
* @return {ValidationResult} This object, for chaining
*/
clear: function() {
this.messages = [];
this.lookup = {};
this.isValid = true;
return this;
},
/**
* Gets all the messages or a single message by name.
*
* @method get
* @param {String} [name] Optional validator name.
* @return {Object|Array}
* {String} .name - Name of the validator that placed the message
* {String} .message - Message output by the specific validator.
*/
get: function(name) {
if (typeof (name) === "string") {
return this.lookup[name];
} else {
return this.messages;
}
}
};
/**
* Attached to each ngModelDirective is an $error_details member of this type.
* This collection contains the results for each validator attached to a ngModelDirective.
* @constructor
*/
var ExtendedModelReporting = function() {
this.data = [];
this.lookup = {};
};
ExtendedModelReporting.prototype = {
/**
* Fetch the extended validation information for a given validator
*
* @method get
* @param {String} valName Name of the validator
* @return {ValidationResult} ValidationResult object for the validator.
*/
get: function(valName) {
return this.lookup[valName];
},
/**
* Set a ValidationResult for a specific validator.
*
* @method set
* @param {String} valName Name of the validator
* @param {ValidationResult} result ValidationResult object for the validator.
*/
set: function(valName, result) {
this.data.push(result);
this.lookup[valName] = result;
},
/**
* Remove a result for a specific validator
*
* @method remove
* @param {String} valName Name of the validator
*/
remove: function(valName) {
if (!this.data.length) {
return;
}
var item = this.lookup[valName];
for (var index = this.data.length - 1; index >= 0; index--) {
if (this.data[index] === item) {
this.data.splice(index, 1);
}
}
delete this.lookup[valName];
},
/**
* Check if there are any results objects stored here.
*
* @method hasResults
* @return {Boolean} true if there are results, false otherwise.
*/
hasResults: function() {
return this.data.length > 0;
},
/**
* Clear the data so we can recalculate
*/
clear: function() {
this.data = [];
this.lookup = {};
}
};
/**
* Attached to each ngFormDirective is an $error_details member of this type.
* This collection contains the results for each field and for each validator attached to a ngFormDirective.
* @constructor
*/
var ExtendedFormReporting = function() {
this.data = {};
};
ExtendedFormReporting.prototype = {
/**
* Fetch the results for a field and validator.
*
* @method get
* @param {String} fieldName Name of the field.
* @param {String} valName Name of the validator.
* @return {ValidationResult} ValidationResult object for the validator.
*/
get: function(fieldName, valName) {
var field = this.data[fieldName] || new ExtendedModelReporting();
if (valName) {
return field.get(valName);
} else {
return field;
}
},
/**
* Set the results for a field and validator.
*
* @method set
* @param {String} fieldName Name of the field.
* @param {String} valName Name of the validator.
* @param {ValidationResult} ValidationResult object for the validator.
*/
set: function(fieldName, valName, result) {
this.data[fieldName] = this.data[fieldName] || new ExtendedModelReporting();
this.data[fieldName].set(valName, result);
return this;
},
/**
* Remove the results for a field and validator.
*
* @method remove
* @param {String} fieldName Name of the field.
* @param {String} valName Name of the validator.
*/
remove: function(fieldName, valName) {
if (this.data[fieldName]) {
this.data[fieldName].remove(valName);
if (!this.data[fieldName].hasResults()) {
this.data[fieldName] = null;
delete this.data[fieldName];
}
}
}
};
return {
// Unit Testing Only
ValidationResult: ValidationResult,
ExtendedModelReporting: ExtendedModelReporting,
ExtendedFormReporting: ExtendedFormReporting,
// Public API
/**
* Helper method to create a result structure in a uniform way.
* @param {Boolean} [clear] Optional, clear the same item if is true so it does not accumulate messages.
* Otherwise accumulate the messages.
* @return {ValidationResult}
* {Boolean} .isValid - true if the results represents a valid value, false otherwise.
* {Object} .messages - Hash of key/message pairs
*/
initializeValidationResult: function(clear) {
var result = new ValidationResult();
if (clear) {
result.clear = true;
}
return result;
},
/**
* Initialize the extended error reporting objects.
* @param {ModelController} ctrl Model controller managing the data.
* @param {FormController} [form] Optional form controller.
*/
initializeExtendedReporting: function(ctrl, form) {
ctrl.$error_details = new ExtendedModelReporting();
if (form) {
form.$error_details = new ExtendedFormReporting();
} else {
if (window.console) {
window.console.log("To participate in extended form validation you must have a ngForm or form around your controls with custom validation.");
}
}
},
/**
* Update a collection of possible error messages. This is use for multi-error aggregate validators.
*
* @param {ModelController} ctrl Model controller managing the data.
* @param {FormController} [form] Optional form controller.
* @param {String[]} names - List of validator names to check.
* @param {ValidationResult} multiResult
*/
updateExtendedReportingList: function(ctrl, form, names, multiResult) {
names.forEach(function(name) {
var error = multiResult.lookup[name];
if (error) {
var result = new ValidationResult();
result.add(error.name, error.message);
this.updateExtendedReporting(multiResult.isValid, ctrl, form, name, result);
}
}, this);
},
/**
* Updates the extended reporting based on the validity of the
* value for a specific field validator.
*
* @param {Booelan} valid true if the model is valid, false otherwise.
* @param {ModelController} ctrl Model controller managing the data.
* @param {FormController} [form] Optional form controller.
* @param {String} name
* @param {ValidationResult} result
*/
updateExtendedReporting: function(valid, ctrl, form, name, result) {
if (!valid) {
if (result.clear) {
ctrl.$error_details.remove(name);
if (form) {
form.$error_details.remove(ctrl.$name, name);
}
}
ctrl.$error_details.set(name, result);
if (form) {
form.$error_details.set(ctrl.$name, name, result);
}
} else {
ctrl.$error_details.remove(name);
if (form) {
form.$error_details.remove(ctrl.$name, name);
}
}
}
};
});
/*
# templates/mod_security/views/dynamicValidatorDirective.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
// TODO: Move to the validators folder
define(
'cjt/directives/dynamicValidatorDirective',[
"angular",
"lodash",
"cjt/validator/validator-utils"
],
function(angular, _, UTILS) {
"use strict";
/**
* Run a single rule.
* @param {Any} value Value to check.
* @param {Object} rule Rule to run, includes a name and optional argument.
* @param {ValidationResult} result ValidationResult to fill in...
* @param {Function} validateFn Validation function to call.
* @return {Boolean} true if valid, false otherwise.
*/
var runRule = function(value, rule, result, validateFn) {
return validateFn(value, rule.name, rule.arg, result);
};
/**
* Normalize a single rule.
* @param {Object|String} rule If an object, its formatted as below. If a string, its just a validation rule name.
* @param {String} [rule.name] Name of the validator.
* @param {Object} [rule.arg] Argument to the validator.
* @return {Object} Rule with both name and arg properties fully expanded.
*/
var normalizeRule = function(rule) {
if (_.isString(rule)) {
return {
name: rule,
arg: "",
};
} else {
return rule;
}
};
/**
* Normalize the list of rules.
* @param {Array} rules List of unnormalized rules.
* @return {Array} List of normalized rules. For details see normalizeRule().
*/
var normalizeRules = function(rules) {
for (var i = 0, l = rules.length; i < l; i++) {
rules[i] = normalizeRule(rules[i]);
}
return rules;
};
/**
* Runs all the validation rules on the given value.
* @param {Array} rules List of rules to run.
* @param {ModelController} ctrl Model controller where we participate in normal validation status stuff
* @param {FormController} form Form controller where extended error details are registered per validator.
* @param {Function} validateFn Aggregate validation function with signature:
* @param {Any} value Value to check.
* @param {String} name Name of the test to run. These functions may contain many tests distinguished by the various names passed.
* @param {Any} [arg] Optional argument to validation function.
* @param {ValidationResult} result Fully filled in result object.
* @return {Boolean} true if valid, false otherwise.
* @param {Any} value Value to check.
* @return {Any} Value to return, will be value passed in if valid, or undefined if invalid.
*/
var runRules = function(rules, ctrl, form, validateFn, value) {
for (var i = 0, l = rules.length; i < l; i++) {
var result = UTILS.initializeValidationResult();
var valid = (ctrl.$pristine && ctrl.$isEmpty(value)) || runRule(value, rules[i], result, validateFn);
var name = rules[i].name;
ctrl.$setValidity(name, valid);
UTILS.updateExtendedReporting(valid, ctrl, form, name, result);
}
return value;
};
var module = angular.module("cjt2.directives.dynamicValidator", []);
module.directive("dynamicValidator", function() {
return {
restrict: "A",
require: "ngModel",
link: function(scope, element, attrs, ctrl) {
var rules = attrs["dynamicValidator"] ? scope.$eval(attrs["dynamicValidator"]) : [];
var validateFn = scope.$eval(attrs["validateFn"]);
var form = element.controller("form");
UTILS.initializeExtendedReporting(ctrl, form);
if (rules && rules.length) {
rules = normalizeRules(rules);
// For DOM -> model validation
ctrl.$parsers.unshift(function(value) {
return runRules(rules, ctrl, form, validateFn, value);
});
// For model -> DOM validation
ctrl.$formatters.unshift(function(value) {
return runRules(rules, ctrl, form, validateFn, value);
});
}
}
};
});
}
);
/*
# cjt/directives/focusFirstErrorDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
// TODO: Add tests for these
/**
*
* @module cjt/directives/focusFirstErrorDirective
* @example
* <form name="form" focus-first-error>
* <input name="foo" required>
* <input name="bar" required>
* <input name="bas" required>
* </form>
*/
define('cjt/directives/focusFirstErrorDirective',["angular"], function(angular) {
var module = angular.module("cjt2.directives.focusFirstError", []);
module.directive("focusFirstError", function() {
return {
retrict: "A",
link: function(scope, elem) {
if (elem[0].tagName === "FORM") {
// set up event handler on the form element
elem.on("submit", function() {
// find the first invalid element
var candidates = angular.element(elem[0].querySelector(".ng-invalid"));
if (candidates && candidates.length > 0) {
candidates[0].focus();
}
});
} else {
throw "The focusFirstError directive can only be used on a FORM element.";
}
}
};
});
});
/*
# cjt/directives/focusInput.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/focusInput',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.focusInput", []);
module.directive("focusInput", ["$timeout", function($timeout) {
return {
link: function(scope, element, attrs) {
element.bind("click", function() {
$timeout(function() {
element.parent().parent().find("input")[0].focus();
});
});
}
};
}]);
}
);
/*
# cjt/directives/formWaiting.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/formWaiting',[
"angular",
"cjt/core"
],
function(angular, CJT) {
"use strict";
var DEFAULT_SPINNER_SIZE = 4;
var module = angular.module("cjt2.directives.formWaiting", []);
var TEMPLATE_PATH = "libraries/cjt2/directives/formWaiting.phtml";
/**
* An attribute directive that disables and overlays a mask with
* spinner on top of a form when the form is submitted. The
* directive’s value is evaluated according to these rules:
* - If it’s boolean true, set the mask on.
* - If it’s a promise, set the mask on only when the promise
* is not yet finished.
* - If it’s falsey, set the mask off.
*
* IMPORTANT: The promise’s resolution needs to interact with
* AngularJS. If you don’t use one of AngularJS’s own promises,
* then you have to imitate its interaction with AngularJS’s
* digest cycle. (maybe $apply() after a 0 $timeout?)
*
* Also, the <form> isn’t itself disabled; it’s actually
* a <fieldset> element that gets inserted into the <form> and
* wraps the given content. But there should be no functional
* difference as long as the browser supports disabled <fieldset>.
* This means that IE9 is not supported and that some other IE
* versions act strangely, but visually there’s no actual breakage.
*
* This could be implemented by disabling the <form> rather than the
* <fieldset>. (Maybe even getting rid of the <fieldset>?) This would
* allow older browsers to work better, but this way seems better
* encapsulated since we’re not altering the <form> element beyond
* the directive’s transclusion.
*
* @example
*
* Example of how to use it:
*
* <form cp-form-waiting="doSubmit()">
* ... form elements
* </form>
*
*/
module.directive("cpFormWaiting", [ "$parse", function($parse) {
return {
restrict: "A",
transclude: true,
scope: {
spinner_size: "@cpFormWaitingSpinnerSize",
},
templateUrl: CJT.config.debug ? CJT.buildFullPath(TEMPLATE_PATH) : TEMPLATE_PATH,
link: function(scope, iElement, iAttrs, controller, transcludeFn) {
var _clear_promise = function _clear_promise() {
delete scope._show_mask;
};
if (!iAttrs.cpFormWaiting) {
throw "cp-form-waiting needs an expression!";
}
// Much of the below is adapted from the AngularJS
// source (src/ng/directive/ngEventDirs.js).
var fn = $parse(iAttrs.cpFormWaiting, /* interceptorFn */ null, /* expensiveChecks */ true);
iElement.on("submit", function(event) {
var promise = fn(scope.$parent, { $event: event });
if (promise === true) {
scope._show_mask = true;
} else if (promise) {
// We can’t assume this promise
// has a .finally() method …
scope._show_mask = promise;
// It’s possible that this promise is already
// completed. $q seems to do .then() on such a
// promise after a timeout; just in case, though,
// add .then() *after* we assign to scope; that way,
// if some engine were to do .then() in in the same
// execution thread, this won’t break.
promise.then(
_clear_promise,
_clear_promise
);
} else {
_clear_promise();
}
scope.$apply();
});
},
controller: [ "$scope", function($scope) {
if (!$scope.spinner_size) {
$scope.spinner_size = DEFAULT_SPINNER_SIZE;
}
} ]
};
} ] );
// ngTransclude will always use a sub-scope. This frustrates
// the intent of this directive to be “seamless”; we don’t want
// the <form>’s transcluded content to have to be “aware” that it
// is transcluded. We use our own transclude logic so we can
// manually set the scope to the same scope that the <form> uses.
module.directive("cpFormWaitingTransclude", function() {
return {
restrict: "C",
link: function(scope, el, attr, ctrl, transclude) {
// Magic incantation lifted from ngTransclude.js
transclude(
// Why we need this directive
scope.$parent,
function(clone) {
el.append(clone);
},
null,
attr.ngTransclude || attr.ngTranscludeSlot
);
},
};
} );
}
);
/*
# cjt/directives/includeExclude.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/includeExclude',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.includeCharacters", []);
module.directive("includeCharacters", ["$parse", function($parse) {
return {
restrict: "A",
require: "ngModel",
link: function(scope, iElement, iAttrs, controller) {
var replaceRegex = new RegExp("[^" + iAttrs.includeCharacters + "]", "g");
scope.$watch(iAttrs.ngModel, function(value) {
if (!value) {
return;
}
$parse(iAttrs.ngModel).assign(scope, value.replace(replaceRegex, ""));
});
}
};
}
]);
module = angular.module("cjt2.directives.excludeCharacters", []);
module.directive("excludeCharacters", ["$parse", function($parse) {
return {
restrict: "A",
require: "ngModel",
link: function(scope, iElement, iAttrs, controller) {
var replaceRegex = new RegExp("[" + iAttrs.excludeCharacters + "]", "g");
scope.$watch(iAttrs.ngModel, function(value) {
if (!value) {
return;
}
$parse(iAttrs.ngModel).assign(scope, value.replace(replaceRegex, ""));
});
}
};
}
]);
}
);
/*
# cjt/directives/jsonFieldDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
/**
* @module cjt/directives/jsonFieldDirective
*/
define(
'cjt/directives/jsonFieldDirective',[
"angular",
"cjt/core"
],
function(angular, CJT) {
var module = angular.module("cjt2.directives.jsonFieldDirective", []);
module.directive("jsonField", [ "$document", "$compile", function($document, $compile) {
return {
restrict: "E",
scope: {
model: "="
},
link: function(scope, element, attrs) {
if ( angular.isDefined(scope.model) ) {
var model = scope.model,
newElement;
model.type = model.type.toLowerCase();
if ( angular.isDefined(model.type) && model.type === "textarea" ) {
newElement = $document[0].createElement(model.type);
} else {
newElement = $document[0].createElement("input");
}
newElement = angular.element(newElement);
if ( !angular.isDefined(model.name) && angular.isDefined(model.id) ) {
model.name = model.id;
}
if ( model.type.indexOf("date", 0) === 0 ) {
// convert the value to a date object
model.value = new Date(model.value);
}
if ( model.type !== "range" && model.type !== "color" &&
model.type !== "checkbox" && model.type !== "radio" ) {
newElement.attr("class", "form-control");
}
if ( model.type === "checkbox" ) {
newElement.attr("ng-true-value", "'true'");
newElement.attr("ng-false-value", "'false'");
}
if ( !angular.isDefined(model.value) ) {
model.value = "";
}
newElement.attr("ng-model", attrs.model + ".value");
angular.forEach(model, function(value, key) {
if ( key === "type" && value !== "textarea" || key !== "value" ) {
try {
newElement.attr(key, value);
} catch (e) {
if ( key !== "$$hashKey") {
// throw an exception on invalid keys
throw (e);
}
}
}
});
newElement = $compile(newElement)(scope.$parent);
element.replaceWith(newElement);
}
}
};
}]);
}
);
/*
* cpanel - share/libraries/cjt2/src/directives/labelSuffixDirective.js
* Copyright(c) 2020 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
/**
* Directive that shows the validation status of an input box in a form.
*
* Default behavior with no attributes specified:
* - For regular input fields: Do nothing
* - For required input fields: Show an asterisk when not filled out
*
* Required attributes
*
* for=...
* - Given an element id, associates the label-suffix with that input
* element. Think of this as analogous to 'for' in label itself.
*
* Optional attributes
*
* show-validation-status
* - Enables the display of the 3-part validation status, which includes:
* 1. (For async validators only) A spinner to let the user know the
* validation is still pending.
* 2. An X to let the user know the input is invalid.
* 3. A check mark (√) to let the user know the input is valid.
*
* @example
*
* This directive mimics the attribute naming of labels, so you can say:
*
* <label for="myField">
* [% locale.maketext('My Field') %]
* <label-suffix for="myField" show-validation-status></label-suffix>
* </label>
* <input name="myField .......
*
* Note that it is recommended for styling reasons, but not technically required,
* to place the label-suffix inside of the label.
*/
define('cjt/directives/labelSuffixDirective',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/directives/spinnerDirective",
],
function(angular, CJT, LOCALE) {
var module = angular.module("cjt2.directives.labelSuffix", [
"cjt2.templates"
]);
var RELATIVE_PATH = "libraries/cjt2/directives/labelSuffix.phtml";
module.directive("labelSuffix", ["spinnerAPI", "$timeout", function(spinnerAPI, $timeout) {
return {
restrict: "E", // label-suffix currently only works as a standalone element, but it could probably
// be adapted pretty easily to fit directly as an attribute on the label itself.
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
scope: {
fieldId: "@for",
},
require: "^form",
link: function(scope, element, attrs, form) {
scope.showValidationStatus = ( typeof attrs["showValidationStatus"] !== "undefined" );
if (!scope.fieldId) {
throw new Error("You must provide the 'for' attribute for label-suffix.");
}
/* All of this setup needs to be done in a 0 ms timeout so that it gets postponed until the
* next digest cycle. This allows sibling directives that create inputs to be initialized in
* time for us to interact with them.
*
* For example:
*
* <label for="foo">
* My Input <label-suffix for="foo"></label-suffix>
* </label>
* <my-input id="foo" name="foo" required></my-input>
*
* We ran into a problem like this with passwordFieldDirective, which wasn't consistently
* initializing the input it creates in time for this directive to check the required status.
*/
scope.form = form; // For use with the scope watch that starts/stops the spinner
/* For simplicity of being able to set up the watch without necessarily being able to locate
* the element itself yet (if it's added by a directive that hasn't been processed yet), we're
* going to assume that the field name is exactly the same as its id. This may not always be
* the case, but it should usually be. If it turns out that this is too much of a limitation,
* feel free to find an alternative approach, as long as it doesn't break compatibility with
* directives like passwordFieldDirective that delay creation of inputs until after this one
* has already finished its setup. */
scope.fieldName = scope.fieldId;
scope.spinnerId = "validationSpinner_" + scope.fieldName;
scope.showAsterisk = function() {
return scope._findInputElem() &&
scope.inputElem.prop("required") &&
(form[scope.fieldName].$pristine || !scope.inputElem.val());
};
scope.isValid = function() {
return scope.showValidationStatus &&
scope._findInputElem() &&
scope.inputElem.val() &&
!form[scope.fieldName].$pristine &&
form[scope.fieldName].$valid &&
!form[scope.fieldName].$pending;
};
scope.isInvalid = function() {
return scope.showValidationStatus &&
scope._findInputElem() &&
scope.inputElem.val() &&
!form[scope.fieldName].$pristine &&
!form[scope.fieldName].$valid &&
!form[scope.fieldName].$pending;
};
scope.text = function(name) {
switch (name) {
case "required":
return LOCALE.maketext("This value is required.");
case "valid":
return LOCALE.maketext("The value you entered is valid.");
case "invalid":
return LOCALE.maketext("The value you entered is not valid.");
case "validating":
return LOCALE.maketext("Validating …");
default:
return LOCALE.maketext("An unknown problem occurred with the validation.");
}
};
scope.$watch("form." + scope.fieldName + ".$pending", function(pending) {
if (scope.showValidationStatus) {
if (pending) {
spinnerAPI.start(scope.spinnerId);
} else {
spinnerAPI.stop(scope.spinnerId);
}
}
});
scope._findInputElem = function() {
if (!scope.inputElem || !scope.inputElem[0]) {
scope.inputElem = angular.element("#" + scope.fieldId);
}
return scope.inputElem && !!scope.inputElem[0];
};
}
};
}]);
}
);
/*
# cjt/directives/lastItem.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/lastItem',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.lastItem", []);
/**
* Directive the calls a method when it sees the last item of a repeater.
* @example
* <div ng-repeat="..." cp-last-item="done()" />
*/
module.directive("cpLastItem", ["$parse", "$timeout", function($parse, $timeout) {
return {
restrict: "A",
link: function(scope, element, attrs) {
scope.$watchGroup(["$index", "$last"], function watchLast() {
if (scope.$last && attrs.cpLastItem) {
$timeout(function() {
scope.$eval(attrs.cpLastItem);
}, 5);
}
});
}
};
}]);
}
);
/*
# cjt/directives/limitRange.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/limitRange',[
"angular",
"cjt/core",
"lodash"
],
function(angular, CJT, _) {
// Constants
var SCOPE_DECLARATION = {
rangeMinimum: "@rangeMinimum",
rangeMaximum: "@rangeMaximum",
rangeDefault: "@rangeDefault"
};
var module = angular.module("cjt2.directives.limitRange", []);
/**
* Directive that prevents numeric inputs from going outside an integer range.
* @attribute {Number} rangeMinimum - minimum value allowed.
* @attribute {Number} rangeMaximum - maximum value allowed.
* @attribute {Number} rangeDefault - default value to us if the field is invalid.
* @example
* <input limit-range range-minimum="1" range-maximum="10" />
*/
module.directive("limitRange", [ function() {
return {
restrict: "A",
require: "?ngModel",
scope: SCOPE_DECLARATION,
link: function(scope, element, attrs, model) {
/* Apply range restrictions on blur. This is better than trying to apply them
* immediately because a partially-typed number might fall outside of the allowable
* range, causing unexpected behavior. */
element.bind("blur", function(e) {
var minimum = _parseIntOrDefault(scope.rangeMinimum, null);
var maximum = _parseIntOrDefault(scope.rangeMaximum, null);
if (!this.value) {
this.value = _parseIntOrDefault(scope.rangeDefault, 1);
}
if (minimum !== null) {
if (this.value < minimum) {
this.value = minimum;
}
}
if (maximum !== null) {
if (this.value > maximum) {
this.value = maximum;
}
}
_update(model, this.value);
});
// Only allow the navigation keys and the number keys
// Key code reference: https://css-tricks.com/snippets/javascript/javascript-keycodes/
element.on("keydown", function(event) {
// Cursor movements and delete/backspace
if (_.includes([
8, // backspace
9, // tab
13, // return/enter
33, // page up
34, // page down
35, // end
36, // home
37, // left arrow
38, // up arrow
39, // right arrow
40, // down arrow
45, // insert - can be used for cut/paste
46 // delete
], event.keyCode)) {
return true;
}
// Numbers, both keypad and main number keys.
if ((event.keyCode >= 48 && event.keyCode <= 57) || (event.keyCode >= 96 && event.keyCode <= 105)) {
return true;
}
// Cut and paste: CTRL-c, CTRL-v, CTRL-x
if ((event.ctrlKey || event.metaKey) && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88)) {
return true;
}
// Otherwise, cancel the event
event.preventDefault();
return false;
});
}
};
}]);
/**
* Parse the string as an integer. If it does not return a number, use the default instead.
* @param {String} string
* @param {Number} defaultValue
* @return {Number}
*/
function _parseIntOrDefault(string, defaultValue) {
var parsed = parseInt(string, 10); // parseInt returns NaN with undefined, null, or empty strings
return isNaN(parsed) ? defaultValue : parsed;
}
/**
* If the model exists, update the model with the dynamically set value and revalidate.
* @param {Object} model
* @param {Any} value
*/
function _update(model, value) {
if (model) {
model.$setViewValue(value);
model.$validate();
}
}
});
/*
# cjt/directives/loadingPanel.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/loadingPanel',[
"angular",
"cjt/core"
],
function(angular, CJT) {
var module = angular.module("cjt2.directives.loadingPanel", []);
/**
* Directive that shows a "loading" box with a spinner and message.
* The message is specified in the body of the element. If you want
* to make this directive's visibility conditional, just use ng-show
* or similar.
*
* @example
*
* Example of how to use it:
*
* <div cp-loading-panel id="loadingUsers" ng-show="loading">
* The system is loading the user list …
* </div>
*
* Or as its own element:
*
* <cp-loading-panel id="loadingUsers" ng-show="loading">
* The system is loading the user list …
* </cp-loading-panel>
*/
module.directive("cpLoadingPanel", [
function() {
var idCounter = 0;
var RELATIVE_PATH = "libraries/cjt2/directives/loadingPanel.phtml";
return {
restrict: "EA",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
transclude: true,
scope: {
id: "@"
},
compile: function(element0, attrs0) {
return {
pre: function(scope, element, attrs) {
if (!angular.isDefined(attrs.id)) {
attrs.id = "loadingPanel" + idCounter++;
}
}
};
}
};
}
]);
}
);
/*
# mail/spam/directives/multiFieldEditor.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define */
define(
'cjt/directives/multiFieldEditor',[
"angular",
"cjt/util/locale",
"cjt/core"
],
function(angular, LOCALE, CJT) {
"use strict";
var app = angular.module("cjt2.directives.multiFieldEditor", []);
app.directive("multiFieldEditor", ["$log", function($log) {
function _link(scope, element, attr) {
scope.addNewLabel = scope.addNewLabel ? scope.addNewLabel : LOCALE.maketext("Add A New Item");
}
function _multiFieldEditorController($scope) {
this.minValuesCount = $scope.minValuesCount || 0;
this.ngModel = $scope.ngModel ? $scope.ngModel : new Array($scope.minValuesCount);
if (this.ngModel.length < this.minValuesCount) {
this.ngModel.length = this.minValuesCount;
}
this.removeRow = function(rowKey) {
if (!this.ngModel.length) {
$log.error("Attempting to remove an item from the MFE when no items are present. Likely this is because of a detachment of the referenced array. Did you do an array= somewhere?");
}
this.ngModel.splice(rowKey, 1);
};
var itemBeingAdded = -1;
this.addRow = function() {
itemBeingAdded = this.ngModel.length;
this.ngModel.length++;
};
this.getAddingRow = function() {
return itemBeingAdded;
};
angular.extend($scope, this);
}
var RELATIVE_PATH = "libraries/cjt2/directives/";
var TEMPLATES_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
var TEMPLATE = TEMPLATES_PATH + "multiFieldEditor.phtml";
return {
templateUrl: TEMPLATE,
restrict: "EA",
require: ["ngModel"],
transclude: true,
scope: {
"parentID": "@id",
"minValuesCount": "=?",
"addNewLabel": "@?",
"ngModel": "="
},
link: _link,
controller: ["$scope", _multiFieldEditorController]
};
}]);
}
);
/*
# cjt/directives/validationContainerDirective.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define(
'cjt/directives/validationContainerDirective',[
"angular",
"cjt/core",
"ngSanitize",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT) {
"use strict";
var module = angular.module("cjt2.directives.validationContainer", [
"ngSanitize", "cjt2.templates"
]);
/**
* Directive that shows a alert.
* @example
*
* To bind to a fields standard and extended validation errors:
* <ul validation-container field-name="textField">
* <li validation-item field-name="textField" validation-name="required">
* The textField is required.
* </li>
* </ul>
*
* To bind to a fields extended validation errors:
* <ul validation-container field-name="textField">
* </ul>
*
* To add a custom prefix to the item ids:
* <ul validation-container field-name="textField" prefix="foo">
* </ul>
*
* To take over rendering of the items manually use:
* <style>
* .bullet::before {
* content: "•";
* padding-right: 10px;
* font-weight: bold;
* font-size: larger;
* }
* </style>
* <ul validation-container field-name="textField" manual>
* <validation-item field-name="textField" validation-name="cidr"></validation-item>
* <validation-item field-name="textField" validation-name="cidr-details" no-icon prefix-class="bullet"></validation-item>
* </ul>
*/
module.directive("validationContainer", ["$log", function($log) {
var RELATIVE_PATH = "libraries/cjt2/directives/validationContainer.phtml";
/**
* Dynamically fetch and cache the field. Caches the field in scope
* along with the needed errors and extendedErrors collections.
*
* @method _attachField
* @private
* @param {ngForm} form Form to which the field is attached.
* @param {String} fieldName Name of the field we are monitoring.
* @param {Scope} scope Scope
* @return {ngField}
*/
function _attachField(form, fieldName, scope) {
var field = scope.field;
if (!field) {
field = form[fieldName];
if (field) {
scope.field = field;
scope.errors = field.$error;
scope.extendedErrors = field.$error_details;
}
}
return field;
}
return {
restrict: "EA",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
replace: true,
transclude: true,
scope: true,
link: function( scope, element, attrs ) {
var form = element.controller("form");
var fieldName = scope.$eval(attrs.fieldName) || attrs.fieldName;
var manual = angular.isDefined(attrs.manual) ? true : false;
var prefix = scope.$eval(attrs.prefix) || attrs.prefix || "validator";
var showWhenPristine = scope.$eval(attrs.showWhenPristine) || false;
var field = _attachField(form, fieldName, scope);
/**
* Determine if the item can be shown
*
* @method canShow
* @return {Boolean}
*/
scope.canShow = function() {
field = _attachField(form, fieldName, scope);
if (!field) {
return false;
}
return (!field.$pristine || showWhenPristine || form.$submitted) && field.$invalid;
};
/**
* Determine if the line item within the item should be shown
*
* @method canShowItem
* @param {String} key Validation key name.
* @return {Boolean}
*/
scope.canShowItem = function(key) {
if (manual) {
return false;
}
field = _attachField(form, fieldName, scope);
if (!field) {
return false;
}
return scope.errors[key] !== false && scope.hasExtendedError(key);
};
/**
* Gets a list of all validation failure message objects for the field.
*
* @method aggregateMessages
* @return {Array} An aggregated list of modified ValidationResult objects that
* are given "id" and "validatorName" properties
*/
scope.aggregateMessages = function() {
field = _attachField(form, fieldName, scope);
var messages = [];
angular.forEach(scope.errors, function(isInvalid, validatorName) {
var messageSet = _getMessageSet(validatorName);
if (isInvalid && messageSet) {
var setLength = messageSet.length;
messageSet.forEach(function(message) {
message.validatorName = validatorName;
// If there is only a single message from the set, we can use the shorter form of the ID.
// This is mainly here to maintain backwards compatability.
message.id = setLength > 1 ? _generateId(validatorName, message.name) : _generateId(validatorName);
messages.push(message);
});
} else if (isInvalid) {
// Invalid but no message set, provides debug info for developer to fix their validator
CJT.debug("[cjt2.directives.validationContainer] “" + validatorName + "” is invalid, but does not have a validation message provided. Ensure inline message was created.");
}
});
return messages;
};
/**
* Gets the set of message objects for a particular validator on the field.
*
* @method _getMessageSet
* @private
* @param {String} validatorName The name of the validator whose messages you wish to fetch
* @return {Array} An array of ValidationResult objects
*/
function _getMessageSet(validatorName) {
if ((field.$pristine && !showWhenPristine) || field.$valid) {
return;
}
if (field.$error_details) {
var details = field.$error_details.get(validatorName);
if ( details && details.hasMessages() ) {
return details.get();
} else {
return false;
}
}
}
/**
* Check if the field has extended error information for the key
*
* @method hasExtendedError
* @param {String} key Name of the category to look at.
* @return {Boolean} true if has extended error information, false otherwise.
*/
scope.hasExtendedError = function(key) {
field = _attachField(form, fieldName, scope);
if (field.$error_details) {
var details = field.$error_details.get(key);
if ( details && details.hasMessages() ) {
return true;
}
}
return false;
};
/**
* Returns a generated id for the item.
*
* @method _generateId
* @private
* @param {String} validatorName The name of the validator
* @param {String} [errorName] Optional. The name of the error/rule within the
* validator that prompted its validation failure.
* This should be provided if there the validator
* adds multiple messages to its ValidationResult.
* @return {String}
*/
function _generateId(validatorName, errorName) {
if (errorName) {
return prefix + "_" + fieldName + "_" + validatorName + "_" + errorName;
} else {
return prefix + "_" + validatorName;
}
}
}
};
}]);
}
);
/*
# mail/spam/directives/multiFieldEditorItem.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define */
define(
'cjt/directives/multiFieldEditorItem',[
"angular",
"lodash",
"cjt/util/locale",
"cjt/core",
"cjt/directives/multiFieldEditor",
"cjt/directives/validationContainerDirective"
],
function(angular, _, LOCALE, CJT) {
"use strict";
var app = angular.module("cjt2.directives.multiFieldEditorItem", []);
app.directive("multiFieldEditorItem", ["$timeout", function($timeout) {
function _link(scope, element, attr, controllers) {
scope.canRemove = _.isUndefined(scope.canRemove) || !(scope.canRemove.toString() === "0" || scope.canRemove.toString() === "false" );
var MFE = controllers.pop();
if (scope.index === MFE.getAddingRow() ) {
$timeout(function() {
MFE.itemBeingAdded = -1;
if (element.find("select").length) {
if (element.find("select").chosen) {
element.find("select").chosen()
.trigger("chosen:activate")
.trigger("chosen:open");
}
} else {
element.find("input").focus();
}
}, 10);
}
scope.requiredFieldMessage = function() {
return LOCALE.maketext("This field is required.");
};
scope.numericValueMessage = function() {
return LOCALE.maketext("This value must be numeric.");
};
scope.remove = function() {
MFE.removeRow(scope.index);
};
}
var RELATIVE_PATH = "libraries/cjt2/directives/";
var TEMPLATES_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
var TEMPLATE = TEMPLATES_PATH + "multiFieldEditorItem.phtml";
return {
templateUrl: TEMPLATE,
restrict: "EA",
require: ["^^multiFieldEditor"],
transclude: true,
scope: {
"index": "=",
"label": "@",
"labelFor": "@",
"canRemove": "=",
"parentID": "@id"
},
link: _link
};
}]);
}
);
/*
# directives/indeterminateState.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define('cjt/directives/indeterminateState',["angular"],
function(angular) {
"use strict";
var module = angular.module("cjt2.directives.indeterminateState", []);
function SetIndeterminateState() {
return {
restrict: "A",
scope: {
checkState: "&"
},
link: function(scope, element) {
scope.$watch(scope.checkState, function(newValue) {
element.prop("indeterminate", newValue);
});
}
};
}
module.directive("indeterminateState", SetIndeterminateState);
}
);
/*
# cjt/directives/ngDebounceDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
# TODO: This will not be needed if we go to 1.3.0 (ngModelOptions)
*/
/* global define: false */
// Main - reusable
// https://gist.github.com/tommaitland/7579618
/**
* Angular directive that prevents input from being processed. Useful when paired with an input filter or ajax request
* to prevent rapid calling of underlining functionality.
*/
function ngDebounce($timeout) {
return {
restrict: "A",
require: "ngModel",
priority: 99,
link: function(scope, elm, attr, ngModelCtrl) {
if (attr.type === "radio" || attr.type === "checkbox") {
return;
}
elm.unbind("input");
var debounce;
elm.bind("input", function() {
$timeout.cancel(debounce);
debounce = $timeout(function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue(elm.val());
});
}, 250);
});
elm.bind("blur", function() {
// http://stackoverflow.com/questions/12729122/prevent-error-digest-already-in-progress-when-calling-scope-apply
$timeout(function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue(elm.val());
});
});
});
}
};
}
define(
'cjt/directives/ngDebounceDirective',[
"angular"
],
function(angular) {
// Retrieve the application object
angular.module("cjt2.directives.ngDebounce", [])
.directive("ngDebounce", ["$timeout", ngDebounce]);
});
/*
# cjt/directives/onKeyupDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/onKeyupDirective',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.onKeyUp", []);
module.directive("cpKeyup", function() {
return {
restrict: "A",
scope: {
callback: "&cpKeyupAction"
},
link: function(scope, elm, attrs) {
// Determine the keys we are filtering on if any
var allowedKeys = scope.$eval(attrs.cpKeyupKeys);
elm.bind("keyup", function(evt) {
if (!allowedKeys || allowedKeys.length === 0) {
// Callback all the time
scope.callback(evt.which);
} else {
angular.forEach(allowedKeys, function(key) {
// Callback only if the key is in the filter set
if (key === evt.which) {
scope.callback(evt.which);
}
});
}
});
}
};
});
}
);
/*
# cjt/filters/qaSafeIDFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/qaSafeIDFilter',[
"angular"
],
function(angular) {
"use strict";
var app;
// Setup the App
app = angular.module("cjt2.filters.qaSafeID", []);
/**
* Filter that creates a qa-safe id for dom elements based on a string
*
* @name qaSafeID
* @param {String} value Value to filter.
* @example
*
* id="{{ "-@_test_email-id@banana.com" | qaSafeID }}" // test_email-id_banana_com
*
*/
app.filter("qaSafeID", function() {
return function(value) {
// requirements based on w3 standard
// http://www.w3.org/TR/html401/types.html#type-name
// "ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed
// by any number of letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
// colons (":"), and periods (".")."
var adjustedValue = value;
// Must Start w/ [A-Za-z]
adjustedValue = adjustedValue.replace(/^[^A-Za-z]+/, "");
// Remove Disallowed characters (based on HTML4 Standard)
// Additionally removing periods as a precaution
adjustedValue = adjustedValue.replace(/[^A-Za-z0-9-_:]/g, "_");
return adjustedValue;
};
});
}
);
/*
# cjt/directives/pageSizeDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/pageSizeDirective',[
"angular",
"lodash",
"cjt/core",
"cjt/util/parse",
"cjt/util/locale",
"cjt/filters/qaSafeIDFilter",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, _, CJT, parse, LOCALE) {
var module = angular.module("cjt2.directives.pageSize", [
"cjt2.templates"
]);
module.directive("pageSize", ["$parse", "pageSizeConfig",
function($parse, pageSizeConfig) {
var RELATIVE_PATH = "libraries/cjt2/directives/pageSizeDirective.phtml";
// A value to assign to the page size entry - 'All'
var PAGE_SIZE_ALL_VALUE = -1;
return {
restrict: "EA",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
require: "ngModel",
replace: true,
scope: {
"parentID": "@id",
"totalItems": "=",
"allowedSizes": "=",
"showAll": "=",
"autoHide": "="
},
link: function(scope, element, attrs, ngModel) {
if (!ngModel) {
return; // do nothing if no ng-model on the directive
}
function getAllowedPageSizes() {
var meta = {};
meta.allowedSizes = scope.allowedSizes || pageSizeConfig.allowedSizes;
meta.totalItems = scope.totalItems || pageSizeConfig.totalItems;
meta.showAllItems = scope.showAll;
if (scope.showAll) {
PAGE_SIZE_ALL_VALUE = scope.totalItems || -1;
}
var sizes = meta.allowedSizes.slice(0);
sizes.sort(function(a, b) {
return a - b;
});
sizes = sizes.map(function(size) {
return {
label: size,
value: size
};
});
if (meta.showAllItems) {
// Set to 'All' if no other values exist
sizes.push({
label: LOCALE.maketext("All"),
value: PAGE_SIZE_ALL_VALUE
});
}
// Removing filtering because it's breaking some interfaces
// When a page doens't have an "All" and has a totalitems
// less than the smallest size (10) it breaks.
if (sizes.length === 1 && sizes[0].value === meta.totalItems) {
scope.pageSize = sizes[0].value;
} else if (sizes.filter(function(size) {
return size.value === scope.pageSize;
}).length === 0) {
// ensure pageSize is an option, otherwise set to lowest option
scope.pageSize = sizes[0].value;
}
return sizes;
}
if (attrs.allowedSizes) {
scope.$parent.$watch($parse(attrs.allowedSizes), function() {
scope.options = getAllowedPageSizes();
});
}
if (attrs.totalItems) {
scope.$parent.$watch($parse(attrs.totalItems), function() {
scope.options = getAllowedPageSizes();
});
}
if (attrs.showAll) {
scope.$parent.$watch($parse(attrs.showAll), function() {
scope.options = getAllowedPageSizes();
});
}
ngModel.$render = function() {
scope.pageSizeTitle = LOCALE.maketext("Page Size");
scope.pageSize = ngModel.$viewValue;
};
scope.$watch("pageSize", function(newValue, oldValue) {
if (newValue === oldValue) {
return; // No update on same value;
}
if (!newValue) {
return; // New value is null or invalid
}
ngModel.$setViewValue(scope.pageSize);
});
scope.$watch("totalItems", function() {
scope.options = getAllowedPageSizes();
});
scope.options = getAllowedPageSizes();
}
};
}
]);
module.constant("pageSizeConfig", {
allowedSizes: [10, 20, 50, 100],
totalItems: 0,
showAllItems: false
});
}
);
/*
# cjt/directives/pageSizeButtonDirective.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/pageSizeButtonDirective',[
"angular",
"lodash",
"cjt/core",
"cjt/util/parse",
"cjt/util/locale",
"cjt/filters/qaSafeIDFilter",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, _, CJT, parse, LOCALE) {
"use strict";
var module = angular.module("cjt2.directives.pageSizeButton", [
"cjt2.templates"
]);
module.directive("pageSizeButton", ["$parse", "pageSizeButtonConfig",
function($parse, pageSizeConfig) {
var RELATIVE_PATH = "libraries/cjt2/directives/pageSizeButtonDirective.phtml";
// A value to assign to the page size entry - 'All'
var PAGE_SIZE_ALL_VALUE = -1;
return {
restrict: "EA",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
require: "ngModel",
replace: true,
scope: {
"parentID": "@id",
"totalItems": "=",
"allowedSizes": "=",
"showAll": "=",
},
link: function(scope, element, attrs, ngModel) {
if (!ngModel) {
return; // do nothing if no ng-model on the directive
}
function getAllowedPageSizes() {
var meta = {};
meta.allowedSizes = scope.allowedSizes || pageSizeConfig.allowedSizes;
meta.totalItems = scope.totalItems || pageSizeConfig.totalItems;
meta.showAllItems = scope.showAll;
if (scope.showAll) {
PAGE_SIZE_ALL_VALUE = scope.totalItems || -1;
}
var sizes = meta.allowedSizes.slice(0);
sizes.sort(function(a, b) {
return a - b;
});
sizes = sizes.map(function(size) {
return {
label: LOCALE.numf(size),
description: LOCALE.maketext("Show [quant,_1,entry per page,entries per page]", size),
value: size,
id: scope.parentID + "_" + size
};
});
if (meta.showAllItems) {
// Set to 'All' if no other values exist
sizes.push({
label: LOCALE.maketext("All"),
description: LOCALE.maketext("Show all entries"),
value: PAGE_SIZE_ALL_VALUE,
id: scope.parentID + "_All"
});
}
// Removing filtering because it's breaking some interfaces
// When a page doesn't have an "All" and has a totalitems
// less than the smallest size (10) it breaks.
if (sizes.length === 1 && sizes[0].value === meta.totalItems) {
scope.pageSize = sizes[0].value;
} else if (sizes.filter(function(size) {
return size.value === scope.pageSize;
}).length === 0) {
// ensure pageSize is an option, otherwise set to lowest option
scope.pageSize = sizes[0].value;
}
return sizes;
}
if (attrs.allowedSizes) {
scope.$parent.$watch($parse(attrs.allowedSizes), function() {
scope.options = getAllowedPageSizes();
});
}
if (attrs.totalItems) {
scope.$parent.$watch($parse(attrs.totalItems), function() {
scope.options = getAllowedPageSizes();
});
}
if (attrs.showAll) {
scope.$parent.$watch($parse(attrs.showAll), function() {
scope.options = getAllowedPageSizes();
});
}
ngModel.$render = function() {
scope.pageSizeTitle = LOCALE.maketext("Page Size");
scope.pageSize = ngModel.$viewValue;
};
scope.$watch("pageSize", function(newValue, oldValue) {
if (newValue === oldValue) {
return; // No update on same value;
}
if (!newValue) {
return; // New value is null or invalid
}
ngModel.$setViewValue(scope.pageSize);
});
scope.$watch("totalItems", function() {
scope.options = getAllowedPageSizes();
});
scope.options = getAllowedPageSizes();
}
};
}
]);
module.constant("pageSizeButtonConfig", {
allowedSizes: [10, 20, 50, 100],
totalItems: 0,
showAllItems: false
});
}
);
/*
# cjt/utils/passwordGenerator.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define('cjt/util/passwordGenerator',["lodash"], function(_) {
var MINIMUM_LENGTH = 5;
var MAXIMUM_LENGTH = 18;
var DEFAULT_OPTIONS = {
length: 12,
uppercase: true,
lowercase: true,
numbers: true,
symbols: true
};
var CHARACTER_SETS = {
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
lowercase: "abcdefghijklmnopqrstuvwxyz",
numbers: "0123456789",
symbols: "!@#$%^&*()-_=+{}[];,.?~"
};
var _buildCharacterSet = function(options) {
var chars = "";
if (options.uppercase) {
chars += CHARACTER_SETS.uppercase;
}
if (options.lowercase) {
chars += CHARACTER_SETS.lowercase;
}
if (options.numbers) {
chars += CHARACTER_SETS.numbers;
}
if (options.symbols) {
chars += CHARACTER_SETS.symbols;
}
return chars;
};
return {
MINIMUM_LENGTH: MINIMUM_LENGTH,
MAXIMUM_LENGTH: MAXIMUM_LENGTH,
DEFAULT_OPTIONS: DEFAULT_OPTIONS,
CHARACTER_SETS: CHARACTER_SETS,
generate: function(options) {
if (!options) {
options = {};
}
// Set the defaults
_.defaults(options, DEFAULT_OPTIONS);
// Validate the types of characters
if (!options.uppercase && !options.lowercase && !options.numbers && !options.symbols) {
throw "invalid options, you must select at lest one character set to generate from.";
}
// Validate the length and adjust as needed.
if (_.isUndefined(options.length) || !_.isNumber(options.length) || options.length < MINIMUM_LENGTH) {
options.length = DEFAULT_OPTIONS.length;
} else if (options.length > MAXIMUM_LENGTH) {
options.length = MAXIMUM_LENGTH;
}
var chars = _buildCharacterSet(options);
// generate the password
var password = "";
for (var i = 0; i < options.length; i++) {
var index = Math.floor(Math.random() * chars.length);
password += chars.substring(index, index + 1);
}
return password;
},
_buildCharacterSet: _buildCharacterSet
};
});
/*
# cjt/directives/validateMinimumPasswordStrength.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/validateMinimumPasswordStrength',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.minimumPasswordStrength", []);
/**
* Directive that checks that the password strength as returned by the backend
* service is stronger then the minimum strength required. To use, you must also
* call the checkPasswordStrength directive.
* @attribute {Number} minimumPasswordStrength Minimum strength.
* @example
* <input check-password-strength minimum-password-strength="10" />
*/
module.directive("minimumPasswordStrength", function() {
return {
require: "^ngModel",
replace: false,
priority: 5,
scope: false,
link: function(scope, elm, attrs, ngModelController) {
/**
* Validate that the strength is >= the minimum strength
*
* @method validatePasswordStrength
* @private
* @param {Number} currentPasswordStrength The users current password strength.
* @return {Boolean} true if the current strength is greater
* than the minimum strength; false otherwise.
*/
function validatePasswordStrength(currentPasswordStrength) {
var valid = (currentPasswordStrength >= scope.minimumPasswordStrength);
ngModelController.$setValidity("minimumPasswordStrength", valid);
}
// Monitor for the passwordStrengthChange event
scope.$on("passwordStrengthChange", function(evt, result) {
if (( scope.fieldId && result.id === scope.fieldId ) || // Matches the id if provided
( !scope.fieldId )) { // Or id check is skipped if not provided
var strength = result.strength;
if (!ngModelController.$validators.required &&
!ngModelController.$viewValue) {
ngModelController.$setValidity("minimumPasswordStrength", true);
} else {
// Only validate this rule if there aren't other validation errors present or if the checkStrength directive is pending.
// The checkStrenght directive uses an asyncValidator and only runs if all of the synchronous validators pass first.
// Other validation errors will set the modelValue to undef, so there is no point showing strength information.
ngModelController.$setValidity("minimumPasswordStrength", true);
if (ngModelController.$valid || (ngModelController.$pending && ngModelController.$pending.passwordStrength)) {
validatePasswordStrength(strength);
}
}
}
});
// Get the minimum password strength
scope.$watch(attrs.minimumPasswordStrength, function(newValue, oldValue, scope) {
scope.minimumPasswordStrength = scope.$eval(attrs.minimumPasswordStrength);
});
// Initially make it valid
ngModelController.$setValidity("minimumPasswordStrength", true);
}
};
});
});
/*
# cjt/directives/updatePasswordStrengthDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/updatePasswordStrengthDirective',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.updatePasswordStrength", []);
module.directive("updatePasswordStrength", function() {
return {
restrict: "A",
require: "ngModel",
replace: false,
scope: {
fieldId: "@?fieldId"
},
link: function(scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
ngModel.$render = function() {
element.attr("value", ngModel.$viewValue || "");
};
// Monitor for the passwordStrengthChange event
scope.$on("passwordStrengthChange", function(evt, result) {
if ( ( scope.fieldId && result.id === scope.fieldId ) || // Matches the id if provided
( !scope.fieldId ) ) { // Or id check is skipped if not provided
var strength = result.strength;
ngModel.$setViewValue(strength);
}
});
}
};
});
});
/*
# cjt/directives/passwordFieldDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/passwordFieldDirective',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/util/passwordGenerator",
"cjt/directives/checkStrength",
"cjt/directives/validateMinimumPasswordStrength",
"cjt/directives/updatePasswordStrengthDirective",
"cjt/directives/displayPasswordStrength",
"cjt/decorators/dynamicName",
"cjt/directives/limitRange",
"cjt/templates"
],
function(angular, CJT, LOCALE, GENERATOR) {
// Constants
var DEFAULT_MINIMUM_STRENGTH = 10;
var DEFAULT_MINIMUM_LENGTH = 1;
var DEFAULT_MAXIMUM_LENGTH = 100;
var DEFAULT_GENERATOR_MINIMUM_LENGTH = 10;
var DEFAULT_GENERATOR_MAXIMUM_LENGTH = 18;
var DEFAULT_NO_REQ_TEXT = LOCALE.translatable("This password has a strength of [_1].");
var DEFAULT_DOES_NOT_MEET_TEXT = LOCALE.translatable("This password has a strength of [_1], but your system requires a strength of [_2].");
var DEFAULT_MEETS_OR_EXCEEDS_TEXT = LOCALE.translatable("This password has a strength of [_1], which meets or exceeds the system requirement of [_2].");
var DEFAULT_PLACEHOLDER = LOCALE.maketext("Enter Password");
var DEFAULT_GENERATE_BUTTON_TEXT = LOCALE.maketext("Generate");
var DEFAULT_GENERATE_BUTTON_TITLE = LOCALE.maketext("Auto generates password.");
var DEFAULT_GENERATE_SETTINGS_TITLE = LOCALE.maketext("Adjust the generate password options.");
var DEFAULT_TOGGLE_VIEW_BUTTON_TITLE = LOCALE.maketext("Show or Hide password.");
var RELATIVE_PATH = "libraries/cjt2/directives/passwordField.phtml";
var SCOPE_DECLARATION = {
name: "@?name",
placeholder: "@?placeholder",
caption: "@?caption",
minimumStrength: "@minimumStrength",
password: "=password",
passwordStrength: "=passwordStrength",
maximumLength: "@maximumLength",
minimumLength: "@minimumLength",
showMeter: "@?showMeter",
showStrength: "@?showStrength",
showToggleView: "@?showToggleView",
strengthMeetsTemplate: "@?",
strengthDoesNotMeetTemplate: "@?",
strengthNoRequirementTemplate: "@?",
showGenerator: "@?showGenerator",
toggleViewButtonTitle: "@?",
toggleViewButtonTabIndex: "@?",
generateMinimumLength: "@?",
generateMaximumLength: "@?",
generateButtonText: "@?",
generateButtonTitle: "@?",
generateButtonTabIndex: "@?",
generateSettingsTitle: "@?",
generateSettingsTabIndex: "@?",
generateSettingsLengthLabel: "@?",
generateSettingsAlphaTitle: "@?",
generateSettingsAlphaBothLabel: "@?",
generateSettingsAlphaLowerLabel: "@?",
generateSettingsAlphaUpperLabel: "@?",
generateSettingsOtherTitle: "@?",
generateSettingsBothNumersAndSymbolsLabel: "@?",
generateSettingsNumbersLabel: "@?",
generateSettingsSymbolsLabel: "@?",
testId:"@?testId"
};
var RESERVED_ATTRIBUTES = Object.keys(SCOPE_DECLARATION);
RESERVED_ATTRIBUTES.push("id");
var module = angular.module("cjt2.directives.password", [
"cjt2.directives.checkPasswordStrength",
"cjt2.directives.minimumPasswordStrength",
"cjt2.directives.updatePasswordStrength",
"cjt2.directives.displayPasswordStrength",
"cjt2.directives.limitRange",
"cjt2.decorators.dynamicName",
"cjt2.templates"
]);
/**
* Convert the form state into the options for the generate method.
*
* @scope private
* @name makeOptions
* @param {Object} scope
* @return {Object} Password generation options.
*/
function makeOptions(scope) {
return {
length: scope.passwordLength,
lowercase: scope.alpha === "both" || scope.alpha === "lower",
uppercase: scope.alpha === "both" || scope.alpha === "upper",
numbers: scope.nonalpha === "both" || scope.nonalpha === "numbers",
symbols: scope.nonalpha === "both" || scope.nonalpha === "symbols"
};
}
/**
* Setup the scope from the default password generator options.
*
* @scope private
* @name initializeScope
* @param {Object} scope
* @param {Object} options
*/
function initializeScope(scope, options) {
scope.defaultLength = GENERATOR.DEFAULT_OPTIONS.length;
scope.length = options.length;
if (options.lowercase && options.uppercase) {
scope.alpha = "both";
} else if (options.lowercase) {
scope.alpha = "lower";
} else if (options.uppercase) {
scope.alpha = "upper";
}
if (options.numbers && options.symbols) {
scope.nonalpha = "both";
} else if (options.numbers) {
scope.nonalpha = "numbers";
} else if (options.symbols) {
scope.nonalpha = "symbols";
}
// make sure the generate can't produce an invalid length password.
if (scope.minimumLength && scope.generateMinimumLength < scope.minimumLength) {
scope.generateMinimumLength = scope.minimumLength;
}
if (scope.maximumLength && scope.generateMaximumLength > scope.maximumLength) {
scope.generateMaximumLength = scope.maximumLength;
}
}
/**
* Directive that renders the a single password field with its main decorations.
*
* @directive password
* @attribute {Number} minimumStrength Minimum strength.
* @attribute {Binding} password
* @attribute {Binding} passwordStrength
* @attribute {String} [name] optional name of the password field
* @attribute {String} [placeholder] optional placeholder text for the password
* @attribute {String} [toggleViewButtonTitle]
*
* Validation specific attributes:
* @attribute {Boolean} [showMeter] optional if truthy, then shows the meter, otherwise hides the meter. If not provided will default to true.
* @attribute {Boolean} [showStrength] optional if truthy, then shows the strength, otherwise hides the strength. If not provided will default to false.
* @attribute {String} [strengthMeetsTemplate] optional, override the meets text template
* @attribute {String} [strengthDoesNotMeetTemplate] optional, override the not meets text template
* @attribute {String} [strengthNoRequirementTemplate] optional, override the no requirement text template
* @attribute {Number} [minimumStrength] optional, minimum strength required. Strength is returnted from a server side service.
* @attribute {Number} [minimumLength] optional, minimum length for a password. 1 if not set.
* @attribute {Number} [maximumLength] optional, maximum length for a password. 100 if not set.
*
* Generator specific attributes:
* @attribute {Boolean} [showGenerator] optional, if truthy, will show the generator, otherwise, with hide the generator. Hidden by default.
* @attribute {Number} [generateMinimumLength] optional, minimum length you can generate a password. Defaults to minimumLength if not set, but minimumLength is set. Otherwise, its 1.
* @attribute {Number} [generateMaximumLength] optional, length limit for the password generator. Defaults to maximumLength if not set, but maximumLength is set. Not enforced otherwise.
* @attribute {String} [generateButtonText] optional, button text on the generate button.
* @attribute {String} [generateButtonTitle] optional, title text on the generate button.
* @attribute {String} [generateSettingsTitle] optional, title ont eh settings button.
* @attribute {String} [generateSettingsLengthLabel] optional, label on the length control.
* @attribute {String} [generateSettingsAlphaTitle] optional, label on the alpha selector as a whole.
* @attribute {String} [generateSettingsAlphaBothLabel] optional, label on the alpha both radio button.
* @attribute {String} [generateSettingsAlphaLowerLabel] optional, label on the lower only radio button.
* @attribute {String} [generateSettingsAlphaUpperLabel] optional, label on the upper only radio button.
* @attribute {String} [generateSettingsOtherTitle] optional, label on the other characters selector as a whole.
* @attribute {String} [generateSettingsBothNumersAndSymbolsLabel] optional, label on the numbers and symbols both radio button.
* @attribute {String} [generateSettingsNumbersLabel] optional, label on the numbers only radio button.
* @attribute {String} [generateSettingsSymbolsLabel] optional, label on the symbols only radio button.
*
* @example
* <input check-password-strength minimum-password-strength="10" />
*/
module.directive("password", ["$timeout", function($timeout) {
var _setDefault = function(attrs, field, def) {
if (angular.isUndefined(attrs[field])) {
attrs[field] = def;
}
};
return {
restrict: "E",
replace: true,
scope: SCOPE_DECLARATION,
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
compile: function(element, attrs) {
var inputEl = element.find("input.field");
var obscuredField = inputEl[0];
var unobscuredField = inputEl[1];
var settingsFirstEl = element.find("input.length-field");
// Copy all the attributes meant for the input
// control from the <span> to the <input> tag.
Object.keys(attrs).forEach(function(name) {
if (!(/^[$]/.test(name)) && RESERVED_ATTRIBUTES.indexOf(name) === -1) {
// Lookup the original attribute name
var markupAttrName = attrs.$attr[name];
// Move the attribute to the input tag.
inputEl.attr(markupAttrName, attrs[name] || "");
element.removeAttr(markupAttrName);
}
});
return {
pre: function(scope, element, attrs) {
_setDefault(attrs, "name", "txtPassword");
_setDefault(attrs, "placeholder", DEFAULT_PLACEHOLDER);
_setDefault(attrs, "caption", LOCALE.maketext("Select the length and characters to use when generating a password:"));
_setDefault(attrs, "showMeter", true);
_setDefault(attrs, "showToggleView", true);
_setDefault(attrs, "showStrength", true);
_setDefault(attrs, "strengthDoesNotMeetTemplate", DEFAULT_DOES_NOT_MEET_TEXT);
_setDefault(attrs, "strengthMeetsTemplate", DEFAULT_MEETS_OR_EXCEEDS_TEXT);
_setDefault(attrs, "strengthNoRequirementTemplate", DEFAULT_NO_REQ_TEXT);
_setDefault(attrs, "minimumStrength", DEFAULT_MINIMUM_STRENGTH);
_setDefault(attrs, "minimumLength", DEFAULT_MINIMUM_LENGTH);
_setDefault(attrs, "maximumLength", DEFAULT_MAXIMUM_LENGTH);
_setDefault(attrs, "showGenerator", false);
_setDefault(attrs, "toggleViewButtonTitle", DEFAULT_TOGGLE_VIEW_BUTTON_TITLE);
_setDefault(attrs, "generateMaximumLength", DEFAULT_GENERATOR_MAXIMUM_LENGTH);
_setDefault(attrs, "generateMinimumLength", DEFAULT_GENERATOR_MINIMUM_LENGTH);
_setDefault(attrs, "generateButtonText", DEFAULT_GENERATE_BUTTON_TEXT);
_setDefault(attrs, "generateButtonTitle", DEFAULT_GENERATE_BUTTON_TITLE);
_setDefault(attrs, "generateSettingsTitle", DEFAULT_GENERATE_SETTINGS_TITLE);
_setDefault(attrs, "generateSettingsLengthLabel", LOCALE.maketext("Length"));
_setDefault(attrs, "generateSettingsAlphaTitle", LOCALE.maketext("Letters"));
_setDefault(attrs, "generateSettingsAlphaBothLabel", LOCALE.maketext("Both [asis,(aBcD)]"));
_setDefault(attrs, "generateSettingsAlphaLowerLabel", LOCALE.maketext("Lowercase [asis,(abcd)]"));
_setDefault(attrs, "generateSettingsAlphaUpperLabel", LOCALE.maketext("Uppercase [asis,(ABCD)]"));
_setDefault(attrs, "generateSettingsOtherTitle", LOCALE.maketext("Numbers and Symbols"));
_setDefault(attrs, "generateSettingsBothNumersAndSymbolsLabel", LOCALE.maketext("Both [asis,(1@3$)]"));
_setDefault(attrs, "generateSettingsNumbersLabel", LOCALE.maketext("Numbers [asis,(123)]"));
_setDefault(attrs, "generateSettingsSymbolsLabel", LOCALE.maketext("Symbols [asis,(@#$)]"));
_setDefault(attrs, "testId", "");
// this needs to be initialized on the scope at this point so we
// can check it in the post
scope.showStrength = attrs.showStrength;
scope.showGenerator = attrs.showGenerator;
scope.generateMinimumLength = attrs.generateMinimumLength;
scope.generateMaximumLength = attrs.generateMaximumLength;
},
post: function(scope, element, attrs) {
scope.showSettings = false;
scope.show = false;
scope.passwordLength = GENERATOR.DEFAULT_OPTIONS.length;
scope.defaultLength = GENERATOR.DEFAULT_OPTIONS.length;
initializeScope(scope, GENERATOR.DEFAULT_OPTIONS);
/**
* Toggles the password field between obscured and hidden text.
*/
scope.toggle = function() {
scope.show = !scope.show;
$timeout(function() {
var el = angular.element(obscuredField);
if (el) {
el.focus();
}
}, 10);
};
/**
* Generate a new password and show it.
*/
scope.generate = function() {
var options = makeOptions(scope);
var newPassword = GENERATOR.generate(options);
scope.password = newPassword;
scope.show = true;
$timeout(function() {
var el = angular.element(unobscuredField);
if (el) {
el.focus();
}
}, 10);
};
/**
* Toggle the setting panel to show or hide it
*/
scope.toggleSettings = function() {
scope.showSettings = !scope.showSettings;
if (scope.showSettings) {
$timeout(function() {
var el = angular.element(settingsFirstEl);
if (el) {
el.focus();
}
}, 10);
}
};
/**
* Listen for the passwordStrengthChange event and update the currentStrengthText when fired.
*/
scope.$on("passwordStrengthChange", function(event, result) {
// Make sure the event is for our control
if ( result.id === scope.name ) {
scope.updateCurrentStrengthText(result.strength, result.password);
}
});
/**
* Updates the guiding strength text that shows up below the password input.
*
* @method updateCurrentStrengthText
* @param {Number} strength The strength of the current password, as
* returned from the passwordStrengthService.
* @param {String} password The current password.
*/
scope.updateCurrentStrengthText = function(strength, password) {
if (angular.isString(scope.minimumStrength)) {
scope.minimumStrength = parseInt(scope.minimumStrength, 10);
if (isNaN(scope.minimumStrength)) {
scope.minimumStrength = DEFAULT_MINIMUM_STRENGTH;
}
}
if (scope.showStrength) {
if (angular.isDefined(strength) && password) {
if (scope.minimumStrength > 0) {
if (strength < scope.minimumStrength) {
if (angular.isDefined(scope.strengthDoesNotMeetTemplate)) {
scope.currentStrengthText = LOCALE.makevar(scope.strengthDoesNotMeetTemplate, strength, scope.minimumStrength);
}
} else {
if (angular.isDefined(scope.strengthMeetsTemplate)) {
scope.currentStrengthText = LOCALE.makevar(scope.strengthMeetsTemplate, strength, scope.minimumStrength);
}
}
} else {
if (angular.isDefined(scope.strengthNoRequirementTemplate)) {
scope.currentStrengthText = LOCALE.makevar(scope.strengthNoRequirementTemplate, strength);
}
}
} else {
// For cases where our password model isn't populated, we don't want any text.
scope.currentStrengthText = "";
}
}
};
}
};
}
};
}]);
return {
DEFAULT_MINIMUM_STRENGTH: DEFAULT_MINIMUM_STRENGTH,
DEFAULT_DOES_NOT_MEET_TEXT: DEFAULT_DOES_NOT_MEET_TEXT,
DEFAULT_MEETS_OR_EXCEEDS_TEXT: DEFAULT_MEETS_OR_EXCEEDS_TEXT,
DEFAULT_PLACEHOLDER: DEFAULT_PLACEHOLDER,
RELATIVE_PATH: RELATIVE_PATH
};
});
/*
# cjt/directives/preventDefaultOnEnter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
# TODO: This will not be needed if we go to 1.3.0 (ngModelOptions)
*/
/* global define: false */
/**
* Angular directive that prevents default when tied to any input field. Useful for blocking accident form submission
*/
function preventDefaultOnEnter() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
elem.bind("keydown", function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
}
}
});
}
};
}
define(
'cjt/directives/preventDefaultOnEnter',[
"angular"
],
function(angular) {
// Retrieve the application object
angular.module("cjt2.directives.preventDefaultOnEnter", [])
.directive("preventDefaultOnEnter", ["$timeout", preventDefaultOnEnter]);
});
/*
# cjt/directives/preventNavigationOnBackspaceDirective.js
# Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/preventNavigationOnBackspaceDirective',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.directives.preventNavigationOnBackspace", []);
/**
* Directive that prevents navigation when the backspace key is hit.
*
* @example
* <div prevent-navigation-on-backspace />
*/
module.directive("preventNavigationOnBackspace", [
"$document",
function($document) {
return {
restrict: "A",
link: function(scope, element, attrs, ngModel) {
$document.unbind("keydown").bind("keydown", function(event) {
var doPrevent = false;
if (event.keyCode === 8) {
var target = event.srcElement || event.target;
if ((target.tagName.toUpperCase() === "INPUT" &&
(
target.type.toUpperCase() === "TEXT" ||
target.type.toUpperCase() === "PASSWORD" ||
target.type.toUpperCase() === "FILE" ||
target.type.toUpperCase() === "SEARCH" ||
target.type.toUpperCase() === "EMAIL" ||
target.type.toUpperCase() === "NUMBER" ||
target.type.toUpperCase() === "DATE" )
) ||
target.tagName.toUpperCase() === "TEXTAREA" ||
target.isContentEditable) {
doPrevent = target.readOnly || target.disabled;
} else {
doPrevent = true;
}
}
if (doPrevent) {
event.preventDefault();
}
});
}
};
}
]);
}
);
/*
** app/directives/processingIconDirective.js
** Copyright(c) 2020 cPanel, L.L.C.
** All rights reserved.
** copyright@cpanel.net http://cpanel.net
** This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/processingIconDirective',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, LOCALE) {
var module = angular.module("cjt2.directives.processingIcon", [
"cjt2.templates"
]);
var states = {
default: 0,
run: 1,
done: 2,
error: 3,
unknown: 4
};
var validStates = [
states.default,
states.run,
states.done,
states.error,
states.unknown
];
module.constant("processingIconStates", states);
var state_lookup = [
"default",
"run",
"done",
"error",
"unknown"
];
/**
* Directive that shows a processing icon in the correct state.
*
* @property {String} [defaultTitle] Override title for the default state.
* @property {String} [doneTitle] Override title for the done state.
* @property {String} [errorTitle] Override title for the error state.
* @property {String} [runTitle] Override title for the run state.
* @property {String} [unknownTitle] Override title for the unknown state.
* @property {Model} [ngModel] Model controlling which state the processing
* icon is in. It can be one of the following:
* 0 - default state
* 1 - running state
* 2 - done state
* 3 - error state
* 4 - unknown state
* @example
*
* Example of a single processing icon:
*
* <span cp-processing-icon ng-model="state"></span>
*
* @example
*
* Example of multiple processing icon with descriptions:
*
* var tasks = [
* { name: "Task 1", state: processingIconStates.run },
* { name: "Task 2", state: processingIconStates.default },
* { name: "Task 3", state: processingIconStates.done },
* ];
*
* <div>
* <span cp-processing-icon ng-model="task[0].state"></span>
* <span>Performing {{tasks[0].name}}</span>
* </div>
* <div>
* <span cp-processing-icon ng-model="task[1].state"></span>
* <span>Performing {{tasks[1].name}}</span>
* </div>
* <div>
* <span cp-processing-icon ng-model="task[2].state"></span>
* <span>Performing {{tasks[2].name}}</span>
* </div>
*/
module.directive("cpProcessingIcon", ["processingIconStates",
function(processingIconStates) {
var RELATIVE_PATH = "libraries/cjt2/directives/processingIcon.phtml";
var TITLES = {
default: "",
run: LOCALE.maketext("Running"),
done: LOCALE.maketext("Done"),
error: LOCALE.maketext("Error"),
unknown: LOCALE.maketext("Unknown"),
};
return {
restrict: "A",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
replace: true,
require: "ngModel",
scope: {
defaultTitle: "@",
runTitle: "@",
doneTitle: "@",
errorTitle: "@",
unknownTitle: "@"
},
compile: function(element, attrs) {
// Initialize any labels not provided
angular.forEach(TITLES, function(value, key) {
var attrName = key + "Title";
if (!angular.isDefined(attrs[attrName])) {
attrs[attrName] = value;
}
});
return function(scope, element, attrs, ngModelCtrl) {
/**
* Lookup the title for the state on the scope.
* @param {Number} state Numeric representation of the state.
* @return {String} Title for the state icon.
*/
var lookupTitle = function(state) {
var stateName = state_lookup[state];
var attrName = stateName + "Title";
return scope[attrName] || scope.defaultTitle;
};
scope.title = lookupTitle(ngModelCtrl.$modelValue || processingIconStates.default);
scope.state = ngModelCtrl.$modelValue || processingIconStates.default;
ngModelCtrl.$validators = function(modelValue, viewValue) {
// verify its one of the allowed values.
var value = modelValue || viewValue;
return validStates.indexOf(value) !== -1;
};
ngModelCtrl.$render = function() {
var state = ngModelCtrl.$viewValue;
scope.state = state;
scope.title = lookupTitle(state);
};
};
}
};
}
]);
}
);
/*
# cjt/directives/quickFiltersDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/quickFiltersDirective',[
"angular",
"lodash",
"cjt/core",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, _, CJT) {
var module = angular.module("cjt2.directives.quickFilters", [
"cjt2.templates"
]);
/**
* Directive that renders a quick filters wrapper
* @attribute {String} id - qa referencable id (transcluded)
* @attribute {String} title - if exists, the title pill for the filters
* @attribute {String} active - default active filter key
* @attribute {String} onFilterChange - function called on filter change
*
* @example
* <quick-filters title="[% locale.maketext('Filter:') %]" active="meta.quickFilterValue" on-filter-change="fetch()">
* <quick-filter-item value="">[% locale.maketext('All') %]</quick-filter-item>
* <quick-filter-item value="wildcards">[% locale.maketext('Wildcard Domains') %]</quick-filter-item>
* <quick-filter-item value="nonwildcards">[% locale.maketext('Non Wildcard Domains') %]</quick-filter-item>
* </quick-filters>
*/
var RELATIVE_PATH = "libraries/cjt2/directives/";
var TEMPLATES_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
module.directive("quickFilters", ["$timeout", function($timeout) {
var TEMPLATE = TEMPLATES_PATH + "quickFilters.phtml";
return {
restrict: "E",
scope: {
id: "@?id",
title: "@?title",
active: "=active",
onFilterChange: "&"
},
transclude: true,
controller: ["$scope", function($scope) {
$scope.active = $scope.active || "";
var filters = [];
this.addFilter = function addFilter(filter) {
filters.push(filter);
if (filter.value === $scope.active) {
filter.active = true;
}
};
$scope.$watch("active", function(newvalue, oldvalue) {
if (newvalue === oldvalue) {
return;
}
$scope.active = newvalue;
filters.forEach(function(filter) {
if (filter.value === newvalue) {
filter.active = true;
} else {
filter.active = false;
}
});
});
function selectFilter(selection) {
$scope.active = selection;
filters.forEach(function(filter) {
if (filter.value === selection) {
filter.active = true;
} else {
filter.active = false;
}
});
// timeout was necessary to ensure no race condition
$timeout($scope.onFilterChange.bind($scope), 10);
}
this.selectFilter = selectFilter;
}],
templateUrl: TEMPLATE
};
}]);
}
);
/*
# cjt/directives/quickFilterItemDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/quickFilterItemDirective',[
"angular",
"lodash",
"cjt/core",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, _, CJT) {
var module = angular.module("cjt2.directives.quickFilterItem", [
"cjt2.templates"
]);
/**
* Directive that renders a quick filter item
* these are then used within the quickFiltersDirective
* @attribute {String} value - qa referencable id (transcluded)
*
* @example
* <quick-filter-item value="">[% locale.maketext('All') %]</quick-filter-item>
*/
var RELATIVE_PATH = "libraries/cjt2/directives/";
var TEMPLATES_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
module.directive("quickFilterItem", function() {
var TEMPLATE = TEMPLATES_PATH + "quickFilterItem.phtml";
return {
restrict: "E",
scope: {
value: "@",
parentID: "@id",
linkTitle: "@title"
},
require: "^quickFilters",
replace: true,
transclude: true,
templateUrl: TEMPLATE,
link: function($scope, $element, $attrs, $ctrl) {
$scope.quickFilter = {
value: $attrs.value,
active: false
};
$scope.selectFilter = $ctrl.selectFilter.bind($ctrl);
$scope.isActive = function isActive() {
return $scope.quickFilter.value === $ctrl.getSelected();
};
$ctrl.addFilter($scope.quickFilter);
}
};
});
}
);
/*
# cjt/directives/selectSortDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/selectSortDirective',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, LOCALE) {
var ASCENDING = "asc";
var DESCENDING = "desc";
var DEFAULT_ASCENDING_TITLE = LOCALE.maketext("Ascending");
var DEFAULT_DESCENDING_TITLE = LOCALE.maketext("Descending");
var module = angular.module("cjt2.directives.selectSort", [
"cjt2.templates"
]);
/**
* Directive that provides a select box to sort a tabular dataset by a single column in ascending or descending order.
*
* @name cpSelectSort
* @attribute {Binding} sortMeta Meta-Data Model where the sort properties are stored.
* Sort properties required are sortDirection, sortBy, and sortType.
* @attribute {Binding} sortFields An array of sortField objects following the pattern:
* The field corresponds to the api.sort.a.field
* The sortType corresponds to api.sort.a.method, e.g. lexical, numeric, ipv4
* The sortReverse boolean will reverse the direction of the sortDirection
* property on the metadata object.
* The label is the text that will be shown on the select option.
* @attribute {Attribute} defaultField The default sort field.
* @attribute {Attribute} defaultDir The default sort direction.
* @attribute {Attribute} idSuffix The suffix for the IDs on the select and direction arrow.
* @attribute {Attribute} label The text for the label that precedes the select box.
* @attribute {Function} onsort Function triggered when a sort operation happens.
* Can handle initiating the backend request or just be used as a callback.
* @callback onsort
* @param {Reference} sortMeta Meta-Data Model where the sort properties are stored.
* Sort properties required are sortDirection, sortBy, and sortType.
* @param {Boolean} defaultSort If true, this sort is being executed because the default sort attributes
* were set and not due to user action.
*
* @example
*
* <cp-select-sort sort-meta="meta"
* onsort="sortList"
* sort-fields="sortFields"
* default-field="id"
* default-dir="desc">
* </cp-select-sort>
*
* Where:
*
* $scope.sortFields = [
* {
* field: "staged",
* label: LOCALE.maketext("Unpublished"),
* sortReverse: true
* },
* {
* field: "vendor_id",
* label: LOCALE.maketext("Vendor")
* },
* {
* field: "id",
* label: LOCALE.maketext("ID"),
* sortType: "numeric"
* }
* ];
*/
module.directive("cpSelectSort", function() {
var suffixCount = 0;
var RELATIVE_PATH = "libraries/cjt2/directives/selectSortDirective.phtml";
return {
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
restrict: "E",
scope: {
sortFields: "=",
sortMeta: "=",
sortAscendingTitle: "@",
sortDescendingTitle: "@",
onsort: "&",
label: "@"
},
controller: ["$scope", "$attrs", function($scope, $attrs) {
/**
* Gets the sort direction, as seen by the end user.
*
* @method _getDir
* @private
* @return {String} A string corresponding to the sort direction.
*/
function _getDir() {
// If we're not reversing, just provide the direction as-is. Otherwise, provide the opposite.
return !$scope.fieldMap[$scope.sortMeta.sortBy].sortReverse ?
$scope.sortMeta.sortDirection : $scope.sortMeta.sortDirection === ASCENDING ?
DESCENDING : ASCENDING;
}
$scope.getDir = _getDir;
/**
* Sets the sort direction.
*
* @method _setDir
* @private
* @param {String} newdir The new direction for the sorting.
*/
function _setDir(newdir) {
// If we're not reversing, just set with the new direction. Otherwise, set with the opposite.
$scope.sortMeta.sortDirection = !$scope.fieldMap[$scope.sortMeta.sortBy].sortReverse ?
newdir : newdir === ASCENDING ?
DESCENDING : ASCENDING;
return _getDir();
}
/**
* Get the title text for the sort direction control.
*
* @method getTitle
*/
$scope.getTitle = function() {
return _getDir() === ASCENDING ? $attrs["sortAscendingTitle"] : $attrs["sortDescendingTitle"];
};
/**
* Changes the sort direction and executes the onsort callback if defined.
*
* @method sort
* @param {Boolean} changeDir If true, the sort direction will flip.
* @param {Boolean} defaultSort If true, this is an initial sort triggered because of default
* values passed in as attributes.
*/
$scope.sort = function(changeDir, defaultSort) {
var meta = $scope.sortMeta;
// Update the sort direction
if (changeDir) {
meta.sortDirection = meta.sortDirection === ASCENDING ? DESCENDING : ASCENDING; // Just flipping this around, so no need to use the setter/getter
} else if (!defaultSort) {
_setDir(ASCENDING); // Changing sort fields, so go back to ascending
}
// Update the sortType
meta.sortType = $scope.fieldMap[meta.sortBy].sortType;
// Make sure onsort exists on the parent scope before executing it
var onsort = $scope.onsort();
if (angular.isFunction(onsort)) {
onsort(meta, defaultSort);
}
};
// Set up a map to easily access the sortField objects.
// The array of sortFields is necessary so that the order can be preserved in the select.
$scope.fieldMap = {};
$scope.sortFields.forEach(function(obj) {
$scope.fieldMap[obj.field] = obj;
});
// Set the suffix from the attribute or generic counter
$scope.idSuffix = $attrs.idSuffix || suffixCount++;
// Set the metadata object properties if defaults have been provided.
// Process the defaultField if it exists
if ($attrs.defaultField) {
// Check if the default sort field is valid
$scope.validDefaultProvided = $scope.sortFields.some(function(sortField) {
return sortField.field === $attrs.defaultField;
});
// If valid, set the default sort field
if ($scope.validDefaultProvided) {
$scope.sortMeta.sortBy = $attrs.defaultField;
}
}
// Set the sortBy to the first item in the sortFields array if it's not
// set in the metadata object and a default field wasn't provided.
if (!$scope.sortMeta.sortBy) {
$scope.sortMeta.sortBy = $scope.sortFields[0].field;
}
// Process the default sort direction if it exists
if ($attrs.defaultDir === DESCENDING || $attrs.defaultDir === ASCENDING) {
_setDir($attrs.defaultDir);
$scope.validDefaultProvided = true;
}
// If there's no defaultDir and the direction isn't set in the metadata, let's default to asc
else if ($scope.sortMeta.sortDirection !== DESCENDING && $scope.sortMeta.sortDirection !== ASCENDING) {
_setDir(ASCENDING);
}
}],
compile: function(element, attributes) {
// Set the asc/desc title attributes if they aren't set
if (!attributes["sortAscendingTitle"]) {
attributes["sortAscendingTitle"] = DEFAULT_ASCENDING_TITLE;
}
if (!attributes["sortDescendingTitle"]) {
attributes["sortDescendingTitle"] = DEFAULT_DESCENDING_TITLE;
}
// Set the label attribute if it isn't set
if (!attributes["label"]) {
attributes["label"] = LOCALE.maketext("Sort by");
}
return function(scope, element, attrs) {
// Once everything is set up, go ahead and fire the callback if
// valid defaults were provided.
if (scope.validDefaultProvided) {
scope.sort(false, true);
}
};
},
};
});
}
);
/*
# cjt/directives/responsiveSortInsertDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/responsiveSortInsertDirective',[
"angular",
"cjt/core",
"cjt/directives/selectSortDirective",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT) {
var module = angular.module("cjt2.directives.responsiveSortInsert", [
"cjt2.directives.responsiveSort",
"cjt2.templates"
]);
/**
* This directive is meant to only be used as a child of the responsiveSortDirective.
* The directive element will be replaced by a generated cp-select-sort directive.
* See the documentation for the cp-responsive-sort directive for more information on
* its available attributes and options.
*
* @name cp-responsive-sort-insert
* @attribute {Attribute} idSuffix The suffix use for the IDs on the select element and direction arrow.
* @attribute {Attribute} label The text for the label that precedes the select box.
*
* @example
*
* <li class="list-table-header row" cp-responsive-sort>
* <span class="visible-xs col-xs-8">
* <cp-responsive-sort-insert default-field="id" default-dir="desc"></cp-responsive-sort-insert>
* </span>
* <span class="hidden-xs col-sm-2">
* <toggle-sort id="sortVendor"
* class="nowrap"
* onsort="sortList"
* sort-meta="meta"
* sort-field="vendor_id">
* [% locale.maketext('Vendor') %]
* </toggle-sort>
* </span>
*/
module.directive("cpResponsiveSortInsert", ["$http", "$compile", "$interpolate", "$templateCache", function($http, $compile, $interpolate, $templateCache) {
var RELATIVE_PATH = "libraries/cjt2/directives/responsiveSortInsertDirective.phtml";
return {
restrict: "E",
scope: true,
require: "^^cpResponsiveSort",
compile: function() {
return function(scope, element, attrs, parentController) {
scope.selectSort.attrs.idSuffix = attrs.idSuffix;
scope.selectSort.attrs.label = attrs.label;
/**
* Interpolates values from scope.selectSort into the directive template,
* compiles the interpolated template, and inserts it into the DOM.
*
* @method _interpolateAndInsert
* @param {Object} scope The directive scope.
* @param {String} template The directive template.
*/
function _interpolateAndInsert(scope, template) {
// Interpolation needs to be explicitly executed before the compile step
// because the selectSortDirective is not set up to $observe its attributes
// and will error out if it sees eval expressions on scope-bound attributes.
template = $interpolate(template)(scope.selectSort);
template = $compile(template)(scope);
element.replaceWith(template);
}
var templateURL = RELATIVE_PATH;
// Check the templateCache to see if we already have the template. If not,
// go grab it. We can't use the regular template/templateURL directive
// definition properties because we need to interpolate manually.
var template = $templateCache.get(templateURL);
if (template) {
_interpolateAndInsert(scope, template);
} else {
$http.get(CJT.buildFullPath(templateURL))
.success(function(template) {
_interpolateAndInsert(scope, template);
$templateCache.put(templateURL, template);
});
}
};
}
};
}]);
}
);
/*
# cjt/directives/toggleSortDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
// ------------------------------------------------------------
// Developer notes:
// ------------------------------------------------------------
// The concept for this construct was derived from:
// http://nadeemkhedr.wordpress.com/2013/09/01/build-angularjs-grid-with-server-side-paging-sorting-filtering/
// Use with permission.
// ------------------------------------------------------------
define(
'cjt/directives/toggleSortDirective',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, LOCALE) {
// Constants
var ASCENDING = "asc";
var DESCENDING = "desc";
var DEFAULT_ASCENDING_TITLE = LOCALE.maketext("Ascending");
var DEFAULT_DESCENDING_TITLE = LOCALE.maketext("Descending");
var module = angular.module("cjt2.directives.toggleSort", [
"cjt2.templates"
]);
/**
* Directive that helps with column sorting for tabular datasets.
*
* @name toggleSort
* @attribute {Binding} sortMeta Meta-Data Model where the current sort information is stored.
* This object includes the following fields:
* {String} sortBy - current field.
* {String} sortDirection - current sort direction for that field.
* {String} sortType - type of sort for that field.
* @attribute {Value} sortField The name of the field in the model to sort when this is active.
* @attribute {Value} [sortType] Optional, the type of sort to perform for this field. e.g. lexical, numeric, defaults to "" which lets the server decide the sort algorithm.
* @attribute {Value} [sortReverse] Optional, if true, inverts the display logic for ascending and descending for the arrow.
* @attribyte {Boolean} [sortReverseDefault] Optional. If given, the default sort for this column will be descending rather than ascending.
* @attribute {Function} [onsort] Optional function triggered when a sort operation happens. Has the following callback signature:
*
* function sort(sortMeta) {
* // your code
* }
*
* where sortMeta has the same fields as the sortMeta attribute above.
* @example
*
* In your markup:
*
* <div toggle-sort
* onsort="sortList"
* sort-meta="meta"
* sort-field="db">
* Database
* </div>
*
* <div toggle-sort
* onsort="sortList"
* sort-meta="meta"
* sort-type="numeric"
* sort-field="id">
* ID
* </div>
*
* In your JavaScript initially set
*
* $scope.meta = {
* sortBy: "id",
* sortDirection: "asc",
* sortType: ""
* };
*
* $scope.sortList = function(meta) {
* // update html history
* // trigger backend call
* // apply a filter or some other
* // operation based on the sort properties.
* };
*
*/
module.directive("toggleSort", function() {
var RELATIVE_PATH = "libraries/cjt2/directives/toggleSortDirective.phtml";
return {
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
restrict: "EA",
transclude: true,
replace: true,
scope: {
sortMeta: "=",
sortType: "@",
sortField: "@",
sortReverse: "@",
sortAscendingTitle: "@",
sortDescendingTitle: "@",
sortReverseDefault: "@",
onsort: "&"
},
compile: function(element, attributes) {
if (!attributes["sortAscendingTitle"]) {
attributes["sortAscendingTitle"] = DEFAULT_ASCENDING_TITLE;
}
if (!attributes["sortDescendingTitle"]) {
attributes["sortDescendingTitle"] = DEFAULT_DESCENDING_TITLE;
}
return function(scope, element, attributes) {
/**
* Get the title text for the sort direction control.
*
* @method getTitle
*/
scope.getTitle = function() {
return _getDir() === ASCENDING ? attributes["sortAscendingTitle"] : attributes["sortDescendingTitle"];
};
/**
* Gets the sort direction, as seen by the end user
*
* @method _getDir
* @private
* @return {String} A string corresponding to the sort direction.
*/
function _getDir() {
return !angular.isDefined(scope.sortReverse) ?
scope.sortMeta.sortDirection : scope.sortMeta.sortDirection === ASCENDING ?
DESCENDING : ASCENDING;
}
scope.getDir = _getDir;
/**
* Sets the sort direction
*
* @method _setDir
* @private
* @param {String} newdir The new direction for the sorting.
*/
function _setDir(newdir) {
scope.sortMeta.sortDirection = !angular.isDefined(scope.sortReverse) ?
newdir : newdir === ASCENDING ?
DESCENDING : ASCENDING;
return _getDir();
}
/**
* Toggle the sort direction on the selected column
*/
scope.sort = function() {
var meta = scope.sortMeta;
if (meta.sortBy === scope.sortField) {
meta.sortDirection = meta.sortDirection === ASCENDING ? DESCENDING : ASCENDING; // Just flipping this around, so no need to use the setter/getter
} else {
meta.sortBy = scope.sortField;
_setDir( angular.isUndefined(scope.sortReverseDefault) ? ASCENDING : DESCENDING );
meta.sortType = scope.sortType;
}
// Make sure onsort exists on the parent scope before executing it
var onsort = scope.onsort();
if (angular.isFunction(onsort)) {
onsort(meta);
}
};
};
}
};
});
}
);
/*
# cjt/directives/responsiveSortDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/responsiveSortDirective',[
"angular",
"cjt/core",
"cjt/directives/responsiveSortInsertDirective",
"cjt/directives/toggleSortDirective",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT) {
var module = angular.module("cjt2.directives.responsiveSort", [
"cjt2.templates"
]);
/**
* This directive wraps a set of toggleSort directives and generates/inserts a cp-select-sort
* directive into the wrapping element. See the documentation for the cp-responsive-sort-insert,
* cp-select-sort, and toggleSort directives for more information. Note that the toggleSort
*
* @name cp-responsive-sort
* @attribute {Attribute} defaultField The default sort field for the selectSort.
* @attribute {Attribute} defaultDir The default sort direction for the selectSort.
*
* @example
*
* <li class="list-table-header row" cp-responsive-sort>
* <span class="visible-xs col-xs-8">
* <cp-responsive-sort-insert default-field="id" default-dir="desc"></cp-responsive-sort-insert>
* </span>
* <span class="hidden-xs col-sm-2">
* <toggle-sort id="sortVendor"
* class="nowrap"
* onsort="sortList"
* sort-meta="meta"
* sort-field="vendor_id">
* [% locale.maketext('Vendor') %]
* </toggle-sort>
* </span>
* <span class="hidden-xs col-sm-2">
* <toggle-sort id="sortID"
* class="nowrap"
* onsort="sortList"
* sort-meta="meta"
* sort-type="numeric"
* sort-field="id">
* [% locale.maketext('ID') %]
* </toggle-sort>
* </span>
* <span class="hidden-xs col-sm-2">
* <toggle-sort id="sortMessage"
* class="nowrap"
* onsort="sortList"
* sort-dir="meta"
* sort-field="meta_msg">
* [% locale.maketext('Message') %]
* </toggle-sort>
* </span>
* </li>
*
* In this example the cp-responsive-sort directive is wrapping 3 toggle-sort directives.
* The generated selectSort directive will be inserted where the cp-responsive-sort-insert
* element is placed.
*/
module.directive("cpResponsiveSort", function() {
return {
restrict: "A",
scope: true,
// This controller is basically just here to enforce the parent-child relationship.
controller: function() {},
// The compile function is used because we need to access the DOM before the
// toggleSort directives are processed. Otherwise, the transcluded text will
// be missing and we cannot derive our option labels.
compile: function(element, attrs) {
// This obj will eventually have consolidated sortBy, sortDir, and onsort keys
// for the selectSort directive. The sortFields key is an array of sortField
// objects which are consumed by the selectSort directive.
var parsed = {
sortFields: []
};
// Generate an array of objects from the toggleSort directives that are
// found within the element. These objects contain key/val pairs of the
// associated attributes.
var toggleSorts = Array.prototype.map.call(element.find("toggle-sort"), function(elem) {
elem = angular.element(elem);
return {
onsort: elem.attr("onsort"),
sortType: elem.attr("sort-type"),
sortReverse: angular.isDefined(elem.attr("sort-reverse")),
sortMeta: elem.attr("sort-meta"),
sortField: elem.attr("sort-field"),
sortLabel: elem.text().trim()
};
});
// Go through the list of bound attributes for each toggleSort found and
// ensure that they are uniform. Put all of the validated data into the
// "parsed" var.
toggleSorts.forEach(function(toggleSort) {
["sortMeta", "onsort"].forEach(function(property) {
// Ignore omitted onsort attributes
if (!toggleSort.onsort) {
return;
}
if (!parsed[property]) { // For the first run-through
if (toggleSort[property]) {
parsed[property] = toggleSort[property];
} else {
throw new ReferenceError("Malformed/incomplete toggle-sort directive found in descendant tree. Responsive sort directive cannot proceed.");
}
} else if (toggleSort[property] !== parsed[property]) {
throw new Error("The responsive sort directive cannot handle more than one " + property + " property at a time.");
}
});
// Create the actual sortField object to be inserted into the array
if (toggleSort.sortField && toggleSort.sortLabel) {
parsed.sortFields.push({
label: toggleSort.sortLabel,
field: toggleSort.sortField,
sortType: toggleSort.sortType,
sortReverse: toggleSort.sortReverse
});
} else {
throw new ReferenceError("Malformed/incomplete toggle-sort directive found in descendant tree. Responsive sort directive cannot proceed.");
}
});
return {
// The scope needs to be set up in the pre-linking function because the
// post-linking order will start with the child directive and thus the
// scope wouldn't be ready.
pre: function link(scope, element, attrs) {
// Set up the view model for the selectSort
scope.selectSort = {
parsed: parsed,
attrs: {
defaultField: attrs.defaultField,
defaultDir: attrs.defaultDir
}
};
}
};
}
};
});
}
);
/*
# cjt/directives/searchDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/searchDirective',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/directives/preventDefaultOnEnter",
"cjt/directives/autoFocus",
"cjt/filters/qaSafeIDFilter",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, LOCALE) {
var DEFAULT_PLACEHOLDER = LOCALE.maketext("Search");
var DEFAULT_TITLE = LOCALE.maketext("Search");
var DEFAULT_AUTO_FOCUS = false;
var DEFAULT_DEBOUNCE = 250;
var RELATIVE_PATH = "libraries/cjt2/directives/searchDirective.phtml";
var module = angular.module("cjt2.directives.search", [
"cjt2.templates",
"cjt2.directives.preventDefaultOnEnter",
"cjt2.directives.autoFocus"
]);
module.directive("search", function() {
return {
restrict: "E",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
require: "ngModel",
replace: true,
scope: {
parentID: "@id",
placeholder: "@?placeholder",
autofocus: "@?autofocus",
title: "@?title",
debounce: "@?debounce"
},
compile: function() {
return {
pre: function(scope, element, attrs) { // eslint-disable-line no-unused-vars
if (angular.isUndefined(attrs.placeholder)) {
attrs.placeholder = DEFAULT_PLACEHOLDER;
}
if (angular.isUndefined(attrs.title)) {
attrs.title = DEFAULT_TITLE;
}
if (angular.isUndefined(attrs.autofocus)) {
attrs.autofocus = DEFAULT_AUTO_FOCUS;
} else {
attrs.autofocus = true;
}
if (angular.isUndefined(attrs.debounce)) {
attrs.debounce = DEFAULT_DEBOUNCE;
}
scope.autofocus = attrs.autofocus;
scope.placeholder = attrs.placeholder;
scope.title = attrs.title;
scope.debounce = Number(attrs.debounce);
scope.ariaLabelSearch = LOCALE.maketext("Search");
scope.ariaLabelClear = LOCALE.maketext("Clear");
scope.modelOptions = { debounce: scope.debounce };
},
post: function(scope, element, attrs, ctrls) { // eslint-disable-line no-unused-vars
var ngModelCtrl = ctrls;
if (!ngModelCtrl) {
return; // do nothing if no ng-model on the directive
}
ngModelCtrl.$render = function() {
scope.filterText = ngModelCtrl.$viewValue;
};
scope.clear = function(event) {
if (event.keyCode === 27) {
scope.filterText = "";
}
};
scope.$watch("filterText", function() {
ngModelCtrl.$setViewValue(scope.filterText);
});
}
};
}
};
});
return {
DEFAULT_PLACEHOLDER: DEFAULT_PLACEHOLDER,
RELATIVE_PATH: RELATIVE_PATH,
DEFAULT_DEBOUNCE: DEFAULT_DEBOUNCE,
};
}
);
/*
# limitDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/statsDirective',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/util/parse",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, LOCALE, PARSE) {
"use strict";
var module = angular.module("cjt2.directives.statsDirective", [
"cjt2.templates"
]);
/**
* Directive that helps to display account limit statistics
*
* @name stats
* @attribute {}
* @example
*
* <stats
* used-id=""
* used=""
* available-id=""
* available=""
* upgrade-link-id=""
* upgrade-link=""
* upgrade-link-text="CLICK ME"
* show-upgrade-link=""
* upgrade-link-target="">
* </stats>
*/
module.directive("stats", ["$parse", function($parse) {
var RELATIVE_PATH = "libraries/cjt2/directives/statsDirective.phtml";
var ctr = 0;
var DEFAULT_CONTROL_NAME = "stats";
var DEFAULT_USED_ID = "lbl";
var DEFAULT_AVAILABLE_ID = "lbl";
var DEFAULT_UPGRADE_LINK_ID = "upgradeLinkID";
var DEFAULT_SHOW_UPGRADE_LINK = false;
return {
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
restrict: "E",
replace: true,
require: "?ngModel",
scope: {
upgradeLink: "@",
upgradeLinkTarget: "@",
upgradeTooltip: "@",
upgradeLinkText: "@",
max: "=",
showWarningDetails: "&onShowWarningDetails",
},
link: {
pre: function(scope, elem, attrs, ngModel) {
if (!ngModel) {
return; // do nothing if no ng-model
}
var id = angular.isDefined(attrs.id) && attrs.id !== "" ? attrs.id : DEFAULT_CONTROL_NAME + ctr++;
scope.usedID = angular.isDefined(attrs.usedId) && attrs.usedId !== "" ? attrs.usedId : (id + "used" || DEFAULT_USED_ID) + ctr++;
scope.availableID = angular.isDefined(attrs.availableId) && attrs.availableId !== "" ? attrs.availableId : (id + "available" || DEFAULT_AVAILABLE_ID) + ctr++;
scope.upgradeLinkID = angular.isDefined(attrs.upgradeLinkId) && attrs.upgradeLinkId !== "" ? attrs.upgradeLinkId : ("lnkUpgrade" + id || DEFAULT_UPGRADE_LINK_ID) + ctr++;
scope.showUpgradeLink = angular.isDefined(attrs.showUpgradeLink) && attrs.showUpgradeLink !== "" ? PARSE.parseBoolean(attrs.showUpgradeLink) : DEFAULT_SHOW_UPGRADE_LINK;
scope.upgradeLink = angular.isDefined(attrs.upgradeLink) && attrs.upgradeLink !== "" ? attrs.upgradeLink : "";
scope.upgradeTooltip = angular.isDefined(attrs.upgradeTooltip) && attrs.upgradeTooltip !== "" ? attrs.upgradeTooltip : LOCALE.maketext("Upgrade");
scope.upgradeLinkText = angular.isDefined(attrs.upgradeLinkText) && attrs.upgradeLinkText !== "" ? attrs.upgradeLinkText : LOCALE.maketext("Upgrade");
scope.usedTitle = LOCALE.maketext("Used");
scope.availableTitle = LOCALE.maketext("Available");
scope.viewDetailsText = LOCALE.maketext("Details");
scope.detailsTooltip = LOCALE.maketext("View Warning Details");
scope.max = angular.isDefined(scope.max) ? scope.max : -1;
function updateValues(value) {
scope.usedValue = angular.isDefined(value) && value !== "" ? LOCALE.numf(value) : 0;
if (scope.max > -1) {
scope.availableValue = LOCALE.numf(scope.max - value);
scope.showWarning = (scope.max - value) === 0;
} else {
scope.availableValue = "∞";
scope.showWarning = false;
}
}
scope.$watch(
function() {
return ngModel.$modelValue;
},
updateValues
);
}
}
};
}]);
}
);
/*
# cjt/util/uaDetect.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
/**
* This module is meant to house user agent (UA) detection logic.
*
* Ordinarily we shouldn’t depend on things like this,
* but there are some applications where there’s not a (known) better
* alternative.
*/
define(
'cjt/util/uaDetect',[],function() {
"use strict";
var _UAD = {
isMacintosh: function _isMacintosh() {
return (_UAD.__window.navigator.platform.indexOf("Mac") === 0);
},
// for mocking
__window: window,
};
return _UAD;
}
);
/*
# cjt/io/websocket.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
// Expand this later as necessary to include metadata.
define('cjt/io/websocket',["cjt/core"], function(CJT) {
"use strict";
// Taken from CPAN Net::WebSocket
var _STATUS = {
SUCCESS: 1000,
ENDPOINT_UNAVAILABLE: 1001,
PROTOCOL_ERROR: 1002,
INVALID_DATA_TYPE: 1003,
// These two never actually go over the wire,
// but they’re how browsers report these conditions.
EMPTY: 1005,
ABORTED: 1006,
INVALID_PAYLOAD: 1007,
POLICY_VIOLATION: 1008,
MESSAGE_TOO_BIG: 1009,
UNSUPPORTED_EXTENSIONS: 1010,
INTERNAL_ERROR: 1011,
SERVICE_RESTART: 1012,
TRY_AGAIN_LATER: 1013,
BAD_GATEWAY: 1014,
};
var _WS = {
MODULE_NAME: "cjt/io/websocket",
MODULE_DESC: "WebSocket tools for cPanel UIs",
MODULE_VERSION: "1.0",
/**
* A lookup of status name to code.
* You’ll probably want this for the “SUCCESS” code.
*/
STATUS: _STATUS,
/**
* Returns the “base” URL for a websocket app;
* e.g., if the page URL is:
*
* https://some.server:2087/cpsess12345678/app/index.html
*
* then this will give:
*
* wss://some.server:2087/cpsess12345678
*/
getUrlBase: function _getUrlBase() {
var protocol = _WS.__window.location.protocol;
if (/^https?:$/.test(protocol)) {
protocol = protocol.replace(/^http/, "ws");
} else {
throw new Error( "Unknown “location.protocol”: [_]".replace(/_/, protocol) );
}
return protocol + "//" + _WS.__window.location.host + CJT.securityToken;
},
/**
* Returns a nicely-formatted string from a non-SUCCESS
* close event. This string is suitable to show in the UI.
*/
getErrorString: function _getErrorString(event) {
var name = this._getStatusName(event.code);
var reason = event.reason;
var str = name || event.code;
if (reason) {
str += ": " + reason;
}
return str;
},
// To facilitate mocking
__window: window,
_getStatusName: function _getStatusName(code) {
for (var name in _STATUS) {
if (_STATUS[name] === code) {
return name;
}
}
},
};
return _WS;
} );
/*
# cjt/io/appstream.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
define('cjt/io/appstream',[],function() {
"use strict";
return {
MODULE_NAME: "cjt/io/appstream",
MODULE_DESC: "JavaScript implementation of the “AppStream” protocol, (cf. Cpanel::Server::WebSocket::AppStream)",
MODULE_VERSION: "1.0",
encodeDataPayload: function _encodeDataPayload(payload) {
if (payload.indexOf(".") === 0) {
payload = "." + payload;
}
return payload;
},
encodeControlPayload: function _encodeControlPayload(payload) {
if (payload.indexOf(".") === 0) {
throw new Error("control payload can’t start with “.”: " + payload);
}
return "." + payload;
},
};
} );
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('xterm/addons/fit/fit',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fit=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});function proposeGeometry(term){if(!term.element.parentElement){return null}var parentElementStyle=window.getComputedStyle(term.element.parentElement);var parentElementHeight=parseInt(parentElementStyle.getPropertyValue("height"));var parentElementWidth=Math.max(0,parseInt(parentElementStyle.getPropertyValue("width")));var elementStyle=window.getComputedStyle(term.element);var elementPadding={top:parseInt(elementStyle.getPropertyValue("padding-top")),bottom:parseInt(elementStyle.getPropertyValue("padding-bottom")),right:parseInt(elementStyle.getPropertyValue("padding-right")),left:parseInt(elementStyle.getPropertyValue("padding-left"))};var elementPaddingVer=elementPadding.top+elementPadding.bottom;var elementPaddingHor=elementPadding.right+elementPadding.left;var availableHeight=parentElementHeight-elementPaddingVer;var availableWidth=parentElementWidth-elementPaddingHor-term.viewport.scrollBarWidth;var geometry={cols:Math.floor(availableWidth/term.renderer.dimensions.actualCellWidth),rows:Math.floor(availableHeight/term.renderer.dimensions.actualCellHeight)};return geometry}exports.proposeGeometry=proposeGeometry;function fit(term){var geometry=proposeGeometry(term);if(geometry){if(term.rows!==geometry.rows||term.cols!==geometry.cols){term.renderer.clear();term.resize(geometry.cols,geometry.rows)}}}exports.fit=fit;function apply(terminalConstructor){terminalConstructor.prototype.proposeGeometry=function(){return proposeGeometry(this)};terminalConstructor.prototype.fit=function(){fit(this)}}exports.apply=apply},{}]},{},[1])(1)});
/*
* # cjt/services/onBeforeUnload.js Copyright 2022 cPanel, L.L.C.
* # All rights reserved.
* # copyright@cpanel.net http://cpanel.net
* # This code is subject to the cPanel license. Unauthorized copying is prohibited
* */
/* global define: false */
define('cjt/services/onBeforeUnload',[
"angular",
],
function(angular) {
"use strict";
/* A tiny service to listen for window.onbeforeunload and record
* when that event has happened.
*
* Example usage:
*
* var windowIsUnloading = onBeforeUnload.windowIsUnloading();
*/
var module = angular.module("cjt2.services.onBeforeUnload", []);
var windowIsUnloading = false;
function _windowIsUnloading() {
return windowIsUnloading;
}
function _onBeforeUnload() {
windowIsUnloading = true;
}
window.addEventListener("beforeunload", _onBeforeUnload);
module.factory("onBeforeUnload", function() {
return {
_onBeforeUnload: _onBeforeUnload, // exposed for tests
windowIsUnloading: _windowIsUnloading,
};
} );
});
/*
# cjt/directives/terminal.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* ----------------------------------------------------------------------
INSTRUCTIONS FOR THIS DIRECTIVE:
You must independently load xterm.js’s CSS file, e.g.:
/libraries/xtermjs/xterm.min.css
/frontend/<theme-name>/libraries/xtermjs/xterm.min.css
The following goes in your AngularJS template:
<cp-terminal></cp-terminal>
This reports alerts to the “myalerts” alert group (cf. the cp-alert-list
directive).
For now there’s not much flexibility because it’s only being used in
contexts where the directive basically *is* the application.
---------------------------------------------------------------------- */
/* global define: false */
define(
'cjt/directives/terminal',[
"lodash",
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/util/uaDetect",
"cjt/util/query",
"cjt/io/websocket",
"cjt/io/appstream",
"xterm",
"xterm/addons/fit/fit",
"cjt/modules",
"cjt/services/alertService",
"cjt/directives/alert",
"cjt/directives/alertList",
"cjt/services/onBeforeUnload",
"uiBootstrap",
],
function(_, angular, CJT, LOCALE, UA, QUERY, WEBSOCKET, APPSTREAM, Terminal, Fit) {
"use strict";
Terminal.applyAddon(Fit);
var module = angular.module("cjt2.directives.terminal", []);
var TEMPLATE_PATH = "libraries/cjt2/directives/terminal.phtml";
var NBSP = "\u00a0";
// A resize is handled by cpsrvd itself rather than the shell,
// so we send this information in an AppStream control message.
function _sendResize(ws, term) {
var rows = term.rows;
var cols = term.cols;
var msg = "resize:" + rows + "," + cols;
ws.send( APPSTREAM.encodeControlPayload(msg) );
}
var shellUrl = WEBSOCKET.getUrlBase() + "/websocket/Shell";
module.directive("cpTerminal", [ "alertService", "$log", "onBeforeUnload",
function(alertService, $log, onBeforeUnload) {
function _reportError(str) {
alertService.add({
type: "danger",
message: str,
closeable: true,
autoClose: false,
replace: false,
group: "myalerts"
});
}
return {
restrict: "E",
replace: true,
templateUrl: CJT.config.debug ? CJT.buildFullPath(TEMPLATE_PATH) : TEMPLATE_PATH,
controller: ["$scope", "$element", function($scope, $element) {
var terminalIsOpen;
var term = new Terminal( {
macOptionIsMeta: UA.isMacintosh(),
// These are defaults that we update
// via fit() prior to opening the WebSocket
// connection. We send the resulting dimensions
// as query parameters; this way the pty
// on the backend has the correct dimensions
// from the get-go.
cols: $scope._DEFAULT_COLS,
rows: $scope._DEFAULT_ROWS,
} );
// Use a closure rather than bind() here so that it’s always
// the same function regardless of the ws instance.
function _sendData(d) {
$scope._ws.send( APPSTREAM.encodeDataPayload(d) );
}
term.on("data", _sendData);
// It’s possible to resize the window between the
// time when we send off the WebSocket handshake and
// when we get the response. When that happens we need
// to mark the need to send off a window resize once
// the connection is open.
var pendingResize;
function fitAndSendGeometry() {
term.fit();
var ws = $scope._ws;
if (ws) {
if (ws.readyState === WebSocket.OPEN) {
_sendResize(ws, term);
} else {
pendingResize = true;
}
}
}
window.addEventListener("resize", fitAndSendGeometry);
if (CJT.isWhm()) {
window.addEventListener("toggle-navigation", fitAndSendGeometry);
window.addEventListener("toggle-navigation", fitAndSendGeometry);
}
term.on( "title", function _setTitle(title) {
$scope.title = title || NBSP;
$scope.$apply();
} );
// ------------------------------------------------
function _wsOnError(e) {
$log.error(e.toString());
}
function _wsOnOpen(e) {
$scope.opening = false;
$scope.$apply();
if (pendingResize) {
_sendResize( e.target, term );
pendingResize = false;
}
}
function _wsOnFirstMessage(e) {
var ws = e.target;
ws.removeEventListener("message", _wsOnFirstMessage);
$scope.loading = false;
$scope.$apply();
term.textarea.disabled = false;
term.textarea.focus();
}
function _wsOnClose(evt) {
$scope._ws = null;
if (!onBeforeUnload.windowIsUnloading() && (evt.code !== WEBSOCKET.STATUS.SUCCESS)) {
var errStr;
if ($scope.opening && (evt.code === WEBSOCKET.STATUS.ABORTED)) {
errStr = LOCALE.maketext("The [asis,WebSocket] handshake failed at [local_datetime,_1,time_format_medium].", new Date());
} else {
var why, errDetail;
try {
why = JSON.parse( evt.reason );
if (why.got_signal) {
errDetail = "SIG" + why.result;
if (why.dumped_core) {
errDetail += ", +core";
}
} else {
$scope.exitCode = why.result;
}
} catch (err) {
// Don’t warn if the server went away
// suddenly.
if (evt.reason) {
$log.warn("JSON parse: " + err);
}
errDetail = WEBSOCKET.getErrorString(evt);
}
if (errDetail) {
errStr = LOCALE.maketext("The connection to the server ended in failure at [local_datetime,_1,time_format_medium]. ([_2])", new Date(), errDetail);
}
}
if (errStr) {
_reportError(errStr);
}
}
$scope.closed = true;
$scope.opening = false;
$scope.loading = false;
$scope.$apply();
if (term.textarea) {
term.textarea.disabled = true;
}
}
// ------------------------------------------------
// Edge & IE11 lack TextDecoder support,
// so we have to do it manually.
// We could send text messages rather than binary,
// but it’ll be nice to be able to add ZMODEM support
// later on, and for that we’ll need binary.
function _setupBinaryParser(ws, term) {
var blobQueue = [];
var fileReader = new FileReader();
// NB: As of March 2018, our PhantomJS version
// doesn’t recognize addEventListener on
// FileReader instances.
fileReader.onload = function(e) {
term.write( e.target.result );
if (blobQueue.length) {
e.target.readAsText( blobQueue.shift() );
}
};
fileReader.onerror = function(e) {
_reportError("UTF-8 decode error: " + e.target.error.toString());
};
$scope._wsOnMessage = function(evt) {
if (fileReader.readyState === FileReader.LOADING) {
blobQueue.push(evt.data);
} else {
fileReader.readAsText(evt.data);
}
};
ws.addEventListener("message", $scope._wsOnMessage);
}
// ------------------------------------------------
function _connect() {
if ($scope._ws) {
throw new Error("WebSocket already open!");
}
$scope.exitCode = null;
if (!terminalIsOpen) {
var parentNode = $element.find(".terminal-xterm").get(0);
if (!parentNode) {
throw new Error("_connect() with no parent node!");
}
term.open( parentNode, true );
term.textarea.disabled = true;
term.fit();
terminalIsOpen = true;
}
var queryStr = QUERY.make_query_string( {
rows: term.rows,
cols: term.cols,
} );
var fullUrl = shellUrl + "?" + queryStr;
var WSConstructor = $scope._WebSocket;
$scope._ws = new WSConstructor(fullUrl);
$scope.opening = true;
$scope.loading = true;
$scope.closed = false;
var ws = $scope._ws;
ws.addEventListener( "error", _wsOnError );
ws.addEventListener( "open", _wsOnOpen );
// We wait until the first message to display
// the terminal because we want there to be a
// “waiting for terminal …” state to report.
ws.addEventListener( "message", _wsOnFirstMessage );
_setupBinaryParser( ws, term );
ws.addEventListener( "close", _wsOnClose );
}
_.assign(
$scope,
{
title: NBSP,
connect: _connect,
// while opening the WebSocket connection:
opening: true,
// while we’ve not received a message yet
// (implies “opening”)
loading: true,
// after a connection closes
closed: false,
exitCode: null,
// Strings
openingString: LOCALE.maketext("Opening a connection …"),
waitingString: LOCALE.maketext("Waiting for the terminal …"),
reconnectString: LOCALE.maketext("Reconnect"),
exitCodeString: LOCALE.maketext("Exit Code"),
// ----------------------------------------
// Testing interface
_alertService: alertService,
_DEFAULT_COLS: 80,
_DEFAULT_ROWS: 24,
_window: window,
_WebSocket: $scope._WebSocket || window.WebSocket,
_ws: null,
_wsOnError: _wsOnError,
_wsOnOpen: _wsOnOpen,
_wsOnFirstMessage: _wsOnFirstMessage,
_wsOnMessage: null,
_wsOnClose: _wsOnClose,
}
);
_connect();
} ],
};
}
] );
}
);
/*
# cjt2/directives/timePicker.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/timePicker',[
"angular",
"cjt/util/locale",
"cjt/core"
],
function(angular, LOCALE, CJT) {
"use strict";
/**
* Directive to render a time picker
*
* @module time-picker
* @memberof cjt2.directives
*
* @example
* <time-picker></time-picker>
*
*/
var RELATIVE_PATH = "libraries/cjt2/directives/";
var TEMPLATES_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
var TEMPLATE = TEMPLATES_PATH + "timePicker.phtml";
var MODULE_NAMESPACE = "cjt2.directives.timePicker";
var module = angular.module(MODULE_NAMESPACE, []);
var LINK = function(scope, element, attrs, ngModel) {
scope.options = angular.extend({
min: 0
}, scope.options);
var unregister = scope.$watch(function() {
return ngModel.$modelValue;
}, initialize);
function initialize(value) {
ngModel.$setViewValue(value);
scope.selectedTime = value;
}
scope.hStep = 1;
scope.mStep = 15;
scope.showMeridian = false;
scope.onChange = function onChange(newDate) {
ngModel.$setViewValue(newDate);
};
scope.$on("$destroy", unregister);
};
var DIRECTIVE_FACTORY = function createTimePickerDirective() {
return {
templateUrl: TEMPLATE,
restrict: "EA",
require: "ngModel",
scope: {
parentID: "@id",
options: "=",
},
transclude: true,
link: LINK
};
};
module.directive("timePicker", DIRECTIVE_FACTORY);
return {
"directiveFactory": DIRECTIVE_FACTORY,
"linkController": LINK,
"namespace": MODULE_NAMESPACE,
"template": TEMPLATE
};
}
);
/*
# cjt/directives/toggleLabelInfoDirective.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define(
'cjt/directives/toggleLabelInfoDirective',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/util/parse",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT, LOCALE, PARSE) {
"use strict";
var module = angular.module("cjt2.directives.toggleLabelInfo", [
"cjt2.templates"
]);
/**
* Directive that helps to toggle form label help
*
* @name toggleLabelInfo
* @attribute {string} for - The control associated with the label
* @attribute {string} labelText - Label Text
* @attribute {string} labelID - Label ID
* @attribute {string} infoIconID - Information Icon ID
* @attribute {string} infoBlockID - Information block ID
* @example
*
* <toggle-label-info
* for="ips"
* label-text="IP Addresses"
* label-id="lblIps"
* info-icon-id="iconToggleIps"
* info-block-id="ips.info.block">
* Provide a list of IPs for this site.
* </toggle-label-info>
*
* @example
*
* <toggle-label-info
* for="ips"
* label-text="IP Addresses"
* label-id="lblIps"
* info-icon-id="iconToggleIps"
* info-block-id="ips.info.block"
* on-toggle="toggleHelp(show)">
* Provide a list of IPs for this site.
* </toggle-label-info>
* <div
*/
module.directive("toggleLabelInfo", function() {
var RELATIVE_PATH = "libraries/cjt2/directives/toggleLabelInfoDirective.phtml";
var ctr = 0;
var DEFAULT_CONTROL_NAME = "toggleLabelInfo";
var DEFAULT_LABEL_ID = "lbl";
var DEFAULT_INFO_ICON_ID = "infoIcon";
var DEFAULT_INFO_BLOCK_ID = "infoText";
var DEFAULT_SHOW_INFO_BLOCK = false;
return {
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
restrict: "E",
replace: true,
transclude: true,
scope: {
for: "@",
labelText: "@",
onToggle: "&",
},
link: {
pre: function(scope, elem, attrs) {
var id = angular.isDefined(attrs.id) && attrs.id !== "" ? attrs.id : DEFAULT_CONTROL_NAME + ctr++;
scope.labelID = angular.isDefined(attrs.labelId) && attrs.labelId !== "" ? attrs.labelId : ("lbl" + id || DEFAULT_LABEL_ID) + ctr++;
scope.infoIconID = angular.isDefined(attrs.infoIconId) && attrs.infoIconId !== "" ? attrs.infoIconId : (id + "infoIcon" || DEFAULT_INFO_ICON_ID) + ctr++;
scope.infoBlockID = angular.isDefined(attrs.infoBlockId) && attrs.infoBlockId !== "" ? attrs.infoBlockId : (id + "infoText" || DEFAULT_INFO_BLOCK_ID) + ctr++;
scope.showInfoBlock = angular.isDefined(attrs.showInfoBlock) && attrs.showInfoBlock !== "" ? PARSE.parseBoolean(attrs.showInfoBlock) : DEFAULT_SHOW_INFO_BLOCK;
scope.toggleActionTitle = scope.showInfoBlock ? LOCALE.maketext("Collapse") : LOCALE.maketext("Expand");
},
post: function(scope, elem, attrs) {
scope.toggleInfoBlock = function() {
scope.showInfoBlock = !scope.showInfoBlock;
scope.toggleActionTitle = scope.showInfoBlock ? LOCALE.maketext("Collapse") : LOCALE.maketext("Expand");
if (angular.isDefined(attrs.onToggle)) {
scope.onToggle({ show: scope.showInfoBlock });
}
};
attrs.$observe("includeLabelSuffix", function(val) {
scope.includeLabelSuffix = "includeLabelSuffix" in attrs;
});
attrs.$observe("showInfoBlock", function(val) {
scope.showInfoBlock = angular.isDefined(attrs.showInfoBlock) && attrs.showInfoBlock !== "" ? PARSE.parseBoolean(attrs.showInfoBlock) : DEFAULT_SHOW_INFO_BLOCK;
});
scope.$on("showHideAllChange", function(event, show) {
scope.showInfoBlock = show;
});
},
}
};
});
}
);
/*
# cjt/directives/toggleSwitchDirective.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/toggleSwitchDirective',[
"angular",
"lodash",
"cjt/core",
"cjt/util/test",
"uiBootstrap",
"cjt/directives/spinnerDirective",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, _, CJT, TEST) {
"use strict";
var module = angular.module("cjt2.directives.toggleSwitch", [
"cjt2.templates",
"cjt2.directives.spinner"
]);
/**
* Directive that renders a toggle switch
* @attribute {String} id -
* @attribute {String} enabledLabel
* @attribute {String} disabledLabel
* @attribute {String} labelPosition - one of right, left, none
* @attribute {String} spinnerPosition - one of right, left
* @attribute {Boolean} noSpinner - true or false, if true will suppress the spinner.
* @attribute {String} ariaLabel
* @attribute {Binding} ngDisabled
* @attribute {Binding} ngModel - required
* @attribute {Function} onToggle - required to make the switch toggle on click. You must provide this and it
* must handle the needed state change to trigger a toggle effect.
* @example
* <toggle-switch ng-model="state" on-toggle="state = !state" />
*/
module.directive("toggleSwitch", ["spinnerAPI",
function(spinnerAPI) {
var RELATIVE_PATH = "libraries/cjt2/directives/toggleSwitch.phtml";
return {
restrict: "E",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
require: "ngModel",
replace: true,
scope: {
parentID: "@id",
enabledLabel: "@",
disabledLabel: "@",
labelPosition: "@",
spinnerPosition: "@",
ariaLabel: "@",
isDisabled: "=ngDisabled",
ngModel: "=",
onToggle: "&",
},
link: function(scope, elem, attrs) {
scope.noSpinner = attrs.noSpinner === "true" || attrs.noSpinner === "1";
scope.spinnerId = scope.parentID + "_toggle_spinner";
if (!scope.labelPosition) {
scope.labelPosition = "right"; // To preserve behavior that existed
} else if (!_.includes(["left", "right", "none"], scope.labelPosition)) {
throw "Invalid label-position set: " + scope.labelPosition + ". Must be one of: left, right, none.";
}
if (!scope.spinnerPosition) {
scope.spinnerPosition = "right"; // To preserve behavior that existed
} else if (!_.includes(["left", "right"], scope.spinnerPosition)) {
throw "Invalid label-position set: " + scope.spinnerPosition + ". Must be one of: left or right.";
}
scope.noLabel = (!scope.enabledLabel && !scope.disabledLabel) || scope.labelPosition === "none";
scope.handle_keydown = function(event) {
// prevent the spacebar from scrolling the window
if (event.keyCode === 32) {
event.preventDefault();
}
};
scope.handle_keyup = function(event) {
// bind to the spacebar and enter keys to toggle the field
if (event.keyCode === 32 || event.keyCode === 13) {
event.preventDefault();
scope.toggle_status();
}
// bind left arrow to turn off
if (event.keyCode === 37) {
event.preventDefault();
if (scope.ngModel) {
scope.toggle_status();
}
}
// bind right arrow to turn on
if (event.keyCode === 39) {
event.preventDefault();
if (!scope.ngModel) {
scope.toggle_status();
}
}
};
scope.get_aria_value = function() {
return scope.ngModel ? "true" : "false";
};
/**
* Start the spinner if needed
*/
var _startSpinner = function() {
if (!scope.noSpinner) {
spinnerAPI.start(scope.spinnerId, false);
}
};
/**
* Stop the spinner if needed
*/
var _stopSpinner = function() {
if (!scope.noSpinner) {
spinnerAPI.stop(scope.spinnerId, false);
}
};
scope.toggle_status = function() {
if (scope.changing_status || scope.isDisabled) {
return;
}
scope.changing_status = true;
_startSpinner();
var promise = scope.onToggle();
if (TEST.isQPromise(promise)) {
promise.finally(function() {
scope.changing_status = false;
_stopSpinner();
});
} else {
scope.changing_status = false;
_stopSpinner();
}
};
}
};
}
]);
}
);
/*
# cjt/directives/triStateCheckbox.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
// ------------------------------------------------------------
// Developer notes:
// ------------------------------------------------------------
// The concept for this construct was derived from:
// http://plnkr.co/edit/PTnzedhD6resVkApBE9K?p=preview
// ------------------------------------------------------------
// 1) Consider converting to use ng-model instead of custom
// binding.
// ------------------------------------------------------------
define(
'cjt/directives/triStateCheckbox',[
"angular",
"cjt/core",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT) {
var module = angular.module("cjt2.directives.triStateCheckbox", [
"cjt2.templates"
]);
/**
* The triStateCheckbox is used to create a check all style controller
* check-box for a group of check boxes.
*
* @directive
* @directiveType Elements
* @attribute {Binding} checkboxes Dataset controlling the dependent check-boxes
*/
module.directive("triStateCheckbox", function() {
var RELATIVE_PATH = "libraries/cjt2/directives/triStateCheckbox.phtml";
return {
replace: true,
restrict: "E",
scope: {
checkboxes: "=",
ngChange: "&",
useInt: "@"
},
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
controller: ["$scope", "$element",
function($scope, $element) {
$scope.toggled = false;
/**
* Handler method when changes to the master controller occur.
*/
$scope.masterChange = function() {
if (typeof $scope.checkboxes === "undefined") {
return;
}
var setting;
if ($scope.master === true) {
setting = $scope.useInt ? 1 : true;
} else {
setting = $scope.useInt ? 0 : false;
}
for (var i = 0, len = $scope.checkboxes.length; i < len; i++) {
$scope.checkboxes[i].selected = setting;
}
$scope.toggled = true;
if ($scope.ngChange) {
$scope.ngChange();
}
};
// Use a deep watch for changes to the model behind the related checkboxes
$scope.$watch("checkboxes", function() {
if (typeof $scope.checkboxes === "undefined") {
return;
}
// shortcut the watch if we just toggled all
if ($scope.toggled) {
$scope.toggled = false;
return;
}
var atLeastOneSet = false;
var allChecked = true;
for (var i = 0, len = $scope.checkboxes.length; i < len; i++) {
if ($scope.checkboxes[i].selected) {
atLeastOneSet = true;
} else {
allChecked = false;
}
}
if (allChecked) {
$scope.master = true;
$element.prop("indeterminate", false);
} else {
$scope.master = false;
$element.prop("indeterminate", atLeastOneSet);
}
}, true);
}
]
};
});
}
);
/*
# cjt/directives/validateEqualsDirective.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
// TODO: Maybe move to the validators folder
define(
'cjt/directives/validateEqualsDirective',[
"angular"
],
function(angular) {
"use strict";
// Get the current application
var module = angular.module("cjt2.directives.validateEquals", []);
/**
* Directive that compares the field value with another value.
* @attribute {String} validateEquals Model to compare against.
* @example
*
* For an optional fields comparison:
* <form name="form">
* <input name="password" ng-model="password">
* <input name="confirm" ng-model="confirm" validate-equals="form.password">
* <button type="submit" ng-disabled="form.$invalid">Submit</button>
* </form>
*
* For an required fields comparison:
* <form name="form">
* <input name="password" ng-model="password" required>
* <input name="confirm" ng-model="confirm" validate-equals="form.password">
* <button type="submit" ng-disabled="form.$invalid">Submit</button>
* </form>
*
* Note: If the field being watch is required and either it or the field with this
* directive attached is empty, it makes the form invalid.
*/
module.directive("validateEquals", function() {
return {
require: "ngModel",
link: function(scope, elm, attrs, ngModel) {
/**
* Validate that the value is equal to the requested value
* @param {String} value Value to test.
* @return {Boolean} true if the passed in value is equal to the registered value.
*/
ngModel.$validators.validateEquals = function validateEquals(value) {
var ngOtherModel = getNgOtherModel();
if (!ngOtherModel) {
// Early in the page life cycle
return true;
}
var thisIsEmpty = ngModel.$isEmpty(value);
var otherIsEmpty = ngOtherModel.$isEmpty(ngOtherModel.$viewValue);
if (thisIsEmpty && otherIsEmpty) {
// If both inputs are empty, it's valid if the other field is not required.
return !ngOtherModel.$validators.required;
} else {
return (
/**
* If we have an asyncValidator in progress then we should validate against the
* viewValue to present immediate results. This validator will be re-evaluated if
* the modelValue changes after the asyncValidation because of the $watchGroup.
*
* We should also use the viewValue if the other model is marked as invalid, to
* make sure that we're validating against an actual value instead of undefined.
* While these results don't actually matter because the other model will need to
* be modified for it to be valid, it would feel awkward without feedback from
* this validator.
*/
(ngOtherModel.$pending || ngOtherModel.$invalid) ?
(value === ngOtherModel.$viewValue) :
(value === ngOtherModel.$modelValue)
);
}
};
/**
* Check for changes on the comparison value and validate if changed. We watch the
* viewValue to ensure that users get immediate feedback before validators run. We
* watch the modelValue to ensure that models that don't pass validation or get
* transformed by parsers/formatters are properly validated.
*/
scope.$watchGroup([
function() {
var ngOtherModel = getNgOtherModel();
return ngOtherModel && ngOtherModel.$viewValue;
},
function() {
var ngOtherModel = getNgOtherModel();
return ngOtherModel && ngOtherModel.$modelValue;
}
], function() {
ngModel.$validate();
});
// We need to use this getter for form controls that are added later in the life cycle.
var _ngOtherModel;
function getNgOtherModel() {
if (!_ngOtherModel) {
_ngOtherModel = scope.$eval(attrs.validateEquals);
}
return _ngOtherModel;
}
}
};
});
});
/*
# cjt/directives/validationItemDirective.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define(
'cjt/directives/validationItemDirective',[
"angular",
"cjt/core",
"cjt/templates" // NOTE: Pre-load the template cache
],
function(angular, CJT) {
"use strict";
var module = angular.module("cjt2.directives.validationItem", [
"cjt2.templates"
]);
/**
* Directive that shows a alert.
* @example
*
* To bind an item to an standard validation error:
*
* <li validation-item field-name="textField" validation-name="required">
* The textField is required.
* </li>
*
* To bind an item to an extended validation error.
*
* <li validation-item field-name="textField" validation-name="custom">
* </li>
*
* NOTE: Assumes that some custom validator adds the string to $error_details collection.
*
* To bind an item manually:
*
* <li validation-item ng-show="form.textField.$error.required">
* </li>
*
* To supress the icon and use custom styling you can use the following:
*
* <li validation-item field-name="textField" validation-name="custom" no-icon prefix-class='bullets'>
* </li>
*
* This is useful when you take over rendering and want items that are subitems of a less specific error.
*/
module.directive("validationItem", [ function() {
var ct = 0;
var RELATIVE_PATH = "libraries/cjt2/directives/validationItem.phtml";
/**
* Dynamically fetch and cache the field. Caches the field in scope
* along with the needed errors and extendedErrors collections.
*
* @method _attachField
* @private
* @param {ngForm} form Form to which the field is attached.
* @param {String} fieldName Name of the field we are monitoring.
* @param {Scope} scope Scope
* @return {ngField}
*/
function _attachField(form, fieldName, scope) {
var field = scope.field;
if (!field) {
// Bail if there is no form to check against.
// We check the return in other places.
if (form === void 0) {
return null;
}
field = form[fieldName];
if (field) {
scope.field = field;
scope.errors = field.$error;
scope.extendedErrors = field.$error_details;
}
}
return field;
}
return {
restrict: "EA",
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
transclude: true,
replace: true,
scope: true,
link: function( scope, element, attrs) {
var prefix = scope.$eval(attrs.prefix) || attrs.prefix || "validator";
var prefixClass = scope.$eval(attrs.prefixClass) || attrs.prefixClass || "";
var showWhenPristine = scope.$eval(attrs.showWhenPristine) || false;
var form = element.controller("form");
var showIcon = angular.isDefined(attrs.noIcon) ? false : true;
var fieldName;
if (attrs.fieldName) {
fieldName = scope.$eval(attrs.fieldName) || attrs.fieldName;
_attachField(form, fieldName, scope);
}
var _validationName = attrs.validationName || "";
/**
* Helper method to see if we should show the icon or the bullet. When true will show
* the standard icon, otherwise the prefix span will show. This allows bullet sublist of
* error details.
* @method showIcon
* @returns {Boolean}
*/
scope.showIcon = function() {
return showIcon;
};
scope.prefixClass = prefixClass;
/**
* Helper method that can be used to test if the item should be shown.
*
* @method canShow
* @param {Object} [field] Optional: Reference to a input field controller. Retrieve from an ngForm[fieldName]. Defaults to the field set by the field-name attribute.
* @param {String} [validationName] Optional: Name of the validation option to check for validity: ex. require. Defaults to the name set in validation-name attribute.
* @return {Boolean} true if there is a matching validation error and the field is not pristine, false otherwise.
*/
scope.canShow = function(field, validationName) {
field = field || _attachField(form, fieldName, scope);
validationName = validationName || _validationName;
if (field && (showWhenPristine || !field.$pristine || form.$submitted) && field.$invalid && validationName) {
// Use automatic matching logic, probably embedded in a validation container.
return field.$error[validationName]; // Show if invalid, hide if valid.
} else {
// Not using automatic matching logic, so let something else decide to show/hide this.
return true;
}
};
/**
* Return the text for the item
*
* @method print
* @param {Object} [field] Optional: Reference to a input field controller. Retrieve from an ngForm[fieldName]. Defaults to the field set by the field-name attribute.
* @param {String} [validationName] Optional: Name of the validation option to check for validity: ex. require. Defaults to the name set in validation-name attribute.
* @return {String}
*/
scope.print = function(field, validationName) {
field = field || _attachField(form, fieldName, scope);
validationName = validationName || _validationName;
if (field && validationName && field.$error[validationName]) {
if (field.$error_details) {
var details = field.$error_details.get(validationName);
if (details && details.hasMessages() && details.hasMessage(validationName)) {
var entry = details.get(validationName);
if (entry) {
return entry.message;
}
}
}
}
};
scope.id = prefix + ct++;
}
};
}]);
}
);
/*
# cjt/filters/breakFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/breakFilter',[
"angular",
"ngSanitize"
],
function(angular) {
/**
* Checks if the value is a TrustedValueHolder and extracts the value if it is.
* This is useful when something earlier in the chain returns a $secDelegate.trustAs()
* wrapped value.
*
* @private
* @method _getTrustedValue
* @param {Any} value This is any var you're trying to extract a trusted value from
* @return {String|Undefined} If it's detected as a TrustedValueHolder, it returns the value
*/
function _getTrustedValue(valHolder) {
var val = angular.isObject(valHolder) && // Some primitive wrappers override Object.prototype.valueOf, so this
// makes the next comparison more accurate and doubles as a guard.
valHolder.valueOf !== Object.prototype.valueOf && // We need to make sure it's not just the inherited valueOf method.
angular.isFunction(valHolder.valueOf);
return val ? valHolder.valueOf() : void 0;
}
var module = angular.module("cjt2.filters.break", [
"ngSanitize"
]);
/**
* Filter that converts newline characters into <div> tags.
*
* @name break
* @param {String} value Value to filter.
* @param {String} [match] Optional match pattern, defaults to \n
* @example
*/
module.filter("break", ["$sceDelegate", "$sce", function($sceDelegate, $sce) {
return function(value, match, inline) {
// If it's not a string, we'll try and fetch the trusted value
if (typeof value !== "string") {
value = _getTrustedValue(value);
}
// If value is still falsy at this point it's time to give up
if (!value) {
return "";
}
// Setup up defaults
match = match || "\n";
inline = typeof (inline) === "undefined" ? false : inline;
var expression = new RegExp(match, "g");
var parts = value.split(expression);
if (inline) {
value = parts.join("<br>");
} else {
value = "<div>" + parts.join("</div><div>") + "</div>";
}
return $sceDelegate.trustAs($sce.HTML, value);
};
}]);
}
);
/*
# cjt/util/html.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define('cjt/util/html',[
"jquery"
],
function($) {
return {
/**
* Encode a value as html.
*
* @static
* @method encode
* @param {String} value String with html characters that need encoding
* @return {String} Same string with html characters encoded.
*/
encode: function(value) {
// create a in-memory div, set it's inner text(which jQuery automatically encodes)
// then grab the encoded contents back out. The div never exists on the page.
return $("<div/>").text(value).html();
},
/**
* Decode a string from html encoding to text.
*
* @static
* @method decode
* @param {String} value String with html encoded characters
* @return {String} Same string with html encoded characters decoded.
*/
decode: function(value) {
return $("<div/>").html(value).text();
}
};
}
);
/*
# cjt/filters/htmlFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/htmlFilter',[
"angular",
"cjt/util/html",
"ngSanitize"
],
function(angular, HTML) {
/**
* Checks if the value is a TrustedValueHolder and extracts the value if it is.
* This is useful when something earlier in the chain returns a $secDelegate.trustAs()
* wrapped value.
*
* @private
* @method _getTrustedValue
* @param {Any} value This is any var you're trying to extract a trusted value from
* @return {String|Undefined} If it's detected as a TrustedValueHolder, it returns the value
*/
function _getTrustedValue(valHolder) {
var val = angular.isObject(valHolder) && // Some primitive wrappers override Object.prototype.valueOf, so this
// makes the next comparison more accurate and doubles as a guard.
valHolder.valueOf !== Object.prototype.valueOf && // We need to make sure it's not just the inherited valueOf method.
angular.isFunction(valHolder.valueOf);
return val ? valHolder.valueOf() : void 0;
}
var module = angular.module("cjt2.filters.encodeHtml", [
"ngSanitize"
]);
/**
* Filter that converts newline characters into <div> tags.
*
* @name encodeHtml
* @param {String} value Value to filter.
* @param {String} [match] Optional match pattern, defaults to \n
* @example
*/
module.filter("encodeHtml", [function() {
return function(value) {
// If it's not a string, we'll try and fetch the trusted value
if (typeof value !== "string") {
value = _getTrustedValue(value);
}
// If value is still falsey at this point it's time to give up
if (!value) {
return "";
}
return HTML.encode(value);
};
}]);
}
);
/*
# cjt/filters/jsonFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/jsonFilter',[
"angular"
],
function(angular) {
"use strict";
var module = angular.module("cjt2.filters.json", []);
/**
* Filter that converts a JavaScript object to a formated JSON text string. Primarily intended for diagnostic/debugging
* usage, but may be used anywhere
*
* @name json
* @type filter
* @param {String} value Value to filter.
* @param {String} [indent] Characters to use to indent. Same as JSON.stringify 3rd argument. Defaults to 2 spaces.
* @param {Function} [fnFilter] Optional filter function to apply to the value to remove items we don't want to print. Same as JSON stringify 2nd argument. Defaults to undefined.
* @returns
* @example
*
* If scope.config = { a: 'a' };
*
* And on the template you have:
*
* {config| json}
*
* This yields something like:
*
* {
* 'a': 'a'
* }
*
* With a indent argument:
*
* {config| json: '\t'}
*
* This yields something like:
*
* {
* \t'a': 'a'
* }
*/
module.filter("json", [function() {
return function(value, indent, fnFilter) {
// Setup the defaults
if (angular.isUndefined(indent)) {
indent = " ";
}
if (angular.isUndefined(fnFilter)) {
fnFilter = null;
}
// Filter the value.
try {
return JSON.stringify(value, fnFilter, indent);
} catch (e) {
return e;
}
};
}]);
}
);
/*
# cjt/filters/nospaceFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/nospaceFilter',[
"angular"
],
function(angular) {
"use strict";
var module = angular.module("cjt2.filters.nospace", []);
/**
* Filter that removes extra white-space from values.
* @example
*/
module.filter("nospace", function() {
return function(value) {
return (!value) ? "" : value.replace(/ /g, "");
};
});
}
);
/*
# cjt/filters/notApplicableFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/notApplicableFilter',[
"angular",
"cjt/util/locale"
],
function(angular, LOCALE) {
var FULL_NOT_APPLICABLE = LOCALE.maketext("Not Applicable");
var ABBR_NOT_APPLICABLE = LOCALE.maketext("N/A");
var module = angular.module("cjt2.filters.na", []);
/**
* Filter that converts null, undefined and empty values to "N/A" or "Not Applicable".
*
* @name na
* @param {String} value Value to filter.
* @param {Boolean} [fullWord] Optional use the full word if true, otherwise use the abbreviated form. Uses the abbreviated form if not defined.
* @example
*/
module.filter("na", [function() {
return function(value, fullWord, inline) {
// If the value is falsy and not 0, return N/A abbreviation or full text
if (!value && value !== 0) {
return (fullWord ? FULL_NOT_APPLICABLE : ABBR_NOT_APPLICABLE);
}
// Otherwise just pass the value through
else {
return value;
}
};
}]);
return {
FULL_NOT_APPLICABLE: FULL_NOT_APPLICABLE,
ABBR_NOT_APPLICABLE: ABBR_NOT_APPLICABLE
};
}
);
/*
# cjt/filters/replaceFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/replaceFilter',[
"angular"
],
function(angular) {
"use strict";
var module = angular.module("cjt2.filters.replace", []);
/**
* Filter that replaces a string in a string with something else.
*
* @name replace
* @param {String} value Value to filter.
* @param {String} match Match pattern.
* @param {String} [replace] Replace pattern.
*/
module.filter("replace", function() {
return function(value, match, replace) {
if (!value) {
return value;
}
// Setup up defaults
match = match || "";
replace = replace || "";
var expression = new RegExp(match, "g");
return value.replace(expression, replace);
};
});
}
);
/*
# cjt/filters/splitFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/splitFilter',[
"angular"
],
function(angular) {
var module = angular.module("cjt2.filters.split", []);
/**
* Filter that splits a string on a pattern into an array.
*
* @name split
* @param {String} value Value to filter.
* @param {String} [match] Optional match pattern, defaults to \n
*/
module.filter("split", function() {
return function(value, match) {
if (!value) {
return [];
}
// Setup up defaults
match = match || "\n";
var expression = new RegExp(match, "g");
return value.split(expression);
};
});
}
);
/*
# cjt/filters/startFromFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/startFromFilter',[
"angular"
],
function(angular) {
"use strict";
var module = angular.module("cjt2.filters.startFrom", []);
/**
* Filter that returns an array with the elements after the specified starting position.
* @param {Array|string} input Source array or string to be sliced
* @param {number} start the position to begin the slice
*/
module.filter("startFrom", function() {
return function(input, start) {
start = Number(start);
if ( isNaN(start) ) {
start = 0;
}
return input.slice(start);
};
});
}
);
/*
# cjt/util/string.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:false*/
/* --------------------------*/
// TODO: Add tests for these
/**
*
* @module cjt/util/string
* @example
*
*/
define('cjt/util/string',["lodash", "punycode"], function(_, PUNYCODE) {
"use strict";
// ------------------------------
// Module
// ------------------------------
var MODULE_NAME = "cjt/util/string";
var MODULE_DESC = "Contains string helper functions.";
var MODULE_VERSION = 2.0;
// Highest Unicode ASCII code point.
var UNICODE_ASCII_CUTOFF = 127;
// As of 2021 we no longer support any browsers that lack TextEncoder.
var textEncoder = new TextEncoder();
/**
* Collection of string helper functions.
*
* @static
* @public
* @class String
*/
var string = {
MODULE_NAME: MODULE_NAME,
MODULE_DESC: MODULE_DESC,
MODULE_VERSION: MODULE_VERSION,
/**
* Left pads the string with leading characters. Will use spaces if
* the padder parameter is not defined. Will pad with "0" if the padder
* is 0.
* @method lpad
* @param {String} str String to modify.
* @param {Number} len Length of the padding.
* @param {String} padder Characters to pad with.
* @return {String} String padded to the full width defined by len parameter.
*/
lpad: function(str, len, padder) {
if (padder === 0) {
padder = "0";
} else if (!padder) {
padder = " ";
}
var deficit = len - str.length;
var pad = "";
var padder_length = padder.length;
while (deficit > 0) {
pad += padder;
deficit -= padder_length;
}
return pad + str;
},
/**
* Reverse the characters in a string.
* @param {String} str String to modify.
* @return {String} New string with characters reversed.
*/
reverse: function(str) {
// Can’t just do this because it mangles non-BMP characters:
// return str.split("").reverse().join("");
var codePoints = PUNYCODE.ucs2.decode(str);
return PUNYCODE.ucs2.encode(codePoints.reverse());
},
/**
* Returns the length, in bytes, of the string’s UTF-8 representation.
*
* @param {String} str String to examine.
* @return {Number} Byte count of the string in UTF-8.
*/
getUTF8ByteCount: function getUTF8ByteCount(str) {
return textEncoder.encode(str).length;
},
/**
* Returns an array of the string’s unique non-ASCII characters.
*
* @param {String} str String to examine.
* @return {String[]} Array of 1-character strings.
*/
getNonASCII: function getNonASCII(str) {
var chars = [];
// We can’t just iterate through the characters as JS sees them
// because the string might contain non-BMP characters like emoji.
var codePoints = PUNYCODE.ucs2.decode(str);
for (var i = 0; i < codePoints.length; i++) {
if (codePoints[i] > UNICODE_ASCII_CUTOFF) {
chars.push( PUNYCODE.ucs2.encode([codePoints[i]]) );
}
}
return _.uniq(chars);
},
};
return string;
});
/*
# cjt/filters/timezoneFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/timezoneFilter',[
"angular",
"cjt/util/string"
],
function(angular, STRING) {
"use strict";
var module = angular.module("cjt2.filters.timezone", []);
/**
* Filter that converts a timezone in minutes to a timezone in +/- hour:min.
*
* @example
*
* {{ -300 | timezone }} => -5:00
* {{ 0 | timezone }} => 0:00
* {{ 0 | timezone: false }} => Z
* {{ 300 | timezone }} => +5:00
* {{ 300 | timezone: false:'z' }} => +5z00
*/
module.filter("timezone", [function() {
return function(value, zulu, timeSeparator) {
timeSeparator = timeSeparator || ":";
zulu = typeof (zulu) !== "undefined" ? zulu : false;
if (zulu && (!value || value === "0")) {
// Need to test for "0" because our JSON serilizer is dealing
// with Perl and sometimes gets 0 and other times get "0".
return "Z";
}
var hours = Math.floor(value / 60);
var minutes = Math.abs(value % 60);
var formattedHours = STRING.lpad(Math.abs(hours).toString(), 2, "0");
if ( hours > 0 ) {
formattedHours = "+" + formattedHours;
} else {
formattedHours = "-" + formattedHours;
}
return formattedHours + timeSeparator + STRING.lpad(minutes.toString(), 2, "0");
};
}]);
}
);
/*
# cjt/filters/wrapFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/wrapFilter',[
"angular",
"ngSanitize"
],
function(angular) {
/**
* Check if the object is a number
* @note Keeping this here to keep dependencies light.
* @private
* @method isNumber
* @param {object} obj Any object to test.
* @return {Boolean} true if the obj is a number, false otherwise
*/
function isNumber(obj) {
return !isNaN(parseFloat(obj));
}
/**
* Checks if the value is a TrustedValueHolder and extracts the value if it is.
* This is useful when something earlier in the chain returns a $secDelegate.trustAs()
* wrapped value.
*
* @private
* @method _getTrustedValue
* @param {Any} value This is any var you're trying to extract a trusted value from
* @return {String|Undefined} If it's detected as a TrustedValueHolder, it returns the value
*/
function _getTrustedValue(valHolder) {
var val = angular.isObject(valHolder) && // Some primitive wrappers override Object.prototype.valueOf, so this
// makes the next comparison more accurate and doubles as a guard.
valHolder.valueOf !== Object.prototype.valueOf && // We need to make sure it's not just the inherited valueOf method.
angular.isFunction(valHolder.valueOf);
return val ? valHolder.valueOf() : void 0;
}
var module = angular.module("cjt2.filters.wrap", [
"ngSanitize"
]);
/**
* Filter that injects hidden whitespace into strings where the match rule exists.
* By default it does this on periods. You can provide your own regex pattern to match
* other characters or patterns.
*
* @example
*
* Default: [Separates on period (.)]
* <div>{{ "abc.tld" | wrap }}</div> => <div>abc.<wbr><span class="wbr"></span>tld</div>
*
* With a custom regex pattern:
* <div>{{ "ABD1:ABD2" | wrap:':' }}</div> => <div>ABD1:<wbr><span class="wbr"></span>ABD2</div>
*
* With a custom complex regex pattern:
* <div>{{ "ABD1:ABD2.DDDD" | wrap:'[:.]' }}</div> => <div>ABD1:<wbr><span class="wbr"></span>ABD2.<wbr><span class="wbr"></span>DDDD</div>
*
* With a secondary wrap on words that exceed a max length: (note: wrapLimit must be > 2)
*
* <div>{{ "DDDD.01234567890123" | wrap:'[.]':10 }}</div> => <div>DDDD.<wbr><span class="wbr"></span>0123456789<wbr><span class="wbr"></span>0123</div>
*/
module.filter("wrap", ["$sceDelegate", "$sce", function($sceDelegate, $sce) {
var template = "<wbr><span class=\"wbr\"></span>";
var trim = new RegExp(template + "$");
var cache = {};
return function(value, match, wrapLimit) {
// If it's not a string, we'll try and fetch the trusted value
if (typeof value !== "string") {
value = _getTrustedValue(value);
}
// If value is still falsy at this point it's time to give up
if (!value) {
return "";
}
// Setup up defaults
match = (!match ? "[.]" : match);
wrapLimit = (parseInt(wrapLimit, 10) || 0);
var ruleId = match + wrapLimit;
// Wrap the match rule in a capture
var expression = cache[ruleId];
if (!expression) {
// The expression is not cached, so create a new cache entry for it
if (typeof (wrapLimit) !== "undefined" && isNumber(wrapLimit) && wrapLimit > 1) {
// -----------------------------------------------------------------------------------
// Notes:
// 1) We only want to take on this overhead if there is a wrapLimit
// 2) we use wrapLimit - 1 since the regex match the range and one more word character
// -----------------------------------------------------------------------------------
expression = new RegExp("((?:\\w{1," + (wrapLimit - 1) + "})\\w|" + match + ")", "g");
} else {
expression = new RegExp("(" + match + ")", "g");
}
cache[ruleId] = expression;
}
// Adjust the string.
value = value.replace(expression, "$1" + template);
if (wrapLimit) {
// Workaround, since the expression for limits matches at the end of the
// string too. I could not find an obvious way to prevent that match.
value = value.replace(trim, "");
}
return $sceDelegate.trustAs($sce.HTML, value);
};
}]);
}
);
/*
# cjt/filters/rangeFilter.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/filters/rangeFilter',[
"angular"
],
function(angular) {
"use strict";
var module = angular.module("cjt2.filters.range", []);
/**
* The range filter returns an array that can populate ng-repeat.
* @input {Array} input Source array, will be emptied
* @min {number} range begin value
* @max {number} range end value
* @reverse {bool} flag to output array in reverse order
* @step {number} step interval
*/
module.filter("range", function() {
return function(input, min, max, reverse, step) {
step = step || 1;
// Depending on one or two inputs
var trueMin = max ? min : 0,
trueMax = max || min;
input = [];
for (var i = trueMin; i <= trueMax; i += step) {
input.push(i);
}
if (reverse) {
input = input.reverse();
}
return input;
};
});
}
);
/*
# cjt/services/APICatcher.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define('cjt/services/APICatcher',[
"angular",
"lodash",
"cjt/util/locale",
"cjt/services/onBeforeUnload",
"cjt/services/APIService",
"cjt/services/APIFailures",
],
function(angular, _, LOCALE) {
"use strict";
/* A service to simplify API interactions by providing default
* error handling logic. Use this for situations where you’re not
* all that concerned with how the API reports failure, as long
* as it reports it *somehow*.
*
* Example usage:
*
* var promise = APICatcher.promise( apiCall );
*
* The returned promise has a “catch”er registered that will
* report the failure to the APIFailures service; anything that
* is registered with that service will then receive a notification.
* The “growlAPIReporter” decorator includes such registration and
* is the intended “complement” module for APICatcher; however,
* if you want some other means of catching unreported failures,
* it’s as simple as creating a new module that registers with
* APIFailures.
*/
var module = angular.module("cjt2.services.apicatcher", [
"cjt2.services.api",
"cjt2.services.apifailures",
"cjt2.services.onBeforeUnload"
]);
// A function to iterate through a batch result and collect
// error strings.
function _collectFailureStrings(result, strings) {
if (!strings) {
strings = [];
}
if (result.is_batch) {
result.data.forEach( function(d) {
_collectFailureStrings(d, strings);
} );
} else if (result.error) {
strings.push(result.error);
}
return strings;
}
module.factory("APICatcher", ["APIService", "APIFailures", "onBeforeUnload", "$q", "$log", function(APIService, APIFailures, onBeforeUnload, $q, $log) {
var errorPhraseToSuppress;
// ----------------------------------------------------------------------
// APIService doesn’t expose the actual reported HTTP error status.
// So we subclass APIService and install a custom “fail” handler that
// checks the HTTP status and, if that status indicates that we should
// suppress the error message, designates that phrase as “the phrase
// to suppress”.
//
// It’s ugly, but the alternative would be to return an object, which
// would break all handlers of the promise’s rejection case.
//
function APIServiceForCatcher() {
return APIService.apply(this, arguments);
}
APIServiceForCatcher.prototype = Object.create(APIService.prototype);
var baseHttpFailHandler = APIService.prototype.presetDefaultHandlers.fail;
var customHttpFailHandler = function(xhr, deferred) {
var mockDeferred = $q.defer();
mockDeferred.promise.then(
function(val) {
throw "Improper APICatcher success: " + val;
},
function(val) {
// Suppress display of the error if the failure
// is reported as HTTP status “0”.
// cf. https://yui.github.io/yui2/docs/yui_2.9.0_full/connection/index.html#failure
var shouldSuppress = onBeforeUnload.windowIsUnloading();
shouldSuppress = shouldSuppress && (xhr.status === 0);
if (shouldSuppress) {
errorPhraseToSuppress = val;
}
deferred.reject(val);
}
);
baseHttpFailHandler.call(this, xhr, mockDeferred);
};
var presetDefaultHandlers = _.assign(
{},
APIService.prototype.presetDefaultHandlers
);
presetDefaultHandlers.fail = customHttpFailHandler;
_.assign(
APIServiceForCatcher.prototype,
{
presetDefaultHandlers: presetDefaultHandlers,
}
);
// The tests mock APIService.promise, so let’s call into
// that function rather than just assigning the function as
// APIServiceForCatcher.promise.
APIServiceForCatcher.promise = function _promise() {
return APIService.promise.apply(this, arguments);
};
// ----------------------------------------------------------------------
var MAX_MESSAGES_DISPLAYED = 6;
function _processResultForMessages(result) {
var messages = [];
if (typeof result !== "object") {
// Assume it's a string
if (result !== errorPhraseToSuppress) {
messages.push({
type: "danger",
content: result,
});
}
} else {
// Response Object
if (result.error) {
_collectFailureStrings(result).forEach( function(str) {
messages.push({
type: "danger",
content: str,
});
} );
}
if (result.warnings && result.warnings.length) {
messages.push.apply(
messages,
result.warnings.map( function(w) {
return {
type: "warning",
content: w,
};
} )
);
}
}
// emit warnings and errors
if (messages.length) {
var displayed = messages.slice(0, MAX_MESSAGES_DISPLAYED);
var notDisplayed = messages.slice(MAX_MESSAGES_DISPLAYED);
if (notDisplayed.length) {
var translateToLog = {
warning: "warn",
danger: "error",
};
notDisplayed.forEach( function(msg) {
$log[ translateToLog[msg.type] ](msg.content);
} );
displayed.push({
type: "warning",
content: "<em>" + LOCALE.maketext("The system suppressed [quant,_1,additional message,additional messages]. Check your browser console for the suppressed [numerate,_1,message,messages].", notDisplayed.length) + "</em>"
});
}
// UAPI and API2 have already escaped their errors
// by this point. WHM v1 hasn’t.
if (!result.messagesAreHtml) {
displayed.forEach( function(msg) {
msg.content = _.escape(msg.content);
} );
}
APIFailures.emit(displayed);
}
return result;
}
function _promiseOrCatch(apiCall) {
var promise = APIServiceForCatcher.promise(apiCall);
promise.then(_processResultForMessages, _processResultForMessages);
return promise;
}
return {
promise: _promiseOrCatch,
};
}] );
}
);
/*
# cjt/services/autoTopService.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/services/autoTopService',[
// Libraries
"angular",
"jquery"
],
function(angular, $) {
var module = angular.module("cjt2.services.autoTop", []);
/**
* The autoTopService will automatically scroll to the top of the view port on
* each view change. To start the service, call autoTopService.initialize() when
* your app starts.
*/
module.factory("autoTopService", ["$rootScope", "$location", "$anchorScroll", function($rootScope, $location, $anchorScroll) {
var removeEvent;
return {
/**
* Start the service
* @method initialize
*/
initialize: function() {
// register listener to watch route changes
removeEvent = $rootScope.$on( "$routeChangeStart", function(event, next, current) {
if (!current) {
return;
}
// ensure new views are scrolled to the top on load
$location.hash("top").replace();
$anchorScroll();
});
},
/**
* Stop the service
* @method stop
*/
stop: function() {
if (removeEvent) {
removeEvent();
}
}
};
}]);
}
);
/*
# cjt/services/dataCacheService.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/services/dataCacheService',[
// Libraries
"angular"
],
function(angular) {
var module = angular.module("cjt2.services.dataCache", []);
module.factory("dataCache", function() {
var _data = {};
/**
* Store some application data in the name slot
* @method set
* @param {String} name
* @param {Any} data
*/
function set(name, data) {
_data[name] = data;
}
/**
* Fetch some application data in the name slot
* @method get
* @param {String} name
* @return {Any}
*/
function get(name) {
return _data[name];
}
/**
* Remove data from a name slot
* @method remove
* @param {String} name
* @return {Any} Returns the data from the slot that is removed.
*/
function remove(name) {
return delete _data[name];
}
/**
* Clears all the data in the appData cache.
* @method clear
*/
function clear() {
_data = {};
}
/**
* Returns the full cache object
* @method cache
* @return {Object} The full cache object
*/
function cache() {
return _data;
}
return {
set: set,
get: get,
remove: remove,
clear: clear,
cache: cache,
};
});
}
);
/*
* share/libraries/cjt2/src/services/viewApi.js
* Copyright(c) 2020 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/services/viewNavigationApi',[
"angular",
"cjt/services/alertService"
],
function(angular) {
var module = angular.module("cjt2.services.viewNavigationApi", [ "cjt2.services.alert" ]);
module.factory("viewNavigationApi", [
"$location",
"alertService",
function(
$location,
alertService
) {
return {
/**
* Loads the specified view using the location service. Note that the current query
* string will be discarded aside from the debug and cache_bust flags unless. If you
* wish to include additional query parameters, you must include them in the query
* object argument.
*
* @method loadView
* @param {String} view The path of the view to load, relative to the docroot.
* @param {Object} [query] Optional. These keys/values will be used to create the new view's query string.
* @param {Object} [options] Optional. A hash of additional options.
* @param {Boolean} [options.clearAlerts] If true, the default alert group in the alertService will be cleared.
* @param {Boolean} [options.replaceState] If true, the current history state will be replaced by the new view.
* @return {$location} The Angular $location service used to perform the view changes.
*/
loadView: function(view, query, options) {
// Grab the old dev flag values
var debugVal = $location.search().debug;
var cacheVal = $location.search().cache_bust;
options = options || {};
// Change the path
$location.path(view);
// Update the search
$location.search({}); // Clear the search for the new view
angular.forEach(query, function(val, key) {
$location.search(key, val);
});
// Bring over the debug-related flags
if (angular.isDefined(debugVal)) {
$location.search("debug", debugVal);
}
if (angular.isDefined(cacheVal)) {
$location.search("cache_bust", cacheVal);
}
// Clear any alerts, if desired
if (options.clearAlerts) {
alertService.clear();
}
// Set the replaceState, if desired
if (options.replaceState) {
$location.replace();
}
return $location;
}
};
}
]);
}
);
/*
# cjt/directives/validateDirectiveFactory.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define(
'cjt/validator/validateDirectiveFactory',[
"lodash",
"angular",
"cjt/validator/validator-utils"
],
function(_, angular, UTILS) {
"use strict";
var module = angular.module("cjt2.validate", []);
module.config(["$compileProvider", function($compileProvider) {
// Capture the compileProvider so we can use it in the future
// to add directives dynamically to the validate module
module.compileProvider = $compileProvider;
}]);
/**
* Run the validation function and update the model and form state
* if there are issues.
*
* @private
* @method run
* @param {String} name Name of the validator
* @param {ModelController} ctrl Model controller where we participate in normal validation status stuff
* @param {FormController} form Form controller where extended error details are registered per validator.
* @param {Function} validateFn Function to valid the value. Only called if value is not empty.
* @param {Any} value Value to validate.
* @param {Any} [argument] Optional argument to the validation function.
* @param {Scope} [scope] The directive scope. Helpful if you want to $eval the argument.
* @return {Boolean} true if the value is valid, false otherwise.
*/
var run = function(name, ctrl, form, validateFn, value, argument, scope) {
if (ctrl.$isEmpty(value)) {
return true;
}
var result = validateFn(value, argument, scope);
UTILS.updateExtendedReporting(result.isValid, ctrl, form, name, result);
return result.isValid;
};
/**
* Run the validation function async and update the model and form state
* if there are issues.
*
* @private
* @method runAsync
* @param {PromiseProvider} $q from angular since we don't have an injector here, the
* caller needs to do the injection. Note: the createDirective()
* style of invocation takes care of this for you. If calling the manually,
* you need to inject $q into your directive/controller and pass it.
* @param {String} name Name of the validator
* @param {ModelController} ctrl Model controller where we participate in normal validation status stuff
* @param {FormController} form Form controller where extended error details are registered per validator.
* @param {Function} validateFn Function to valid the value. Only called if value is not empty.
* @param {Any} value Value to validate.
* @param {Any} [argument] Optional argument to the validation function.
* @param {Scope} [scope] The directive scope. Helpful if you want to $eval the argument.
* @return {Promise}
*/
var runAsync = function($q, name, ctrl, form, validateAsyncFn, value, argument, scope) {
var defer = $q.defer();
if (!ctrl.$isEmpty(value)) {
validateAsyncFn(value, argument, scope).then(
function(result) {
UTILS.updateExtendedReporting(result.isValid, ctrl, form, name, result);
ctrl.$setValidity(name, result.isValid);
if (result.isValid) {
defer.resolve();
} else {
defer.reject();
}
},
function(error) {
defer.reject(error);
});
}
return defer.promise;
};
/**
* Generate a directive dynamically for the specified
* validator configuration and name.
*
* @private
* @method createDirective
* @param {String} validationKey [description]
* @param {Object} validationObject [description]
*/
var createDirective = function(validationKey, validationObject, $q) {
module.compileProvider.directive(validationKey, function() {
var directiveName = validationKey;
return {
require: "ngModel",
link: function(scope, elem, attr, ctrl) {
var form = elem.controller("form");
UTILS.initializeExtendedReporting(ctrl, form);
ctrl.$validators[directiveName] = function(value) {
var argument = attr[directiveName];
var fn = validationObject[directiveName];
var isAsync = fn.async;
if (!isAsync) {
return run(directiveName, ctrl, form, fn, value, argument, scope);
} else {
return runAsync($q, directiveName, ctrl, form, fn, value, argument, scope);
}
};
scope.$watch(
function() {
return attr[directiveName];
},
function(newVal) {
ctrl.$validate();
}
);
}
};
});
};
/**
* Creates directives based on the configuration object passed.
*
* @method generate
* @param {Object} validationObject Collection of key/function pairs for each directive to generate where:
* @param {String} Name of the validator directive.
* @param {Function} Implementation of the validator directive validation method with the signature:
* fn(value, args...) where the arguments list is as follows:
* @param {String} value Value to validate.
* @param {Any} [arg] Optional argument with a context to the specific validator.
* @param {Object} [$q] Optional. Only required if creating an asynchronous validator.
*
* @example Creating a synchronous validator
*
* var validators = {
* isStuff: function(value) {
* var result = validationUtils.initializeValidationResult();
* if (!/stuff/.test(value)) {
* result.isValid = false;
* result.add("stuff", "Not stuff");
* }
* return result;
* }
* };
*
* var validatorModule = angular.module("validate");
* validatorModule.run(["validatorFactory",
* function(validatorFactory) {
* validatorFactory.generate(validators);
* }
* ]);
*
* @example Creating an async validator
*
* var validatorModule = angular.module("validate");
* validatorModule.run(["validatorFactory", "$q"
* function(validatorFactory, $q) {
* var validators = {
* isAsyncStuff: function(value) {
* var result = validationUtils.initializeValidationResult();
* var defer = $q.defer();
* var timeout = $timeout(function() {
* result.isValid = false;
* result.add("stuff", "Not stuff");
* defer.resolve(result);
* });
* return defer.promise;
* }
* };
* validators.isAsyncStuff.async = true;
*
* validatorFactory.generate(validators, $q);
* }
* ]);
*/
var generate = function(validationObject, $q) {
var keys = _.keys(validationObject);
for (var i = 0, len = keys.length; i < len; i++) {
var name = keys[i];
createDirective(name, validationObject, $q);
}
};
/**
* Construct the factory method
*
* @private
* @method validate
* @return {Object}
* {Function} generate Factory method to generate validation directives from configuration.
*/
var validate = function() {
return {
generate: generate,
};
};
module.factory("validatorFactory", validate);
return {
run: run,
runAsync: runAsync,
};
}
);
/*
# cjt/views/applicationController.js.example Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
/**
* DEVELOPER NOTES: This module is used to contain common methods used by child views.
*
* Usage:
*
* <div ng-controller="applicationController">
* <div ng-controller="childController">
* <a ng-click="loadView('child1')">
* Load a view using the loadView method inherited from applicationController
* </a>
* </div>
* </div>
*/
define(
'cjt/views/applicationController',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/util/httpStatus",
"ngRoute",
"cjt/services/alertService",
"cjt/services/viewNavigationApi"
],
function(angular, CJT, LOCALE, HTTP_STATUS) {
var module = angular.module("cjt2.views.applicationController", [
"ngRoute",
"cjt2.services.alert",
"cjt2.services.viewNavigationApi",
]);
/**
* Parent controller for applications. Provides a common API for controllers and handles some
* other common scenarios such as routeChange errors.
*/
module.controller("applicationController", [
"$scope",
"$location",
"$anchorScroll",
"$route",
"$rootScope",
"alertService",
"viewNavigationApi",
function($scope, $location, $anchorScroll, $route, $rootScope, alertService, viewNavigationApi) {
if (angular.module("ngRoute")) {
// Add a handler for route change failures, such as session expiring or template failing to load.
$rootScope.$on("$routeChangeError", function(e, next, last, error) {
if (error && error.status !== 200) {
var message = LOCALE.maketext("The system failed to change the route with the following error: [_1] - [_2]", error.status, HTTP_STATUS.convertHttpStatusToReadable(error.status));
if (error.status === 401 || error.status === 403) {
message += " " + LOCALE.maketext("Your session may have expired or you logged out of the system. [output,url,_1,Log in] again to continue.", CJT.getLoginPath());
}
alertService.add({
message: message,
type: "danger"
});
} else {
alertService.add({
message: LOCALE.maketext("The system failed to change the route, but there is no information about the error."),
type: "danger"
});
}
});
}
/**
* Get the current route
*
* @method getCurrentRoute
* @return {RouteObject}
*/
$scope.getCurrentRoute = function() {
return $route.current;
};
/**
* Loads the specified view using the location service
*
* @method loadView
* @param {String} view The name of the view to load
* @param {Object} query Optional query string properties passed as an hash.
* @param {Object} [options] Optional. A hash of additional options.
* @param {Boolean} [options.clearAlerts] If true, the default alert group in the alertService will be cleared.
* @param {Boolean} [options.replaceState] If true, the current history state will be replaced by the new view.
* @return {$location} The Angular $location service used to perform the view changes.
* @see cjt2/services/viewNavigationApi.js
*/
$scope.loadView = viewNavigationApi.loadView;
/**
* Scrolls to the specified id using the location hash
*
* @method scrollTo
* @param {String} id The id of the anchor to scroll the view to
* @param {Boolean} [cancel] If provided and true, will cancel the routing, otherwise, triggers routing
* @reference To prevent actual routing: http://stackoverflow.com/questions/17711232/scroll-to-in-angularjs
*/
$scope.scrollTo = function(id, cancel) {
var oldId;
if (cancel) {
oldId = $location.hash();
}
$location.hash(id);
$anchorScroll();
if (cancel) {
$location.hash(oldId);
}
};
$scope.viewDoneLoading = false;
/**
* Hide any view loading panels that are showing.
*/
$scope.hideViewLoadingPanel = function() {
$scope.viewDoneLoading = true;
};
/**
* Show any view loading panels that are showing.
*/
$scope.showViewLoadingPanel = function() {
$scope.viewDoneLoading = false;
};
/**
* Marks the phrase in the template as translatable for the harvester. // ## no extract maketext
* This is a convenience function for use in templates and partials.
*
* @method translatable // ## no extract maketext
* @param {String} str Translatable string
* @return {String} Same string, this is just a marker function for the harvester
* @example
* In your template:
*
* <directive param="translatable('Some string to translate with parameters [_1]')"> // ## no extract maketext
* </directive>
*
* In your JavaScript:
*
* var localized = LOCALE.makevar(template)
*
*/
$scope.translatable = function(str) { // ## no extract maketext
return str;
};
}
]);
}
);
/*
* services/popupService.js Copyright(c) 2020 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* DEVELOPERS NOTES:
*/
/* global define: false */
define('cjt/services/popupService',[
"angular",
"lodash"
], function(
angular,
_
) {
var module = angular.module("cjt2.services.popupService", []);
// Applications can customize the defaults by
// injecting it in startup code replacing values
// as needed.
module.value("popupServiceDefaults", {
top: 0,
left: 0,
width: 400,
height: 300,
autoCenter: true,
name: "_blank"
}
);
module.service("popupService", [
"$window",
"popupServiceDefaults",
function(
$window,
DEFAULTS
) {
var numberProps = { "top": true, "left": true, "width": true, "height": true };
var booleanProps = { "scrollbar": true, "menubar": true, "toolbar": true, "location": true, "status": true };
var ignoredProps = { "autoCenter": true, "newTab": true, "name": true };
/**
* Generate a window.open() style specification string.
*
* @private
* @name _makeWindowSpec
* @param {Object} opts
* @param {Number} [opts.top] Optional. Top position of dialog
* @param {Number} [opts.left] Optional. Left position of dialog
* @param {Number} [opts.width] Optional. Width of the dialog
* @param {Number} [opts.height] Optional. Height of the dialog
* @param {Boolean} [opts.scrollBar] Optional. Shows scrollbar if true, hides it if false.
* @param {Boolean} [opts.autoCenter] Optional. Centers the popup if true, does not center if false. Defaults to auto centering.
* @return {String}
*/
function _makeWindowSpec(opts) {
var spec = [];
_.each(opts, function(value, key) {
if (numberProps[key] && !angular.isUndefined(value) && angular.isNumber(value)) {
spec.push(key + "=" + value);
} else if (booleanProps[key] && !angular.isUndefined(value)) {
spec.push(key + "=" + (value ? "yes" : "no"));
} else if (!ignoredProps[key]) {
throw "Unsupported property: " + key;
}
});
return spec.join(",");
}
return {
/**
* Show a popup window with the provided url and name. Additional
* options are available in the opts as described below:
*
* @method openPopupWindow
* @public
* @param {String} url Url to navigate to in the window.
* @param {String} [name] Optional. Name of the window.
* @param {Object} [opts] Optional. With the following possible properties:
* @param {Number} [opts.top] Optional. Top position of dialog
* @param {Number} [opts.left] Optional. Left position of dialog
* @param {Number} [opts.width] Optional. Width of the dialog
* @param {Number} [opts.height] Optional. Height of the dialog
* @param {Boolean} [opts.scrollbars] Optional. Shows scrollbar if true, hides it if false.
* @param {Boolean} [opts.autoCenter] Optional. Centers the popup if true, does not center if false. Defaults to auto centering.
* @param {Boolean} [opts.newTab] Optional. Opens in a new tab instead of a popup. top/left/width/height are ignored.
* @return {WindowHandle} Handle to the popup.
*/
openPopupWindow: function(url, name, opts) {
if (!opts) {
opts = {};
}
name = name || DEFAULTS.name;
// Initialize the defaults
_.each(numberProps, function(value, key) {
if (!angular.isUndefined(opts[key]) ||
!angular.isUndefined(DEFAULTS[key])) {
opts[key] = opts[key] || DEFAULTS[key];
}
});
_.each(booleanProps, function(value, key) {
if (!angular.isUndefined(opts[key]) ||
!angular.isUndefined(DEFAULTS[key])) {
opts[key] = opts[key] || DEFAULTS[key];
}
});
// Calculate the centering if needed
if (!opts.newTab && opts.autoCenter) {
// Since IE does not support availTop and availLeft, we degrade to showing the
// popup in the primary monitor using screen.top and screen.left to help decide
// NOTE: Auto centering does not work on IEEdge if the parent window is not full
// screen. This seems to be related to this bug:
// https://connect.microsoft.com/IE/Feedback/Details/2434857
var top = !angular.isUndefined($window.screen.availTop) ? $window.screen.availTop : $window.screenTop;
var left = !angular.isUndefined($window.screen.availLeft) ? $window.screen.availLeft : $window.screenLeft;
var height = !angular.isUndefined($window.screen.availHeight) ? $window.screen.availHeight : $window.screen.height;
var width = !angular.isUndefined($window.screen.availWidth) ? $window.screen.availWidth : $window.screen.width;
// Now do the centering
opts.top = (top + height / 2) - (opts.height / 2);
opts.left = (left + width / 2) - (opts.width / 2);
}
if (opts.newTab) {
delete opts.top;
delete opts.left;
delete opts.width;
delete opts.height;
}
var spec = _makeWindowSpec(opts);
return $window.open(url, name, spec);
},
defaults: DEFAULTS,
};
}
]);
});
/*
* services/windowMonitorService.js Copyright(c) 2020 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* DEVELOPERS NOTES:
* 1) Currently this service sets up a separate monitor for each window.
* It may be more efficient to setup a single monitor when the first window
* is registered with the service and keep it around until the last window
* monitored is detached.
* 2) I'm not sure the current _isWindow() method provides enough verification
* if the passed object is a window though its adequate to keep from getting
* exceptions.
*/
/* global define: false */
define('cjt/services/windowMonitorService',[
"angular",
"lodash"
], function(
angular,
_
) {
var module = angular.module("cjt2.services.windowMonitor", []);
module.constant("windowMonitorServiceConfig", {
/**
* The windowMonitorService polls windows handels registered
* with it to detect if the window is closed. Once it detects
* a closed window, it will call a callback. This interval is
* the time between polling runs.
*
* @name windowMonitorServiceConfig.monitorInterval
* @type {Number} Number of milliseconds to wait between polls of the
* windows to see if they have closed.
*/
monitorInterval: 250 // millisecond
});
module.factory("windowMonitorService", [
"$q",
"$interval",
"windowMonitorServiceConfig",
function(
$q,
$interval,
windowMonitorServiceConfig
) {
var _id = 0;
var _intervals = {};
/**
* Performs a very basic check to see if the passed object is of type Window.
* This is helpful since we can't rely on instanceof for window objects and
* there's no uniform standard for identifying a window object, particularly
* once it has been closed.
*
* @method _isWindow
* @private
* @param {Object} obj The object to be tested.
* @return {Boolean} True if it's a Window object.
*/
function _isWindow(obj) {
return angular.isDefined(obj.closed);
}
/**
* Stop a referenced window monitor and cleanup
*
* @method _stop
* @private
* @param {Object} reference
*/
function _stop(reference) {
if (reference) {
$interval.cancel(reference.promise);
delete _intervals[reference.id];
}
}
var api = {
/**
* Periodically check to see if a window in storage is closed or not, and execute a
* callback function, if it is closed.
*
* @method start
* @param {Window} handle The window to monitor.
* @param {Function} callback The function to execute if the window is closed.
* @param {Number} [frequency] The interval in milliseconds. Defaults to what is
* set in the windowMonitorServiceConfig.monitorInterval.
*/
start: function(handle, callback, frequency) {
var promise;
var id = _id++;
var interval = frequency || windowMonitorServiceConfig.monitorInterval;
var fn = function() {
if (handle.closed) {
callback("closed", handle);
api.stop(handle, true);
}
};
if (handle && angular.isFunction(callback)) {
promise = $interval(fn, interval);
// Save the interval information
_intervals[id] = {
id: id,
handle: handle,
promise: promise,
callback: callback
};
} else {
throw new ReferenceError("Both an window and a callback function are required.");
}
return handle;
},
/**
* Stop monitoring a window that is being watched
*
* @method stop
* @param {Window} handle The window being monitored
* @param {Boolean} [auto] Used internally only.
*/
stop: function(handle, auto) {
if (!handle) {
throw new ReferenceError("The stop method requires an window handle argument.");
}
var reference;
if (_isWindow(handle)) {
reference = _.find(_intervals, function(ref) {
return handle === ref.handle;
});
}
if (reference) {
if (!auto) {
reference.callback("canceled", reference.handle);
}
_stop(reference);
}
},
/**
* Check if the service is monitoring anything.
*
* @method isMonitoring
* @return {Boolean} true if monitoring anything, false otherwise.
*/
isMonitoring: function() {
return _intervals && Object.keys(_intervals).length > 0;
},
/**
* Stop all registered monitors
*
* @method stopAll
*/
stopAll: function() {
angular.forEach(_intervals, function(reference) {
_stop(reference);
});
}
};
return api;
}
]);
});
/*
* cjt2/services/pageIdentifierService.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Service that generates a page specific identifer for use by other services
*
* @module cjt/services/pageIdentifer
*
*/
define(
'cjt/services/pageIdentifierService',[
"angular",
"cjt/core",
"cjt/util/locale",
],
function(angular, CJT, LOCALE) {
"use strict";
// Retrieve the current application
var module = angular.module("cjt2.services.pageIdentiferService", []);
module.factory("pageIdentifierService", [
"$document",
function($document) {
return {
/**
* Generated page identifier.
*
* @name _pageIdentifier
* @type {?String}
* @private
*/
_pageIdentifier: null,
/**
* jqLite wrapped dom element.
*
* @external jqLiteObject
* @see {@link https://docs.angularjs.org/api/ng/function/angular.element|jqLiteObject}
*/
/**
* Get the jqlite wrapped document object.
*
* @method _getDocument
* @return {jqLiteObject} Document wrapped in a jqList wrapper.
*/
_getDocument: function _getDocument() {
return $document;
},
/**
* Get the document id from the markup.
*
* @method _getDocumentId
* @return {String} The unique id for the body element of the page.
*/
_getDocumentId: function _getDocumentId() {
return this._getDocument().find("body").attr("id");
},
/**
* Builds the page identifier to be used for saving components
*
* @method _buildPageIdentifier
* @private
* @return {Boolean|String} Returns a boolean value of false if it fails,
* otherwise it returns the identifier generated.
*
*/
_buildPageIdentifier: function _buildPageIdentifier() {
if (this._pageIdentifier) {
return this._pageIdentifier;
}
if (!CJT.applicationName) {
throw new Error(LOCALE.maketext("The system could not generate a page identifier with the [asis,pageIdentiferService]. It also could not determine the “[asis,(cjt/core).applicationName]” for the running application."));
}
var bodyId = this._getDocumentId();
if (!bodyId) {
throw new Error(LOCALE.maketext("The system could not generate a page identifier with the [asis,pageIdentiferService]. You must specify the [asis,body.id]."));
}
if (CJT.applicationName && bodyId) {
this._pageIdentifier = "CSSS_" + CJT.applicationName + "_" + bodyId;
}
return this._pageIdentifier;
},
/**
* Get the page identifier for the current page.
*
* @method getPageIdentifier
* @return {String|Boolean} Returns the identifier or false if it can not be generated
* for the page.
*/
getPageIdentifier: function getPageIdentifier() {
if (this._pageIdentifier) {
return this._pageIdentifier;
}
return this._buildPageIdentifier();
}
};
}
]);
}
);
/*
# cjt/models/searchSettingsModel.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Provides a factory method for generating a searchSettingsPanel
* directive for a given environment.
*
* @module cjt/directives/searchSettingsModel
*/
/* eslint-disable camelcase */
define(
'cjt/models/searchSettingsModel',[
"angular",
"cjt/util/locale",
],
function(angular, LOCALE) {
"use strict";
// Retrieve the current application
var module = angular.module("cjt2.directives.searchSettingsModel", []);
/**
* @external ngFilter
* @see {@link https://docs.angularjs.org/api/ng/filter/filter|$filter}
*/
/**
* @typedef {Object} SearchSettingOption
* @property {String} label label for the option. Should be localized.
* @property {String} description description of the option. Should be localized.
* @property {String|Number} value value for the option
*/
/**
* The SearchSettingsModel consists of one or more of these objects.
*
* @typedef {Object} SearchSetting
* @property {String} label label for the setting.
* @property {String} item_key key to find the item in nvdata.
* @property {SearchSettingOption[]} options list of options that can be selected
*/
/**
* Factory for creating a SearchSettingsModel
*
* @ngfactory SearchSettingsModel
* @param {ngFilter} $filter Filter service from angularjs.
* @return {SearchSettingsModel} [description]
*/
module.factory("SearchSettingsModel", ["$filter", function($filter) {
/**
*
* @class SearchSettingsModel
* @param {Object.<string, SearchSetting>} options set of options to be displayed by the SearchSettingsPanel which would look as follows:
*
* {
* optionList2: {
* label: "This is the column label of the option",
* item_key: "this_is_the_reference_key_to_filter_on",
* options: [
* {
* value: "value_item_key_must_be_if_this_is_selected",
* label: "Label of the option listed on the panel",
* description: "uib-tooltip description for option"
* }
* ...
* ]
* },
* ...
* optionList2: ...
* }
*
* @param {Object.<string, Object>} option_values default values for specific options:
*
* {
* uniqueOptionKey: {
* "value_item_key_must_be_if_this_is_selected": "default value"
* ...
* },
* ...
* }
*
* @return {SearchSettingsModel}
*
*/
function SearchSettingsModel(options, option_values) {
var self = this;
self.settings = null;
self.settings_values = null;
// Because option_values is optional
option_values = option_values || {};
/**
* Uses the originally passed "options" parameter to generate the
* initial settings and values.
*
* @method _initate_values
* @private
*
*/
self._initate_values = function _initate_values() {
if (self.settings) {
return true;
}
self.settings = {};
self.settings_values = {};
angular.forEach(options, function(setting, filterKey) {
self.settings[filterKey] = setting;
self.settings_values[filterKey] = {};
angular.forEach(setting.options, function(option) {
if (option_values[filterKey] && !angular.isUndefined(option_values[filterKey][option.value])) {
self.settings_values[filterKey][option.value] = option_values[filterKey][option.value];
} else {
self.settings_values[filterKey][option.value] = true;
}
});
});
};
}
angular.extend(SearchSettingsModel.prototype, {
/**
* Get the parsed settings object
*
* @method get_settings
* @return {Object} current settings object
*/
get_settings: function() {
this._initate_values();
return this.settings;
},
/**
* Get the current set values of the settings
*
* @method get_values
* @return {Object} Returns the settings values object. The values correlate to the options->value from the original options array.
*
* {
* uniqueOptionKey: {
* "value_item_key_must_be_if_this_is_selected":true,
* "value_item_key_must_be_if_this_is_selected2":true,
* "value_item_key_must_be_if_this_is_selected3":true
* ...
* },
* ...
* }
*
*/
get_values: function() {
this._initate_values();
return this.settings_values;
},
/**
* Get a list of values for filtered items for a specific filterType
*
* @method get_filtered_values
* @param {String} filterType The column for which you want to get the labels (correlates to the original options top level keys)
* @return {Array.<Any>} returns a flat array of values from a filterType.
* [true, false, true, true, true]
*
*/
get_filtered_values: function(filterType) {
var self = this;
var filterOptions = self.settings[filterType];
var values = [];
var settings_values = self.get_values();
if (!settings_values) {
return [];
}
if (filterOptions) {
angular.forEach(filterOptions.options, function(option) {
if (settings_values[filterType][option.value]) {
values.push(option.value);
}
});
}
return values;
},
/**
* Get a list of display labels for filtered items for a specific filterType
*
* @method get_filtered_labels
* @param {String} filterType The column for which you want to get the labels (correlates to the original options top level keys)
* @return {Array.<String>} returns a flat array of labels to display. If no items are filtered, the array is empty.
* ["Lela", "Fry", "Bender"]
*/
get_filtered_labels: function(filterType) {
var self = this;
var filterOptions = self.settings[filterType];
var values = [];
var settings_values = self.get_values();
if (!settings_values) {
return [];
}
if (filterOptions) {
angular.forEach(filterOptions.options, function(option) {
if (settings_values[filterType][option.value]) {
values.push(option.label);
}
});
// so that we don't display the label if we're showing "all", but we don't have to relay on in-dom logic
if (values.length === filterOptions.options.length) {
return [];
}
}
return values.length ? values : [LOCALE.maketext("None")];
},
/**
* Set all the items in a column to a value
*
* @method set_search_filter_values
* @param {String} filterKey The column for which you want to update the values (correlates to the original options top level keys)
* @param {*} filterValue new value to set the items to
* @return {None} does not return a value
*/
set_search_filter_values: function(filterKey, filterValue) {
var self = this;
angular.forEach(self.settings_values[filterKey], function(option, key) {
self.settings_values[filterKey][key] = filterValue;
});
},
/**
* Isolate a specific item in a filter to display only that one
*
* @method show_only
* @param {String} filterKey key associated with the column
* @param {String} itemKey key associated with the row in the column
* @return {None} None
*/
show_only: function(filterKey, itemKey) {
var self = this;
self.set_search_filter_values(filterKey, false);
if (self.settings_values[filterKey]) {
self.settings_values[filterKey][itemKey] = true;
}
},
/**
* This is the hook function used to filter the items list. It will use the existing setting_values and compare them to the item value key
*
* @method filter
* @param {Array} items An array of items to filter based on each of the options in the panel
*
* [
* {
* key: value, // where 'key' correlates the the 'item_key' established by each column in the initial options array
* key2: value
* }
* ]
*
* @return {Array} filtered version of the passed in items array
*
*/
filter: function(items) {
var typesToFilter = {};
var self = this;
angular.forEach(self.settings, function(searchFilterOption, filterKey) {
var true_values = [];
angular.forEach(searchFilterOption.options, function(option) {
// One of the options isn't selected; filter based on it
if (self.settings_values[filterKey][option.value]) {
true_values.push(option.value);
}
});
// lengths are not equal; some are disabled.
if (true_values.length !== searchFilterOption.options.length) {
// store in keyed values for faster lookup
typesToFilter[filterKey] = {};
angular.forEach(true_values, function(true_value) {
typesToFilter[filterKey][true_value] = 1;
});
}
});
return $filter("filter")(items, function(item) {
for (var settingKey in typesToFilter) {
if (typesToFilter.hasOwnProperty(settingKey)) {
var itemKey = self.settings[settingKey].item_key;
var itemValue = item[itemKey];
// value doesn't exist in the allowed and filtered values
if (!self.settings_values[settingKey][itemValue]) {
return false;
}
}
}
return true;
});
}
});
return SearchSettingsModel;
}]);
}
);
/*
# cjt/modules.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false, console: false */
define(
'cjt/modules',[
PAGE.CJT2_MODULES_DEPENDENCY_PATH,
"cjt/util/module",
"cjt/bootstrap",
"cjt/decorators/$httpDecorator",
"cjt/decorators/alertAPIReporter",
"cjt/decorators/angularChosenDecorator",
"cjt/decorators/dynamicName",
"cjt/decorators/growlAPIReporter",
"cjt/decorators/growlDecorator",
"cjt/decorators/paginationDecorator",
"cjt/decorators/uibTypeaheadDecorator",
"cjt/diag/routeDirective",
"cjt/directives/actionButtonDirective",
"cjt/directives/alert",
"cjt/directives/alertList",
"cjt/directives/autoFocus",
"cjt/directives/boolToInt",
"cjt/directives/breadcrumbs",
"cjt/directives/bytesInput",
"cjt/directives/callout",
"cjt/directives/checkStrength",
"cjt/directives/copyField",
"cjt/directives/datePicker",
"cjt/directives/deepTriStateCheckbox",
"cjt/directives/disableAnimations",
"cjt/directives/displayPasswordStrength",
"cjt/directives/dynamicValidatorDirective",
"cjt/directives/focusFirstErrorDirective",
"cjt/directives/focusInput",
"cjt/directives/formWaiting",
"cjt/directives/includeExclude",
"cjt/directives/jsonFieldDirective",
"cjt/directives/labelSuffixDirective",
"cjt/directives/lastItem",
"cjt/directives/limitRange",
"cjt/directives/loadingPanel",
"cjt/directives/multiFieldEditor",
"cjt/directives/multiFieldEditorItem",
"cjt/directives/indeterminateState",
"cjt/directives/ngDebounceDirective",
"cjt/directives/onKeyupDirective",
"cjt/directives/pageSizeDirective",
"cjt/directives/pageSizeButtonDirective",
"cjt/directives/passwordFieldDirective",
"cjt/directives/preventDefaultOnEnter",
"cjt/directives/preventNavigationOnBackspaceDirective",
"cjt/directives/processingIconDirective",
"cjt/directives/quickFiltersDirective",
"cjt/directives/quickFilterItemDirective",
"cjt/directives/responsiveSortDirective",
"cjt/directives/responsiveSortInsertDirective",
"cjt/directives/searchDirective",
"cjt/directives/selectSortDirective",
"cjt/directives/spinnerDirective",
"cjt/directives/statsDirective",
"cjt/directives/terminal",
"cjt/directives/timePicker",
"cjt/directives/toggleLabelInfoDirective",
"cjt/directives/toggleSortDirective",
"cjt/directives/toggleSwitchDirective",
"cjt/directives/triStateCheckbox",
"cjt/directives/updatePasswordStrengthDirective",
"cjt/directives/validateEqualsDirective",
"cjt/directives/validateMinimumPasswordStrength",
"cjt/directives/validationContainerDirective",
"cjt/directives/validationItemDirective",
"cjt/filters/breakFilter",
"cjt/filters/htmlFilter",
"cjt/filters/jsonFilter",
"cjt/filters/nospaceFilter",
"cjt/filters/notApplicableFilter",
"cjt/filters/replaceFilter",
"cjt/filters/splitFilter",
"cjt/filters/startFromFilter",
"cjt/filters/timezoneFilter",
"cjt/filters/wrapFilter",
"cjt/filters/rangeFilter",
"cjt/services/APIFailures",
"cjt/services/APICatcher",
"cjt/services/APIService",
"cjt/services/alertService",
"cjt/services/autoTopService",
"cjt/services/dataCacheService",
"cjt/services/onBeforeUnload",
"cjt/services/passwordStrengthService",
"cjt/services/viewNavigationApi",
"cjt/validator/validateDirectiveFactory",
"cjt/views/applicationController",
"cjt/filters/qaSafeIDFilter",
"cjt/services/popupService",
"cjt/services/windowMonitorService",
"cjt/services/pageIdentifierService",
"cjt/models/searchSettingsModel",
],
function(APPLICATION_SPECIFIC_MODULES, MODULE_UTIL) {
"use strict";
// Create the cjt2 module package and set its dependencies
MODULE_UTIL.createModule(PAGE.CJT2_ANGULAR_MODULE_NAME, [
// Decorators
"cjt2.decorators.$http",
"cjt2.decorators.alertAPIReporter",
"cjt2.decorators.angularChosenDecorator",
"cjt2.decorators.dynamicName",
"cjt2.decorators.growlAPIReporter",
"cjt2.decorators.growlDecorator",
"cjt2.decorators.paginationDecorator",
"cjt2.decorators.uibTypeaheadDecorator",
// Diagnostics
"cjt2.diag.route",
// Directives
"cjt2.directives.actionButton",
"cjt2.directives.alert",
"cjt2.directives.alertList",
"cjt2.directives.autoFocus",
"cjt2.directives.boolToInt",
"cjt2.directives.breadcrumbs",
"cjt2.directives.bytesInput",
"cjt2.directives.callout",
"cjt2.directives.checkPasswordStrength",
"cjt2.directives.copyField",
"cjt2.directives.datePicker",
"cjt2.directives.deepTriStateCheckbox",
"cjt2.directives.disableAnimations",
"cjt2.directives.displayPasswordStrength",
"cjt2.directives.dynamicValidator",
"cjt2.directives.focusFirstError",
"cjt2.directives.focusInput",
"cjt2.directives.formWaiting",
"cjt2.directives.excludeCharacters",
"cjt2.directives.includeCharacters",
"cjt2.directives.jsonFieldDirective",
"cjt2.directives.labelSuffix",
"cjt2.directives.lastItem",
"cjt2.directives.limitRange",
"cjt2.directives.loadingPanel",
"cjt2.directives.multiFieldEditor",
"cjt2.directives.multiFieldEditorItem",
"cjt2.directives.indeterminateState",
"cjt2.directives.ngDebounce",
"cjt2.directives.onKeyUp",
"cjt2.directives.pageSize",
"cjt2.directives.pageSizeButton",
"cjt2.directives.password",
"cjt2.directives.preventDefaultOnEnter",
"cjt2.directives.preventNavigationOnBackspace",
"cjt2.directives.processingIcon",
"cjt2.directives.quickFilters",
"cjt2.directives.quickFilterItem",
"cjt2.directives.responsiveSort",
"cjt2.directives.responsiveSortInsert",
"cjt2.directives.search",
"cjt2.directives.selectSort",
"cjt2.directives.spinner",
"cjt2.directives.statsDirective",
"cjt2.directives.terminal",
"cjt2.directives.timePicker",
"cjt2.directives.toggleLabelInfo",
"cjt2.directives.toggleSort",
"cjt2.directives.toggleSwitch",
"cjt2.directives.triStateCheckbox",
"cjt2.directives.updatePasswordStrength",
"cjt2.directives.validateEquals",
"cjt2.directives.minimumPasswordStrength",
"cjt2.directives.validationContainer",
"cjt2.directives.validationItem",
// Filters
"cjt2.filters.break",
"cjt2.filters.encodeHtml",
"cjt2.filters.json",
"cjt2.filters.nospace",
"cjt2.filters.na",
"cjt2.filters.replace",
"cjt2.filters.split",
"cjt2.filters.startFrom",
"cjt2.filters.timezone",
"cjt2.filters.wrap",
"cjt2.filters.qaSafeID",
"cjt2.filters.range",
// Services
"cjt2.services.alert",
"cjt2.services.api",
"cjt2.services.apifailures",
"cjt2.services.apicatcher",
"cjt2.services.autoTop",
"cjt2.services.dataCache",
"cjt2.services.onBeforeUnload",
"cjt2.services.passwordStrength",
"cjt2.services.pageIdentiferService",
"cjt2.services.popupService",
"cjt2.services.viewNavigationApi",
"cjt2.services.windowMonitor",
// Views
"cjt2.views.applicationController",
// Validation
"cjt2.validate"
].concat(APPLICATION_SPECIFIC_MODULES));
}
);
/*
# cjt/directives/copyField.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/directives/copyField',[
"angular",
"cjt/util/locale",
"cjt/core",
"cjt/modules",
"cjt/services/alertService"
],
function(angular, LOCALE, CJT) {
"use strict";
function execCopyToClipboard(fieldID) {
var field = document.getElementById(fieldID);
field.focus();
field.select();
return document.execCommand("copy");
}
/**
* Field and button combo to copy a pre-formatted text to the users clipboard
*
* @module copy-field
* @restrict E
* @memberof cjt2.directives
*
* @example
* <copy-field text="copy me to your clipboard"></copy-field>
*
*/
var RELATIVE_PATH = "libraries/cjt2/directives/";
var TEMPLATE_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
TEMPLATE_PATH += "copyField.phtml";
var MODULE_NAMESPACE = "cjt2.directives.copyField";
var MODULE_REQUIREMENTS = [ "cjt2.services.alert" ];
var CONTROLLER_INJECTABLES = ["$scope", "$timeout", "alertService"];
var CONTROLLER = function CopyFieldController($scope, $timeout, $alertService) {
$scope._onSuccess = function _onSuccess() {
$alertService.success(LOCALE.maketext("Successfully copied to the clipboard."));
$scope.copying = true;
$timeout(function() {
$scope.copying = false;
}, 3000);
};
$scope._execCopy = function() {
return execCopyToClipboard($scope.copyFieldID);
};
/**
*
* Copy the text currently in the $scope.text to the clipboard
*
*/
$scope.copyToClipboard = function copyToClipboard() {
if ($scope._execCopy()) {
$scope._onSuccess();
}
};
/**
*
* Process updated text to determine if it is multiline or not
*
*/
$scope.processText = function processText() {
if (!$scope.text) {
return;
}
var newTextParts = $scope.text.split("\n");
if (newTextParts.length > 1) {
$scope.multilineRows = newTextParts.length - 1;
}
};
$scope.$watch("text", $scope.processText);
$scope.processText();
};
var module = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
var DIRECTIVE_LINK = function($scope, $element, $attrs) {
$scope.multilineRows = 1;
$scope.copyFieldID = $scope.parentID + "_recordField";
$scope.copying = false;
$scope.placeholderText = $attrs.placeholder ? $attrs.placeholder : LOCALE.maketext("Nothing to copy");
$scope.copyLabel = $attrs.copyLabel || LOCALE.maketext("Copy");
};
module.directive("copyField", function copyFieldDirectiveFactory() {
return {
templateUrl: TEMPLATE_PATH,
scope: {
parentID: "@id",
text: "=",
label: "@"
},
restrict: "E",
replace: true,
transclude: true,
link: DIRECTIVE_LINK,
controller: CONTROLLER_INJECTABLES.concat(CONTROLLER)
};
});
return {
"class": CONTROLLER,
"namespace": MODULE_NAMESPACE,
"link": DIRECTIVE_LINK,
"template": TEMPLATE_PATH,
execCopyToClipboard: execCopyToClipboard
};
}
);
/*
# cjt/directives/searchSettingsPanelFactory.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Provides a factory method for generating a searchSettingsPanel
* directive for a given environment.
*
* @module cjt/directives/searchSettingPanelFactory
*/
/* eslint-disable camelcase */
define(
'cjt/directives/searchSettingsPanelFactory',[
"angular",
"cjt/core",
"cjt/util/locale",
"ngSanitize",
"cjt/models/searchSettingsModel"
],
function(angular, CJT, LOCALE) {
"use strict";
/**
* Generates a searchSettingPanel directive for the given environment
*
* @method makeDirective
* @param {String} moduleName Name of the angular module.
* @param {Array.<String>} moduleDependencies List of dependencies for the angular module.
*/
return function makeDirective(moduleName, moduleDependencies) {
// Retrieve the current application
var module = angular.module(moduleName, moduleDependencies);
var RELATIVE_PATH = "libraries/cjt2/directives/";
var TEMPLATES_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH;
var TEMPLATE = TEMPLATES_PATH + "searchSettingsPanel.phtml";
/**
* Function that is called when the user changes one of the settings.
*
* @callback ngChangeCallback
*/
/**
* Creates a panel used with advanced search options.
* @class SearchSettingsPanel
* @ngdirective searchSettingsPanel
*/
module.directive("searchSettingsPanel", ["componentSettingSaverService",
function($CSSS) {
return {
templateUrl: TEMPLATE,
restrict: "EA",
transclude: true,
scope: {
/**
* Collection of configuration settings for filling
* in the UI of the panel.
* @property ngModel
* @ngattr
* @type {SearchSettingsModel}
*/
ngModel: "=",
/**
* Parent element id.
*
* @property id
* @ngattr
* @type {String}
*/
parentID: "@id",
/**
* Called when the user changes one of the settings
*
* @property ngChange
* @ngattr
* @type {ngChangeCallback}
*/
ngChange: "&",
/**
* Determines whether or not the total number of items is shown.
*
* @property showCount
* @ngattr
* @type {Boolean}
*/
showCount: "=?",
/**
* Determines whether or not to show labels under the search bar
* corresponding to the filtering options chosen in the settings
* panel.
*
* @property displaySetValues
* @ngattr
* @type {Boolean}
*/
displaySetValues: "=",
/**
* Determines whether or not the settings panel is open/shown.
*
* @property displaySettingsPanel
* @ngattr
* @type {Boolean}
*/
displaySettingsPanel: "="
},
link: function(scope) {
scope.options = scope.ngModel.get_settings();
scope.values = scope.ngModel.get_values();
scope.filteredItemsToDisplay = false;
scope.searchSettingsID = scope.parentID + "_SearchSettingsPanel";
scope.setValuesID = scope.parentID + "_SetValuePanel";
scope.all_label = LOCALE.maketext("All");
scope.all_checked = {};
/**
* Set all the items in a column to a value
*
* @method set_search_filter_values
* @scope
* @param {String} filterKey The key for which you want to update the values.
* @param {*} filterValue new value to set the items to
*/
scope.set_search_filter_values = function(filterKey, filterValue) {
scope.ngModel.set_search_filter_values(filterKey, filterValue);
scope.update();
};
/**
* Builds the list of displayable filtered items
*
* @method update_display_values
* @scope
*/
scope.update_display_values = function() {
var showItems = false;
angular.forEach(scope.options, function(optionSettings, optionKey) {
scope.all_checked[optionKey] = false;
if (scope.get_filtered_labels(optionKey).length) {
showItems = true;
}
if (scope.get_filtered_values(optionKey).length === optionSettings.options.length) {
scope.all_checked[optionKey] = true;
}
});
scope.filteredItemsToDisplay = showItems;
};
/**
* Force open the display settings panel
*
* @method open_settings
* @scope
*/
scope.open_settings = function() {
scope.displaySettingsPanel = true;
};
/**
* Initiate an NVData save of the current state of the settings panel
*
* @method saveSettings
* @scope
*/
scope.saveSettings = function() {
$CSSS.set(scope.parentID, scope.values);
};
/**
* Update display values, save settings, and dispatch an ngChange event
*
* @method update
* @scope
*/
scope.update = function() {
scope.update_display_values();
scope.saveSettings();
scope.ngChange();
};
/**
* Update the display labels with the item counts
*
* @method update_display_labels
* @scope
*/
scope.update_display_labels = function() {
scope.all_label = scope.all_label + " (" + scope.showCount.length + ")";
var searchable_items = scope.showCount;
var search_setting_values = {};
/*
translating this:
domainType: {
label: LOCALE.maketext("Domain Types:"),
item_key: "type",
options: [{
"value": "main_domain",
"label": LOCALE.maketext("Main"),
"description": LOCALE.maketext("Only list Main domains.")
}
into this:
search_setting_values["domainType"]["main_domain"] = 0;
*/
// This is split up into three separate loops to allow faster processing and fewer nested loops
angular.forEach(scope.options, function(search_option, property_key) {
search_setting_values[property_key] = {};
angular.forEach(search_option.options, function(type) {
var property_value = type.value;
// set count of each value to zero for each property item
search_setting_values[property_key][property_value] = 0;
});
});
angular.forEach(searchable_items, function(searchable_item) {
angular.forEach(search_setting_values, function(option_value, key) {
var lookup_key = scope.options[key].item_key;
var searchable_item_value = searchable_item[lookup_key];
search_setting_values[key][searchable_item_value]++;
});
});
angular.forEach(scope.options, function(search_option, property_key) {
angular.forEach(search_option.options, function(type) {
var property_value = type.value;
if (!type.original_label) {
type.original_label = type.label;
}
// set count of each value to zero for each property item
var count = search_setting_values[property_key][property_value];
type.label = type.original_label + " (" + count + ")";
});
});
};
if (scope.showCount && typeof (scope.showCount) === "object") {
scope.update_display_labels();
scope.$watch("showCount", function() {
// Only updated it if it's being displayed
if (scope.displaySettingsPanel) {
scope.update_display_labels();
}
}, true);
scope.$watch("displaySettingsPanel", function(newVal, oldVal) {
if (newVal && newVal !== oldVal) {
scope.update_display_labels();
}
});
}
scope.get_filtered_labels = scope.ngModel.get_filtered_labels.bind(scope.ngModel);
scope.get_filtered_values = scope.ngModel.get_filtered_values.bind(scope.ngModel);
var registering = $CSSS.register(scope.parentID);
if (registering) {
registering.then(function(result) {
angular.forEach(result, function(filterGroup, filterKey) {
// Filter Key doesn't exist (column no longer exists)
if (!scope.options[filterKey]) {
return;
}
angular.forEach(filterGroup, function(filterItemValue, filterItemKey) {
// Filter Item Key doesn't exist (group item no longer exists)
if (angular.isUndefined(scope.values[filterKey][filterItemKey])) {
return;
}
scope.values[filterKey][filterItemKey] = filterItemValue;
});
});
scope.update_display_values();
scope.ngChange();
// add watch after registration to prevent overwriting data before stored data is loaded
scope.$watch(function() {
return scope.values;
}, function() {
scope.update();
}, true);
});
}
scope.$on("$destroy", function() {
$CSSS.unregister(scope.parentID);
});
},
};
}
]);
};
}
);
/* eslint-enable camelcase */
;
/*
* cjt2/src/services/componentSettingSaverFactory.js
* Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Factory module that generates componentSettingSaverServices for the
* current application environment.
*
* @module cjt/services/componentSettingSaverFactory
*
*/
define(
'cjt/services/componentSettingSaverFactory',[
"angular",
"cjt/core",
"cjt/util/locale",
"cjt/services/pageIdentifierService"
],
function(angular, CJT, LOCALE) {
"use strict";
/**
* Factory method to generate the componentSettingSaverService for the
* context.
*
* @method makeService
* @param {String} moduleName angularjs module name for the generated service
* @return {ComponentSettingSaverService}
*/
return function makeService(moduleName, dependencies) {
// Retrieve the current application
var module = angular.module(moduleName, dependencies);
return module.factory("componentSettingSaverService", [
"nvDataService",
"$log",
"$q",
"$window",
"pageIdentifierService",
function(nvDataService, $log, $q, $window, pageIdentifierService) {
return {
/**
* The most recent promise fetching the nvdata if any.
* If null, there is no outstanding promise.
*
* @name _currentGetPromise
* @private
* @type {?Promise}
*/
_currentGetPromise: null,
/**
* The last time an update was requested
*
* @name _lastUpdateRequest
* @private
* @type {DateTime}
*/
_lastUpdateRequest: -1,
/**
* Last time a save was requested
*
* @name _lastSavedRequest
* @private
* @type {DateTime}
*/
_lastSavedRequest: 0,
/**
* The collection of components that have been fetched or set. All the data is
* stored as a JSON serialized version of this object. Each key is a component
* and each value is the settings stored for that component.
*
* @name _components
* @private
* @type {Object}
*/
_components: {},
/**
* The collection of components that are managed with this service.
* Each key is a component name. All these components are associated
* with the page identifier so are page specific.
*
* @name _registeredComponents
* @private
* @type {Object}
*/
_registeredComponents: {},
/**
* Use for registering new saveable components. When a component name is registered
* a get request is automatically started.
*
* @method register
* @async
* @param {String} componentName page unique identifier for the saveable component
* @return {Promise.<NameValuePair|Object.<NameValuePair>>} if componentName is specified, it will return just that setting, otherwise it returns all the settings for the specific _pageIdentifier
* @throws {Promise.<String>} If the api call fails, the request times out or the request fails
*/
register: function registerComponent(componentName) {
if (!componentName) {
throw new Error(LOCALE.maketext("The [asis,register] method requires the [asis,componentName]."));
}
var pageId = pageIdentifierService.getPageIdentifier();
if ( !pageId ) {
throw new Error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to register the component “[_1]”. You must set the page identifier.", componentName));
}
// register as a deletable NVData file if function is available to do so.
// TODO: Replace with something portable from cjt2
if (!angular.isFunction($window["register_interfacecfg_nvdata"])) {
$log.warn(LOCALE.maketext("The system could not register the page for the interface settings reset. Is the [asis,interfacereset.js] file missing?"));
} else {
// TODO: Alter register_interfacecfg_nvdata() to prevent duplicate registration
$window.register_interfacecfg_nvdata(this._pageIdentifier);
}
// QUESTION: From what I can tell the only thing that the register() is used for is
// to prevent you from using the same componentName twice, but register() is really
// just a call to get(). What is the reason we want to prevent calling get() twice with
// the same name. It already has the promise cache to prevent multiple backend requests.
if (this._registeredComponents[componentName]) {
throw new Error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to register the component “[_1]”. A component with the same identifier already exists.", componentName));
}
this._registeredComponents[componentName] = componentName;
return this.get(componentName);
},
/**
* Unregisters a saveable component. Single Page Apps may try to register the same component name
* more than once when navigating from one view to another and back again. Use this method to free
* up the component key when it's no longer needed for a view.
*
* @method unregisterComponent
* @param {String} componentName The unique identifier for the component.
* @return {Boolean} True if it succeeds, false if it fails.
*/
unregister: function unregisterComponent(componentName) {
// QUESTION: Should unregistering a component also clear it data from the _components
// structure since its not part of the other view?
// QUESTION: It seems like this tool needs to have a way to pick up the view name also?
// NOTE: If we remove register(), then unregister can be removed too.
if (this._registeredComponents[componentName]) {
delete this._registeredComponents[componentName];
return true;
} else {
$log.error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to unregister the component “[_1]”. No such component exists.", componentName));
return false;
}
},
/**
* Set the value of a component
*
* @method set
* @async
* @param {String} componentName page unique identifier for the saveable component. must be registered
* @param {Object|Array} settings JSON encodeable object representing the component settings
* @return {Promise.<Array.<SavedNameValuePair>>} returns a promise call that is setting the values via api
* @throws {Promise.<String>} If the api call fails, the request times out or the request fails
*/
set: function setComponentSettings(componentName, settings) {
var pageId = pageIdentifierService.getPageIdentifier();
if (!pageId) {
$log.error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to save the component settings for “[_1]”. You must set the page identifier.", componentName));
return false; // TODO: It's complicated to return both a promise and a bool. Should probably throw or return a $q.defer().reject()
}
this._components[componentName] = settings;
var nvdata = {};
nvdata[pageId] = JSON.stringify(this._components);
this._lastSavedRequest = new Date().getTime();
return nvDataService.setObject(nvdata);
},
/**
* Used for getting the updated values of a component as stored in the NVData
*
* @method get
* @async
* @param {String} componentName page unique identifier for the saveable component. must be registered through .register()
* @return {Promise.<NameValuePair|Object.<NameValuePair>>} if componentName is specified, it will return just that setting, otherwise it returns all the settings for the specific _pageIdentifier
* @throws {Promise.<String>} If the api call fails, the request times out or the request fails
*/
get: function getComponentSettings(componentName) {
var self = this;
// If we are in the process of getting an updated file, just use the existing promise
if (self._currentGetPromise) {
return self._currentGetPromise;
}
// If we haven't saved anything new since we last saved this page, don't retrieve again.
if (self._lastUpdateRequest > self._lastSavedRequest) {
var deferred = $q.defer();
if (componentName) {
deferred.resolve(self._components[componentName]);
} else {
deferred.resolve(self._components);
}
return deferred.promise;
}
self._lastUpdateRequest = new Date().getTime();
var pageId = pageIdentifierService.getPageIdentifier();
if (!pageId) {
$log.error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to retrieve the requested component settings. You must set the page identifier."));
return false;
}
self._currentGetPromise = nvDataService.get(pageId).then(
function(pairs) {
var pair = pairs.pop();
var value = pair.value;
if (value) {
try {
value = JSON.parse(value);
} catch (e) {
var pageId = pageIdentifierService.getPageIdentifier();
$log.error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to parse the stored [asis,NVData] file for this page “[_1]”.", pageId));
value = {};
}
self._components = value;
}
// Need to clear this before calling again so it resolves properly
self._currentGetPromise = null;
return self.get(componentName);
}).finally(function() {
self._currentGetPromise = null;
});
return self._currentGetPromise;
},
/**
* Get the cached settings for a component. This is useful when you are not willing to wait on
* the network request and just want to get the currently known value.
*
* @param {string} componentName The component whose values we want to retrieve.
* @returns {object} An object containing cachedValue and requestInProgress keys.
*/
getCached: function getCachedComponentSettings(componentName) {
return {
cachedValue: componentName ? this._components[componentName] : this._components,
requestInProgress: Boolean( this._currentGetPromise ),
};
},
};
}
]);
};
}
);
/*
* cjt2/src/services/cpanel/componentSettingSaverService.js
* Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Provides componentSettingSaverService for cPanel. The service saves
* page specific data by component. Each component is commonly a view.
*
* @module cjt/services/cpanel/componentSettingSaverService
* @ngmodule cjt2.services.cpanel.componentSettingSaverService
*/
define(
'cjt/services/cpanel/componentSettingSaverService',[
"cjt/services/componentSettingSaverFactory",
"cjt/services/cpanel/nvDataService",
],
function(makeService) {
"use strict";
/**
* @ngService componentSettingSaverService
* @borrows module:cjt/services/componentSettingSaverFactory.getPageIdentifier as getPageIdentifier
* @borrows module:cjt/services/componentSettingSaverFactory.register as register
* @borrows module:cjt/services/componentSettingSaverFactory.unregister as unregister
* @borrows module:cjt/services/componentSettingSaverFactory.set as set
* @borrows module:cjt/services/componentSettingSaverFactory.get as get
*/
return makeService(
"cjt2.services.cpanel.componentSettingSaverService",
[
"cjt2.services.cpanel.nvdata",
"cjt2.services.pageIdentiferService"
]
);
}
);
/*
* cjt2/src/directives/cpanel/searchSettingsPanel.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Provides searchSettingsPanel for cPanel. The service saves
* page specific data by component. Each component is commonly a view.
*
* @module cjt/directives/cpanel/searchSettingsPanel
* @ngmodule cjt2.directives.cpanel.searchSettingsPanel
*/
/* global define: false */
define(
'cjt/directives/cpanel/searchSettingsPanel',[
"cjt/directives/searchSettingsPanelFactory",
"cjt/services/cpanel/componentSettingSaverService",
],
function(makeDirective) {
"use strict";
/**
* @ngDirective componentSettingSaverService
* @borrows module:cjt/directives/searchSettingsPanelFactory/SearchSettingsPanel.ngModel as SearchSettingsPanel.ngModel
* @borrows module:cjt/directives/searchSettingsPanelFactory/SearchSettingsPanel.id as SearchSettingsPanel.id
* @borrows module:cjt/directives/searchSettingsPanelFactory/SearchSettingsPanel.ngChange as SearchSettingsPanel.ngChange
* @borrows module:cjt/directives/searchSettingsPanelFactory/SearchSettingsPanel.showCount as SearchSettingsPanel.showCount
* @borrows module:cjt/directives/searchSettingsPanelFactory/SearchSettingsPanel.displaySetValues as SearchSettingsPanel.displaySetValues
* @borrows module:cjt/directives/searchSettingsPanelFactory/SearchSettingsPanel.displaySettingsPanel as SearchSettingsPanel.displaySettingsPanel
*/
return makeDirective(
"cjt2.directives.cpanel.searchSettingsPanel",
[
"cjt2.services.cpanel.componentSettingSaverService",
"cjt2.directives.searchSettingsModel",
"cjt2.templates"
]
);
}
);
/*
# cjt/e2e.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
(function() {
"use strict";
window.__collectedErrors = [];
window.onerror = function(error) {
window.__collectedErrors.push(error);
};
window.getJavascriptErrors = function() {
return window.__collectedErrors;
};
window.clearJavascriptErrors = function() {
window.__collectedErrors = [];
};
})();
define("cjt/e2e", function(){});
/*
# cjt/io/api2-request.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* eslint-env amd */
/* --------------------------*/
// -----------------------------------------------------------------------
// DEVELOPER NOTES:
// 1) Unlike UAPI, the API2 does not conform to a strict results layout.
// As development continues we will likely have to add various exceptions
// to the getData() and getError() methods...
// -----------------------------------------------------------------------
// TODO: Add tests for these
/**
*
* @module cjt/io/api2-request
* @example
require(["lodash","cjt/io/api2-request"], function(_, REQUEST) {
// TODO:
});
*/
define('cjt/io/api2-request',["lodash", "cjt/util/test"], function(_, TEST) {
"use strict";
// ------------------------------
// Module
// ------------------------------
var MODULE_NAME = "cjt/io/api2-request"; // requirejs(["cjt/io/api2-request"], function(REQUEST) {}); -> cjt/io/api2-request.js || cjt/io/api2-request.min.js
var MODULE_DESC = "Contains a helper object used to build API2 call parameters.";
var MODULE_VERSION = 2.0;
// ------------------------------
// Constants
// ------------------------------
var DEFAULT_PAGE_SIZE = 10;
var ASCENDING = 0;
var DESCENDING = 1;
/**
* Creates an empty metadata object
* @method _getEmptyRequestMeta
* @return {Object} Initialized abstract request metadata object
*/
var _getEmptyRequestMeta = function() {
return {
paginate: {
start_page: 0,
start_record: 0,
page_size: DEFAULT_PAGE_SIZE
},
filter: [],
sort: []
};
};
// -----------------------------------------------------------------------------------
// Api request Api2 calls.
//
// @class Api2Request
//
// -----------------------------------------------------------------------------------
var Api2Request = function() {
/**
* API version number to use for UAPI.
*
* @class Api2Request
* @property {Number} version
*/
this.version = 2;
/**
* API module name for UAPI call. Represents the module in <Module>::<Call>().
*
* @class Api2Request
* @property {String} module
*/
this.module = "";
/**
* API function name for UAPI call. Represents the call in <Module>::<Call>().
*
* @class Api2Request
* @property {String} func
*/
this.func = "";
/**
* Collection of arguments for the API call.
*
* @class WhmV1Request
* @property {Object} args
*/
this.args = {};
/**
* API call meta data for the UAPI call. Should be a hash with names for
* each meta data parameter passed to the UAPI call. This data is used to
* control properties such as sorting, filters and paging.
*
* @class Api2Request
* @property {Object} meta
*/
this.meta = _getEmptyRequestMeta();
/**
* Number to add to an auto argument.
*
* @class WhmV1Request
* @property {Number} auto
*/
this.auto = 1;
};
/**
*
* @static
* @class Api2Request
* @property {enum} [sort] Contains sorting constants
*/
Api2Request.sort = {
ASCENDING: ASCENDING,
DESCENDING: DESCENDING
};
Api2Request.prototype = {
/**
* Initialize the sync data
*
* @class Api2Request
* @method initialize
* @param {String} module Name of the module where the API function exists.
* @param {String} func Name of the function to call.
* @param {Object} data Data for the function call.
* @param {Object} meta Meta-data such as sorting, filtering, paging and similar data for the api call.
*/
initialize: function(module, func, data, meta) {
this.module = module;
this.func = func;
this.data = data || {};
this.meta = meta || {};
this.auto = 1;
return this; // for chaining
},
/**
* Adds an argument
*
* @class WhmV1Request
* @method addArgument
* @param {String} name Name of the argument
* @param {Object} value Value of the argument
* @param {Boolean} [isAuto] Optional, will add an auto counter to this argument
*/
addArgument: function(name, value, isAuto) {
if (!isAuto) {
this.args[name] = value;
} else {
this.args[name + this.auto] = value;
}
},
/**
* Removes an argument
*
* @class WhmV1Request
* @method removeArgument
* @param {[type]} name [description]
* @param {Boolean} isAuto [description]
* @return {[type]} [description]
*/
removeArgument: function(name, isAuto) {
if (!isAuto) {
this.args[name] = null;
delete this.args[name];
} else {
this.args[name + this.auto] = null;
delete this.args[name + this.auto];
}
},
/**
* Make the argument counter increment.
* @class WhmV1Request
* @method incrementAuto
* @return {Number} Current value of the increment.
*/
incrementAuto: function() {
this.auto++;
return this.auto;
},
/**
* Clears the arguments
*
* @class WhmV1Request
* @method clearArguments
* @param {String} name Name of the argument
* @param {Object} value Value of the argument
*/
clearArguments: function() {
this.args = {};
},
/**
* Adds the paging meta data to the run parameters in UAPI format
*
* @class Api2Request
* @method addPaging
* @param {Number} start Start page
* ATTR apiIndex.
* @param {Number} size Optional page size, inherits from previous initialization.
*/
addPaging: function(start, size) {
// if the size is equal to the "All" page size sentinel value,
// abort pagination
if (size === -1 || this.meta.size === -1) {
return;
}
size = size || this.meta.size || 10;
this.meta.paginate = this.meta.paginate || {};
this.meta.paginate.start = ((start - 1) * size) + 1;
this.meta.paginate.size = size;
},
/**
* Clears the paging rules from the meta data.
*
* @class Api2Request
* @method clearPaging
*/
clearPaging: function() {
delete this.meta.paginate;
},
/**
* Add sorting rules meta data to the run parameters in API2 format.
*
* @class Api2Request
* @method addSorting
* @param {Hash} [options] Optional options passed in from the outside
* @param {String} field Name of the field to sort on.
* @param {String} direction asc or dsc. Defaults to asc.
* @param {String} type Sort types supported by the API. Defaults to
* equality
*/
addSorting: function(field, direction, type) {
var sortField = field;
var sortDir = direction || "asc";
var sortType = type || "";
if (sortField) {
var api2SortRule = (sortDir === "asc" ? "" : "!") + sortField;
// If we have a type, the we need the complex array format
if (sortType !== "") {
api2SortRule = [api2SortRule, sortType];
}
// Store the rule.
if (this.meta.sort) {
this.meta.sort.push(api2SortRule);
} else {
this.meta.sort = [api2SortRule];
}
}
},
/**
* Clear the sorting rules meta data.
*
* @class Api2Request
* @method clearSorting
*/
clearSorting: function() {
delete this.meta.sort;
},
/**
* Add filter rules meta data to the run parameters in UAPI format.
*
* @class Api2Request
* @method addFilter
* @param {String|Array} key Field name or array of three elements [key, operator, value].
* @param {String} [operator] Comparison operator to use. Optional if the first parameter is an array.
* @param {String} [value] Value to compare the field with. Optional if the first parameter is an array.
*/
addFilter: function(key, operator, value) {
var filter;
if (_.isArray(key)) {
filter = key;
} else {
filter = [key, operator, value];
}
// ---------------------------------------------
// Building a structure that looks like this:
//
// [
// [ key1 , operator1, value1 ],
// [ key2 , operator2, value2 ],
// ...
// ]
// ---------------------------------------------
if (filter) {
if (this.meta.filter) {
this.meta.filter.push(filter);
} else {
this.meta.filter = [filter];
}
}
},
/**
* Clear the meta data for the filter.
*
* @class Api2Request
* @method clearFilter
*/
clearFilter: function() {
delete this.meta.filter;
},
/**
* Build the API2 call data structure.
*
* @class Api2Request
* @method getRunArguments
* @param {String Array} [fields] List of fields to extract.
* @param {Object } [context] Context to call callbacks with.
* @return {Object} Packages up an api call base on collected information.
*/
getRunArguments: function(fields, context) {
return {
version: this.version,
module: this.module,
func: this.func,
meta: this.meta,
args: this.args
};
}
};
// Publish the component
return {
MODULE_NAME: MODULE_NAME,
MODULE_DESC: MODULE_DESC,
MODULE_VERSION: MODULE_VERSION,
Class: Api2Request
};
});
/*
# api/io/api2.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* eslint-env amd */
/* eslint camelcase: "off" */
/* --------------------------*/
// TODO: Add tests for these
/**
* Contain the IAPI Driver implementation used to process cpanel api2
* request/response messages.
* @module cjt2/io/api2
* @example
*
*/
define('cjt/io/api2',["lodash", "cjt/io/base", "cjt/util/test"], function(_, BASE, TEST) {
"use strict";
// ------------------------------
// Module
// ------------------------------
var MODULE_NAME = "cjt/io/api2"; // requirejs(["cjt/io/api2"], function(api) {}); -> cjt/io/api2.js || cjt/io/api2.debug.js
var MODULE_DESC = "Contains the unique bits for integration with API2 calls.";
var MODULE_VERSION = "2.0";
var API_VERSION = 2;
// ------------------------------
// Shortcuts
// ------------------------------
/**
* IAPIDriver for API2
*
* @static
* @public
* @class API2
*/
var api2 = {
MODULE_NAME: MODULE_NAME,
MODULE_DESC: MODULE_DESC,
MODULE_VERSION: MODULE_VERSION,
/**
* Parse an api2-request object to extract
* the interesting parts of a cPanel API 2 call response.
*
* @method parse_response
* @param {object} response The asyncRequest response object
* @return {object} See api_base._parse_response for the format of this object.
*/
parse_response: function(response) {
return BASE._parse_response(api2, response);
},
/**
* Return a list of messages from a cPanel API 2 response, normalized as a
* list of [ { level:"info|warn|error", content:"..." }, ... ]
*
* @method find_messages
* @param {object} response The parsed API JSON response
* @return {array} The messages that the API call returned
*/
find_messages: function(response) {
if (!response || !response.cpanelresult) {
return [{
level: "error",
content: BASE._unknown_error_msg()
}];
}
if ("error" in response.cpanelresult) {
var err = response.cpanelresult.error;
return [{
level: "error",
content: err ? _.escape(err) : BASE._unknown_error_msg()
}];
}
// Some apis (e.g. ZoneEdit::fetchzone) return an error as part of the data in the first data argument
// another api (ZoneEdit::remove_zone_record) returns an error as part of a result object in the data object
// NOTE: This doesn't account for the actual status that is returned, it just grabs whatever message is there.
// We are depending on the callers of the api call to check the status before displaying any messages found.
if (response.cpanelresult.data &&
response.cpanelresult.data[0]) {
var msg;
if (response.cpanelresult.data[0].hasOwnProperty("statusmsg")) {
msg = response.cpanelresult.data[0].statusmsg;
} else if (response.cpanelresult.data[0].hasOwnProperty("result") &&
response.cpanelresult.data[0].result.hasOwnProperty("statusmsg")) {
msg = response.cpanelresult.data[0].result.statusmsg;
}
if (msg) {
return [{
level: "error",
content: msg ? _.escape(msg) : BASE._unknown_error_msg()
}];
}
}
return [];
},
/**
* Indicates whether this module’s find_messages() function
* HTML-escapes.
*/
HTML_ESCAPES_MESSAGES: true,
/**
* Return what a cPanel API 2 call says about whether it succeeded or not
*
* @method find_status
* @param {object} response The parsed API JSON response
* @return {boolean} Whether the API call says it succeeded
*/
find_status: function(response) {
try {
var status = false;
if (response && response.cpanelresult) {
var result = response.cpanelresult;
if (result.event && result.event.result) {
// We prefer to use this method, but some older api's may not correctly return this yet
// or under certain exceptional conditions, this may not be correctly filled.
status = (response.cpanelresult.event.result === 1);
if (status && result.error) {
// Some apis are really bad and return success and an error which should be an error.
status = false;
}
// Some apis (e.g. ZoneEdit::fetchzone) return an error as part of the data in the first data argument
// another api (ZoneEdit::remove_zone_record) returns an error as part of a result object in the data object
if (result.data &&
result.data[0]) {
if (result.data[0].hasOwnProperty("status")) {
status = result.data[0].status;
} else if (result.data[0].hasOwnProperty("result") &&
result.data[0].result.hasOwnProperty("status")) {
status = result.data[0].result.status;
}
}
} else {
// In the case of apis that don't quite comply with the API2 layout or ones that
// can fail without returning the event container above, we have this as a fallback
// to detecting an error.
status = !result.error;
}
}
return status;
} catch (e) {
return false;
}
},
/**
* Return normalized data from a cPanel API 2 call
*
* @method get_data
* @param {object} response The parsed API JSON response
* @return {array} The data that the API returned
*/
get_data: function(response) {
return response.cpanelresult.data;
},
/**
* Return normalized data from a cPanel API 2 call
*
* @method get_meta
* @param {object} response The parsed API JSON response
* @return {array} The data that the API returned
*/
get_meta: function(response) {
var meta = {
paginate: {
is_paged: false,
total_records: 0,
current_record: 0,
total_pages: 0,
current_page: 0,
page_size: 0
},
filter: {
is_filtered: false,
records_before_filter: NaN,
records_filtered: NaN
}
};
if (TEST.objectHasPath(response, "cpanelresult.paginate")) {
var paginate = meta.paginate;
paginate.is_paged = true;
paginate.total_records = response.cpanelresult.paginate.total_results || paginate.total_results || 0;
paginate.current_record = response.cpanelresult.paginate.start_result || paginate.start_result || 0;
paginate.total_pages = response.cpanelresult.paginate.total_pages || paginate.total_pages || 0;
paginate.current_page = response.cpanelresult.paginate.current_page || paginate.current_page || 0;
paginate.page_size = response.cpanelresult.paginate.results_per_page || paginate.results_per_page || 0;
}
/**
* @todo API2 returns the filter data as part of the main object, not inside of a "filter" key.
* It's ok though, since we catch that field in the loop over the "custom" metadata properties.
* So this code is not used from what I can tell.
*/
if (TEST.objectHasPath(response, "cpanelresult.filter")) {
meta.filter.is_filtered = true;
meta.filter.records_before_filter = response.cpanelresult.records_before_filter || 0;
}
// Copy any custom meta data properties.
if (TEST.objectHasPath(response, "cpanelresult")) {
for (var key in response.cpanelresult) {
if (response.cpanelresult.hasOwnProperty(key) &&
( key !== "filter" && key !== "paginate")) {
meta[key] = response.cpanelresult[key];
}
}
}
return meta;
},
/**
* Build the call structure from the arguments and data.
*
* @method build_query
* @param {Object} args_obj Arguments passed to the call.
* @return {Object} Object representation of the call arguments
*/
build_query: function(args_obj) {
// Utility variables, used in specific contexts below.
var s, cur_sort, f, cur_filter;
var api_prefix = "api2_";
var api_call = {
cpanel_jsonapi_apiversion: API_VERSION,
cpanel_jsonapi_module: args_obj.module,
cpanel_jsonapi_func: args_obj.func
};
if (args_obj.args) {
_.extend(api_call, args_obj.args);
}
if (args_obj.meta) {
if (args_obj.meta.sort) {
var sort_count = args_obj.meta.sort.length;
if (sort_count) {
api_call.api2_sort = 1;
}
if (sort_count === 1) {
cur_sort = args_obj.meta.sort[0];
if (cur_sort instanceof Array) {
api_call[api_prefix + "sort_method"] = cur_sort[1];
cur_sort = cur_sort[0];
}
if (cur_sort.charAt(0) === "!") {
api_call[api_prefix + "sort_reverse"] = 1;
cur_sort = cur_sort.substr(1);
}
api_call[api_prefix + "sort_column"] = cur_sort;
} else {
for (s = 0; s < sort_count; s++) {
cur_sort = args_obj.meta.sort[s];
if (cur_sort instanceof Array) {
api_call[api_prefix + "sort_method_" + s] = cur_sort[1];
cur_sort = cur_sort[0];
}
if (cur_sort.charAt(0) === "!") {
api_call[api_prefix + "sort_reverse_" + s] = 1;
cur_sort = cur_sort.substr(1);
}
api_call[api_prefix + "sort_column_" + s] = cur_sort;
}
}
}
if (args_obj.meta.filter) {
var filter_count = args_obj.meta.filter.length;
if (filter_count) {
api_call.api2_filter = 1;
}
if (filter_count === 1) {
cur_filter = args_obj.meta.filter[0];
api_call[api_prefix + "filter_column"] = cur_filter[0];
api_call[api_prefix + "filter_type"] = cur_filter[1];
api_call[api_prefix + "filter_term"] = cur_filter[2];
} else {
for (f = 0; f < filter_count; f++) {
cur_filter = args_obj.meta.filter[f];
api_call[api_prefix + "filter_column_" + f] = cur_filter[0];
api_call[api_prefix + "filter_type_" + f] = cur_filter[1];
api_call[api_prefix + "filter_term_" + f] = cur_filter[2];
}
}
}
if (args_obj.meta.paginate) {
api_call.api2_paginate = 1;
if ("start" in args_obj.meta.paginate) {
api_call[api_prefix + "paginate_start"] = args_obj.meta.paginate.start;
}
if ("size" in args_obj.meta.paginate) {
api_call[api_prefix + "paginate_size"] = args_obj.meta.paginate.size;
}
}
delete args_obj.meta;
}
return api_call;
},
/**
* Assemble the url for the request.
*
* @method get_url
* @param {String} token cPanel Security Token
* @param {Object} args_obj Arguments passed to the call.
* @return {String} Url prefix for the call
*/
get_url: function(token) {
return token + "/json-api/cpanel";
}
};
return api2;
});
/*
# cjt/io/batch-request.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* ----------------------------------------------------------------------
* EXAMPLE USAGE:
*
* // APIREQUEST is, e.g., io/uapi-request or io/whm-v1-request
* var call1 = new APIREQUEST.Class().initialize( .. );
* var call2 = new APIREQUEST.Class().initialize( .. );
*
* var batch = new BATCH.Class( [call1, call2] );
*
* // APICatcher is just used as an example here. Anything that would
* // accept “call1” or “call2” individually can also take “batch”.
* APICatcher.promise(batch).then( (resp) => {
*
* // resp.data is an array that contains the individual
* // API calls’ response objects, so, e.g.:
*
* if (!resp.data[0].parsedResponse.status) {
* throw "nonono";
* }
*
* // .. and so on ..
* } );
*
* ----------------------------------------------------------------------
*
* This library ties into io/base’s batch-handling logic to give API
* callers a seamless experience of batching API calls. This module handles
* all of the formatting aspects of batched API calls automatically
* so you can just write your API calls, and it “just works”. :)
*
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
// Expand this later as necessary to include metadata.
define('cjt/io/batch-request',["lodash"], function(_) {
"use strict";
var BatchRequest = function( calls ) {
calls = calls || [];
this._runargs = [];
// PhantomJS doesn’t have Function.prototype.bind still,
// so use lodash’s wrapper. :-/
calls.forEach( _.bind(this.add, this) );
};
_.extend(
BatchRequest.prototype,
{
add: function(apiCallObj) {
var run = apiCallObj.getRunArguments();
if (this._last_version) {
if (this._last_version !== run.version) {
throw ( "Version mismatch! " + this._last_version + " vs. " + run.version );
}
} else {
this._last_version = run.version;
}
this._runargs.push(run);
return this;
},
getRunArguments: function() {
if (!this._runargs.length) {
throw "Empty batch!";
}
return {
version: this._last_version,
batch: this._runargs
};
}
}
);
return {
MODULE_NAME: "cjt/io/batch-request",
MODULE_DESC: "API-agnostic wrapper for batch API requests.",
MODULE_VERSION: "1.0",
Class: BatchRequest
};
});
/*
# cjt/util/promise.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* ----------------------------------------------------------------------
* promise.js - Promise creator abstraction
*
* This abstracts over native Promise so that using IE11 isn’t as awkward.
* ----------------------------------------------------------------------
*
* EXAMPLE USAGE:
*
* // Just like native Promise …
* var promise = PROMISE.create( function(resolve, reject) { ... } );
*
* ----------------------------------------------------------------------
*/
// NB: The fact that this uses jQuery is an implementation detail.
// It could just as easily use a polyfill or some other solution.
define('cjt/util/promise',["jquery"], function(jQ) {
"use strict";
var _module;
function create( promiseCallback ) {
var promise;
if (_module._Promise) {
promise = new _module._Promise(promiseCallback);
} else {
var deferred = jQ.Deferred();
var res = function(obj) {
deferred.resolveWith(window, [obj]);
};
var rej = function(obj) {
deferred.rejectWith(window, [obj]);
};
promiseCallback(res, rej);
promise = deferred.promise();
}
return promise;
}
_module = {
MODULE_NAME: "cjt/util/promise",
MODULE_DESC: "Native Promise wrapper",
MODULE_VERSION: "1.0",
create: create,
// for testing
_Promise: window.Promise,
};
return _module;
});
/*
# cjt/util/getScript.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* ----------------------------------------------------------------------
* EXAMPLE USAGE:
*
* // same args as native EventSource constructor
* GETSCRIPT.getScript("/url/to/script", opts).then(
* (xhr) => ...
* ).catch( (err) => _handleFailure(err) );
*
* ----------------------------------------------------------------------
*
* NOTE: This runs the loaded JS in ES5 Strict Mode.
*
*
* This library improves upon jQuery’s getScript() in a couple ways:
*
* 1) It’s pure JavaScript rather than jQuery’s approach of using DOM.
*
* 2) It reports network, HTTP, and parse errors via promise rejection.
*
*
* require() can kind of do the work for this, but its error reporting
* is inconsistent, and it doesn’t return a promise.
*
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
// jQuery for Promises
define('cjt/util/getScript',["cjt/util/promise"], function(PROMISE) {
"use strict";
var _module;
/**
* @function getScript
*
* @param url {string} The URL of the JavaScript to load.
*
* @param opts {Object} Optional:
*
* - context: The context in which to run the loaded JavaScript.
* Default is the window object.
*/
function getScript(url, opts) {
var ctx = opts && opts.context || window;
return PROMISE.create( function(res, rej) {
var xhr = new _module._XMLHttpRequest();
xhr.addEventListener("load", function(e) {
if (this.status === 200) {
var js = "'use strict'; " + this.responseText;
try {
Function(js).bind(ctx)();
res(e);
} catch (err) {
rej( new Error("Parse error (" + url + "): " + err) );
}
} else {
rej( new Error("HTTP error (" + url + "): " + this.statusText) );
}
} );
xhr.addEventListener("error", function(e) {
rej( new Error("Network error (" + url + "): " + e));
} );
xhr.open("GET", url);
xhr.send();
} );
}
_module = {
MODULE_NAME: "cjt/util/getScript",
MODULE_DESC: "Improved version of jQuery getScript",
MODULE_VERSION: "1.0",
getScript: getScript,
// for testing
_XMLHttpRequest: window.XMLHttpRequest,
};
return _module;
});
/*
# cjt/io/eventsource.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* ----------------------------------------------------------------------
* eventsource.js - Quick and easy EventSource!
* ----------------------------------------------------------------------
*
* EXAMPLE USAGE:
*
* // same args as native EventSource constructor
* EVENTSOURCE.create( "/url/to/sse/stream", config ).then(
* (evt) => { ... }, // evt.target is your EventSource instance
* (errObj) => { ... }
* } );
*
* ----------------------------------------------------------------------
*
* This library solves two problems:
*
* 1) Microsoft browsers (including Edge) lack native EventSource support.
* cf. https://developer.microsoft.com/en-us/microsoft-edge/platform/status/serversenteventseventsource/
*
* 2) Turn EventSource’s indication of initial connection success/failure
* into a nice, friendly promise.
*
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
define( 'cjt/io/eventsource',[
"cjt/util/getScript",
"cjt/util/promise",
], function(GETSCRIPT, PROMISE) {
"use strict";
var _ES;
function _returnEvtSrcPromise(url, config) {
return PROMISE.create( function(res, rej) {
var es = new _ES._EventSource(url, config);
function _onError(e) {
_clearListeners();
rej(e);
}
function _onOpen(e) {
_clearListeners();
res(e);
}
function _clearListeners() {
es.removeEventListener("open", _onOpen);
es.removeEventListener("error", _onError);
}
es.addEventListener("open", _onOpen);
es.addEventListener("error", _onError);
} );
}
function _create(url, config) {
if (_ES._EventSource) {
return _returnEvtSrcPromise(url, config);
} else {
var ctx = {};
var polyfill = GETSCRIPT.getScript(
_ES._POLYFILL_URL,
{ context: ctx }
);
return polyfill.then( function() {
_ES._EventSource = ctx.EventSource;
return _returnEvtSrcPromise(url, config);
} );
}
}
_ES = {
MODULE_NAME: "cjt/io/eventsource",
MODULE_DESC: "EventSource wrapper with promise",
MODULE_VERSION: "1.0",
// to facilitate mocking
_EventSource: window.EventSource,
_POLYFILL_URL: "/libraries/eventsource-polyfill/eventsource.js",
create: _create,
};
return _ES;
} );
/*
# cjt/jquery/plugins/rangeSelection.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/jquery/plugins/rangeSelection',[
"jquery"
],
function($) {
/**
* Simple jquery plugin to select a range in a textbox
*
* @method selectRange
* @param {Number} start
* @param {Number} end
* @return {JqueryNodeWrapper}
*/
$.fn.selectRange = function(start, end) {
if (!end) {
end = start;
}
return this.each(function() {
if (this.setSelectionRange) {
this.focus();
this.setSelectionRange(start, end);
} else if (this.createTextRange) {
var range = this.createTextRange();
range.collapse(true);
range.moveEnd("character", end);
range.moveStart("character", start);
range.select();
}
});
};
}
);
/*
* cjt/services/cpanel/sslStatus.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/** @namespace cjt.services.cpanel.sslStatus */
define(
'cjt/services/cpanel/SSLStatus',[
"angular",
"lodash",
"cjt/util/locale",
"cjt/io/uapi-request",
"cjt/services/APICatcher",
"cjt/modules"
],
function(angular, _, LOCALE, APIRequest, APICatcher ) {
"use strict";
var CERT_TYPES = {
EV: "ev",
OV: "ov",
DV: "dv",
SELF_SIGNED: "self-signed",
};
var SSLCertificate = function() {
this.certificateErrors = [];
this.hasErrors = false;
};
SSLCertificate.prototype.getIconClasses = function getIconClasses() {
switch (this.validationType) {
case CERT_TYPES.DV:
case CERT_TYPES.OV:
case CERT_TYPES.EV:
return "fas fa-lock";
case CERT_TYPES.SELF_SIGNED:
default:
return "fas fa-unlock-alt";
}
};
SSLCertificate.prototype.getStatusColorClass = function getStatusColorClass() {
if (this.hasErrors) {
return "text-danger";
}
switch (this.validationType) {
case CERT_TYPES.DV:
case CERT_TYPES.OV:
case CERT_TYPES.EV:
return "text-success";
case CERT_TYPES.SELF_SIGNED:
default:
return "text-danger";
}
};
SSLCertificate.prototype.getTypeName = function getSSLCertTypeName(options) {
var isExpanded = options && options.stripMarkup;
switch (this.validationType) {
case CERT_TYPES.DV:
return isExpanded ? LOCALE.maketext("Domain Validated Certificate") : LOCALE.maketext("[output,abbr,DV,Domain Validated] Certificate");
case CERT_TYPES.EV:
return isExpanded ? LOCALE.maketext("Extended Validation Certificate") : LOCALE.maketext("[output,abbr,EV,Extended Validation] Certificate");
case CERT_TYPES.OV:
return isExpanded ? LOCALE.maketext("Organization Validated Certificate") : LOCALE.maketext("[output,abbr,OV,Organization Validated] Certificate");
case CERT_TYPES.SELF_SIGNED:
return LOCALE.maketext("Self-signed Certificate");
default:
return LOCALE.maketext("No Valid Certificate");
}
};
SSLCertificate.prototype.addError = function addError(errorMessage) {
this.certificateErrors.push(errorMessage);
this.hasErrors = true;
return this.certificateErrors;
};
SSLCertificate.prototype.getErrors = function getErrors(errorMessage) {
return this.certificateErrors;
};
var SSLCertFactory = function() {
this.make = function(certificate) {
var sslCert = new SSLCertificate();
sslCert.isSelfSigned = certificate.is_self_signed && certificate.is_self_signed.toString() === "1";
sslCert.validationType = sslCert.isSelfSigned ? CERT_TYPES.SELF_SIGNED : certificate.validation_type;
return sslCert;
};
};
var _certFactory = new SSLCertFactory();
var MODULE_NAMESPACE = "cjt2.services.cpanel.sslStatus";
var SERVICE_NAME = "sslStatusService";
var MODULE_REQUIREMENTS = [ ];
var SERVICE_INJECTABLES = ["$log", "$q", "APICatcher"];
var SERVICE_FACTORY = function($log, $q, APICatcher) {
/**
* service wrapper for domain related functions
*
* @module sslStatusService
*
* @param {Object} APICatcher cjt2 APICatcher service
*
* @example
* $sslStatusService.getDomainSSLCertificate("foo.com")
*
*/
var Service = function() {};
Service.prototype = Object.create(APICatcher);
_.assign(Service.prototype, {
_domainsChecked: {},
/**
* Wrapper for building an apiCall
*
* @private
*
* @param {String} module module name to call
* @param {String} func api function name to call
* @param {Object} args key value pairs to pass to the api
* @returns {UAPIRequest} returns the api call
*
* @example _apiCall( "Tokens", "rename", { name:"OLDNAME", new_name:"NEWNAME" } )
*/
_apiCall: function _createApiCall(module, func, args) {
var apiCall = new APIRequest.Class();
apiCall.initialize(module, func, args);
return apiCall;
},
_getDomainCertificate: function _getDomainCertificate(domain) {
if (!this._domainSSLMap) {
return;
}
var sslDomains = Object.keys(this._domainSSLMap);
for (var i = 0; i < sslDomains.length; i++) {
var sslDomain = sslDomains[i];
// THe domain is covered by a direct match
if (domain === sslDomain) {
return this._domainSSLMap[sslDomain];
}
// The domain is covered by a wildcard match foo.bar.com matches *.bar.com
var wildcardDomain = domain.replace(/^[^.]+\./, "*.");
if (wildcardDomain === sslDomain) {
return this._domainSSLMap[sslDomain];
}
}
// Return an "unsecured" certificate
var certificate = _certFactory.make({});
certificate.addError(LOCALE.maketext("You have no valid [asis,SSL] certificate configured for this domain."));
return certificate;
},
_parseInstalledHost: function _parseInstalledHost(installedHost, retainInvalidCerts) {
var self = this;
if (!self._domainSSLMap) {
self._domainSSLMap = {};
}
var now = new Date();
now = now.getTime() / 1000;
if (!installedHost.certificate) {
$log.debug("Skipping installed host for lack of a certificate.", installedHost);
return;
}
var certificate = _certFactory.make(installedHost.certificate);
if (installedHost.verify_error) {
$log.debug("Certificate did not pass SSL Verification.", installedHost);
certificate.addError(_.escape(installedHost.verify_error));
}
var notValidAfter = parseInt(installedHost.certificate.not_after, 10);
if (notValidAfter < now) {
$log.debug("The certificate has expired.", installedHost);
certificate.addError(LOCALE.maketext("The certificate has expired."));
}
if (!retainInvalidCerts && certificate.hasErrors) {
$log.debug("Skipping certificate because of errors.");
return;
}
var domains = installedHost.certificate.domains || [];
domains.forEach(function(domain) {
self._domainSSLMap[domain] = certificate;
});
},
/**
* API Wrapper call to fetch installed hosts for the current user
*
* @method getValidSSLCertificates
*
* @param {String} user username to use to check the domain status, this is required, but only used by WHM
*
* @return {Promise<Object>} returns the promise that returns a domain object map of ssl certs
*
*/
getValidSSLCertificates: function getValidSSLCertificates() {
var self = this;
if (self._domainSSLMap) {
return $q.resolve(self._domainSSLMap);
}
var apiCall = self._apiCall("SSL", "installed_hosts");
$log.debug("getValidSSLCertificates");
self._domainSSLMap = {};
return self._promise(apiCall).then(function(result) {
var data = result.data || [];
data.forEach(self._parseInstalledHost.bind(self), self);
return self._domainSSLMap;
});
},
/**
* API Wrapper call to get the SSL Status of a single domain
*
* @method getDomainSSLCertificate
*
* @param {String} user username to use to check the domain status
* @param {String} domain domain name to get the ssl status of
*
* @return {Promise<String>} returns the promise that returns the ssl status string
*
*/
getDomainSSLCertificate: function getDomainSSLCertificate(domain, includeInvalid) {
var self = this;
$log.debug("getDomainSSLCertificate", domain);
if (self._domainsChecked[domain]) {
return self._getDomainCertificate(domain);
}
var apiCall = self._apiCall("SSL", "installed_host", {
domain: domain,
verify_certificate: 1
});
return self._promise(apiCall).then(function(result) {
// Mark it checked
self._domainsChecked[domain] = true;
var data = result.data || {};
self._parseInstalledHost.call(self, data, includeInvalid);
return self._getDomainCertificate(domain);
});
},
/**
* Wrapper for .promise method from APICatcher
*
* @param {Object} apiCall api call to pass to .promise
* @returns {Promise}
*
* @example $service._promise( $service._apiCall( "Tokens", "rename", { name:"OLDNAME", new_name:"NEWNAME" } ) );
*/
_promise: function _promise() {
// Because nested inheritence is annoying
return APICatcher.promise.apply(this, arguments);
}
});
return new Service();
};
var module = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
SERVICE_INJECTABLES.push(SERVICE_FACTORY);
module.factory(SERVICE_NAME, SERVICE_INJECTABLES );
return {
"class": SERVICE_FACTORY,
"serviceName": SERVICE_NAME,
"namespace": MODULE_NAMESPACE,
CERT_TYPES: CERT_TYPES,
_certFactory: _certFactory,
SSLCertificateClass: SSLCertificate
};
}
);
/*
* _assets/services/cpanel/notificationsService.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/services/cpanel/notificationsService',[
// Libraries
"angular",
// CJT
"cjt/io/api",
"cjt/io/uapi-request",
"cjt/io/uapi" // IMPORTANT: Load the driver so its ready
],
function(angular, API, APIREQUEST, APIDRIVER) {
"use strict";
var module = angular.module("cjt2.services.cpanel.notifications", []);
/**
* Setup the notifications count API service
*/
module.factory("notificationsService", ["$q", function($q) {
/**
* Get number of notifications.
*
* @method getCount
* @async
* @return {Promise.<Number>} - Promise resolves to the number of notifications.
* @throws {Promise.<String>} - If the api call returns a failed status, the error is returned in the rejected promise.
*/
function getCount() {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Notifications", "get_notifications_count");
var deferred = $q.defer();
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response.data);
} else {
deferred.reject(response.error);
}
});
// pass the promise back to the controller
return deferred.promise;
}
// return the factory interface
return {
getCount: getCount
};
}]);
}
);
/*
# cjt/modules.cpanel.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* Provides the dependencies for cPanel's CJT2 Angular module.
*
* @module cjt/module.cpanel
*/
define(
'cjt/modules.cpanel',[
"cjt/config/cpanel/configProvider",
"cjt/directives/cpanel/searchSettingsPanel",
"cjt/services/cpanel/componentSettingSaverService",
"cjt/services/cpanel/SSLStatus",
"cjt/services/cpanel/notificationsService",
"cjt/services/cpanel/nvDataService"
],
function() {
"use strict";
// Return the Angular modules provided by the dependency files
return [
"cjt2.config.cpanel.configProvider",
"cjt2.directives.cpanel.searchSettingsPanel",
"cjt2.services.cpanel.componentSettingSaverService",
"cjt2.services.cpanel.sslStatus",
"cjt2.services.cpanel.notifications",
"cjt2.services.cpanel.nvdata"
];
}
);
/* global define: false */
(function() {
"use strict";
// Constants
var DEFAULT_REPLACE_PATTERN = /%/;
var DEFAULT_LOCALE_EXTENSION = "-%";
var DEFAULT_TEST_FN = function(name) {
return true;
};
/**
* Test if what is passed is a function.
* @method isFunction
* @param {Object} fn
* @return {Boolean} true if fn is a function, false otherwise.
*/
function isFunction(fn) {
var getType = {};
return fn && getType.toString.call(fn) === "[object Function]";
}
/**
* Convert the configuration into a function depending on
* how it is passed.
*
* @method setupIsLocalizableFunction
* @param {String|Regexp|Function} isLocalizableCfg
* @return {Function}
*/
function setupIsLocalizableFunction(isLocalizableCfg) {
var isLocalizableFn;
if (typeof isLocalizableCfg === "string") {
var pattern = isLocalizableCfg;
var regexp = new RegExp(pattern);
isLocalizableFn = function(name) {
return regexp.test(name);
};
} else if (isLocalizableCfg instanceof RegExp) {
isLocalizableFn = function(name) {
return isLocalizableCfg.test(name);
};
} else if (isFunction(isLocalizableCfg)) {
isLocalizableFn = isLocalizableCfg;
}
if (!isLocalizableFn) {
isLocalizableFn = DEFAULT_TEST_FN;
}
return isLocalizableFn;
}
/**
* Initialize the replacement pattern.
*
* @method setupReplacePattern
* @param {String|Regexp} replace Candidate replacement pattern.
* @return {Regexp} Expression
*/
function setupReplacePattern(replace) {
var regexReplace;
if (typeof replace === "string") {
regexReplace = new RegExp(replace);
} else if (replace instanceof RegExp) {
regexReplace = replace;
}
if (!regexReplace) {
regexReplace = DEFAULT_REPLACE_PATTERN;
}
return regexReplace;
}
/**
* Initialize the configuration
*
* @method initConfig
* @param {Object} config
*/
function initConfig(config) {
if (config.locale) {
if (!config.locale.isInitialized) {
// We only want to do this once
if (config.locale.isLocalizable) {
// Cache it so we don't have to do this for each call
config.locale.isLocalizable = setupIsLocalizableFunction(config.locale.isLocalizable);
} else {
config.locale.isLocalizable = DEFAULT_TEST_FN;
}
if (!config.locale.replace) {
config.locale.replace = DEFAULT_REPLACE_PATTERN;
} else {
config.locale.replace = setupReplacePattern(config.locale.replace);
}
if (!config.locale.extension) {
config.locale.extension = DEFAULT_LOCALE_EXTENSION;
}
config.locale.isInitialized = true;
}
} else {
config.locale = {
disabled: true,
isInitialized: true
};
}
}
/**
* Checks if the config has a current locale set
*
* @method hasCurrentLocale
* @param {Object} config
* @return {Boolean}
*/
function hasCurrentLocale(config) {
return config &&
config.locale &&
typeof (config.locale.currentLocale) !== "undefined" &&
config.locale.currentLocale !== "";
}
define('cjt/plugins/locale',{
version: "2.1.0",
name: "cPanel locale requirejs plugin",
description: "The locale requirejs plugin loads files and their related locale file.",
/**
* Load the localized resource if a locale is setup.
*
* @method load
* @param {String} name Module to load.
* @param {Function} req Require function instance.
* @param {Function} onload Load callback.
* @param {Object} config Current requirejs configuration
*/
load: function(name, req, onload, config) {
// Handle configure normalization first.
config = config || {};
initConfig(config);
if (!config.locale.disabled &&
hasCurrentLocale(config) &&
config.locale.isLocalizable(name)) {
// Remove any query args from the url, before appending
// the locale tag.
//
// Load the requested file and the lexicon for the requested file by
// appending ?locale= to the js file
var url = req.toUrl(name) || "";
var queryArgs = url.split("?");
var dest = queryArgs[0] +
(config.locale.addMin && queryArgs[0].indexOf(".min") === -1 ? ".min" : "") +
".js?locale=" + config.locale.currentLocale +
"&locale_revision=" + config.locale.revision;
req([dest], function(module, lexicon) {
// We are passing the lexicon to onload mainly for unit tests
// Most lexicon systems should just auto register themselves with
// some framework. The module must be first so that clients that
// need the return value will still get it in the position they expect.
onload(module, lexicon);
});
} else {
// Load the requested file, probably debug mode
// for development.
req([name], function(value) {
onload(value);
});
}
}
});
})();
/*
* cjt/services/fuzzy.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define */
/** @namespace cpanel.cjt.services.Fuzzy */
define(
'cjt/services/fuzzy',[],
function() {
"use strict";
/**
* Fuzzy search engine service based on the https://en.wikipedia.org/wiki/Levenshtein_distance
*
* @module Fuzzy
*
* @param {Array} set [optional] set of searchables
*
* @example
* var fuzzy = new Fuzzy();
* fuzzy.loadSet(["apple", "orange", "banana"]);
* var result = fuzzy.search("orage");
*
*/
var Fuzzy = function(set) {
this._storedSet = set ? set : [];
this._cache = {};
this._cacheBySet = {};
/**
* Sort two objects by the distance value
*
* @method _distanceSort
* @private
*
* @param {Object} a object with a distance
* @param {Object} b object with a distance
*
* @return {Number} returns sort number from comparison
*
*/
this._distanceSort = function(a, b) {
if (a.distance === b.distance) {
if (a.lengthDiff === b.lengthDiff) {
return 0;
}
return a.lengthDiff < b.lengthDiff ? -1 : 1;
}
return a.distance < b.distance ? -1 : 1;
};
/**
* Cache some value in a nested object
*
* @method _setCache
* @private
*
* @param {Object} cache object to store on
* @param {String} itemA top level key to store on
* @param {String} itemB second level key to store on
* @param {*} value value to be stored in the cache
*
* @return {Object} return the stored value
*
*/
this._setCache = function _setCache(cache, itemA, itemB, value) {
cache[itemA] = cache[itemA] ? cache[itemA] : {};
return cache[itemA][itemB] = value;
};
/**
* Return the stored value from the cache
*
* @method _getCache
* @private
*
* @param {Array} cache cache to return from
* @param {String} itemA top level key to store on
* @param {String} itemB second level key to store on
*
* @return {*} return the stored value
*
*/
this._getCache = function _getCache(cache, itemA, itemB) {
if (cache[itemA] && cache[itemA][itemB]) {
return cache[itemA][itemB];
}
return;
};
/**
* Deep logic to compare two strings
*
* @method _searchStrings
* @private
*
* @param {String} haystack string to test pattern against
* @param {String} needle pattern to check
*
* @return {Object} comparison result object
*
*/
this._searchStrings = function _searchStrings(haystack, needle) {
if (haystack === needle) {
return this._setCache(this._cache, haystack, needle, {
distance: 0,
substring: haystack,
pattern: needle,
match: haystack
});
}
var needleLength = needle.length;
var haystackLength = haystack.length;
var a = [], // current row
b = [], // previous row
pa = [], // from
pb = [],
s, i, j;
for (i = 0; i <= needleLength; i++) {
s = b;
b = a;
a = s;
s = pb;
pb = pa;
pa = s;
for (j = 0; j <= haystackLength; j++) {
if (i && j) {
a[j] = a[j - 1] + 1;
pa[j] = pa[j - 1];
s = b[j - 1] + (haystack[j - 1] === needle[i - 1] ? 0 : 1);
if (a[j] > s) {
a[j] = s;
pa[j] = pb[j - 1];
}
if (a[j] > b[j] + 1) {
a[j] = b[j] + 1;
pa[j] = pb[j];
}
} else {
a[j] = i;
pa[j] = j;
}
}
}
s = 0;
for (j = a.length - 1; j >= 1; j--) {
if (a[j] < a[s]) {
s = j;
}
}
var subMatch = haystack.slice(pa[s], s);
return this._setCache(this._cache, haystack, needle, {
distance: a[s] + 1,
substring: subMatch,
lengthDiff: Math.abs(needleLength - haystackLength),
pattern: needle,
match: haystack
});
};
/**
* Search for a string within a set
*
* @method search
*
* @param {String} item item to search for in a set
* @param {Array} set [optional] set of items to search
*
* @return {Array} array of matches ranked in ascending distance
*
*/
this.search = function _search(item, set) {
if (set) {
this.loadSet(set);
} else {
set = this.getSet();
}
var sString = set.join("");
if (this._getCache(this._cacheBySet, sString, item)) {
return this._getCache(this._cacheBySet, sString, item);
}
var result = [];
set.forEach(function(setItem, index) {
result[index] = this._searchStrings(setItem, item);
}, this);
result = result.sort(this._distanceSort);
return this._setCache(this._cacheBySet, sString, item, result);
};
/**
* Get the currently stored set
*
* @method getSet
*
* param jsdocparam maybe?
*
* @return {Array} returns the current set of searchables
*
*/
this.getSet = function _getSet() {
return this._storedSet;
};
/**
* Load a new set of searchables
*
* @method loadSet
*
* @param {Array} set new set of searchables to store
*
*/
this.loadSet = function _loadSet(set) {
this._storedSet = set;
};
};
return Fuzzy;
}
);
/*
* cjt/startup.js Copyright 2022 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* DEVELOPERS NOTES:
* This is common application startup code. Use it within *.dist.js files.
*/
/* global define: false, require: false */
define('cjt/startup',[],function() {
/**
* Expands a list of dependencies. If one is not provided, then a default
* list is generated from the passed in second argument.
*
* @method _expandDependencies description
* @private
* @param {String|Array} dependencies
* @return {Array} List of dependencies to load.
*/
function _expandDependencies(dependencies) {
if (Array.isArray(dependencies)) {
return dependencies;
} else if (typeof dependencies === "string") {
return [
dependencies
];
}
throw "You must pass either an array of dependencies or a single string dependency";
}
return {
/**
* Start up the application with the requested dependencies.
*
* @method startApplication
* @param {Array|String} dependencies List of dependencies.
* If a string is passed, it is converted into an array with that one item.
* The parameter defaults to:
*
* [ "app/index" ]
* @return {Object} reference to this so these can be chained
*/
startApplication: function startApplication(dependencies) {
dependencies = _expandDependencies(dependencies || "app/index");
require(
dependencies,
function(APP) {
if (APP) {
APP();
}
}
);
return this;
},
/**
* Start up the master application with the requested
* dependencies.
*
* @method startMaster
* @param {Array|String} dependencies List of dependencies.
* If a string is passed, it is converted into an array with that one item.
* The parameter defaults to:
*
* [ "master/master" ]
* @return {Object} reference to this so these can be chained
*/
startMaster: function startMaster(dependencies) {
dependencies = _expandDependencies(dependencies || "master/master");
require(
dependencies,
function(MASTER) {
if (MASTER) {
MASTER();
}
});
return this;
},
/**
* Start up the master application with the requested
* dependencies after a short delay.
*
* @method deferStartMaster
* @param {Array|String} dependencies List of dependencies.
* If a string is passed, it is converted into an array with that one item.
* The parameter defaults to:
*
* [ "master/master" ]
* @return {Object} reference to this so these can be chained
*/
deferStartMaster: function deferStartMaster(dependencies) {
// Defer this since the primary task here is this page,
// so we can wait a sec for the search tool to start working...
var self = this;
setTimeout(function() {
self.startMaster(dependencies);
});
return this;
}
};
});
/*
# cjt/util/base64.js Copyright(c) 2021 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/util/base64',[],function() {
"use strict";
return {
/**
* @function decode
*
* A UTF-8-aware base64 decoder: it decodes base64 octets
* to a JS string (each code point of which will be 0-255)
* then decodes that string’s code points as UTF-8.
*
* This is nearly always preferable to the browser’s built-in
* atob(), which only decodes the base64 octets without
* the additional UTF-8 decoding step.
*
* @param b64 {string} The base64 text to decode.
*
* @return {string} The decoded text.
*/
decodeUTF8: function(b64) {
// This is slow, but it works. If more speed is necessary,
// consider feeding the base64 to fetch() to get an array
// buffer. Then use TextDecode on that buffer. (This will
// be asynchronous, so it won’t work here.)
//
return decodeURIComponent(escape(atob(b64)));
},
};
}
);
/*
# cjt/util/class.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
/* jshint -W098 */
define('cjt/util/class',[ ],
function() {
"use strict";
return {
/**
* Create a named constructor for a subclass.
* The “myConstructor” logic gets executed after the parent’s
* constructor logic in the subclass.
*
* @static
* @method subclass
* @parentConstructor {Function} The parent class’s constructor
* @name {String} The new constructor’s name,
* which will show up in stack
* traces and thus aid debugging.
* @myConstructor {Function} (optional) Constructor logic.
*
* @return {Function} The new constructor. This will never be the
* same function as “myConstructor”, though
* it will call “myConstructor”.
*/
subclass: function subclass(parentConstructor, name, myConstructor) {
if (!name) {
console.log(arguments);
throw "I need a name!";
}
var instantiate = function() {
parentConstructor.apply(this, arguments);
if (myConstructor) {
myConstructor.apply(this, arguments);
}
};
// Do it this way to get a “named” function; this helps
// with reading stack traces and the like.
// cf. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
//
/* jshint -W061 */
var newClass = eval("[function %() { instantiate.apply(this, arguments) }][0]".replace(/%/, name));
/* jshint +W061 */
newClass.prototype = Object.create(parentConstructor.prototype);
return newClass;
},
};
}
);
/*
# cjt/utils/dumper.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W089 */
/* global define: false */
define('cjt/util/dumper',[],function() {
return {
dump: function dump(object, options) {
options = options || {};
options.tabs = options.tabs || 0;
options.tabCharacter = options.tabCharacter || "\t";
options.nlCharacter = options.nlCharacter || "\n";
var result = "";
for (var propertyName in object) {
var propertyValue = object[propertyName];
if (typeof propertyValue === "string") {
propertyValue = "'" + propertyValue + "'";
} else if (typeof propertyValue === "function") {
propertyValue = "function(){ ... }";
} else if (typeof propertyValue === "object") {
if (propertyValue instanceof Array) {
propertyValue = "[" + options.nlCharacter;
for (var i = 0, l = propertyValue.length; i < l; i++) {
propertyValue += dump(propertyValue[i], { tabs: options.tabs + 1 });
}
propertyValue += [options.tabs].join(options.tabCharacter) + "]" + options.nlCharacter;
} else {
propertyValue = "{" + options.nlCharacter;
propertyValue += dump(propertyValue, { tabs: options.tabs + 1 });
propertyValue += [options.tabs].join(options.tabCharacter) + "}" + options.nlCharacter;
}
}
result += [options.tabs].join(options.tabCharacter);
result += "'" + propertyName + "' : " + propertyValue + ",";
result += options.nlCharacter;
}
return result;
}
};
});
/*
# cjt/util/flatObject.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
*
* @module cjt/util/flatObject
* @example
* var deep = {
* a: 1,
* b: {
* c: 1
* d: {
* e: 1
* }
* }
* };
* var flat = FLAT.flatten(deep);
*
* Now flat will look like:
*
* {
* a: 1,
* b.c: 1,
* b.d.e: 1
* }
*/
define('cjt/util/flatObject',[],function() {
"use strict";
/**
* Convert a deep nested object into a single layer object with the properties named
* with the full deep names with period separators.
* @param {Object} inputObject Deep object
* @return {Object} Flattened object
*/
function flatten(inputObject) {
var outputObject = {};
for (var prop in inputObject) {
if (inputObject.hasOwnProperty(prop)) {
if ((typeof inputObject[prop]) === "object") {
var flatObject = flatten(inputObject[prop]);
for (var innerProp in flatObject) {
if (flatObject.hasOwnProperty(innerProp)) {
outputObject[prop + "." + innerProp] = flatObject[innerProp];
}
}
} else {
outputObject[prop] = inputObject[prop];
}
}
}
return outputObject;
}
return {
flatten: flatten
};
});
/*
# cjt/util/unicode.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define( 'cjt/util/unicode',[
"punycode",
],
function(PUNYCODE) {
"use strict";
var ucs2Encode = PUNYCODE.ucs2.encode;
function _augmentLookup(cpArray, lookup, value, asCharYN) {
for (var d = 0; d < cpArray.length; d++) {
if (cpArray[d] instanceof Array) {
for (var i = cpArray[d][0]; i <= cpArray[d][1]; i++) {
lookup[ asCharYN ? ucs2Encode([i]) : i ] = value;
}
} else {
lookup[ asCharYN ? ucs2Encode( [cpArray[d]] ) : cpArray[d] ] = value;
}
}
return lookup;
}
function createCharacterLookup(cpArray) {
return _augmentLookup(cpArray, {}, true, true);
}
function augmentCodePointLookup(cpArray, lookup, value) {
return _augmentLookup(cpArray, lookup, value, false);
}
return {
createCharacterLookup: createCharacterLookup,
augmentCodePointLookup: augmentCodePointLookup,
};
} );
/*
# cjt/util/idnDisallowed.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* ----------------------------------------------------------------------
* idnDisallowed.js - disallowed IDN characters
*
* This provides a part of the IDN validation algorithm. It’s not the
* whole thing—a full IDN validation requires knowledge of Unicode character
* classes and other such—but it at least gets us part of the way there.
*
* The list of characters here comes from RFC 5892 and RFC 6452.
*
* Note also that, because of JS limitations, this only examines characters
* in the Basic Multilingual Plane (U+0000 - U+ffff). (It may be possible to
* work around this?)
* ----------------------------------------------------------------------
*
* EXAMPLE USAGE:
*
* disallowedCharsArray = idnDisallowed.getDisallowedInLabel( labelString )
*
* ----------------------------------------------------------------------
*/
define('cjt/util/idnDisallowed',[
"lodash",
"punycode",
"cjt/util/unicode",
], function(_, PUNYCODE, UNICODE) {
"use strict";
// A mutation of the table at:
// https://tools.ietf.org/html/rfc5892#appendix-B.1
var DISALLOWED = [
[0x0, 0x2c],
[0x2e, 0x2f],
[0x3a, 0x60],
[0x7b, 0xb6],
[0xb8, 0xde],
0xf7,
0x100,
0x102,
0x104,
0x106,
0x108,
0x10a,
0x10c,
0x10e,
0x110,
0x112,
0x114,
0x116,
0x118,
0x11a,
0x11c,
0x11e,
0x120,
0x122,
0x124,
0x126,
0x128,
0x12a,
0x12c,
0x12e,
0x130,
[0x132, 0x134],
0x136,
0x139,
0x13b,
0x13d,
[0x13f, 0x141],
0x143,
0x145,
0x147,
[0x149, 0x14a],
0x14c,
0x14e,
0x150,
0x152,
0x154,
0x156,
0x158,
0x15a,
0x15c,
0x15e,
0x160,
0x162,
0x164,
0x166,
0x168,
0x16a,
0x16c,
0x16e,
0x170,
0x172,
0x174,
0x176,
[0x178, 0x179],
0x17b,
0x17d,
0x17f,
[0x181, 0x182],
0x184,
[0x186, 0x187],
[0x189, 0x18b],
[0x18e, 0x191],
[0x193, 0x194],
[0x196, 0x198],
[0x19c, 0x19d],
[0x19f, 0x1a0],
0x1a2,
0x1a4,
[0x1a6, 0x1a7],
0x1a9,
0x1ac,
[0x1ae, 0x1af],
[0x1b1, 0x1b3],
0x1b5,
[0x1b7, 0x1b8],
0x1bc,
[0x1c4, 0x1cd],
0x1cf,
0x1d1,
0x1d3,
0x1d5,
0x1d7,
0x1d9,
0x1db,
0x1de,
0x1e0,
0x1e2,
0x1e4,
0x1e6,
0x1e8,
0x1ea,
0x1ec,
0x1ee,
[0x1f1, 0x1f4],
[0x1f6, 0x1f8],
0x1fa,
0x1fc,
0x1fe,
0x200,
0x202,
0x204,
0x206,
0x208,
0x20a,
0x20c,
0x20e,
0x210,
0x212,
0x214,
0x216,
0x218,
0x21a,
0x21c,
0x21e,
0x220,
0x222,
0x224,
0x226,
0x228,
0x22a,
0x22c,
0x22e,
0x230,
0x232,
[0x23a, 0x23b],
[0x23d, 0x23e],
0x241,
[0x243, 0x246],
0x248,
0x24a,
0x24c,
0x24e,
[0x2b0, 0x2b8],
[0x2c2, 0x2c5],
[0x2d2, 0x2eb],
0x2ed,
[0x2ef, 0x2ff],
[0x340, 0x341],
[0x343, 0x345],
0x34f,
0x370,
0x372,
0x374,
0x376,
0x37a,
0x37e,
[0x384, 0x38a],
0x38c,
[0x38e, 0x38f],
[0x391, 0x3a1],
[0x3a3, 0x3ab],
[0x3cf, 0x3d6],
0x3d8,
0x3da,
0x3dc,
0x3de,
0x3e0,
0x3e2,
0x3e4,
0x3e6,
0x3e8,
0x3ea,
0x3ec,
0x3ee,
[0x3f0, 0x3f2],
[0x3f4, 0x3f7],
[0x3f9, 0x3fa],
[0x3fd, 0x42f],
0x460,
0x462,
0x464,
0x466,
0x468,
0x46a,
0x46c,
0x46e,
0x470,
0x472,
0x474,
0x476,
0x478,
0x47a,
0x47c,
0x47e,
0x480,
0x482,
[0x488, 0x48a],
0x48c,
0x48e,
0x490,
0x492,
0x494,
0x496,
0x498,
0x49a,
0x49c,
0x49e,
0x4a0,
0x4a2,
0x4a4,
0x4a6,
0x4a8,
0x4aa,
0x4ac,
0x4ae,
0x4b0,
0x4b2,
0x4b4,
0x4b6,
0x4b8,
0x4ba,
0x4bc,
0x4be,
[0x4c0, 0x4c1],
0x4c3,
0x4c5,
0x4c7,
0x4c9,
0x4cb,
0x4cd,
0x4d0,
0x4d2,
0x4d4,
0x4d6,
0x4d8,
0x4da,
0x4dc,
0x4de,
0x4e0,
0x4e2,
0x4e4,
0x4e6,
0x4e8,
0x4ea,
0x4ec,
0x4ee,
0x4f0,
0x4f2,
0x4f4,
0x4f6,
0x4f8,
0x4fa,
0x4fc,
0x4fe,
0x500,
0x502,
0x504,
0x506,
0x508,
0x50a,
0x50c,
0x50e,
0x510,
0x512,
0x514,
0x516,
0x518,
0x51a,
0x51c,
0x51e,
0x520,
0x522,
0x524,
[0x531, 0x556],
[0x55a, 0x55f],
0x587,
[0x589, 0x58a],
0x5be,
0x5c0,
0x5c3,
0x5c6,
[0x600, 0x603],
[0x606, 0x60f],
0x61b,
[0x61e, 0x61f],
0x640,
[0x66a, 0x66d],
[0x675, 0x678],
0x6d4,
[0x6dd, 0x6de],
0x6e9,
[0x700, 0x70d],
0x70f,
[0x7f6, 0x7fa],
[0x830, 0x83e],
[0x958, 0x95f],
[0x964, 0x965],
0x970,
[0x9dc, 0x9dd],
0x9df,
[0x9f2, 0x9fb],
0xa33,
0xa36,
[0xa59, 0xa5b],
0xa5e,
0xaf1,
[0xb5c, 0xb5d],
0xb70,
[0xbf0, 0xbfa],
[0xc78, 0xc7f],
// [0xcf1, 0xcf2], RFC 6452 allows this now
[0xd70, 0xd75],
0xd79,
0xdf4,
0xe33,
0xe3f,
0xe4f,
[0xe5a, 0xe5b],
0xeb3,
[0xedc, 0xedd],
[0xf01, 0xf0a],
[0xf0c, 0xf17],
[0xf1a, 0xf1f],
[0xf2a, 0xf34],
0xf36,
0xf38,
[0xf3a, 0xf3d],
0xf43,
0xf4d,
0xf52,
0xf57,
0xf5c,
0xf69,
0xf73,
[0xf75, 0xf79],
0xf81,
0xf85,
0xf93,
0xf9d,
0xfa2,
0xfa7,
0xfac,
0xfb9,
[0xfbe, 0xfc5],
[0xfc7, 0xfcc],
[0xfce, 0xfd8],
[0x104a, 0x104f],
[0x109e, 0x10c5],
[0x10fb, 0x10fc],
[0x1100, 0x11ff],
[0x1360, 0x137c],
[0x1390, 0x1399],
0x1400,
[0x166d, 0x166e],
0x1680,
[0x169b, 0x169c],
[0x16eb, 0x16f0],
[0x1735, 0x1736],
[0x17b4, 0x17b5],
[0x17d4, 0x17d6],
[0x17d8, 0x17db],
[0x17f0, 0x17f9],
[0x1800, 0x180e],
0x1940,
[0x1944, 0x1945],
[0x19de, 0x19ff],
[0x1a1e, 0x1a1f],
[0x1aa0, 0x1aa6],
[0x1aa8, 0x1aad],
[0x1b5a, 0x1b6a],
[0x1b74, 0x1b7c],
[0x1c3b, 0x1c3f],
[0x1c7e, 0x1c7f],
0x1cd3,
[0x1d2c, 0x1d2e],
[0x1d30, 0x1d3a],
[0x1d3c, 0x1d4d],
[0x1d4f, 0x1d6a],
0x1d78,
[0x1d9b, 0x1dbf],
0x1e00,
0x1e02,
0x1e04,
0x1e06,
0x1e08,
0x1e0a,
0x1e0c,
0x1e0e,
0x1e10,
0x1e12,
0x1e14,
0x1e16,
0x1e18,
0x1e1a,
0x1e1c,
0x1e1e,
0x1e20,
0x1e22,
0x1e24,
0x1e26,
0x1e28,
0x1e2a,
0x1e2c,
0x1e2e,
0x1e30,
0x1e32,
0x1e34,
0x1e36,
0x1e38,
0x1e3a,
0x1e3c,
0x1e3e,
0x1e40,
0x1e42,
0x1e44,
0x1e46,
0x1e48,
0x1e4a,
0x1e4c,
0x1e4e,
0x1e50,
0x1e52,
0x1e54,
0x1e56,
0x1e58,
0x1e5a,
0x1e5c,
0x1e5e,
0x1e60,
0x1e62,
0x1e64,
0x1e66,
0x1e68,
0x1e6a,
0x1e6c,
0x1e6e,
0x1e70,
0x1e72,
0x1e74,
0x1e76,
0x1e78,
0x1e7a,
0x1e7c,
0x1e7e,
0x1e80,
0x1e82,
0x1e84,
0x1e86,
0x1e88,
0x1e8a,
0x1e8c,
0x1e8e,
0x1e90,
0x1e92,
0x1e94,
[0x1e9a, 0x1e9b],
0x1e9e,
0x1ea0,
0x1ea2,
0x1ea4,
0x1ea6,
0x1ea8,
0x1eaa,
0x1eac,
0x1eae,
0x1eb0,
0x1eb2,
0x1eb4,
0x1eb6,
0x1eb8,
0x1eba,
0x1ebc,
0x1ebe,
0x1ec0,
0x1ec2,
0x1ec4,
0x1ec6,
0x1ec8,
0x1eca,
0x1ecc,
0x1ece,
0x1ed0,
0x1ed2,
0x1ed4,
0x1ed6,
0x1ed8,
0x1eda,
0x1edc,
0x1ede,
0x1ee0,
0x1ee2,
0x1ee4,
0x1ee6,
0x1ee8,
0x1eea,
0x1eec,
0x1eee,
0x1ef0,
0x1ef2,
0x1ef4,
0x1ef6,
0x1ef8,
0x1efa,
0x1efc,
0x1efe,
[0x1f08, 0x1f0f],
[0x1f18, 0x1f1d],
[0x1f28, 0x1f2f],
[0x1f38, 0x1f3f],
[0x1f48, 0x1f4d],
0x1f59,
0x1f5b,
0x1f5d,
0x1f5f,
[0x1f68, 0x1f6f],
0x1f71,
0x1f73,
0x1f75,
0x1f77,
0x1f79,
0x1f7b,
0x1f7d,
[0x1f80, 0x1faf],
[0x1fb2, 0x1fb4],
[0x1fb7, 0x1fc4],
[0x1fc7, 0x1fcf],
0x1fd3,
[0x1fd8, 0x1fdb],
[0x1fdd, 0x1fdf],
0x1fe3,
[0x1fe8, 0x1fef],
[0x1ff2, 0x1ff4],
[0x1ff7, 0x1ffe],
[0x2000, 0x200b],
[0x200e, 0x2064],
[0x206a, 0x2071],
[0x2074, 0x208e],
[0x2090, 0x2094],
[0x20a0, 0x20b8],
[0x20d0, 0x20f0],
[0x2100, 0x214d],
[0x214f, 0x2183],
[0x2185, 0x2189],
[0x2190, 0x23e8],
[0x2400, 0x2426],
[0x2440, 0x244a],
[0x2460, 0x26cd],
[0x26cf, 0x26e1],
0x26e3,
[0x26e8, 0x26ff],
[0x2701, 0x2704],
[0x2706, 0x2709],
[0x270c, 0x2727],
[0x2729, 0x274b],
0x274d,
[0x274f, 0x2752],
[0x2756, 0x275e],
[0x2761, 0x2794],
[0x2798, 0x27af],
[0x27b1, 0x27be],
[0x27c0, 0x27ca],
0x27cc,
[0x27d0, 0x2b4c],
[0x2b50, 0x2b59],
[0x2c00, 0x2c2e],
0x2c60,
[0x2c62, 0x2c64],
0x2c67,
0x2c69,
0x2c6b,
[0x2c6d, 0x2c70],
0x2c72,
0x2c75,
[0x2c7c, 0x2c80],
0x2c82,
0x2c84,
0x2c86,
0x2c88,
0x2c8a,
0x2c8c,
0x2c8e,
0x2c90,
0x2c92,
0x2c94,
0x2c96,
0x2c98,
0x2c9a,
0x2c9c,
0x2c9e,
0x2ca0,
0x2ca2,
0x2ca4,
0x2ca6,
0x2ca8,
0x2caa,
0x2cac,
0x2cae,
0x2cb0,
0x2cb2,
0x2cb4,
0x2cb6,
0x2cb8,
0x2cba,
0x2cbc,
0x2cbe,
0x2cc0,
0x2cc2,
0x2cc4,
0x2cc6,
0x2cc8,
0x2cca,
0x2ccc,
0x2cce,
0x2cd0,
0x2cd2,
0x2cd4,
0x2cd6,
0x2cd8,
0x2cda,
0x2cdc,
0x2cde,
0x2ce0,
0x2ce2,
[0x2ce5, 0x2ceb],
0x2ced,
[0x2cf9, 0x2cff],
0x2d6f,
[0x2e00, 0x2e2e],
[0x2e30, 0x2e31],
[0x2e80, 0x2e99],
[0x2e9b, 0x2ef3],
[0x2f00, 0x2fd5],
[0x2ff0, 0x2ffb],
[0x3000, 0x3004],
[0x3008, 0x3029],
[0x302e, 0x303b],
[0x303d, 0x303f],
[0x309b, 0x309c],
[0x309f, 0x30a0],
0x30ff,
[0x3131, 0x318e],
[0x3190, 0x319f],
[0x31c0, 0x31e3],
[0x3200, 0x321e],
[0x3220, 0x32fe],
[0x3300, 0x33ff],
[0x4dc0, 0x4dff],
[0xa490, 0xa4c6],
[0xa4fe, 0xa4ff],
[0xa60d, 0xa60f],
0xa640,
0xa642,
0xa644,
0xa646,
0xa648,
0xa64a,
0xa64c,
0xa64e,
0xa650,
0xa652,
0xa654,
0xa656,
0xa658,
0xa65a,
0xa65c,
0xa65e,
0xa662,
0xa664,
0xa666,
0xa668,
0xa66a,
0xa66c,
[0xa670, 0xa673],
0xa67e,
0xa680,
0xa682,
0xa684,
0xa686,
0xa688,
0xa68a,
0xa68c,
0xa68e,
0xa690,
0xa692,
0xa694,
0xa696,
[0xa6e6, 0xa6ef],
[0xa6f2, 0xa6f7],
[0xa700, 0xa716],
[0xa720, 0xa722],
0xa724,
0xa726,
0xa728,
0xa72a,
0xa72c,
0xa72e,
0xa732,
0xa734,
0xa736,
0xa738,
0xa73a,
0xa73c,
0xa73e,
0xa740,
0xa742,
0xa744,
0xa746,
0xa748,
0xa74a,
0xa74c,
0xa74e,
0xa750,
0xa752,
0xa754,
0xa756,
0xa758,
0xa75a,
0xa75c,
0xa75e,
0xa760,
0xa762,
0xa764,
0xa766,
0xa768,
0xa76a,
0xa76c,
0xa76e,
0xa770,
0xa779,
0xa77b,
[0xa77d, 0xa77e],
0xa780,
0xa782,
0xa784,
0xa786,
[0xa789, 0xa78b],
[0xa828, 0xa82b],
[0xa830, 0xa839],
[0xa874, 0xa877],
[0xa8ce, 0xa8cf],
[0xa8f8, 0xa8fa],
[0xa92e, 0xa92f],
[0xa95f, 0xa97c],
[0xa9c1, 0xa9cd],
[0xa9de, 0xa9df],
[0xaa5c, 0xaa5f],
[0xaa77, 0xaa79],
[0xaade, 0xaadf],
0xabeb,
[0xd7b0, 0xd7c6],
[0xd7cb, 0xd7fb],
[0xd800, 0xfa0d],
0xfa10,
0xfa12,
[0xfa15, 0xfa1e],
0xfa20,
0xfa22,
[0xfa25, 0xfa26],
[0xfa2a, 0xfa2d],
[0xfa30, 0xfa6d],
[0xfa70, 0xfad9],
[0xfb00, 0xfb06],
[0xfb13, 0xfb17],
0xfb1d,
[0xfb1f, 0xfb36],
[0xfb38, 0xfb3c],
0xfb3e,
[0xfb40, 0xfb41],
[0xfb43, 0xfb44],
[0xfb46, 0xfbb1],
[0xfbd3, 0xfd3f],
[0xfd50, 0xfd8f],
[0xfd92, 0xfdc7],
[0xfdd0, 0xfdfd],
[0xfe00, 0xfe19],
[0xfe30, 0xfe52],
[0xfe54, 0xfe66],
[0xfe68, 0xfe6b],
[0xfe70, 0xfe72],
0xfe74,
[0xfe76, 0xfefc],
0xfeff,
[0xff01, 0xffbe],
[0xffc2, 0xffc7],
[0xffca, 0xffcf],
[0xffd2, 0xffd7],
[0xffda, 0xffdc],
[0xffe0, 0xffe6],
[0xffe8, 0xffee],
[0xfff9, 0xffff],
/*
** Code points above 0xffff cause JS (PhantomJS, anyway)
** to misidentify ordinary letters like “f” as disallowed.
** punycode.js’s ucs2 stuff compensates for that.
*/
[0x10100, 0x10102],
[0x10107, 0x10133],
[0x10137, 0x1018a],
[0x10190, 0x1019b],
[0x101d0, 0x101fc],
[0x10320, 0x10323],
0x10341,
0x1034a,
0x1039f,
[0x103d0, 0x103d5],
[0x10400, 0x10427],
[0x10857, 0x1085f],
[0x10916, 0x1091b],
0x1091f,
0x1093f,
[0x10a40, 0x10a47],
[0x10a50, 0x10a58],
[0x10a7d, 0x10a7f],
[0x10b39, 0x10b3f],
[0x10b58, 0x10b5f],
[0x10b78, 0x10b7f],
[0x10e60, 0x10e7e],
[0x110bb, 0x110c1],
[0x12400, 0x12462],
[0x12470, 0x12473],
[0x1d000, 0x1d0f5],
[0x1d100, 0x1d126],
[0x1d129, 0x1d1dd],
[0x1d200, 0x1d245],
[0x1d300, 0x1d356],
[0x1d360, 0x1d371],
[0x1d400, 0x1d454],
[0x1d456, 0x1d49c],
[0x1d49e, 0x1d49f],
0x1d4a2,
[0x1d4a5, 0x1d4a6],
[0x1d4a9, 0x1d4ac],
[0x1d4ae, 0x1d4b9],
0x1d4bb,
[0x1d4bd, 0x1d4c3],
[0x1d4c5, 0x1d505],
[0x1d507, 0x1d50a],
[0x1d50d, 0x1d514],
[0x1d516, 0x1d51c],
[0x1d51e, 0x1d539],
[0x1d53b, 0x1d53e],
[0x1d540, 0x1d544],
0x1d546,
[0x1d54a, 0x1d550],
[0x1d552, 0x1d6a5],
[0x1d6a8, 0x1d7cb],
[0x1d7ce, 0x1d7ff],
[0x1f000, 0x1f02b],
[0x1f030, 0x1f093],
[0x1f100, 0x1f10a],
[0x1f110, 0x1f12e],
0x1f131,
0x1f13d,
0x1f13f,
0x1f142,
0x1f146,
[0x1f14a, 0x1f14e],
0x1f157,
0x1f15f,
0x1f179,
[0x1f17b, 0x1f17c],
0x1f17f,
[0x1f18a, 0x1f18d],
0x1f190,
0x1f200,
[0x1f210, 0x1f231],
[0x1f240, 0x1f248],
[0x1fffe, 0x1ffff],
[0x2f800, 0x2fa1d],
[0x2fffe, 0x2ffff],
[0x3fffe, 0x3ffff],
[0x4fffe, 0x4ffff],
[0x5fffe, 0x5ffff],
[0x6fffe, 0x6ffff],
[0x7fffe, 0x7ffff],
[0x8fffe, 0x8ffff],
[0x9fffe, 0x9ffff],
[0xafffe, 0xaffff],
[0xbfffe, 0xbffff],
[0xcfffe, 0xcffff],
[0xdfffe, 0xdffff],
0xe0001,
[0xe0020, 0xe007f],
[0xe0100, 0xe01ef],
[0xefffe, 0x10ffff],
0x19da, // RFC 6452 addition
];
// A transform of the above datastore to a lookup of single characters.
var DISALLOWED_LOOKUP;
function _getDisallowedLookup() {
if (!DISALLOWED_LOOKUP) {
DISALLOWED_LOOKUP = UNICODE.createCharacterLookup(DISALLOWED);
}
return DISALLOWED_LOOKUP;
}
/**
* @function getDisallowedInLabel
* @param String specimen - the input
* @return Array an array of the unique invalid characters from the input
*/
function getDisallowedInLabel(specimen) {
var disallowedLookup = _getDisallowedLookup();
var codePoints = PUNYCODE.ucs2.decode(specimen);
// We could use an object/dict for this, but we’d depend on
// object insertion order being preserved in iteration.
var badChars = [];
var myChar;
for (var i = 0; i < codePoints.length; i++) {
myChar = (codePoints[i] > 0xffff) ? PUNYCODE.ucs2.encode([codePoints[i]]) : String.fromCharCode(codePoints[i]);
if ( disallowedLookup[myChar] ) {
badChars.push(myChar);
}
}
return _.uniq(badChars);
}
return {
getDisallowedInLabel: getDisallowedInLabel,
};
} );
/*
# cjt/util/idn.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* ----------------------------------------------------------------------
* idn.js - IDN validation per RFC 5891/4.2
*
* This provides part of the IDN validation algorithm. Specifically,
* this identifies:
*
* - DISALLOWED characters
*
* - improper hyphens
*
* - most contextual rule violations
*
* This currently does NOT identify:
*
* - UNASSIGNED characters
*
* - leading combining marks
*
* - certain contextual rules
*
* - violations of Bidi criteria (RFC 5893/2)
* ----------------------------------------------------------------------
*
* EXAMPLE USAGE:
*
* problemsStr = IDN.getLabelDefects( labelString )
*
* ----------------------------------------------------------------------
*/
define( 'cjt/util/idn',[
"lodash",
"punycode",
"cjt/util/locale",
"cjt/util/idnDisallowed",
"cjt/util/unicode",
],
function(_, PUNYCODE, LOCALE, IDN_DISALLOWED, UNICODE) {
"use strict";
// NB: many of the characters that fit these ranges are
// also on IDN’s DISALLOWED list. The ranges below that
// the DISALLOWED list fully excludes are commented out.
//
// We could edit the ranges that only partly overlap with the
// DISALLOWED list, but that would make it harder to compare
// our data with the upstream source. So partially-DISALLOWED
// ranges are left in place.
//
// The lists of code points below come from:
// https://www.unicode.org/Public/12.1.0/ucd/Scripts.txt
//
// The most recent version will be at:
// https://www.unicode.org/Public/UCD/latest/ucd/Scripts.txt
//
// It may be useful at a later point to take steps to keep
// these lists in sync with further revisions of that list.
// For now we just publish static lists and hope that upstream
// changes to these groups are rare.
var SCRIPT_DATA = {
greek: [
[0x370, 0x373],
0x375,
[0x376, 0x377],
// 0x37a, // DISALLOWED
[0x37b, 0x37d],
0x37f,
// 0x384, // DISALLOWED
// 0x386, // DISALLOWED
// [0x388, 0x38a], // DISALLOWED
// 0x38c, // DISALLOWED
[0x38e, 0x3a1],
[0x3a3, 0x3e1],
[0x3f0, 0x3f5],
// 0x3f6, // DISALLOWED
[0x3f7, 0x3ff],
[0x1d26, 0x1d2a],
// [0x1d5d, 0x1d61], // DISALLOWED
// [0x1d66, 0x1d6a], // DISALLOWED
// 0x1dbf, // DISALLOWED
[0x1f00, 0x1f15],
// [0x1f18, 0x1f1d], // DISALLOWED
[0x1f20, 0x1f45],
// [0x1f48, 0x1f4d], // DISALLOWED
[0x1f50, 0x1f57],
// 0x1f59, // DISALLOWED
// 0x1f5b, // DISALLOWED
// 0x1f5d, // DISALLOWED
[0x1f5f, 0x1f7d],
[0x1f80, 0x1fb4],
[0x1fb6, 0x1fbc],
// 0x1fbd, // DISALLOWED
// 0x1fbe, // DISALLOWED
// [0x1fbf, 0x1fc1], // DISALLOWED
// [0x1fc2, 0x1fc4], // DISALLOWED
[0x1fc6, 0x1fcc],
// [0x1fcd, 0x1fcf], // DISALLOWED
[0x1fd0, 0x1fd3],
[0x1fd6, 0x1fdb],
// [0x1fdd, 0x1fdf], // DISALLOWED
[0x1fe0, 0x1fec],
// [0x1fed, 0x1fef], // DISALLOWED
// [0x1ff2, 0x1ff4], // DISALLOWED
[0x1ff6, 0x1ffc],
// [0x1ffd, 0x1ffe], // DISALLOWED
// 0x2126, // DISALLOWED
0xab65,
[0x10140, 0x10174],
[0x10175, 0x10178],
[0x10179, 0x10189],
[0x1018a, 0x1018b],
[0x1018c, 0x1018e],
0x101a0,
// [0x1d200, 0x1d241], // DISALLOWED
// [0x1d242, 0x1d244], // DISALLOWED
// 0x1d245, // DISALLOWED
],
hebrew: [
[ 0x591, 0x5bd ],
// 0x5be, // DISALLOWED
0x5bf,
// 0x5c0, // DISALLOWED
[ 0x5c1, 0x5c2 ],
// 0x5c3, // DISALLOWED
[ 0x5c4, 0x5c5 ],
// 0x5c6, // DISALLOWED
0x5c7,
[ 0x5d0, 0x5ea ],
[ 0x5ef, 0x5f2 ],
[ 0x5f3, 0x5f4 ],
// 0xfb1d, // DISALLOWED
0xfb1e,
// [ 0xfb1f, 0xfb28 ], // DISALLOWED
// 0xfb29, // DISALLOWED
// [ 0xfb2a, 0xfb36 ], // DISALLOWED
// [ 0xfb38, 0xfb3c ], // DISALLOWED
// 0xfb3e, // DISALLOWED
// [ 0xfb40, 0xfb41 ], // DISALLOWED
// [ 0xfb43, 0xfb44 ], // DISALLOWED
// [ 0xfb46, 0xfb4f ], // DISALLOWED
],
hiragana: [
[ 0x3041, 0x3096 ],
[ 0x309d, 0x309e ],
// 0x309f, // DISALLOWED
[ 0x1b001, 0x1b11e ],
[ 0x1b150, 0x1b152 ],
// 0x1f200, // DISALLOWED
],
katakana: [
[0x30a1, 0x30fa],
[0x30fd, 0x30fe],
// 0x30ff, // DISALLOWED
[0x31f0, 0x31ff],
// [0x32d0, 0x32fe], // DISALLOWED
// [0x3300, 0x3357], // DISALLOWED
// [0xff66, 0xff6f], // DISALLOWED
// [0xff71, 0xff9d], // DISALLOWED
0x1b000,
[0x1b164, 0x1b167],
],
han: [
// [0x2e80, 0x2e99], // DISALLOWED
// [0x2e9b, 0x2ef3], // DISALLOWED
// [0x2f00, 0x2fd5], // DISALLOWED
0x3005,
0x3007,
// [0x3021, 0x3029], // DISALLOWED
// [0x3038, 0x303a], // DISALLOWED
// 0x303b, // DISALLOWED
[0x3400, 0x4db5],
[0x4e00, 0x9fef],
[0xf900, 0xfa6d],
// [0xfa70, 0xfad9], // DISALLOWED
[0x20000, 0x2a6d6],
[0x2a700, 0x2b734],
[0x2b740, 0x2b81d],
[0x2b820, 0x2cea1],
[0x2ceb0, 0x2ebe0],
// [0x2f800, 0x2fa1d], // DISALLOWED
],
};
var VIRAMA_LIST = [
0x94d,
0x9cd,
0xa4d,
0xacd,
0xb4d,
0xbcd,
0xc4d,
0xccd,
0xd3b,
0xd3c,
0xd4d,
0xdca,
0xe3a,
0xeba,
0xf84,
0x1039,
0x103a,
0x1714,
0x1734,
0x17d2,
0x1a60,
0x1b44,
0x1baa,
0x1bab,
0x1bf2,
0x1bf3,
0x2d7f,
0xa806,
0xa8c4,
0xa953,
0xa9c0,
0xaaf6,
0xabed,
0x10a3f,
0x11046,
0x1107f,
0x110b9,
0x11133,
0x11134,
0x111c0,
0x11235,
0x112ea,
0x1134d,
0x11442,
0x114c2,
0x115bf,
0x1163f,
0x116b6,
0x1172b,
0x11839,
0x119e0,
0x11a34,
0x11a47,
0x11a99,
0x11c3f,
0x11d44,
0x11d45,
0x11d97,
];
var SCRIPT_LOOKUP;
var KATAKANA_MIDDLE_DOT_OK = {
han: true,
katakana: true,
hiragana: true,
};
function _getCodePointScript(cp) {
if (!SCRIPT_LOOKUP) {
var scriptNames = Object.keys(SCRIPT_DATA);
SCRIPT_LOOKUP = {};
scriptNames.forEach( function(script) {
UNICODE.augmentCodePointLookup(SCRIPT_DATA[script], SCRIPT_LOOKUP, script);
} );
}
return SCRIPT_LOOKUP[cp];
}
function _encodeCP(cp) {
return PUNYCODE.ucs2.encode([cp]);
}
function _getContextDefectCPs(label) {
var badContext = [];
// Implementations of various parts of
// https://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml
var codePoints = PUNYCODE.ucs2.decode(label);
CODE_POINT:
for (var i = 0; i < codePoints.length; i++) {
var ii;
switch (codePoints[i]) {
case 0x200c:
// TODO: We have the Virama logic but need the check
// on joining type for this to be functional.
break;
case 0x200d:
// Previous character’s canonical combining class
// must be Virama.
if (-1 === VIRAMA_LIST.indexOf(codePoints[i - 1])) {
badContext.push(codePoints[i]);
}
break;
case 0xb7:
if (codePoints[i - 1] !== 0x6c || codePoints[i + 1] !== 0x6c) {
badContext.push(codePoints[i]);
}
break;
case 0x375:
// The script of the following character MUST be Greek.
if (_getCodePointScript(codePoints[i + 1]) !== "greek") {
badContext.push(codePoints[i]);
}
break;
case 0x5f3:
case 0x5f4:
// The script of the preceding character MUST be Hebrew.
if (_getCodePointScript(codePoints[i - 1]) !== "hebrew") {
badContext.push(codePoints[i]);
}
break;
case 0x30fb:
// At least one character in the label must be of the
// Hiragana, Katakana, or Han script.
for (ii = 0; ii < codePoints.length; ii++) {
var cpScript = _getCodePointScript(codePoints[ii]);
if (KATAKANA_MIDDLE_DOT_OK[cpScript]) {
continue CODE_POINT;
}
}
badContext.push(codePoints[i]);
break;
default:
// Arabic-Indic digits can’t be with Extended Arabic-Indic
if (codePoints[i] >= 0x660 && codePoints[i] <= 0x669) {
for (ii = 0; ii < codePoints.length; ii++) {
if (codePoints[ii] >= 0x6f0 && codePoints[ii] <= 0x6f9) {
badContext.push(codePoints[i]);
break;
}
}
}
// Extended Arabic-Indic digits can’t be with
// (regular) Arabic-Indic
if (codePoints[i] >= 0x6f0 && codePoints[i] <= 0x6f9) {
for (ii = 0; ii < codePoints.length; ii++) {
if (codePoints[ii] >= 0x660 && codePoints[ii] <= 0x669) {
badContext.push(codePoints[i]);
break;
}
}
}
}
}
return _.uniq(badContext);
}
function _codePointsToUplus(cps) {
return cps.map( function(cp) {
return "U+" + _.padStart(cp.toString(16).toUpperCase(), 4, "0");
} );
}
/**
* @function getLabelDefects
*
* @param label String The input to parse as an IDN label.
* @returns Array Human-readable descriptions of the validation errors.
*/
function getLabelDefects(label) {
var phrases = [];
var disallowed = IDN_DISALLOWED.getDisallowedInLabel(label);
if (disallowed.length) {
var cps = PUNYCODE.ucs2.decode( disallowed.join("") );
var upluses = _codePointsToUplus(cps);
phrases.push( LOCALE.maketext("Domain names may not contain [list_or_quoted,_1] ([list_or,_2]).", disallowed, upluses) );
}
var badContextCPs = _getContextDefectCPs(label);
if (badContextCPs.length) {
var chars = badContextCPs.map( _encodeCP );
var ctxUpluses = _codePointsToUplus(badContextCPs);
phrases.push( LOCALE.maketext("You must use [list_and_quoted,_1] ([list_and,_2]) properly in domain names.", chars, ctxUpluses) );
}
if (label.substr(2, 2) === "--") {
phrases.push( LOCALE.maketext("“[_1]” is forbidden at the third position of a domain label.", "--") );
}
if (/^-|-$/.test(label)) {
phrases.push( LOCALE.maketext("“[_1]” is forbidden at the start or end of a domain label.", "-") );
}
return phrases;
}
return {
getLabelDefects: getLabelDefects,
// for testing only
_lists: _.assign(
{},
SCRIPT_DATA,
{ virama: VIRAMA_LIST }
),
};
});
/*
# cpanel - share/libraries/cjt2/src/util/inet6.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false, module: false */
/**
*
* @module cjt/util/inet6
* @example var output = inet6.parse(input).toString();
* @exports cjt/util/inet6
*/
(function(root, factory) {
if (typeof define === "function" && define.amd) {
/*
* AMD; Register as an anonymous module because
* the filename (in this case cjt/util/inet6) will
* become the name of the module.
*/
define('cjt/util/inet6',[], factory);
} else if (typeof exports === "object") {
/*
* Node. Does not work with strict CommonJS, but * only CommonJS-like
* enviroments that support module.exports, like Node.
*/
module.exports = factory();
} else {
/*
* Export to cPanel browser global namespace
*/
if (root.CPANEL) {
root.CPANEL.inet6 = factory();
} else {
root.inet6 = factory();
}
}
}(this, function() {
"use strict";
var inet = {};
// TODO: replace with $Cpanel::Regex::regex{'ipv4'}
var ipv4Regex = /^\d{1,3}(?:\.\d{1,3}){3}$/;
var ipv6PartRegex = /^[0-9a-f]{1,4}$/i;
/**
* @constructor
* @param {string} address - String we want to represent an IPv4 address
* portion of a IPv4 compatible address
*/
inet.Address = function(address) {
if (address === void 0 || Object.prototype.toString.call(address) !== "[object String]") {
throw "Invalid input: Not a String";
}
var parts = address.split(".");
if (parts.length > 4) {
throw "Invalid IPv4 address: Too many components";
}
if (parts.length < 4) {
throw "Invalid IPv4 address: Too few components";
}
for (var i = 0, len = parts.length; i < len; i++) {
var part = parts[i];
if (part > 255 || part < 0) {
throw "Invalid IPv4 address: Invalid component";
}
this.push(part);
}
};
inet.Address.prototype = [];
inet.Address.prototype.toString = function() {
return this.join(".");
};
var inet6 = {};
/**
* @constructor
* @param {string} address - the string we want to convert into an IPv6 object
*/
inet6.Address = function(address) {
var self = this;
/*
* A quick convenience for adding zero padding groups to the current
* object.
*/
function pad(count) {
for (var i = 0; i < count; i++) {
self.push(0x0000);
}
}
if (address === void 0 || Object.prototype.toString.call(address) !== "[object String]") {
throw "Invalid input: Not a String";
}
/*
* First, take a look through all the address components passed to the
* constructor.
*/
var parts = address.split(":");
var expected = 8;
var minimum = 3;
var count = parts.length; /* Number of logical parts in address */
var length = parts.length; /* Number of string parts in address */
var padded = false;
var i, part, value, first, last;
/*
* This value will change to true if there is a trailing IPv4 address
* embedded in the address string.
*/
var hasv4Address = false;
/*
* If the address does not contain at least "::", then bail, of course.
*/
if (length < minimum) {
throw "Invalid IPv6 address: Too few components";
}
if (length > 3 && parts[0] === "" && parts[1] === "" && parts[length - 1] === "" && parts[length - 2] === "") {
throw "Invalid IPv6 address: Too many colons";
}
if (parts[0] === "" && parts[1] !== "") {
throw "Invalid IPv6 address: Missing beginning component";
}
if (parts[length - 1] === "" && parts[length - 2] !== "") {
throw "Invalid IPv6 address: Missing end component";
}
/*
* Get rid of the leading and trailing double-colon effects
*/
if (parts[0] === "" && parts[1] === "") {
parts.shift();
length = parts.length;
count = parts.length;
}
if (parts[parts.length - 1] === "" && parts[parts.length - 2] === "") {
parts.pop();
length = parts.length;
count = parts.length;
}
/*
* If we're left with one empty item, our original address was
* ::, so just pad the whole thing out and be done.
*/
if (length === 1 && parts[0] === "") {
pad(8);
return;
}
/*
* This counter is used to keep track of the number of empty components
* in the middle of a tokenized IPv6 address string. For example:
*
* fe80::1::2
*
* Any more than one empty component in the middle of an address leads
* to an ambiguity in determining how much zero padding to use in an
* address.
*/
var emptyMiddle = 0;
/*
* Increase the parts count by one for each IPv4 address component
* found.
*/
for (i = 0; i < length; i++) {
part = parts[i].trim();
if (ipv4Regex.test(part)) {
count++;
}
}
for (i = 0; i < length; i++) {
part = parts[i].trim();
value = null;
first = (i === 0) ? true : false;
last = (i === (length - 1)) ? true : false;
if (ipv4Regex.test(part)) {
/*
* Check for an embedded IPv4 address
*/
if (i !== length - 1) {
throw "Invalid IPv6 address: Embedded IPv4 address not at end";
}
for (var n = 4; n < expected - count; n++) {
this.shift();
}
var inet4address = new inet.Address(part);
this.push((inet4address[0] << 8) | inet4address[1]);
value = (inet4address[2] << 8) | inet4address[3];
hasv4Address = true;
} else if (ipv6PartRegex.test(part)) {
/*
* Check for a valid IPv6 part
*/
value = parseInt(part, 16);
} else if (part === "") {
emptyMiddle++;
/*
* If we have reached an empty component, and no padding has
* been applied yet, then introduce the requisite amount of
* zero padding.
*/
if (!padded) {
pad(expected - count);
padded = true;
}
value = 0x0000;
} else {
throw "Invalid IPv6 address: Invalid component " + part;
}
this.push(value);
}
if (emptyMiddle > 1) {
throw "Invalid IPv6 address: Too many colons";
}
if (this.length < expected) {
throw "Invalid IPv6 address: Too few components";
}
if (this.length > expected) {
throw "Invalid IPv6 address: Too many components";
}
if (hasv4Address) {
for (i = 0; i < 5; i++) {
if (this[i] !== 0x0000) {
throw "Invalid IPv4 compatible address";
}
}
if (this[5] !== 0xffff) {
throw "Invalid IPv6 compatible address";
}
}
};
inet6.Address.prototype = [];
/**
* Stringify an IPv6 address with an embedded IPv4 address
* @return {string}
*/
inet6.Address.prototype.toString_v4Compat = function() {
var parts = [];
parts.push((this[6] & 0xff00) >> 8);
parts.push( this[6] & 0x00ff);
parts.push((this[7] & 0xff00) >> 8);
parts.push( this[7] & 0x00ff);
return "::ffff:" + parts.join(".");
};
/**
* Returns true if the current address object is an IPv4 compatibility
* address; in other words, an address in the ::ffff:0:0/96 space.
*
* @return {boolean}
*/
inet6.Address.prototype.isv4Compat = function() {
/*
* Ensure the first five uint16s of the address are 0x0000 values.
*/
for (var i = 0; i < 5; i++) {
if (this[i] !== 0x0000) {
return 0;
}
}
/*
* At this point, the sixth uint16 determines if we do indeed have an
* IPv4 compatibility address.
*/
return (this[5] === 0xffff) ? true : false;
};
/**
* Stringify an IPv6 address
* @return {string}
*/
inet6.Address.prototype.toString = function() {
var ranges = [];
var count = this.length;
var last = null;
var longest = null;
var range = null;
/*
* If this is an IPv4 compatible address, stringify using a method that
* will encode it in the proper quad octet notation.
*/
if (this.isv4Compat()) {
return this.toString_v4Compat();
}
/*
* First, collate contiguous groups of zeroes into an array of
* ranges, indicating the index within the current address object
* of their first and their last occurences. Along the way,
* determine which range of contiguous zeroes is the longest,
* preferring the rightmost one if there are multiple groups of
* zeroes in the address.
*/
for (var i = 0; i < count; i++) {
var value = this[i];
if (value !== 0x0000 || (value === 0x0000 && last !== 0x0000)) {
ranges.push({
"value": value,
"first": i,
"last": i,
"longest": false
});
}
range = ranges[ranges.length - 1];
range.last = i;
if (longest === null) {
longest = range;
}
var currentSize = range.last - range.first;
var longestSize = longest.last - longest.first;
if (value === 0x0000 && currentSize > longestSize) {
longest = range;
}
last = value;
}
/*
* Next, format the number ranges into an array of string tokens,
* adding empty tokens along the way where necessary to express
* contiguous ranges of zeroes as accurately as possible.
*/
var ret = [];
var len = ranges.length;
for (i = 0; i < len; i++) {
range = ranges[i];
if (range.value === 0x0000 && range === longest) {
/*
* If this is the first range of contiguous zeroes in the
* address, then add an empty token to the left of the
* address to be returned.
*/
if (i === 0) {
ret.push("");
}
/*
* Regardless of the position of the longest range of
* contiguous zeroes, add an empty token to the output.
*/
ret.push("");
/*
* If this is the last range of contiguous zeroes in the
* address, then add another empty token to the output.
*/
if (i === len - 1) {
ret.push("");
}
} else {
for (var n = range.first; n <= range.last; n++) {
ret.push(range.value.toString(16));
}
}
}
return ret.join(":");
};
/**
* Exported method to validate an IPv6 address
* @param {string} address - IPv6 address string
* @return {boolean}
*/
inet6.isValid = function(address) {
try {
this.parse(address);
return true;
} catch (e) {
return false;
}
};
/**
* Exported method for parsing IPv6 addresses to inet6.Address objects
* @param {string} address - IPv6 address string
* @return {inet6.Address}
*/
inet6.parse = function(address) {
if (address === void 0 || Object.prototype.toString.call(address) !== "[object String]") {
throw "Invalid input: Not a String";
}
return new this.Address(address);
};
/**
* Reformat an IPv6 address into its canonical compact representation for
* display; if the input is an invalid IPv6 address, it is returned to the
* caller unmodified, otherwise the newly-reformatted address is returned
* upon success
*
* @param {string} address - IPv6 address string
* @return {string}
*/
inet6.formatForDisplay = function(address) {
var ret;
try {
var inet6 = new this.Address(address);
ret = inet6.toString();
} catch (e) {
ret = address;
}
return ret;
};
return inet6;
}));
/*
# cjt/utils/limits.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define('cjt/util/limits',[],function() {
var UNLIMITED = -1,
UNLIMITED_STR = "unlimited";
return {
UNLIMITED: UNLIMITED,
UNLIMITED_STR: UNLIMITED_STR,
/**
* Parse a max limit
* @param {Object} limit [description]
* @return {Number} [description]
*/
parseMaxLimit: function(limit) {
if (!limit) {
return 0;
}
return parseInt(limit.max === UNLIMITED_STR ? UNLIMITED : limit._max, 10);
},
/**
* Retrieve the current count
* @param {Object} limit [description]
* @return {Number} [description]
*/
parseTotalItems: function(limit) {
if (!limit) {
return 0;
}
return parseInt(limit._count, 10);
},
/**
* Checks if a possibly unlimited value is in its limit.
* @param {Number} max [description]
* @param {Number} current [description]
* @return {Bool} [description]
*/
outOfLimits: function(max, current) {
return (max !== UNLIMITED && current >= max);
}
};
});
/*
# cjt/util/logic.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/util/logic',['require'],function($) {
return {
/**
* If value1 defined, compare value1 to value2, otherwise return the default value
*
* @method compareOrDefault
* @param {String} value1 First operand if defined
* @param {String} value2 Second operand if first is defined.
* @param {Boolean} def Default value if first operand is undefined.
* @return {Boolean}
*/
compareOrDefault: function(value1, value2, def) {
if (typeof (value1) !== "undefined") {
return value1 === value2;
} else {
return def;
}
},
/**
* Translates a pair of binary operands to a named state. Useful for translating independent states
* to shared state variable for things like radio buttons that need to share a model.
* @param {Boolean} arg1 State 1
* @param {Boolean} arg2 State 2
* @param {Any} both_true Returned if both state args are true.
* @param {Any} arg1_true Returned if only arg1 is true.
* @param {Any} arg2_true Returned if only arg2 is true.
* @param {Any} none_true Returned if neither arg1 or arg2 are true
* @return {Any} See above.
*/
translateBinaryAndToState: function(arg1, arg2, both_true, arg1_true, arg2_true, none_true) {
if (arg1 && arg2) {
return both_true;
} else if (arg1) {
return arg1_true;
} else if (arg2) {
return arg2_true;
} else {
return none_true;
}
}
};
}
);
/*
# cjt/util/logMetaformat.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
// Expand this later as necessary to include metadata.
define('cjt/util/logMetaformat',[], function() {
"use strict";
return {
MODULE_NAME: "cjt/io/logMetaformat",
MODULE_DESC: "Parser for the encoding of Cpanel::Log::MetaFormat",
MODULE_VERSION: "1.0",
parse: function(input, metadata) {
input = input.replace(/\.(\.|[^.\n][^\n]*\n)/mg, function(match, p1) {
if (p1 === ".") {
return ".";
}
var decoded = JSON.parse(p1); // trailing newline is ok
metadata[decoded[0]] = decoded[1];
return "";
});
return input;
},
};
} );
/*
# cjt/util/scrollSelect.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define:true */
/* --------------------------*/
// Expand this later as necessary to include metadata.
define('cjt/util/scrollSelect',[], function() {
"use strict";
return {
MODULE_NAME: "cjt/io/scrollSelect",
MODULE_DESC: "Utilities for scrolling a DOM <select> node",
MODULE_VERSION: "1.0",
scrollToEnd: function(el) {
el.scrollTop = el.scrollHeight;
},
// copied from base/sharedjs/transfers/TransferLogRender.js
isAtEnd: function(el) {
return (el.scrollTop + el.offsetHeight + 1 >= el.scrollHeight) || (el.scrollHeight <= el.offsetHeight);
},
};
} );
/*
# cpanel - cjt/util/table.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/util/table',[
"lodash",
"cjt/util/locale",
],
function(_, LOCALE) {
/**
* Creates a Table object which handles search/sort/paging functionality for
* client-side data. Does not support search/sort/paging via an AJAX call at this point.
* It is expected to be used with an array of objects.
* It is expected to be used with one table, that is 1 Table object for 1 Table being displayed.
*
* @class
*/
function Table() {
this.items = [];
this.filteredList = this.items;
this.selected = [];
this.allDisplayedRowsSelected = false;
this.searchFunction = void 0;
this.filterOptionFunction = void 0;
this.meta = {
sortBy: "",
sortDirection: "asc",
totalItems: 0,
pageNumber: 1,
pageSize: 10,
pageSizes: [10, 20, 50, 100],
start: 0,
limit: 0,
searchText: "",
filterOption: ""
};
// NOTE: This is only here since it is used by our apps to set the
// max-pages setting in the ui-bootstrap uib-pagination directive
this.meta.maxPages = 0;
this.last_id = 0;
}
/**
* Load data into the table
*
* @method load
* @param {Array} data - an array of objects representing the data to display
*/
Table.prototype.load = function(data) {
if (!_.isArray(data)) {
throw "Developer Exception: load requires an array";
}
// reset the last id
this.last_id = 0;
this.items = data;
for (var i = 0, len = this.items.length; i < len; i++) {
if (!_.isObject(this.items[i])) {
throw "Developer Exception: load requires an array of objects";
}
// add a unique id to each piece of data
this.items[i]._id = i;
// initialize the selected array with the ids of selected items
if (this.items[i].selected) {
this.selected.push(this.items[i]._id);
}
}
this.last_id = i;
};
/**
* Set the search function to be used for searching the table. It is up to
* the implementor to define how this will work. We are just providing a hook
* point here.
*
* @method setSearchFunction
* @param {Function} func - a function that can be used to search the data
* @note The function passed to this function must
* - return a boolean
* - accept the following args: an item object and the search text
*/
Table.prototype.setSearchFunction = function(func) {
if (!_.isFunction(func)) {
throw "Developer Error: setSearchFunction requires a function";
}
this.searchFunction = func;
};
/**
* Set the filter option function. This is intended to be used with a
* way to apply filters to the data in the interface (e.g. pre-defined
* values like Most Used or Most Recent). It is up to the implementor to
* define how this will work. We are just providing a hook point here.
*
* @method setFilterOptionFunction
* @param {Function} func - a function that can be used to filter data
* @note The function passed to this function must
* - return a boolean
* - accept the following args: an item object and the search text
*/
Table.prototype.setFilterOptionFunction = function(func) {
if (!_.isFunction(func)) {
throw "Developer Error: setFilterOptionFunction requires a function";
}
this.filterOptionFunction = func;
};
/**
* Set the sort by and direction for the data
*
* @method setSort
* @param {String} by - the field(s) you want to sort on. you can specify multiple fields
* by separating them with a comma
* @param {String} direction - the direction you want to sort, "asc" or "desc"
*/
Table.prototype.setSort = function(by, direction) {
if (!_.isUndefined(by)) {
this.meta.sortBy = by;
}
if (!_.isUndefined(direction)) {
this.meta.sortDirection = direction;
}
};
/**
* Get the table metadata
*
* @method getMetadata
* @return {Object} The metadata for the table. We return a
* reference here so that callers can update the object and
* changes can easily be propagated.
*/
Table.prototype.getMetadata = function() {
return this.meta;
};
/**
* Get the table data
*
* @method getList
* @return {Array} The table data
*/
Table.prototype.getList = function() {
return this.filteredList;
};
/**
* Get the table data that is selected
*
* @method getSelectedList
* @return {Array} The table data that is selected
*/
Table.prototype.getSelectedList = function() {
return this.items.filter(function(item) {
return item.selected;
});
};
/**
* Determine if all the filtered table rows are selected
*
* @method areAllDisplayedRowsSelected
* @return {Boolean}
*/
Table.prototype.areAllDisplayedRowsSelected = function() {
return this.allDisplayedRowsSelected;
};
/**
* Get the total selected rows in the table
*
* @method getTotalRowsSelected
* @return {Number} total of selected rows in the table
*/
Table.prototype.getTotalRowsSelected = function() {
return this.selected.length;
};
/**
* Select all items for a single page of data in the table
*
* @method selectAllDisplayed
*/
Table.prototype.selectAllDisplayed = function() {
// Select the rows if they were previously selected on this page.
for (var i = 0, filteredLen = this.filteredList.length; i < filteredLen; i++) {
var item = this.filteredList[i];
item.selected = true;
// make sure this item is not already in the list
if (this.selected.indexOf(item._id) !== -1) {
continue;
}
this.selected.push(item._id);
}
this.allDisplayedRowsSelected = true;
};
/**
* Unselect all items for a single page of data in the table
*
* @method unselectAllDisplayed
*/
Table.prototype.unselectAllDisplayed = function() {
// Extract the unselected items and remove them from the selected collection.
var unselected = this.filteredList.map(function(item) {
item.selected = false;
return item._id;
});
this.selected = _.difference(this.selected, unselected);
this.allDisplayedRowsSelected = false;
};
/**
* Select an item on the current page.
*
* @method selectItem
* @param {Object} item - the item that we want to mark as selected.
*/
Table.prototype.selectItem = function(item) {
if (_.isUndefined(item)) {
return;
}
item.selected = true;
// make sure this item is not already in the list
if (this.selected.indexOf(item._id) !== -1) {
return;
}
this.selected.push(item._id);
// Check if all of the displayed rows are now selected
this.allDisplayedRowsSelected = this.filteredList.every(function(thisitem) {
return thisitem.selected;
});
};
/**
* Unselect an item on the current page.
*
* @method unselectItem
* @param {Object} item - the item that we want to mark as unselected.
*/
Table.prototype.unselectItem = function(item) {
if (_.isUndefined(item)) {
return;
}
item.selected = false;
// remove this item from the list of selected items
this.selected = this.selected.filter(function(thisid) {
return thisid !== item._id;
});
this.allDisplayedRowsSelected = false;
};
/**
* Clear all selections for all pages.
*
* @method clearAllSelections
*/
Table.prototype.clearAllSelections = function() {
this.selected = [];
for (var i = 0, len = this.items.length; i < len; i++) {
var item = this.items[i];
item.selected = false;
}
this.allDisplayedRowsSelected = false;
};
/**
* Clear the entire table.
*
* @method clear
*/
Table.prototype.clear = function() {
this.items = [];
this.selected = [];
this.last_id = 0;
this.allDisplayedRowsSelected = false;
this.update();
};
/**
* Update the table with data accounting for filtering, sorting, and paging
*
* @method update
* @return {Array} the table data
*/
Table.prototype.update = function() {
var filtered = [];
var self = this;
// search the data if search text is specified
if (this.meta.searchText !== null &&
this.meta.searchText !== void 0 &&
this.meta.searchText !== "" &&
this.searchFunction !== void 0) {
filtered = this.items.filter(function(item) {
return self.searchFunction(item, self.meta.searchText);
});
} else {
filtered = this.items;
}
// apply a filter to the list if one is specified
if (this.meta.filterOption !== null &&
this.meta.filterOption !== void 0 &&
this.meta.filterOption !== "" &&
this.filterOptionFunction !== void 0) {
filtered = filtered.filter(function(item) {
return self.filterOptionFunction(item, self.meta.filterOption);
});
}
// sort the filtered list
// Check for multiple sort fields separated by a comma
var sort_options = this.meta.sortBy.split(",");
if (this.meta.sortDirection !== "" && sort_options.length) {
filtered = _.orderBy(filtered, sort_options, [this.meta.sortDirection]);
}
// update the total items
this.meta.totalItems = filtered.length;
// page the data accordingly or display it all.
// we need to check the page sizes here since the page sizes can change based on the
// number of items in our list (the pageSizeDirective does this).
if (this.meta.totalItems > _.min(this.meta.pageSizes) ) {
var start = (this.meta.pageNumber - 1) * this.meta.pageSize;
var limit = this.meta.pageNumber * this.meta.pageSize;
filtered = _.slice(filtered, start, limit);
this.meta.start = start + 1;
this.meta.limit = start + filtered.length;
} else {
if (filtered.length === 0) {
this.meta.start = 0;
} else {
this.meta.start = 1;
}
this.meta.limit = filtered.length;
}
// select the appropriate items
var countNonSelected = 0;
for (var i = 0, filteredLen = filtered.length; i < filteredLen; i++) {
var item = filtered[i];
// Select the rows if they were previously selected on this page.
if (this.selected.indexOf(item._id) !== -1) {
item.selected = true;
} else {
item.selected = false;
countNonSelected++;
}
}
this.filteredList = filtered;
// Clear the 'Select All' checkbox if at least one row is not selected.
this.allDisplayedRowsSelected = (filtered.length > 0) && (countNonSelected === 0);
return filtered;
};
/**
* Add an item to the table.
*
* @method add
* @param {Object} item - the item that we want to add to the list.
*/
Table.prototype.add = function(item) {
if (!_.isObject(item)) {
throw "Developer Exception: add requires an object";
}
// update our internal ID counter
this.last_id++;
item._id = this.last_id;
this.items.push(item);
this.update();
};
/**
* Remove an item from the table.
* If the object exists in the table more than once, only the first instance is removed.
*
* @method remove
* @param {Object} item - the item that we want to remove from the list.
*/
Table.prototype.remove = function(item) {
if (!_.isObject(item)) {
throw "Developer Exception: remove requires an object";
}
var found = false;
if (item.hasOwnProperty("_id")) {
for (var i = 0, len = this.items.length; i < len; i++) {
if (this.items[i]._id === item._id) {
found = true;
break;
}
}
if (found) {
this.items.splice(i, 1);
this.update();
}
}
};
/**
* Create a localized message for the table stats
*
* @method paginationMessage
* @return {String}
*/
Table.prototype.paginationMessage = function() {
return LOCALE.maketext("Displaying [numf,_1] to [numf,_2] out of [quant,_3,item,items]", this.meta.start, this.meta.limit, this.meta.totalItems);
};
return Table;
}
);
/*
# cjt/util/unique.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define(
'cjt/util/unique',[],function() {
/**
* Generate unique IDs for use as pseudo-private/protected names.
* Similar in concept to
* <http: *wiki.ecmascript.org/doku.php?id=strawman:names>.
*
* The goals of this function are twofold:
*
* * Provide a way to generate a string guaranteed to be unique when compared
* to other strings generated by this function.
* * Make the string complex enough that it is highly unlikely to be
* accidentally duplicated by hand (this is key if you're using `ID`
* as a private/protected name on an object).
*
* @param {String} [prefix] Optional prefix to add to the id.
* @return {String} Unique id based on random number generation.
* @example
*
* With default options:
*
* var privateName = unique();
* var o = {};
* o[privateName] = "bar";
*
* privateName will be something like: '_???????'
*
* With a prefix:
*
* var privateName = unique("cp");
* var o = {};
* o[privateName] = "bar";
*
* privateName will be something like: 'cp_???????'
*/
return function(prefix) {
// Math.random should be unique because of its seeding algorithm.
// Convert it to base 36 (numbers + letters), and grab the first 9 characters
// after the decimal.
return prefix + "_" + Math.random().toString().substr(2, 9);
};
}
);
/*
# cjt/util/url.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false */
define('cjt/util/url',[],function() {
return {
/**
* Join the path parts of the uri correctly making sure the slashes are
* normalized correctly.
*
* @static
* @method join
* @param {String...} One or more url components to join together.
* @return {String} Valid url/uri path component.
* @example
* define(
* "cjt/util/url",
* function(URL)) {
* var url = URL.join("http://abc", "def", "ghi");
* assert(url === "http://abc/def/ghi");
*
* url = URL.join("http://abc/", "/def", "/ghi");
* assert(url === "http://abc/def/ghi");
*
* url = URL.join("http://abc/", "/def/", "/ghi/");
* assert(url === "http://abc/def/ghi/");
* }
* );
*
*/
join: function() {
var parts = [];
for (var i = 0, l = arguments.length; i < l; i++) {
var arg = arguments[i];
// Only adjust inner / separators.
// Assume the leading elements leading component is right
if (i > 0) {
arg = arg.replace(/^[\/]/, "");
}
// Assume the trailing elements trailing component is right
if (i !== l - 1) {
arg = arg.replace(/[\/]$/, "");
}
if (arg) {
parts.push(arg);
}
}
return parts.join("/");
}
};
}
);
/*
# ascii-data-validators.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
* This module is a collection of data-type validators
*
* @module ascii-data-validators
* @requires angular, validator-utils, validate, locale
*/
define('cjt/validator/ascii-data-validators',[
"angular",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/validator/validateDirectiveFactory"
],
function(angular, UTILS, LOCALE) {
var validators = {
/**
* Validates if the input has all alphabets
*
* @method alpha
* @param {String} val Text to validate
* @return {Object} Validation result
*/
alpha: function(val) {
var result = UTILS.initializeValidationResult();
var regExp = /^[a-zA-Z]+$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("alpha", LOCALE.maketext("The value should only contain the letters [asis,a-z] and [asis,A-Z]."));
return result;
}
return result;
},
/**
* Validates if the input is all upper case
*
* @method upperCaseOnly
* @param {String} val Text to validate
* @return {Object} Validation result
*/
upperCaseOnly: function(val) {
var result = UTILS.initializeValidationResult();
var regExp = /^[A-Z]+$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("upperCaseOnly", LOCALE.maketext("The value should only contain uppercase letters."));
return result;
}
return result;
},
/**
* Validates if the input is all lower case
*
* @method lowerCaseOnly
* @param {String} val Text to validate
* @return {Object} Validation result
*/
lowerCaseOnly: function(val) {
var result = UTILS.initializeValidationResult();
var regExp = /^[a-z]+$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("lowerCaseOnly", LOCALE.maketext("The value should only contain lowercase letters."));
return result;
}
return result;
},
/**
* Validates if the input is alpha numeric only
*
* @method alphaNumeric
* @param {String} val Text to validate
* @return {Object} Validation Result
*/
alphaNumeric: function(val) {
var result = UTILS.initializeValidationResult();
var regExp = /^\w+$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("alphaNumeric", LOCALE.maketext("The value should only contain alphanumeric characters."));
return result;
}
return result;
},
/**
* Validates if the input starts with the passed in pattern.
*
* @method startsWith
* @param {String} val Text to validate
* @param {String} match Text to match
* @return {Object} Validation Result
*/
startsWith: function(val, match) {
var result = UTILS.initializeValidationResult();
var regExp = new RegExp("^" + match);
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("startsWith", LOCALE.maketext("The value should start with “[_1]”.", match));
return result;
}
return result;
},
/**
* Validates if the input ends with the passed in pattern.
*
* @method endsWith
* @param {String} val Text to validate
* @param {String} match Text to match
* @return {Object} Validation Result
*/
endsWith: function(val, match) {
var result = UTILS.initializeValidationResult();
var regExp = new RegExp(match + "$");
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("endsWith", LOCALE.maketext("The value should end with “[_1]”.", match));
return result;
}
return result;
}
};
// Generate a directive for each validation function
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(validators);
}
]);
return {
methods: validators,
name: "ascii-data-validators",
description: "Validation library for ascii values.",
version: 2.0,
};
});
/*
# compare-validators.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
* This module has a collection of compare validators
*
* @module compare-validators
* @requires angular, lodash, validator-utils, validate, locale
*/
define('cjt/validator/compare-validators',[
"angular",
"lodash",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/validator/validateDirectiveFactory"
],
function(angular, _, UTILS, LOCALE) {
var IS_FRACTION_REGEXP = /\.([0-9]+)$/;
var MATCH_WHITESPACE_REGEX = /^[^\s]+$/;
var precheckArgumentsAreNumbers = function(result, value, valueToCompare) {
var val = parseFloat(value);
if (isNaN(val)) {
result.isValid = false;
result.messages["default"] = LOCALE.maketext("The entered value, [_1], is not a number.", value);
return;
}
var valCmp = parseFloat(valueToCompare);
if (isNaN(valCmp)) {
result.isValid = false;
result.messages["default"] = LOCALE.maketext("The compare-to value, [_1], is not a number.", valueToCompare);
return;
}
return {
val: val,
valCmp: valCmp
};
};
var validators = {
/**
* Validates the input is equal the specified string
*
* @method stringEqual
* @param {String} val Text to validate
* @param {String} valueToCompare Value to compare against
* @return {Object} Validation result
*/
stringEqual: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
if (typeof (value) === "string" && value !== valueToCompare) {
result.isValid = false;
result.add("stringEqual", LOCALE.maketext("The text you have entered is not equal to “[_1]”.", valueToCompare));
return result;
}
return result;
},
/**
* Validates the input is equal the specified string (ignoring case)
*
* @method stringEqualIgnoreCase
* @param {String} val Text to validate
* @param {String} valueToCompare Value to compare against
* @return {Object} Validation result
*/
stringEqualIgnoreCase: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
if (typeof (value) === "string" && value.toLowerCase() !== valueToCompare.toLowerCase()) {
result.isValid = false;
result.add("stringEqualIgnoreCase", LOCALE.maketext("The text you have entered is not equal to “[_1]”.", valueToCompare));
return result;
}
return result;
},
/**
* Validates the input is equal the specified number
*
* @method numEqual
* @param {String} val Text to validate
* @param {Number} valueToCompare Value to compare against
* @return {Object} Validation result
*/
numEqual: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
var args = precheckArgumentsAreNumbers(result, value, valueToCompare);
if (!args) {
return result;
}
var val = args.val;
var valCmp = args.valCmp;
if (val !== valCmp) {
result.isValid = false;
result.add("numEqual", LOCALE.maketext("The number you have entered is not equal to [numf,_1].", valueToCompare));
return result;
}
return result;
},
/**
* Validates the input is less than a specified number
*
* @method numLessThan
* @param {String} val Text to validate
* @param {Number} valueToCompare Value to compare against
* @return {Object} Validation result
*/
numLessThan: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
var args = precheckArgumentsAreNumbers(result, value, valueToCompare);
if (!args) {
return result;
}
var val = args.val;
var valCmp = args.valCmp;
if (val >= valCmp) {
result.isValid = false;
result.add("numLessThan", LOCALE.maketext("The number should be less than [numf,_1].", valueToCompare));
return result;
}
return result;
},
/**
* Validates the input is less than or equal to a specified number
*
* @method numLessThanEqual
* @param {String} val Text to validate
* @param {Number} valueToCompare Value to compare against
* @return {Object} Validation result
*/
numLessThanEqual: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
var args = precheckArgumentsAreNumbers(result, value, valueToCompare);
if (!args) {
return result;
}
var val = args.val;
var valCmp = args.valCmp;
if (val > valCmp) {
result.isValid = false;
result.add("numLessThanEqual", LOCALE.maketext("The number should be less than or equal to [numf,_1].", valueToCompare));
return result;
}
return result;
},
/**
* Validates the input is greater than a specified number
*
* @method numGreaterThan
* @param {String} val Text to validate
* @param {Number} valueToCompare Value to compare against
* @return {Object} Validation result
*/
numGreaterThan: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
var args = precheckArgumentsAreNumbers(result, value, valueToCompare);
if (!args) {
return result;
}
var val = args.val;
var valCmp = args.valCmp;
if (val <= valCmp) {
result.isValid = false;
result.add("numGreaterThan", LOCALE.maketext("The number should be greater than [numf,_1].", valueToCompare));
return result;
}
return result;
},
/**
* Validates the input is greater than or equal to a specified number
*
* @method numGreaterThanEqual
* @param {String} val Text to validate
* @param {Number} valueToCompare Value to compare against
* @return {Object} Validation result
*/
numGreaterThanEqual: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
var args = precheckArgumentsAreNumbers(result, value, valueToCompare);
if (!args) {
return result;
}
var val = args.val;
var valCmp = args.valCmp;
if (val < valCmp) {
result.isValid = false;
result.add("numGreaterThanEqual", LOCALE.maketext("The number should be greater than or equal to [numf,_1].", valueToCompare));
return result;
}
return result;
},
/**
* Validates that the input is a multiple of the given value
*
* @method numIsMultipleOf
* @param {String} val Text to validate
* @param {Number} valueToCompare Value to compare against
* @return {Object} Validation result
*/
numIsMultipleOf: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
var args = precheckArgumentsAreNumbers(result, value, valueToCompare);
if (!args) {
return result;
}
var valStr = "" + args.val;
var valCmpStr = "" + args.valCmp;
if (!IS_FRACTION_REGEXP.test(valStr)) {
valStr += ".0";
}
if (!IS_FRACTION_REGEXP.test(valCmpStr)) {
valCmpStr += ".0";
}
var valMatch = valStr.match(IS_FRACTION_REGEXP);
var valCmpMatch = valCmpStr.match(IS_FRACTION_REGEXP);
// To use the modulo (%) operator, both operands
// need to be integers, which means we need to
// shift the decimal place to the right until
// that’s true. First, find out how many decimal
// places we have to work with.
var digits_to_shift = Math.max(
valMatch[1].length,
valCmpMatch[1].length
);
for (var d = 0; d < digits_to_shift; d++) {
valStr += "0";
valCmpStr += "0";
}
// Now we do the actual bit shifting.
var replace_regexp = new RegExp("\\.([0-9]{" + digits_to_shift + "})");
valStr = valStr.replace(replace_regexp, "$1.");
valCmpStr = valCmpStr.replace(replace_regexp, "$1.");
if (parseInt(valStr, 10) % parseInt(valCmpStr, 10)) {
result.isValid = false;
result.add("numMultipleOf", LOCALE.maketext("The number must be an even multiple of [numf,_1].", valueToCompare));
return result;
}
return result;
},
/**
* Validates if the input contains certain invalid characters
*
* @method excludeCharacters
* @param {String} val Text to validate
* @param {String} valueToCompare Characters to compare
* @return {Object} Validation result
*/
excludeCharacters: function(value, chars) {
var result = UTILS.initializeValidationResult(),
excludeChars;
if (chars !== null) {
// convert chars into an array if it is not
if (_.isString(chars)) {
excludeChars = chars.split("");
} else {
excludeChars = chars;
}
var found = [];
for (var i = 0, len = excludeChars.length; i < len; i++) {
var chr = excludeChars[i];
if (value.indexOf(chr) !== -1) {
found.push(chr);
}
}
if (found.length > 0) {
result.isValid = false;
result.add("excludeCharacters", LOCALE.maketext("The value contains the following excluded characters, which are not allowed: [_1]", found.join()));
}
}
return result;
},
/**
* Ensures the input contains no spaces
*
* @method noSpaces
* @param {String} val Text to validate
* @return {Object} Validation result
*/
noSpaces: function(value) {
var result = UTILS.initializeValidationResult();
if (!value || value === "") {
return result;
}
result.isValid = MATCH_WHITESPACE_REGEX.test(value);
if (!result.isValid) {
result.add("noSpaces", LOCALE.maketext("The value contains spaces."));
}
return result;
},
/**
* Validates if the input is not equal to a specified string
*
* @method stringNotEqual
* @param {String} val Text to validate
* @param {String} valToCompare Text to compare against
* @return {Object} Validation result
*/
stringNotEqual: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
if (value === valueToCompare) {
result.isValid = false;
result.add("stringNotEqual", LOCALE.maketext("The text you have entered can not be equal to “[_1]”.", valueToCompare));
return result;
}
return result;
},
/**
* Validates if the input is not equal to a specified string (ignore case)
*
* @method stringNotEqualIgnoreCase
* @param {String} val Text to validate
* @param {String} valToCompare Text to compare against
* @return {Object} Validation result
*/
stringNotEqualIgnoreCase: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
// ISSUE: Won't work with localization like turkish
if (value.toLowerCase() === valueToCompare.toLowerCase()) {
result.isValid = false;
result.add("stringNotEqualIgnoreCase", LOCALE.maketext("The text that you entered cannot be equal to “[_1]”.", valueToCompare));
return result;
}
return result;
},
/**
* Validates if the input is not equal to a specified number
*
* @method numNotEqual
* @param {String} val Text to validate
* @param {Number} valToCompare Number to compare against
* @return {Object} Validation result
*/
numNotEqual: function(value, valueToCompare) {
var result = UTILS.initializeValidationResult();
var args = precheckArgumentsAreNumbers(result, value, valueToCompare);
if (!args) {
return result;
}
var val = args.val;
var valCmp = args.valCmp;
if (val === valCmp) {
result.isValid = false;
result.add("numNotEqual", LOCALE.maketext("The number you have entered can not be equal to [numf,_1].", valueToCompare));
return result;
}
return result;
},
};
// Generate a directive for each validation function
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(validators);
}
]);
return {
methods: validators,
name: "compare-validators",
description: "Validation library for comparison of values.",
version: 2.0,
};
});
/*
# datatype-validators.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
* This module is a collection of data-type validators
*
* @module datatype-validators
* @requires angular, validator-utils, validate, locale
*/
define('cjt/validator/datatype-validators',[
"angular",
"lodash",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/validator/validateDirectiveFactory"
],
function(angular, _, UTILS, LOCALE) {
"use strict";
var DOLLAR_AMOUNT_REGEXP = /^[0-9]+(?:\.[0-9]{1,2})?$/;
/**
* Validates the given value to see if it is a float number with the given
* precision value.
*
* @param {String} numberToValidate The value to be validated.
* @param {String} decimalPrecision The precision up to which the decimal places need to be validated.
* @param {Boolean} allowNegativeValues Whether it should validate negative numbers or not.
* @returns {Boolean} Validity of value true or false.
*/
function validateFloatNumbers(numberToValidate, decimalPrecision, allowNegativeValues) {
decimalPrecision = (_.isFinite(parseInt(decimalPrecision))) ? decimalPrecision : "";
var allowNegativeRegex = (allowNegativeValues) ? "-?" : "";
var precisionRegex = "{1," + decimalPrecision + "}";
var regex = new RegExp("^" + allowNegativeRegex + "\\d+(\\.\\d" + precisionRegex + ")?$");
return regex.test(numberToValidate);
}
var validators = {
/**
* Validates if the input is a digit
*
* @method digits
* @param {String} val Text to validate
* @return {Object} Validation result
*/
digits: function(val) {
var result = UTILS.initializeValidationResult();
var regExp = /^\d+$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("digits", LOCALE.maketext("The input should only contains numbers."));
return result;
}
return result;
},
/**
* Validates if the input is a dollar amount.
* Currently this does NOT accept either localized
* numbers (e.g., '12,45' in German) or thousands separators
* (e.g., '1,200' in English).
*
* @method digits
* @param {String} val Text to validate
* @return {Object} Validation result
*/
isDollarAmount: function(val) {
var result = UTILS.initializeValidationResult();
var isValid = (val !== "") && DOLLAR_AMOUNT_REGEXP.test(val);
if (!isValid) {
result.isValid = false;
result.add("isDollarAmount", LOCALE.maketext("The input should contain a dollar (USD) amount."));
return result;
}
return result;
},
/**
* Validates if the input is an integer
*
* @method integer
* @param {String} val Text to validate
* @return {Object} Validation result
*/
integer: function(val) {
var result = UTILS.initializeValidationResult();
var regExp = /^-?\d+$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("integer", LOCALE.maketext("The input should be a whole number."));
return result;
}
return result;
},
/**
* Validates if the input is a positive integer
*
* Validates that the input is a NONNEGATIVE integer. Note that this validator is,
* for historical reasons, misnamed; a “positive integer” validator should actually reject 0.
* Consider using `positiveOrZeroInteger` in all new code.
*
* @method positiveInteger
* @param {String} val Text to validate
* @param {String} message optional error message to report
* @return {Object} Validation result
*/
positiveInteger: function(val, message) {
var result = UTILS.initializeValidationResult();
var msg;
if (message) {
msg = message;
} else {
msg = LOCALE.maketext("The input should be a positive whole number.");
}
var regExp = /^\d+$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("positiveInteger", msg);
return result;
}
return result;
},
/**
* Validates if the input is a positive integer or 0
*
* @method positiveOrZeroInteger
* @param {String} val Text to validate
* @return {Object} Validation result
*/
positiveOrZeroInteger: function(val) {
var msg = LOCALE.maketext("The input should be zero or a positive whole number.");
return validators.positiveInteger(val, msg);
},
/**
* Validates if the input is a negative integer
*
* @method negativeInt
* @param {String} val Text to validate
* @return {Object} Validation result
*/
negativeInteger: function(val) {
var result = UTILS.initializeValidationResult();
var regExp = /^-\d+$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("negativeInteger", LOCALE.maketext("The input should be a negative whole number."));
return result;
}
return result;
},
/**
* Validates if the input is a float with the given decimal precision.
*
* @method float
* @param {String} val Text to validate
* @param {number} decimalPrecision number specifying the precision.
* @return {Object} Validation result
*/
float: function(val, decimalPrecision) {
var result = UTILS.initializeValidationResult();
var isValid = validateFloatNumbers(val, decimalPrecision, true);
if (!isValid) {
result.isValid = false;
result.add("float", LOCALE.maketext("The input must be a float number with up to [quant,_1, decimal place, decimal places].", decimalPrecision));
return result;
}
return result;
},
/**
* Validates if input is a positive float number with the given decimal precision.
*
* @method positiveFloat
* @param {String} val Text to validate
* @param {number} decimalPrecision number specifying the precision.
* @return {Object} Validation result
*/
positiveFloat: function(val, decimalPrecision) {
var result = UTILS.initializeValidationResult();
var isValid = validateFloatNumbers(val, decimalPrecision, false);
if (!isValid) {
result.isValid = false;
result.add("positiveFloat", LOCALE.maketext("The input must be a positive float number with up to [quant,_1, decimal place, decimal places].", decimalPrecision));
return result;
}
return result;
},
/**
* Validates if the input is a valid hex color
*
* @method hexColor
* @param {String} val Text to validate
* @return {Object} Validation result
*/
hexColor: function(val) {
var result = UTILS.initializeValidationResult();
var regExp = /^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
var isValid = val !== "" ? regExp.test(val) : false;
if (!isValid) {
result.isValid = false;
result.add("hexColor", LOCALE.maketext("The input should be a valid hexadecimal color (excluding the pound sign)."));
return result;
}
return result;
}
};
// Generate a directive for each validation function
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(validators);
}
]);
return {
methods: validators,
name: "datatype-validators",
description: "Validation library for integer, digit, and similar.",
version: 2.0,
};
});
/*
# ip-validators.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* This module has a collection of IP Address validators
*
* @module ip-validators
* @requires angular, validator-utils, validate, locale
*/
define('cjt/validator/ip-validators',[
"angular",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/util/inet6",
"cjt/validator/validateDirectiveFactory",
],
function(angular, UTILS, LOCALE, INET6) {
"use strict";
var POSITIVE_INTEGER = /^\d+$/;
var LEADING_ZEROES = /^0+[0-9]+$/;
var validators = {
/**
* Validate an IPv4 Address
*
* @method ipv4
* @param {string} str - A string representing an IP address
* @return {object} validation result
*/
ipv4: function(str) {
var result = UTILS.initializeValidationResult();
if (str === null || typeof str === "undefined") {
result.isValid = false;
result.add("ipv4", LOCALE.maketext("You must specify a valid [asis,IP] address."));
return result;
}
var chunks = str.split(".");
if (chunks.length !== 4 || chunks[0] === "0") {
result.isValid = false;
result.add("ipv4", LOCALE.maketext("You must specify a valid [asis,IP] address."));
return result;
}
for (var i = 0; i < chunks.length; i++) {
if (!POSITIVE_INTEGER.test(chunks[i])) {
result.isValid = false;
break;
}
if (chunks[i] > 255) {
result.isValid = false;
break;
}
// We need to account for leading zeroes, since those cause issues with BIND
// Check for leading zeroes and error out if the value is not just a zero
if (LEADING_ZEROES.test(chunks[i])) {
result.isValid = false;
break;
}
}
if (!result.isValid) {
result.add("ipv4", LOCALE.maketext("You must specify a valid [asis,IP] address."));
}
return result;
},
/**
* Validate an IPv6 Address
*
* @method ipv6
* @param {string} str - A string representing an IPv6 address
* @return {object} validation result
*/
ipv6: function(str) {
var result = UTILS.initializeValidationResult();
var check = INET6.isValid(str);
if (!check) {
result.isValid = false;
result.add("ipv6", LOCALE.maketext("You must specify a valid [asis,IP] address."));
return result;
}
return result;
},
/**
* Validate an IPv4 CIDR Range
*
* @method cidr4
* @param {string} str - A string representing an IPv6 address
* @return {object} validation result
*/
cidr4: function(str) {
var cidr = str.split("/");
var range = cidr[1], address = cidr[0];
var isOctetValid = function(rule, octet, name, result) {
if (!rule) {
return false;
} else if ( !Object.prototype.hasOwnProperty.call(rule, name) ) {
// octent can be anything
return true;
} else if (rule[name] === 0 && octet !== 0) {
result.add("cidr-details", LOCALE.maketext("In an [asis,IP] address like [asis,a.b.c.d], the “[_1]” octet must be the value 0 for this CIDR range.", name));
// octet must be 0
return false;
} else if ( rule[name].length && !rule[name].includes(octet)) {
result.add("cidr-details", LOCALE.maketext("In an [asis,IP] address like [asis,a.b.c.d], the “[_1]” octet must be one of the values in: [list_or,_2].", name, rule[name]));
// octet must be one of the list
return false;
} else if ( rule[name].max ) {
if (octet < rule[name].min || octet > rule[name].max) {
result.add("cidr-details", LOCALE.maketext("In an [asis,IP] address like [asis,a.b.c.d], the “[_1]” octet must be greater than or equal to “[_2]” and less than or equal to “[_3]”.", name, rule[name].min, rule[name].max));
// octet out of the allowed range
return false;
} else if (octet % rule[name].by !== 0) {
result.add("cidr-details", LOCALE.maketext("In an [asis,IP] address like [asis,a.b.c.d], the “[_1]” octet must be evenly divisible by “[_2]”.", name, rule[name].by));
// octet not evenly divisible
return false;
}
}
return true;
};
var result = this.ipv4(address);
if (result.isValid) {
// check the cidr range
if (range) {
if (range < 0 || range > 32) {
result.isValid = false;
result.add("cidr", LOCALE.maketext("You must specify a valid [asis,CIDR] range between 0 and 32."));
return result;
}
// Precalculate the validation rules
// CIDR Format:
//
// a.b.c.d/range
//
// Each record below defines the rules for a specific range
// For any octet slot (a,b,c,d) that underfined in the rule,
// any value is allowed.
var rules = {
32: {},
31: { d: { min: 0, max: 254, by: 2 } },
30: { d: { min: 0, max: 252, by: 4 } },
29: { d: { min: 0, max: 248, by: 8 } },
28: { d: { min: 0, max: 240, by: 16 } },
27: { d: { min: 0, max: 224, by: 32 } },
26: { d: [ 0, 64, 128, 192 ] },
25: { d: [ 0, 128 ] },
24: { d: 0 },
23: { d: 0, c: { min: 0, max: 254, by: 2 } },
22: { d: 0, c: { min: 0, max: 252, by: 4 } },
21: { d: 0, c: { min: 0, max: 248, by: 8 } },
20: { d: 0, c: { min: 0, max: 240, by: 16 } },
19: { d: 0, c: { min: 0, max: 224, by: 32 } },
18: { d: 0, c: [ 0, 64, 128, 192 ] },
17: { d: 0, c: [ 0, 128 ] },
16: { d: 0, c: 0 },
15: { d: 0, c: 0, b: { min: 0, max: 254, by: 2 } },
14: { d: 0, c: 0, b: { min: 0, max: 252, by: 4 } },
13: { d: 0, c: 0, b: { min: 0, max: 248, by: 8 } },
12: { d: 0, c: 0, b: { min: 0, max: 240, by: 16 } },
11: { d: 0, c: 0, b: { min: 0, max: 224, by: 32 } },
10: { d: 0, c: 0, b: [ 0, 64, 128, 192 ] },
9: { d: 0, c: 0, b: [ 0, 128 ] },
8: { d: 0, c: 0, b: 0 },
7: { d: 0, c: 0, b: 0, a: { min: 0, max: 254, by: 2 } },
6: { d: 0, c: 0, b: 0, a: { min: 0, max: 252, by: 4 } },
5: { d: 0, c: 0, b: 0, a: { min: 0, max: 248, by: 8 } },
4: { d: 0, c: 0, b: 0, a: { min: 0, max: 240, by: 16 } },
3: { d: 0, c: 0, b: 0, a: { min: 0, max: 224, by: 32 } },
2: { d: 0, c: 0, b: 0, a: [ 0, 64, 128, 192 ] },
1: { d: 0, c: 0, b: 0, a: [ 0, 128 ] },
0: { d: 0, c: 0, b: 0, a: 0 },
};
var octets = address.split(/\./); // a.b.c.d
var rule = rules[range];
var isValid = ["a", "b", "c", "d"].reduce(function(isValid, name, index) {
isValid = isValid && isOctetValid(rule, Number(octets[index]), name, result);
return isValid;
}, true);
if (!isValid) {
result.isValid = false;
result.add("cidr", LOCALE.maketext("The [asis,IP] address, [_1], in the [asis,CIDR] range is not supported for the range /[_2].", address, range));
}
} else {
result.isValid = false;
result.add("cidr", LOCALE.maketext("The [asis,CIDR] range must include a ‘/’ followed by the range."));
}
}
return result;
},
};
// Generate a directive for each validation function
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(validators);
},
]);
return {
methods: validators,
name: "ip-validators",
description: "Validation library for IP Addresses.",
version: 2.0,
};
});
/*
# domain-validators.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
* This module has a collection of domain validators
*
* @module domain-validators
* @requires angular, validator-utils, validate, locale
*/
define('cjt/validator/domain-validators',[
"lodash",
"angular",
"punycode",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/util/string",
"cjt/validator/ip-validators",
"cjt/util/idn",
"cjt/validator/validateDirectiveFactory",
],
function(_, angular, PUNYCODE, UTILS, LOCALE, STRING, IP_VALIDATORS, IDN) {
"use strict";
// IMPORTANT!!! You MUST pair use of these regexps with
// a check for disallowed characters. (cf. IDN)
var LABEL_REGEX_IDN = /^[a-zA-Z0-9\u0080-\uffff]([a-zA-Z0-9\u0080-\uffff-]*[a-zA-Z0-9\u0080-\uffff])?$/;
// Hostname labels are a subset of DNS labels. They may not include underscores. Interior hyphens are the only allowed special char.
var HOSTNAME_LABEL_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/; // Same as the 'label' regex in Cpanel::Validate::Domain::Tiny
// DNS labels may include underscores for DKIM and service records. Other special chars are not in common use.
var DNS_LABEL_REGEX = /^[\w]([\w-]*[\w])?$/;
var VALID_TLD_REGEX = /^[.][a-zA-Z0-9]+$/;
var VALID_IDN_TLD_REGEX = /^[.]xn--[a-zA-Z0-9-]+$/;
var URI_SPLITTER_REGEX = /(?:([^:/?#]+):)?(?:\/\/([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/;
var PORT_REGEX = /:(\d+)$/;
var ILLEGAL_URI_CHARS_REGEX = /[^a-z0-9:/?#[\]@!$&'()*+,;=._~%-]/i;
var INVALID_CHAR_ESCAPE = /%[^0-9a-f]/i;
var INCOMPLETE_CHAR_ESCAPE = /%[0-9a-f](:?[^0-9a-f]|$)/i;
var MAX_DOMAIN_BYTES = 254;
var MAX_LABEL_BYTES = 63;
/**
* Tests Punycoded entry does not exceed DNS octet limit
*
* @private
* @param {string} str - string to validate
* @param {object} result - validation result object to which validation errors will be added
* @return {boolean} validation result
*/
function _checkLength(input, result) {
// if entry does not include the trailing dot, add it
if (input && input.charAt(input.length - 1) !== ".") {
input = input + ".";
}
// check the maximum domain name length
if (input.length > MAX_DOMAIN_BYTES) {
result.addError("length", LOCALE.maketext("The domain or record name cannot exceed [quant,_1,character,characters].", MAX_DOMAIN_BYTES));
return false;
}
var punycoded = PUNYCODE.toASCII(input);
if (punycoded.length > MAX_DOMAIN_BYTES) {
var multiBytes = STRING.getNonASCII(input);
result.addError("length", LOCALE.maketext("The [asis,Punycode] representation of this domain or record name cannot exceed [quant,_1,character,characters]. (Non-[asis,ASCII] characters, like “[_2]”, require multiple characters to represent in [asis,Punycode].)", MAX_DOMAIN_BYTES, multiBytes[0]));
return false;
}
return true;
}
/**
* Tests entry includes at least 2 labels
*
* @private
* @param {array} groups - array of labels to validate
* @param {object} result - validation result object to which validation errors will be added
* @return {boolean} validation result
*/
function _checkTwoLabels(groups, result) {
if (groups.length < 2) {
result.addError("labels", LOCALE.maketext("The domain name must include at least two labels."));
return false;
}
return true;
}
/**
* Validate DNS label meets min and max length requirements when Punycoded
*
* @private
* @param {string} str - label to validate
* @param {object} result - validation result object to which validation errors will be added
* @return {boolean} validation result
*/
function _checkLabelLength(str, result) {
if (str.length === 0) {
result.addError("labelLength", LOCALE.maketext("A [asis,DNS] label must not be empty."));
return false;
}
if (str.length > MAX_LABEL_BYTES) {
result.addError("labelLength", LOCALE.maketext("A [asis,DNS] label must not exceed [quant,_1,character,characters].", MAX_LABEL_BYTES));
return false;
}
if (PUNYCODE.toASCII(str).length > MAX_LABEL_BYTES) {
var multiBytes = STRING.getNonASCII(str);
result.addError("labelLength", LOCALE.maketext("The [asis,DNS] label’s [asis,Punycode] representation cannot exceed [quant,_1,byte,bytes]. (Non-[asis,ASCII] characters, like “[_2]”, require multiple characters to represent in [asis,Punycode].)", MAX_LABEL_BYTES, multiBytes[0]));
return false;
}
return true;
}
/**
* Validates that a string is not an IPV4 or IPV6 address
*
* @private
* @param {string} str - string to validate
* @param {object} result - validation result object to which validation errors will be added
* @return {boolean} validation result
*/
function _checkNotIP(str, result) {
var isIPV4 = IP_VALIDATORS.methods.ipv4(str).isValid;
var isIPV6 = IP_VALIDATORS.methods.ipv6(str).isValid;
if (isIPV4 || isIPV6) {
result.isValid = false;
result.add("ip", LOCALE.maketext("The domain or record name cannot be [asis,IPv4] or [asis,IPv6]."));
return false;
}
return true;
}
/**
* Tests validity of DNS label
* DNS labels, a superset of hostname labels, may include underscores
*
* @private
* @param {string} str - label to validate
* @param {object} result - validation result object to which validation errors will be added
* @return {boolean} validation result
*/
function _checkValidDNSLabel(str, result) {
var validLabel = DNS_LABEL_REGEX.test(str);
if (!validLabel) {
result.isValid = false;
result.add("dnsLabel", LOCALE.maketext("The [asis,DNS] label must contain only the following characters: [list_and,_1].", ["a-z", "A-Z", "0-9", "-", "_"]));
return false;
}
return true;
}
/**
* If a string ends in a period, this function removes that trailing period
*
* @private
* @param {string} str - string to convert
* @return {string} string with trailing period removed
*/
function _removeTrailingPeriod(str) {
if (str && str.charAt(str.length - 1) === ".") {
str = str.slice(0, -1);
}
return str;
}
var validators = {
/**
* Validate fully-qualified domain name, with or without
* a leading “*.”. IDNs are accepted and minimally validated.
*
* @method wildcardFqdnAllowTld
* @param {string} input Domain name
* @return {object} validation result
*/
wildcardFqdnAllowTld: function wildcardFqdnAllowTld(input) {
var result = UTILS.initializeValidationResult();
if (!input.length) {
result.addError("empty", LOCALE.maketext("You must enter a domain name."));
} else if (input[0] === ".") {
result.addError("form", LOCALE.maketext("A domain name cannot begin with “[_1]”.", "."));
} else if (input[ input.length - 1 ] === ".") {
result.addError("form", LOCALE.maketext("A domain name cannot end with “[_1]”.", "."));
} else if (/\.\./.test(input)) {
result.addError("form", LOCALE.maketext("A domain name cannot contain two consecutive dots."));
} else if (/^[0-9.]+$/.test(input)) {
result.addError("form", LOCALE.maketext("A domain name cannot contain only numerals."));
// check the maximum domain name length
} else if (_checkLength(input, result)) {
var groups = input.split(".");
if (_checkTwoLabels(groups, result)) {
var tested = {};
groups.forEach( function(s, i) {
if (s === "*") {
if (i !== 0) {
result.addError("form", LOCALE.maketext("“[_1]” can appear only at the start of a wildcard domain name.", "*"));
}
} else if ( !tested[s] ) {
_labelAllowIdn(s, result);
}
tested[s] = true;
} );
}
}
return result;
},
/**
* Validate fully qualified (non-IDN) domain name
*
* @method fqdn
* @param {string} fqDomain Domain name
* @return {object} validation result
*/
fqdn: function(fqDomain) {
var result = UTILS.initializeValidationResult();
var groups = fqDomain.split(".");
var tldPart;
// check the domain and tlds
// must have at least one domain and tld
if (groups.length < 2) {
result.isValid = false;
result.add("oneDomain", LOCALE.maketext("The domain name must include at least two labels."));
return result;
}
// check the maximum domain name length
if (!_checkLength(fqDomain, result)) {
return result;
}
// check the first group for a valid domain
result = _label(groups[0]);
if (!result.isValid) {
return result;
}
// check the last group for a valid tld
tldPart = "." + groups[groups.length - 1];
result = _tld(tldPart);
if (!result.isValid) {
return result;
}
// check the remaining groups
for (var i = 1, length = groups.length - 1; i < length; i++) {
// every part in between must start with a letter/digit
// and end with letter/digits.
// You can have '-' in these parts, but
// only if they occur in between such characters.
result = _label(groups[i]);
if (!result.isValid) {
return result;
}
}
return result;
},
/**
* Validates that an FQDN contains at least 3 parts. Technically, you don't
* need 3 parts for an FQDN, but at cPanel that is often what we mean. That's
* why this validator is separate from the regular fqdn validator.
*
* @method threePartFqdn
* @param {String} fqdn The string to validate
* @return {Object} The validation result
*/
threePartFqdn: function(fqdn) {
var result = validators.fqdn(fqdn);
if (!result.isValid) {
return result;
}
var parts = fqdn.split(".");
if (parts.length < 3) {
result.isValid = false;
result.add("threeParts", LOCALE.maketext("A fully qualified domain name must contain at least 3 parts."));
}
return result;
},
/**
* Validate fully qualified domain name, but accept wildcards
*
* @method wildcardFqdn
* @param {string} fqDomain Domain name
* @return {object} validation result
*/
wildcardFqdn: function(fqDomain) {
var result = UTILS.initializeValidationResult();
var groups = fqDomain.split(".");
var tldPart;
// check the domain and tlds
// must have at least one domain and tld
if (groups.length < 2) {
result.isValid = false;
result.add("oneDomain", LOCALE.maketext("The domain name must include at least two labels."));
return result;
}
// check the maximum domain name length
if (fqDomain.length > MAX_DOMAIN_BYTES ) {
result.isValid = false;
result.add("length", LOCALE.maketext("The domain name cannot exceed [quant,_1,character,characters].", MAX_DOMAIN_BYTES));
return result;
}
// check the first group for a valid domain
if (groups.length > 2) {
// first can be wildcard or domain
if (groups[0] !== "*") {
// first must be domain
result = _label(groups[0]);
if (!result.isValid) {
return result;
}
}
} else {
// first must be domain
result = _label(groups[0]);
if (!result.isValid) {
return result;
}
}
// check the last group for a valid tld
tldPart = "." + groups[groups.length - 1];
result = _tld(tldPart);
if (!result.isValid) {
return result;
}
// check the remaining groups
for (var i = 1, length = groups.length - 1; i < length; i++) {
// every part in between must start with a letter/digit
// and end with letter/digits.
// You can have '-' in these parts, but
// only if they occur in between such characters.
result = _label(groups[i]);
if (!result.isValid) {
return result;
}
}
return result;
},
/**
* Validates a subdomain: http://<u>foo</u>.cpanel.net
*
* @method subdomain
* @param {string} str string to validate
* @return {object} Validation result
*/
subdomain: function(domainName) {
var result = UTILS.initializeValidationResult();
var groups = domainName.split(".");
// check each group
for (var i = 0, length = groups.length; i < length; i++) {
var str = groups[i];
result = _label(str);
if (!result.isValid) {
return result;
}
}
return result;
},
/**
* Validate URL (http or https)
*
* @param {String} url - a string that represents a URL
* @return {Object} validation result
*/
url: function(str) {
var result = UTILS.initializeValidationResult();
if (str === null || typeof str === "undefined") {
result.isValid = false;
result.add("url", LOCALE.maketext("You must specify a [asis,URL]."));
return result;
}
return _test_uri(str);
},
/**
* Validate fully qualified domain name for addon domains
*
* @method addonDomain
* @param {string} Addon Domain name
* @return {object} validation result
*/
addonDomain: function(fqDomain) {
var result = UTILS.initializeValidationResult();
var groups = fqDomain.split(".");
// check the domain and tlds
// must have at least one domain and tld
if (groups.length < 2) {
result.isValid = false;
result.add("domainLength", LOCALE.maketext("The domain name must include at least two labels."));
return result;
}
// check each group
for (var i = 0, length = groups.length; i < length; i++) {
var tldPart;
// the first entry must be a domain
if (i === 0) {
// Call to subdomain() is the only difference between fqdn() and addonDomain()
result = this.subdomain(groups[i]);
if (!result.isValid) {
return result;
}
} else if (i === groups.length - 1) { // the last entry must be a tld
tldPart = "." + groups[i];
result = _tld(tldPart);
if (!result.isValid) {
return result;
}
} else {
result = _label(groups[i]);
if (!result.isValid) {
return result;
}
}
}
return result;
},
/**
* Validate domain name (similar to hostname, but allowing underscores)
* Validates CNAME, DNAME, NAPTR records
*
* @method domainName
* @param {string} DNAME record
* @return {object} validation result
*/
domainName: function(str) {
var result = UTILS.initializeValidationResult();
var domainName = _removeTrailingPeriod(str);
// If string is empty, null, or undefined, exit early with validation error
if (!domainName) {
result.isValid = false;
result.add("domainName", LOCALE.maketext("You must specify a valid domain name."));
return result;
}
_checkLength(domainName, result);
_checkNotIP(domainName, result);
var chunks = domainName.split(".");
for (var i = 0; i < chunks.length; i++) {
_checkLabelLength(chunks[i], result);
_checkValidDNSLabel(chunks[i], result);
}
return result;
},
/**
* Validate hostname input
*
* @method hostname
* @param {string} hostname value
* @return {object} validation result
*/
hostname: function(str) {
var result = UTILS.initializeValidationResult();
var hostnameToValidate = _removeTrailingPeriod(str);
// If string is empty, null, or undefined, exit early with validation error
if (!hostnameToValidate) {
result.isValid = false;
result.add("hostname", LOCALE.maketext("You must specify a valid hostname."));
return result;
}
_checkLength(hostnameToValidate, result);
_checkNotIP(hostnameToValidate, result);
var chunks = hostnameToValidate.split(".");
for (var i = 0; i < chunks.length; i++) {
_label(chunks[i], result);
}
return result;
},
/**
* Validate a redirect url
* @method mbox
* @param {string} mbox value
* @return {Object} Validation result
*/
mbox: function(str) {
var result = UTILS.initializeValidationResult();
var mboxToValidate = _removeTrailingPeriod(str);
var mboxRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-+#]*[a-zA-Z0-9])?$/;
if (!mboxToValidate) {
result.isValid = false;
result.add("mbox", LOCALE.maketext("You must specify a valid [asis,mbox] name."));
return result;
}
var chunks = mboxToValidate.split(".");
// mbox name is an email address in domain format, with a "." in place of the "@"
// ex - cpanemail.cpanel.net
// for this regex, we are testing the first section of the mbox name - allowing it to contain "#" and "+" for plus addressing
// ex - cpan+email.cpanel.net
// ex - cpan#email.cpanel.net
// RFC for reference - https://tools.ietf.org/html/rfc5233
var mboxFirstChunk = mboxRegex.test(chunks[0]);
if (!mboxFirstChunk) {
result.isValid = false;
result.add("mbox", LOCALE.maketext("The first [asis,mbox] label must contain only the following characters: [list_and,_1]. The label cannot begin or end with a symbol.", ["a-z", "A-Z", "0-9", "-", "+", "#"]));
}
for (var i = 1; i < chunks.length; i++) {
_label(chunks[i], result);
}
return result;
},
/**
* Validate a redirect url
*
* @param {String} str - a url used for redirection
* @return {Object} Validation result
*/
redirectUrl: function(str) {
var result = UTILS.initializeValidationResult();
// grab the domain and tlds
var front_slashes = str.search(/:\/\//);
if (front_slashes) {
str = str.substring(front_slashes + 3);
}
// see if there is something after the last tld (path)
var back_slash = str.search(/\//);
if (back_slash === -1) {
back_slash = str.length;
}
var domain_and_tld = str.substring(0, back_slash);
if (domain_and_tld) {
result = this.fqdn(domain_and_tld);
}
return result;
},
/**
* Validate a DNS Zone Name
* This method attempts to match the validation in the Whostmgr::DNS module for DNS Names.
*
* @method zoneName
* @param {string} str - a zone name
* @return {object} validation result
*/
zoneName: function(str, type) {
var result = UTILS.initializeValidationResult();
var zoneNameToValidate = _removeTrailingPeriod(str);
// If string is empty, null, or undefined, exit early with validation error
if (!zoneNameToValidate) {
result.isValid = false;
result.add("zoneName", LOCALE.maketext("You must specify a valid zone name."));
return result;
}
// A and AAAA records can not contain an underscore, however other records can
if((type === "A" || type === "AAAA")) {
var testStr = str.split(".");
for (var i = 0, len = testStr.length; i < len; i++) {
if(testStr[i].includes("_")) {
result.isValid = false;
result.add("zoneName", LOCALE.maketext("An “[_1]” record may not contain an underscore. Are you trying to create a “[asis,CNAME]”?", type));
return result;
};
};
};
_checkLength(zoneNameToValidate, result);
var chunks = zoneNameToValidate.split(".");
var firstChunkIsAnAsterisk = (chunks[0] === "*");
var i = 0;
if (firstChunkIsAnAsterisk) {
i = 1;
}
for (var len = chunks.length; i < len; i++) {
_checkLabelLength(chunks[i], result);
_checkValidDNSLabel(chunks[i], result);
}
return result;
},
/**
* Validate a DNS Zone Value that should be a FQDN, but not an IPv4 or IPv6 address
*
* @method zoneFqdn
* @param {string} str - a zone record value
* @return {object} validation result
*/
zoneFqdn: function(str) {
var result = UTILS.initializeValidationResult();
if (str === null || typeof str === "undefined") {
result.isValid = false;
result.add("zoneFqdn", LOCALE.maketext("You must specify a fully qualified domain name."));
return result;
}
// make sure it is not an ipv4 or ipv6 address
var validIpv4 = IP_VALIDATORS.methods.ipv4(str);
var validIpv6 = IP_VALIDATORS.methods.ipv6(str);
if (validIpv4.isValid || validIpv6.isValid) {
result.isValid = false;
result.add("zoneFqdn", LOCALE.maketext("The domain cannot be [asis,IPv4] or [asis,IPv6]."));
return result;
}
// finally, check if it is an fqdn
// The last trailing dot (.) is a DNS convention to identify whether a domain is qualified or not. In UI it is optional for the user to input it. The backend will add it before saving the dns record if it is not present.
// Ignoring it if present before validating further.
return validators.fqdn(str.replace(/\.$/, ""));
}
};
/**
* Validates a top level domain (TLD): .com, .net, .org, .co.uk, etc
* This function does not check against a list of TLDs. Instead it makes sure that the TLD is formatted correctly.
* TLD must begin with a period (.)
* This method attempts to match the functionality of the
* Cpanel::Validate::Domain::Tiny module.
*
* @method _tld
* @private
* @param {string} str string to be validated
* @return {object} validation result
*/
// XXX: A TLD can be multiple labels, but this function only accepts
// single-label TLDs.
function _tld(str) {
var result = UTILS.initializeValidationResult();
if (!VALID_TLD_REGEX.test(str) && !VALID_IDN_TLD_REGEX.test(str)) {
result.isValid = false;
result.add("tld", LOCALE.maketext("The domain name must include a valid [output,acronym,TLD,Top Level Domain]."));
return result;
}
return result;
}
function _labelBasics(str, result) {
if (!result) {
result = UTILS.initializeValidationResult();
}
// label name cannot be longer than 63 characters
if (str.length === 0) {
result.addError("length", LOCALE.maketext("A [asis,DNS] label must not be empty."));
} else if (str.length > MAX_LABEL_BYTES) {
result.addError("length", LOCALE.maketext("A [asis,DNS] label must not exceed [quant,_1,character,characters].", MAX_LABEL_BYTES));
} else if (str[0] === "-") {
result.addError("charCondition", LOCALE.maketext("A [asis,DNS] label must not begin with “[_1]”.", "-"));
} else if (str[ str.length - 1 ] === "-") {
result.addError("charCondition", LOCALE.maketext("A [asis,DNS] label must not end with “[_1]”.", "-"));
} else if ( PUNYCODE.toASCII(str).length > MAX_LABEL_BYTES ) {
var multiBytes = STRING.getNonASCII(str);
result.addError("length", LOCALE.maketext("The [asis,DNS] label’s [asis,Punycode] representation cannot exceed [quant,_1,byte,bytes]. (Non-[asis,ASCII] characters, like “[_2]”, require multiple characters to represent in [asis,Punycode].)", MAX_LABEL_BYTES, multiBytes[0]));
}
return result;
}
/**
* Validates a label: http://<u>cpanel</u>.net
* This method attempts to match the functionality of the
* Cpanel::Validate::Domain::Tiny module.
*
* @method _label
* @private
* @param {string} str string to validate
* @return {object} validation result
*/
function _label(str, result) {
result = _labelBasics(str, result);
// As long as the label starts with letters/digits
// and ends with letters/digits, you can have '-' in domain labels.
// Also, single character domain labels are ok.
if (!HOSTNAME_LABEL_REGEX.test(str)) {
result.addError("labelCharCondition", LOCALE.maketext("The [asis,DNS] label must contain only the following characters: [list_and,_1].", ["a-z", "A-Z", "0-9", "-"]));
}
return result;
}
function _labelAllowIdn(str, result) {
result = _labelBasics(str, result);
if (LABEL_REGEX_IDN.test(str)) {
// Only validate as an IDN if there is non-ASCII in there:
if (/[\u0080-\uffff]/.test(str)) {
var defects = IDN.getLabelDefects(str);
if (defects.length) {
result.addError("charCondition", defects.join(" "));
}
}
} else {
result.addError("charCondition", LOCALE.maketext("The [asis,DNS] label must contain only non-[asis,ASCII] characters and the following: [list_and,_1].", ["a-z", "A-Z", "0-9", "-"]));
}
return result;
}
/**
* Split a URI into its expected parts (scheme, authority, path)
*
* @private
* @param {String} str - a URI to be split
* @return {Array} An array of the parts of a URI
*/
function _split_uri(str) {
var matches = URI_SPLITTER_REGEX.exec(str);
// throw away the first result since it will always be populated by our regex
matches.splice(0, 1);
return matches;
}
/**
* Validate a uri using Data::Validate::URI::is_web_uri() as a reference.
* Note that the user info component (i.e. "username@password:" portion) is not supported by this validator.
*
* @private
* @param {String} str - check if this is a valid url
* @return {object} validation result
*/
function _test_uri(str) {
var result = UTILS.initializeValidationResult();
var scheme, authority, path, matches, lc_scheme, domain_valid;
// check for illegal characters
if (ILLEGAL_URI_CHARS_REGEX.test(str)) {
result.isValid = false;
result.add("url", LOCALE.maketext("A [asis,URL] must not contain illegal characters."));
return result;
}
// check for hex escapes that aren't complete
if (INVALID_CHAR_ESCAPE.test(str) || INCOMPLETE_CHAR_ESCAPE.test(str)) {
result.isValid = false;
result.add("url", LOCALE.maketext("A [asis,URL] must not contain invalid hexadecimal escaped characters."));
return result;
}
matches = _split_uri(str);
scheme = matches[0];
authority = matches[1];
path = matches[2] || ""; // normalize logic for path
if (typeof scheme === "undefined") {
result.isValid = false;
result.add("url", LOCALE.maketext("A [asis,URL] must contain a valid protocol."));
return result;
}
// We only check for http and https
lc_scheme = scheme.toLowerCase();
if (lc_scheme !== "http" && lc_scheme !== "https") {
result.isValid = false;
result.add("url", LOCALE.maketext("A [asis,URL] must contain a valid protocol."));
return result;
}
// fully-qualified URIs must have an authority section
if (typeof authority === "undefined" || authority.length === 0) {
result.isValid = false;
result.add("url", LOCALE.maketext("A [asis,URL] must contain a domain."));
return result;
}
// allow a port component, but extract it since we don't need it
authority = authority.replace(PORT_REGEX, "");
// check for a valid domain or an IPv4 address
// our fqdn validator allows ipv4 addresses, so no need to check for it explicitly
domain_valid = validators.fqdn(authority);
if (!domain_valid.isValid) {
return domain_valid;
}
result.isValid = true;
return result;
}
// Generate a directive for each validation function
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(validators);
}
]);
return {
methods: validators,
name: "domain-validators",
description: "Validation library for domain names.",
version: 2.0,
};
});
/*
# email-validators.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
* This module has a collection of email validators
*
* @module email-validators
* @requires angular, lodash, domain-validator, validator-utils, validate, locale
*/
define('cjt/validator/email-validator',[
"angular",
"lodash",
"cjt/validator/domain-validators",
"cjt/validator/validator-utils",
"cjt/validator/validateDirectiveFactory",
"cjt/util/locale"
],
function(angular, _, domainValidator, validationUtils, validate, LOCALE) {
"use strict";
var LOCAL_MAX_LENGTH = 64;
/**
* Check if a given email address is a sub-address of a reserved email address.
*
* @param {String} email Email that we are trying to figure out is a sub-account of the reserved email account passed.
* @param {String} reservedEmail Reserved email
* @return {Boolean} true is the email address is a sub account of the reserved email address, false otherwise.
*/
function _isSubAddressOf(email, reservedEmail) {
if (!/[^+]+[+][^+]*@.+/.test(email)) {
return false;
}
var emailParts = email.split("@");
var reservedEmailParts = reservedEmail.split("@");
if (emailParts[1] !== reservedEmailParts[1]) {
// can not be a subaddress since host is different
return false;
}
if (emailParts[0] === reservedEmailParts[0]) {
// can not be a subaddress since they are already equal
return false;
}
var emailBox = emailParts[0];
var reservedBox = reservedEmailParts[0];
var subAddressRegEx = new RegExp("^" + reservedBox + "[+].*$");
return subAddressRegEx.test(emailBox);
}
/**
* Check if the email is in the reserved list passed.
*
* @param {String} email
* @param {String|Array} reservedEmails
* @param {Boolean} subAddressesReserved
* @return {Boolean} true if the email is reserved, false if the email is not reserved.
*/
function _isEmailReserved(email, reservedEmails, subAddressesReserved) {
var result = validationUtils.initializeValidationResult();
if (!/^[^@]+@.+$/.test(email)) {
return result; // The email is not a valid email yet.
}
if (!angular.isDefined(subAddressesReserved)) {
subAddressesReserved = true;
} else {
subAddressesReserved = !!subAddressesReserved;
}
if (!reservedEmails) {
return result; // no reserved emails passed, nothing to do.
}
if ( angular.isString(reservedEmails)) {
reservedEmails = [ reservedEmails ];
}
if (!reservedEmails.length) {
return result; // no reserved emails passed, nothing to do.
}
for (var i = 0, l = reservedEmails.length; i < l; i++) {
var reservedEmail = reservedEmails[i];
if (!/^[^@]+@.+$/.test(reservedEmail)) {
return result; // The reserved email is not a valid email yet.
}
var isReserved = email === reservedEmail;
var isSubAddressOfReserved = subAddressesReserved && _isSubAddressOf(email, reservedEmail);
if ( isReserved || isSubAddressOfReserved ) {
var message;
if (subAddressesReserved && isSubAddressOfReserved) {
message = LOCALE.maketext(
"You must use an email address that does not exist in the following list: [list_or,_1], or a [asis,subaddress] of [quant,_2,this email address, one of these email addresses].",
reservedEmails,
reservedEmails.length
);
} else {
message = LOCALE.maketext(
"You must use an email address that does not exist in the following list: [list_or,_1].",
reservedEmails
);
}
result.addError(
"reservedEmail",
message
);
break; // We do not need to check on every one if one fails.
}
}
return result;
}
var emailValidators = {
/**
* Validates an email address
*
* @method email
* @param {String} val The input value
* @param {String} spec The spec to validate against (rfc or cpanel), defaults to "rfc".
* @return {Object} A ValidationResult object
*/
email: function(val, spec) {
spec = spec || "rfc";
if (!_.includes(["cpanel", "rfc"], spec)) {
throw new Error("Invalid spec passed to email() validator: " + spec + ".");
}
var result = validationUtils.initializeValidationResult();
if (val !== "") {
// split on the @ symbol
var groups = val.split("@");
// must be split into two at this point
if (groups.length !== 2) {
result.addError("twoParts", LOCALE.maketext("The email must contain a username and a domain.") );
return result;
}
var localPart = groups[0],
domainPart = groups[1];
result = _getUsernameResult(localPart, spec);
if (result.isValid) {
var fqdn = domainValidator.methods.fqdn;
result = fqdn(domainPart);
}
}
return result;
},
/**
* Validates that the email is not in the reserved list or a sub-address
* of any of the emails in the reserved list. Performs case insensitive
* matches. Assumes the <box>+<subbox>@<domain> style of sub addressing.
*
* @method emailNotReserved
* @param {String} email The email to be created or edited
* @param {String|Array} reservedEmails The alternative email.
* @return {Object} A ValidationResult object
*/
emailNotReservedIncludeSubAddresses: function(email, reservedEmails) {
return _isEmailReserved(email, reservedEmails, true);
},
/**
* Validates that the email is not in the reserved list. Performs case insensitive
* exact matches only.
*
* @method emailNotReserved
* @param {String} email The email to be created or edited
* @param {String|Array} reservedEmails The alternative email.
* @return {Object} A ValidationResult object
*/
emailNotReserved: function(email, reservedEmails) {
return _isEmailReserved(email, reservedEmails, false);
},
/**
* Validates the username (like the local part of an email) based on
* cpanel or rfc rules. A username is everything before the @ sign in
* a fully qualified username like "user2@domain.tld".
*
* The username in this instance should just be "user2".
*
* @method username
* @param {String} username The username to validate
* @param {String} spec The spec to validate against. The only
* valid values are "cpanel" and "rfc". Defaults to "rfc".
* @return {Object} result A ValidationResult object
*/
username: function(username, spec) {
spec = spec || "rfc";
if (!_.includes(["cpanel", "rfc"], spec)) {
throw "Invalid spec passed to email() validator: " + spec + ".";
}
return _getUsernameResult(username, spec);
},
};
/**
* A phrase map to associate short failure strings with localized phrases.
*/
var phrases = {
username: {
rfc: {
emptyString: LOCALE.maketext("You must enter a username."),
maxLength: LOCALE.maketext("The username cannot exceed [numf,_1] characters.", LOCAL_MAX_LENGTH),
invalidChars: LOCALE.maketext("The username can only contain the following characters: [asis,a-zA-Z0-9!#$%][output,asis,amp()][output,apos][asis,*+/=?^_`{|}~-]"),
atSign: LOCALE.maketext("Do not include the [asis,@] character or the domain name."),
startEndPeriod: LOCALE.maketext("The username cannot begin or end with a period."),
doublePeriod: LOCALE.maketext("The username cannot contain two consecutive periods.")
},
cpanel: {
emptyString: LOCALE.maketext("You must enter a username."),
maxLength: LOCALE.maketext("The username cannot exceed [numf,_1] characters.", LOCAL_MAX_LENGTH),
invalidChars: LOCALE.maketext("The username can only contain letters, numbers, periods, hyphens, and underscores."),
atSign: LOCALE.maketext("Do not include the [asis,@] character or the domain name."),
startEndPeriod: LOCALE.maketext("The username cannot begin or end with a period."),
doublePeriod: LOCALE.maketext("The username cannot contain two consecutive periods.")
}
}
};
/**
* Performs the username validation and returns a ValidationResult object.
* See emailValidators.username for more information on usernames.
*
* @method _getUsernameResult
* @private
* @param {String} username The username
* @param {String} spec The spec to validate against
* @return {Object} A ValidationResult object
*/
function _getUsernameResult(username, spec) {
var failures = _validateUsername(username, spec);
return _makeResult(failures, phrases.username[spec]);
}
/**
* Transforms a list of failures into a full ValidationResult object with
* corresponding messages added, using a phrase map. If the failure list
* is empty, no messages are added and a valid ValidationResult is returned.
*
* @method _makeResult
* @private
* @param {Array} failures A list of short failure strings
* @param {Object} phraseMap An object that maps short failure strings
* to localized, informative messages
* @return {Object} A ValidationResult object with zero, one,
* or multiple messages
*/
function _makeResult(failures, phraseMap) {
var result = validationUtils.initializeValidationResult();
failures.forEach(function(failureName) {
result.addError(failureName, phraseMap[failureName]);
});
return result;
}
/**
* Validates a username and returns a list of failures for any part of
* the username that don't comply with the specified validation rules.
* See emailValidators.username for more information on usernames.
*
* @method _validateUsername
* @private
* @param {String} username The username
* @param {String} spec The spec to validate against
* @return {Array} An array of short failure strings
*/
function _validateUsername(username, spec) {
// Initialize the parameters
var failures = [];
spec = spec || "rfc";
// If it's an empty string there's no sense in checking anything else.
// In Angular, validators don't process empty strings but leaving this
// here for any other utilities that might use it.
if (username === "") {
failures.push("emptyString");
return failures;
}
if (username.length > LOCAL_MAX_LENGTH) {
failures.push("maxLength");
}
// Validate the inputs
if (spec !== "cpanel" && spec !== "rfc") {
throw ("CJT2/validator/email-validator: invalid spec argument!");
}
// username must contain only these characters
var pattern;
if (spec === "rfc") {
pattern = new RegExp("[^.a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]");
} else if (spec === "cpanel") {
// This is the current set of chars allowed when creating a new cPanel sub-account
pattern = new RegExp("[^.a-zA-Z0-9_-]");
}
if (pattern.test(username) === true) {
failures.push("invalidChars");
}
if (username.indexOf("@") > -1) {
failures.push("atSign");
}
// If the username has '.' as the first or last character then it's not valid
if (username.charAt(0) === "." || username.charAt(username.length - 1) === ".") {
failures.push("startEndPeriod");
}
// If the username contains '..' then it's not valid
if (/\.\./.test(username) === true) {
failures.push("doublePeriod");
}
return failures;
}
// Register the directive
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(emailValidators);
}
]);
return {
methods: emailValidators,
name: "email-validators",
description: "Validation library for email addresses.",
version: 2.0,
};
});
/*
# length-validators.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
* This module has a collection of length validators
*
* @module length-validators
* @requires angular, lodash, validator-utils, validate, locale
*/
define('cjt/validator/length-validators',[
"angular",
"lodash",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/util/string",
"cjt/validator/validateDirectiveFactory",
],
function(angular, _, UTILS, LOCALE, STRING) {
"use strict";
var validators = {
/**
* Validates the length of the text
*
* @method length
* @param {String} val Text to validate
* @param {Number} length required length
* @return {Object} Validation result
*/
length: function(val, length) {
var result = UTILS.initializeValidationResult();
if (val.length !== length) {
result.isValid = false;
result.add("length", LOCALE.maketext("The length of the string should be [quant,_1,character,characters,zero].", length));
return result;
}
return result;
},
/**
* Validates if the length of the text does not exceed a certain length
* @method maxLength
* @param {String} val Text to validate
* @param {Number} maxLength The maximum length of the input value
* @return {Object} Validation result
*/
maxLength: function(val, maxLength) {
var result = UTILS.initializeValidationResult();
if (val.length > maxLength) {
result.isValid = false;
result.add("maxLength", LOCALE.maketext("The length of the string cannot be greater than [quant,_1,character,characters].", maxLength));
return result;
}
return result;
},
maxUTF8Length: function(val, maxLength) {
// Give the simplest error message possible.
// Any string whose UCS-2 character count exceeds maxLength
// will always exceed the UTF-8 byte limit as well.
// In that case let’s use the simpler error message.
//
if (val.length > maxLength) {
return validators.maxLength(val, maxLength);
}
// We’re not out of the woods yet. If I have, e.g., 200 “é”
// characters, that’s 400 bytes of UTF-8, which we need
// to reject. Unfortunately more technical lingo is necessary
// to describe this problem. :(
var result = UTILS.initializeValidationResult();
if (STRING.getUTF8ByteCount(val) > maxLength) {
result.isValid = false;
// We got here because even though val.length is under
// our numeric limit, the actual UTF-8 byte count
// exceeds that limit. This is a tricky thing to explain
// concisely in non-technical terms.
result.add("maxUTF8Length", LOCALE.maketext("This string is too long or complex. Shorten it, or replace complex (non-[asis,ASCII]) characters with simple ([asis,ASCII]) ones. (The string’s [asis,UTF-8] encoding cannot exceed [quant,_1,byte,bytes].)", maxLength));
}
return result;
},
/**
* Validates if the input has a minimum length
*
* @method minLength
* @param {String} val Text to validate
* @param {Number} minLength The minimum length of the input value
* @return {Object} Validation result
*/
minLength: function(val, minLength) {
var result = UTILS.initializeValidationResult();
if (val.length < minLength) {
result.isValid = false;
result.add("minLength", LOCALE.maketext("The length of the string cannot be less than [quant,_1,character,characters].", minLength));
return result;
}
return result;
},
/**
* Validates if the minimum number of items are selected
*
* @method minSelect
* @param {String} val Text to validate
* @param {Number} minSelections The minimum number of selections needed
* @return {Object} Validation result
*/
minSelect: function(val, minSelections) {
var result = UTILS.initializeValidationResult(),
selected;
if (val !== null && _.isArray(val)) {
selected = val.length;
}
if (selected < minSelections) {
result.isValid = false;
result.add("minSelect", LOCALE.maketext("Select at least [quant,_1,item,items] from the list.", minSelections));
return result;
}
return result;
},
};
// Generate a directive for each validation function
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(validators);
},
]);
return {
methods: validators,
name: "length-validators",
description: "Validation library for length measurement of strings.",
version: 2.0,
};
});
/*
# path-validators.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
* This module has a collection of path validators
*
* @module path-validators
* @requires angular, lodash, validator-utils, validate, locale
*/
define('cjt/validator/path-validators',[
"angular",
"lodash",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/validator/validateDirectiveFactory"
],
function(angular, _, validationUtils, LOCALE) {
var INVALID_PATH_CHARS = ["\\", "*", "|", "\"", "<", ">"];
var INVALID_PATH_REGEX = new RegExp("[" + _.escapeRegExp(INVALID_PATH_CHARS.join("")) + "]");
var MAX_PATH_LENGTH = 255;
/**
* Validate document root
*
* @method validPath
* @param {string} document root path
* @return {object} validation result
*/
var pathValidators = {
validPath: function(val) {
var result = validationUtils.initializeValidationResult();
if (val === null || typeof val === "undefined") {
result.isValid = false;
result.add("path", LOCALE.maketext("You must specify a valid path."));
return result;
}
if (INVALID_PATH_REGEX.test(val)) {
result.isValid = false;
result.add("path",
LOCALE.maketext("The path cannot contain the following characters: [join, ,_1]",
INVALID_PATH_CHARS));
return result;
}
var folderNames = val.split("/");
if (folderNames && folderNames.length > 0) {
for (var i = 0, len = folderNames.length; i < len; i++) {
var name = folderNames[i];
if (name.length > MAX_PATH_LENGTH) {
result.isValid = false;
result.add("path",
LOCALE.maketext("Folder name is long by [quant,_1,byte,bytes]. The maximum allowed length is [quant,_2,byte,bytes].",
name.length - MAX_PATH_LENGTH,
MAX_PATH_LENGTH));
return result;
}
if (name === "." || name === "..") {
result.isValid = false;
result.add("path",
LOCALE.maketext("You cannot use the [list_and,_1] directories.", [".", ".."]));
return result;
}
}
}
return result;
}
};
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(pathValidators);
}
]);
return {
methods: pathValidators,
name: "path-validators",
description: "Validation library for paths.",
version: 2.0,
};
});
/*
# sql-data-validators.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
// TODO: Add tests for these
/**
* This module is a collection of sql validators
*
* @module sql-data-validators
* @requires angular, lodash, validator-utils, validate, locale
*/
define('cjt/validator/sql-data-validators',[
"angular",
"lodash",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/validator/validateDirectiveFactory"
],
function(angular, _, validationUtils, validate, LOCALE) {
var sqlValidators = {
/**
* Validates if the input is alpha numeric and
* can have underscore and hyphen
*
* @method sqlAlphaNumeric
* @param {String} val Text to validate
* @return {Object} Validation result
*/
sqlAlphaNumeric: function(val) {
var result = validationUtils.initializeValidationResult();
var regExp = /^[a-zA-Z0-9_-]+$/;
// string cannot be empty
if (val !== "") {
// string cannot contain a trailing underscore
if ((/_$/.test(val)) !== true) {
if (regExp.test(val) === true) {
result.isValid = false;
result.messages["regexRule"] = "The sql name should only contain alpha numeric _ and -";
return result;
}
} else {
result.isValid = false;
result.messages["trailingUnderscore"] = "The sql name can not contain a trailing underscore";
return result;
}
} else {
result.isValid = false;
result.messages["notEmpty"] = "The sql name can not be empty";
return result;
}
return result;
}
};
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(sqlValidators);
}
]);
});
/*
# username-validators.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* global define: false */
/* --------------------------*/
/**
* This module has a collection of username validators
*
* @module username-validators
* @requires angular, lodash, validator-utils, validate, locale
*/
define('cjt/validator/username-validators',[
"angular",
"lodash",
"cjt/validator/validator-utils",
"cjt/util/locale",
"cjt/validator/validateDirectiveFactory"
],
function(angular, _, validationUtils, LOCALE) {
"use strict";
var FTP_USERNAME_REGEX = "[^0-9a-zA-Z_-]",
FTP_USERNAME_MAX_LENGTH = 25;
var usernameValidators = {
/**
* Validate FTP username
*
* @method ftpUsername
* @param {string} userName FTP user name
* @return {object} validation result
*/
ftpUsername: function(val) {
var result = validationUtils.initializeValidationResult();
if (typeof (val) === "string") {
// username cannot be "FTP"
if (val.toLowerCase() === "ftp") {
result.isValid = false;
result.add("ftpUsername", LOCALE.maketext("User name cannot be “[_1]”.", "ftp"));
return result;
}
// username cannot be longer than CPANEL.v2.app.Constants.FTP_USERNAME_MAX_LENGTH
if (val.length > FTP_USERNAME_MAX_LENGTH) {
result.isValid = false;
result.add("ftpUsername", LOCALE.maketext("User name cannot be longer than [quant,_1,character,characters].", FTP_USERNAME_MAX_LENGTH));
return result;
}
// username must only contain these characters
var pattern = new RegExp(FTP_USERNAME_REGEX);
if (pattern.test(val) === true) {
result.isValid = false;
result.add("ftpUsername", LOCALE.maketext("The user name should only contain the following characters: [asis,a-zA-Z0-9-]."));
return result;
}
}
return result;
}
};
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(usernameValidators);
}
]);
return {
methods: usernameValidators,
name: "username-validators",
description: "Validation library for usernames.",
version: 2.0,
};
});
/*
# cjt/webAccessibilityChecker.js Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
(function() {
"use strict";
window.__accessibilityViolations = [];
window.getAccessibilityViolations = function() {
return window.__accessibilityViolations;
};
window.setAccessibilityViolations = function(violations) {
window.__accessibilityViolations = violations;
};
window.clearAccessibilityViolations = function() {
window.__accessibilityViolations = [];
};
window.runAccessibilityChecker = function(id) {
var context = id || window.AXE_CONFIG.context || document;
var options = window.AXE_CONFIG.options || {};
return window.axe.run(context, options)
.then(function(results) {
window.setAccessibilityViolations(results.violations);
return;
});
};
})();
define("cjt/webAccessibilityChecker", function(){});
define("cjt2.cpanel.cmb", function(){});
}());