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

/*
# 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(
    [
        "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;
        });
    }
);
Back to Directory File Manager