Viewing File: /usr/local/cpanel/whostmgr/docroot/templates/easyapache4/services/ea4Data.js

/*
# cpanel - whostmgr/docroot/templates/easyapache4/services/ea4Data.js
#                                      Copyright 2025 WebPros International, LLC
#                                                           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(
    [
        "angular",
        "lodash",

        // CJT
        "cjt/io/api",
        "cjt/io/whm-v1-request",
        "cjt/io/whm-v1",

        // Angular components
        "cjt/services/APIService",

        // App components
        "app/services/ea4Util",
        "lz-string",
    ],
    function(angular, _, API, APIREQUEST, WHMv1, APIService, ea4Util, LZString) {
        "use strict";

        var app = angular.module("whm.easyapache4.ea4Data", []);

        app.factory("ea4Data", ["$q", "$location", "ea4Util", "APIService", function($q, $location, ea4Util, APIService) {
            var oData = {
                isReadyForProvision: false,
            };

            oData.updateRegexesForPrefixes = function(prefixes) {
                var allPrefixes = ["ea"].concat(prefixes || []);
                var prefixPattern = "(" + allPrefixes.join("|") + ")";

                oData.mpmRegex = new RegExp(prefixPattern + "-apache\\d{2}-mod[_-]mpm.*", "i");
                oData.modulesRegex = new RegExp(prefixPattern + "-apache\\d{2}-mod.*", "i");
                oData.phpRegex = new RegExp("^" + prefixPattern + "-php\\d{2}$", "i");
                oData.phpExtRegex = new RegExp(prefixPattern + "-php\\d{2}-.*", "i");
                oData.rubyRegex = new RegExp(prefixPattern + "-ruby\\d{2}-.*", "i");
            };

            // Initialize with default 'ea' only regexes
            oData.updateRegexesForPrefixes([]);

            /**
             * Sets or gets the provision value
             *
             * @method provisionReady
             * @param {Boolean} value
             * @return {Boolean}
             */
            oData.provisionReady = function(value) {
                if (typeof value === "boolean") {
                    oData.isReadyForProvision = value;
                } else {
                    return oData.isReadyForProvision;
                }
            };

            /**
             * @method getProfiles
             * @return {Promise}
             */
            oData.getProfiles = function() {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "ea4_list_profiles");

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {

                            // Keep the promise

                            // CJT2 whm-v1.js has parsedResponse wrapper
                            // which is returning wrong data when only 1 key exists
                            // in this response's data.
                            // i.e. when only 'cpanel' key exists in the response,
                            // whm-v1.js -> _reduce_list_data is removing it and returning
                            // and returning data differently.
                            // That is reason 'response.raw.data' is being used here
                            // to have a consistent return always.
                            // the above whm-v1.js method needs to be fixed.
                            deferred.resolve(response.raw.data);
                        } else {

                            // Pass the error along
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            /**
             * @method getVhostsByPhpVersion
             * @param {String} - php version
             * @return {Promise}
             */
            oData.getVhostsByPhpVersion = function(version) {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "php_get_vhosts_by_version");
                apiCall.addArgument("version", version);

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {

                            // Keep the promise
                            deferred.resolve(response.data);
                        } else {

                            // Pass the error along
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            /**
             * TODO: this method needs to handle API failure
             *
             * @method ea4GetCurrentPkgList
             * @return {Promise}
             */
            oData.ea4GetCurrentPkgList = function() {
                var apiCall = new APIREQUEST.Class();
                var apiService = new APIService();

                apiCall.initialize("", "ea4_get_currently_installed_packages");

                var deferred = apiService.deferred(apiCall);
                return deferred.promise;
            };

            /**
             * Returns ea4 meta info from the ea4_metainfo.json
             * It currently contains information about additional pacakges,
             * default PHP handler & default PHP package used.
             *
             * @method getEA4MetaInfo
             * @return {Promise}
             */
            oData.getEA4MetaInfo = function() {
                var apiCall = new APIREQUEST.Class();
                var apiService = new APIService();

                apiCall.initialize("", "ea4_metainfo");

                var deferred = apiService.deferred(apiCall);
                return deferred.promise;
            };

            /**
             * Returns pkgInfoList for additional pacakges.
             *
             * @method getPkgInfoForAdditionalPackages
             * @argument {Object} pkgInfoList
             * @return {Object}
             */
            oData.getPkgInfoForAdditionalPackages = function(pkgInfoList) {
                var list = {};

                _.each(ea4Util.additionalPkgList, function(pkg) {
                    var pkgInfo = pkgInfoList[pkg];
                    if (typeof pkgInfo !== "undefined") {
                        list[pkg] = pkgInfo;
                    }
                });
                return list;
            };

            /**
             * Returns pkgInfoList filtered by type.
             *
             * @method getPkgInfoSubset
             * @argument {String} type
             * @argument {Object} pkgInfoList
             * @return {Object}
             */
            oData.getPkgInfoSubset = function(type, pkgInfoList) {
                var regex;
                if (type === "additional") {
                    return oData.getPkgInfoForAdditionalPackages(pkgInfoList);
                }

                switch (type) {
                    case "mpm":
                        regex = oData.mpmRegex;
                        break;
                    case "modules":
                        regex = oData.modulesRegex;
                        break;
                    case "php":
                        regex = oData.phpRegex;
                        break;
                    case "extensions":
                        regex = oData.phpExtRegex;
                        break;
                    case "ruby":
                        regex = oData.rubyRegex;
                        break;
                }
                return oData.filterByRegex(regex, pkgInfoList);
            };

            /**
             * @method filterByRegex
             * @argument {Regex} regex
             * @argument {Object} pkgInfoList
             * @return {Promise}
             */
            oData.filterByRegex = function(regex, pkgInfoList) {
                var list = {};
                if (typeof regex === "undefined") {
                    return pkgInfoList;
                }

                var filterPkgs =
                    _.filter(_.keys(pkgInfoList), function(pkg) {
                        return regex.test(pkg);
                    });
                filterPkgs.sort();
                _.each(filterPkgs, function(pkg) {
                    list[pkg] = pkgInfoList[pkg];
                });
                return list;
            };

            /**
             * @method resolvePackages
             * @argument {Array} packages - Array of string names
             * @return {Promise}
             */
            oData.resolvePackages = function(packages) {
                var deferred = $q.defer();

                oData.getAdditionalPkgPrefixes().then(function(additionalPrefixes) {
                    ea4Util.cachePrefixes(additionalPrefixes);
                    ea4Util.updateRegexForPrefixes(additionalPrefixes);

                    var allNamespaces = ["ea"].concat(additionalPrefixes);

                    if (!packages || packages.length === 0) {
                        deferred.resolve({
                            install: [],
                            uninstall: [],
                            upgrade: [],
                            unaffected: [],
                        });
                        return;
                    }

                    var apiCall = new APIREQUEST.Class();
                    apiCall.initialize("", "package_manager_resolve_actions");

                    var nsIndex = 0;
                    var pkgIndex = 0;

                    _.each(allNamespaces, function(ns) {
                        if (nsIndex === 0) {
                            apiCall.addArgument("ns", ns);
                        } else {
                            apiCall.addArgument("ns-" + nsIndex, ns);
                        }
                        nsIndex++;
                    });

                    _.each(packages, function(pkg) {
                        if (pkgIndex === 0) {
                            apiCall.addArgument("package", pkg);
                        } else {
                            apiCall.addArgument("package-" + pkgIndex, pkg);
                        }
                        pkgIndex++;
                    });

                    API.promise(apiCall.getRunArguments())
                        .done(function(response) {
                            response = response.parsedResponse;

                            if (response && response.status && response.raw && response.raw.data) {
                                deferred.resolve(response.raw.data);
                            } else {
                                deferred.resolve({
                                    install: [],
                                    uninstall: [],
                                    upgrade: [],
                                    unaffected: [],
                                });
                            }
                        })
                        .fail(function(error) {
                            deferred.reject(error);
                        });

                }, function(error) {

                    // Fallback to EA only when additional prefixes API fails
                    ea4Util.cachePrefixes([]);
                    ea4Util.updateRegexForPrefixes([]);

                    var eaPackages = _.filter(packages, function(pkg) {
                        return pkg.startsWith("ea-");
                    });

                    if (eaPackages.length === 0) {
                        deferred.resolve({
                            install: [],
                            uninstall: [],
                            upgrade: [],
                            unaffected: [],
                        });
                        return;
                    }

                    var apiCall = new APIREQUEST.Class();
                    apiCall.initialize("", "package_manager_resolve_actions");
                    apiCall.addArgument("ns", "ea");
                    _.each(eaPackages, function(pkg, index) {
                        if (index === 0) {
                            apiCall.addArgument("package", pkg);
                        } else {
                            apiCall.addArgument("package-" + index, pkg);
                        }
                    });

                    API.promise(apiCall.getRunArguments())
                        .done(function(response) {
                            response = response.parsedResponse;
                            if (response && response.status && response.raw && response.raw.data) {
                                deferred.resolve(response.raw.data);
                            } else {
                                deferred.resolve({
                                    install: [],
                                    uninstall: [],
                                    upgrade: [],
                                    unaffected: [],
                                });
                            }
                        })
                        .fail(function(error) {
                            deferred.reject(error);
                        });
                });
                return deferred.promise;
            };

            /**
             * @method doProvision
             * @argument {Array} installPackages - Array of string names
             * @argument {Array} uninstallPackages - Array of string names
             * @argument {Array} upgradePackages - Array of string names
             * @argument {String} profileID
             * @return {Promise}
             */
            oData.doProvision = function(installPackages, uninstallPackages, upgradePackages, profileID) {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();
                apiCall.initialize("", "package_manager_submit_actions");

                // apiCall.addArgument("profileID", profileID);

                // Prepare the package list
                _.each(installPackages, function(pkg, index) {
                    apiCall.addArgument("install-" + index, pkg );
                });
                _.each(uninstallPackages, function(pkg, index) {
                    apiCall.addArgument("uninstall-" + index, pkg );
                });
                _.each(upgradePackages, function(pkg, index) {
                    apiCall.addArgument("upgrade-" + index, pkg );
                });
                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        deferred.resolve(response.raw.data);
                    });

                return deferred.promise;
            };

            /**
             * @method tailingLog
             * @argument {Number} buildID
             * @argument {Number} offset
             * @return {Promise}
             */
            oData.tailingLog = function(buildID, offset) {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "package_manager_get_build_log");

                // Send the pid
                apiCall.addArgument("build", buildID);
                apiCall.addArgument("offset", offset);

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        deferred.resolve(response.raw.data);
                    });

                return deferred.promise;
            };

            /**
             * @method getPkgInfoList
             * @return {Promise}
             */
            oData.getPkgInfoList = function() {
                var deferred = $q.defer();

                oData.getAdditionalPkgPrefixes().then(function(additionalPrefixes) {
                    ea4Util.cachePrefixes(additionalPrefixes);
                    ea4Util.updateRegexForPrefixes(additionalPrefixes);

                    var namespaces = ["ea"].concat(additionalPrefixes);
                    var promises = [];

                    _.each(namespaces, function(ns) {
                        var apiCall = new APIREQUEST.Class();
                        apiCall.initialize("", "package_manager_get_package_info");
                        apiCall.addArgument("ns", ns);
                        promises.push(API.promise(apiCall.getRunArguments()));
                    });

                    $q.all(promises).then(function(responses) {
                        var combinedData = {};

                        _.each(responses, function(response) {
                            response = response.parsedResponse;
                            if (response.status && response.raw.data && response.raw.data.packages) {
                                _.each(response.raw.data.packages, function(pkg) {
                                    combinedData[pkg.package] = pkg;
                                });
                            }
                        });

                        deferred.resolve(combinedData);
                    }).catch(function(error) {
                        deferred.reject(error);
                    });
                }, function(error) {
                    ea4Util.cachePrefixes([]);
                    ea4Util.updateRegexForPrefixes([]);

                    var apiCall = new APIREQUEST.Class();
                    apiCall.initialize("", "package_manager_get_package_info");
                    apiCall.addArgument("ns", "ea");

                    API.promise(apiCall.getRunArguments())
                        .done(function(response) {
                            response = response.parsedResponse;
                            var pkgData = {};
                            _.each(response.raw.data.packages, function(pkg) {
                                pkgData[pkg.package] = pkg;
                            });
                            deferred.resolve(pkgData);
                        })
                        .fail(function(error) {
                            deferred.reject(error);
                        });
                });
                return deferred.promise;
            };

            /**
             * @method cancelOperation
             */
            oData.cancelOperation = function() {
                oData.clearEA4LocalStorageItems();
                $location.path("profile");
            };

            /**
             * @method clearEA4LocalStorageItems
             */
            oData.clearEA4LocalStorageItems = function() {
                localStorage.removeItem("pkgInfoList");
                localStorage.removeItem("selectedProfile");
                localStorage.removeItem("selectedPkgs");
                localStorage.removeItem("provisionActions");
                localStorage.removeItem("customize");
                localStorage.removeItem("ea4Recommendations");
                localStorage.removeItem("ea4RawPkgList");
                localStorage.removeItem("ea4Update");

                ea4Util.hideFooter();
            };

            /**
             * Saves data in Local Storage
             *
             * @method setData
             * @argument {Object} dataItems
             */
            oData.setData = function(dataItems) {
                _.each(_.keys(dataItems), function(item) {
                    try {
                        localStorage.setItem(
                            item,
                            LZString.compress(
                                JSON.stringify(dataItems[item])
                            )
                        );
                    } catch (e) {
                        console.error("Error storing localStorage item " + item + ": ", e);
                    }
                });
            };

            /**
             * Gets data from Local Storage
             * Handles both compressed and uncompressed data for backward compatibility
             *
             * @method getData
             * @argument {String} item
             */
            oData.getData = function(item) {
                try {
                    var rawData = localStorage.getItem(item);

                    if (!rawData) {
                        return null;
                    }

                    var decompressedData = LZString.decompress(rawData);

                    if (decompressedData) {
                        return JSON.parse(decompressedData);
                    } else {

                        // Decompression failed, assume it's uncompressed data (legacy format)
                        return JSON.parse(rawData);
                    }
                } catch (e) {
                    return null;
                }
            };

            /**
             * Adds new properties to the packages object
             *
             * @method initPkgUIProps
             * @argument {Object} pkgData
             * @return {Object}
             */
            oData.initPkgUIProps = function(pkgData) {
                pkgData["actions"] = ea4Util.pkgActions;
                pkgData["recommendations"] = [];
                pkgData["multiRequirements"] = ea4Util.multiRequirements;
                pkgData["vhostWarning"] = ea4Util.vhostWarning;
                pkgData["mpmMissing"] = false;
                pkgData["mpmMissingMsg"] = "";
                pkgData["autoSelectExt"] = [];

                // TODO:: Add more as you come across. It's easy to know and track all the properties
                // if they are initialized at one place.
                return pkgData;
            };

            // move to ea4Util
            var setPkgSelections = function(selPkgs, pkgInfoList) {
                var allSelectedPkgs = ea4Util.gatherAllRequirementsOfPkgs(selPkgs, pkgInfoList);

                // Select all packages that are installed.
                _.each(allSelectedPkgs, function(pkg) {
                    if (typeof pkgInfoList[pkg] !== "undefined") {
                        pkgInfoList[pkg].selectedPackage = true;
                    }
                });
                return pkgInfoList;
            };

            /**
             * @method buildPkgInfoList
             * @argument {Array} selPkgs - Array of Strings
             * @argument {Array} packagesInfo - Array of Objects
             * @argument {Object} ea4Recommendations - The key is the pkg name, the value an Array of recommendations
             * @return {Object}
             */
            oData.buildPkgInfoList = function(selPkgs, packagesInfo, ea4Recommendations) {

                // Set all packages info.
                var pkgInfoList = {};
                var additionalPrefixes = ea4Util.getCachedPrefixes() || [];
                var allPrefixes = ["ea"].concat(additionalPrefixes);

                // Ignore any debug packages
                packagesInfo = _.filter(packagesInfo, function(pkg) {
                    return (!/.*\-debuginfo/.test(pkg.package));
                });

                _.each(packagesInfo, function(pkg) {
                    var pkgName = pkg.package;

                    // Start adding UI specific attributes to each package data.
                    pkg = oData.initPkgUIProps(pkg);
                    if (!_.isEmpty(ea4Recommendations[pkgName])) {
                        pkg.recommendations = ea4Recommendations[pkgName];
                    }

                    // Add prefix information
                    var pkgPrefix = "ea";
                    var match = _.find(allPrefixes, function(prefix) {
                        return pkgName.startsWith(prefix + "-");
                    });
                    if (match) {
                        pkgPrefix = match;
                    }
                    pkg.prefix = pkgPrefix !== "ea" ? pkgPrefix : null;

                    pkg.selectedPackage = false;

                    // displayName: Clean name for display (used in customize wizard with labels)
                    pkg.displayName = ea4Util.getFormattedPackageName(pkgName);

                    // filterName: Full name for filtering (keeps prefixes for non-EA packages)
                    if (pkgPrefix === "ea") {
                        pkg.filterName = ea4Util.getFormattedPackageName(pkgName);
                    } else {
                        pkg.filterName = pkgName;
                    }

                    // reviewDisplayName: For review page, keep full names for non-EA packages
                    if (pkgPrefix === "ea") {

                        // For EA packages, use the stripped name (ea-php81 -> php81)
                        pkg.reviewDisplayName = ea4Util.getFormattedPackageName(pkgName);
                    } else {

                        // For non-EA packages, use the full package name (alt-php56 stays alt-php56)
                        pkg.reviewDisplayName = pkgName;
                    }

                    pkgInfoList[pkgName] = pkg;
                });

                pkgInfoList = setPkgSelections(selPkgs, pkgInfoList);
                return pkgInfoList;
            };

            /**
             * @method fixYumCache
             * @return {Promise}
             */
            oData.fixYumCache = function() {
                var apiCall = new APIREQUEST.Class();
                var apiService = new APIService();
                apiCall.addArgument("ns", "ea");
                apiCall.initialize("", "package_manager_fixcache");

                var deferred = apiService.deferred(apiCall);
                return deferred.promise;
            };

            /**
             * @method saveAsNewProfile
             * @argument {Object} content
             * @argument {String} filename
             * @argument {Number} overwrite
             * @return {Promise}
             */
            oData.saveAsNewProfile = function(content, filename, overwrite) {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "ea4_save_profile");
                apiCall.addArgument("filename", filename);
                apiCall.addArgument("name", content.name);
                apiCall.addArgument("overwrite", overwrite);
                apiCall.addArgument("desc", content.desc || "");
                apiCall.addArgument("version", content.version || "");
                _.each(content.pkgs, function(pkg, index) {
                    apiCall.addArgument("pkg-" + index, pkg );
                });

                _.each(content.tags, function(tag, index) {
                    apiCall.addArgument("tag-" + index, tag );
                });

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            deferred.resolve(response.data);
                        } else {
                            deferred.reject(response);
                        }
                    });
                return deferred.promise;
            };

            /**
             * @method getEA4Recommendations
             * @return {Promise}
             */
            oData.getEA4Recommendations = function() {
                var apiCall = new APIREQUEST.Class();
                var apiService = new APIService();
                apiCall.initialize("", "ea4_recommendations");

                var deferred = apiService.deferred(apiCall);
                return deferred.promise;
            };

            /**
             * @method getUploadContentFromUrl
             * @argument {Object} uploadUrl
             * @return {Promise}
             */
            oData.getUploadContentFromUrl = function(uploadUrl) {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "cors_proxy_get");
                apiCall.addArgument("url", uploadUrl);

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            deferred.resolve(response.data);
                        } else {
                            deferred.reject(response.error);
                        }
                    });
                return deferred.promise;
            };

            /**
             * @method dataIsAvailable
             * @return {Boolean}
             */
            oData.dataIsAvailable = function() {
                if (oData.getData("pkgInfoList")) {
                    return true;
                } else {
                    return false;
                }
            };

            /**
             * @method getPkgInfoByType
             * @argument {String} type
             * @argument {Object} pkgInfoList
             * @return {Object}
             */
            oData.getPkgInfoByType = function(type, pkgInfoList) {
                var subsetPkgInfoList = [];
                if (type) {
                    subsetPkgInfoList = oData.getPkgInfoSubset(type, pkgInfoList);
                }
                return subsetPkgInfoList;
            };

            /**
             * Get additional EA4 package prefixes
             * @method getAdditionalPkgPrefixes
             * @return {Promise}
             */
            oData.getAdditionalPkgPrefixes = function() {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "ea4_get_additional_pkg_prefixes");

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            deferred.resolve(response.raw.data.additional_pkg_prefixes);
                        } else {
                            deferred.reject(response.raw.metadata.reason);
                        }
                    });

                return deferred.promise;
            };

            // Expose only required methods.
            return {
                provisionReady: oData.provisionReady,
                getProfiles: oData.getProfiles,
                ea4GetCurrentPkgList: oData.ea4GetCurrentPkgList,
                cancelOperation: oData.cancelOperation,
                getPkgInfoForAdditionalPackages: oData.getPkgInfoForAdditionalPackages,
                getAdditionalPkgPrefixes: oData.getAdditionalPkgPrefixes,
                getPkgInfoSubset: oData.getPkgInfoSubset,
                updateRegexesForPrefixes: oData.updateRegexesForPrefixes,
                filterByRegex: oData.filterByRegex,
                clearEA4LocalStorageItems: oData.clearEA4LocalStorageItems,
                resolvePackages: oData.resolvePackages,
                doProvision: oData.doProvision,
                tailingLog: oData.tailingLog,
                getPkgInfoList: oData.getPkgInfoList,
                setData: oData.setData,
                getData: oData.getData,
                initPkgUIProps: oData.initPkgUIProps,
                buildPkgInfoList: oData.buildPkgInfoList,
                getVhostsByPhpVersion: oData.getVhostsByPhpVersion,
                fixYumCache: oData.fixYumCache,
                saveAsNewProfile: oData.saveAsNewProfile,
                getEA4Recommendations: oData.getEA4Recommendations,
                getUploadContentFromUrl: oData.getUploadContentFromUrl,
                dataIsAvailable: oData.dataIsAvailable,
                getPkgInfoByType: oData.getPkgInfoByType,
                getEA4MetaInfo: oData.getEA4MetaInfo,
            };
        }]);
    }
);
Back to Directory File Manager