📁
SKYSHELL MANAGER
PHP v8.2.31
Create
Create
Path:
root
/
home
/
thevaxnx
/
nativize.com
/
staging
/
wp-includes
/
js
/
tinymce
/
themes
/
Name
Size
Perm
Actions
📁
inlite
-
0755
🗑️
🏷️
🔒
📁
modern
-
0755
🗑️
🏷️
🔒
📄
wp-links-opml.php
6.83 KB
0444
🗑️
🏷️
⬇️
✏️
🔒
Edit: index.cmb.js
/* # cpanel - whostmgr/docroot/templates/easyapache4/services/ea4Util.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( 'app/services/ea4Util',[ "angular", "cjt/util/locale", "lodash", "cjt/io/api", "cjt/io/whm-v1-request", "cjt/io/whm-v1", "cjt/services/APIService", ], function(angular, LOCALE, _) { "use strict"; var app = angular.module("whm.easyapache4.ea4Util", []); app.factory("ea4Util", ["wizardState", function(wizardState) { var util = { eaRegex: /^ea-/i, phpVerRegex: /^ea-php(\d{2})$/i, phpExtRegex: /^ea-php(\d{2}-)/i, apacheVerRegex: /^ea-apache(\d{2})$/i, rubyVerRegex: /^ea-ruby(\d{2})/i, apacheModulesRegex: /^ea-apache(\d{2}-)/i, additionalPkgList: [], defaultMeta: { // Search/Filter settings filterList: {}, filterValue: "", isEmptyList: false, // Pager settings showPager: true, maxPages: 0, totalItems: 0, currentPage: 1, pageSize: 10, pageSizes: [10, 20, 50, 100], start: 0, limit: 10, }, pkgActions: { removeList: [], addList: [], actionNeeded: false, }, vhostWarning: { exist: false, text: "", }, multiRequirements: { exist: false, orList: [], chosenPackage: "", }, autoSelectExt: { list: [], text: "", errorList: [], show: false, showError: false, showCommonExtensions: false, }, checkUpdateInfo: { isLoading: true, pkgNumber: 0, btnText: LOCALE.maketext("Checking for updates …"), btnTitle: LOCALE.maketext("Checking for updates …"), btnCss: "btn-ea4-looking-updates disabled", }, }; util.cachedPrefixes = null; util.cachePrefixes = function(prefixes) { util.cachedPrefixes = prefixes; }; util.getCachedPrefixes = function() { return util.cachedPrefixes || []; }; // Cached EA4 metainfo used for version support checks util.cachedMetaInfo = null; util.cacheMetaInfo = function(metaInfo) { util.cachedMetaInfo = metaInfo; }; util.getCachedMetaInfo = function() { return util.cachedMetaInfo || {}; }; util.getOutdatedLabel = function() { return LOCALE.maketext("Outdated"); }; /** * Check if a non-alt PHP package is outdated based on oldest_supported_version from metainfo. * Applies to ea-phpNN and other non-alt prefixes. * @param {String} pkgName - Package name (e.g., 'ea-php81', 'custom-php80') * @return {Boolean} - True if the PHP version is outdated */ util.isOutdatedPhpVersion = function(pkgName) { if (PAGE && PAGE.suppress_php_eol_status) { return false; } var metaInfo = util.getCachedMetaInfo(); var oldestSupported = metaInfo.oldest_supported_version; if (!oldestSupported || !pkgName) { return false; } var phpVersionMatch = pkgName.match(/^([^-]+)-php(\d{2})(?:-|$)/i); if (!phpVersionMatch) { return false; } var prefix = phpVersionMatch[1].toLowerCase(); if (prefix === "alt") { return false; } var versionDigits = phpVersionMatch[2]; var packageMajor = parseInt(versionDigits.charAt(0), 10); var packageMinor = parseInt(versionDigits.charAt(1), 10); var oldestParts = oldestSupported.split("."); var oldestMajor = parseInt(oldestParts[0], 10); var oldestMinor = parseInt(oldestParts[1] || "0", 10); if (packageMajor < oldestMajor) { return true; } if (packageMajor === oldestMajor && packageMinor < oldestMinor) { return true; } return false; }; /** * Append outdated label to PHP package display names. * Uses round brackets unless parentheses are already present. * @param {String} displayName - The current display name * @param {String} pkgName - The package name to check * @param {String} outdatedLabel - Localized "Outdated" text * @return {String} - Display name with outdated label if applicable */ util.appendOutdatedLabel = function(displayName, pkgName, outdatedLabel) { if (!util.isOutdatedPhpVersion(pkgName)) { return displayName; } if (!outdatedLabel) { outdatedLabel = util.getOutdatedLabel(); } if (displayName.indexOf(outdatedLabel) !== -1) { return displayName; } return displayName + " (" + outdatedLabel + ")"; }; util.gatherAllRequirementsOfPkgs = function(pkgsToConsider, packageListDetails) { var allRequires = []; _.each(pkgsToConsider, function(pkg) { if (!_.includes(allRequires, pkg)) { var pkgInfo = packageListDetails[pkg]; if (typeof pkgInfo !== "undefined") { var result = util.recurseForRequires(pkgInfo, pkg, allRequires, packageListDetails); if (result !== undefined) { allRequires = result; } } allRequires.push(pkg); } }); return allRequires; }; util.recurseForRequires = function(pkgInfo, origPkgName, allPkgList, packageListDetails) { var pkgName = pkgInfo.package; var additionalPrefixes = util.getCachedPrefixes(); var allPrefixes = ["ea", "alt"].concat(additionalPrefixes); var matchesAnyPrefix = _.some(allPrefixes, function(prefix) { return pkgName.startsWith(prefix + "-"); }); if (!matchesAnyPrefix) { return; } if (_.includes(allPkgList, pkgName)) { return; } if (typeof origPkgName !== "undefined" && pkgName !== origPkgName) { allPkgList.push(pkgName); } var recurseArray = []; if (pkgInfo.pkg_dep.requires.length > 0) { recurseArray = _.clone(pkgInfo.pkg_dep.requires); recurseArray = _.filter(recurseArray, function(pkg) { if (typeof pkg !== "string" || _.isArray(pkg)) { return false; } var reqMatchesPrefix = _.some(allPrefixes, function(prefix) { return pkg.startsWith(prefix + "-"); }); return reqMatchesPrefix; }); recurseArray = _.difference(recurseArray, allPkgList); } _.forEach(recurseArray, function(reqPkg) { var reqPkgInfo = packageListDetails[reqPkg]; if (typeof reqPkgInfo !== "undefined") { util.recurseForRequires(reqPkgInfo, origPkgName, allPkgList, packageListDetails); } }); return allPkgList; }; /** * @method getFormattedPackageList * Takes an Array of packages names and returns a new sorted list * with formatted names. By default takes out the "ea-" part of * the package name. Also takes a second optional regex param to change the default behavior. * @example getFormattedPackageList("ea-openssl"); // returns "openssl" * @param {Array} pkgList - Array contains a list of packages names. * @param {RegExp} regex - Optional * @return {Array} - New formatted and sorted list. */ util.getFormattedPackageList = function(pkgList, regex) { var formattedList = []; var additionalPrefixes = util.getCachedPrefixes() || []; var allPrefixes = ["ea"].concat(additionalPrefixes); _.each(pkgList, function(pkg) { var formattedName; // For review list, preserve non-EA prefixes var pkgPrefix = "ea"; var match = _.find(allPrefixes, function(prefix) { return pkg.startsWith(prefix + "-"); }); if (match) { pkgPrefix = match; } if (pkgPrefix === "ea") { // For EA packages, strip the prefix as usual formattedName = util._getReadableName(pkg, regex); } else { // For non-EA packages, keep the full name to preserve context formattedName = pkg; } formattedList.push(formattedName); }); return formattedList; }; util.getFormattedPackageName = function(pkg, regex) { return util._getReadableName(pkg, regex); }; util._getReadableName = function(pkg, replaceRegex) { var strippedName = pkg; if (typeof replaceRegex !== "undefined" && replaceRegex !== "") { strippedName = pkg.replace(replaceRegex, ""); } else { if (util.apacheModulesRegex.test(pkg)) { strippedName = pkg.replace(util.apacheModulesRegex, ""); } else { // Strip ALL prefixes for consistent naming and sorting var additionalPrefixes = util.getCachedPrefixes() || []; var allPrefixes = ["ea"].concat(additionalPrefixes); var prefixPattern = "^(" + allPrefixes.join("|") + ")-"; var prefixRegex = new RegExp(prefixPattern); if (prefixRegex.test(pkg)) { strippedName = pkg.replace(prefixRegex, ""); } else { strippedName = pkg; } } } if (strippedName === "php") { strippedName = "php (DSO)"; } return strippedName; }; /** * @method getProfilePackagesByCategories * Takes an Array of packages names and groups them together in different categories. * The method will return an Object where the keys are the new categories. * @param {Array} pkgList - list of packages names. * @return {Object} - each keys equals a new category. */ util.getProfilePackagesByCategories = function(pkgList) { var pkgCategories = util._pkgByCategory(pkgList); return pkgCategories; }; util._pkgByCategory = function(pkgList) { var categories = {}, apacheList = [], phpExtList = [], others = [], phpVersions = [], apacheVersion = ""; var apachePrefix = "ea"; var additionalPrefixes = util.getCachedPrefixes(); var allPrefixes = ["ea"].concat(additionalPrefixes); _.each(pkgList, function(pkg) { if (util.apacheVerRegex.test(pkg)) { apacheVersion = pkg; var match = pkg.match(new RegExp("^(" + allPrefixes.join("|") + ")-apache")); if (match) { apachePrefix = match[1]; } } else if (util.apacheModulesRegex.test(pkg)) { apacheList = _.concat(apacheList, pkg); } else if (util.phpVerRegex.test(pkg)) { phpVersions = _.concat(phpVersions, pkg); } else if (util.phpExtRegex.test(pkg)) { phpExtList = _.concat(phpExtList, pkg); } else { others = _.concat(others, pkg); } }); if (apacheList.length > 0) { apacheVersion = util._getReadableName(apacheVersion, util.eaRegex); var categoryKey = apachePrefix + "_" + apacheVersion; categories[categoryKey] = { "name": util.versionToString(apacheVersion), "prefix": apachePrefix !== "ea" ? apachePrefix : null, "packages": util.getFormattedPackageListForProfile(apacheList, util.apacheModulesRegex), }; } if (phpVersions.length > 0) { _.each(phpVersions.sort(), function(version) { var verRegex = new RegExp("^" + _.escapeRegExp(version) + "-"); var extensions = _.filter(phpExtList, function(pkg) { return verRegex.test(pkg); }); phpExtList = _.difference(phpExtList, extensions); var phpversion = util._getReadableName(version, util.eaRegex); var phpPrefix = "ea"; var match = version.match(new RegExp("^(" + allPrefixes.join("|") + ")-php")); if (match) { phpPrefix = match[1]; } var categoryKey = phpPrefix + "_" + phpversion; var phpVersionName = util.versionToString(phpversion).replace("Php", "PHP"); var phpVersionOutdated = util.isOutdatedPhpVersion(version); categories[categoryKey] = { "name": phpVersionName, "prefix": phpPrefix !== "ea" ? phpPrefix : null, "isOutdated": phpVersionOutdated, "packages": util.getFormattedPackageListForProfile(extensions, util.phpExtRegex), }; }); } // Handle Others. if (others.length > 0) { categories["others"] = { "name": LOCALE.maketext("Additional Packages"), "prefix": null, "packages": util.getFormattedPackageListForProfile(others, /^ea-/), }; } return categories; }; /** * @method getFormattedPackageListForProfile * Like getFormattedPackageList but always strips prefixes for profile view * since it has labels to show namespace context * @param {Array} pkgList - Array contains a list of packages names. * @param {RegExp} regex - Optional * @return {Array} - New formatted and sorted list with ALL prefixes stripped. */ util.getFormattedPackageListForProfile = function(pkgList, regex) { var formattedList = []; _.each(pkgList, function(pkg) { // For profile view, always strip prefixes since we have labels var formattedName = util._getReadableName(pkg, regex); formattedList.push(formattedName); }); return formattedList; }; /** * @method versionToString * Returns a version in a user friendly format. * @example versionToString("apache24") // returns "Apache 2.4" * @param {String} * @return {String} */ util.versionToString = function(version) { return version.replace(/^([a-z]+)(\d)(\d)$/, function(match, p1, p2, p3) { return _.capitalize(p1) + " " + p2 + "." + p3; }); }; /** * @method getPackageLabel * Returns appropriate package label for a given package state. * @example getPackageLabel(true, "updatable"); // returns "Update" * @param {Boolean} selected * @param {String} state * @return {String} */ util.getPackageLabel = function(selected, state) { var str = ""; if (selected) { if (state === "updatable") { str = "Update"; } else if (state !== "installed" && state !== "updatable") { str = "Install"; } else if (state === "installed") { str = "Unaffected"; } } else if (state === "installed" || state === "updatable") { str = "Uninstall"; } return str; }; /** * @method getPackageClass * Returns the proper css callout class for a given package state. * @param {Boolean} selected * @param {String} state * @return {String} */ util.getPackageClass = function(selected, state) { var classString; if (selected) { classString = "callout"; if (state === "updatable") { classString += " callout-info"; } else { classString += " callout-success"; } } else { if (state === "installed" || state === "updatable") { classString = "callout callout-warning"; } else { classString = "no-callout"; } } return classString; }; /** * @method getDefaultMetaData * Returns a deep copy of the Default Meta Data needed * to initialize the Wizard component * @return {Object} */ util.getDefaultMetaData = function() { return _.clone(util.defaultMeta); }; /** * @method getDefaultPageSizes * Returns an Array with the Default page size values * @return {Array} */ util.getDefaultPageSizes = function() { return util.defaultMeta.pageSizes; }; /** * @method getUpdatedMetaData * Returns updated meta data. While updating the metadata, * the package list may be filtered depending on the search criteria. * @param {Object} list - List of current packages * @param {Object} meta - Current meta data * @return {Object} updated meta data */ util.getUpdatedMetaData = function(list, meta) { if (typeof list !== "undefined" && _.keys(list).length <= 0) { meta.isEmptyList = true; meta.totalItems = 0; return meta; } // Filter settings. var searchStr = meta.filterValue; if (searchStr) { var searchString = new RegExp(".*" + searchStr + ".*", "i"); list = _.pickBy(list, function(value, key) { return (searchString.test(value.package) || searchString.test(value.short_description)); }); } // Pager settings. var pkgKeys = _.keys(list); var pgSizes = util.getDefaultPageSizes(); pgSizes = _.filter(pgSizes, function(size) { return (size <= pkgKeys.length); }); meta.pageSizes = pgSizes; var totalItems = pkgKeys.length; // filter list based on page size and pagination if (totalItems > _.min(pgSizes)) { var startIdx = (meta.currentPage - 1) * meta.pageSize; var endIdx = startIdx + meta.pageSize; endIdx = endIdx > totalItems ? totalItems : endIdx; list = _.pick(list, _.slice(pkgKeys, startIdx, endIdx)); // list statistics meta.start = startIdx + 1; meta.limit = endIdx; meta.showPager = true; } else { meta.showPager = false; if (pkgKeys.length === 0) { meta.start = 0; } else { // list statistics meta.start = 1; } meta.limit = pkgKeys.length; } meta.totalItems = totalItems; meta.filterList = list; meta.isEmptyList = _.keys(meta.filterList).length <= 0; return meta; }; /** * @method getPageShowingText * Returns a formated string message * @param {Object} meta - Current meta data * @return {String} */ util.getPageShowingText = function(meta) { var newString = ""; if (meta && typeof meta.start !== "undefined" && typeof meta.limit !== "undefined" && typeof meta.totalItems !== "undefined" ) { newString = LOCALE.maketext("[output,strong,Showing] [_1] - [_2] of [_3] items", meta.start, meta.limit, meta.totalItems); } return newString; }; /** * @method getExtensionsForPHPVersion * Filters the package list and returns a new list * with only the packages associated to the PHP version provided * @param {String} version - PHP version (eg. ea-php71). * @param {Array} pkgList - Array of package names string * @return {Array} */ util.getExtensionsForPHPVersion = function(version, pkgList) { var testString = new RegExp(version + ".*", "i"); var list = _.filter(pkgList, function(name) { return testString.test(name); }); return list; }; /** * @method decideShowHideRecommendations * Based on the current state of package selection (i.e. on select or unselect), * this method filters the recommendations and decides if the recommendation is to be shown or hidden. * @param {Array} recommendations - Array of recommendation Objects * @param {Array} pkgListToCheck - Package list to check if they match the recommendation criteria. * @param {Boolean} onSelect * @return {Array} - Array of filtered recommendation Objects */ util.decideShowHideRecommendations = function(recommendations, pkgListToCheck, onSelect, pkg) { // Currently this recommendation system works strictly for DSO recommendation only. It can be modified to suit other // recommendations as they come. _.each(recommendations, function(reco) { if (_.isUndefined(reco)) { return; } if (typeof onSelect !== "undefined") { // The display of a recommendation depends on when it should be // shown (i.e. when installing a package or uninstalling a package) if (onSelect) { reco.show = (reco.on === "add"); } else { reco.show = (reco.on === "remove"); } } else { reco.show = false; } if (reco.show) { var pkgDisplayName = pkg; // Show readable PHP version name if the pkg is PHP version. if (util.phpVerRegex.test(pkg)) { pkgDisplayName = util._getReadableName(pkg, util.eaRegex); pkgDisplayName = util.versionToString(pkgDisplayName).replace("Php", "PHP"); } var localizedName = LOCALE.makevar(reco.name); // The value of this variable is pulled in for translation via an ea4_recommendations TPDS reco.displayName = LOCALE.maketext("Recommendations for “[_1]”: [_2]", pkgDisplayName, localizedName); reco.desc = LOCALE.makevar(reco.desc); // The value of this variable is pulled in for translation via an ea4_recommendations TPDS reco.showFootnote = false; // 'showReco' flag is strictly used for the DSO recommendation. var showReco = true; _.each(reco.options, function(option) { if (!_.isNil(option.recommended)) { option.title = (option.recommended) ? LOCALE.maketext("Recommended") : LOCALE.maketext("Not Recommended"); } option.text = LOCALE.makevar(option.text); // The value of this variable is pulled in for translation via an ea4_recommendations TPDS option.show = true; if (!_.isEmpty(option.items)) { showReco = option.show = _.isEmpty(_.intersection(option.items, pkgListToCheck)); // If none of the options that include package recos need not be shown // then we do not need to show the footnote. if (!reco.showFootnote) { reco.showFootnote = option.show; } } else { // In DSO recommendation, we are hiding the second option // as well when the first option option.show = showReco; } }); reco.show = !_.every(reco.options, ["show", false]); } }); return recommendations; }; /** * @method getExtensionsOfSelectedPHPVersions * @param {Object} pkgInfoList * @param {Object} currPkgInfoList * @param {Array} selectedPkgs - Array of packages names * @return {Object} */ util.getExtensionsOfSelectedPHPVersions = function(pkgInfoList, currPkgInfoList, selectedPkgs) { var extToConsider = []; var noPHPSelected = false; var allPhpVersions = _.filter(_.keys(pkgInfoList), function(pkg) { return util.phpVerRegex.test(pkg); }).sort(); var versionsToConsider = _.filter(selectedPkgs, function(pkg) { return (util.phpVerRegex.test(pkg)); }).sort(); // Determine if all versions are selected or not. If selected, // we do not need to extract a subset, we can just show all extensions. if (!_.isEqual(allPhpVersions, versionsToConsider)) { if (versionsToConsider.length > 0) { // Extract only the extension packages of the versions to consider. var workingExt = _.keys(currPkgInfoList); _.each(versionsToConsider, function(ver) { var testString = new RegExp(ver + ".*", "i"); var verExt = _.remove(workingExt, function(ext) { return testString.test(ext); }); extToConsider = _.concat(extToConsider, verExt); }); } else { noPHPSelected = true; } } else { versionsToConsider = allPhpVersions; extToConsider = _.keys(currPkgInfoList); } return { versions: versionsToConsider, extensions: extToConsider, noPHPSelected: noPHPSelected }; }; /** * Following validations are done for filename: * - It is invalid if the input is just . or .. * - It is invalid if the filename contains / or NUL byte. * - It is valid for all other cases. * * @method validateFilename */ util.validateFilename = function(filename) { var valData = { valid: true, valMsg: "" }; if (/^\.{1,2}$/.test(filename)) { valData.valid = false; valData.valMsg = LOCALE.maketext("Filename [output,strong,cannot] be “[output,strong,_1]”.", filename); } else if (/\/|\0/.test(filename)) { valData.valid = false; valData.valMsg = LOCALE.maketext("Filename [output,strong,cannot] include the following characters: [list_and,_1]", ["/", "NUL"]); } return valData; }; util.setupVhostWarning = function(pkgInfo, vhostsCount) { // Show them as a warning. pkgInfo.vhostWarning.exist = true; var localizedText = LOCALE.maketext( "[quant,_1,virtual host currently uses,virtual hosts currently use] this version of PHP. If you remove this PHP version, your virtual hosts may not work properly.", vhostsCount); // This hack is to apply the right class to the number. This // is to overcome the limitation of locale system to take multiple output types for a string. localizedText = localizedText.replace(/^(\d+)/, "<span class='vhost-emphasis'>$1</span>"); pkgInfo.vhostWarning.text = localizedText; return pkgInfo; }; util.resetVhostWarning = function(pkgInfo) { pkgInfo.vhostWarning = angular.copy(util.vhostWarning); }; /** * Creates tags based on the packages list provided. * * NOTE: [ Needs Improvement ] * Until there is a better way to identify the important packages in a * "Currently Installed Packages", we are going to use regex to extract * Apache, and all PHP versions installed and add them as tags. * * @method createTagsForActiveProfile * @param {Array} packages A list of packages. */ util.createTagsForActiveProfile = function(packages) { var tags = []; var skipRuby = false; var foundPhpVersions = new Set(); // Create static regexes for tag creation that won't be affected by prefix updates var additionalPrefixes = util.getCachedPrefixes() || []; var allPrefixes = ["ea", "alt"].concat(additionalPrefixes); var phpTagRegex = new RegExp("^(" + allPrefixes.join("|") + ")-php(\\d{2})$", "i"); var apacheTagRegex = new RegExp("^(" + allPrefixes.join("|") + ")-apache(\\d{2})$", "i"); var rubyTagRegex = new RegExp("^(" + allPrefixes.join("|") + ")-ruby(\\d{2})", "i"); _.each(packages, function(pkg) { var newTag; var apacheMatch = pkg.match(apacheTagRegex); if (apacheMatch) { var apacheVersion = apacheMatch[2]; newTag = "Apache " + apacheVersion.replace(/(\d)(\d)$/, "$1.$2"); tags = _.concat(tags, newTag); } else { var phpMatch = pkg.match(phpTagRegex); if (phpMatch) { var phpPrefix = phpMatch[1]; var phpVersion = phpMatch[2]; var formattedVersion = "PHP " + phpVersion.replace(/(\d)(\d)$/, "$1.$2"); phpPrefix = "ea"; if (pkg.startsWith("alt-")) { phpPrefix = "alt"; } else if (!pkg.startsWith("ea-") && !pkg.startsWith("alt-")) { phpPrefix = phpMatch[1]; } var tagKey = phpPrefix.toLowerCase() + "-" + formattedVersion; if (!foundPhpVersions.has(tagKey)) { foundPhpVersions.add(tagKey); if (phpPrefix.toLowerCase() === "alt") { if (!(PAGE && PAGE.suppress_php_eol_status)) { var securedTag = LOCALE.maketext("Secured"); tags = _.concat(tags, formattedVersion + " (alt)(" + securedTag + ")"); } else { tags = _.concat(tags, formattedVersion + " (alt)"); } } else if (phpPrefix.toLowerCase() !== "ea") { var prefixedTag = formattedVersion + " (" + phpPrefix + ")"; prefixedTag = util.appendOutdatedLabel(prefixedTag, pkg, util.getOutdatedLabel()); tags = _.concat(tags, prefixedTag); } else { var tagToAdd = util.appendOutdatedLabel(formattedVersion, pkg, util.getOutdatedLabel()); tags = _.concat(tags, tagToAdd); } } } } var rubyMatch = pkg.match(rubyTagRegex); if (rubyMatch && !skipRuby) { var rubyVersion = rubyMatch[2]; newTag = "Ruby " + rubyVersion.replace(/(\d)(\d)$/, "$1.$2"); tags = _.concat(tags, newTag); skipRuby = true; } }); return tags; }; /** * @method checkMPMRequirement * Checks if the user has any MPM packages (at least one needs to be installed). * Updates MPM callout feedback. * @param {Object} pkgData * @param {Object} resolvedData * @param {Array} selectedPkgs * @param {Boolean} whenSelected * @return {Object} */ util.checkMPMRequirement = function(pkgData, resolvedData, selectedPkgs, whenSelected) { var mpmRegex = /mod[-_]mpm/; var mpmInRemoveList = _.filter(_.uniq(resolvedData.removeList), function(pkg) { return mpmRegex.test(pkg); }); if (!whenSelected && mpmRegex.test(pkgData.package)) { mpmInRemoveList = _.union(mpmInRemoveList, [pkgData.package]); } var subList = _.difference(selectedPkgs, mpmInRemoveList); var mpmIndexInRemove = _.findIndex(subList, function(pkg) { return mpmRegex.test(pkg); }); var mpmIndexInAdd = _.findIndex(resolvedData.addList, function(pkg) { return mpmRegex.test(pkg); }); if (mpmIndexInRemove === -1 && mpmIndexInAdd === -1) { // Show MPM callout; pkgData.mpmMissing = true; pkgData.mpmMissingMsg = LOCALE.maketext("Your selection removed [list_and,_1].", mpmInRemoveList) + " " + LOCALE.maketext("An [asis,MPM] package must exist on your system. Click “Continue” to select a new [asis,MPM] package.") + " " + LOCALE.maketext("Click “Cancel” to cancel this operation."); pkgData.actions.actionNeeded = false; } return pkgData; }; util.getCommonlyInstalledExtensions = function(allExtensions, installedPhpVersions) { // Filter only installed extensions. var installedExt = _.map(_.filter(allExtensions, ["selectedPackage", true]), "package"); var extGroupByVersions = []; _.each(installedPhpVersions, function(version, index) { // Group the extensions by PHP versions and normalize them to // a common name to make it easy to extract the // common extensions across all installed versions. // Example: // Trying to process the following array: // [ // "ea-php54-libc-client", "ea-php54-build", "ea-php54-pear", // "ea-php54-php-bcmath", "ea-php55-libc-client", "ea-php55-build", "ea-php55-pear", // "ea-php70-libc-client", "ea-php70-build", "ea-php70-pear", // "ea-php70-php-calendar", "ea-php70-php-curl" // ] // To: // [ // [ "libc-client", "build", "pear", "php-bcmath" ], // [ "libc-client", "build", "pear" ], // [ "libc-client", "build", "pear", "php-calendar", "php-curl" ] // ] extGroupByVersions[index] = _.chain(installedExt) .filter(function(ext) { return _.startsWith(ext, version); }) .map(function(ext) { return _.replace(ext, util.phpExtRegex, ""); }) .value(); }); // Extract the common extensions. // Note: EA packages use "php-bcmath" format while ALT packages use "bcmath" format. // We need to normalize for comparison but preserve the original names. var commonExtensions = []; if (extGroupByVersions.length === 1) { commonExtensions = extGroupByVersions[0]; } else { // Helper function to normalize extension name for comparison // Strips "php-" prefix if present to allow matching between EA and ALT packages var normalizeForComparison = function(ext) { return ext.indexOf("php-") === 0 ? ext.substring(4) : ext; }; // Build a map of normalized names to original names for each group var normalizedGroups = _.map(extGroupByVersions, function(group) { var map = {}; _.each(group, function(ext) { var normalizedName = normalizeForComparison(ext); // Prefer the original name (with php- prefix if it exists) if (!map[normalizedName] || ext.indexOf("php-") === 0) { map[normalizedName] = ext; } }); return map; }); // Find common normalized extension names var commonNormalized = _.keys(normalizedGroups[0]); for (var i = 1; i < normalizedGroups.length; i++) { commonNormalized = _.intersection(commonNormalized, _.keys(normalizedGroups[i])); } // Map back to original names, preferring the php- prefixed version for EA compatibility commonExtensions = _.map(commonNormalized, function(normalizedName) { // Look through all groups and prefer the php- prefixed version for (var j = 0; j < normalizedGroups.length; j++) { var original = normalizedGroups[j][normalizedName]; if (original && original.indexOf("php-") === 0) { return original; } } // If no php- prefixed version found, return any version return normalizedGroups[0][normalizedName]; }); } // Only single PHP version can have DSO package installed at any instance. So // if a DSO package come up in the commonExtensions, pull it out to eliminate // having conflict issues while auto selecting common extensions. // Note: Generally a DSO package is 'ea-php##-php'. Since 'ea-php##-' is stripped above, we are comparing it with just php. var excludeList = ["php", "scldevel" ]; _.pullAll(commonExtensions, excludeList); return commonExtensions; }; /** * Looks for ea-ruby##-** packages and return true if they do exist. * @param {Object} pkgList Package information list, * @return {Boolean} True if at least one ruby package exists. False otherwise. */ util.doRubyPkgsExist = function(pkgList) { return _.some(pkgList, function(pkg) { return util.rubyVerRegex.test(pkg.package); }); }; util.updateRegexForPrefixes = function(additionalPrefixes) { var allPrefixes = ["ea"].concat(additionalPrefixes || []); var prefixPattern = "(" + allPrefixes.join("|") + ")"; util.eaRegex = new RegExp("^(" + allPrefixes.join("|") + ")-", "i"); util.phpVerRegex = new RegExp("^" + prefixPattern + "-php(\\d{2})$", "i"); util.phpExtRegex = new RegExp("^" + prefixPattern + "-php(\\d{2}-)", "i"); util.apacheVerRegex = new RegExp("^" + prefixPattern + "-apache(\\d{2})$", "i"); util.apacheModulesRegex = new RegExp("^" + prefixPattern + "-apache(\\d{2}-)", "i"); util.rubyVerRegex = new RegExp("^" + prefixPattern + "-ruby(\\d{2})", "i"); }; /** * Looks for additional packages and return true if they do exist. * @param {Array} additionalPkgsList Array of additional package objects. * @param {Object} pkgList Package information list, * @return {Boolean} True if at least one additional package exists. False otherwise. */ util.doAdditionalPkgsExist = function(additionalPkgsList, pkgList) { return _.some(additionalPkgsList, function(addlPkg) { return _.includes(_.keys(pkgList), addlPkg); }); }; /** * Show Footer container of the wizard. */ util.showFooter = function() { wizardState.showFooter = true; }; /** * Hide Footer container of the wizard. */ util.hideFooter = function() { wizardState.showFooter = false; }; return util; }]); } ); var LZString=function(){var r=String.fromCharCode,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",e={};function t(r,o){if(!e[r]){e[r]={};for(var n=0;n<r.length;n++)e[r][r.charAt(n)]=n}return e[r][o]}var i={compressToBase64:function(r){if(null==r)return"";var n=i._compress(r,6,function(r){return o.charAt(r)});switch(n.length%4){default:case 0:return n;case 1:return n+"===";case 2:return n+"==";case 3:return n+"="}},decompressFromBase64:function(r){return null==r?"":""==r?null:i._decompress(r.length,32,function(n){return t(o,r.charAt(n))})},compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(r){return null==r?"":""==r?null:i._decompress(r.length,16384,function(o){return r.charCodeAt(o)-32})},compressToUint8Array:function(r){for(var o=i.compress(r),n=new Uint8Array(2*o.length),e=0,t=o.length;e<t;e++){var s=o.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null==o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;e<t;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(r){return null==r?"":i._compress(r,6,function(r){return n.charAt(r)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(o){return t(n,r.charAt(o))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(r,o,n){if(null==r)return"";var e,t,i,s={},u={},a="",p="",c="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<r.length;i+=1)if(a=r.charAt(i),Object.prototype.hasOwnProperty.call(s,a)||(s[a]=f++,u[a]=!0),p=c+a,Object.prototype.hasOwnProperty.call(s,p))c=p;else{if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e<h;e++)m<<=1,v==o-1?(v=0,d.push(n(m)),m=0):v++;for(t=c.charCodeAt(0),e=0;e<8;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;e<h;e++)m=m<<1|t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=c.charCodeAt(0),e=0;e<16;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;0==--l&&(l=Math.pow(2,h),h++),s[p]=f++,c=String(a)}if(""!==c){if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e<h;e++)m<<=1,v==o-1?(v=0,d.push(n(m)),m=0):v++;for(t=c.charCodeAt(0),e=0;e<8;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;e<h;e++)m=m<<1|t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=c.charCodeAt(0),e=0;e<16;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;0==--l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==o-1){d.push(n(m));break}v++}return d.join("")},decompress:function(r){return null==r?"":""==r?null:i._decompress(r.length,32768,function(o){return r.charCodeAt(o)})},_decompress:function(o,n,e){var t,i,s,u,a,p,c,l=[],f=4,h=4,d=3,m="",v=[],g={val:e(0),position:n,index:1};for(t=0;t<3;t+=1)l[t]=t;for(s=0,a=Math.pow(2,2),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 2:return""}for(l[3]=c,i=c,v.push(c);;){if(g.index>o)return"";for(s=0,a=Math.pow(2,d),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(c=s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 2:return v.join("")}if(0==f&&(f=Math.pow(2,d),d++),l[c])m=l[c];else{if(c!==h)return null;m=i+i.charAt(0)}v.push(m),l[h++]=i+m.charAt(0),i=m,0==--f&&(f=Math.pow(2,d),d++)}}};return i}();"function"==typeof define&&define.amd?define('lz-string',[],function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString}); /* # 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 */ define( 'app/services/ea4Data',[ "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) { console.error("Error parsing localStorage data:", 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.isOutdated = ea4Util.isOutdatedPhpVersion(pkgName); 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); pkg.reviewDisplayName = ea4Util.appendOutdatedLabel(pkg.reviewDisplayName, pkgName, ea4Util.getOutdatedLabel()); } 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, }; }]); } ); /* # cpanel - whostmgr/docroot/templates/easyapache4/directives/eaWizard.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 */ define( 'app/directives/eaWizard',[ "angular", "cjt/core", "lodash", "cjt/util/locale", "cjt/filters/qaSafeIDFilter", "app/services/ea4Data", "app/services/ea4Util", ], function(angular, CJT, _, LOCALE) { "use strict"; // Retrieve the current application var app = angular.module("App"); app.directive("eaWizard", [ "$timeout", "ea4Data", "ea4Util", "wizardApi", "pkgResolution", function($timeout, ea4Data, ea4Util, wizardApi, pkgResolution) { var TEMPLATE_PATH = "directives/eaWizard.ptt"; var RELATIVE_PATH = "templates/easyapache4/" + TEMPLATE_PATH; var ddo = { replace: true, restrict: "E", templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH, scope: { idPrefix: "@", totalPackageInfoList: "=", selectedPackages: "=", metaData: "=", stepName: "@", stepIndex: "@", stepTitle: "@", stepPath: "@", stepNext: "@", onToggleFn: "&", showSearch: "@", showPagination: "@", }, link: function postLink(scope, element, attrs) { var continueResolvingDeps = function(thisPackage) { thisPackage.multiRequirements.exist = false; var chosenPkgName = thisPackage.multiRequirements.chosenPackage; var data = pkgResolution.continueResolvingDependencies(thisPackage, scope.totalPackageInfoList[chosenPkgName], chosenPkgName, scope.totalPackageInfoList, scope.selectedPackages); // Check if orListStructure exists. If yes - Do setup multireq view. if (data.orListExist) { thisPackage.multiRequirements = pkgResolution.setupMultiRequirementForUserInput(scope.totalPackageInfoList); // return false since resolving dependencies is not yet complete. return false; } else if (data.actionNeeded) { // If not orlist, check if action is needed. If yes - setup conflict/resolution alert view. thisPackage = pkgResolution.setupConDepCallout(thisPackage, scope.totalPackageInfoList); // return false since resolving dependencies is not yet complete. return false; } else { // If not orlist OR action needed, call apply dependency. // Since no action is needed it is simply assumed that there are no conflicts // and all dependencies can be added without any harm. return scope.applyDependency(thisPackage); } }; /** * Get required PHP extensions for a currently selected PHP version. * * @method getAllRequiredPhpExtensions * @param {string} pkgName - PHP version package name * @return {Array} reqPkgs - A list of required extensions. */ var getAllRequiredPhpExtensions = function(pkgName) { var reqPkgs = []; var allDeps = pkgResolution.getAllDepsRecursively(true, scope.totalPackageInfoList[pkgName], pkgName, scope.totalPackageInfoList, scope.selectedPackages); if (allDeps) { reqPkgs = ea4Util.getExtensionsForPHPVersion(pkgName, allDeps.requiredPkgs); } return reqPkgs; }; /** * Get common PHP extensions that can be auto selected for a currently selected PHP version. * * @method getAutoSelectPhpExtensions * @param {string} package - PHP version package name * @return {Array} autoSelectExtList - A list of auto select extensions. */ var getAutoSelectPhpExtensions = function(thisPackage) { var autoSelectExtList = []; var extList = scope.php.commonExtensions; // Determine if this is a non-EA PHP package (uses different naming convention) // EA packages: ea-php81-php-bcmath (with "php-" prefix on extension) // Non-EA packages (alt, etc.): alt-php85-bcmath (no "php-" prefix on extension) var isNonEaPackage = !(/^ea-/i.test(thisPackage)); extList = _.map(extList, function(ext) { var candidateName = thisPackage + "-" + ext; // If the candidate exists in the package list, use it if (scope.totalPackageInfoList[candidateName]) { return candidateName; } // For non-EA packages, try without the "php-" prefix // (e.g., "php-bcmath" -> "bcmath") if (isNonEaPackage && ext.indexOf("php-") === 0) { var nonEaCandidateName = thisPackage + "-" + ext.substring(4); if (scope.totalPackageInfoList[nonEaCandidateName]) { return nonEaCandidateName; } } // For EA packages, try with the "php-" prefix if not present // (in case commonExtensions came from non-EA packages) if (!isNonEaPackage && ext.indexOf("php-") !== 0) { var eaCandidateName = thisPackage + "-php-" + ext; if (scope.totalPackageInfoList[eaCandidateName]) { return eaCandidateName; } } // Return original candidate (will be filtered out later if not found) return candidateName; }); var requiredExtensions = getAllRequiredPhpExtensions(thisPackage); // Check if the common extensions are actually available for the newly selected PHP version. // only returns the extensions that are NOT required. extList = _.filter(extList, function(ext) { var extension = scope.totalPackageInfoList[ext]; var notRequired = false; if (typeof extension !== "undefined" && !_.includes(requiredExtensions, ext)) { notRequired = true; } return notRequired; }); // Load the common extensions into the view model. autoSelectExtList = _.map(extList, function(ext) { var displayName = ea4Util.getFormattedPackageName(ext); return { package: ext, displayName: displayName, isSelected: true, }; }); return autoSelectExtList; }; var formatPhpVersionForDisplay = function(ver) { var phpMatch = ver.match(ea4Util.phpVerRegex); var formatted = ""; if (phpMatch) { var phpVersion = phpMatch[1]; formatted = "PHP " + phpVersion.replace(/(\d)(\d)$/, "$1.$2"); } return formatted; }; /** * Updates PHP package accordingly when selected/unselected. Details below: * For PHP just calling eaWizard.checkDependency isn't enough when * a PHP version package is unselected. There are some PHP specific stuff * that are needed to be done. * 1. Find if there are any vhosts that are using the PHP version being uninstalled * and warn the user about that. * 2. If all dependencies are resolved and everything is ok, then just remove all * extensions of the PHP version being uninstalled as well. * This method takes care of that. * * @method updatePHP * @param {object} thisPackage The package info object of PHP version. */ var updatePHP = function(thisPackage) { // The state of the selectedPackage is not toggled by default // by the toggle-switch directive. That is for now explicitly // done by common function eaWizard.checkDependency called by all EA4 templates. // Since in this template we need to do some vhost checks even before calling that // function the state is toggled temporarily. This is a nasty hack but will be there // until toggle-switch directive is fixed to toggle the ng-model's state correctly // before firing on-toggle event. var selectedState = !thisPackage.selectedPackage; var version = thisPackage.package; if (!selectedState) { // Find vhosts if any that are using this PHP version. var vhostsCount = 0; ea4Data.getVhostsByPhpVersion(version).then(function(data) { if (typeof data !== "undefined") { vhostsCount = data.length; } if (vhostsCount > 0) { thisPackage = ea4Util.setupVhostWarning(thisPackage, vhostsCount); return; } var isComplete = scope.checkDependency(thisPackage); if (isComplete) { scope.php.updatePHPExtensionList(thisPackage); } }, function(error) { // Do nothing. }); thisPackage.autoSelectExt = angular.copy(ea4Util.autoSelectExt); } else { scope.checkDependency(thisPackage); if (thisPackage.selectedPackage) { thisPackage.autoSelectExt = angular.copy(ea4Util.autoSelectExt); thisPackage.autoSelectExt.list = getAutoSelectPhpExtensions(thisPackage.package); var extCount = thisPackage.autoSelectExt.list.length; thisPackage.autoSelectExt.text = LOCALE.maketext("In addition to the dependencies that this version of [asis,PHP] requires, the system detected [quant,_1,extension,extensions] of all other installed PHP versions that it will install for this version.", extCount); var displayVersion = formatPhpVersionForDisplay(version); thisPackage.autoSelectExt.okText = LOCALE.maketext("[_1][comment,package name] and Extensions[comment,action text]", displayVersion); thisPackage.autoSelectExt.cancelText = LOCALE.maketext("[_1][comment,package name] Only[comment,action text]", displayVersion); thisPackage.autoSelectExt.show = (extCount > 0 && !thisPackage.actions.actionNeeded) ? true : false; } } }; /** * Final part of updating a PHP version select/unselect action. * * @method finishUpdatePHP * @param {object} thisPackage The package info object of PHP version. */ var finishUpdatePHP = function(thisPackage) { // When uninstall make sure to remove all corresponding extensions. if (!thisPackage.selectedPackage) { var isComplete = scope.applyDependency(thisPackage); if (isComplete) { scope.php.updatePHPExtensionList(thisPackage); } } else { scope.applyDependency(thisPackage); scope.php.autoSelectExtList = getAutoSelectPhpExtensions(thisPackage.package); thisPackage.autoSelectExt.show = (thisPackage.autoSelectExt.list.length > 0 && !thisPackage.actions.actionNeeded) ? true : false; } }; /** * Continues to resolve dependencies after the user decides to continue. * * @method continueResolvingDeps * @param {object} thisPackage The package info object of PHP version. */ var continueResolvingDepsForPhp = function(thisPackage) { // When uninstall make sure to remove all corresponding extensions. if (!thisPackage.selectedPackage) { var isComplete = continueResolvingDeps(thisPackage); if (isComplete) { scope.php.updatePHPExtensionList(thisPackage); } } else { continueResolvingDeps(thisPackage); } }; /** *************** * SCOPE STUFF *****************/ scope.php = { commonExtensions: [], /** * Updates PHP Extension list by removing all extensions of the PHP version * that is being uninstalled. * * @method updatePHPExtensionList * @param {object} thisPackage The package object of PHP version being uninstalled. */ updatePHPExtensionList: function(thisPackage) { if (!thisPackage.selectedPackage) { var list = ea4Util.getExtensionsForPHPVersion(thisPackage.package, scope.selectedPackages); _.each(list, function(pkg) { if (typeof scope.totalPackageInfoList[pkg] !== "undefined") { scope.totalPackageInfoList[pkg].selectedPackage = false; } _.pull(scope.selectedPackages, pkg); }); } }, /** * Continues to check for dependencies after the user acknowledges the vhost warning and * continues to uninstall a PHP version. * * @method continueCheckDependency * @param {object} thisPackage The package info object of PHP version. */ continueCheckDependency: function(thisPackage) { // If you come to this point, the vhost warning is already acknowledged // by now and it is safe to assume that necessary steps are taken. ea4Util.resetVhostWarning(thisPackage); var isComplete = scope.checkDependency(thisPackage); if (!thisPackage.selectedPackage && isComplete) { scope.php.updatePHPExtensionList(thisPackage); } }, /** * Reset the vhost warning object when select/unselect of a PHP version is canceled * * @method resetVhostWarning * @param {object} thisPackage The package info object of PHP version. */ resetVhostWarning: function(thisPackage) { return ea4Util.resetVhostWarning(thisPackage); }, }; scope.extensions = { _extToConsider: [], _extensionList: {}, noPHPSelected: false, filterPHPExtensions: function() { var phpVersions = scope.phpVersions; var showList = []; _.each(phpVersions, function(ver) { if (ver.selected) { var testString = new RegExp(ver.version + ".*", "i"); var list = _.filter(scope.extensions._extToConsider, function(name) { return testString.test(name); }); showList = _.union(showList, list); } }); scope.currPkgInfoList = _.pick(scope.extensions._extensionList, showList); scope.applyMetaData(); }, showPhpFilterTags: function() { return (scope.stepName === "extensions" && !_.isEmpty(scope.extensions._extensionList)); }, }; scope.toggleLabel = function(thisPackage) { return ea4Util.getPackageLabel(thisPackage.selectedPackage, thisPackage.state); }; scope.packageClass = function(thisPackage) { return ea4Util.getPackageClass(thisPackage.selectedPackage, thisPackage.state); }; scope.applyDependency = function(thisPackage) { var pkgName = thisPackage.package; var selectedPackages = scope.selectedPackages; var resData = pkgResolution.getResolvedData(); // At least 1 MPM package should be in the selected list // Check for it everytime an MPM package is removed/uninstalled. // Show a callout if there is no MPM in selected pkgs. thisPackage.mpmMissing = false; thisPackage.mpmMissingMsg = ""; if (thisPackage.actions.actionNeeded) { thisPackage = ea4Util.checkMPMRequirement(thisPackage, resData, selectedPackages, thisPackage.selectedPackage); if (thisPackage.mpmMissing) { return false; } } // Apply the actions // 1. removeList must be pulled out of selected package list. _.each(resData.removeList, function(conflict) { _.pull(selectedPackages, conflict); if ( typeof scope.totalPackageInfoList[conflict] !== "undefined" ) { scope.totalPackageInfoList[conflict].selectedPackage = false; } }); // 2. addList must be added into selected package list. _.each(resData.addList, function(dep) { selectedPackages = _.concat(selectedPackages, dep); if ( typeof scope.totalPackageInfoList[dep] !== "undefined" ) { scope.totalPackageInfoList[dep].selectedPackage = true; } }); if (!thisPackage.selectedPackage) { _.pull(selectedPackages, pkgName); if ( typeof scope.totalPackageInfoList[pkgName] !== "undefined" ) { scope.totalPackageInfoList[pkgName].selectedPackage = false; } } // Finally set the current profile's pkgs. scope.selectedPackages = selectedPackages; thisPackage = pkgResolution.resetPkgResolveActions(thisPackage); ea4Data.setData( { "pkgInfoList": scope.totalPackageInfoList } ); // Return true if everything is complete. return true; }; scope.checkDependency = function(thisPackage) { var selected = false; var pkgName = thisPackage.package; var selectedPackages = scope.selectedPackages; // REFACTOR: This needs to be re arranged (please see the comment in ea4Data.resetcommonvariables method) thisPackage.actions = { removeList: [], addList: [], actionNeeded: false }; // Toggle the status of the package selection. thisPackage.selectedPackage = scope.totalPackageInfoList[pkgName].selectedPackage = selected = !thisPackage.selectedPackage; thisPackage.recommendations = ea4Util.decideShowHideRecommendations(thisPackage.recommendations, scope.selectedPackages, selected, pkgName); thisPackage.showRecommendations = !_.every(thisPackage.recommendations, [ "show", false ]); if (selected) { var data = pkgResolution.resolveDependenciesWhenSelected(thisPackage, selectedPackages, scope.totalPackageInfoList); // Check if orListStructure exists. If yes - Do setup multireq view. if (data.orListExist) { thisPackage.multiRequirements = pkgResolution.setupMultiRequirementForUserInput(scope.totalPackageInfoList); // return false since resolving dependencies is not yet complete. return false; } else if (data.actionNeeded) { // If not orlist, check if action is needed. If yes - setup conflict/resolution alert view. thisPackage = pkgResolution.setupConDepCallout(thisPackage, scope.totalPackageInfoList); // return false since resolving dependencies is not yet complete. return false; } else { // If not orlist OR action needed, call apply dependency. if (thisPackage.showRecommendations) { thisPackage.actions.actionNeeded = true; return false; } else { // Since no action is needed it is simply assumed that there are no conflicts // and all dependencies can be added without any harm. return scope.applyDependency(thisPackage); } } } else { pkgResolution.resolveDependenciesWhenUnSelected(thisPackage, selectedPackages, scope.totalPackageInfoList); if (thisPackage.showRecommendations) { thisPackage.actions.actionNeeded = true; return false; } else { thisPackage.mpmMissing = false; thisPackage.mpmMissingMsg = ""; var resData = pkgResolution.getResolvedData(); thisPackage = ea4Util.checkMPMRequirement(thisPackage, resData, selectedPackages, selected); if (thisPackage.mpmMissing) { return false; } else { return scope.applyDependency(thisPackage); } } } }; scope.applyMetaData = function() { scope.metaData = ea4Util.getUpdatedMetaData(scope.currPkgInfoList, scope.metaData); }; scope.getShowingText = function() { return ea4Util.getPageShowingText(scope.metaData); }; scope.showSearchPageSection = function() { if (scope.stepName === "extensions") { return (scope.stepName === "extensions" && !scope.extensions.noPHPSelected); } else { return true; } }; scope.showEmptyMessage = function() { var show = false; if (scope.stepName === "extensions") { show = (!scope.extensions.noPHPSelected && scope.metaData.isEmptyList); } else { show = scope.metaData.isEmptyList; } return show; }; scope.showToggleSwitch = function(pkgData) { if (scope.stepName === "php") { return (!pkgData.actions.actionNeeded && !pkgData.multiRequirements.exist && !pkgData.mpmMissing && !pkgData.vhostWarning.exist); } else { return (!pkgData.actions.actionNeeded && !pkgData.multiRequirements.exist && !pkgData.mpmMissing); } }; scope.initializeSelection = function(pkgData) { if (scope.stepName === "php") { updatePHP(pkgData); } else { scope.checkDependency(pkgData); } }; scope.php.resetAutoSelectExtensions = function(pkgData) { pkgData.autoSelectExt = angular.copy(ea4Util.autoSelectExt); }; scope.php.performAutoSelect = function(thisPackage) { var extList = _.filter(thisPackage.autoSelectExt.list, ["isSelected", true]); _.each(extList, function(oExt) { var pkgName = oExt.package; var selected = true; var selectedPackages = scope.selectedPackages; var pkgData = scope.totalPackageInfoList[pkgName]; pkgResolution.resetCommonVariables(); // Toggle the status of the package selection. pkgData.selectedPackage = selected; var data = pkgResolution.resolveDependenciesWhenSelected(pkgData, selectedPackages, scope.totalPackageInfoList); // Check if orListStructure exists. If yes - Do setup multireq view. if (data.orListExist || data.actionNeeded) { pkgData.selectedPackage = !selected; // Record conflicted packages thisPackage.autoSelectExt.errorList.push(pkgName); } else { // Since no action is needed it is simply assumed that there are no conflicts // and all dependencies can be added without any harm. return scope.applyDependency(pkgData); } }); if (thisPackage.autoSelectExt.errorList.length > 0) { thisPackage.autoSelectExt.showError = true; } else { scope.php.resetAutoSelectExtensions(thisPackage); } }; scope.continueProcess = function(pkgData) { if (scope.stepName === "php") { return continueResolvingDepsForPhp(pkgData); } else { return continueResolvingDeps(pkgData); } }; scope.finalizeSelection = function(pkgData) { if (scope.stepName === "php") { finishUpdatePHP(pkgData); } else { scope.applyDependency(pkgData); } }; // When user is shown with a compiled list of add/remove packages, // if the user chose not to proceed with the change, this method ensures nothing // is changed. scope.resetSelection = function(thisPackage) { thisPackage.selectedPackage = !thisPackage.selectedPackage; thisPackage = pkgResolution.resetPkgResolveActions(thisPackage); }; scope.proceed = function(step) { ea4Data.setData( { "pkgInfoList": scope.totalPackageInfoList, "selectedPkgs": scope.selectedPackages, } ); wizardApi.next(step); }; var initWizard = function() { scope.showSearch = false; scope.showPagination = false; var thisStep = wizardApi.getStepByName(scope.stepName); if (typeof thisStep !== "undefined") { scope.stepIndex = thisStep.stepIndex || -1; scope.stepPath = thisStep.path || ""; scope.stepTitle = thisStep.title || ""; scope.idPrefix = thisStep.name || "eaWizard"; scope.stepNext = thisStep.nextStep || ""; } scope.currPkgInfoList = ea4Data.getPkgInfoByType(scope.stepName, scope.totalPackageInfoList); scope.metaData = ea4Util.getDefaultMetaData(); scope.applyMetaData(scope.currPkgInfoList, scope.metaData); /* Set initial focus to the wizard step title. Also reset focus to the new step title every time user clicks "Next". This improves usability when navigating by keyboard. */ $timeout(function() { element.find("#wizard-step-title").focus(); }); if (scope.stepName === "extensions") { var currentPkgInfoList = scope.currPkgInfoList; var phpVersionsAndExtensions = ea4Util.getExtensionsOfSelectedPHPVersions(scope.totalPackageInfoList, currentPkgInfoList, scope.selectedPackages); scope.extensions.noPHPSelected = phpVersionsAndExtensions.noPHPSelected; scope.phpVersions = _.map(phpVersionsAndExtensions.versions, function(ver) { if (typeof scope.totalPackageInfoList[ver] !== "undefined") { return { version: scope.totalPackageInfoList[ver].package, name: scope.totalPackageInfoList[ver].filterName || scope.totalPackageInfoList[ver].displayName, selected: true, }; } }); scope.extensions._extToConsider = phpVersionsAndExtensions.extensions; scope.extensions._extensionList = _.pick(currentPkgInfoList, scope.extensions._extToConsider); scope.currPkgInfoList = scope.extensions._extensionList; scope.applyMetaData(); } else if (scope.stepName === "php") { // Find Common extensions installed on all existing PHP versions. var allPhpExtensions = ea4Data.getPkgInfoByType("extensions", scope.totalPackageInfoList); var installedPhpVersions = _.map( _.filter( scope.currPkgInfoList, function(pkgInfo) { return (pkgInfo.state !== "not_installed"); } ), "package" ); scope.php.commonExtensions = ea4Util.getCommonlyInstalledExtensions(allPhpExtensions, installedPhpVersions); } }; initWizard(); }, }; return ddo; }, ] ); } ); /* # templates/easyapache4/directives/saveAsProfile.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( 'app/directives/saveAsProfile',[ "angular", "cjt/util/locale", "cjt/core", "lodash", "cjt/decorators/growlDecorator", "app/services/ea4Data", "app/services/ea4Util", "cjt/directives/validationContainerDirective", "cjt/directives/validationItemDirective" ], function(angular, LOCALE, CJT, _) { // Retrieve the current application var app = angular.module("App"); app.directive("saveAsProfile", [ "ea4Data", "ea4Util", "growl", "growlMessages", function(ea4Data, ea4Util, growl, growlMessages) { var initContent = { name: "", filename: { name: "", valMsg: "" }, tags: [], description: "", version: "", overwrite: false }; var TEMPLATE_PATH = "directives/saveAsProfile.ptt"; var RELATIVE_PATH = "templates/easyapache4/" + TEMPLATE_PATH; var ddo = { replace: true, restrict: "E", templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH, scope: { idPrefix: "@", packages: "=", actionHandler: "=", position: "@", onCancel: "&", onSaveSuccess: "&", onSaveError: "&", show: "@", saveButtonText: "@" }, link: function postLink(scope, element, attrs) { scope.saveAsData = _.cloneDeep(initContent); scope.highlightOverwrite = false; scope.actionHandler = scope.actionHandler || {}; scope.idPrefix = scope.idPrefix || "save"; scope.position = scope.position || "top"; scope.saveButtonText = scope.saveButtonText || LOCALE.maketext("Save"); /** * Clears the save as profile form * * @method clearSaveProfileForm */ var clearSaveProfileForm = function() { // reseting model values scope.saveAsData = _.cloneDeep(initContent); if (scope.form && scope.form.$dirty) { scope.form.txtFilename.$setValidity("invalidFilename", true); // mark the form pristine scope.form.$setPristine(); } if (!_.isUndefined(scope.onCancel)) { scope.onCancel({ position: scope.position }); } }; /** * Save as new profile. * * @method saveForm */ scope.actionHandler.saveForm = function() { // Destroy all growls before attempting to submit something. growlMessages.destroyAllMessages(); // Throw console error when packages are not provided. if (_.isUndefined(scope.packages)) { throw "Packages for the profile are not provided. Wherever this directive is used, make sure to fill the packages attribute correctly."; } if (scope.form.$valid) { // upload profile var overwrite = scope.saveAsData.overwrite ? 1 : 0; var inputTags = _.split(scope.saveAsData.tagsAsString, /\s*,\s*/); var filenameWithExt = scope.saveAsData.filename.name + ".json"; var contentJson = { "name": scope.saveAsData.name, "desc": scope.saveAsData.desc, "pkgs": scope.packages, "tags": _.compact(inputTags) }; return ea4Data.saveAsNewProfile(contentJson, filenameWithExt, overwrite) .then(function(data) { if (typeof data !== "undefined" && !_.isEmpty(data.path)) { // TODO: Make the profile name to be a link to profiles page in the message. growl.success(LOCALE.maketext("The system successfully saved the current packages to the “[_1]” profile. It is available in the EasyApache 4 profiles page.", _.escape(scope.saveAsData.name))); clearSaveProfileForm(); if (!_.isUndefined(scope.onSaveSuccess)) { scope.onSaveSuccess(); } } }, function(response) { if (typeof response.data !== "undefined" && response.data.already_exists) { scope.highlightOverwrite = true; } if (!_.isUndefined(scope.onSaveSuccess)) { scope.onSaveError(); } growl.error(_.escape(response.error)); }); } }; /** * Cancel save action. * * @method cancel */ scope.actionHandler.cancel = function() { clearSaveProfileForm(); }; /** * Run filename validation and set the validation * inputs with the results accordingly. * * @method validateFilenameInput */ scope.validateFilenameInput = function() { var valData = ea4Util.validateFilename(scope.saveAsData.filename.name); scope.saveAsData.filename.valMsg = valData.valMsg; scope.form.txtFilename.$setValidity("invalidFilename", valData.valid); }; } }; return ddo; } ] ); } ); /* # cpanel - whostmgr/docroot/templates/easyapache4/services/pkgResolution.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( 'app/services/pkgResolution',[ "angular", "lodash", "cjt/io/api", "cjt/io/whm-v1-request", "cjt/io/whm-v1", "cjt/services/APIService", ], function(angular, _) { "use strict"; var app = angular.module("whm.easyapache4.pkgResolution", []); app.factory("pkgResolution", function() { var oData = { // Common variables used. eaRegex: /^ea-/, allConflicts: [], allRequires: [], resolvedRemoveList: [], requireStructure: { "safe": [], // This will have array of safe required packages. "unsafe": {}, // This will have { 'unsafe_req_pkg': [ con_pkgs_for_this_unsafe_req_pkg, … ], … } }, conflictStructure: { "safe": [], // This will have array of safe conflict packages. "unsafe": {}, // This will have { 'unsafe_conflict_pkg': [ req_pkgs_for_this_unsafe_conflict_pkg, … ], … } }, orListStructure: { exist: false, orLists: [], }, resolvedPackages: { addList: [], removeList: [], actionNeeded: false, }, }; /** * Resets all the common variables that are used during the conflict/requirement resolution. * @method resetCommonVariables */ oData.resetCommonVariables = function() { // Reset the common variables to keep them ready to re-use for next select/unselect actions. oData.allConflicts = []; oData.allRequires = []; oData.resolvedRemoveList = []; oData.requireStructure = { "safe": [], "unsafe": {}, }; oData.conflictStructure = { "safe": [], "unsafe": {}, }; oData.orListStructure = { exist: false, orLists: [], }; oData.resolvedPackages = { addList: [], removeList: [], actionNeeded: false, }; }; /** * Returns multirequirement data. * * @method getOrListStructure * @returns {object} */ oData.getOrListStructure = function() { return oData.orListStructure; }; /** * Updates multirequirement related data. * * @method setOrListStructure * @param {object} data - New orListStructure data. */ oData.setOrListStructure = function(data) { oData.orListStructure = data; }; /** * Returns the resolved pacakge data. * * @method getResolvedData * @returns {object} */ oData.getResolvedData = function() { return oData.resolvedPackages; }; /** * Resolves the requirements and conflicts by going through all * nested levels of the current package dependencies. * * @method resolveDependenciesWhenSelected * @param {object} thisPackage - The selected package data. * @param {array} selectedPackages - Current list of selected packages. * @param {array} pkgInfoList - A detailed list of all packages data. * @returns {object} - Returns an object with information on more action needed and multirequirement status. */ oData.resolveDependenciesWhenSelected = function(thisPackage, selectedPackages, pkgInfoList) { var retData = { orListExist: false, actionNeeded: false, }; if (thisPackage) { // Recurse through all requirements for this package // and collect the requirements into 'allRequires' // and the corresponding conflicts into 'allConflicts' oData.recurseWhenSelected(thisPackage, thisPackage.package, pkgInfoList); // Add this package too as it isn't added during the recursion. oData.allRequires.push(thisPackage.package); oData.orListStructure = oData.updateMultiRequirements(oData.orListStructure, selectedPackages, oData.allRequires, oData.allConflicts, pkgInfoList); if (oData.orListStructure.exist) { retData.orListExist = oData.orListStructure.exist; } else { var actionNeeded = oData.proceedToResolveRequirementsAndConflicts(thisPackage, selectedPackages, pkgInfoList); retData.actionNeeded = actionNeeded; } } return retData; }; /** * Updates the multi requirement object. * * @method updateMultiRequirements * @param {object} multiReq - The multiRequirment object to be updated. * @param {array} selectedPackages - Current list of selected packages. * @param {array} allRequires - A list of all required packages in the current context. * @param {array} allConflicts - A list of all conflicts in the current context. * @param {object} pkgInfoList - A list containing package information of all the available packages in EA4 repo. * @returns {object} - Updated multiRequirement object. */ oData.updateMultiRequirements = function(multiReq, selectedPackages, allRequires, allConflicts, pkgInfoList) { if (multiReq) { // In multiRequirement data, check to see if the packages in orLists // are already in selectedPackages or in allRequires. If yes then resolve them. var orLists = _.clone(multiReq.orLists); _.each(orLists, function(eachList) { var removeThisList = false; var filterList = _.filter(eachList, function(pkg) { // Consider only EasyApache packages if they are present on the system. return (oData.eaRegex.test(pkg) && (typeof pkgInfoList[pkg] !== "undefined")); }); removeThisList = _.isEmpty(filterList); var alreadyInRequires = []; if (!removeThisList) { // See if any package in this OR list instance is among // selected packages. alreadyInRequires = _.intersection(filterList, selectedPackages); // If not, see if any package in this OR list instance is already // a required package. if (_.isEmpty(alreadyInRequires)) { alreadyInRequires = _.intersection(filterList, allRequires); } if (!_.isEmpty(alreadyInRequires)) { if (filterList.length === 1) { oData.allRequires = _.union(allRequires, filterList); } removeThisList = true; } else { var findConflicts = _.intersection(filterList, allConflicts); filterList = _.difference(filterList, findConflicts); if (filterList.length === 1) { oData.allRequires = _.union(allRequires, filterList); removeThisList = true; } } } // Look if this list can be removed now. if (removeThisList) { _.pull(multiReq.orLists, eachList); } else if (!_.isEqual(eachList, filterList)) { // In this case replace old list with new one. _.pull(multiReq.orLists, eachList); if (!_.isEmpty(filterList)) { multiReq.orLists = _.concat(multiReq.orLists, [filterList]); } } }); multiReq.exist = multiReq.orLists.length > 0; } return multiReq; }; /** * Continues to resolve conflict/requirements from where it previously * left off. (i.e. If a multirequirement existence is detected, then it stops the resolution * operation and prompts the user to choose one from the multiple requirements. Then after that it continues * from this function.) * * @method continueResolvingDependencies * @param {object} thisPackage - The selected package data. * @param {object} chosenPkg - The chosen package data. * @param {string} chosenPkgName * @param {array} pkgInfoList - A detailed list of all packages data. * @param {array} selectedPackages - Current list of selected packages. * @returns {object} - Returns an object with information on more action needed and multirequirement status. */ oData.continueResolvingDependencies = function(thisPackage, chosenPkg, chosenPkgName, pkgInfoList, selectedPackages) { var retData = { orListExist: false, actionNeeded: false, }; // Recurse through all requirements for this package // and collect the requirements into 'allRequires' // and the corresponding conflicts into 'allConflicts' oData.recurseWhenSelected(chosenPkg, chosenPkgName, pkgInfoList); // Add this package too as it isn't added during the recursion. oData.allRequires.push(chosenPkgName); oData.orListStructure = oData.updateMultiRequirements(oData.orListStructure, selectedPackages, oData.allRequires, oData.allConflicts, pkgInfoList); if (oData.orListStructure.exist) { retData.orListExist = oData.orListStructure.exist; } else { var actionNeeded = oData.proceedToResolveRequirementsAndConflicts(thisPackage, selectedPackages, pkgInfoList); retData.actionNeeded = actionNeeded; } return retData; }; /** * Second part of the conflict/dependency process after all nested levels * of conflicts and requirements are collected through recursive process. * * @method proceedToResolveRequirementsAndConflicts * @param {object} thisPackage - The selected package data. * @param {array} selectedPackages - Current list of selected packages. * @param {array} pkgInfoList - A detailed list of all packages data. * @returns {Boolean} - Returns a flag if action needed or not. */ oData.proceedToResolveRequirementsAndConflicts = function(thisPackage, selectedPackages, pkgInfoList) { // REFACTOR: thisPackage can be removed since it's not used in this method anywhere. // var selectedPackages = $scope.selectedProfile.pkgs; // var pkgInfoList = pkgInfoList; // Get the required packages that are not selected. var reqToConsider = _.difference(oData.allRequires, selectedPackages); if (reqToConsider.length > 0) { // Constructs requireStructure by // adding safe and unsafe requirements appropriately. oData.requireStructure = oData.buildRequireStructure(reqToConsider, selectedPackages, pkgInfoList); } var consToConsider = _.intersection(oData.allConflicts, selectedPackages); if (consToConsider.length > 0) { // Constructs conflictStructure by // adding safe and unsafe conflicts appropriately. oData.conflictStructure = oData.buildConflictStructure(consToConsider, selectedPackages, pkgInfoList); } // This step ensures all unsafe require are resolved // and moved to requireStructure.safe array. oData.requireStructure = oData.resolveUnsafeRequires(selectedPackages, oData.requireStructure, pkgInfoList); // This step ensures all unsafe conflicts are resolved // and moved to conflictStructure.safe array. oData.conflictStructure = oData.resolveUnsafeConflicts(selectedPackages, oData.conflictStructure, pkgInfoList); // At this point we have the following data: // * resolvedRemoveList - list of all conflicts resolved and ready to remove // * conflictStructure.safe - list of all safe conflicts to remove // * requireStructure.safe - list of all safe requires to add oData.resolvedPackages.removeList = _.union(oData.resolvedRemoveList, oData.conflictStructure.safe); oData.resolvedPackages.addList = oData.requireStructure.safe; oData.resolvedPackages.actionNeeded = oData.resolvedPackages.removeList.length > 0; return oData.resolvedPackages.actionNeeded; }; /** * Builds a requirement structure which separates out the safe and unsafe requirements. * Safe requirement: If a particular required package is not conflicting with any installed packages. * Unsafe requirement: If it conflicts with any installed package. * * @method buildRequireStructure * @param {array} reqToConsider * @param {array} selectedPackages - Current list of selected packages. * @param {array} pkgInfoList - A detailed list of all packages data. * @returns {object} - Returns an object with safe and unsafe requirements. */ oData.buildRequireStructure = function(reqToConsider, selectedPackages, pkgInfoList) { var reqStruct = oData.requireStructure; if (pkgInfoList) { _.each(reqToConsider, function(req) { var reqIsSafe = true; _.each(selectedPackages, function(pkg) { var pkgDetails = pkgInfoList[pkg]; if (typeof pkgDetails !== "undefined" && _.includes(pkgDetails.pkg_dep.conflicts, req)) { // At this point this 'req' is unsafe since it conflicts with an existing package. // initialize an array (if it's not already) to store this unsafe req's conflicts. if (typeof reqStruct.unsafe[req] === "undefined") { reqStruct.unsafe[req] = []; } reqStruct.unsafe[req].push(pkg); reqIsSafe = false; } }); if (reqIsSafe) { reqStruct.safe.push(req); } }); } return reqStruct; }; /** * Builds a conflict structure which separates out the safe and unsafe conflicts. * Safe conflict: If a particular conflicting package is a not a requirement for any installed packages. * Unsafe conflict: If it is a requirement for any installed packages. * * @method buildConflictStructure * @param {array} consToConsider * @param {array} selectedPackages - Current list of selected packages. * @param {array} pkgInfoList - A detailed list of all packages data. * @returns {object} - Returns an object with safe and unsafe conflicts. */ oData.buildConflictStructure = function(consToConsider, selectedPackages, pkgInfoList) { var conStruct = oData.conflictStructure; _.each(consToConsider, function(con) { var conIsSafe = true; _.each(selectedPackages, function(pkg) { var pkgDetails = pkgInfoList[pkg]; if (typeof pkgDetails !== "undefined" && _.includes(pkgDetails.pkg_dep.requires, con)) { // At this point this 'con' is unsafe since it is required by an existing package. // initialize an array (if it's not already) to store this unsafe con's requirements. if (typeof conStruct.unsafe[con] === "undefined") { conStruct.unsafe[con] = []; } conStruct.unsafe[con].push(pkg); conIsSafe = false; } }); if (conIsSafe) { conStruct.safe.push(con); } }); return conStruct; }; /** * Unsafe requirements are examined and resolved by adding them to the * remove list accordingly. * * @method resolveUnsafeRequires * @param {array} selectedPackages - Current list of selected packages. * @param {object} reqStruct * @param {array} pkgInfoList - A detailed list of all packages data. * @returns {object} - Updated reqStruct with resolved unsafe requirements. */ oData.resolveUnsafeRequires = function(selectedPackages, reqStruct, pkgInfoList) { if (reqStruct) { var oUnsafeRequires = reqStruct.unsafe; var keys = _.keys(oUnsafeRequires); _.each(keys, function(req) { var conflictsToResolve = oUnsafeRequires[req]; conflictsToResolve = _.difference(conflictsToResolve, oData.resolvedRemoveList); _.each(conflictsToResolve, function(con) { // TODO: The includes if condition can be removed since resolvedRemoveList items // are removed from conflictsToResolve list in line above using _.difference operation. if (!_.includes(oData.resolvedRemoveList, con)) { var selPkgsToConsider = _.difference(selectedPackages, oData.resolvedRemoveList); oData.recurseConflictRequirements(pkgInfoList[con], con, selPkgsToConsider, pkgInfoList); oData.resolvedRemoveList = _.union(oData.resolvedRemoveList, [con]); } }); // Move this 'req' from unsafe to safe. reqStruct.safe.push(req); // TODO: This line may not be needed since the unsafe object is reset at the end of this function. _.unset(reqStruct.unsafe, req); }); // At this point all unsafe requires are resolved. So remove unsafe from reqStruct. reqStruct.unsafe = {}; } return reqStruct; }; /** * Unsafe conflict are examined and resolved by adding them to the * remove list accordingly. * * @method resolveUnsafeConflicts * @param {array} selectedPackages - Current list of selected packages. * @param {object} conStruct * @param {array} pkgInfoList - A detailed list of all packages data. * @returns {object} - Updated conStruct with resolved unsafe conflicts. */ oData.resolveUnsafeConflicts = function(selectedPackages, conStruct, pkgInfoList) { if (conStruct) { // Remove all conflict objects from conflictStructure.unsafe that are present in resolvedRemoveList. // since they are already resolved. var oUnsafeConflicts = _.omit(conStruct.unsafe, oData.resolvedRemoveList); var keys = _.keys(oUnsafeConflicts); _.each(keys, function(con) { var reqsToResolve = _.difference(oUnsafeConflicts[con], oData.resolvedRemoveList); _.each(reqsToResolve, function(req) { // TODO: The includes if condition can be removed since resolvedRemoveList items // are removed from conflictsToResolve list in line above using _.difference operation. if (!_.includes(oData.resolvedRemoveList, req)) { var selPkgsToConsider = _.difference(selectedPackages, oData.resolvedRemoveList); oData.recurseConflictRequirements(pkgInfoList[req], req, selPkgsToConsider, pkgInfoList); oData.resolvedRemoveList = _.union(oData.resolvedRemoveList, [req]); } }); // Move this 'con' from unsafe to safe. conStruct.safe.push(con); // TODO: This line may not be needed since the unsafe object is reset at the end of this function. _.unset(conStruct.unsafe, con); }); // At this point all unsafe conflicts are resolved. So remove unsafe from conStruct. conStruct.unsafe = {}; } return conStruct; }; /** * Recursive method that recurses through all requirements of a package and * collect their corresponding conflicts. * * @method recurseConflictRequirements * @param {object} conPackage - conflict package data. * @param {string} origPkgName - The original package name of the package selected in the current context. * @param {array} selPkgsToConsider - Current list selected packages. * @param {array} pkgInfoList - A detailed list of all packages data. */ oData.recurseConflictRequirements = function(conPackage, origPkgName, selPkgsToConsider, pkgInfoList) { var pkgName = conPackage.package; if (origPkgName !== pkgName) { oData.resolvedRemoveList.push(pkgName); } var recurseArray = _.pull(selPkgsToConsider, pkgName); // Find all selected packages that require pkgName. recurseArray = _.filter(recurseArray, function(pkg) { var pkgDetails = pkgInfoList[pkg]; return (typeof pkgDetails !== "undefined" && _.includes(pkgDetails.pkg_dep.requires, pkgName)); }); _.each(recurseArray, function(recurPkg) { oData.recurseConflictRequirements(pkgInfoList[recurPkg], origPkgName, selPkgsToConsider, pkgInfoList); }); }; // TODO: Try to return data instead of changing global variable from within // this method. /** * This method takes the selected package (origPkgName) * and finds recursively requirements and conflicts down to all levels and store them in * 'allRequires' & 'allConflicts' variables respectively. It also identifies packages * which have multi requirement options and feed them into 'orListStructure' variable. They are handled * through other methods. * * @method recurseWhenSelected * @param {object} pkgInfo - The selected package data. * @param {string} origPkgName - The original package name of the package selected in the current context. * @param {array} pkgInfoList - A detailed list of all packages data. */ oData.recurseWhenSelected = function(pkgInfo, origPkgName, pkgInfoList) { if (pkgInfo) { var pkgName = pkgInfo.package; // Proceed only if it is an EA package and not previously went through this recursion. if (!oData.eaRegex.test(pkgName)) { return; } if (_.includes(oData.allRequires, pkgName)) { return; } // Add the package to oData.allRequires list unless it is the package selected in UI. if (typeof origPkgName !== "undefined" && pkgName !== origPkgName) { oData.allRequires.push(pkgName); } // Collect all 'ea' packages that are in conflict with current pkg. oData.allConflicts = _.union(oData.allConflicts, _.filter(pkgInfo.pkg_dep.conflicts, function(pkg) { return (oData.eaRegex.test(pkg)); })); var recurseArray = []; if (pkgInfo.pkg_dep.requires.length > 0) { recurseArray = _.clone(pkgInfo.pkg_dep.requires); // IDENTIFY multi-requirement arrays. // A multi-requirement array means for example cgid requires either // mpm-event OR mpm-worker // so cgid will have a multi-requirement array: // [ mpm-event, mpm-worker ] // We identify such multi-requirement arrays, find if any one of them is selected, // and use that. If not we collect such arrays in oData.orListStructure.orLists // and show it to the user and let them choose. var orLists = _.remove(recurseArray, function(pkg) { return _.isArray(pkg); }); if (orLists.length > 0) { oData.orListStructure.orLists = _.unionWith(oData.orListStructure.orLists, orLists, _.isEqual); } recurseArray = _.filter(recurseArray, function(pkg) { return (oData.eaRegex.test(pkg)); }); recurseArray = _.difference(recurseArray, oData.allRequires); } _.forEach(recurseArray, function(reqPkg) { var reqPkgInfo = pkgInfoList[reqPkg]; if (typeof reqPkgInfo !== "undefined") { oData.recurseWhenSelected(reqPkgInfo, origPkgName, pkgInfoList); } }); } }; /** * Get all dependencies(requires/conflicts) recursively for a given package when selected. * * @method getAllDepsRecursively * @param {Boolean} selected - given package selected state. * @param {Object} pkgInfo - Package information object. * @param {String} origPkgName - Package name. * @param {String} pkgInfoList - List of all package info objects. * @param {array} selectedPackages - Current list of selected packages. * @return {Object} - * On package select action: { 'requiredPkgs': [...], conflictPkgs: [...] } * On package unselect action: { 'removedList': [...] } */ oData.getAllDepsRecursively = function(selected, pkgInfo, origPkgName, pkgInfoList, selectedPackages) { var deps = {}; if (selected) { oData.recurseWhenSelected(pkgInfo, origPkgName, pkgInfoList); deps = { requiredPkgs: oData.allRequires, conflictPkgs: oData.allConflicts, }; } else { oData.recurseWhenUnselected(pkgInfo, origPkgName, pkgInfoList, selectedPackages); deps = { removedList: oData.resolvedRemoveList, }; } return deps; }; /** * Resolves the requirements and conflicts of an unselected package, by going through all * nested levels of the current package dependencies. * Updates the resolvePackages.removeList. * * @method resolveDependenciesWhenUnSelected * @param {object} thisPackage - The selected package data. * @param {array} selectedPackages - Current list of selected packages. * @param {array} pkgInfoList - A detailed list of all packages data. */ oData.resolveDependenciesWhenUnSelected = function(thisPackage, selectedPackages, pkgInfoList) { if (thisPackage) { var pkgName = thisPackage.package; oData.resolvedRemoveList.push(pkgName); oData.recurseWhenUnselected(thisPackage, pkgName, pkgInfoList, selectedPackages); // Updates oData.resolvedRemoveList. oData.resolvedPackages.removeList = _.uniq(oData.resolvedRemoveList); } }; /** * This recursive method takes the unselected package (origPkgName) * and finds packages that depend on it and add it to the removedList. * The recursion gets applied to the removedList all the way up and find package dependencies * that need to be removed. * * @method recurseWhenUnselected * @param {object} pkgInfo - The selected package data. * @param {string} origPkgName - The selected package name. * @param {array} pkgInfoList - A detailed list of all packages data. * @param {array} selectedPkgsToUnselectFrom - Selected package list that is used as a reference to check what needs to be removed. */ oData.recurseWhenUnselected = function(pkgInfo, origPkgName, pkgInfoList, selectedPkgsToUnselectFrom) { var pkgName = pkgInfo.package; // Proceed only if it is an EA package and not previously went through this recursion. if (!oData.eaRegex.test(pkgName)) { return; } if (_.includes(oData.allRequires, pkgName)) { return; } // Add the package to allRequires list unless it is the package selected in UI. if (typeof origPkgName !== "undefined" && pkgName !== origPkgName) { oData.allRequires.push(pkgName); } /* NOTES: * Pick the packages from the `selectedPackages` list which depend (require) on the package being unselected. * Recurse back all the way until we find all packages that depend on the one's being unselected. **/ var recurseArray = []; if (selectedPkgsToUnselectFrom.length > 0) { _.each(selectedPkgsToUnselectFrom, function(pkg) { if (_.includes(pkgInfoList && pkgInfoList[pkg] && pkgInfoList[pkg].pkg_dep.requires, pkgName)) { // At this point, the package 'pkg' depends directly/indirectly on the original package. So, // add this to the remove list. oData.resolvedRemoveList.push(pkg); recurseArray.push(pkg); } }); selectedPkgsToUnselectFrom = _.pullAll(selectedPkgsToUnselectFrom, recurseArray); // IDENTIFY multi-requirement arrays. // A multi-requirement array means for example cgid requires either // mpm-event OR mpm-worker // so cgid will have a multi-requirement array: // [ mpm-event, mpm-worker ] // We identify such multi-requirement arrays, find if any one of them is selected, // and use that. If not we collect such arrays in orListStructure.orLists // and show it to the user and let them choose. var orLists = _.remove(recurseArray, function(pkg) { return _.isArray(pkg); }); // From each orList, add the one's that are currently selected. _.each(orLists, function(orList) { _.concat(recurseArray, _.filter(orList, function(pkg) { return (typeof pkgInfoList[pkg] !== "undefined" && pkgInfoList[pkg].selectedPackage); })); }); recurseArray = _.filter(recurseArray, function(pkg) { return (oData.eaRegex.test(pkg)); }); recurseArray = _.difference(recurseArray, oData.allRequires); } _.forEach(recurseArray, function(reqPkg) { var reqPkgInfo = pkgInfoList[reqPkg]; if (typeof reqPkgInfo !== "undefined") { oData.recurseWhenUnselected(reqPkgInfo, origPkgName, pkgInfoList, selectedPkgsToUnselectFrom); } }); }; /** * Constructs multiRequirement object for a package's dependencies. * * @method setupMultiRequirementForUserInput * @param {object} pkgInfoList An package info object with key (package name) value (package object) pairs. */ oData.setupMultiRequirementForUserInput = function(pkgInfoList) { var multiRequirements = {}; // Check if orLists is empty. If empty, // Pull the first orList from multiReq. var orListStruct = oData.getOrListStructure(); if (typeof orListStruct !== "undefined") { var orList = orListStruct.orLists.shift(); if (typeof orList !== "undefined" && orList.length > 0) { var orListWithPkgNames = _.map(orList, function(orPkg) { if (typeof pkgInfoList[orPkg] !== "undefined") { return { "package": orPkg, "displayName": pkgInfoList[orPkg].displayName }; } }); multiRequirements = { exist: orListStruct.exist, orList: orListWithPkgNames, chosenPackage: "", }; } } return multiRequirements; }; /** * Resets all common and package object variables that deal with * conflict/requirement resolving process. * * @method resetPkgResolveActions * @param {object} pkgInfo - Package object. * @returns {object} - package object. */ oData.resetPkgResolveActions = function(pkgInfo) { oData.resetCommonVariables(); // Reset the actions of this package to empty if (pkgInfo) { // TODO: merge pkgInfo.actions & multirequirements into // pkgInfo.resolveData = { // removeList: [], addList: [], actionNeeded: false, // multiRequirements: {} // }; pkgInfo.actions = { removeList: [], addList: [], actionNeeded: false }; pkgInfo.mpmMissing = false; pkgInfo.mpmMissingMsg = ""; pkgInfo.multiRequirements = {}; } return pkgInfo; }; /** * Prepares the callout to display the conflict requirements that are identified * during the resolving process. * * @method setupConDepCallout * @param {object} pkgInfo - The selected package data. * @param {array} pkgInfoList - A detailed list of all packages data. * @returns {object} - Selected package data with updated dependency resolution info for callout . */ oData.setupConDepCallout = function(pkgInfo, pkgInfoList) { // get resolved package data. var resData = oData.getResolvedData(); if (resData.actionNeeded) { pkgInfo.actions.actionNeeded = resData.actionNeeded; pkgInfo.actions.removeList = _.map(resData.removeList, function(pkg) { return pkgInfoList[pkg].displayName; }); pkgInfo.actions.addList = _.map(resData.addList, function(pkg) { return pkgInfoList[pkg].displayName; }); } return pkgInfo; }; return oData; }); } ); /* # cpanel - whostmgr/docroot/templates/easyapache4/services/wizardApi.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( 'app/services/wizardApi',[ "angular", "lodash", "cjt/util/locale", // CJT "cjt/io/api", "cjt/io/whm-v1-request", "cjt/io/whm-v1", // Angular components "cjt/services/APIService", // App components ], function(angular, _, LOCALE) { "use strict"; var app = angular.module("whm.easyapache4.wizardApi", []); app.factory("wizardApi", ["$location", "wizardState", "ea4Data", "ea4Util", function($location, wizardState, ea4Data, ea4Util) { var oData = { defaultWizardState: { showWizard: false, showSearchAndPage: false, currentStepIndex: 0, showFooter: false, currentStep: "", lastStepName: "", steps: { "mpm": { name: "mpm", title: LOCALE.maketext("Apache [output,acronym,MPM,Multi-Processing Modules]"), path: "mpm", stepIndex: 1, nextStep: "modules" }, "modules": { name: "modules", title: LOCALE.maketext("Apache Modules"), path: "modules", stepIndex: 2, nextStep: "php" }, "php": { name: "php", title: LOCALE.maketext("[output,acronym,PHP,PHP Hypertext Preprocessor] Versions"), path: "php", stepIndex: 3, nextStep: "extensions" }, "extensions": { name: "extensions", title: LOCALE.maketext("[output,acronym,PHP,PHP Hypertext Preprocessor] Extensions"), path: "extensions", stepIndex: 4, nextStep: "ruby" }, "ruby": { name: "ruby", title: LOCALE.maketext("[asis,Ruby] via [asis,Passenger]"), path: "ruby", stepIndex: 5, nextStep: "additional" }, "additional": { name: "additional", title: LOCALE.maketext("Additional Packages"), path: "additional", stepIndex: 6, nextStep: "review" }, "review": { name: "review", title: LOCALE.maketext("Review"), path: "review", stepIndex: 7, nextStep: "" }, }, }, }; oData.getDefaultWizardState = function() { return oData.defaultWizardState; }; /** * Checks the existence of certain packages and keeps or removes certain steps. After * the evaluation it rebuilds the wizard steps accordingly. * @param {Object} wizardSteps Object containing wizard steps. * @param {Object} rebuildArgs * @return {Object} Returns the new rebuilt wizardSteps Object. */ oData.rebuildWizardSteps = function(wizardSteps, rebuildArgs) { wizardSteps = wizardSteps || {}; if (!rebuildArgs.rubyPkgsExist) { delete wizardSteps["ruby"]; } if (!rebuildArgs.additionalPkgsExist) { delete wizardSteps["additional"]; } // Sort the steps. orderby isn't working directly in the ng-repeat (shrug). var sortedSteps = _.orderBy(_.values(wizardSteps), ["stepIndex"], ["asc"]); wizardSteps = _.keyBy(sortedSteps, function(step) { return step.name; }); return wizardSteps; }; oData.init = function() { wizardState.steps = oData.defaultWizardState.steps; var pkgList = ea4Data.getData("pkgInfoList"); if (pkgList) { var rebuildArgs = { rubyPkgsExist: ea4Util.doRubyPkgsExist(pkgList), additionalPkgsExist: ea4Data.getData("additionalPkgsExist"), }; wizardState.steps = oData.rebuildWizardSteps(oData.defaultWizardState.steps, rebuildArgs); } wizardState.showWizard = false; wizardState.showSearchAndPage = false; wizardState.showFooter = false; wizardState.currentStepIndex = 1; wizardState.lastStepName = "review"; }; oData.updateWizard = function(config) { _.each(_.keys(config), function(key) { wizardState[key] = config[key]; }); }; oData.getStepByName = function(stepName) { return wizardState.steps[stepName]; }; oData.getStepNameByIndex = function(index) { var stepObj = _.find(wizardState.steps, ["stepIndex", index]); if (typeof stepObj !== "undefined") { return stepObj.name; } }; /** * Reset the wizard to it initial state. It will forward any * arguments passed into the call to the registered * function. * * @name reset */ oData.reset = function() { wizardState = oData.getDefaultWizardState(); }; /** * This function auto updates wizardState to the next step index and go to that step * if no arguments are passed. * If stepName argument is passed, then it updates the wizardState to the given step, * and goes to the given step. * * @name next * @arg stepName [optional] If passed, this method will send to the given step's view. */ oData.next = function(stepName) { if (stepName) { wizardState.currentStepIndex = oData.getStepByName(stepName).stepIndex; wizardState.currentStep = stepName; } else { wizardState.currentStepIndex++; stepName = oData.getNextStepNameByIndex(wizardState.currentStepIndex); wizardState.currentStep = stepName; } $location.path(stepName); }; oData.getNextStepNameByIndex = function(index) { var lastStepIndex = oData.getLastStep().stepIndex; var stepObj = _.find(wizardState.steps, ["stepIndex", index]); if (typeof stepObj === "undefined") { // Find the next available step. for (var i = index + 1; i <= lastStepIndex; i++) { stepObj = _.find(wizardState.steps, ["stepIndex", i]); if (stepObj === "undefined") { continue; } } } return (stepObj) ? stepObj.name : ""; }; /** * Get the wizard's last step object. * @return {Object} Returns wizard step object. */ oData.getLastStep = function() { return wizardState.steps[wizardState.lastStepName]; }; return { init: oData.init, getStepByName: oData.getStepByName, updateWizard: oData.updateWizard, next: oData.next, getDefaultWizardState: oData.getDefaultWizardState, reset: oData.reset, rebuildWizardSteps: oData.rebuildWizardSteps, getLastStep: oData.getLastStep, getNextStepNameByIndex: oData.getNextStepNameByIndex, }; }]); } ); /* # whostmgr/docroot/templates/easyapache4/directives/fileModel.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/directives/fileModel',[ "angular" ], function(angular) { // This directive updates the $scope when an <input type="file"> changes. // AngularJS ng-model does not keep the state of <input type="file"> linked with $scope. angular.module("App") .directive("fileModel", ["$parse", function($parse) { return { restrict: "A", require: "ngModel", link: function link($scope, $element, $attrs, $ngModelCtrl) { var model = $parse($attrs.fileModel); $element.bind("change", function() { var file = this.files[0]; if (file) { $scope.$apply(function() { model.assign($scope, file); // Mark as dirty $ngModelCtrl.$setViewValue($ngModelCtrl.$modelValue); $ngModelCtrl.$setDirty(); }); } }); } }; }]); }); /* # whostmgr/docroot/templates/cpanel_customization/directive/fileType.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('app/directives/fileType',[ "angular" ], function(angular) { // This directive validates an <input type="file"> based on the "type" property of a selected file. // The file-type attribute should contain an expression defining an array of valid types. angular.module("App") .directive("fileType", [function() { function checkType(file, types) { var valid = false; var fileType = file.type; // IE doesn't return file.type for some MIME types. // For example it doesn't for 'json' type. // FIX: For 'json' in IE browsers. // If (file.type is empty){ // match file extension with requested type. // } // NOTE: This is not a fix for all but will cover at least JSON. if (fileType === "") { var matchArr = file.name.match(/\.((?:.(?!\.))+)$/); fileType = (matchArr.length > 0) ? matchArr[1] : ""; // Hack for json type if (fileType === "json") { fileType = "application/" + fileType; } } valid = types.some(function(type) { return type === fileType; }); return valid; } return { restrict: "A", require: "ngModel", link: function link($scope, $element, $attrs, $ngModelCtrl) { $element.bind("change", function() { var file = this.files[0]; if (file && !checkType(file, $scope.$eval($attrs.fileType))) { $ngModelCtrl.$setValidity("filetype", false); } else { $ngModelCtrl.$setValidity("filetype", true); } }); } }; }]); }); /* # whostmgr/docroot/templates/cpanel_customization/directive/fileSize.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 */ define('app/directives/fileSize',[ "angular" ], function(angular) { // This directive validates an <input type="file"> based on the "type" property of a selected file. // The file-type attribute should contain an expression defining an array of valid types. angular.module("App") .directive("fileSize", [function() { return { restrict: "A", require: "ngModel", link: function link($scope, $element, $attrs, $ngModelCtrl) { $element.bind("change", function() { var file = this.files[0]; if (file) { // Check for empty files being uploaded if (file.size === 0) { $ngModelCtrl.$setValidity("fileSize", false); } else { $ngModelCtrl.$setValidity("fileSize", true); } } }); } }; }]); }); /* # cpanel - whostmgr/docroot/templates/easyapache4/views/profile.js # Copyright 2026 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( 'app/views/profile',[ "angular", "cjt/util/locale", "lodash", "cjt/services/alertService", "cjt/directives/alertList", "cjt/decorators/growlDecorator", "app/directives/fileModel", "app/directives/fileType", "app/directives/fileSize", "app/services/ea4Data", "app/services/ea4Util", "app/services/pkgResolution", ], function(angular, LOCALE, _) { "use strict"; // Retrieve the current application var app = angular.module("App"); app.controller("profile", [ "$scope", "$timeout", "$location", "$uibModal", "$q", "ea4Data", "ea4Util", "alertService", "growl", "growlMessages", function($scope, $timeout, $location, $uibModal, $q, ea4Data, ea4Util, alertService, growl, growlMessages) { $scope.profileList = []; $scope.activeProfile = {}; $scope.loadingProfiles = false; $scope.errorOccurred = false; $scope.noProfiles = false; $scope.upload = { show: false, profile: {}, content: {}, disableLocalSec: true, localSecIsOpen: true, disableUrlSec: false, urlSecIsOpen: false, url: { value: "", filename: "", filenameValMsg: "", showFilenameInput: false, }, overwrite: false, highlightOverwrite: false, }; $scope.convertProfile = { show: false }; var _ea4Recommendations = {}; $scope.isLoading = function() { return ( $scope.loadingProfiles || $scope.loadingProfileData ); }; var resetEA4UI = function() { // Reset wizard attributes. // TODO: This will stay here until ui.router is implemented // in the next template refactor. $scope.customize.wizard.currentStep = ""; $scope.customize.wizard.showWizard = false; alertService.clear(); // This cancels any previously customized packages. ea4Data.clearEA4LocalStorageItems(); }; var customizeProfile = function(thisProfile) { // This cancels any previously customized packages. ea4Data.clearEA4LocalStorageItems(); ea4Data.setData( { "selectedPkgs": thisProfile.pkgs, "customize": true, "ea4Recommendations": _ea4Recommendations, }); $location.path("loadPackages"); }; var goProvision = function(thisProfile) { ea4Data.setData( { "selectedProfile": thisProfile } ); $location.path("review"); }; $scope.$on("$viewContentLoaded", function() { resetEA4UI(); $scope.loadProfiles(); }); $scope.checkForEA4Updates = function() { $scope.customize.checkUpdateInfo = angular.copy(ea4Util.checkUpdateInfo); var promise = ea4Data.getPkgInfoList(); promise.then(function(data) { if (typeof data !== "undefined") { var rawPkgList = data; ea4Data.setData({ "ea4RawPkgList": rawPkgList }); var updatePkgs = _.map(_.filter(rawPkgList, ["state", "updatable"]), function(pkg) { return pkg.package; }); // Count the number of packages in updatable state. $scope.customize.checkUpdateInfo.pkgNumber = updatePkgs.length; $scope.customize.toggleUpdateButton(); } $scope.customize.checkUpdateInfo.isLoading = false; }, function(error) { alertService.add({ type: "danger", message: error, id: "alertMessages", closeable: false, }); }); }; $scope.loadProfiles = function() { $scope.profileList = []; $scope.loadingProfiles = true; var metaPromise = ea4Data.getEA4MetaInfo().then(function(response) { if (response.data) { ea4Util.cacheMetaInfo(response.data); } }).catch(function() { ea4Util.cacheMetaInfo(null); return null; }); var prefixesPromise = ea4Data.getAdditionalPkgPrefixes().then(function(prefixes) { ea4Util.cachePrefixes(prefixes || []); ea4Util.updateRegexForPrefixes(prefixes || []); }).catch(function() { ea4Util.cachePrefixes([]); ea4Util.updateRegexForPrefixes([]); return null; }); $q.all([metaPromise, prefixesPromise]).finally(function() { ea4Data.getProfiles().then(function(profData) { if (typeof profData !== "undefined") { $scope.noProfiles = false; $scope.checkForEA4Updates(); $scope.loadingProfileData = true; ea4Data.getEA4Recommendations(). then(function(result) { _ea4Recommendations = result.data; setProfileData(profData, _ea4Recommendations); }, function(error) { showProfileErrors(error); }).finally(function() { $scope.loadingProfileData = false; }); } else { $scope.noProfiles = true; } }, function(error) { if (error) { $scope.errorOccurred = true; ea4Data.setData( { "ea4ThrewError": true } ); $location.path("yumUpdate"); } }).finally(function() { $scope.loadingProfiles = false; }); }); }; $scope.viewProfile = function(thisProfile) { var viewingProfile = angular.copy(thisProfile); $uibModal.open({ templateUrl: "profileModalContent.tmpl", controller: "ModalInstanceCtrl", resolve: { data: function() { return viewingProfile; }, }, }); }; $scope.customizeCurrentProfile = function(thisProfile) { customizeProfile(thisProfile); }; $scope.proceedNext = function(thisProfile, customize) { // Track if customize button clicked or provision button clicked. $scope.clickedCustomize = customize; // Show a warning if there are packages in profile not on server. if (!thisProfile.isValid) { thisProfile.showValidationWarning = true; return; } thisProfile.showValidationWarning = false; $scope.continueAction(thisProfile); }; $scope.continueAction = function(thisProfile) { var customize = $scope.clickedCustomize; // Reset the clicked variable for next use. $scope.clickedCustomize = false; // Insert Apache 2.4 into the profile. This ensures people // get apache in whatever state their profile is. if (thisProfile.pkgs.indexOf("ea-apache24") === -1) { thisProfile.pkgs.push("ea-apache24"); } if (customize) { customizeProfile(thisProfile); } else { goProvision(thisProfile); } }; /** * Resets the clicked variable for next use. * * @method reset */ $scope.reset = function(thisProfile) { $scope.clickedCustomize = false; thisProfile.showValidationWarning = false; }; $scope.hideRecommendations = function(activeProfile) { activeProfile.showRecommendations = false; // Upon closing recommendation panel, return focus to recommendation link for screenreader/keyboard users $timeout(function() { angular.element("#toggleRecommendations").focus(); }); }; $scope.showRecommendations = function(activeProfile) { activeProfile.showRecommendations = true; // Apply focus to recommendation container for screenreader/keyboard users $timeout(function() { angular.element("#recommendations_container").focus(); }); }; var recommendationsOfActiveProfile = function(activeProfile, recommendations) { var currPkgList = activeProfile.pkgs; var filterPkgsWithRecos = _.intersection(currPkgList, _.keys(recommendations)); var filteredRecos = {}; _.each(filterPkgsWithRecos, function(pkg) { var reco = recommendations[pkg]; var recosList = ea4Util.decideShowHideRecommendations(reco, currPkgList, true, pkg); // passing 'true' as args to get recommendations of installed packages. // On the profiles page show only recommendations that have level: danger. recosList = _.filter(recosList, ["level", "danger"]); if (!_.isEmpty(recosList)) { filteredRecos[pkg] = {}; filteredRecos[pkg].recosList = recosList; filteredRecos[pkg].show = !_.every(recosList, [ "show", false ]); // Set the footnote. filteredRecos[pkg].footNote = LOCALE.maketext("These recommendations appear because you have “[_1]” installed on your system.", pkg); } }); return filteredRecos; }; /* Upload Popover section */ var resetValidators = function(formInput) { var valErrors = formInput.$error; if (typeof valErrors !== "undefined") { _.each(_.keys(valErrors), function(valKey) { $scope.formUpload.profile_file.$setValidity(valKey, true); }); } }; /** * Clears everything in the upload popover. * * @method clearUploadPopover */ var clearUploadPopover = function() { // Destroy all growls before attempting to submit something. growlMessages.destroyAllMessages(); // reseting model values var uploadData = $scope.upload; uploadData.content = {}; uploadData.overwrite = false; uploadData.highlightOverwrite = false; uploadData.disableLocalSec = true; uploadData.localSecIsOpen = true; uploadData.disableUrlSec = false; uploadData.urlSecIsOpen = false; clearUploadLocalForm($scope.upload); clearUploadUrlForm($scope.upload.url); }; /** * Clears the upload local section in Upload accordion. * * @method clearUploadLocalForm */ function clearUploadLocalForm(uploadData) { // reseting upload local section. uploadData.profile = {}; if ($scope.formUpload && $scope.formUpload.$dirty) { resetValidators($scope.formUpload.profile_file); // mark the form pristine $scope.formUpload.$setPristine(); try { angular.element("#profile_file").val(null); // for IE11, latest browsers } catch (error) { console.error("Error resetting form:", error); // For IE10 and others angular.element("#form_upload_profile").reset(); } } } /** * Clears the upload url section in Upload accordion. * * @method clearUploadUrlForm */ function clearUploadUrlForm(uploadUrlData) { // var uploadData = $scope.upload; uploadUrlData.filename = ""; uploadUrlData.filenameValMsg = ""; uploadUrlData.value = ""; uploadUrlData.showFilenameInput = false; if ($scope.formUpload && $scope.formUpload.$dirty) { var valErrors = $scope.formUpload.profile_file_url.$error; if (typeof valErrors !== "undefined") { _.each(_.keys(valErrors), function(valKey) { $scope.formUpload.profile_file.$setValidity(valKey, true); }); } resetValidators($scope.formUpload.profile_file_url); resetValidators($scope.formUpload.txtUploadUrlFilename); // mark the form pristine $scope.formUpload.$setPristine(); } } /** * Validates the profile content to * check if it contains name & at least * one package. * * @method validateProfile */ var validateProfile = function(fileContent) { var valid = true; if (_.isEmpty(fileContent.name) || _.isEmpty(fileContent.pkgs)) { valid = false; } return valid; }; /** * Validate the uploaded filename to see if it contains * restricted characters. * * @method validateFilename */ var validateFilename = function(filename) { var valid = true; if (/(?:\.\.|\\|\/)/.test(filename)) { valid = false; } return valid; }; /** * Reads the uploaded file to validate it. * * @method getAndValidateUploadData */ var getAndValidateUploadData = function() { // Destroy all growls before attempting to submit something. growlMessages.destroyAllMessages(); var fileData = $scope.upload.profile; if (!validateFilename(fileData.name)) { $scope.$apply($scope.formUpload.profile_file.$setValidity("invalidfilename", false)); return; } else { $scope.$apply($scope.formUpload.profile_file.$setValidity("invalidfilename", true)); } var reader = new FileReader(); reader.readAsText(fileData); reader.onloadend = function() { if (reader.readyState && !reader.error) { // Check if the file has the required data. var upContent = validateUploadContent(reader.result); _.each(_.keys(upContent.val_results), function(valKey) { $scope.$apply($scope.formUpload.profile_file.$setValidity(valKey, upContent.val_results[valKey])); }); if ($scope.formUpload.profile_file_url.$valid) { $scope.upload.content = upContent.content; } } }; }; /** * This validates the given content * and updates the validators accordingly. * * @method validateUploadContent */ function validateUploadContent(uploadContent) { // Check if the file has the required data. var content = ""; var valResults = {}; try { content = JSON.parse(uploadContent); valResults["invalidformat"] = true; if (!validateProfile(content)) { valResults["content"] = false; } else { valResults["content"] = true; } } catch (e) { valResults["invalidformat"] = false; console.error("Error parsing upload content:", e); } return { "content": content, "val_results": valResults }; } /** * Cancels the upload action for local section. * * @method cancelUpload */ $scope.cancelUpload = function() { $scope.upload.show = false; clearUploadPopover(); }; /** * Cancels the upload action for url section. * * @method resetUploadUrl */ $scope.resetUploadUrl = function() { clearUploadUrlForm($scope.upload.url); }; /** * Gets the content from the provided url and performs * validation checks to make sure it is a valid JSON * content with valid profile data. * * @method getAndValidateUploadDataFromURL */ $scope.getAndValidateUploadDataFromURL = function() { // Destroy all growls before attempting to submit something. growlMessages.destroyAllMessages(); return ea4Data.getUploadContentFromUrl($scope.upload.url.value) .then(function(data) { if (typeof data !== "undefined" && data.status === "200") { var contentType = data.headers["content-type"]; var validType = /^(application|text)\/json/.test(contentType); if (!validType) { $scope.formUpload.profile_file_url.$setValidity("filetype", false); return; } else { $scope.formUpload.profile_file_url.$setValidity("filetype", true); } // Check if the file has the required data. var upContent = validateUploadContent(data.content); _.each(_.keys(upContent.val_results), function(valKey) { $scope.formUpload.profile_file_url.$setValidity(valKey, upContent.val_results[valKey]); }); if ($scope.formUpload.profile_file_url.$valid) { $scope.upload.content = upContent.content; $scope.upload.url.showFilenameInput = true; } else { $scope.upload.url.showFilenameInput = false; } } else { var errorMsg = LOCALE.maketext("Status: “[output,strong,_1]”. Reason: “[output,em,_2]”.", _.escape(data.status), _.escape(data.reason)); growl.error(errorMsg); } }, function(error) { growl.error(_.escape(error)); }); }; /** * Scope method that calls ea4Util service's validateFilename method and * sets the validation message accordingly. * * @method validateFilenameInput */ $scope.validateFilenameInput = function() { var valData = ea4Util.validateFilename($scope.upload.url.filename); $scope.upload.url.filenameValMsg = valData.valMsg; $scope.formUpload.txtUploadUrlFilename.$setValidity("valFilename", valData.valid); }; /** * Uploads Profiles. * * @method uploadProfile */ $scope.uploadProfile = function() { // Destroy all growls before attempting to submit something. growlMessages.destroyAllMessages(); if ($scope.formUpload.$valid) { // upload profile var overwrite = $scope.upload.overwrite ? 1 : 0; var filenameWithExt = (typeof $scope.upload.profile.name !== "undefined") ? $scope.upload.profile.name : $scope.upload.url.filename + ".json"; return ea4Data.saveAsNewProfile($scope.upload.content, filenameWithExt, overwrite) .then(function(data) { if (typeof data !== "undefined" && !_.isEmpty(data.path)) { $scope.loadProfiles(); growl.success(LOCALE.maketext("The system successfully uploaded your profile.")); $scope.cancelUpload(); } }, function(response) { if (!_.isEmpty(response.data) && response.data.already_exists) { $scope.upload.highlightOverwrite = true; } growl.error(_.escape(response.error)); }); } }; /** * This toggle function handles enabling/disabling sections * so that at least one upload section is always open. * * @method handleAccordionToggle */ $scope.handleAccordionToggle = function() { clearUploadLocalForm($scope.upload); clearUploadUrlForm($scope.upload.url); if ($scope.upload.urlSecIsOpen) { $scope.upload.disableLocalSec = false; $scope.upload.disableUrlSec = true; } else { $scope.upload.disableUrlSec = false; $scope.upload.disableLocalSec = true; } }; /** * Shows the given popover with their initialization logic. * * @method showPopover */ $scope.showPopover = function(popoverName) { $scope.convertProfile.cancel(); switch (popoverName) { case "upload": $scope.upload.show = true; document.querySelector("#profile_file").onchange = getAndValidateUploadData; var accordionLinkEls = document.querySelectorAll(".panel-heading a"); _.each(accordionLinkEls, function(el) { el.onclick = $scope.handleAccordionToggle; }); break; case "convert": $scope.convertProfile.show = true; break; } }; /** * This method sets all the profile data including recommendations. * * @method setProfileData */ function setProfileData(data, recommendations) { var profileTypes = _.sortBy(_.keys(data)); _.each(profileTypes, function(type) { if (typeof data[type] !== "undefined") { _.each(data[type], function(profile) { profile.profileType = type; // Custom profile also handles tags like ActiveProfile for currently installed packages if (type === "custom") { var profileTags = ea4Util.createTagsForActiveProfile(profile.pkgs); profile.tags = profileTags; profile.tagsAsString = LOCALE.list_and(profileTags); } else { profile.tagsAsString = LOCALE.list_and(profile.tags); } // Initialize with a valid flag. profile.isValid = true; profile.showValidationWarning = false; if (!profile.active) { // Active profile is shown separately. profile.isValid = _.isEmpty(profile.validation_data.not_on_server); if (!profile.isValid) { profile.validation_data.not_on_server_without_prefix = ea4Util.getFormattedPackageList(profile.validation_data.not_on_server); } profile.id = type + "_" + profile.path.replace(/\.json/, ""); // If the type is other than cPanel Or Custom, it should be a vendor in which case the // path changes a bit. var pathByType = ( type !== "cpanel" && type !== "custom" ) ? "vendor/" + type : type; profile.downloadUrl = "ea4_profile_download/" + pathByType + "?filename=" + profile.path; $scope.profileList.push(profile); } else { $scope.activeProfile = profile; // need active profile packages in customize scope so can run packages updates $scope.customize.activeProfilePkgs = profile.pkgs; var recos = recommendationsOfActiveProfile($scope.activeProfile, recommendations); if (!_.isEmpty(_.keys(recos))) { $scope.activeProfile.showRecommendations = false; var actualRecos = _.pickBy(recos, function(value, key) { return recos[key].show; }); var recoCnt = 0; _.each(_.keys(actualRecos), function(key) { recoCnt += _.filter(actualRecos[key].recosList, ["show", true]).length; } ); $scope.activeProfile.recommendations = actualRecos; $scope.activeProfile.recommendationLabel = LOCALE.maketext("[quant,_1,Recommendation,Recommendations]", recoCnt); $scope.activeProfile.recommendationsExist = recoCnt ? true : false; } else { $scope.activeProfile.recommendations = {}; } } }); } }); // Check if there are any profiles. $scope.noProfiles = ($scope.profileList.length <= 0); // Active Profile. At present active profile will always be 'Currently Installed Packages' // This may change in future. // TODO: Add this method to ea4Util var tags = ea4Util.createTagsForActiveProfile($scope.activeProfile.pkgs); $scope.activeProfile.tags = tags; $scope.activeProfile.tagsAsString = LOCALE.list_and(tags); } /** * Error handling method for profile load failures. * * @method showProfileErrors */ function showProfileErrors(error) { $scope.errorOccurred = true; alertService.add({ type: "danger", message: error, id: "alertMessages", closeable: false, }); } }, ] ); app.controller("ModalInstanceCtrl", ["$scope", "$uibModalInstance", "data", "ea4Util", function($scope, $uibModalInstance, data, ea4Util) { $scope.modalData = {}; var profileInfo = data; profileInfo.pkgs = ea4Util.getProfilePackagesByCategories(profileInfo.pkgs); $scope.modalData = profileInfo; $scope.closeModal = function() { $uibModalInstance.close(); }; }, ] ); } ); /* # templates/easyapache4/views/yumUpdate.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, PAGE */ define( 'app/views/yumUpdate',[ "angular", "cjt/util/locale", "lodash", "cjt/services/alertService", "cjt/directives/alertList", "app/services/ea4Data", "app/services/ea4Util", "app/services/pkgResolution" ], function(angular, LOCALE, _) { // Retrieve the current application var app = angular.module("App"); app.controller("yumUpdate", [ "$scope", "$location", "ea4Data", "ea4Util", "alertService", "growl", "growlMessages", function($scope, $location, ea4Data, ea4Util, alertService, growl, growlMessages) { $scope.fixFailed = false; var fixYumCache = function() { $scope.fixingYum = true; ea4Data.fixYumCache().then(function(result) { if (result.status && result.data.cache_seems_ok_now) { app.firstLoad = false; ea4Data.setData( { "ea4ThrewError": false } ); $location.path("profile"); } else { $scope.fixFailed = true; } }, function(error) { $scope.fixFailed = true; }).finally(function() { $scope.fixingYum = false; }); }; $scope.$on("$viewContentLoaded", function() { // Destroy all old growls when view is loaded. growlMessages.destroyAllMessages(); var error = ea4Data.getData("ea4ThrewError"); if (error) { fixYumCache(); } }); }]); } ); /* # cpanel - whostmgr/docroot/templates/easyapache4/views/customize.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 */ define( 'app/views/customize',[ "angular", "lodash", "cjt/util/locale", "app/services/ea4Data", "app/services/pkgResolution", ], function(angular, _, LOCALE) { "use strict"; // Retrieve the current application var app = angular.module("App"); app.controller("customize", [ "$scope", "ea4Data", "pkgResolution", "wizardState", "wizardApi", "$location", "ea4Util", function($scope, ea4Data, pkgResolution, wizardState, wizardApi, $location, ea4Util) { $scope.customize = { pkgInfoList: {}, selectedPkgs: [], // This contains only the current step's package info. currentPkgInfoList: {}, saveProfilePopup: { position: "top", showTop: false, showBottom: false, }, // this contains the packages to run the update activeProfilePkgs: [], }; $scope.customize.wizard = wizardState; $scope.customize.wizardApi = wizardApi; /* ------- Save As Profile ------- */ $scope.customize.showsaveProfilePopup = function(position) { $scope.customize.saveProfilePopup.position = position; if (position !== "top") { $scope.customize.saveProfilePopup.showTop = false; $scope.customize.saveProfilePopup.showBottom = true; } else { $scope.customize.saveProfilePopup.showTop = true; $scope.customize.saveProfilePopup.showBottom = false; } }; $scope.customize.clearSaveProfilePopup = function(position) { if (position !== "top") { $scope.customize.saveProfilePopup.showTop = false; $scope.customize.saveProfilePopup.showBottom = false; $scope.customize.saveProfilePopup.position = "top"; } else { $scope.customize.saveProfilePopup.showTop = false; $scope.customize.saveProfilePopup.showBottom = false; } }; $scope.customize.loadData = function(type) { pkgResolution.resetCommonVariables(); $scope.customize.pkgInfoList = ea4Data.getData("pkgInfoList"); var customizeMode = ea4Data.getData("customize"); if (_.keys($scope.customize.pkgInfoList).length <= 0) { ea4Data.cancelOperation(); } else { $scope.customize.selectedPkgs = ea4Data.getData("selectedPkgs"); // set showWizard flag wizardApi.updateWizard( { "showWizard": customizeMode, "currentStep": type, } ); if (type === "review") { ea4Util.hideFooter(); } else { ea4Util.showFooter(); } } }; $scope.customize.processPkgInfoList = function(data) { if (typeof data !== "undefined") { var additionalPrefixes = ea4Util.getCachedPrefixes() || []; ea4Data.updateRegexesForPrefixes(additionalPrefixes); var recos = ea4Data.getData("ea4Recommendations"); $scope.customize.pkgInfoList = ea4Data.buildPkgInfoList($scope.customize.selectedPkgs, data, recos); ea4Data.setData({ "pkgInfoList": $scope.customize.pkgInfoList }); // Clear the raw package list after processing to free up localStorage space localStorage.removeItem("ea4RawPkgList"); } }; $scope.customize.loadPkgInfoList = function() { pkgResolution.resetCommonVariables(); $scope.customize.selectedPkgs = ea4Data.getData("selectedPkgs"); var promise = ea4Data.getPkgInfoList(); promise.then(function(data) { $scope.customize.processPkgInfoList(data); }); return promise; }; $scope.customize.proceed = function(step) { ea4Data.setData( { "pkgInfoList": $scope.customize.pkgInfoList, "selectedPkgs": $scope.customize.selectedPkgs, } ); wizardApi.next(step); }; $scope.customize.getStepClass = function(step) { if (step === $scope.customize.wizard.currentStep) { return "active"; } }; $scope.customize.getViewWidthCss = function(isWizard) { return (isWizard ? "col-xs-9" : "col-xs-12"); }; $scope.customize.provisionEA4Updates = function() { // This cancels any previously customized packages. ea4Data.clearEA4LocalStorageItems(); ea4Data.setData( { "selectedPkgs": $scope.customize.activeProfilePkgs, "ea4Update": true, }); $location.path("review"); }; $scope.customize.toggleUpdateButton = function() { var updateCount = $scope.customize.checkUpdateInfo.pkgNumber; if (updateCount > 0) { $scope.customize.checkUpdateInfo.btnText = LOCALE.maketext("Update [asis,EasyApache 4]"); $scope.customize.checkUpdateInfo.btnTitle = LOCALE.maketext("Update [asis,EasyApache 4]"); $scope.customize.checkUpdateInfo.btnCss = "btn-primary"; } else { $scope.customize.checkUpdateInfo.btnText = LOCALE.maketext("[asis,EasyApache 4] is up to date[comment,no punctuation due to usage]"); $scope.customize.checkUpdateInfo.btnTitle = LOCALE.maketext("[asis,EasyApache 4] is up to date[comment,no punctuation due to usage]"); $scope.customize.checkUpdateInfo.btnCss = "btn-primary disabled"; } }; }, ] ); } ); /* # cpanel - whostmgr/docroot/templates/easyapache4/views/loadPackages.js # Copyright 2026 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( 'app/views/loadPackages',[ "angular", "lodash", "cjt/services/alertService", ], function(angular, _) { "use strict"; // Retrieve the current application var app = angular.module("App"); app.controller("loadPackages", ["$scope", "alertService", "wizardApi", "wizardState", "ea4Data", "ea4Util", "pkgResolution", function($scope, alertService, wizardApi, wizardState, ea4Data, ea4Util, pkgResolution) { var loadPkgInfoData = function() { var rawPkgList = ea4Data.getData("ea4RawPkgList"); if (rawPkgList === null) { var promise = $scope.customize.loadPkgInfoList(); // REFACTOR: ERROR returns should be handled correctly. promise.then(function() { ea4Data.getEA4MetaInfo().then(function(response) { if (response.data) { // Cache metainfo for outdated version checks ea4Util.cacheMetaInfo(response.data); ea4Util.additionalPkgList = response.data.additional_packages; // Find if additional packages don't exist in the system. var additionalPkgsExist = ea4Util.doAdditionalPkgsExist(ea4Util.additionalPkgList, $scope.customize.pkgInfoList); ea4Data.setData({ "additionalPkgsExist": additionalPkgsExist }); var rebuildArgs = { rubyPkgsExist: ea4Util.doRubyPkgsExist($scope.customize.pkgInfoList), additionalPkgsExist: additionalPkgsExist, }; wizardState.steps = wizardApi.rebuildWizardSteps(wizardState.steps, rebuildArgs); $scope.customize.proceed("mpm"); } }, function(error) { alertService.add({ type: "danger", message: error, id: "alertMessages", closeable: false, }); }); }, function(error) { alertService.add({ type: "danger", message: error, id: "alertMessages", closeable: false, }); }); } else { pkgResolution.resetCommonVariables(); $scope.customize.selectedPkgs = ea4Data.getData("selectedPkgs"); $scope.customize.processPkgInfoList(rawPkgList); wizardApi.init(); $scope.customize.proceed("mpm"); } }; $scope.$on("$viewContentLoaded", function() { loadPkgInfoData(); }); }, ] ); } ); /* # templates/easyapache4/views/mpm.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/views/mpm',[ "angular", ], function(angular) { // Retrieve the current application var app = angular.module("App"); app.controller("mpm", ["$scope", function($scope) { $scope.$on("$viewContentLoaded", function() { $scope.customize.loadData("mpm"); }); } ] ); } ); /* # templates/easyapache4/views/modules.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/views/modules',[ "angular", ], function(angular) { // Retrieve the current application var app = angular.module("App"); app.controller("modules", ["$scope", function($scope) { $scope.$on("$viewContentLoaded", function() { $scope.customize.loadData("modules"); }); } ] ); } ); /* # templates/easyapache4/views/php.js Copyright(c) 2020 cPanel, L.L.C. # All rights reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited */ define( 'app/views/php',[ "angular", "cjt/util/locale", ], function(angular, LOCALE) { // Retrieve the current application var app = angular.module("App"); app.controller("php", ["$scope", "PAGE", "$sce", function($scope, PAGE, $sce) { /** * Builds the CloudLinux promotion banner and shows it off. * * @method setClBanner */ var setClBanner = function() { var productData; var phpelsProductData; var productIsLicensed; $scope.showCLBanner = false; $scope.promotePhpEls = PAGE.promote_phpels === true; $scope.promoteCL = PAGE.cl_data.cl_is_supported && !PAGE.cl_data.cl_is_installed; phpelsProductData = PAGE.phpels_data; $scope.phpelsHasCustomUrl = phpelsProductData.purchase_phpels_data.is_url; $scope.phpelsIcon = PAGE.phpels_icon; productData = PAGE.cl_data; productIsLicensed = PAGE.cl_licensed === true; $scope.hasCustomUrl = productData.purchase_cl_data.is_url; $scope.clIcon = PAGE.cl_icon; $scope.cl_is_supported = productData.cl_is_supported; $scope.linkTarget = "_blank"; $scope.purchaseLink = ""; $scope.clActionText = ""; $scope.clBannerText = ""; $scope.clBannerHtml = $sce.trustAsHtml(""); var purchaseCLData = productData.purchase_cl_data; if (productData.cl_is_supported && !productData.cl_is_installed && !purchaseCLData.disable_upgrade) { if ( purchaseCLData.server_timeout || (purchaseCLData.error_msg && purchaseCLData.error_msg !== "")) { $scope.hideUpgradeOption = true; } else { $scope.hideUpgradeOption = false; if (productIsLicensed) { $scope.purchaseLink = "scripts13/install_cloudlinux_EA4"; $scope.clActionText = LOCALE.maketext("Install [_1]", "CloudLinux"); $scope.linkTarget = "_self"; // No need for popup if staying in WHM } else { $scope.purchaseLink = productData.purchase_cl_data.url; $scope.clActionText = LOCALE.maketext("Upgrade to [_1]", "CloudLinux"); } } $scope.purchaseClData = purchaseCLData; } var purchaseData = phpelsProductData.purchase_phpels_data; $scope.phpelsBannerTitle = LOCALE.maketext("[output,strong,Need to keep older PHP versions secure?]"); $scope.phpelsBannerBody = LOCALE.maketext("cPanel provides the latest stable versions of PHP. If you require legacy versions like PHP 5.6 or 7.x, you can keep outdated websites secure with [output,strong,TuxCare Extended Lifecycle Support (ELS)] from CloudLinux. This service provides hardened PHP versions continuously patched against known vulnerabilities, making it the easiest way to maintain security for older PHP versions. [output,url,_1,Learn more.,target,_2]", "https://go.cpanel.net/about-elsphp", "blank" ); $scope.phpelsBannerText = $scope.phpelsBannerTitle + "<br>" + $scope.phpelsBannerBody; $scope.phpelsBannerHtml = $sce.trustAsHtml($scope.phpelsBannerText); $scope.clBannerTitle = LOCALE.maketext("[output,strong,Upgrade your server security and performance.]"); $scope.clBannerBody = LOCALE.maketext("[output,strong,CloudLinux OS] is a feature-rich platform solution that addresses multiple server challenges in a single package, including hardened PHP for legacy version, while also delivering enhanced security, performance, and resource isolation across your hosting environment. [output,url,_1,Learn more.,target,_2]", "https://go.cpanel.net/about-cloudlinux", "blank" ); $scope.clBannerText = $scope.clBannerTitle + "<br>" + $scope.clBannerBody; $scope.clBannerHtml = $sce.trustAsHtml($scope.clBannerText); $scope.phpelsPurchaseLink = phpelsProductData.purchase_phpels_data.url || "scripts13/purchase_phpels_init_MULTIPHP"; $scope.phpelsActionText = LOCALE.maketext("Buy [_1]", "TuxCare ELS PHP"); $scope.purchasePhpelsData = purchaseData; if ( $scope.promotePhpEls || $scope.promoteCL ) { $scope.showCLBanner = true; } }; $scope.sendMixPanelEvent = function(eventName) { var mixpanel = window && window.mixpanel; if (mixpanel && typeof mixpanel.track === "function") { mixpanel.track(eventName, {}); } }; $scope.$on("$viewContentLoaded", function() { $scope.customize.loadData("php"); setClBanner(); }); }, ] ); } ); /* # templates/easyapache4/views/extensions.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/views/extensions',[ "angular" ], function(angular) { // Retrieve the current application var app = angular.module("App"); app.controller("extensions", ["$scope", function($scope) { $scope.$on("$viewContentLoaded", function() { $scope.customize.loadData("extensions"); }); } ] ); } ); /* # templates/easyapache4/views/additionalPackages.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/views/additionalPackages',[ "angular", ], function(angular) { "use strict"; // Retrieve the current application var app = angular.module("App"); app.controller("additionalPackages", ["$scope", function($scope) { $scope.$on("$viewContentLoaded", function() { $scope.customize.loadData("additional"); }); } ] ); } ); /* # templates/easyapache4/views/review.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/views/review',[ "angular", "lodash", "app/services/ea4Data", "app/services/ea4Util" ], function(angular, _) { // Retrieve the current application var app = angular.module("App"); app.controller("review", [ "$scope", "$location", "ea4Data", "ea4Util", "growlMessages", function($scope, $location, ea4Data, ea4Util, growlMessages) { $scope.readyToProvision = false; $scope.gettingResults = true; /** * We can send packages from external apps directly to this view. * The data should be sent as querystring params. * 'queryStr' variable will track those params. * * querystring param: 'install': to install a package (can be multiple). * querystring param: 'uninstall': to uninstall a package (can be multiple). * Example: * <url>/<cpsesskey>/scripts7/EasyApache4/review?install=mpm-prefork&install=cgi&uninstall=mpm-worker&uninstall=cgid */ var queryStr = {}; /** * Prepares the selected packages to be installed and does an * API call to resolve those packages. * * @method prepareForReview * @param {Array} A list of packages to be installed. * @param {String} [Optional] profile id(usually path of a profile). */ var prepareForReview = function(packageListForReview, profileId) { // Get status of each package in the package list ea4Data.resolvePackages(packageListForReview).then(function(data) { // Get the packages display format $scope.installList = ea4Util.getFormattedPackageList(data.install); $scope.uninstallList = ea4Util.getFormattedPackageList(data.uninstall); $scope.upgradeList = ea4Util.getFormattedPackageList(data.upgrade); $scope.existingList = ea4Util.getFormattedPackageList(data.unaffected); if (!$scope.installList.length && !$scope.upgradeList.length && !$scope.uninstallList.length) { $scope.noActionRequired = true; return; } // Put all lists into Web Storage ea4Data.setData( { "provisionActions": { profileId: profileId, install: data.install, uninstall: data.uninstall, upgrade: data.upgrade } }); // Enable the Provision button $scope.readyToProvision = true; // Allow provision to run ea4Data.provisionReady(true); }, function(error) { $scope.apiError = true; $scope.yumErrorMessage = error; }).finally(function() { $scope.gettingResults = false; }); }; /** * update selectedPackages with new install and/or uninstall * packages sent through querystring from directly called from * an external application. * This helps in by-passing customize steps when we need to install * few packages required in other applications. * * @method updateSelPackagesAndReview * @param {Object} angular query string object */ var updateSelPackagesAndReview = function(qs) { // 1. Get the current package list. // 2. Add packages that need to be installed to 'selPkgs' // 3. Remove packages that need to be uninstalled from 'selPkgs'. var selPkgs = []; ea4Data.ea4GetCurrentPkgList().then(function(result) { if (result.status) { selPkgs = result.data; // qs["install"] may have a single string or an array of strings. var installList = (_.isArray(qs["install"])) ? qs["install"] : [ qs["install"] ]; selPkgs = _.union(selPkgs, installList); // qs["uninstall"] may have a single string or an array of strings. var uninstallList = (_.isArray(qs["uninstall"])) ? qs["uninstall"] : [ qs["uninstall"] ]; selPkgs = _.difference(selPkgs, uninstallList); prepareForReview(selPkgs); } }); }; $scope.$on("$viewContentLoaded", function() { // A list of install/uninstall package set sent through querystring from an external location. queryStr = $location.search(); if (!_.isEmpty(queryStr) && (!_.isEmpty(queryStr["install"]) || !_.isEmpty(queryStr["uninstall"]))) { updateSelPackagesAndReview(queryStr); } else { var customize = ea4Data.getData("customize"); var ea4Update = ea4Data.getData("ea4Update"); if (customize) { $scope.customize.loadData("review"); prepareForReview(ea4Data.getData("selectedPkgs")); } else if (ea4Update) { prepareForReview(ea4Data.getData("selectedPkgs")); } else { var selectedProfile = ea4Data.getData("selectedProfile"); if (!selectedProfile) { ea4Data.cancelOperation(); } prepareForReview(selectedProfile.pkgs, selectedProfile.fullPath); } } }); $scope.cancel = function() { ea4Data.cancelOperation(); }; }]); } ); /* # templates/easyapache4/views/provision.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/views/provision',[ "angular", "cjt/util/locale", "app/services/ea4Data", "app/services/ea4Util" ], function(angular, LOCALE) { "use strict"; // Retrieve the current application var app = angular.module("App"); app.controller("provision", [ "$scope", "$location", "ea4Data", "spinnerAPI", function($scope, $location, ea4Data, spinnerAPI) { var realTimeLog = ""; // REFACTOR: One-way binding eligible. $scope.realTimeLogDisplay = ""; var errorDetected = false; var startTailing = function() { ea4Data.tailingLog($scope.buildID, $scope.currentTailingPosition) .then(function(data) { // $scope.inErrorMode = false; var inErrorMode = false; for (var i = 0, content = data.content; i < content.length; i++) { // Ignore the beginning and ending lines of log, replace them with more meaningful words if (content[i] === "-- " + $scope.buildID + " --") { var startText = LOCALE.maketext("Provision process started."); realTimeLog += startText + "\r\n"; $scope.realTimeLogDisplay += "<span class='text-success'><strong>" + startText + "</strong></span>\r\n"; continue; } if (content[i] === "-- /" + $scope.buildID + " --") { var endText = LOCALE.maketext("Provision process finished."); realTimeLog += endText + "\r\n"; $scope.realTimeLogDisplay += "<span class='text-success'><strong>" + endText + "</strong></span>\r\n"; continue; } realTimeLog += content[i] + "\r\n"; content[i] = content[i].replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/'/gm, "'").replace(/"/gm, """); // Detect error messages if (content[i] === "-- error(" + $scope.buildID + ") --") { inErrorMode = true; errorDetected = true; continue; } if (content[i] === "-- /error(" + $scope.buildID + ") --") { inErrorMode = false; continue; } if (inErrorMode) { content[i] = "<span class='text-danger'>" + content[i] + "</span>"; } if (/Error:.*/gm.test(content[i])) { content[i] = "<span class='text-danger'>" + content[i] + "</span>"; errorDetected = true; } $scope.realTimeLogDisplay += content[i] + "\r\n"; } // Because of the $scope digest, putting 100 ms delay on auto scrolling // the output window // TODO: Split this out into a directive to avoid touching the DOM directly window.setTimeout(function() { var log = document.getElementById("log"); if (log) { log.scrollTop = log.scrollHeight; } }, 100); $scope.currentTailingPosition = data.offset; if (data.still_running) { window.setTimeout(startTailing(), 100); } else { spinnerAPI.stop("provisionSpinner"); $scope.finished = true; ea4Data.provisionReady(false); } }); }; var startProvision = function(provisionActions) { spinnerAPI.start("provisionSpinner"); $scope.provisionStarted = true; errorDetected = false; ea4Data.doProvision(provisionActions.install, provisionActions.uninstall, provisionActions.upgrade, provisionActions.profileId) .then(function(data) { // TODO: see if this shud be in scope $scope.buildID = data.build; // TODO: see if this shud be in scope $scope.currentTailingPosition = 0; startTailing(); }).finally(function() { // every time we provision we are getting updates // so we reset the update button state $scope.customize.checkUpdateInfo.pkgNumber = 0; $scope.customize.toggleUpdateButton(); ea4Data.clearEA4LocalStorageItems(); app.firstLoad = false; ea4Data.php_set_session_save_path(); }); }; $scope.$on("$viewContentLoaded", function() { // Reset wizard attributes. $scope.customize.wizard.currentStep = ""; $scope.customize.wizard.showWizard = false; var provisionActions = ea4Data.getData("provisionActions"); if (!ea4Data.provisionReady() || (typeof provisionActions === "undefined")) { ea4Data.cancelOperation(); } // REFACTOR: THIS part needs to be re-visited when working // on using latest tail log method. var hash = $location.hash(); if (hash === "bottom") { startProvision(provisionActions); } else { $location.hash("bottom"); } }); $scope.cancel = function() { ea4Data.cancelOperation(); }; $scope.resultReady = function() { var result = null; if (!errorDetected) { result = "alert-success"; $scope.resultSummary = LOCALE.maketext("The provision process is complete."); } else { result = "alert-danger"; $scope.resultSummary = LOCALE.maketext("The provision process exited with errors. Please check the log for details."); } return result; }; var destroyClickedElement = function(event) { // remove the link from the DOM document.body.removeChild(event.target); }; // REFACTOR: Need to be re-written. $scope.saveLog = function() { // grab the content of the form field and place it into a variable var textToWrite = realTimeLog; var textFileAsBlob = new Blob([textToWrite], { type: "text/plain" }); var fileNameToSaveAs = "log.txt"; var downloadLink = document.createElement("a"); downloadLink.download = fileNameToSaveAs; downloadLink.innerHTML = "My Hidden Link"; window.URL = window.URL || window.webkitURL; downloadLink.href = window.URL.createObjectURL(textFileAsBlob); downloadLink.onclick = destroyClickedElement; downloadLink.style.display = "none"; document.body.appendChild(downloadLink); downloadLink.click(); }; } ] ); } ); /* # templates/easyapache4/views/ruby.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/views/ruby',[ "angular" ], function(angular) { // Retrieve the current application var app = angular.module("App"); app.controller("ruby", ["$scope", function($scope) { $scope.$on("$viewContentLoaded", function() { $scope.customize.loadData("ruby"); }); } ] ); } ); /* # cpanel - whostmgr/docroot/templates/easyapache4/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 */ define( 'app/index',[ "angular", "cjt/core", "lodash", "cjt/modules", ], function(angular, CJT, _) { "use strict"; return function() { // First create the application angular.module("App", [ "cjt2.config.whm.configProvider", // This needs to load first "ngRoute", "ui.bootstrap", "cjt2.whm", "angular-growl", "whm.easyapache4.ea4Util", "whm.easyapache4.ea4Data", "whm.easyapache4.pkgResolution", "whm.easyapache4.wizardApi", ]); // Then load the application dependencies var appModule = require( [ "cjt/bootstrap", "cjt/util/locale", // Application Modules "cjt/directives/toggleSwitchDirective", "cjt/directives/searchDirective", "app/directives/eaWizard", "app/directives/saveAsProfile", "app/services/ea4Data", "app/services/ea4Util", "app/services/pkgResolution", "app/services/wizardApi", "cjt/views/applicationController", "app/views/profile", "app/views/yumUpdate", "app/views/customize", "app/views/loadPackages", "app/views/mpm", "app/views/modules", "app/views/php", "app/views/extensions", "app/views/additionalPackages", "app/views/review", "app/views/provision", "app/views/ruby", ], function(BOOTSTRAP, LOCALE) { var app = angular.module("App"); app.value("PAGE", PAGE); // REFACTOR: This can be sent into ea4Data service. app.firstLoad = true; var wizardState = {}; app.value("wizardState", wizardState); app.config(["$routeProvider", "$compileProvider", function($routeProvider, $compileProvider) { // Setup the routes $routeProvider .when("/profile", { controller: "profile", templateUrl: CJT.buildFullPath("easyapache4/views/profile.ptt"), }) .when("/loadPackages", { controller: "loadPackages", templateUrl: CJT.buildFullPath("easyapache4/views/loadPackages.ptt"), }) .when("/mpm", { controller: "mpm", templateUrl: CJT.buildFullPath("templates/easyapache4/views/mpm.phtml"), }) .when("/modules", { controller: "modules", templateUrl: CJT.buildFullPath("templates/easyapache4/views/modules.phtml"), }) .when("/php", { controller: "php", templateUrl: CJT.buildFullPath("easyapache4/views/php.ptt"), }) .when("/extensions", { controller: "extensions", templateUrl: CJT.buildFullPath("templates/easyapache4/views/extensions.phtml"), }) .when("/additional", { controller: "additionalPackages", templateUrl: CJT.buildFullPath("templates/easyapache4/views/additionalPackages.phtml"), }) .when("/review", { controller: "review", templateUrl: CJT.buildFullPath("easyapache4/views/review.ptt"), }) .when("/ruby", { controller: "ruby", templateUrl: CJT.buildFullPath("templates/easyapache4/views/ruby.phtml"), }) .when("/provision", { controller: "provision", templateUrl: CJT.buildFullPath("easyapache4/views/provision.ptt"), }) .when("/yumUpdate", { controller: "yumUpdate", templateUrl: CJT.buildFullPath("easyapache4/views/yumUpdate.ptt"), }) .otherwise({ "redirectTo": "/profile", }); $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); }, ]); app.run(["$rootScope", "$location", "ea4Util", "wizardApi", "wizardState", "PAGE", function($rootScope, $location, ea4Util, wizardApi, wizardState, PAGE) { $rootScope.PAGE = PAGE; if (_.isEmpty(wizardState)) { wizardApi.init(); ea4Util.hideFooter(); } // register listener to watch route changes $rootScope.$on("$routeChangeStart", function() { $rootScope.currentRoute = $location.path(); }); }]); BOOTSTRAP(document); } ); return appModule; }; } );
Save