Viewing File: /usr/local/cpanel/base/frontend/jupiter/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/util/locale",
        "cjt/core",
    ],
    function(LOCALE, CJT) {

        "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.phtml"),
                "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 a [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 cpanel.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 = "cpanel.emailDeliverability.controllers.route";
        var MODULE_REQUIREMENTS = [];
        var CONTROLLER_NAME = "RouteController";
        var CONTROLLER_INJECTABLES = ["$scope", "$rootScope", "$location", "alertService"];

        var CONTROLLER = function RouteController($scope, $rootScope, $location, $alertService) {

            /**
             * 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";
        var TEMPLATE_PATH = "decorators/pagination.phtml";
        var RELATIVE_PATH = "email_accounts/" + TEMPLATE_PATH;

        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 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,
            template: TEMPLATE_PATH
        };
    }
);

(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 = {
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&#x27;",
        "`": "&#x60;"
      };
      unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
      return text.replace(unsafe_chars, function(chr) {
        return map[chr] || "&amp;";
      });
    };

    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/uapi-request",
        "ngRoute",
        "ngAnimate",
        "angular-chosen",
        "cjt/modules",
        "cjt/io/uapi",
        "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";

        /**
         * App Controller for Email Deliverability
         *
         * @module index
         *
         * @memberof cpanel.emailDeliverability
         *
         */

        return function() {

            var APP_MODULE_NAME = "cpanel.emailDeliverability";

            var appModules = [
                "ngRoute",
                "ngAnimate",
                "cjt2.cpanel",
                "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);
                cjtDependentModules.push("shared/js/email_deliverability/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", "views/additionalResourcesPanel.ptt");

                app.factory("APIInitializer", function() {

                    var APIInitializer = function() {

                        function _initialize(module, func) {

                            var apiCall = new APIRequest.Class();
                            return apiCall.initialize(module, func);

                        }

                        function _buildBatchCommandItem(module, func, paramsObj) {
                            return JSON.stringify([module, func, paramsObj]);
                        }

                        this.init = _initialize.bind(this);
                        this.buildBatchCommandItem = _buildBatchCommandItem.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);

            });

            if ( window.mixpanel ) {
                window.mixpanel.track("Email-Deliverability-Page-Visit");
            }

            return app;
        };
    }
);

Back to Directory File Manager