/*
# 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(
[
"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 || [];
};
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"].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;
categories[categoryKey] = {
"name": util.versionToString(phpversion).replace("Php", "PHP"),
"prefix": phpPrefix !== "ea" ? phpPrefix : null,
"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"].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 phpVersion = phpMatch[2];
var formattedVersion = "PHP " + phpVersion.replace(/(\d)(\d)$/, "$1.$2");
if (!foundPhpVersions.has(formattedVersion)) {
foundPhpVersions.add(formattedVersion);
tags = _.concat(tags, formattedVersion);
}
}
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;
}]);
}
);