Viewing File: /usr/local/cpanel/whostmgr/docroot/templates/email_deliverability/index.cmb.js
/*
# email_deliverability/controllers/ROUTES.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(
'app/controllers/ROUTES',[
"cjt/core",
"cjt/util/locale",
],
function(CJT, LOCALE) {
"use strict";
/**
* @module ROUTES
*/
/** @static */
var ROUTES = [
{
"id": "listDomains",
"route": "/",
"hideTitle": true,
"controller": "ListDomainsController",
"controllerAs": "listDomains",
"templateUrl": CJT.buildFullPath("shared/js/email_deliverability/views/listDomains.ptt"),
"title": LOCALE.maketext("List Domains"),
"resolve": {
"initialDomains": ["DomainsService", function($service) {
return $service.fetchAll();
}],
},
},
{
"id": "manageDomain",
"route": "/manage",
"controller": "ManageDomainController",
"controllerAs": "manageDomain",
"templateUrl": CJT.buildFullPath("shared/js/email_deliverability/views/manageDomain.ptt"),
"hideTitle": true,
"title": LOCALE.maketext("Manage the Domain"),
"resolve": {
"initialDomains": ["DomainsService", function($service) {
return $service.fetchAll();
}],
},
"parentID": "listDomains",
},
{
"id": "manageDomainSPF",
"route": "/manage/spf",
"controller": "ManageDomainSPFController",
"controllerAs": "manageDomainSPF",
"templateUrl": CJT.buildFullPath("shared/js/email_deliverability/views/manageDomainSPF.ptt"),
"hideTitle": true,
"title": LOCALE.maketext("Customize an [output,abbr,SPF,Sender Policy Framework] Record"),
"resolve": {
"initialDomains": ["DomainsService", function($service) {
return $service.fetchAll();
}],
},
"parentID": "manageDomain",
},
{
"id": "manageDomainDKIM",
"route": "/manage/dkim",
"controller": "ManageDomainDKIMController",
"controllerAs": "manageDomainDKIM",
"templateUrl": CJT.buildFullPath("shared/js/email_deliverability/views/manageDomainDKIM.ptt"),
"hideTitle": true,
"title": LOCALE.maketext("View the [output,acronym,DKIM,DomainKeys Identified Mail] Private Key"),
"resolve": {
"initialDomains": ["DomainsService", function($service) {
return $service.fetchAll();
}],
},
"parentID": "manageDomain",
},
];
ROUTES.forEach(function addBreadcrumbs(ROUTE) {
ROUTE.breadcrumb = {
id: ROUTE.id,
name: ROUTE.title,
path: ROUTE.route,
parentID: ROUTE.parentID,
};
});
return ROUTES;
}
);
/*
# email_deliverability/controllers/main.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(
'app/controllers/route',[
"angular",
"app/controllers/ROUTES",
"cjt/directives/breadcrumbs",
"cjt/services/alertService",
],
function(angular, ROUTES) {
"use strict";
/**
* Controller for App-level Route Handling
*
* @module RouteController
*
* @memberof whm.emailDeliverability
*
* @param {Object} $scope angular scope
* @param {Object} $rootScope angular rootScope
* @param {Object} $location angular location Object
* @param {Object} $alertService cjt2 alertService
*
*/
var MODULE_NAMESPACE = "whm.emailDeliverability.controllers.route";
var MODULE_REQUIREMENTS = [];
var CONTROLLER_NAME = "RouteController";
var CONTROLLER_INJECTABLES = ["$scope", "$rootScope", "$location", "alertService", "PAGE"];
var CONTROLLER = function RouteController($scope, $rootScope, $location, $alertService, PAGE) {
/**
* Find a Route by the Path
*
* @private
*
* @method _getRouteByPath
* @param {String} path route to match against the .route property of the existing routes
*
* @returns {Object} route that matches the provided path
*
*/
function _getRouteByPath(path) {
var foundRoute;
$scope.routes.forEach(function(route, key) {
if (route.route === path) {
foundRoute = key;
}
});
return foundRoute;
}
/**
* Find a Tab / View by the ID
*
* @private
*
* @method _getTabByID
* @param {String} id id to match against the .route property of the existing routes
*
* @returns {Object} route that matches the provided path
*
*/
function _getTabByID(id) {
var parentTab;
$scope.routes.forEach(function(route) {
if (route.id === id) {
parentTab = route;
}
});
return parentTab;
}
/**
* Initiate the $rootScope listeners
*
* @private
*
* @method _init
*
*/
function _init() {
$rootScope.$on("$routeChangeStart", function() {
$scope.loading = true;
$alertService.clear("danger");
});
$rootScope.$on("$routeChangeSuccess", function(event, current) {
$scope.loading = false;
$scope.parentTab = null;
if (current) {
var currentRouteKey = _getRouteByPath(current.$$route.originalPath);
$scope.currentTab = $scope.routes[currentRouteKey];
$scope.activeTab = currentRouteKey;
if ($scope.currentTab) {
$scope.parentTab = _getTabByID($scope.currentTab.parentRoute);
}
}
});
$rootScope.$on("$routeChangeError", function() {
$scope.loading = false;
});
}
angular.extend($scope, {
activeTab: 0,
currentTab: null,
routes: ROUTES,
});
_init();
};
CONTROLLER_INJECTABLES.push(CONTROLLER);
var app = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
app.controller(CONTROLLER_NAME, CONTROLLER_INJECTABLES);
return {
class: CONTROLLER,
namespace: MODULE_NAMESPACE,
};
}
);
/*
# email_deliverability/services/Domain.class.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('shared/js/email_deliverability/services/Domain.class',[
"lodash",
], function(_) {
"use strict";
/**
* Class to contain domain object functions
*
* @property {Boolean} recordsLoaded whether setRecordsLoaded has been called
* @property {Boolean} recordsValid whether the current records make the domain valid
* @memberof cpanel.emailDeliverability
* @class
*/
var Domain = function Domain(domainObject) {
var self = this;
var _requiredArgs = ["domain"];
this.records = [];
this._recordDetails = {};
this.reloadingIn = 0;
this.nameservers = [];
this.zone = "";
this.recordsLoaded = false;
this.recordsValid = false;
this._recordTypesLoaded = [];
this._recordsTypesWithIssues = [];
this._suggestedRecords = {};
this._expectedMatch = {};
this.hasNSAuthority = false;
this.mailIP = {
version: null,
address: null,
};
this.hadAnyNSErrors = false;
/**
*
* Initiate the domain object
*
* @param {Object} domainObject object to process in construction
*/
function init(domainObject) {
Object.keys(domainObject).forEach(function(key) {
self[key] = domainObject[key];
});
self.protocol = "http";
self.isWildcard = self["domain"] && self["domain"].substr(0, 1) === "*";
_requiredArgs.forEach(function(reqArg) {
if (!self[reqArg]) {
throw new Error("“" + reqArg + "” is a required parameter.");
}
});
}
/**
* Add a DNS Record to the Domain
*
* @method
* @param {Object} record new record to be added to the domain
*
*/
function addRecord(record) {
if (_.isUndefined(record.recordType)) {
throw new Error("recordType must be defined on a record");
}
if (_.isUndefined(record.valid)) {
throw new Error("valid must be defined on a record of type “" + record.recordType + "” for domain “" + this.domain + "”");
}
this.records.push(record);
}
/**
* Updates the status and validity of the domain based on existing records
*
* @method
*
*/
function setRecordsLoaded(recordTypesLoaded) {
this._recordTypesLoaded = recordTypesLoaded;
this.recordsLoaded = true;
this._recordsTypesWithIssues = [];
recordTypesLoaded.forEach(function(recordType) {
var records = this.getRecords(recordType);
if (records.length !== 1 || !records[0].valid) {
this._recordsTypesWithIssues.push(recordType);
}
}, this);
var details = this.getRecordDetails();
Object.keys(details).forEach(function(recordType) {
if ( details[recordType].error ) {
this.hadAnyNSErrors = true;
}
}, this);
this.recordsValid = this._recordsTypesWithIssues.length === 0;
}
/**
*
* Get the record types that have been loaded into this Domain
*
* @returns {Array<String>} array of record types
*/
function getRecordTypesLoaded() {
return this._recordTypesLoaded;
}
/**
*
* Reset loaded records
*
*/
function resetRecordLoaded() {
this.recordsLoaded = false;
this.recordTypesLoaded = [];
this.reloadingIn = 0;
this.records = [];
this.hasNSAuthority = false;
this.recordsValid = false;
this._recordsTypesWithIssues = [];
this.hadAnyNSErrors = false;
}
/**
*
* Is a specific record type valid
*
* @param {String} recordType record type to check
* @returns {Boolean} is the record type valid
*/
function isRecordValid(recordType) {
return this._recordsTypesWithIssues.indexOf(recordType) === -1;
}
/**
* Get the record status for the domain
*
* @method
* @return {Array<Object>} an array of DNS records stored on the domain
*
*/
function getRecords(recordTypes) {
var records = this.records;
if (recordTypes) {
records = records.filter(function(record) {
if (recordTypes.indexOf(record.recordType) !== -1) {
return true;
}
return false;
});
}
return records;
}
/**
*
* Get the suggested valid record of a type for this domain
*
* @param {String} recordType record type to get the suggestion for
* @returns {Object} suggested record
*/
function getSuggestedRecord(recordType) {
if (this.recordsLoaded && this.isRecordValid(recordType)) {
return this.getCurrentRecord(recordType);
}
return this._suggestedRecords[recordType];
}
/**
*
* Set the suggested record for the domain
*
* @param {String} recordType record type to set the suggestion for
* @param {Object} recordObject record suggestion
*/
function setSuggestedRecord(recordType, recordObject) {
this._suggestedRecords[recordType] = recordObject;
}
/**
*
* Get the expected valid record of a type for this domain
*
* @param {String} recordType record type to get the suggestion for
* @returns {Object} expected record
*/
function getExpectedMatch(recordType) {
return this._expectedMatch[recordType];
}
/**
*
* Set the expected record for the domain
*
* @param {String} recordType record type to set the suggestion for
* @param {Object} recordObject record suggestion
*/
function setExpectedMatch(recordType, recordObject) {
this._expectedMatch[recordType] = recordObject;
}
function getCurrentRecord(recordType) {
var records = this.getRecords([recordType]);
var topRecord = records[0];
return topRecord ? topRecord.current : {};
}
/**
* Get a list of record types with issues
*
* @method
* @return {Array<String>} an array of record types stored on the domain
*
*/
function getRecordTypesWithIssues() {
return this._recordsTypesWithIssues;
}
/**
* Get a list of record types with NS errors
*
* @method
* @return {Array<String>} an array of record types that had NS errors
*/
function getRecordTypesWithNSErrors() {
var types = [];
Object.keys(this._recordDetails).forEach(function(recordType) {
if ( this._recordDetails[recordType].error ) {
types.push(recordType);
}
}, this);
return types;
}
/**
*
* Set the mail ip object for this domain
*
* @param {int} version IP Version 4 or Version 6
* @param {String} address IP Address
*/
function setMailIP(version, address) {
this.mailIP.version = version;
this.mailIP.address = address;
}
/**
*
* Get the mail IP object for the domain
*
* @returns {Object} object containing the .version and .address
*/
function getMailIP() {
return this.mailIP;
}
function recordHadNSError(recordType) {
return !!this._recordDetails[recordType].error;
}
this.init = init.bind(this);
this.addRecord = addRecord.bind(this);
this.setRecordsLoaded = setRecordsLoaded.bind(this);
this.getRecords = getRecords.bind(this);
this.isRecordValid = isRecordValid.bind(this);
this.getRecordTypesWithIssues = getRecordTypesWithIssues.bind(this);
this.getRecordTypesWithNSErrors = getRecordTypesWithNSErrors.bind(this);
this.getSuggestedRecord = getSuggestedRecord.bind(this);
this.setSuggestedRecord = setSuggestedRecord.bind(this);
this.getExpectedMatch = getExpectedMatch.bind(this);
this.setExpectedMatch = setExpectedMatch.bind(this);
this.resetRecordLoaded = resetRecordLoaded.bind(this);
this.setMailIP = setMailIP.bind(this);
this.getMailIP = getMailIP.bind(this);
this.getRecordTypesLoaded = getRecordTypesLoaded.bind(this);
this.getCurrentRecord = getCurrentRecord.bind(this);
this.recordHadNSError = recordHadNSError.bind(this);
this.init(domainObject);
};
_.assign(
Domain.prototype,
{
setRecordDetails: function(rtype, details) {
// sanity check - enforce lower-case
if (!/^[a-z]+$/.test(rtype)) {
throw new Error("Invalid record type: " + rtype);
}
this._recordDetails[rtype] = details;
},
getRecordDetails: function(rtype) {
if ( !rtype ) {
return this._recordDetails;
}
if (!this._recordDetails.hasOwnProperty(rtype)) {
throw new Error("No stored details: " + rtype);
}
return this._recordDetails[rtype];
},
}
);
return Domain;
}
);
/*
# email_deliverability/services/domainFactory.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('shared/js/email_deliverability/services/domainFactory',[
"lodash",
"shared/js/email_deliverability/services/Domain.class",
], function(_, Domain) {
"use strict";
/**
* Factory for Creating Domain based on type
*
* @module DomainFactory
* @memberof cpanel.emailDeliverability
*
*/
var DOMAIN_TYPE_CONSTANTS = {
SUBDOMAIN: "subdomain",
ADDON: "addon",
ALIAS: "alias",
MAIN: "main_domain",
};
/**
* Creates a Domain
*
* @method
* @return {Domain}
*/
function DomainFactory(mainHomedir, mainDomain) {
this._mainHomedir = mainHomedir;
this._mainDomain = mainDomain;
/**
*
* Clean a raw API object
*
* @param {Object} rawDomain raw API Object
* @returns {Object} cleaned object
*/
function _cleanRaw(rawDomain) {
rawDomain.homedir = this._mainHomedir;
rawDomain.documentRoot = rawDomain.documentRoot || rawDomain.dir;
rawDomain.rootDomain = rawDomain.rootDomain || rawDomain.rootdomain || this._mainDomain;
rawDomain.redirectsTo = rawDomain.status === "not redirected" ? null : rawDomain.status,
delete rawDomain.dir;
delete rawDomain.rootdomain;
delete rawDomain.status;
return rawDomain;
}
/**
* create a new Domain from a raw API result
*
* @method create
* @public
*
* @param {Object} rawDomain raw API domain object
* @returns {Domain} new Domain();
*/
function _create(rawDomain) {
var cleanedDomain = this._cleanRaw(rawDomain);
return new Domain(cleanedDomain);
}
/**
* Get the Domain Type Constants
*
* @method getTypeConstants
* @public
*
* @returns {Object} domain type constants
*/
function _getTypeConstants() {
return DOMAIN_TYPE_CONSTANTS;
}
/**
* Set the main home dir for the user
*
* @method setMainHomedir
* @public
*
* @param {String} homedir main home dir for the user
*/
function _setMainHomedir(homedir) {
this._mainHomedir = homedir;
}
/**
* Set the main domain for the user
*
* @method setMainDomain
* @public
*
* @param {String} domain main domain for the user
*/
function _setMainDomain(domain) {
this._mainDomain = domain;
}
/**
* Get the class for the Factory
*
* @method getClass
* @public
*
* @returns {Class} returns the Domain class
*/
function getClass() {
return Domain;
}
this._cleanRaw = _cleanRaw.bind(this);
this.create = _create.bind(this);
this.setMainHomedir = _setMainHomedir.bind(this);
this.setMainDomain = _setMainDomain.bind(this);
this.getTypeConstants = _getTypeConstants.bind(this);
this.getClass = getClass.bind(this);
}
return new DomainFactory();
}
);
// Copyright 2024 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.
define('shared/js/email_deliverability/services/DKIMRecordProcessor',[], function() {
"use strict";
/**
*
* @module DKIMRecordProcessor
* @memberof cpanel.emailDeliverability
*
*/
function DKIMRecordProcessor() {
/**
* Get the Validate API call for this Record Processor
*
* @method getValidateAPI
* @public
*
* @returns {Object} api call .module and .func
*/
function _getValidateAPI() {
return { module: "EmailAuth", func: "validate_current_dkims" };
}
/**
* Get the Install API call for this Record Processor
*
* @method getInstallAPI
* @public
*
* @returns {Object} api call .module and .func
*/
function _getInstallAPI() {
return { module: "EmailAuth", func: "enable_dkim" };
}
/**
* Process an Individual API result item
*
* @method processResultItem
* @public
*
* @param {Object} resultItem API result item
* @returns {Array<Object>} parsed records for the result item
*/
function _processResultItem(resultItem) {
var records = [];
if (resultItem) {
records = resultItem.records.map(this.parseRecord.bind(this));
}
return records;
}
/**
* Process the result items for an API
*
* @method processResultItems
* @public
*
* @param {Array<Object>} resultItems
* @returns {Array<Array>} processed records organized by domain
*/
function _processResultItems(resultItems) {
var domainRecords = [];
resultItems.forEach(function(resultItem) {
domainRecords.push(this.processResultItem(resultItem));
}, this);
return domainRecords;
}
/**
* Parse a record of this processor's type
*
* @method parseRecord
* @public
*
* @param {Object} record record object
* @returns validated and parsed record
*/
function _parseRecord(record) {
record.recordType = "dkim";
return this.validateState(record);
}
/**
* Validate a record of this processor's type
*
* @method validateState
* @public
*
* @param {Object} record record object
* @returns validated record
*/
function _validateState(record) {
record.valid = false;
switch (record.state) {
case "MISMATCH":
case "PERMFAIL":
break;
case "PASS":
case "VALID":
record.valid = true;
break;
}
return record;
}
/**
* Preprocess result items for consistent results during processResultItems call
*
* @method normalizeResults
* @public
*
* @param {Array<Object>} results unprocessed results
* @returns {Array<Object>} normalized results
*/
function _normalizeResults(results) {
results.data.forEach(function(resultItem) {
var recordName = resultItem.domain;
resultItem.records.forEach(function(record) {
record.current = {
name: recordName + ".",
value: record.current,
};
});
});
return results;
}
/**
* Generated an expected record for this processor type
*
* @method generateSuggestedRecord
* @public
*
* @param {Object} resultItem result item to generate a record for
* @returns {String} expected record string
*/
function _generateSuggestedRecord(resultItem) {
return { name: resultItem.domain + ".", value: resultItem.expected };
}
this.generateSuggestedRecord = _generateSuggestedRecord.bind(this);
this.normalizeResults = _normalizeResults.bind(this);
this.getValidateAPI = _getValidateAPI.bind(this);
this.getInstallAPI = _getInstallAPI.bind(this);
this.processResultItem = _processResultItem.bind(this);
this.processResultItems = _processResultItems.bind(this);
this.parseRecord = _parseRecord.bind(this);
this.validateState = _validateState.bind(this);
}
return new DKIMRecordProcessor();
});
// Copyright 2024 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited
define('shared/js/email_deliverability/services/DMARCRecordProcessor',[], function() {
"use strict";
/**
*
* @module DMARCRecordProcessor
* @memberof cpanel.emailDeliverability
*
*/
function DMARCRecordProcessor() {
/**
* Get the Validate API call for this Record Processor
*
* @method getValidateAPI
* @public
*
* @returns {Object} api call .module and .func
*/
function _getValidateAPI() {
return { module: "EmailAuth", func: "validate_current_dmarcs" };
}
/**
* Get the Install API call for this Record Processor
*
* @method getInstallAPI
* @public
*
* @returns {Object} api call .module and .func
*/
function _getInstallAPI() {
return { module: "EmailAuth", func: "apply_dmarc" };
}
/**
* Process an Individual API result item
*
* @method processResultItem
* @public
*
* @param {Object} resultItem API result item
* @returns {Array<Object>} parsed records for the result item
*/
function _processResultItem(resultItem) {
var records = [];
resultItem.record = [resultItem.record];
if (resultItem) {
records = resultItem.records.map(this.parseRecord.bind(this));
}
return records;
}
/**
* Process the result items for an API
*
* @method processResultItems
* @public
*
* @param {Array<Object>} resultItems
* @returns {Array<Array>} processed records organized by domain
*/
function _processResultItems(resultItems) {
var domainRecords = [];
resultItems.forEach(function(resultItem) {
domainRecords.push(this.processResultItem(resultItem));
}, this);
return domainRecords;
}
/**
* Parse a record of this processor's type
*
* @method parseRecord
* @public
*
* @param {Object} record record object
* @returns validated and parsed record
*/
function _parseRecord(record) {
record.recordType = "dmarc";
return this.validateState(record);
}
/**
* Validate a record of this processor's type
*
* @method validateState
* @public
*
* @param {Object} record record object
* @returns validated record
*/
function _validateState(record) {
record.valid = false;
switch (record.state) {
case "MALFORMED":
case "DKIM_SPF_ERROR":
case "DKIM_ERROR":
case "SPF_ERROR":
case "MISSING":
case "DNS_ERROR":
break;
case "VALID":
record.valid = true;
break;
}
return record;
}
/**
* Preporocess result items for consistent results during processResultItems call
*
* @method normalizeResults
* @public
*
* @param {Array<Object>} results unprocessed results
* @returns {Array<Object>} normalized results
*/
function _normalizeResults(results) {
results.data.forEach((resultItem) => {
var recordName = resultItem.domain;
resultItem.records = [{}];
resultItem.records.forEach((record) => {
record.current = {
name: recordName + ".",
value: resultItem.record,
};
record.state = resultItem.state;
});
});
return results;
}
/**
* Generated an expected record for this processor type
*
* @method generateSuggestedRecord
* @public
*
* @param {Object} resultItem result item to generate a record for
* @returns {String} expected record string
*/
function _generateSuggestedRecord(resultItem) {
return { name: resultItem.domain + ".", value: resultItem.suggested };
}
this.generateSuggestedRecord = _generateSuggestedRecord.bind(this);
this.normalizeResults = _normalizeResults.bind(this);
this.getValidateAPI = _getValidateAPI.bind(this);
this.getInstallAPI = _getInstallAPI.bind(this);
this.processResultItem = _processResultItem.bind(this);
this.processResultItems = _processResultItems.bind(this);
this.parseRecord = _parseRecord.bind(this);
this.validateState = _validateState.bind(this);
}
return new DMARCRecordProcessor();
});
/*
* email_deliverability/services/spfParser.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(
'shared/js/email_deliverability/services/spfParser',["lodash"],
function(_) {
"use strict";
var MechanismError = function MechanismError(message, type) {
this.name = "MechanismError";
this.message = message;
this.type = type || "warning";
this.stack = new Error().stack;
};
function domainPrefixCheck(name, pattern, term) {
var parts = term.match(pattern);
var value = parts[1];
if (!value) {
return null;
}
if (value === ":" || value === "/") {
throw new MechanismError("Blank argument for the " + name + " mechanism", "error");
}
// Value starts with ":" so it"s a domain
if (/^:/.test(value)) {
value = value.replace(/^:/, "");
}
return value;
}
function domainCheckNullable(name, pattern, term) {
return domainCheck(name, pattern, term, true);
}
function domainCheck(name, pattern, term, nullable) {
var value = term.match(pattern)[1];
if (!nullable && !value) {
throw new MechanismError("Missing mandatory argument for the " + name + " mechanism", "error");
}
if (value === ":" || value === "=") {
throw new MechanismError("Blank argument for the " + name + " mechanism", "error");
}
if (/^(:|=)/.test(value)) {
value = value.replace(/^(:|=)/, "");
}
return value;
}
MechanismError.prototype = Object.create(Error.prototype);
MechanismError.prototype.constructor = MechanismError;
var MECHANISMS = {
version: {
description: "The SPF record version",
pattern: /^v=(.+)$/i,
validate: function validate(r) {
var version = r.match(this.pattern)[1]; // NOTE: This test can never work since we force match it to spf1 in index.js
// if (version !== 'spf1') {
// throw new MechanismError(`Invalid version '${version}', must be 'spf1'`);
// }
return version;
},
},
all: {
description: "Always matches. It goes at the end of your record",
pattern: /^all$/i,
},
ip4: {
// ip4:<ip4-address>
// ip4:<ip4-network>/<prefix-length>
description: "Match if IP is in the given range",
pattern: /^ip4:(([\d.]*)(\/\d+)?)$/i,
validate: function validate(r) {
var parts = r.match(this.pattern);
var value = parts[1];
if (!value) {
throw new MechanismError("Missing or blank mandatory network specification for the 'ip4' mechanism.", "error");
}
return value;
},
},
ip6: {
// ip6:<ip6-address>
// ip6:<ip6-network>/<prefix-length>
description: "Match if IPv6 is in the given range",
pattern: /^ip6:((.*?)(\/\d+)?)$/i,
validate: function validate(r) {
var parts = r.match(this.pattern);
var value = parts[1];
if (!value) {
throw new MechanismError("Missing or blank mandatory network specification for the 'ip6' mechanism.", "error");
}
return value;
},
},
a: {
// a
// a/<prefix-length>
// a:<domain>
// a:<domain>/<prefix-length>
description: "Match if IP has a DNS 'A' record in given domain",
pattern: /a((:.*?)?(\/\d*)?)?$/i,
validate: function validate(r) {
return domainPrefixCheck("a", this.pattern, r);
},
},
mx: {
// mx
// mx/<prefix-length>
// mx:<domain>
// mx:<domain>/<prefix-length>
description: "",
pattern: /mx((:.*?)?(\/\d*)?)?$/i,
validate: function validate(r) {
return domainPrefixCheck("mx", this.pattern, r);
},
},
ptr: {
// ptr
// ptr:<domain>
description: "Match if IP has a DNS 'PTR' record within given domain",
pattern: /^ptr(:.*?)?$/i,
validate: function validate(r) {
return domainCheckNullable("ptr", this.pattern, r);
},
},
exists: {
pattern: /^exists(:.*?)?$/i,
validate: function validate(r) {
return domainCheck("exists", this.pattern, r);
},
},
include: {
description: "The specified domain is searched for an 'allow'",
pattern: /^include(:.*?)?$/i,
validate: function validate(r) {
return domainCheck("include", this.pattern, r);
},
},
redirect: {
description: "The SPF record for the value replaces the current record",
pattern: /redirect(=.*?)?$/i,
validate: function validate(r) {
return domainCheck("redirect", this.pattern, r);
},
},
exp: {
description: "Explanation message to send with rejection",
pattern: /exp(=.*?)?$/i,
validate: function validate(r) {
return domainCheck("exp", this.pattern, r);
},
},
};
var PREFIXES = {
"+": "Pass",
"-": "Fail",
"~": "SoftFail",
"?": "Neutral",
};
var versionRegex = /^v=spf1/i;
var mechanismRegex = /(\+|-|~|\?)?(.+)/i; // * Values that will be set for every mechanism:
// Prefix
// Type
// Value
// PrefixDesc
// Description
function parseTerm(term, messages) {
// Match up the prospective mechanism against the mechanism regex
var parts = term.match(mechanismRegex);
var record = {}; // It matched! Let's try to see which specific mechanism type it matches
if (parts !== null) {
// Break up the parts into their pieces
var prefix = parts[1];
var mechanism = parts[2]; // Check qualifier
if (prefix) {
record.prefix = prefix;
record.prefixdesc = PREFIXES[prefix];
} else if (versionRegex.test(mechanism)) {
record.prefix = "v";
} else {
// Default to "pass" qualifier
record.prefix = "+";
record.prefixdesc = PREFIXES["+"];
}
var found = false;
for (var name in MECHANISMS) {
if (Object.prototype.hasOwnProperty.call(MECHANISMS, name)) {
var settings = MECHANISMS[name]; // Matches mechanism spec
if (settings.pattern.test(mechanism)) {
found = true;
record.type = name;
record.description = settings.description;
if (settings.validate) {
try {
var value = settings.validate.call(settings, mechanism);
if (typeof value !== "undefined" && value !== null) {
record.value = value;
}
} catch (err) {
if (err instanceof MechanismError) {
// Error validating mechanism
messages.push({
message: err.message,
type: err.type,
});
break;
} // else {
// throw err;
// }
}
}
break;
}
}
}
if (!found) {
messages.push({
message: "Unknown standalone term '".concat(mechanism, "'"),
type: "error",
});
}
}
return record;
}
function parse(record) {
// Remove whitespace
record = record.trim();
var records = {
mechanisms: [],
messages: [],
// Valid flag will be changed at end of function
valid: false,
};
if (!versionRegex.test(record)) {
// throw new Error();
records.messages.push({
message: "No valid version found, record must start with 'v=spf1'",
type: "error",
});
return records;
}
var terms = record.split(/\s+/); // Give an error for duplicate Modifiers
var duplicateMods = terms.filter(function(x) {
return new RegExp("=").test(x);
}).map(function(x) {
return x.match(/^(.*?)=/)[1];
}).filter(function(x, i, arr) {
return _.includes(arr, x, i + 1);
});
if (duplicateMods && duplicateMods.length > 0) {
records.messages.push({
type: "error",
message: "Modifiers like \"".concat(duplicateMods[0], "\" may appear only once in an SPF string"),
});
return records;
} // Give warning for duplicate mechanisms
var duplicateMechs = terms.map(function(x) {
return x.replace(/^(\+|-|~|\?)/, "");
}).filter(function(x, i, arr) {
return _.includes(arr, x, i + 1);
});
if (duplicateMechs && duplicateMechs.length > 0) {
records.messages.push({
type: "warning",
message: "One or more duplicate mechanisms were found in the policy",
});
}
try {
for (var i = 0; i < terms.length; i++) {
var term = terms[i];
var mechanism = parseTerm(term, records.messages);
if (mechanism) {
records.mechanisms.push(mechanism);
}
} // See if there's an "all" or "redirect" at the end of the policy
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
if (records.mechanisms.length > 0) {
// More than one modifier like redirect or exp is invalid
// if (records.mechanisms.filter(x => x.type === 'redirect').length > 1 || records.mechanisms.filter(x => x.type === 'exp').length > 1) {
// records.messages.push({
// type: 'error',
// message: 'Modifiers like "redirect" and "exp" can only appear once in an SPF string'
// });
// return records;
// }
// let lastMech = records.mechanisms[records.mechanisms.length - 1];
var redirectMech = _.find(records.mechanisms, function(x) {
return x.type === "redirect";
});
var allMech = _.find(records.mechanisms, function(x) {
return x.type === "all";
}); // if (lastMech.type !== "all" && lastMech !== "redirect") {
if (!allMech && !redirectMech) {
records.messages.push({
type: "warning",
message: 'SPF strings should always either use an "all" mechanism or a "redirect" modifier to explicitly terminate processing.',
});
} // Give a warning if "all" is not last mechanism in policy
var allIdx = -1;
records.mechanisms.forEach(function(x, index) {
if (x.type === "all" && allIdx === -1) {
allIdx = index;
}
});
if (allIdx > -1) {
if (allIdx < records.mechanisms.length - 1) {
records.messages.push({
type: "warning",
message: "One or more mechanisms were found after the \"all\" mechanism. These mechanisms will be ignored",
});
}
} // Give a warning if there"s a redirect modifier AND an "all" mechanism
if (redirectMech && allMech) {
records.messages.push({
type: "warning",
message: 'The "redirect" modifier will not be used, because the SPF string contains an "all" mechanism. A "redirect" modifier is only used after all mechanisms fail to match, but "all" will always match',
});
}
} // If there are no messages, delete the key from "records"
if (!Object.keys(records.messages).length > 0) {
delete records.messages;
}
records.valid = true;
return records;
}
return {
parse: parse,
parseTerm: parseTerm,
mechanisms: MECHANISMS,
prefixes: PREFIXES,
};
}
);
// Copyright 2024 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.
define('shared/js/email_deliverability/services/SPFRecordProcessor',[
"shared/js/email_deliverability/services/spfParser",
], function(SPFParser) {
"use strict";
function SPFRecordProcessor() {
/**
*
* @module SPFRecordProcessor
* @memberof cpanel.emailDeliverability
*
*/
/**
* Get the Validate API call for this Record Processor
*
* @method getValidateAPI
* @public
*
*
* @returns {Object} api call .module and .func
*/
function _getValidateAPI() {
return { module: "EmailAuth", func: "validate_current_spfs" };
}
/**
* Get the Install API call for this Record Processor
*
* @method getInstallAPI
* @public
*
*
* @returns {Object} api call .module and .func
*/
function _getInstallAPI() {
return { module: "EmailAuth", func: "install_spf_records" };
}
/**
* Process an Individual API result item
*
* @method processResultItem
* @public
*
* @param {Object} resultItem API result item
* @returns {Array<Object>} parsed records for the result item
*/
function _processResultItem(resultItem) {
var records = [];
if (resultItem) {
records = resultItem.records.map(this.parseRecord.bind(this));
}
return records;
}
/**
* Process the result items for an API
*
* @method processResultItems
* @public
*
* @param {Array<Object>} resultItems
* @returns {Array<Array>} processed records organized by domain
*/
function _processResultItems(resultItems) {
var domainRecords = [];
resultItems.forEach(function(resultItem) {
domainRecords.push(this.processResultItem(resultItem));
}, this);
return domainRecords;
}
/**
* Parse a record of this processor's type
*
* @method parseRecord
* @public
*
* @param {Object} record record object
* @returns validated and parsed record
*/
function _parseRecord(record) {
var currentName = record.current.name;
var currentValue = record.current.value;
var parsedRecord = this._parseSPF(currentValue);
parsedRecord.state = record.state;
parsedRecord.recordType = "spf";
parsedRecord.current = { name: currentName, value: currentValue };
parsedRecord = this.validateState(parsedRecord);
return parsedRecord;
}
/**
* Validate a record of this processor's type
*
* @method validateState
* @public
*
* @param {Object} record record object
* @returns validated record
*/
function _validateState(record) {
record.valid = false;
switch (record.state) {
case "SOFTFAIL":
case "PERMERROR":
break;
case "PASS":
case "VALID":
record.valid = true;
break;
}
return record;
}
/**
* Preporocess result items for consistent results during processResultItems call
*
* @method normalizeResults
* @public
*
* @param {Array<Object>} results unprocessed results
* @returns {Array<Object>} normalized results
*/
function _normalizeResults(results) {
results.data.forEach(function(resultItem) {
var recordName = resultItem.domain;
resultItem.records.forEach(function(record) {
record.current = {
name: recordName + ".",
value: record.current,
};
});
});
return results;
}
/**
* Combine a string record with new mechanisms and de-dupe
*
* @method generatedExpectedRecord
* @public
*
* @param {String} record record to combine with new mechanisms
* @param {Array<String>} mechanisms new mechanisms to inject into the record
* @returns {String} combined string record
*/
function _combineRecords(oldRecord, mechanisms) {
oldRecord = oldRecord || "";
var parsedRecord = this._parseSPF(oldRecord);
// de-dupe (remove instances of updated mechanisms in old record)
var mechanismsToRemove = [];
mechanisms.forEach(function(mechanism) {
parsedRecord.mechanisms.forEach(function(oldRecordMech, index) {
if (oldRecordMech.type === mechanism.type && oldRecordMech.value === mechanism.value) {
// Item with type and value exist, regardless of prefix, remove it.
mechanismsToRemove.push(index);
}
});
});
// Starting from the last index, remove all matching indexes
mechanismsToRemove = mechanismsToRemove.sort().reverse();
mechanismsToRemove.forEach(function(mechanismIndex) {
parsedRecord.mechanisms.splice(mechanismIndex, 1);
});
var finalRecordMechanisms = [];
// Add preceeding old mechanisms
var insertIndex = 0;
while (parsedRecord.mechanisms[insertIndex] && parsedRecord.mechanisms[insertIndex].type && parsedRecord.mechanisms[insertIndex].type.match(/^(version|ip|mx|a)$/)) {
finalRecordMechanisms.push(parsedRecord.mechanisms[insertIndex]);
insertIndex++;
}
// Add new mechanisms
mechanisms.forEach(function(mechanism) {
finalRecordMechanisms.push(mechanism);
});
// Add rest of old mechanisms
while (insertIndex < parsedRecord.mechanisms.length) {
finalRecordMechanisms.push(parsedRecord.mechanisms[insertIndex]);
insertIndex++;
}
// convert to a string
var newRecord = finalRecordMechanisms.map(function(mechanism) {
var mechanismString;
if (mechanism.type === "version") {
mechanismString = mechanism.prefix + "=" + mechanism.value;
} else {
mechanismString = mechanism.prefix + mechanism.type;
if (mechanism.value) {
mechanismString += ":" + mechanism.value;
}
}
return mechanismString;
}).join(" ");
// return the completed record
return newRecord;
}
/**
* Generated an expected record for this processor type
*
* @method generateSuggestedRecord
* @public
*
* @param {Object} resultItem result item to generate a record for
* @returns {String} expected record string
*/
function _generateSuggestedRecord(resultItem) {
var missingPieces = resultItem.expected || "";
var missingMechanisms = missingPieces.split(/\s+/).map(this._parseSPFTerm);
var suggestedRecord = "";
if (resultItem.records.length) {
var firstCurrent = resultItem.records[0].current;
suggestedRecord = this.combineRecords(firstCurrent.value, missingMechanisms);
} else {
suggestedRecord = this.combineRecords("v=spf1 +mx +a ~all", missingMechanisms);
}
return { name: resultItem.domain + ".", value: suggestedRecord, originalExpected: resultItem.expected };
}
this.generateSuggestedRecord = _generateSuggestedRecord.bind(this);
this.normalizeResults = _normalizeResults.bind(this);
this.getValidateAPI = _getValidateAPI.bind(this);
this.getInstallAPI = _getInstallAPI.bind(this);
this.processResultItem = _processResultItem.bind(this);
this.processResultItems = _processResultItems.bind(this);
this.parseRecord = _parseRecord.bind(this);
this.validateState = _validateState.bind(this);
this._parseSPF = SPFParser.parse.bind(SPFParser);
this._parseSPFTerm = SPFParser.parseTerm.bind(SPFParser);
this.combineRecords = _combineRecords.bind(this);
}
return new SPFRecordProcessor();
});
/*
# email_deliverability/services/PTRRecordProcessor 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('shared/js/email_deliverability/services/PTRRecordProcessor',[], function() {
"use strict";
function PTRRecordProcessor() {
/**
*
* @module PTRRecordProcessor
* @memberof cpanel.emailDeliverability
*
*/
/**
* Get the Validate API call for this Record Processor
*
* @method getValidateAPI
* @public
*
* @returns {Object} api call .module and .func
*/
function _getValidateAPI() {
return { module: "EmailAuth", func: "validate_current_ptrs" };
}
/**
* Get the Install API call for this Record Processor
*
* @method getInstallAPI
* @public
*
* @returns {Object} api call .module and .func
*/
function _getInstallAPI() {
throw new Error("ptr does not do install in this interface");
}
/**
* Process an Individual API result item
*
* @method processResultItem
* @public
*
* @param {Object} resultItem API result item
* @returns {Array<Object>} parsed records for the result item
*/
function _processResultItem(resultItem) {
var records = [];
if (resultItem) {
records = resultItem.records.map(this.parseRecord.bind(this));
}
return records;
}
/**
* Process the result items for an API
*
* @method processResultItems
* @public
*
* @param {Array<Object>} resultItems
* @returns {Array<Array>} processed records organized by domain
*/
function _processResultItems(resultItems) {
var domainRecords = [];
resultItems.forEach(function(resultItem) {
domainRecords.push(this.processResultItem(resultItem));
}, this);
return domainRecords;
}
/**
* Parse a record of this processor's type
*
* @method parseRecord
* @public
*
* @param {Object} record record object
* @returns validated and parsed record
*/
function _parseRecord(record) {
record.recordType = "ptr";
return this.validateState(record);
}
/**
* Validate a record of this processor's type
*
* @method validateState
* @public
*
* @param {Object} record record object
* @returns validated record
*/
function _validateState(record) {
record.valid = false;
switch (record.state) {
case "PASS":
case "VALID":
record.valid = true;
break;
}
return record;
}
/**
* Preporocess result items for consistent results during processResultItems call
*
* @method normalizeResults
* @public
*
* @param {Array<Object>} results unprocessed results
* @returns {Array<Object>} normalized results
*/
function _normalizeResults(results) {
var normalized = results.data.map( function(ptrEntry) {
var mailDomain = ptrEntry.domain;
var ptrRecords = ptrEntry.ptr_records.map( function(record) {
return {
current: {
name: ptrEntry.arpa_domain + ".",
value: record.domain,
},
domain: mailDomain,
state: record.state,
};
} );
return {
expected: ptrEntry.arpa_domain,
domain: mailDomain,
records: ptrRecords,
details: ptrEntry,
};
} );
return { data: normalized };
}
/**
* Generated an expected record for this processor type
*
* @method generateSuggestedRecord
* @public
*
* @param {Object} resultItem result item to generate a record for
* @returns {String} expected record string
*/
function _generateSuggestedRecord(resultItem) {
var expected = resultItem.expected;
return { name: expected + ".", value: "unknown" };
}
this.generateSuggestedRecord = _generateSuggestedRecord.bind(this);
this.normalizeResults = _normalizeResults.bind(this);
this.getValidateAPI = _getValidateAPI.bind(this);
this.getInstallAPI = _getInstallAPI.bind(this);
this.processResultItem = _processResultItem.bind(this);
this.processResultItems = _processResultItems.bind(this);
this.parseRecord = _parseRecord.bind(this);
this.validateState = _validateState.bind(this);
}
return new PTRRecordProcessor();
});
// Copyright 2024 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.
define(
'shared/js/email_deliverability/services/domains',[
"angular",
"lodash",
"cjt/util/locale",
"shared/js/email_deliverability/services/domainFactory",
"shared/js/email_deliverability/services/DKIMRecordProcessor",
"shared/js/email_deliverability/services/DMARCRecordProcessor",
"shared/js/email_deliverability/services/SPFRecordProcessor",
"shared/js/email_deliverability/services/PTRRecordProcessor",
"cjt/modules",
"cjt/services/alertService",
"cjt/modules",
"cjt/services/APICatcher",
],
function(angular, _, LOCALE, domainFactory, DKIMRecordProcessor, DMARCRecordProcessor, SPFRecordProcessor, PTRRecordProcessor) {
"use strict";
var MODULE_NAMESPACE = "shared.emailDeliverability.services.domains";
var SERVICE_NAME = "DomainsService";
var MODULE_REQUIREMENTS = [ "cjt2.services.apicatcher", "cjt2.services.alert" ];
var SERVICE_INJECTABLES = ["$q", "$log", "$interval", "alertService", "APIInitializer", "APICatcher", "DOMAIN_TYPE_CONSTANTS", "PAGE"];
// UAPI’s EmailAuth::validate_current_dkims returns the DKIM
// record name as “domain”. This function intends to strip
// the subdomain from that name to arrive at the domain name
// for which the record exists. For example,
// “newyork._domainkey.bob.com” becomes just “bob.com”.
function _STRIP_DKIM_SUBDOMAIN(d) {
return d.replace(/^.+\._domainkey\./, "");
}
var Zone = function(zoneName) {
this.zone = zoneName;
this.domains = [];
this.lockDomain = null;
};
Zone.prototype.addDomain = function addDomainToZone(domain) {
this.domains.push(domain);
};
// Returns a Domain object or null.
Zone.prototype.getLockDomain = function getLockDomain() {
return this.lockDomain;
};
// Requires a Domain object.
Zone.prototype.lock = function lockZone(domainObj) {
if (typeof domainObj !== "object") {
throw new Error("Give a domain object!");
}
if (this.lockDomain) {
throw new Error("Zone " + this.zone + " cannot lock for " + domainObj.domain + " because the zone is already locked for " + this.lockDomain + "!");
}
this.lockDomain = domainObj;
};
Zone.prototype.unlock = function unlockZone() {
this.lockDomain = null;
};
/**
*
* Service Factory to generate the Domains service
*
* @module DomainsService
* @memberof cpanel.emailDeliverability
*
* @param {ng.$q} $q
* @param {ng.$log} $log
* @param {Object} APICatcher base service
* @param {Object} DOMAIN_TYPE_CONSTANTS constants lookup of domain types
* @param {Object} PAGE window PAGE object created in template
* @returns {Domains} instance of the Domains service
*/
var SERVICE_FACTORY = function($q, $log, $interval, $alertService, $apiInit, APICatcher, DOMAIN_TYPE_CONSTANTS, PAGE) {
var _flattenedDomains;
var _domainLookupMap = {};
var _domainZoneMap = {};
var _zones = [];
var Domains = function() {};
Domains.prototype = Object.create(APICatcher);
/**
*
* Cache domains for ease of later lookup
*
* @private
*
* @param {Domain} domain to cache for later lookup
* @returns {Domain} stored domain
*/
Domains.prototype._cacheDomain = function _cacheDomain(domain) {
if (!_flattenedDomains) {
_flattenedDomains = [];
}
_domainLookupMap[domain.domain] = domain;
// Cache DKIM Key Domain too
_domainLookupMap["default._domainkey." + domain.domain] = domain;
// Store Domain in flattened domains
_flattenedDomains.push(domain);
return domain;
};
/**
*
* Remove a domain from the cache lookup
*
* @private
*
* @param {Domain} domain to remove from cache
* @returns {Boolean} success of domain removal
*/
Domains.prototype._uncacheDomain = function _uncacheDomain(domain) {
var self = this;
if (!_flattenedDomains) {
return false;
}
var domainObject = self._getDomainObject(domain);
for (var i = _flattenedDomains.length - 1; i >= 0; i--) {
if (_flattenedDomains[i].domain === domainObject.domain) {
_flattenedDomains.splice(i, 1);
return true;
}
}
return false;
};
/**
*
* Return a domain object. This is used to ensure you're always working with a Domain()
* even when a string might have been passed to a function.
*
* @public
*
* @param {string|Domain} wantedDomain - domain name or domain object
* @returns {Domain} matching wantedDomain
*/
Domains.prototype._getDomainObject = function _getDomainObject(wantedDomain) {
var self = this;
var domain = wantedDomain;
if (typeof domain === "string") {
domain = self.findDomainByName(domain);
}
if (domain instanceof domainFactory.getClass() !== true) {
$log.warn("Could not find domain “" + wantedDomain + "”");
}
return domain;
};
/**
*
* Find a domain using the domain name string
*
* @public
*
* @param {string} domainName - domain name to find
* @returns {Domain} matching domainName
*/
Domains.prototype.findDomainByName = function _findDomainByName(domainName) {
return _domainLookupMap[domainName];
};
/**
*
* Returns all currently loaded and stored domains
*
* @public
*
* @returns Array<Domain> returns a list of all loaded domains
*/
Domains.prototype.getAll = function getAllDomains() {
return _flattenedDomains;
};
/**
*
* API Wrapper to fetch the all domains (main, addon, subdomain, alias) and cache them
*
* @public
*
* @returns Promise<Array<Domain>>
*/
Domains.prototype.fetchAll = function fetchAllDomains() {
var self = this;
if (self.getAll()) {
return $q.resolve(self.getAll());
}
_flattenedDomains = [];
var domains = PAGE.domains || [];
domains.forEach(function(domain) {
if (domain.substring(0, 2) === "*.") {
return;
}
var domainObj = domainFactory.create({
domain: domain,
type: domain === PAGE.mainDomain ? DOMAIN_TYPE_CONSTANTS.MAIN : DOMAIN_TYPE_CONSTANTS.DOMAIN,
});
self._cacheDomain(domainObj);
});
return $q.resolve(self.getAll());
};
/**
*
* Extract Mail IPS from a validate_current_ptrs API Result
*
* @private
*
* @param {Object} results object from API with .data array
*/
Domains.prototype._extractMailIPs = function _extractMailIPs(results) {
var data = results.data;
data.forEach(function(ptrResult) {
var domain = this._getDomainObject(ptrResult.domain);
if (domain) {
domain.setMailIP(ptrResult.ip_version, ptrResult.ip_address);
}
}, this);
};
/**
* Find a domain zone by the domain name
*
* @param {String} zoneName name of the zone to find
* @returns {Zone}
*/
Domains.prototype.findZoneByName = function findZoneByName(zoneName) {
for (var i = 0; i < _zones.length; i++) {
if (_zones[i].zone === zoneName) {
return _zones[i];
}
}
return null;
};
/**
* Find domain zone by domain or domain name
*
* @param {Domain|String} domain
* @returns {Zone}
*/
Domains.prototype.findZoneByDomain = function findZoneByDomain(domain) {
var domainObj = this._getDomainObject(domain);
return _domainLookupMap[domainObj.domain];
};
/**
* Add a domain to an existing zone, or create it
*
* @private
*
* @param {Domain} domain
* @param {String} zone
*/
Domains.prototype._addDomainToZone = function _addDomainToZone(domain, zone) {
var zoneObj = this.findZoneByName(zone);
if (!zoneObj) {
zoneObj = new Zone(zone);
_zones.push(zoneObj);
}
domain.zone = zone;
zoneObj.addDomain(domain);
_domainZoneMap[domain.domain] = zoneObj;
};
/**
*
* Process the results of a has_ns_authority results item
*
* @private
*
* @param {Object} resultItem
* @returns {Domain} the updated Domain
*/
Domains.prototype._processNSAuthResultItem = function _processNSAuthResultItem(resultItem) {
var domainObj = this._getDomainObject(resultItem.domain);
if (domainObj) {
domainObj.soaError = resultItem.error || undefined;
domainObj.hasNSAuthority = resultItem.local_authority.toString() === "1";
domainObj.nameservers = resultItem.nameservers;
this._addDomainToZone(domainObj, resultItem.zone);
}
return domainObj;
};
/**
*
* Process a results item based on recordType and add records and suggested records to domains
*
* @private
*
* @param {Object} processor for handling recordType example services/DKIMRecordProcessor
* @param {Object} recordTypeResults api results for the specific api call {data:...}
* @param {string} recordType record type being processed
*/
Domains.prototype._processRecordTypeResults = function _processRecordTypeResults(processor, recordTypeResults, recordType) {
var normalizedResults = processor.normalizeResults(recordTypeResults);
if (recordType === "ptr") {
this._extractMailIPs(recordTypeResults);
}
var normalizedResultsData = normalizedResults.data;
var processedResults = processor.processResultItems(normalizedResultsData);
normalizedResultsData.forEach(function(normalizedResultItem, index) {
var domainObj = this._getDomainObject(normalizedResultItem.domain);
if (!domainObj) {
$log.debug("domain not found", normalizedResultItem);
return;
}
var domainRecords = processedResults[index];
var suggestedRecord = processor.generateSuggestedRecord(normalizedResultItem);
domainObj.setSuggestedRecord(recordType, suggestedRecord);
domainObj.setRecordDetails(recordType, normalizedResultItem.details || normalizedResultItem);
if (recordType === "spf") {
domainObj.setExpectedMatch(recordType, normalizedResultItem.expected);
}
domainRecords.forEach( domainObj.addRecord.bind(domainObj) );
}, this);
};
function _reportDKIMValidityCacheUpdates(recordTypeResults) {
var validityCacheUpdated = recordTypeResults.data.filter( function(item) {
return item.validity_cache_update === "set";
} );
if (validityCacheUpdated.length) {
var domains = validityCacheUpdated.map( function(item) {
return _.escape( _STRIP_DKIM_SUBDOMAIN(item.domain) );
} );
domains = _.sortBy( domains, function(d) {
return d.length;
} );
// “domains” shouldn’t be a long list. If it becomes
// so, we’ll need to chop this up so that it gives just
// a few domains and then says something like
// “… and N others”. That will avoid creating a big blob
// in what should normally be just an informative alert.
$alertService.add({
type: "info",
replace: false,
message: LOCALE.maketext("The system detected [quant,_1,domain,domains] whose [output,acronym,DKIM,DomainKeys Identified Mail] signatures were inactive despite valid [asis,DKIM] configuration. The system has automatically enabled [asis,DKIM] signatures for the following [numerate,_1,domain,domains]: [list_and_quoted,_2]", domains.length, domains),
});
}
}
/**
*
* Process the results of the fetchMailRecords API
*
* @private
*
* @param {Array} results batch api results [{data:...},{data:...}] where the first is the result of the has_ns_authority call, and the rest are recordType related
* @param {Array<String>} recordTypes record types requested during the fetchMailRecords API call
*/
Domains.prototype._processMailRecordResults = function _processMailRecordResults(results, recordTypes) {
var batchResultItems = results.data;
var nsAuthResult = batchResultItems.shift();
if (nsAuthResult.data) {
nsAuthResult.data.forEach(this._processNSAuthResultItem, this);
}
recordTypes = recordTypes ? recordTypes : this.getSupportedRecordTypes();
var processors = this.getRecordProcessors();
recordTypes.forEach(function(recordType, index) {
var recordTypeResults = batchResultItems[index];
var processor = processors[recordType];
this._processRecordTypeResults(processor, recordTypeResults, recordType);
if (recordType === "dkim") {
_reportDKIMValidityCacheUpdates(recordTypeResults);
}
}, this);
};
/**
*
* API Wrapper to fetch the status of all mail records for a set of domains
*
* @public
*
* @param {Array<Domain>} domains array of Domain() objects for which to fetch records
* @param {Array<String>} recordTypes array of record types for which to fetch records
* @returns {Promise<void>} returns the promise, but does not return an results object. That data is updated on the domain objects.
*/
Domains.prototype.fetchMailRecords = function fetchMailRecords(domains, recordTypes) {
var self = this;
if (domains.length === 0) {
return $q.resolve();
}
var start = new Date();
var startTime = start.getTime();
var apiCall = $apiInit.init("Batch", "strict");
var flatDomains = {};
domains.forEach(function(domain, index) {
return flatDomains["domain-" + index] = domain.domain;
});
var commands = [];
commands.push($apiInit.buildBatchCommandItem("DNS", "has_local_authority", flatDomains));
recordTypes = recordTypes ? recordTypes : this.getSupportedRecordTypes();
var processors = this.getRecordProcessors();
recordTypes.forEach(function(recordType) {
var processor = processors[recordType];
var apiName = processor.getValidateAPI();
commands.push($apiInit.buildBatchCommandItem(apiName.module, apiName.func, flatDomains));
}, this);
apiCall.addArgument("command", commands);
// If there’s an in-progress fetch already, then abort it
// because we know we don’t need its data.
if (this._fetchMailRecordsPromise) {
$log.debug("Canceling prior record status load");
this._fetchMailRecordsPromise.cancelCpCall();
}
this._fetchMailRecordsPromise = this.promise(apiCall);
return this._fetchMailRecordsPromise.then(function(result) {
var end = new Date();
var endTime = end.getTime();
$log.debug("Updating record statuses load took " + (endTime - startTime) + "ms for " + domains.length + " domains");
startTime = endTime;
self._processMailRecordResults(result, recordTypes);
}).finally(function() {
delete self._fetchMailRecordsPromise;
var end = new Date();
var endTime = end.getTime();
$log.debug("Updating record statuses parsing took " + (endTime - startTime) + "ms for " + domains.length + " domains");
});
};
/**
*
* Reset the records for a given domain, and poll for the records
*
* @public
*
* @param {Array<Domain>} domains array of domains for which to fetch records
* @param {Array<string>} recordTypes array of record types for which to fetch records
* @returns {Promise<void>} returns the promise, but does not return an results object. That data is updated on the domain objects.
*/
Domains.prototype.validateAllRecords = function validateAllRecords(domains, recordTypes) {
var self = this;
recordTypes = recordTypes ? recordTypes : self.getSupportedRecordTypes();
// Filter out domains already queued for a reload
domains = domains.filter(function(domain) {
if (domain.reloadingIn) {
return false;
}
return true;
});
domains.forEach(function(domain) {
domain.resetRecordLoaded();
}, self);
return self.fetchMailRecords(domains, recordTypes).then(function() {
domains.forEach(function(domain) {
domain.setRecordsLoaded(recordTypes);
}, self);
});
};
/**
*
* Returns a list of available processors for available record types. This is the key function for disabling record types.
*
* @public
*
* @returns {Object} returns object with keys representing record types, and value representing processor objects
*/
Domains.prototype.getRecordProcessors = function recordProcessors() {
var processors = {
"dkim": DKIMRecordProcessor,
"spf": SPFRecordProcessor,
"dmarc": DMARCRecordProcessor,
};
if ( PAGE.skipPTRLookups === undefined || !PAGE.skipPTRLookups ) {
processors.ptr = PTRRecordProcessor;
}
return processors;
};
/**
*
* Returns the currently supported record types.
*
* @public
*
* @returns {Array<strings>} array of supported record type strings
*/
Domains.prototype.getSupportedRecordTypes = function getSupportedRecordTypes() {
return Object.keys(this.getRecordProcessors());
};
/**
*
* Repair SPF records for a set of domains
*
* @public
*
* @param {Array<Domain>} domains array of domains to update records for
* @param {Array<string>} records new SPF record strings to update
* @returns <Promise<Object>> returns a promise and then an API results object
*/
Domains.prototype.repairSPF = function repairSPF(domains, records) {
var processors = this.getRecordProcessors();
var processor = processors["spf"];
var apiName = processor.getInstallAPI();
var flatDomains = domains.map(function(domain) {
return domain.domain;
});
var apiCall = $apiInit.init(apiName.module, apiName.func);
apiCall.addArgument("domain", flatDomains);
apiCall.addArgument("record", records);
return this.promise(apiCall);
};
/**
* Present a warning that the change may not be reflected
*
* @param {string} domain
*/
Domains.prototype.unreflectedChangeMessage = function unreflectedchangeMessage(domain) {
var zoneEditorURL = PAGE.zoneEditorUrl;
var message = "";
message += LOCALE.maketext("Because this is not an authoritative nameserver for the domain “[_1]”, the current or suggested records will not reflect your changes.", domain);
if (zoneEditorURL) {
message += " ";
message += LOCALE.maketext("Use the [output,url,_1,Zone Editor] to ensure that the system applied your changes.", zoneEditorURL + domain);
}
$alertService.add({
message: message,
type: "warning",
});
};
/**
*
* Repair DKIM records for a set of domains
*
* @public
*
* @param {Array<Domain>} domains array of domains to update records for
* @returns <Promise<Object>> returns a promise and then an API results object
*/
Domains.prototype.repairDKIM = function repairDKIM(domains) {
var processors = this.getRecordProcessors();
var processor = processors["dkim"];
var apiName = processor.getInstallAPI();
var flatDomains = domains.map(function(domain) {
return domain.domain;
});
var apiCall = $apiInit.init(apiName.module, apiName.func);
apiCall.addArgument("domain", flatDomains);
return this.promise(apiCall);
};
/**
*
* Repair DMARC records for a set of domains **current unsupported**
*
* @public
*
*/
Domains.prototype.repairDMARC = function repairDMARC(domain) {
throw new Error("Repairing DMARC Records are not currently supported in this interface.");
};
/**
*
* Repair PTR records for a set of domains **current unsupported**
*
* @public
*
*/
Domains.prototype.repairPTR = function repairPTR(domain) {
throw new Error("Installing PTR Records are not currently supported in this interface.");
};
/**
*
* Repair a record type for a single domain
*
* @public
*
* @param {Domain} domain domain to repair the record for
* @param {String} recordType record type to repair
* @param {String} newRecord new record, if setting a new record for the domain (SPF)
* @returns {Promise<Object>} returns a promise and then the API result object
*/
Domains.prototype.repairRecord = function repairRecord(domain, recordType, newRecord) {
if (recordType === "spf") {
return this.repairSPF([domain], [newRecord]);
} else if (recordType === "dkim") {
return this.repairDKIM([domain], [newRecord]);
} else if (recordType === "ptr") {
return this.repairPTR([domain], [newRecord]);
} else if (recordType === "dmarc") {
return this.repairDMARC([domain], [newRecord]);
}
};
/**
*
* Called on success of a record repair
*
* @private
*
* @param {Domain} domainObj domain updated during the repair call
* @param {String} recordType record type repaired during the repair call
* @param {String} record resulting updated record
*/
Domains.prototype._repairRecordSuccess = function _repairRecordSuccess(domainObj, recordType, record) {
$alertService.success({
replace: false,
message: LOCALE.maketext("The system updated the “[_1]” record for “[_2]” to the following: [_3]", recordType.toUpperCase(), _.escape(domainObj.domain), "<pre>" + _.escape(record) + "</pre>"),
});
};
/**
*
* Called on failure of a record repair
*
* @private
*
* @param {Domain} domainObj domain updated during the repair call
* @param {String} recordType record type repaired during the repair call
* @param {String} error
*/
Domains.prototype._repairRecordFailure = function _repairRecordSuccess(domainObj, recordType, error) {
$alertService.add({
type: "error",
replace: false,
message: LOCALE.maketext("The system failed to update the “[_1]” record for “[_2]” because of an error: [_3]", recordType.toUpperCase(), _.escape(domainObj.domain), _.escape(error)),
});
};
Domains.prototype._interval = function _interval(func, interval, count) {
return $interval(func, interval, count);
};
Domains.prototype._validateUntilSuccessComplete = function _validateUntilSuccessComplete(domainObj, successTypeRecords, waitAfter, startTime) {
var self = this;
$log.debug("[" + domainObj.domain + "] fetchMailRecords completed");
var recordTypes = self.getSupportedRecordTypes();
domainObj.setRecordsLoaded(recordTypes);
var someFailed = successTypeRecords.some(function(recordType) {
return !domainObj.isRecordValid(recordType);
});
var timePassed = (new Date().getTime() - startTime) / 1000;
$log.debug("[" + domainObj.domain + "] time passed since records set: " + timePassed);
// If not, double waitAfter and wait that long to check again
if (someFailed && timePassed < 120) {
$log.debug("[" + domainObj.domain + "] some failed, wait " + (waitAfter) + "s, and then try again.");
// Only bother alerting if it's been more than 5 seconds since the first call.
// A safety for the I/O issue found in COBRA-8775
if (timePassed > 5) {
$alertService.add({
type: "info",
replace: true,
message: LOCALE.maketext("The server records have not updated after [quant,_1,second,seconds]. The system will try again in [quant,_2,second,seconds].", Math.floor(timePassed), Math.floor(waitAfter)),
});
}
domainObj.resetRecordLoaded();
domainObj.reloadingIn = waitAfter;
return self._interval(function() {
domainObj.reloadingIn--;
}, 1000, waitAfter).then(function() {
domainObj.reloadingIn = 0;
waitAfter *= 2;
$log.debug("[" + domainObj.domain + "] done waiting, trying again.");
return self.validateUntilSuccess(domainObj, successTypeRecords, waitAfter, startTime);
});
} else {
if (!someFailed) {
$log.debug("[" + domainObj.domain + "] all records fixed.");
$alertService.success({
replace: true,
message: LOCALE.maketext("The system successfully updated the [asis,DNS] records."),
});
} else if (timePassed > 120) {
$log.debug("[" + domainObj.domain + "] more than 120s was taken to validate the change.");
$alertService.add({
type: "warning",
replace: true,
message: LOCALE.maketext("The system cannot verify that the record updated after 120 seconds."),
});
}
// If records are valid, we're done
domainObj.setRecordsLoaded(self.getSupportedRecordTypes());
}
};
Domains.prototype.validateUntilSuccess = function validateUntilSuccess(domain, successTypeRecords, waitAfter, startTime) {
var self = this;
var domainObj = this._getDomainObject(domain);
var recordTypes = self.getSupportedRecordTypes();
$log.debug("[" + domain.domain + "] begin validateUntilSuccess @" + (waitAfter) + "s");
return self.fetchMailRecords([domainObj], recordTypes).then(function() {
return self._validateUntilSuccessComplete(domainObj, successTypeRecords, waitAfter, startTime);
});
};
Domains.prototype.getDomainZoneObject = function getDomainZoneObject(domain) {
var domainObj = this._getDomainObject(domain);
return this.findZoneByName(domainObj.zone) || false;
};
Domains.prototype._repairDomainComplete = function _repairDomainComplete(result, domain, recordTypes, records, skipValidation) {
$log.debug("[" + domain.domain + "] beginning _repairDomainComplete.");
var self = this;
if (result.data) {
var recordsThatSucceeded = [];
result.data.forEach(function _processRecordResultObj(recordResultObj, index) {
var recordType = recordTypes[index];
var record = records[index];
var domainResultObj = recordResultObj.data[0];
$log.debug("[" + domainResultObj.domain + "] being parsed in _repairDomainComplete status: [" + domainResultObj.status + "].");
if (domainResultObj.status.toString() !== "1") {
this._repairRecordFailure(domain, recordType, domainResultObj.msg);
} else {
recordsThatSucceeded.push(recordType);
this._repairRecordSuccess(domain, recordType, record);
}
}, this);
if (skipValidation) {
$log.debug("[" + domain.domain + "] does not have NS Authority. Do not continue validation check");
return;
}
if (recordsThatSucceeded.length) {
$log.debug("[" + domain.domain + "] some records succeeeded.", recordsThatSucceeded.join(","));
var startTime = new Date().getTime();
return self.validateUntilSuccess(domain, recordsThatSucceeded, 5, startTime);
} else {
return self.validateAllRecords([domain]);
}
}
};
Domains.prototype.repairDomain = function repairDomain(domain, recordTypes, records, skipValidation) {
recordTypes = recordTypes.slice();
records = records.slice();
var self = this;
var domainObj = this._getDomainObject(domain);
// Lock the Zone
var zoneObj = this.findZoneByName(domainObj.zone);
zoneObj.lock(domainObj);
$log.debug("[" + domainObj.domain + "] locking zone: ", zoneObj.zone);
$log.debug("[" + domainObj.domain + "] beginning repairDomain.");
// Only reset it if we have auth, because otherwise it won't come back
if (!skipValidation) {
domainObj.resetRecordLoaded();
}
$log.debug("[" + domainObj.domain + "] resetting loaded records.");
// Start the Repair - Batch is serial, so this is fine to do
var apiCall = $apiInit.init("Batch", "strict");
var processors = self.getRecordProcessors();
var commands = recordTypes.map(function(recordType, index) {
var record = records[index];
var processor = processors[recordType];
var apiName = processor.getInstallAPI();
$log.debug("[" + domainObj.domain + "] adding batch command for “" + recordType + "”.");
if ( window.mixpanel ) {
window.mixpanel.track("Email-Deliverability-Repair-Record", { type: recordType });
}
if (recordType === "dmarc") {
return $apiInit.buildBatchCommandItem(apiName.module, apiName.func, { "domain": domainObj.domain, policy: record });
}
return $apiInit.buildBatchCommandItem(apiName.module, apiName.func, { "domain": domainObj.domain, record: record });
});
apiCall.addArgument("command", commands);
return self.promise(apiCall).then(function(result) {
$log.debug("[" + domainObj.domain + "] batch command completed.");
zoneObj.unlock();
return self._repairDomainComplete(result, domainObj, recordTypes, records, skipValidation);
});
};
/**
*
* Process the API result for fetchPrivateDKIMKey
*
* @private
*
* @param {Object} result API results object {data:...}
* @returns first result of API results.data array
*/
Domains.prototype._processGetPrivateDKIMKey = function _processGetPrivateDKIMKey(result) {
return result.data.pop();
};
/**
*
* Get the private DKIM key for a domain
*
* @public
*
* @param {Domain} domain domain to request the DKIM key for
* @returns {Promise<Object>} DKIM Key object {pem:...,domain:...}
*/
Domains.prototype.fetchPrivateDKIMKey = function fetchPrivateDKIMKey(domain) {
var domainObj = this._getDomainObject(domain);
var apiCall = $apiInit.init("EmailAuth", "fetch_dkim_private_keys");
apiCall.addArgument("domain", domainObj.domain);
return this.promise(apiCall).then(this._processGetPrivateDKIMKey);
};
/**
*
* Get the top most current record for a domain and record type
*
* @public
*
* @param {Domain} domain domain to get the record from
* @param {String} recordType record type to fetch the record for
* @returns {String} returns the string record or an empty string
*/
Domains.prototype.getCurrentRecord = function getCurrentRecord(domain, recordType) {
var domainObj = this._getDomainObject(domain);
return domainObj.getCurrentRecord(recordType);
};
Domains.prototype.getNoAuthorityMessage = function getNoAuthorityMessage(domainObj, recordType) {
var nameserversHtml = domainObj.nameservers.map( _.escape );
var message;
if (domainObj.nameservers.length) {
message = LOCALE.maketext("This system does not control [asis,DNS] for the “[_1]” domain.", domainObj.domain);
} else {
message = LOCALE.maketext("This system does not control [asis,DNS] for the “[_1]” domain and the system did not find any authoritative nameservers for this domain.", domainObj.domain);
}
if (recordType === "spf" || recordType === "dkim") {
// Handle differently for spf
message += " ";
message += "<strong>";
message += LOCALE.maketext("You can install the suggested “[_1]” record locally. However, this server is not the authoritative nameserver. If you install this record, this change will not be effective.", recordType.toUpperCase());
message += "</strong>";
}
message += " ";
if (domainObj.nameservers.length) {
message += LOCALE.maketext("Contact the person responsible for the [list_and_quoted,_3] [numerate,_2,nameserver,nameservers] and request that they update the “[_1]” record with the following:", recordType.toUpperCase(), domainObj.nameservers.length, nameserversHtml);
} else {
message += LOCALE.maketext("Contact your domain registrar to verify this domain’s registration.");
}
return message;
};
/**
*
* API Wrapper to obtain the helo record for a domain
*
* @param {Domain} domain domain to request the helo record for
* @returns {Promise} get_mail_helo_ip promise
*/
Domains.prototype.fetchMailHeloIP = function fetchMailHeloIP(domain) {
var domainObj = this._getDomainObject(domain);
var apiCall = $apiInit.init("EmailAuth", "get_mail_helo_ip");
apiCall.addArgument("domain", domainObj.domain);
return this.promise(apiCall).then(this._processMailHeloResult);
};
Domains.prototype.localDKIMExists = function localDKIMExists(domain) {
var recordType = "dkim";
if (domain.isRecordValid(recordType)) {
return true;
}
var record = domain.getSuggestedRecord(recordType);
if (record.value) {
return true;
}
return false;
};
Domains.prototype.ensureLocalDKIMKeyExists = function ensureLocalDKIMKeyExists(domain) {
var self = this;
var apiCall = $apiInit.init("EmailAuth", "ensure_dkim_keys_exist");
apiCall.addArgument("domain", domain.domain);
return self.promise(apiCall).then(function(results) {
var data = results.data;
var domainResult = data.pop();
if (domainResult.status.toString() === "1") {
return self.fetchMailRecords([domain]);
} else {
return $alertService.add({
type: "error",
message: _.escape(domainResult.msg),
});
}
});
};
// To be called whenever a view changes so that pending
// “load” API calls can be canceled or abandoned.
Domains.prototype.markViewLoad = function() {
if (this._fetchMailRecordsPromise) {
this._fetchMailRecordsPromise.cancelCpCall();
}
};
return new Domains();
};
SERVICE_INJECTABLES.push(SERVICE_FACTORY);
var app = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
app.value("PAGE", PAGE);
app.value("DOMAIN_TYPE_CONSTANTS", domainFactory.getTypeConstants());
app.factory(SERVICE_NAME, SERVICE_INJECTABLES);
return {
"class": SERVICE_FACTORY,
"namespace": MODULE_NAMESPACE,
};
}
);
/*
# email_deliverability/filters/htmlSafeString.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(
'shared/js/email_deliverability/filters/htmlSafeString',[
"angular",
"lodash",
],
function(angular, _) {
"use strict";
/**
* Wrapper for lodash escape
*
* @module htmlSafeString
* @memberof cpanel.emailDeliverability
*
* @example
* {{ domain.domain | htmlSafeString }}
*
*/
var MODULE_NAMESPACE = "shared.emailDeliverability.htmlSafeString.filter";
var MODULE_REQUIREMENTS = [ ];
var CONTROLLER_INJECTABLES = [];
var CONTROLLER = function CopyFieldController() {
return _.escape;
};
var module = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
module.filter("htmlSafeString", CONTROLLER_INJECTABLES.concat(CONTROLLER));
return {
"class": CONTROLLER,
"namespace": MODULE_NAMESPACE,
};
}
);
/*
# 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(
'app/decorators/paginationDecorator',[
"angular",
"cjt/core",
"cjt/util/locale",
"uiBootstrap",
],
function(angular, CJT, LOCALE) {
"use strict";
var module;
var MODULE_NAMESPACE = "cpanel.emailAccounts";
try {
module = angular.module(MODULE_NAMESPACE);
} catch (e) {
module = angular.module(MODULE_NAMESPACE, ["ui.bootstrap.pagination"]);
}
module.config(["$provide", function($provide) {
// Extend the ngModelDirective to interpolate its name attribute
$provide.decorator("uibPaginationDirective", ["$delegate", function($delegate) {
var TEMPLATE_PATH = "decorators/pagination.phtml";
var RELATIVE_PATH = "email_accounts/" + TEMPLATE_PATH;
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;
});
};
/**
* Update aria labels page collection
*
* @method updateIds
* @param {Array} pages
*/
var updateAriaLabel = function(pages) {
if (!pages) {
return;
}
pages.forEach(function(page) {
page.ariaLabel = LOCALE.maketext("Go to page “[_1]”.", page.text);
});
};
/**
* Update current selected text
*
* @method updateCurrentSelectedText
* @param {string} page - Current page number
* @param {string} totalPages - Total pages
* @returns {string} Text to display
*/
var updateCurrentSelectedText = function(page, totalPages) {
return LOCALE.maketext("Page [numf,_1] of [numf,_2]", page, totalPages);
};
// Use a local template
uiPaginationDirective.templateUrl = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH;
// Extend the page model with the id field.
var linkFn = uiPaginationDirective.link;
/**
* Compile function for uiPagination Directive
*
* @method uiPaginationDirective.compile
*/
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."),
};
scope.updateCurrentSelectedText = updateCurrentSelectedText;
var render = paginationCtrl.render;
paginationCtrl.render = function() {
render.apply(paginationCtrl);
updateIds(scope.pages, scope.parentId);
updateAriaLabel(scope.pages);
};
};
};
return $delegate;
}]);
}]);
return {
namespace: MODULE_NAMESPACE,
};
}
);
(function(root) {
define("jquery-chosen", ["jquery"], function() {
return (function() {
/*!
Chosen, a Select Box Enhancer for jQuery and Prototype
by Patrick Filler for Harvest, http://getharvest.com
Version 1.5.1
Full source at https://github.com/harvesthq/chosen
Copyright (c) 2011-2016 Harvest http://getharvest.com
MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
This file is generated by `grunt build`, do not edit it by hand.
*/
(function() {
var $, AbstractChosen, Chosen, SelectParser, _ref,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
SelectParser = (function() {
function SelectParser() {
this.options_index = 0;
this.parsed = [];
}
SelectParser.prototype.add_node = function(child) {
if (child.nodeName.toUpperCase() === "OPTGROUP") {
return this.add_group(child);
} else {
return this.add_option(child);
}
};
SelectParser.prototype.add_group = function(group) {
var group_position, option, _i, _len, _ref, _results;
group_position = this.parsed.length;
this.parsed.push({
array_index: group_position,
group: true,
label: this.escapeExpression(group.label),
title: group.title ? group.title : void 0,
children: 0,
disabled: group.disabled,
classes: group.className
});
_ref = group.childNodes;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
option = _ref[_i];
_results.push(this.add_option(option, group_position, group.disabled));
}
return _results;
};
SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
if (option.nodeName.toUpperCase() === "OPTION") {
if (option.text !== "") {
if (group_position != null) {
this.parsed[group_position].children += 1;
}
this.parsed.push({
array_index: this.parsed.length,
options_index: this.options_index,
value: option.value,
text: option.text,
html: option.innerHTML,
title: option.title ? option.title : void 0,
selected: option.selected,
disabled: group_disabled === true ? group_disabled : option.disabled,
group_array_index: group_position,
group_label: group_position != null ? this.parsed[group_position].label : null,
classes: option.className,
style: option.style.cssText
});
} else {
this.parsed.push({
array_index: this.parsed.length,
options_index: this.options_index,
empty: true
});
}
return this.options_index += 1;
}
};
SelectParser.prototype.escapeExpression = function(text) {
var map, unsafe_chars;
if ((text == null) || text === false) {
return "";
}
if (!/[\&\<\>\"\'\`]/.test(text)) {
return text;
}
map = {
"<": "<",
">": ">",
'"': """,
"'": "'",
"`": "`"
};
unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
return text.replace(unsafe_chars, function(chr) {
return map[chr] || "&";
});
};
return SelectParser;
})();
SelectParser.select_to_array = function(select) {
var child, parser, _i, _len, _ref;
parser = new SelectParser();
_ref = select.childNodes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
parser.add_node(child);
}
return parser.parsed;
};
AbstractChosen = (function() {
function AbstractChosen(form_field, options) {
this.form_field = form_field;
this.options = options != null ? options : {};
if (!AbstractChosen.browser_is_supported()) {
return;
}
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();
}
AbstractChosen.prototype.set_default_values = function() {
var _this = this;
this.click_test_action = function(evt) {
return _this.test_active_click(evt);
};
this.activate_action = function(evt) {
return _this.activate_field(evt);
};
this.active_field = false;
this.mouse_on_container = false;
this.results_showing = false;
this.result_highlighted = null;
this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
this.disable_search_threshold = this.options.disable_search_threshold || 0;
this.disable_search = this.options.disable_search || false;
this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
this.group_search = this.options.group_search != null ? this.options.group_search : true;
this.search_contains = this.options.search_contains || false;
this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
this.max_selected_options = this.options.max_selected_options || Infinity;
this.inherit_select_classes = this.options.inherit_select_classes || false;
this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
this.include_group_label_in_selected = this.options.include_group_label_in_selected || false;
return this.max_shown_results = this.options.max_shown_results || Number.POSITIVE_INFINITY;
};
AbstractChosen.prototype.set_default_text = function() {
if (this.form_field.getAttribute("data-placeholder")) {
this.default_text = this.form_field.getAttribute("data-placeholder");
} else if (this.is_multiple) {
this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
} else {
this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
}
return 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(item) {
if (this.include_group_label_in_selected && (item.group_label != null)) {
return "<b class='group-name'>" + item.group_label + "</b>" + item.html;
} else {
return item.html;
}
};
AbstractChosen.prototype.mouse_enter = function() {
return this.mouse_on_container = true;
};
AbstractChosen.prototype.mouse_leave = function() {
return this.mouse_on_container = false;
};
AbstractChosen.prototype.input_focus = function(evt) {
var _this = this;
if (this.is_multiple) {
if (!this.active_field) {
return setTimeout((function() {
return _this.container_mousedown();
}), 50);
}
} else {
if (!this.active_field) {
return this.activate_field();
}
}
};
AbstractChosen.prototype.input_blur = function(evt) {
var _this = this;
if (!this.mouse_on_container) {
this.active_field = false;
return setTimeout((function() {
return _this.blur_test();
}), 100);
}
};
AbstractChosen.prototype.results_option_build = function(options) {
var content, data, data_content, shown_results, _i, _len, _ref;
content = '';
shown_results = 0;
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
data = _ref[_i];
data_content = '';
if (data.group) {
data_content = this.result_add_group(data);
} else {
data_content = this.result_add_option(data);
}
if (data_content !== '') {
shown_results++;
content += data_content;
}
if (options != null ? options.first : void 0) {
if (data.selected && this.is_multiple) {
this.choice_build(data);
} else if (data.selected && !this.is_multiple) {
this.single_set_selected_text(this.choice_label(data));
}
}
if (shown_results >= this.max_shown_results) {
break;
}
}
return content;
};
AbstractChosen.prototype.result_add_option = function(option) {
var classes, option_el;
if (!option.search_match) {
return '';
}
if (!this.include_option_in_results(option)) {
return '';
}
classes = [];
if (!option.disabled && !(option.selected && this.is_multiple)) {
classes.push("active-result");
}
if (option.disabled && !(option.selected && this.is_multiple)) {
classes.push("disabled-result");
}
if (option.selected) {
classes.push("result-selected");
}
if (option.group_array_index != null) {
classes.push("group-option");
}
if (option.classes !== "") {
classes.push(option.classes);
}
option_el = document.createElement("li");
option_el.className = classes.join(" ");
option_el.style.cssText = option.style;
option_el.setAttribute("data-option-array-index", option.array_index);
option_el.innerHTML = option.search_text;
if (option.title) {
option_el.title = option.title;
}
return this.outerHTML(option_el);
};
AbstractChosen.prototype.result_add_group = function(group) {
var classes, group_el;
if (!(group.search_match || group.group_match)) {
return '';
}
if (!(group.active_options > 0)) {
return '';
}
classes = [];
classes.push("group-result");
if (group.classes) {
classes.push(group.classes);
}
group_el = document.createElement("li");
group_el.className = classes.join(" ");
group_el.innerHTML = group.search_text;
if (group.title) {
group_el.title = group.title;
}
return this.outerHTML(group_el);
};
AbstractChosen.prototype.results_update_field = function() {
this.set_default_text();
if (!this.is_multiple) {
this.results_reset_cleanup();
}
this.result_clear_highlight();
this.results_build();
if (this.results_showing) {
return this.winnow_results();
}
};
AbstractChosen.prototype.reset_single_select_options = function() {
var result, _i, _len, _ref, _results;
_ref = this.results_data;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
result = _ref[_i];
if (result.selected) {
_results.push(result.selected = false);
} else {
_results.push(void 0);
}
}
return _results;
};
AbstractChosen.prototype.results_toggle = function() {
if (this.results_showing) {
return this.results_hide();
} else {
return this.results_show();
}
};
AbstractChosen.prototype.results_search = function(evt) {
if (this.results_showing) {
return this.winnow_results();
} else {
return this.results_show();
}
};
AbstractChosen.prototype.winnow_results = function() {
var escapedSearchText, option, regex, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref;
this.no_results_clear();
results = 0;
searchText = this.get_search_text();
escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
zregex = new RegExp(escapedSearchText, 'i');
regex = this.get_search_regex(escapedSearchText);
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
option = _ref[_i];
option.search_match = false;
results_group = null;
if (this.include_option_in_results(option)) {
if (option.group) {
option.group_match = false;
option.active_options = 0;
}
if ((option.group_array_index != null) && this.results_data[option.group_array_index]) {
results_group = this.results_data[option.group_array_index];
if (results_group.active_options === 0 && results_group.search_match) {
results += 1;
}
results_group.active_options += 1;
}
option.search_text = option.group ? option.label : option.html;
if (!(option.group && !this.group_search)) {
option.search_match = this.search_string_match(option.search_text, regex);
if (option.search_match && !option.group) {
results += 1;
}
if (option.search_match) {
if (searchText.length) {
startpos = option.search_text.search(zregex);
text = option.search_text.substr(0, startpos + searchText.length) + '</em>' + option.search_text.substr(startpos + searchText.length);
option.search_text = text.substr(0, startpos) + '<em>' + text.substr(startpos);
}
if (results_group != null) {
results_group.group_match = true;
}
} else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) {
option.search_match = true;
}
}
}
}
this.result_clear_highlight();
if (results < 1 && searchText.length) {
this.update_results_content("");
return this.no_results(searchText);
} else {
this.update_results_content(this.results_option_build());
return this.winnow_results_set_highlight();
}
};
AbstractChosen.prototype.get_search_regex = function(escaped_search_string) {
var regex_anchor;
regex_anchor = this.search_contains ? "" : "^";
return new RegExp(regex_anchor + escaped_search_string, 'i');
};
AbstractChosen.prototype.search_string_match = function(search_string, regex) {
var part, parts, _i, _len;
if (regex.test(search_string)) {
return true;
} else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) {
parts = search_string.replace(/\[|\]/g, "").split(" ");
if (parts.length) {
for (_i = 0, _len = parts.length; _i < _len; _i++) {
part = parts[_i];
if (regex.test(part)) {
return true;
}
}
}
}
};
AbstractChosen.prototype.choices_count = function() {
var option, _i, _len, _ref;
if (this.selected_option_count != null) {
return this.selected_option_count;
}
this.selected_option_count = 0;
_ref = this.form_field.options;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
option = _ref[_i];
if (option.selected) {
this.selected_option_count += 1;
}
}
return this.selected_option_count;
};
AbstractChosen.prototype.choices_click = function(evt) {
evt.preventDefault();
if (!(this.results_showing || this.is_disabled)) {
return this.results_show();
}
};
AbstractChosen.prototype.keyup_checker = function(evt) {
var stroke, _ref;
stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
this.search_field_scale();
switch (stroke) {
case 8:
if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) {
return this.keydown_backstroke();
} else if (!this.pending_backstroke) {
this.result_clear_highlight();
return this.results_search();
}
break;
case 13:
evt.preventDefault();
if (this.results_showing) {
return this.result_select(evt);
}
break;
case 27:
if (this.results_showing) {
this.results_hide();
}
return true;
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(evt) {
var _this = this;
return setTimeout((function() {
return _this.results_search();
}), 50);
};
AbstractChosen.prototype.container_width = function() {
if (this.options.width != null) {
return this.options.width;
} else {
return "" + this.form_field.offsetWidth + "px";
}
};
AbstractChosen.prototype.include_option_in_results = function(option) {
if (this.is_multiple && (!this.display_selected_options && option.selected)) {
return false;
}
if (!this.display_disabled_options && option.disabled) {
return false;
}
if (option.empty) {
return false;
}
return true;
};
AbstractChosen.prototype.search_results_touchstart = function(evt) {
this.touch_started = true;
return this.search_results_mouseover(evt);
};
AbstractChosen.prototype.search_results_touchmove = function(evt) {
this.touch_started = false;
return this.search_results_mouseout(evt);
};
AbstractChosen.prototype.search_results_touchend = function(evt) {
if (this.touch_started) {
return this.search_results_mouseup(evt);
}
};
AbstractChosen.prototype.outerHTML = function(element) {
var tmp;
if (element.outerHTML) {
return element.outerHTML;
}
tmp = document.createElement("div");
tmp.appendChild(element);
return tmp.innerHTML;
};
AbstractChosen.browser_is_supported = function() {
if (/iP(od|hone)/i.test(window.navigator.userAgent)) {
return false;
}
if (/Android/i.test(window.navigator.userAgent)) {
if (/Mobile/i.test(window.navigator.userAgent)) {
return false;
}
}
if (/IEMobile/i.test(window.navigator.userAgent)) {
return false;
}
if (/Windows Phone/i.test(window.navigator.userAgent)) {
return false;
}
if (/BlackBerry/i.test(window.navigator.userAgent)) {
return false;
}
if (/BB10/i.test(window.navigator.userAgent)) {
return false;
}
if (window.navigator.appName === "Microsoft Internet Explorer") {
return document.documentMode >= 8;
}
return true;
};
AbstractChosen.default_multiple_text = "Select Some Options";
AbstractChosen.default_single_text = "Select an Option";
AbstractChosen.default_no_result_text = "No results match";
return AbstractChosen;
})();
$ = jQuery;
$.fn.extend({
chosen: function(options) {
if (!AbstractChosen.browser_is_supported()) {
return this;
}
return this.each(function(input_field) {
var $this, chosen;
$this = $(this);
chosen = $this.data('chosen');
if (options === 'destroy') {
if (chosen instanceof Chosen) {
chosen.destroy();
}
return;
}
if (!(chosen instanceof Chosen)) {
$this.data('chosen', new Chosen(this, options));
}
});
}
});
Chosen = (function(_super) {
__extends(Chosen, _super);
function Chosen() {
_ref = Chosen.__super__.constructor.apply(this, arguments);
return _ref;
}
Chosen.prototype.setup = function() {
this.form_field_jq = $(this.form_field);
this.current_selectedIndex = this.form_field.selectedIndex;
return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl");
};
Chosen.prototype.set_up_html = function() {
var container_classes, container_props;
container_classes = ["chosen-container"];
container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
if (this.inherit_select_classes && this.form_field.className) {
container_classes.push(this.form_field.className);
}
if (this.is_rtl) {
container_classes.push("chosen-rtl");
}
container_props = {
'class': container_classes.join(' '),
'style': "width: " + (this.container_width()) + ";",
'title': this.form_field.title
};
if (this.form_field.id.length) {
container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
}
this.container = $("<div />", container_props);
if (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>');
} else {
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();
if (this.is_multiple) {
this.search_choices = this.container.find('ul.chosen-choices').first();
this.search_container = this.container.find('li.search-field').first();
} else {
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();
return 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 _this = this;
this.container.bind('touchstart.chosen', function(evt) {
_this.container_mousedown(evt);
return evt.preventDefault();
});
this.container.bind('touchend.chosen', function(evt) {
_this.container_mouseup(evt);
return evt.preventDefault();
});
this.container.bind('mousedown.chosen', function(evt) {
_this.container_mousedown(evt);
});
this.container.bind('mouseup.chosen', function(evt) {
_this.container_mouseup(evt);
});
this.container.bind('mouseenter.chosen', function(evt) {
_this.mouse_enter(evt);
});
this.container.bind('mouseleave.chosen', function(evt) {
_this.mouse_leave(evt);
});
this.search_results.bind('mouseup.chosen', function(evt) {
_this.search_results_mouseup(evt);
});
this.search_results.bind('mouseover.chosen', function(evt) {
_this.search_results_mouseover(evt);
});
this.search_results.bind('mouseout.chosen', function(evt) {
_this.search_results_mouseout(evt);
});
this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) {
_this.search_results_mousewheel(evt);
});
this.search_results.bind('touchstart.chosen', function(evt) {
_this.search_results_touchstart(evt);
});
this.search_results.bind('touchmove.chosen', function(evt) {
_this.search_results_touchmove(evt);
});
this.search_results.bind('touchend.chosen', function(evt) {
_this.search_results_touchend(evt);
});
this.form_field_jq.bind("chosen:updated.chosen", function(evt) {
_this.results_update_field(evt);
});
this.form_field_jq.bind("chosen:activate.chosen", function(evt) {
_this.activate_field(evt);
});
this.form_field_jq.bind("chosen:open.chosen", function(evt) {
_this.container_mousedown(evt);
});
this.form_field_jq.bind("chosen:close.chosen", function(evt) {
_this.input_blur(evt);
});
this.search_field.bind('blur.chosen', function(evt) {
_this.input_blur(evt);
});
this.search_field.bind('keyup.chosen', function(evt) {
_this.keyup_checker(evt);
});
this.search_field.bind('keydown.chosen', function(evt) {
_this.keydown_checker(evt);
});
this.search_field.bind('focus.chosen', function(evt) {
_this.input_focus(evt);
});
this.search_field.bind('cut.chosen', function(evt) {
_this.clipboard_event_checker(evt);
});
this.search_field.bind('paste.chosen', function(evt) {
_this.clipboard_event_checker(evt);
});
if (this.is_multiple) {
return this.search_choices.bind('click.chosen', function(evt) {
_this.choices_click(evt);
});
} else {
return this.container.bind('click.chosen', function(evt) {
evt.preventDefault();
});
}
};
Chosen.prototype.destroy = function() {
$(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
if (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');
return this.form_field_jq.show();
};
Chosen.prototype.search_field_disabled = function() {
this.is_disabled = this.form_field_jq[0].disabled;
if (this.is_disabled) {
this.container.addClass('chosen-disabled');
this.search_field[0].disabled = true;
if (!this.is_multiple) {
this.selected_item.unbind("focus.chosen", this.activate_action);
}
return this.close_field();
} else {
this.container.removeClass('chosen-disabled');
this.search_field[0].disabled = false;
if (!this.is_multiple) {
return this.selected_item.bind("focus.chosen", this.activate_action);
}
}
};
Chosen.prototype.container_mousedown = function(evt) {
if (!this.is_disabled) {
if (evt && evt.type === "mousedown" && !this.results_showing) {
evt.preventDefault();
}
if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) {
if (!this.active_field) {
if (this.is_multiple) {
this.search_field.val("");
}
$(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action);
this.results_show();
} else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) {
evt.preventDefault();
this.results_toggle();
}
return this.activate_field();
}
}
};
Chosen.prototype.container_mouseup = function(evt) {
if (evt.target.nodeName === "ABBR" && !this.is_disabled) {
return this.results_reset(evt);
}
};
Chosen.prototype.search_results_mousewheel = function(evt) {
var delta;
if (evt.originalEvent) {
delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
}
if (delta != null) {
evt.preventDefault();
if (evt.type === 'DOMMouseScroll') {
delta = delta * 40;
}
return this.search_results.scrollTop(delta + this.search_results.scrollTop());
}
};
Chosen.prototype.blur_test = function(evt) {
if (!this.active_field && this.container.hasClass("chosen-container-active")) {
return this.close_field();
}
};
Chosen.prototype.close_field = function() {
$(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
this.active_field = false;
this.results_hide();
this.container.removeClass("chosen-container-active");
this.clear_backstroke();
this.show_search_field_default();
return this.search_field_scale();
};
Chosen.prototype.activate_field = function() {
this.container.addClass("chosen-container-active");
this.active_field = true;
this.search_field.val(this.search_field.val());
return this.search_field.focus();
};
Chosen.prototype.test_active_click = function(evt) {
var active_container;
active_container = $(evt.target).closest('.chosen-container');
if (active_container.length && this.container[0] === active_container[0]) {
return this.active_field = true;
} else {
return this.close_field();
}
};
Chosen.prototype.results_build = function() {
this.parsing = true;
this.selected_option_count = null;
this.results_data = SelectParser.select_to_array(this.form_field);
if (this.is_multiple) {
this.search_choices.find("li.search-choice").remove();
} else if (!this.is_multiple) {
this.single_set_selected_text();
if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
this.search_field[0].readOnly = true;
this.container.addClass("chosen-container-single-nosearch");
} else {
this.search_field[0].readOnly = false;
this.container.removeClass("chosen-container-single-nosearch");
}
}
this.update_results_content(this.results_option_build({
first: true
}));
this.search_field_disabled();
this.show_search_field_default();
this.search_field_scale();
return this.parsing = false;
};
Chosen.prototype.result_do_highlight = function(el) {
var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
if (el.length) {
this.result_clear_highlight();
this.result_highlight = el;
this.result_highlight.addClass("highlighted");
maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
visible_top = this.search_results.scrollTop();
visible_bottom = maxHeight + visible_top;
high_top = this.result_highlight.position().top + this.search_results.scrollTop();
high_bottom = high_top + this.result_highlight.outerHeight();
if (high_bottom >= visible_bottom) {
return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
} else if (high_top < visible_top) {
return this.search_results.scrollTop(high_top);
}
}
};
Chosen.prototype.result_clear_highlight = function() {
if (this.result_highlight) {
this.result_highlight.removeClass("highlighted");
}
return this.result_highlight = null;
};
Chosen.prototype.results_show = function() {
if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
this.form_field_jq.trigger("chosen:maxselected", {
chosen: this
});
return false;
}
this.container.addClass("chosen-with-drop");
this.results_showing = true;
this.search_field.focus();
this.search_field.val(this.search_field.val());
this.winnow_results();
return this.form_field_jq.trigger("chosen:showing_dropdown", {
chosen: this
});
};
Chosen.prototype.update_results_content = function(content) {
return this.search_results.html(content);
};
Chosen.prototype.results_hide = function() {
if (this.results_showing) {
this.result_clear_highlight();
this.container.removeClass("chosen-with-drop");
this.form_field_jq.trigger("chosen:hiding_dropdown", {
chosen: this
});
}
return this.results_showing = false;
};
Chosen.prototype.set_tab_index = function(el) {
var ti;
if (this.form_field.tabIndex) {
ti = this.form_field.tabIndex;
this.form_field.tabIndex = -1;
return this.search_field[0].tabIndex = ti;
}
};
Chosen.prototype.set_label_behavior = function() {
var _this = this;
this.form_field_label = this.form_field_jq.parents("label");
if (!this.form_field_label.length && this.form_field.id.length) {
this.form_field_label = $("label[for='" + this.form_field.id + "']");
}
if (this.form_field_label.length > 0) {
return this.form_field_label.bind('click.chosen', function(evt) {
if (_this.is_multiple) {
return _this.container_mousedown(evt);
} else {
return _this.activate_field();
}
});
}
};
Chosen.prototype.show_search_field_default = function() {
if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
this.search_field.val(this.default_text);
return this.search_field.addClass("default");
} else {
this.search_field.val("");
return this.search_field.removeClass("default");
}
};
Chosen.prototype.search_results_mouseup = function(evt) {
var target;
target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
if (target.length) {
this.result_highlight = target;
this.result_select(evt);
return this.search_field.focus();
}
};
Chosen.prototype.search_results_mouseover = function(evt) {
var target;
target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
if (target) {
return this.result_do_highlight(target);
}
};
Chosen.prototype.search_results_mouseout = function(evt) {
if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) {
return this.result_clear_highlight();
}
};
Chosen.prototype.choice_build = function(item) {
var choice, close_link,
_this = this;
choice = $('<li />', {
"class": "search-choice"
}).html("<span>" + (this.choice_label(item)) + "</span>");
if (item.disabled) {
choice.addClass('search-choice-disabled');
} else {
close_link = $('<a />', {
"class": 'search-choice-close',
'data-option-array-index': item.array_index
});
close_link.bind('click.chosen', function(evt) {
return _this.choice_destroy_link_click(evt);
});
choice.append(close_link);
}
return this.search_container.before(choice);
};
Chosen.prototype.choice_destroy_link_click = function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (!this.is_disabled) {
return this.choice_destroy($(evt.target));
}
};
Chosen.prototype.choice_destroy = function(link) {
if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) {
this.show_search_field_default();
if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) {
this.results_hide();
}
link.parents('li').first().remove();
return this.search_field_scale();
}
};
Chosen.prototype.results_reset = function() {
this.reset_single_select_options();
this.form_field.options[0].selected = true;
this.single_set_selected_text();
this.show_search_field_default();
this.results_reset_cleanup();
this.form_field_jq.trigger("change");
if (this.active_field) {
return this.results_hide();
}
};
Chosen.prototype.results_reset_cleanup = function() {
this.current_selectedIndex = this.form_field.selectedIndex;
return this.selected_item.find("abbr").remove();
};
Chosen.prototype.result_select = function(evt) {
var high, item;
if (this.result_highlight) {
high = this.result_highlight;
this.result_clear_highlight();
if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
this.form_field_jq.trigger("chosen:maxselected", {
chosen: this
});
return false;
}
if (this.is_multiple) {
high.removeClass("active-result");
} else {
this.reset_single_select_options();
}
high.addClass("result-selected");
item = this.results_data[high[0].getAttribute("data-option-array-index")];
item.selected = true;
this.form_field.options[item.options_index].selected = true;
this.selected_option_count = null;
if (this.is_multiple) {
this.choice_build(item);
} else {
this.single_set_selected_text(this.choice_label(item));
}
if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) {
this.results_hide();
}
this.show_search_field_default();
if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) {
this.form_field_jq.trigger("change", {
'selected': this.form_field.options[item.options_index].value
});
}
this.current_selectedIndex = this.form_field.selectedIndex;
evt.preventDefault();
return this.search_field_scale();
}
};
Chosen.prototype.single_set_selected_text = function(text) {
if (text == null) {
text = this.default_text;
}
if (text === this.default_text) {
this.selected_item.addClass("chosen-default");
} else {
this.single_deselect_control_build();
this.selected_item.removeClass("chosen-default");
}
return this.selected_item.find("span").html(text);
};
Chosen.prototype.result_deselect = function(pos) {
var result_data;
result_data = this.results_data[pos];
if (!this.form_field.options[result_data.options_index].disabled) {
result_data.selected = false;
this.form_field.options[result_data.options_index].selected = false;
this.selected_option_count = null;
this.result_clear_highlight();
if (this.results_showing) {
this.winnow_results();
}
this.form_field_jq.trigger("change", {
deselected: this.form_field.options[result_data.options_index].value
});
this.search_field_scale();
return true;
} else {
return false;
}
};
Chosen.prototype.single_deselect_control_build = function() {
if (!this.allow_single_deselect) {
return;
}
if (!this.selected_item.find("abbr").length) {
this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
}
return this.selected_item.addClass("chosen-single-with-deselect");
};
Chosen.prototype.get_search_text = function() {
return $('<div/>').text($.trim(this.search_field.val())).html();
};
Chosen.prototype.winnow_results_set_highlight = function() {
var do_high, selected_results;
selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
if (do_high != null) {
return this.result_do_highlight(do_high);
}
};
Chosen.prototype.no_results = function(terms) {
var no_results_html;
no_results_html = $('<li class="no-results">' + this.results_none_found + ' "<span></span>"</li>');
no_results_html.find("span").first().html(terms);
this.search_results.append(no_results_html);
return 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 next_sib;
if (this.results_showing && this.result_highlight) {
next_sib = this.result_highlight.nextAll("li.active-result").first();
if (next_sib) {
return this.result_do_highlight(next_sib);
}
} else {
return this.results_show();
}
};
Chosen.prototype.keyup_arrow = function() {
var prev_sibs;
if (!this.results_showing && !this.is_multiple) {
return this.results_show();
} else if (this.result_highlight) {
prev_sibs = this.result_highlight.prevAll("li.active-result");
if (prev_sibs.length) {
return this.result_do_highlight(prev_sibs.first());
} else {
if (this.choices_count() > 0) {
this.results_hide();
}
return this.result_clear_highlight();
}
}
};
Chosen.prototype.keydown_backstroke = function() {
var next_available_destroy;
if (this.pending_backstroke) {
this.choice_destroy(this.pending_backstroke.find("a").first());
return this.clear_backstroke();
} else {
next_available_destroy = this.search_container.siblings("li.search-choice").last();
if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) {
this.pending_backstroke = next_available_destroy;
if (this.single_backstroke_delete) {
return this.keydown_backstroke();
} else {
return this.pending_backstroke.addClass("search-choice-focus");
}
}
}
};
Chosen.prototype.clear_backstroke = function() {
if (this.pending_backstroke) {
this.pending_backstroke.removeClass("search-choice-focus");
}
return this.pending_backstroke = null;
};
Chosen.prototype.keydown_checker = function(evt) {
var stroke, _ref1;
stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
this.search_field_scale();
if (stroke !== 8 && this.pending_backstroke) {
this.clear_backstroke();
}
switch (stroke) {
case 8:
this.backstroke_length = this.search_field.val().length;
break;
case 9:
if (this.results_showing && !this.is_multiple) {
this.result_select(evt);
}
this.mouse_on_container = false;
break;
case 13:
if (this.results_showing) {
evt.preventDefault();
}
break;
case 32:
if (this.disable_search) {
evt.preventDefault();
}
break;
case 38:
evt.preventDefault();
this.keyup_arrow();
break;
case 40:
evt.preventDefault();
this.keydown_arrow();
break;
}
};
Chosen.prototype.search_field_scale = function() {
var div, f_width, h, style, style_block, styles, w, _i, _len;
if (this.is_multiple) {
h = 0;
w = 0;
style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
for (_i = 0, _len = styles.length; _i < _len; _i++) {
style = styles[_i];
style_block += style + ":" + this.search_field.css(style) + ";";
}
div = $('<div />', {
'style': style_block
});
div.text(this.search_field.val());
$('body').append(div);
w = div.width() + 25;
div.remove();
f_width = this.container.outerWidth();
if (w > f_width - 10) {
w = f_width - 10;
}
return this.search_field.css({
'width': w + 'px'
});
}
};
return Chosen;
})(AbstractChosen);
}).call(this);
}).apply(root, arguments);
});
}(this));
(function(root) {
define("angular-chosen", ["angular","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 indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
angular.module('localytics.directives', []);
angular.module('localytics.directives').directive('chosen', [
'$timeout', function($timeout) {
var CHOSEN_OPTION_WHITELIST, NG_OPTIONS_REGEXP, isEmpty, snakeCase;
NG_OPTIONS_REGEXP = /^\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]+?))?$/;
CHOSEN_OPTION_WHITELIST = ['persistentCreateOption', 'createOptionText', 'createOption', 'skipNoResults', 'noResultsText', 'allowSingleDeselect', 'disableSearchThreshold', 'disableSearch', 'enableSplitWordSearch', 'inheritSelectClasses', 'maxSelectedOptions', 'placeholderTextMultiple', 'placeholderTextSingle', 'searchContains', 'singleBackstrokeDelete', 'displayDisabledOptions', 'displaySelectedOptions', 'width', 'includeGroupLabelInSelected', 'maxShownResults'];
snakeCase = function(input) {
return input.replace(/[A-Z]/g, function($1) {
return "_" + ($1.toLowerCase());
});
};
isEmpty = function(value) {
var key;
if (angular.isArray(value)) {
return value.length === 0;
} else if (angular.isObject(value)) {
for (key in value) {
if (value.hasOwnProperty(key)) {
return false;
}
}
}
return true;
};
return {
restrict: 'A',
require: '?ngModel',
priority: 1,
link: function(scope, element, attr, ngModel) {
var chosen, empty, initOrUpdate, match, options, origRender, startLoading, stopLoading, updateMessage, valuesExpr, viewWatch;
scope.disabledValuesHistory = scope.disabledValuesHistory ? scope.disabledValuesHistory : [];
element = $(element);
element.addClass('localytics-chosen');
options = scope.$eval(attr.chosen) || {};
angular.forEach(attr, function(value, key) {
if (indexOf.call(CHOSEN_OPTION_WHITELIST, key) >= 0) {
return attr.$observe(key, function(value) {
options[snakeCase(key)] = String(element.attr(attr.$attr[key])).slice(0, 2) === '{{' ? value : scope.$eval(value);
return updateMessage();
});
}
});
startLoading = function() {
return element.addClass('loading').attr('disabled', true).trigger('chosen:updated');
};
stopLoading = function() {
element.removeClass('loading');
if (angular.isDefined(attr.disabled)) {
element.attr('disabled', attr.disabled);
} else {
element.attr('disabled', false);
}
return element.trigger('chosen:updated');
};
chosen = null;
empty = false;
initOrUpdate = function() {
var defaultText;
if (chosen) {
return element.trigger('chosen:updated');
} else {
$timeout(function() {
chosen = element.chosen(options).data('chosen');
});
if (angular.isObject(chosen)) {
return defaultText = chosen.default_text;
}
}
};
updateMessage = function() {
if (empty) {
element.attr('data-placeholder', chosen.results_none_found).attr('disabled', true);
} else {
element.removeAttr('data-placeholder');
}
return element.trigger('chosen:updated');
};
if (ngModel) {
origRender = ngModel.$render;
ngModel.$render = function() {
origRender();
return initOrUpdate();
};
element.on('chosen:hiding_dropdown', function() {
return scope.$apply(function() {
return ngModel.$setTouched();
});
});
if (attr.multiple) {
viewWatch = function() {
return ngModel.$viewValue;
};
scope.$watch(viewWatch, ngModel.$render, true);
}
} else {
initOrUpdate();
}
attr.$observe('disabled', function() {
return element.trigger('chosen:updated');
});
if (attr.ngOptions && ngModel) {
match = attr.ngOptions.match(NG_OPTIONS_REGEXP);
valuesExpr = match[7];
scope.$watchCollection(valuesExpr, function(newVal, oldVal) {
var timer;
return timer = $timeout(function() {
if (angular.isUndefined(newVal)) {
return startLoading();
} else {
empty = isEmpty(newVal);
stopLoading();
return updateMessage();
}
});
});
return scope.$on('$destroy', function(event) {
if (typeof timer !== "undefined" && timer !== null) {
return $timeout.cancel(timer);
}
});
}
}
};
}
]);
}).call(this);
}).apply(root, arguments);
});
}(this));
// Copyright 2024 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.
define(
'shared/js/email_deliverability/directives/suggestedRecordSet',[
"angular",
"lodash",
"cjt/directives/copyField",
"shared/js/email_deliverability/filters/htmlSafeString",
"cjt/util/locale",
"cjt/core",
"cjt/directives/callout",
"cjt/modules",
],
function(angular, _, CopyField, HTMLSafeString, LOCALE, CJT) {
"use strict";
var RELATIVE_PATH = "shared/js/email_deliverability/directives/suggestedRecordSet.ptt";
var TEMPLATE_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : CJT.buildPath(RELATIVE_PATH);
var MODULE_NAMESPACE = "shared.emailDeliverability.suggestedRecordSet.directive";
var MODULE_REQUIREMENTS = [ CopyField.namespace, HTMLSafeString.namespace ];
var SPLIT_REGEX = new RegExp("(.{1,255})", "g");
var CONTROLLER_INJECTABLES = [ "$scope" ];
var CONTROLLER = function SuggestedRecordSetController($scope) {
$scope.domainName = $scope.domain.domain;
$scope._suggestionMode = function() {
$scope.label = LOCALE.maketext("Suggested “[_1]” ([_2]) Record", $scope.recordType.toUpperCase(), $scope.recordZoneType );
$scope.record = $scope.domain.getSuggestedRecord($scope.recordType);
$scope.noRecordMessage = LOCALE.maketext("Suggested “[_1]” does not exist.", $scope.recordType.toUpperCase());
$scope.nameText = $scope.record.name;
$scope.originalValueText = $scope.valueText = $scope.record.value;
};
$scope._currentMode = function() {
$scope.label = LOCALE.maketext("Current “[_1]” ([_2]) Record", $scope.recordType.toUpperCase(), $scope.recordZoneType );
$scope.record = $scope.domain.getCurrentRecord($scope.recordType);
$scope.noRecordMessage = LOCALE.maketext("Current “[_1]” does not exist.", $scope.recordType.toUpperCase());
$scope.nameText = $scope.record.name;
$scope.originalValueText = $scope.valueText = $scope.record.value;
};
$scope._recordsReady = function() {
if (!$scope.domain.recordsLoaded) {
return;
}
$scope.recordValid = $scope.domain.isRecordValid($scope.recordType);
$scope.recordsLoaded = true;
if ($scope.alwaysCurrent) {
$scope._currentMode();
} else if ($scope.alwaysSuggested) {
$scope._suggestionMode();
} else if ($scope.recordValid) {
$scope._currentMode();
} else {
$scope._suggestionMode();
}
if (Object.keys($scope.record).length === 0) {
$scope.record = false;
}
};
$scope._checkRecordsReady = function() {
return $scope.domain.recordsLoaded;
};
$scope.splitMode = "full";
$scope.toggleSplitMode = function() {
$scope.splitMode = $scope.splitMode === "full" ? "split" : "full";
if ( $scope.splitMode === "split" ) {
if ( !$scope.splitText ) {
var split = $scope.originalValueText.match(SPLIT_REGEX);
$scope.splitText = _.join( _.map(split, function(e) {
return "\"" + e + "\"";
}), " " );
}
$scope.valueText = $scope.splitText;
} else {
$scope.valueText = $scope.originalValueText;
}
};
$scope.$watch($scope._checkRecordsReady, $scope._recordsReady);
$scope.label = LOCALE.maketext("Loading “[_1]” Record", $scope.recordType.toUpperCase() );
};
var module = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
var DIRECTIVE_LINK = function($scope, $element, $attrs) {
if (_.isUndefined($attrs["hideExtras"]) === false) {
$scope.hideExtras = true;
}
if (_.isUndefined($attrs["alwaysSuggested"]) === false) {
$scope.alwaysSuggested = true;
}
if (_.isUndefined($attrs["alwaysCurrent"]) === false) {
$scope.alwaysCurrent = true;
}
};
module.directive("suggestedRecordSet", function suggestedRecordSetDirectiveFactory() {
return {
templateUrl: TEMPLATE_PATH,
scope: {
parentID: "@id",
domain: "=",
recordType: "@",
recordZoneType: "@",
splitable: "=",
},
restrict: "E",
replace: true,
transclude: true,
link: DIRECTIVE_LINK,
controller: CONTROLLER_INJECTABLES.concat(CONTROLLER),
};
});
return {
"class": CONTROLLER,
"namespace": MODULE_NAMESPACE,
"template": TEMPLATE_PATH,
};
}
);
// Copyright 2024 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.
define(
'shared/js/email_deliverability/views/manageDomain',[
"angular",
"lodash",
"cjt/core",
"cjt/util/locale",
"cjt/util/inet6",
"shared/js/email_deliverability/services/domains",
"cjt/directives/copyField",
"shared/js/email_deliverability/directives/suggestedRecordSet",
"cjt/modules",
"cjt/directives/callout",
"cjt/directives/actionButtonDirective",
],
function(angular, _, CJT, LOCALE, INET6, DomainsService, CopyField, SuggestedRecordSet) {
"use strict";
/**
* Controller for Managing a Domain
*
* @module ManageDomainController
* @memberof cpanel.emailDeliverability
*
*/
var MODULE_NAMESPACE = "shared.emailDeliverability.views.manageDomain";
var MODULE_REQUIREMENTS = [
DomainsService.namespace,
CopyField.namespace,
SuggestedRecordSet.namespace,
];
var CONTROLLER_NAME = "ManageDomainController";
var CONTROLLER_INJECTABLES = ["$scope", "$location", "$routeParams", "DomainsService", "alertService", "ADD_RESOURCE_PANEL", "PAGE"];
function formatRecordError(domain, recordType, error) {
return LOCALE.maketext( "The system failed to complete validation of “[_1]”’s “[_2]” because of an error: [_3]", domain, recordType, error);
}
var CONTROLLER = function($scope, $location, $routeParams, $domainsService, $alertService, ADD_RESOURCE_PANEL, PAGE) {
$scope.canReturnToLister = !PAGE.CAN_SKIP_LISTER || $domainsService.getAll().length > 1;
$scope._returnToLister = function() {
$location.path("/").search("");
};
/**
*
* Init the view
*
*/
$scope.init = function init() {
var domains = $domainsService.getAll();
$scope.currentDomain = $domainsService.findDomainByName($routeParams["domain"]);
$scope.skipPTRLookups = PAGE.skipPTRLookups !== undefined ? PAGE.skipPTRLookups : false;
if (!$scope.currentDomain && domains.length > 1) {
$alertService.add({
"message": LOCALE.maketext("You did not specify a domain to manage."),
"type": "info",
});
$scope._returnToLister();
return;
} else if (domains.length === 1) {
$scope.currentDomain = domains[0];
}
angular.extend($scope, {
isWhm: CJT.isWhm(),
confirmDKIMDownloadRequest: false,
showConfirmDKIM: false,
dkimPrivateKey: false,
ptrServerName: "",
resourcesPanelTemplate: ADD_RESOURCE_PANEL,
isRecordValid: $scope.currentDomain.isRecordValid.bind($scope.currentDomain),
getSuggestedRecord: $scope.currentDomain.getSuggestedRecord.bind($scope.currentDomain),
errors: {},
});
var promise = $domainsService.validateAllRecords([$scope.currentDomain]).then($scope._handleRecordErrors);
if (!$scope.skipPTRLookups) {
promise.then($scope._populatePTRInfo).then( $scope._handlePTRStatus );
}
return promise;
};
$scope._populatePTRInfo = function _populatePTRInformation() {
var ptrDetails = $scope.currentDomain.getRecordDetails("ptr");
var suggestedRecord = $scope.currentDomain.getSuggestedRecord("ptr");
suggestedRecord.value = ptrDetails.helo + ".";
$scope.currentDomain.setSuggestedRecord("ptr", suggestedRecord);
$scope.ptrServerName = ptrDetails.helo;
$scope.ptrServerIP = ptrDetails.ip_address;
};
/**
*
* Determine if current domain has nameserver authority
*
* @returns {Boolean} nameserver authority status
*/
$scope.hasNSAuthority = function hasNSAuthority() {
return $scope.currentDomain.hasNSAuthority;
};
/**
*
* Return the string message for a bad configuration
*
* @param {String} recordType record type to generate the message for
* @returns {String} bad configuration message
*/
$scope.badConfigurationMessage = function badConfigurationMessage(recordType) {
var currentRecords = $scope.currentDomain.getRecords([recordType]);
if (currentRecords.length) {
return LOCALE.maketext("“[_1]” is [output,strong,not] properly configured for this domain.", recordType.toUpperCase());
} else {
return LOCALE.maketext("A “[_1]” record does [output,strong,not] exist for this domain.", recordType.toUpperCase());
}
};
/**
*
* Generate the string message for a user with no authority
*
* @param {String} recordType record type to generate the message for
* @returns {String} the no authority message
*/
$scope.noAuthorityMessage = function noAuthorityMessage(recordType) {
return $domainsService.getNoAuthorityMessage($scope.currentDomain, recordType);
};
/**
*
* Generate a string message for a valid record
*
* @param {String} recordType record type to generate the message for
* @returns {String} valid record message
*/
$scope.validRecordMessage = function validRecordMessage(recordType) {
return LOCALE.maketext("“[_1]” is properly configured for this domain.", recordType.toUpperCase());
};
/**
*
* Repair a record for the current domain
*
* @param {String} recordType record type to repair
* @returns {Promise} repair record promise
*/
$scope.repairRecord = function repairRecord(recordType) {
var newRecord = $scope.getSuggestedRecord(recordType);
var promise = $domainsService.repairDomain($scope.currentDomain, [recordType], [newRecord.value]);
// Do this manually if no ns authority because the service does not
if (!$scope.currentDomain.hasNSAuthority) {
promise.then($domainsService.validateAllRecords([$scope.currentDomain]));
}
if (!$scope.skipPTRLookups) {
promise.then($scope._populatePTRInfo);
}
promise.finally(function() {
if (!$scope.currentDomain.hasNSAuthority) {
$domainsService.unreflectedChangeMessage($scope.currentDomain.domain);
}
$scope.showConfirmDKIM = false;
});
return promise;
};
/**
*
* Toggle the visible state of the confirm DKIM installation message
*
*/
$scope.toggleShowConfirmDKIM = function toggleShowConfirmDKIM() {
$scope.showConfirmDKIM = !$scope.showConfirmDKIM;
};
/**
*
* Get the current record for the current domain
*
* @param {String} recordType record type to get the current record for the domain
* @returns {String} current record or empty string
*/
$scope.getCurrentRecord = function getCurrentRecord(recordType) {
return $domainsService.getCurrentRecord($scope.currentDomain, recordType);
};
$scope._handleRecordErrors = function _handleRecordErrors() {
var allDetails = $scope.currentDomain.getRecordDetails();
Object.keys(allDetails).forEach(function(recordType) {
var record = allDetails[recordType];
if ( record.error ) {
$scope.errors[recordType] = formatRecordError( $scope.currentDomain.domain, recordType.toUpperCase(), record.error );
}
});
};
/**
*
* Get the ptr message based on the status of the invalid ptr record
*
* @returns {String} string message regarding PTR
*/
$scope._handlePTRStatus = function _handlePTRStatus() {
var details = $scope.currentDomain.getRecordDetails("ptr");
var ptrState = details.state;
var ptrRecords = details.ptr_records;
var mailIP = details.ip_address;
var mailHelo = details.helo;
var ptrName = details.arpa_domain;
$scope.ptrStatusCode = ptrState;
if (ptrState === "IP_IS_PRIVATE") {
$scope.ipIsPrivate = true;
} else {
var recordName = $scope.getSuggestedRecord("ptr").name;
recordName = recordName.replace(/\.$/, "");
$scope.ptrRecordName = _.escape(recordName);
var ptrNameservers = details.nameservers.map(_.escape) || [];
ptrNameservers.sort();
if (ptrState === "MISSING_PTR") {
$scope.badPTRMessages = [ LOCALE.maketext("There is no reverse [asis,DNS] configured for the [asis,IP] address ([_1]) that the system uses to send this domain’s outgoing email.", mailIP) ];
if (CJT.isWhm()) {
if (ptrNameservers.length) {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, create the following [asis,PTR] record at [list_and_quoted,_1]:", ptrNameservers);
} else {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, create the following [asis,PTR] record in [asis,DNS]:");
}
} else {
if (ptrNameservers.length) {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, contact your system administrator and request that they create the following [asis,PTR] record at [list_and_quoted,_1]:", ptrNameservers);
} else {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, contact your system administrator and request that they create the following [asis,PTR] record in [asis,DNS]:");
}
}
}
// At least one PTR value isn’t the expected value.
if (ptrState === "HELO_MISMATCH") {
var badNames = ptrRecords.filter( function(r) {
return (r.domain !== mailHelo);
} ).map( function(r) {
return r.domain;
} );
var resolvesSentence = LOCALE.maketext("The system sends “[_1]”’s outgoing email from the “[_2]” [output,abbr,IP,Internet Protocol] address.", $scope.currentDomain.domain, mailIP);
$scope.badPTRMessages = [
resolvesSentence + " " + LOCALE.maketext("The only [asis,PTR] value for this [output,abbr,IP,Internet Protocol] address must be “[_1]”. This is the name that this server sends with [output,abbr,SMTP,Simple Mail Transfer Protocol]’s “[_2]” command to send “[_3]”’s outgoing email.", mailHelo, "HELO", $scope.currentDomain.domain ),
LOCALE.maketext("[numf,_1] unexpected [asis,PTR] [numerate,_1,value exists,values exist] for this [output,abbr,IP,Internet Protocol] address:", badNames.length),
];
$scope.badPTRNames = badNames;
if (CJT.isWhm()) {
if (ptrNameservers.length) {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, replace all [asis,PTR] records for “[_1]” with the following record at [list_and_quoted,_2]:", ptrName, ptrNameservers);
} else {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, replace all [asis,PTR] records for “[_1]” with the following record:", ptrName);
}
} else {
if (ptrNameservers.length) {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, contact your system administrator and request that they replace all [asis,PTR] records for “[_1]” with the following record at [list_and_quoted,_2]:", ptrName, ptrNameservers);
} else {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, contact your system administrator and request that they replace all [asis,PTR] records for “[_1]” with the following record:", ptrName);
}
}
}
// All PTRs are the expected value, but either there
// are no forward IPs or they all mismatch the mail IP.
//
// TODO: Because the PTR has the correct value, it’s
// probably more sensible to report this as a HELO
// problem than as a reverse DNS problem.
if (ptrState === "PTR_MISMATCH") {
var ips = ptrRecords[0].forward_records;
var badmsg = LOCALE.maketext("The system sends the domain “[_1]” in the [output,abbr,SMTP,Simple Mail Transfer Protocol] handshake for this domain’s email.", mailHelo);
if (!ips.length) {
badmsg += " " + LOCALE.maketext("“[_1]” does not resolve to any [output,abbr,IP,Internet Protocol] addresses.", mailHelo);
} else {
badmsg += " " + LOCALE.maketext("“[_1]” resolves to [list_and_quoted,_2], not “[_3]”.", mailHelo, ips, mailIP);
}
$scope.badPTRMessages = [badmsg];
var recordType = INET6.isValid(mailIP) ? "AAAA" : "A";
if (CJT.isWhm()) {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, create a [output,abbr,DNS,Domain Name System] “[_1]” record for “[_2]” whose value is “[_3]”.", recordType, mailHelo, mailIP);
} else {
$scope.ptrToFixMessage = LOCALE.maketext("To fix this problem, contact your system administrator and request that they create a [output,abbr,DNS,Domain Name System] “[_1]” record for “[_2]” whose value is “[_3]”.", recordType, mailHelo, mailIP);
}
}
}
};
$scope.localDKIMExists = function() {
return $domainsService.localDKIMExists($scope.currentDomain);
};
$scope.ensureLocalDKIMKeyExists = function() {
return $domainsService.ensureLocalDKIMKeyExists($scope.currentDomain).then( $scope._handleRecordErrors );
};
return $scope.init();
};
CONTROLLER_INJECTABLES.push(CONTROLLER);
var app = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
app.controller(CONTROLLER_NAME, CONTROLLER_INJECTABLES);
return {
class: CONTROLLER,
namespace: MODULE_NAMESPACE,
};
}
);
/*
# email_deliverability/controller/manageDomain.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(
'shared/js/email_deliverability/views/manageDomainSPF',[
"angular",
"lodash",
"cjt/util/locale",
"shared/js/email_deliverability/services/domains",
"shared/js/email_deliverability/services/spfParser",
"shared/js/email_deliverability/services/SPFRecordProcessor",
"cjt/directives/copyField",
"cjt/modules",
"cjt/services/cpanel/componentSettingSaverService",
"cjt/directives/callout",
"cjt/directives/multiFieldEditorItem",
"cjt/directives/multiFieldEditor",
"cjt/directives/actionButtonDirective",
"cjt/validator/ip-validators",
"cjt/validator/domain-validators",
],
function(angular, _, LOCALE, DomainsService, SPFParser, SPFRecordProcessor, CopyField) {
"use strict";
/**
* Controller for Managing a Domain
*
* @module ManageDomainController
* @memberof cpanel.emailDeliverability
*
*/
var MODULE_NAMESPACE = "shared.emailDeliverability.views.manageDomainSPF";
var MODULE_REQUIREMENTS = [
DomainsService.namespace,
CopyField.namespace,
];
var CONTROLLER_NAME = "ManageDomainSPFController";
var CONTROLLER_INJECTABLES = ["$scope", "$log", "$location", "$routeParams", "DomainsService", "alertService", "componentSettingSaverService", "ADD_RESOURCE_PANEL"];
var CONTROLLER = function($scope, $log, $location, $routeParams, $domainsService, $alertService, $CSSS, ADD_RESOURCE_PANEL) {
var MECHANISM_ARRAYS = ["additionalHosts", "additionalMXServers", "additionalIPv4Addresses", "additionalIPv6Addresses", "additionalINCLUDEItems"];
$scope._updateSuggestedRecords = function _updateSuggestedRecords() {
$scope.suggestedRecord = $scope.currentDomain.getSuggestedRecord("spf");
var originalExpected = $scope.suggestedRecord.originalExpected || "";
var originalExpectedTerms = originalExpected.trim().split(/\s+/);
$scope.missingMechanisms = originalExpectedTerms.map(SPFRecordProcessor._parseSPFTerm).filter(function(term) {
if ( !term.type ) {
return false;
}
return true;
});
$scope.workingRecord = $scope.suggestedRecord;
$scope.populateFormFrom($scope.workingRecord);
$scope.updatePreview();
};
/**
*
* Initiate the current view
*
*/
$scope.init = function init() {
var domains = $domainsService.getAll();
if (!$scope.currentDomain && domains.length > 1) {
$alertService.add({
"message": LOCALE.maketext("You did not specify a domain to manage."),
"type": "danger",
});
$location.path("/").search("");
return;
} else if (domains.length === 1) {
$scope.currentDomain = domains[0];
}
$CSSS.get(CONTROLLER_NAME).then(function(response) {
if (typeof response !== "undefined" && response) {
$scope.showAllHelp = response.showAllHelp;
}
});
$CSSS.register(CONTROLLER_NAME);
$scope.$on("$destroy", function() {
$CSSS.unregister(CONTROLLER_NAME);
});
MECHANISM_ARRAYS.forEach(function(attr) {
$scope.$watchCollection(attr, $scope.updatePreview.bind($scope));
});
return $domainsService.validateAllRecords([$scope.currentDomain]).then($scope._updateSuggestedRecords).then(function() {
$scope._hasNSAuthority = $scope.currentDomain.hasNSAuthority;
});
};
/**
*
* Parse SPF record into various mechanisms
*
* @param {String} record SPF record to parse
*/
$scope.parseRecord = function parseRecord(record) {
if (!record) {
return;
}
if (!record.value) {
return;
}
var currentRecordParts = SPFParser.parse(record.value);
var mechanisms = currentRecordParts.mechanisms;
mechanisms.forEach(function(mechanism) {
if (mechanism.type !== "all" && !mechanism.value) {
return;
}
if (["version", "all"].indexOf(mechanism.type) === -1 && mechanism.prefix !== "+") {
$log.debug("Non-pass mechanism exists. Presenting warning.", mechanism);
$scope.nonPassPrefixesExist = true;
return;
}
if (mechanism.type === "mx") {
$scope.additionalMXServers.push(mechanism.value);
} else if (mechanism.type === "ip4") {
$scope.additionalIPv4Addresses.push(mechanism.value);
} else if (mechanism.type === "ip6") {
$scope.additionalIPv6Addresses.push(mechanism.value);
} else if (mechanism.type === "all" && mechanism.prefix === "-") {
$scope.excludeAllOtherDomains = true;
} else if (mechanism.type === "a") {
$scope.additionalHosts.push(mechanism.value);
} else if (mechanism.type === "include") {
$scope.additionalINCLUDEItems.push(mechanism.value);
}
});
};
/**
*
* Remove duplicates from each of the mechanism arrays
*
*/
$scope.removeDuplicates = function removeDuplicates() {
MECHANISM_ARRAYS.forEach(function(MECH_AR) {
var original = $scope[MECH_AR].slice(0);
var uniq = _.uniq(original);
$scope[MECH_AR].splice(0, $scope[MECH_AR].length);
uniq.forEach($scope[MECH_AR].push);
});
};
/**
*
* Populate form from passed records
*
* @param {String} record SPF record to populate form from
*/
$scope.populateFormFrom = function populateFormFromRecords(record) {
// Clear current values
MECHANISM_ARRAYS.forEach(function(MECH_AR) {
$scope[MECH_AR].splice(0, $scope[MECH_AR].length);
});
$scope.parseRecord(record);
};
$scope.toggleExcludeAllOtherDomains = function toggleExcludeAllOtherDomains() {
$scope.excludeAllOtherDomains = !$scope.excludeAllOtherDomains;
$scope.updatePreview();
};
/**
*
* Update the $scope.workingPreview variable with the SPF Record
*
*/
$scope.updatePreview = function updatePreview() {
// Build new user requested record.
var newWorkingPreview = ["v=spf1", "+mx", "+a"];
if (!$scope.missingMechanisms) {
return;
}
// add +a records
$scope.additionalHosts.forEach(function(item) {
newWorkingPreview.push("+a:" + item);
});
// add +mx records
$scope.additionalMXServers.forEach(function(item) {
newWorkingPreview.push("+mx:" + item);
});
// add all other ip4 addresses
$scope.additionalIPv4Addresses.forEach(function(item) {
newWorkingPreview.push("+ip4:" + item);
});
// add all other ip6 addresses
$scope.additionalIPv6Addresses.forEach(function(item) {
newWorkingPreview.push("+ip6:" + item);
});
// add all includes
// these need to be last
$scope.additionalINCLUDEItems.forEach(function(item) {
newWorkingPreview.push("+include:" + item);
});
// preserve non-pass mechanisms from current record
// overridden ones will get collapsed away.
var currentRecord = $scope.currentDomain.getCurrentRecord("spf");
var currentRecordParts, mechanisms;
if (currentRecord && currentRecord.value) {
currentRecordParts = SPFParser.parse(currentRecord.value);
mechanisms = currentRecordParts.mechanisms;
mechanisms.forEach(function(mechanism) {
if (["version", "all"].indexOf(mechanism.type) === -1 && mechanism.prefix !== "+") {
// non plus mechanisms
newWorkingPreview.push(mechanism.prefix + mechanism.type + ":" + mechanism.value);
}
});
}
// add ~all?
if ($scope.excludeAllOtherDomains) {
newWorkingPreview.push("-all");
} else {
newWorkingPreview.push("~all");
}
var newWorkingPreviewString = newWorkingPreview.join(" ");
// v=spf1 +a +mx +ip4:10.215.218.115 +a:aaaaaaaaaa.com +mx:mxxxxxx.com +ip4:192.168.1.1 +include:include.com -all
$scope.workingRecord = SPFRecordProcessor.combineRecords(newWorkingPreviewString, $scope.missingMechanisms);
};
/**
*
* Determine if current domain has nameserver authority
*
* @returns {Boolean} nameserver authority status
*/
$scope.hasNSAuthority = function hasNSAuthority() {
return $scope._hasNSAuthority;
};
/**
*
* Return the string message for a bad configuration
*
* @param {String} recordType record type to generate the message for
* @returns {String} bad configuration message
*/
$scope.badConfigurationMessage = function badConfigurationMessage(recordType) {
return LOCALE.maketext("“[_1]” is [output,strong,not] properly configured for this domain.", recordType.toUpperCase());
};
/**
*
* Generate the string message for a user with no authority
*
* @param {String} recordType record type to generate the message for
* @returns {String} the no authority message
*/
$scope.noAuthorityMessage = function noAuthorityMessage(recordType) {
return $domainsService.getNoAuthorityMessage($scope.currentDomain, recordType);
};
/**
*
* Toggle the visible help for the form
*
*/
$scope.toggleHelp = function toggleHelp() {
$scope.showAllHelp = !$scope.showAllHelp;
$scope.saveToComponentSettings();
};
/**
*
* Update the NVData saved aspects of this view
*
*/
$scope.saveToComponentSettings = function saveToComponentSettings() {
$CSSS.set(CONTROLLER_NAME, {
showAllHelp: $scope.showAllHelp,
});
};
/**
*
* Repair the SPF record for the current domain
*
* @returns {Promise} repairRecord promise
*/
$scope.update = function update() {
return $domainsService.repairDomain($scope.currentDomain, ["spf"], [$scope.workingRecord], !$scope.hasNSAuthority())
.then(function() {
// Do this manually if no ns authority because the service does not
if (!$scope.hasNSAuthority()) {
return $domainsService.validateAllRecords([$scope.currentDomain]).then($scope._updateSuggestedRecords);
}
})
.finally(function() {
if (!$scope.hasNSAuthority()) {
$domainsService.unreflectedChangeMessage($scope.currentDomain.domain);
}
$scope.showConfirmDKIM = false;
});
};
$scope.currentDomain = $domainsService.findDomainByName($routeParams["domain"]);
angular.extend($scope, {
getCurrentRecord: $scope.currentDomain.getCurrentRecord.bind($scope.currentDomain),
resourcesPanelTemplate: ADD_RESOURCE_PANEL,
showAllHelp: false,
currentRecord: "",
workingRecord: "",
/* form fields */
excludeAllOtherDomains: false,
additionalHosts: [],
additionalMXServers: [],
additionalIPv4Addresses: [],
additionalIPv6Addresses: [],
additionalINCLUDEItems: [],
});
return $scope.init();
};
CONTROLLER_INJECTABLES.push(CONTROLLER);
var app = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
app.controller(CONTROLLER_NAME, CONTROLLER_INJECTABLES);
return {
class: CONTROLLER,
namespace: MODULE_NAMESPACE,
};
}
);
/*
# email_deliverability/controller/manageDomainDKIM.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(
'shared/js/email_deliverability/views/manageDomainDKIM',[
"angular",
"lodash",
"cjt/util/locale",
"shared/js/email_deliverability/services/domains",
"cjt/directives/copyField",
"cjt/modules",
"cjt/services/cpanel/componentSettingSaverService",
"cjt/directives/callout",
"cjt/directives/multiFieldEditorItem",
"cjt/directives/multiFieldEditor",
"cjt/directives/actionButtonDirective",
],
function(angular, _, LOCALE, DomainsService, CopyField) {
"use strict";
/**
* Controller for Managing a Domain
*
* @module ManageDomainController
* @memberof cpanel.emailDeliverability
*
*/
var MODULE_NAMESPACE = "shared.emailDeliverability.views.manageDomainDKIM";
var MODULE_REQUIREMENTS = [
DomainsService.namespace,
CopyField.namespace,
];
var CONTROLLER_NAME = "ManageDomainDKIMController";
var CONTROLLER_INJECTABLES = ["$scope", "$location", "$routeParams", "DomainsService", "alertService", "componentSettingSaverService", "ADD_RESOURCE_PANEL"];
var CONTROLLER = function($scope, $location, $routeParams, $domainsService, $alertService, $CSSS, ADD_RESOURCE_PANEL) {
/**
*
* Update the scope with the working domain records
*
*/
$scope.getWorkingRecords = function getWorkingRecords() {
$scope.suggestedRecord = $scope.currentDomain.getSuggestedRecord("spf");
$scope.workingRecord = $scope.suggestedRecord;
var dkimRecords = $scope.currentDomain.getRecords(["dkim"]);
$scope.currentRecord = dkimRecords[0] ? dkimRecords[0].current : "";
};
/**
*
* Initate the view
*
*/
$scope.init = function init() {
var domains = $domainsService.getAll();
if (!$scope.currentDomain && domains.length > 1) {
$alertService.add({
"message": LOCALE.maketext("You did not specify a domain to manage."),
"type": "danger",
});
$location.path("/").search("");
return;
} else if (domains.length === 1) {
$scope.currentDomain = domains[0];
}
$scope.getWorkingRecords();
if (!$scope.suggestedRecord) {
$domainsService.validateAllRecords([$scope.currentDomain]).then($scope.getWorkingRecords);
}
$CSSS.get(CONTROLLER_NAME).then(function(response) {
if (typeof response !== "undefined" && response) {
$scope.showAllHelp = response.showAllHelp;
}
});
$CSSS.register(CONTROLLER_NAME);
$scope.$on("$destroy", function() {
$CSSS.unregister(CONTROLLER_NAME);
});
};
/**
*
* Toggle the visible help for the form
*
*/
$scope.toggleHelp = function toggleHelp() {
$scope.showAllHelp = !$scope.showAllHelp;
$scope.$broadcast("showHideAllChange", $scope.showAllHelp);
};
/**
*
* Update the NVData saved aspects of this view
*
*/
$scope.saveToComponentSettings = function saveToComponentSettings() {
$CSSS.set(CONTROLLER_NAME, {
showAllHelp: $scope.showAllHelp,
});
};
/**
*
* Verify if a user has nameserver authority for the current domain
*
* @returns {Boolean} representative of nameserver authority
*/
$scope.hasNSAuthority = function hasNSAuthority() {
return $scope.currentDomain.hasNSAuthority;
};
/**
*
* Toggle the Confirm Download DKIM message
*
*/
$scope.requestConfirmDownloadDKIMKey = function requestConfirmDownloadDKIMKey() {
$scope.confirmDKIMDownloadRequest = true;
};
/**
*
* Post API Processing Function for confirmRevealDKIMKey
*
* @private
*
* @param {Object} dkimKeyObj API result DKIM Key Object {pem:...}
*/
$scope._getPrivateDKIMKeyLoaded = function _getPrivateDKIMKeyLoaded(dkimKeyObj) {
$scope.dkimPrivateKey = dkimKeyObj.pem;
};
/**
*
* Download the DKIM Key and display it
*
* @returns {Promise} fetchPrivateDKIMKey promise
*/
$scope.confirmRevealDKIMKey = function confirmRevealDKIMKey() {
return $domainsService.fetchPrivateDKIMKey($scope.currentDomain)
.then($scope._getPrivateDKIMKeyLoaded)
.finally($scope.cancelRevealDKIMKey);
};
/**
*
* Close teh DKIMKey Download Confirmation
*
*/
$scope.cancelRevealDKIMKey = function cancelRevealDKIMKey() {
$scope.confirmDKIMDownloadRequest = false;
};
angular.extend($scope, {
currentDomain: $domainsService.findDomainByName($routeParams["domain"]),
resourcesPanelTemplate: ADD_RESOURCE_PANEL,
showAllHelp: false,
currentRecord: "",
workingRecord: "",
});
$scope.init();
};
CONTROLLER_INJECTABLES.push(CONTROLLER);
var app = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
app.controller(CONTROLLER_NAME, CONTROLLER_INJECTABLES);
return {
class: CONTROLLER,
namespace: MODULE_NAMESPACE,
};
}
);
/*
# email_deliverability/directives/recordStatus.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(
'shared/js/email_deliverability/directives/recordStatus',[
"angular",
"cjt/util/locale",
"cjt/core",
],
function(angular, LOCALE, CJT) {
"use strict";
/**
* Record Status
*
* @module record-status
* @restrict EA
*
* @memberof cpanel.emailDeliverability
*
* @example
* <td record-status
* domain="{domain:'domain.com'}"
* header="{field: 'headerField' label: 'Header'}" ></td>
*
*/
var RELATIVE_PATH = "shared/js/email_deliverability/directives/recordStatus.phtml";
var TEMPLATE_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : CJT.buildPath(RELATIVE_PATH);
var MODULE_NAMESPACE = "cpanel.emailDeliverabilitty.recordStatus.directive";
var MODULE_REQUIREMENTS = [];
var CONTROLLER_INJECTABLES = ["$scope"];
var CONTROLLER = function RecordStatusController($scope) {
/**
* Generates a status icon class based on record validity
*
* @method _getStatusIconClass
* @private
*
* @memberof RecordStatusController
*
* @param {Boolean} A boolean indicating whether or not the records being checked were valid
* @param {Boolean} A boolean indicating whether or not a DNS lookup error occurred
*
* @return {Boolean} returns a string of font awesome classes and colors
*
*/
$scope._getStatusIconClass = function _getStatusIconClass(valid, nsError) {
if (nsError) {
return "fa-times text-danger";
}
if (valid) {
return "fa-check text-success";
}
return "fa-exclamation-triangle text-warning";
};
/**
* Generates a status description based on record validity
*
* @method _getStatusDescription
* @private
*
* @param {Boolean} A boolean indicating whether or not the records being checked were valid
* @param {Boolean} A boolean indicating whether or not a DNS lookup error occurred
*
* @return {Boolean} returns a string description of the status
*
*/
$scope._getStatusDescription = function _getStatusDescription(valid, nsError) {
if (nsError) {
return LOCALE.maketext("One or more [asis,DNS] errors occurred while validating this domain.");
}
if (valid) {
return LOCALE.maketext("No problems exist on this domain.");
}
return LOCALE.maketext("One or more problems exist on this domain.");
};
/**
* Generates a status label based on record validity
*
* @method _getStatusLabel
* @private
*
* @param {Boolean} A boolean indicating whether or not the records being checked were valid
* @param {Boolean} A boolean indicating whether or not a DNS lookup error occurred
*
* @return {Boolean} returns a string label of the status
*
*/
$scope._getStatusLabel = function _getStatusLabel(valid, nsError) {
if ( nsError ) {
return LOCALE.maketext("[asis,DNS] Errors Occurred");
}
if (valid) {
return LOCALE.maketext("Valid");
}
return LOCALE.maketext("Problems Exist");
};
/**
* Update the status variables this record from the domain status
* Called by the watcher.
*
* @method _getStatusLabel
* @private
*
*/
$scope._updateStatus = function _updateStatus() {
if (!$scope.domain || !$scope.domain.recordsLoaded) {
$scope.recordLoading = true;
} else {
var someRecordsFail = $scope.records.some(function(record) {
return !$scope.domain.isRecordValid(record);
});
var hadNSError = $scope.records.some(function(record) {
return $scope.domain.recordHadNSError(record);
});
var recordsValid = !someRecordsFail;
angular.extend($scope, {
statusIconClass: $scope._getStatusIconClass(recordsValid, hadNSError),
statusLabel: $scope._getStatusLabel(recordsValid, hadNSError),
statusDescription: $scope._getStatusDescription(recordsValid, hadNSError),
recordLoading: false,
});
}
};
$scope.getLoadingMessage = function getLoadingMessage() {
if (!$scope.domain) {
return "";
}
if ($scope.domain.reloadingIn) {
return LOCALE.maketext("Rechecking the server records in [quant,_1,second,seconds] …", $scope.domain.reloadingIn);
} else {
return LOCALE.maketext("Loading …");
}
};
var unwatch = $scope.$watch(function() {
if (!$scope.domain) {
return false;
}
return $scope.domain.recordsLoaded;
}, $scope._updateStatus);
if ($scope.domain) {
$scope._updateStatus();
}
$scope.$on("$destroy", function() {
unwatch();
});
};
var module = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
var DIRECTIVE_LINK = function(scope, element, attrs) {
scope.recordLoading = true;
};
module.directive("recordStatus", function itemListerItem() {
return {
templateUrl: TEMPLATE_PATH,
scope: {
parentID: "@id",
records: "=",
domain: "=",
},
restrict: "EA",
replace: false,
link: DIRECTIVE_LINK,
controller: CONTROLLER_INJECTABLES.concat(CONTROLLER),
};
});
return {
"class": CONTROLLER,
"namespace": MODULE_NAMESPACE,
"link": DIRECTIVE_LINK,
"template": TEMPLATE_PATH,
};
}
);
/*
# email_deliverability/directives/domainListerViewDirective.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(
'shared/js/email_deliverability/directives/edDomainListerViewDirective',[
"angular",
"lodash",
"cjt/core",
"cjt/util/locale",
"shared/js/email_deliverability/directives/recordStatus",
"shared/js/email_deliverability/filters/htmlSafeString",
"shared/js/email_deliverability/services/domains",
],
function(angular, _, CJT, LOCALE, RecordStatusDirective, SafeStringFilter, DomainsService) {
"use strict";
/**
* Domain Lister View is a view that pairs with the item lister to
* display domains and docroots as well as a manage link. It must
* be nested within an item lister
*
* @module domain-lister-view
* @restrict EA
* @memberof cpanel.emailDeliverability
*
* @example
* <item-lister>
* <domain-lister-view></domain-lister-view>
* </item-lister>
*
*/
var MODULE_NAMESPACE = "cpanel.emailDeliverabilitty.domainListerView.directive";
var MODULE_REQUIREMENTS = [ RecordStatusDirective.namespace, DomainsService.namespace, SafeStringFilter.namespace ];
var RELATIVE_PATH = "shared/js/email_deliverability/directives/edDomainListerViewDirective.ptt";
var TEMPLATE_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : CJT.buildPath(RELATIVE_PATH);
var CONTROLLER_INJECTABLES = ["$scope", "DomainsService", "ITEM_LISTER_CONSTANTS", "DOMAIN_TYPE_CONSTANTS", "PAGE"];
var CONTROLLER = function DomainListViewController($scope, $domainsService, ITEM_LISTER_CONSTANTS, DOMAIN_TYPE_CONSTANTS, PAGE) {
$scope.DOMAIN_TYPE_CONSTANTS = DOMAIN_TYPE_CONSTANTS;
$scope.EMAIL_ACCOUNTS_APP_EXISTS = PAGE.EMAIL_ACCOUNTS_APP_EXISTS;
$scope.webserverRoleAvailable = PAGE.hasWebServerRole;
$scope.recordsToCheck = $domainsService.getSupportedRecordTypes();
$scope._confirmingRepairDomains = {};
/**
* Get the list of domains
*
* @method getDomains
*
* @public
*
* @return {Array<Domain>} returns an array of Domain objects
*
*/
$scope.getDomains = function getDomains() {
return $scope.domains;
};
$scope.escapeString = _.escape;
/**
*
* Get the row class based on the records status for the domain
*
* @param {Domain} domain domain object to obtain the status from
* @returns {String|Boolean} will return the status if applicable, or false if not
*/
$scope.getDomainRowClasses = function getDomainRowClasses(domain) {
if (domain.recordsLoaded) {
if ( domain.hadAnyNSErrors ) {
domain._rowClasses = "danger";
} else if (!domain.recordsValid) {
domain._rowClasses = "warning";
} else {
return false;
}
}
return domain._rowClasses;
};
/**
*
* Determine if a message should be shown that record types are filtered
*
* @param {Domain} domain Object to determine the status of
* @returns {Boolean} value of whether some records are filtered from view
*/
$scope.showRecordsFilteredMessage = function showRecordsFilteredMessage(domain) {
if (!domain.recordsLoaded) {
return false;
}
if (!domain.recordsValid) {
return false;
}
var loadedRecords = domain.getRecordTypesLoaded();
var supportedRecords = $domainsService.getSupportedRecordTypes();
return loadedRecords.length !== supportedRecords.length;
};
/**
*
* Show the "Confirm Repair" dialog
*
* @param {Domain} domain Subject to display the dialog for
*/
$scope.confirmRepair = function confirmRepair(domain) {
if ( window.mixpanel ) {
window.mixpanel.track("Email-Deliverability-Repair-Menu-Expand");
}
$scope._confirmingRepairDomains[domain.domain] = true;
};
/**
*
* Cancel displaying the confirm repair dialog
*
* @param {Domain} domain Subject to cancel the dialog for
*/
$scope.cancelConfirmRepair = function cancelConfirmRepair(domain) {
if ( window.mixpanel ) {
window.mixpanel.track("Email-Deliverability-Repair-Menu-Closed");
}
$scope._confirmingRepairDomains[domain.domain] = false;
};
/**
*
* Determine if the Confirm Repair dialog is shown
*
* @param {Domain} domain Subject to check the status of the confirm dialog for
* @returns {Boolean} is the dialog shown
*/
$scope.isConfirmingRepair = function isConfirmingRepair(domain) {
return $scope._confirmingRepairDomains[domain.domain];
};
function _getZoneLockDomain(domain) {
var zoneObj = $domainsService.getDomainZoneObject(domain);
return zoneObj && zoneObj.getLockDomain();
}
$scope.getDomainLockedMessage = function getDomainLockedMessage(domain) {
if (domain.recordsLoaded && !domain.hasNSAuthority) {
// Not authoritative, return relevant message
if (domain.nameservers.length) {
return LOCALE.maketext("This system does not control [asis,DNS] for the “[_1]” domain. Contact the person responsible for the [list_and_quoted,_3] [numerate,_2,nameserver,nameservers] and request that they update the records.", domain.domain, domain.nameservers.length, domain.nameservers);
}
return LOCALE.maketext("This system does not control [asis,DNS] for the “[_1]” domain, and the system did not find any authoritative nameservers for this domain. Contact your domain registrar to verify this domain’s registration.", domain.domain);
}
if (!domain.recordsLoadingIn && _getZoneLockDomain(domain)) {
// Locked while updates are occuring, return relevant message
return LOCALE.maketext("You cannot modify this domain while a domain on the “[_1]” zone is updating.", domain.zone);
}
return false;
};
/**
*
* String to describe why this domain be auto-repaired.
*
* @param {Domain} domain Subject of the auto-repair inquiry
* @returns {String} Localized description of why auto-repair isn’t possible, or undefined if auto-repair is indeed possible.
*/
$scope.whyCannotAutoRepairDomain = function whyCannotAutoRepairDomain(domain) {
var msg;
if (!domain.recordsLoaded) {
msg = LOCALE.maketext("Loading …");
} else if (!domain.hasNSAuthority) {
msg = LOCALE.maketext("Automatic repair is not available for this domain because this system is not authoritative for this domain.");
} else {
var lockDomain = _getZoneLockDomain(domain);
if ( lockDomain ) {
msg = LOCALE.maketext("Automatic repair is currently unavailable for this domain. You must wait until “[_1]”’s operation completes because these two domains share the same DNS zone.", lockDomain);
} else if ( domain.isRecordValid("spf") && domain.isRecordValid("dkim") ) {
msg = LOCALE.maketext("This domain’s DKIM and SPF configurations are valid.");
}
}
return msg;
};
/**
* dispatches a TABLE_ITEM_BUTTON_EVENT event
*
* @method actionButtonClicked
*
* @public
*
* @param {String} type type of action taken
* @param {String} domain the domain on which the action occurred
*
* @return {Boolean} returns the result of the $scope.$emit function
*
*/
$scope.actionButtonClicked = function actionButtonClicked(type, domain) {
return $scope.$emit(ITEM_LISTER_CONSTANTS.TABLE_ITEM_BUTTON_EVENT, { actionType: type, item: domain, interactionID: domain.domain });
};
var recordTypeOrder = ["dkim", "spf", "ptr"];
function _getSortedRecordLabel(recordsWithIssue) {
if (recordsWithIssue.length === 0) {
return false;
}
recordsWithIssue.sort( function(a, b) {
a = recordTypeOrder.indexOf(a);
b = recordTypeOrder.indexOf(b);
return ( a < b ? -1 : a > b ? 1 : 0 );
} );
recordsWithIssue = recordsWithIssue.map(function(record) {
record = record.toUpperCase();
if (record === "PTR") {
return LOCALE.maketext("Reverse [asis,DNS]");
}
return record.toUpperCase();
});
return LOCALE.list_and(recordsWithIssue);
}
/**
*
* Get a list of record types with issues
*
* @param {Domain} domain Subject of inquiry
* @returns {Array<String>} list of record types with issues
*/
$scope.getRecordTypesWithIssues = function getRecordTypesWithIssues(domain) {
return _getSortedRecordLabel( domain.getRecordTypesWithIssues() );
};
/**
* Get a list of record types with DNS lookup errors
*
* @param {Domain} domain Subject of inquiry
* @returs {Array<String>} list of record types with DNS lookup errors
*/
$scope.getRecordTypesWithNSErrors = function getRecordTypesWithNSErrors(domain) {
return _getSortedRecordLabel( domain.getRecordTypesWithNSErrors() );
};
$scope.localDKIMExists = function(domain) {
return $domainsService.localDKIMExists(domain);
};
$scope.ensureLocalDKIMKeyExists = function(domain) {
return $domainsService.ensureLocalDKIMKeyExists(domain);
};
};
var module = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
module.value("PAGE", PAGE);
var DIRECTIVE_LINK = function($scope, $element, $attrs, $ctrl) {
$scope.domains = [];
$scope.headerItems = $ctrl.getHeaderItems();
$scope.updateView = function updateView(viewData) {
$scope.domains = viewData;
};
$ctrl.registerViewCallback($scope.updateView.bind($scope));
$scope.$on("$destroy", function() {
$ctrl.deregisterViewCallback($scope.updateView);
});
};
module.directive("domainListerView", function itemListerItem() {
return {
templateUrl: TEMPLATE_PATH,
restrict: "EA",
replace: true,
require: "^itemLister",
link: DIRECTIVE_LINK,
controller: CONTROLLER_INJECTABLES.concat(CONTROLLER),
};
});
return {
"class": CONTROLLER,
"namespace": MODULE_NAMESPACE,
"link": DIRECTIVE_LINK,
"template": TEMPLATE_PATH,
};
}
);
/*
# email_deliverability/directives/tableShowingDirecitve.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(
'shared/js/email_deliverability/directives/tableShowingDirective',[
"angular",
"cjt/util/locale",
"cjt/core",
],
function(angular, LOCALE, CJT) {
"use strict";
/**
* Directive to render the "Showing 1 - 4 of 10"
*
* @module table-showing
* @memberof cpanel.emailDeliverability
*
* @param {Number} start first number in range ([1]-4)
* @param {Number} limit second number in range (1-[4])
* @param {Number} total total number of items (10)
*
* @example
* <table-showing start="1" limit="4" total="10"></table-showing>
*
*/
var RELATIVE_PATH = "shared/js/email_deliverability/directives/tableShowingDirective.phtml";
var TEMPLATE_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : CJT.buildPath(RELATIVE_PATH);
var MODULE_NAMESPACE = "shared.emailDeliverability.tableShowing.directive";
var module = angular.module(MODULE_NAMESPACE, []);
var CONTROLLER = function($scope) {
/**
* Get the rendered string from LOCALE
*
* @method getShowingText
* @public
*
* @return {String} localized string
*
*/
$scope.getShowingText = function getShowingText() {
return LOCALE.maketext("[_1] - [_2] of [_3]", $scope.start, $scope.limit, $scope.total);
};
};
module.directive("tableShowing", function tableShowing() {
return {
templateUrl: TEMPLATE_PATH,
restrict: "EA",
scope: {
start: "=",
limit: "=",
total: "=",
},
transclude: true,
controller: ["$scope", CONTROLLER],
};
});
return {
"class": CONTROLLER,
"namespace": MODULE_NAMESPACE,
"template": TEMPLATE_PATH,
};
}
);
/*
# email_deliverability/directives/itemLister.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(
'shared/js/email_deliverability/directives/itemLister',[
"angular",
"lodash",
"cjt/core",
"shared/js/email_deliverability/directives/tableShowingDirective",
"ngSanitize",
"ngRoute",
"cjt/modules",
"cjt/directives/pageSizeButtonDirective",
"cjt/services/cpanel/componentSettingSaverService",
"cjt/directives/toggleSortDirective",
"cjt/directives/searchDirective",
"cjt/directives/pageSizeDirective",
"cjt/filters/startFromFilter",
"cjt/decorators/paginationDecorator",
],
function(angular, _, CJT, TableShowingDirective) {
"use strict";
/**
* Item Lister combines the typical table functions, pageSize,
* showing, paginator, search, and allows you to plug in multiple
* views.
*
* @module item-lister
* @memberof cpanel.emailDeliverability
* @restrict EA
*
* @param {String} id disseminated to other objects
* @param {Array} items Items that will be paginated, array of objs
* @param {Array} header-items represents the columns of the table
*
* @example
* <item-lister
* id="MyItemLister"
* items="[a,b,c,d,e]"
* header-items="[{field:"blah",label:"Blah",sortable:false}]">
* <my-item-lister-view></my-item-lister-view>
* </item-lister>
*
*/
var MODULE_REQUIREMENTS = [
TableShowingDirective.namespace,
"ngRoute",
"ngSanitize",
"cjt2.filters.startFrom",
];
var MODULE_NAMESPACE = "shared.emailDeliverability.itemLister.directive";
var CSSS_COMPONENT_NAME = "domainsItemLister";
var CONTROLLER_INJECTABLES = ["$routeParams", "$scope", "$filter", "$log", "$window", "componentSettingSaverService", "ITEM_LISTER_CONSTANTS"];
var CONTROLLER = function itemListerController($routeParams, $scope, $filter, $log, $window, $CSSS, ITEM_LISTER_CONSTANTS) {
$scope.viewCallbacks = [];
var filters = {
filter: $filter("filter"),
orderBy: $filter("orderBy"),
startFrom: $filter("startFrom"),
limitTo: $filter("limitTo"),
};
/**
*
* Filter items based on filterValue
*
* @private
*
* @param {Array} filteredItems items to filter
* @returns {Array} filtered items
*/
$scope._filter = function _filter(filteredItems) {
// filter list based on search text
if ($scope.filterValue !== "") {
return filters.filter(filteredItems, { domain: $scope.filterValue }, false);
}
return filteredItems;
};
/**
*
* Sort items based on sort.sortDirection and sort.sortBy
*
* @private
*
* @param {Array} filteredItems items to sort
* @returns {Array} sorted items
*/
$scope._sort = function _sort(filteredItems) {
// sort the filtered list
if ($scope.sort.sortDirection !== "" && $scope.sort.sortBy !== "") {
return filters.orderBy(filteredItems, $scope.sort.sortBy, $scope.sort.sortDirection !== "asc");
}
return filteredItems;
};
/**
*
* Paginate the items based on pageSize and currentPage
*
* @private
*
* @param {Array} filteredItems items to paginate
* @returns {Array} paginated items
*/
$scope._paginate = function _paginate(filteredItems) {
// filter list based on page size and pagination
if ($scope.totalItems > _.min($scope.pageSizes)) {
var start = ($scope.currentPage - 1) * $scope.pageSize;
var limit = $scope.pageSize;
filteredItems = filters.startFrom(filteredItems, start);
filteredItems = filters.limitTo(filteredItems, limit);
$scope.showPager = true;
// table statistics
$scope.start = start + 1;
$scope.limit = start + filteredItems.length;
} else {
// hide pager and pagination
$scope.showPager = false;
if (filteredItems.length === 0) {
$scope.start = 0;
} else {
// table statistics
$scope.start = 1;
}
$scope.limit = filteredItems.length;
}
return filteredItems;
};
/**
*
* Update the NVData stored settings for the directive
*
* @private
*
* @param {String} lastInteractedItem last item interacted with
*/
$scope._updatedListerState = function _updatedListerState(lastInteractedItem) {
if ($scope.loadingInitialState) {
return;
}
var storedSettings = {
totalItems: $scope.totalItems,
currentPage: $scope.currentPage,
pageSize: $scope.pageSize,
start: $scope.start,
limit: $scope.limit,
lastInteractedItem: lastInteractedItem,
filterValue: $scope.filterValue,
sort: {
sortDirection: $scope.sort.sortDirection,
sortBy: $scope.sort.sortBy,
},
};
$CSSS.set(CSSS_COMPONENT_NAME, storedSettings);
};
/**
*
* Event function called on interaction with an item
*
* @private
*
* @param {Object} event event object
* @param {Object} parameters event parameters {interactionID:...}
*/
$scope._itemInteracted = function _itemInteracted(event, parameters) {
if (parameters.interactionID) {
$scope._updatedListerState(parameters.interactionID);
}
};
/**
* Register a callback to call on the update of the lister
*
* @method registerViewCallback
*
* @param {Function} callback function to callback to
*
*/
this.registerViewCallback = function registerViewCallback(callback) {
$scope.viewCallbacks.push(callback);
callback($scope.filteredItems);
};
/**
* Get the header items
*
* @method getHeaderItems
*
* @return {Array} returns array of objects containing labels
*
*/
this.getHeaderItems = function getHeaderItems() {
return $scope.headerItems;
};
/**
* Deregister a callback (useful for view changes)
*
* @method deregisterViewCallback
*
* @param {Function} callback callback to deregister
*
*/
this.deregisterViewCallback = function deregisterViewCallback(callback) {
for (var i = $scope.viewCallbacks.length - 1; i >= 0; i--) {
if ($scope.viewCallbacks[i] === callback) {
$scope.viewCallbacks.splice(i, 1);
}
}
};
/**
* Function called to rebuild the view from internal components
*
* @return {Array} filtered items
*/
$scope.fetch = function fetch() {
var filteredItems = [];
filteredItems = $scope._filter($scope.items) || [];
// update the total items after search
$scope.totalItems = filteredItems.length;
filteredItems = $scope._sort(filteredItems);
filteredItems = $scope._paginate(filteredItems);
$scope.filteredItems = filteredItems;
$scope._updatedListerState();
angular.forEach($scope.viewCallbacks, function updateCallback(viewCallback) {
viewCallback($scope.filteredItems);
});
$scope.$emit(ITEM_LISTER_CONSTANTS.ITEM_LISTER_UPDATED_EVENT, { meta: { filterValue: $scope.filterValue }, items: filteredItems });
return filteredItems;
};
/**
* Return the focus of the page to the search at the top and scroll to it
*
*/
$scope.focusSearch = function focusSearch() {
angular.element(document).find("#" + $scope.parentID + "_search_input").focus();
$window.scrollTop = 0;
};
/**
*
* Event function for a table configuration being clicked
*
* @param {Object} config which config was clicked
*/
$scope.tableConfigurationClicked = function tableConfigurationClicked(config) {
$scope.$emit(ITEM_LISTER_CONSTANTS.TABLE_ITEM_BUTTON_EVENT, { actionType: "tableConfigurationClicked", config: config });
};
$scope.$on(ITEM_LISTER_CONSTANTS.TABLE_ITEM_BUTTON_EVENT, $scope._itemInteracted);
angular.extend($scope, {
maxPages: 5,
totalItems: ( $scope.items || [] ).length,
filteredItems: [],
currentPage: 1,
pageSize: 20,
pageSizes: [10, 20, 50],
start: 0,
limit: 20,
filterValue: "",
sort: {
sortDirection: "asc",
sortBy: ( $scope.headerItems || [] ).length ? $scope.headerItems[0].field : "",
},
}, {
filterValue: $routeParams["q"],
});
/**
*
* Initiate CSSS saved state is loaded
*
* @private
*
* @param {Object} initialState saved state for directive
*/
$scope._savedStateLoaded = function _savedStateLoaded(initialState) {
angular.extend($scope, initialState, {
filterValue: $routeParams["q"],
});
};
var registerSuccess = $CSSS.register(CSSS_COMPONENT_NAME);
if ( registerSuccess ) {
$scope.loadingInitialState = true;
registerSuccess.then($scope._savedStateLoaded, $log.error).finally(function() {
$scope.loadingInitialState = false;
$scope.fetch();
});
}
$scope.$on("$destroy", function() {
$CSSS.unregister(CSSS_COMPONENT_NAME);
});
$scope.fetch();
$scope.$watch("items", $scope.fetch);
};
var RELATIVE_PATH = "shared/js/email_deliverability/directives/itemLister.ptt";
var TEMPLATE_PATH = CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : CJT.buildPath(RELATIVE_PATH);
var DIRECTIVE_INJECTABLES = ["$window", "$log", "componentSettingSaverService"];
var DIRECTIVE_LINK = function(scope, element) {
scope.controlsBlock = null;
scope.contentBlock = null;
/**
*
* Attach controls to the view
*
* @private
*
* @param {HTMLElement} elem html element to transclude as controls
*/
scope._attachControls = function _attachControls(elem) {
scope.controlsBlock.append(elem);
};
/**
*
* Attach Other items to view
*
* @private
*
* @param {HTMLElement} elem element to treat as the table body
*/
scope._attachOthers = function _attachOthers(elem) {
elem.setAttribute("id", scope.parentID + "_transcludePoint");
elem.setAttribute("ng-if", "filteredItems.length");
scope.contentBlock.replaceWith(elem);
};
/**
*
* Attach a transclude item
*
* @private
*
* @param {HTMLElement} elem html element to determine attachment point for
*/
scope._attachTransclude = function _attachTransclude(elem) {
if (angular.element(elem).hasClass("lister-controls")) {
scope._attachControls(elem);
} else {
scope._attachOthers(elem);
}
};
/**
*
* Find transclude items to attach to the view
*
*/
scope._findTranscludes = function _findTranscludes() {
// *cackles maniacally*
// *does a multi-transclude anyways*
scope.controlsBlock = element.find("#" + scope.parentID + "_transcludedControls");
scope.contentBlock = element.find("#" + scope.parentID + "_transcludePoint");
var transcludedBlock = element.find("div.transcluded");
var transcludedItems = transcludedBlock.children();
angular.forEach(transcludedItems, scope._attachTransclude, scope);
transcludedBlock.remove();
};
/* There is a dumb race condition here */
/* So we have to delay to get the content transcluded */
setTimeout(scope._findTranscludes, 2);
};
var DIRECTIVE = function itemLister($window, $log, $CSSS) {
return {
templateUrl: TEMPLATE_PATH,
restrict: "EA",
scope: {
parentID: "@id",
items: "=",
headerItems: "=",
tableConfigurations: "=",
},
transclude: true,
replace: true,
link: DIRECTIVE_LINK,
controller: CONTROLLER_INJECTABLES.concat(CONTROLLER),
};
};
var module = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
module.directive("itemLister", DIRECTIVE_INJECTABLES.concat(DIRECTIVE));
return {
"class": CONTROLLER,
"namespace": MODULE_NAMESPACE,
"link": DIRECTIVE_LINK,
"template": TEMPLATE_PATH,
};
}
);
/*
# email_deliverability/controllers/listDomains.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(
'shared/js/email_deliverability/views/listDomains',[
"angular",
"lodash",
"cjt/util/locale",
"shared/js/email_deliverability/directives/edDomainListerViewDirective",
"shared/js/email_deliverability/directives/tableShowingDirective",
"shared/js/email_deliverability/directives/itemLister",
"shared/js/email_deliverability/services/domains",
"cjt/modules",
],
function(angular, _, LOCALE, DomainListerViewDirective, TableShowingDirective, ItemListerDirective, DomainsService) {
"use strict";
/**
* Controller for Listing Domains
*
* @module ListDomainsController
* @memberof cpanel.emailDeliverability
*
*/
/**
* @class TableHeader
* @memberof cpanel.emailDeliverability
*
* @property {String} field which field the column will sort by
* @property {String} label Label descriptor of the column
* @property {boolean} sortable is this column sortable
*/
var TableHeader = function() {
this.field = "";
this.label = "";
this.sortable = false;
};
/**
* Factory to create a TableHeader
*
* @module TableHeaderFactory
* @memberof cpanel.emailDeliverability
*
*/
function createTableHeader(field, sortable, label, description, hiddenOnMobile) {
function _makeLabel() {
if (!description) {
return label;
}
return label + " <span class='thead-desc'>" + description + "</span>";
}
var tableHeader = new TableHeader();
tableHeader.field = field;
tableHeader.label = _makeLabel();
tableHeader.sortable = sortable;
tableHeader.hiddenOnMobile = hiddenOnMobile;
return tableHeader;
}
var MODULE_NAMESPACE = "shared.emailDeliverability.views.listDomains";
var MODULE_REQUIREMENTS = [
TableShowingDirective.namespace,
ItemListerDirective.namespace,
DomainListerViewDirective.namespace,
DomainsService.namespace,
];
var CONTROLLER_NAME = "ListDomainsController";
var CONTROLLER_INJECTABLES = ["$scope", "$timeout", "$location", "$log", "$q", "alertService", "DomainsService", "initialDomains", "ITEM_LISTER_CONSTANTS", "PAGE"];
var CONTROLLER = function ListDomainsController($scope, $timeout, $location, $log, $q, $alertService, $domainsService, initialDomains, ITEM_LISTER_CONSTANTS, PAGE) {
/**
* Called when a ITEM_LISTER_CONSTANTS.TABLE_ITEM_BUTTON_EVENT is $emit'd
*
* @method
* @param {Object} event even object emitted
* @param {Object} parameters parameters object emitted by the source
*
*/
$scope.itemChangeRequested = function _itemChangeRequested(event, parameters) {
switch (parameters.actionType) {
case "manage":
$scope.manageDomain(parameters.item.domain);
break;
case "repair":
$scope.repairDomain(parameters.item.domain);
break;
case "repairAll":
$scope.repairAllDomainRecords(parameters.domain);
break;
}
};
/**
*
* Function that when called moves the user to the manage view for a domain
*
* @public
*
* @param {String} domain string domain name
*/
$scope.manageDomain = function _manageDomain(domain) {
if ( window.mixpanel ) {
window.mixpanel.track("Email-Deliverability-Manage-Clicked");
}
$domainsService.markViewLoad();
$location.path("manage").search("domain", domain);
};
/**
*
* Get the suggested for a record type for a specific domain
*
* @public
*
* @param {Domain} domainObj domain from which to get the suggested record
* @param {string} recordType type of record to fetch
* @returns {String} string suggested record for the domain
*/
$scope.getSuggestedRecord = function getSuggestedRecord(domainObj, recordType) {
return domainObj.getSuggestedRecord(recordType);
};
/**
*
* Repair all repairable records for a specific domain
*
* @param {Domain} domain domain to repair
* @returns {Promise} returns the repair promise
*/
$scope.repairDomain = function repairDomain(domain) {
var domainObj = $domainsService.findDomainByName(domain);
$alertService.clear();
var recordTypes = $scope.getDisplayedRecordTypes();
recordTypes = recordTypes.filter(function(recordType) {
if (recordType !== "ptr" && !domainObj.isRecordValid(recordType)) {
return true;
}
return false;
});
var records = recordTypes.map(function(recordType) {
var newRecord = $scope.getSuggestedRecord(domainObj, recordType);
return newRecord.value;
});
return $domainsService.repairDomain(domain, recordTypes, records);
};
/**
*
* Get the record types that are not disabled in this view
*
* @returns {Array<String>} array of record types
*/
$scope.getDisplayedRecordTypes = function getDisplayedRecordTypes() {
return $domainsService.getSupportedRecordTypes();
};
/**
*
* Fetch the validation data for the displayed domains
*
* @private
*
* @param {Array<Domain>} domains array of domains to update
* @returns {Promise} promise for the validateAllRecords call
*/
$scope._fetchTableData = function _fetchTableData(domains) {
var recordTypes = $scope.getDisplayedRecordTypes();
return $domainsService.validateAllRecords(domains, recordTypes);
};
/**
*
* Debounce call for fetching domains (prevents doubling up of fetch calls)
*
* @private
*
*/
$scope._beginDelayedFetch = function _beginDelayedFetch() {
if ($scope.currentTimeout) {
$timeout.cancel($scope.currentTimeout);
$scope.currentTimeout = null;
}
$scope.currentTimeout = $timeout($scope._fetchTableData, 500, true, $scope.pageDomains);
};
/**
*
* Event capture call for emitted ITEM_LISTER_CONSTANTS.ITEM_LISTER_UPDATED_EVENT events
*
* @private
*
* @param {Object} event event object
* @param {Object} parameters event parameters {meta:{filterValue:...},items:...}
*/
$scope._itemListerUpdated = function _itemListerUpdated(event, parameters) {
$scope.itemListerMeta = parameters.meta;
$scope.currentSearchFilterValue = $scope.itemListerMeta.filterValue;
$scope.pageDomains = parameters.items;
$scope._beginDelayedFetch();
};
/**
*
* Build the table headers for the lister
*
* @private
*
* @returns {Array<TableHeader>} array of table headers
*/
$scope._buildTableHeaders = function _buildTableHeaders() {
var tableHeaderItems = [];
tableHeaderItems.push( createTableHeader( "domain", true, LOCALE.maketext("Domain"), false, false ) );
tableHeaderItems.push( createTableHeader( "status", false, LOCALE.maketext("Email Deliverability Status"), false, true ) );
tableHeaderItems.push( createTableHeader( "actions", false, "", false ) );
return tableHeaderItems;
};
/**
*
* Get a list of filtered domains
*
* @returns {Array<Domain>} list of domains to display
*/
$scope.getFilteredDomains = function getFilteredDomains() {
return $scope.filteredDomains;
};
/**
*
* Function called upon completion of the CSSS load
*
* @private
*
*/
$scope._readyToDisplay = function _readyToDisplay() {
$scope.$on(ITEM_LISTER_CONSTANTS.TABLE_ITEM_BUTTON_EVENT, $scope.itemChangeRequested);
$scope.$on(ITEM_LISTER_CONSTANTS.ITEM_LISTER_UPDATED_EVENT, $scope._itemListerUpdated);
};
/**
*
* Initate the view
*
*/
$scope.init = function init() {
if (initialDomains.length === 1 && PAGE.CAN_SKIP_LISTER) {
$location.path("/manage").search("domain", initialDomains[0].domain);
} else {
$scope.domains = initialDomains;
$scope.filteredDomains = initialDomains;
$scope.tableHeaderItems = $scope._buildTableHeaders();
$scope._readyToDisplay();
}
};
$scope.init();
};
CONTROLLER_INJECTABLES.push(CONTROLLER);
var app = angular.module(MODULE_NAMESPACE, MODULE_REQUIREMENTS);
app.controller(CONTROLLER_NAME, CONTROLLER_INJECTABLES);
return {
class: CONTROLLER,
namespace: MODULE_NAMESPACE,
};
}
);
/*
# email_deliverability/index.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 require, define, PAGE */
/** @namespace cpanel.emailDeliverability */
define(
'app/index',[
"angular",
"lodash",
"cjt/core",
"app/controllers/ROUTES",
"app/controllers/route",
"shared/js/email_deliverability/services/domains",
"shared/js/email_deliverability/filters/htmlSafeString",
"app/decorators/paginationDecorator",
"cjt/io/whm-v1-request",
"cjt/io/whm-v1",
"ngRoute",
"ngAnimate",
"cjt/modules",
"angular-chosen",
"cjt/directives/breadcrumbs",
"cjt/services/alertService",
"cjt/directives/loadingPanel",
"shared/js/email_deliverability/views/manageDomain",
"shared/js/email_deliverability/views/manageDomainSPF",
"shared/js/email_deliverability/views/manageDomainDKIM",
"shared/js/email_deliverability/views/listDomains",
],
function(angular, _, CJT, ROUTES, RouteController, DomainsService, SafeStringFilter, PaginationDecorator, APIRequest) {
"use strict";
CJT.config.html5Mode = false;
/**
* App Controller for Email Deliverability
*
* @module index
*
* @memberof whm.emailDeliverability
*
*/
return function() {
var APP_MODULE_NAME = "whm.emailDeliverability";
var appModules = [
"ngRoute",
"ngAnimate",
"cjt2.whm",
"cjt2.directives.loadingPanel",
"cjt2.services.alert",
DomainsService.namespace,
SafeStringFilter.namespace,
RouteController.namespace,
PaginationDecorator.namespace,
];
var cjtDependentModules = [
"cjt/bootstrap",
];
ROUTES.forEach(function(route) {
appModules.push("shared.emailDeliverability.views." + route.controllerAs);
});
// First create the application
angular.module(APP_MODULE_NAME, appModules);
// Then load the application dependencies
var app = require(cjtDependentModules, function(BOOTSTRAP) {
var app = angular.module(APP_MODULE_NAME);
app.value("PAGE", PAGE);
app.value("ADD_RESOURCE_PANEL", CJT.buildFullPath("email_deliverability/views/additionalResourcesPanel.ptt"));
app.factory("APIInitializer", function() {
var APIInitializer = function() {
function _apiTranslator(module, func) {
if (module === "Batch" && func === "strict") {
return ["", "batch"];
}
return ["", func];
}
function _initialize(module, func) {
var apiCall = new APIRequest.Class();
return apiCall.initialize.apply(apiCall, this.apiTranslator(module, func) );
}
function _buildBatchCommandItem(module, func, paramsObj) {
var commandString = func;
if (paramsObj) {
var commandParams = [];
angular.forEach(paramsObj, function(value, key) {
commandParams.push(key + "=" + encodeURIComponent(value));
});
commandString += "?" + commandParams.join("&");
}
return commandString;
}
this.init = _initialize.bind(this);
this.buildBatchCommandItem = _buildBatchCommandItem.bind(this);
this.apiTranslator = _apiTranslator.bind(this);
};
return new APIInitializer();
});
app.value("ITEM_LISTER_CONSTANTS", {
TABLE_ITEM_BUTTON_EVENT: "TableItemActionButtonEmitted",
ITEM_LISTER_UPDATED_EVENT: "ItemListerUpdatedEvent",
});
app.value("RECORD_STATUS_CONSTANTS", {
VALID: "validRecordStatus",
OUT_OF_SYNC: "outOfSyncRecordStatus",
MISSING: "missingRecordStatus",
TOO_MANY: "tooManyRecordStatus",
LOADING: null,
});
app.config([
"$routeProvider",
"$animateProvider",
function($routeProvider, $animateProvider) {
$animateProvider.classNameFilter(/^((?!no-animate).)*$/);
ROUTES.forEach(function(route) {
$routeProvider.when(route.route, route);
});
$routeProvider.otherwise({
"redirectTo": "/",
});
},
]);
BOOTSTRAP("#content", APP_MODULE_NAME);
});
return app;
};
}
);
Back to Directory
File Manager