API Docs for: 0.8.0
Show:

File: include/admin_navigation.js

/*
 Copyright (C) 2016  PencilBlue, LLC

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//dependencies
var util = require('./util.js');

module.exports = function AdminNavigationModule(pb) {

    //PB dependencies
    var SecurityService = pb.SecurityService;
    var GLOBAL_SITE = pb.SiteService.GLOBAL_SITE;

    /**
     * Provides function to construct the structure needed to display the navigation
     * in the Admin section of the application.
     *
     * @module Services
     * @submodule Admin
     * @class AdminNavigation
     * @constructor
     */
    function AdminNavigation() {}

    /**
     *
     * @private
     * @static
     * @property additions
     * @type {Array}
     */
    AdminNavigation.additions = {};

    /**
     *
     * @private
     * @static
     * @property childrenAdditions
     * @type {Object}
     */
    AdminNavigation.childrenAdditions = {};

    /**
     *
     * @private
     * @static
     * @readonly
     * @property MULTISITE_NAV
     * @return {Array}
     */
    var MULTISITE_NAV = Object.freeze({
            id: 'site_entity',
            title: 'admin.MANAGE_SITES',
            icon: 'sitemap',
            href: '/admin/sites',
            access: SecurityService.ACCESS_ADMINISTRATOR

        }
    );

    /**
     *
     * @private
     * @static
     * @readonly
     * @property CONTENT_NAV
     * @return {Array}
     */
    var CONTENT_NAV = Object.freeze({
        id: 'content',
        title: 'generic.CONTENT',
        icon: 'quote-right',
        href: '#',
        access: SecurityService.ACCESS_WRITER,
        children: [
            {
                id: 'navigation',
                title: 'generic.NAVIGATION',
                icon: 'th-large',
                href: '/admin/content/navigation',
                access: SecurityService.ACCESS_EDITOR
            },
            {
                id: 'topics',
                title: 'admin.TOPICS',
                icon: 'tags',
                href: '/admin/content/topics',
                access: SecurityService.ACCESS_EDITOR
            },
            {
                id: 'pages',
                title: 'admin.PAGES',
                icon: 'file-o',
                href: '/admin/content/pages',
                access: SecurityService.ACCESS_EDITOR
            },
            {
                id: 'articles',
                title: 'admin.ARTICLES',
                icon: 'files-o',
                href: '/admin/content/articles',
                access: SecurityService.ACCESS_WRITER
            },
            {
                id: 'media',
                title: 'admin.MEDIA',
                icon: 'camera',
                href: '/admin/content/media',
                access: SecurityService.ACCESS_WRITER
            },
            {
                id: 'comments',
                title: 'generic.COMMENTS',
                icon: 'comments',
                href: '/admin/content/comments',
                access: SecurityService.ACCESS_EDITOR
            },
            {
                id: 'custom_objects',
                title: 'admin.CUSTOM_OBJECTS',
                icon: 'cubes',
                href: '/admin/content/objects/types',
                access: SecurityService.ACCESS_EDITOR
            }
        ]
    });

    var PLUGINS_NAV = Object.freeze({
        id: 'plugins',
        title: 'admin.PLUGINS',
        icon: 'puzzle-piece',
        href: '#',
        access: SecurityService.ACCESS_ADMINISTRATOR,
        children: [
            {
                divider: true,
                id: 'manage',
                title: 'generic.MANAGE',
                icon: 'upload',
                href: '/admin/plugins'
            },
            {
                id: 'themes',
                title: 'admin.THEMES',
                icon: 'magic',
                href: '/admin/themes'
            }
        ]
    });

    var USERS_NAV = Object.freeze({
        id: 'users',
        title: 'admin.USERS',
        icon: 'users',
        href: '#',
        access: SecurityService.ACCESS_EDITOR,
        children: [
            {
                id: 'manage',
                title: 'generic.MANAGE',
                icon: 'users',
                href: '/admin/users',
                access: SecurityService.ACCESS_EDITOR
            },
            {
                id: 'permissions',
                title: 'generic.PERMISSIONS',
                icon: 'lock',
                href: '/admin/users/permissions',
                access: SecurityService.ACCESS_ADMINISTRATOR
            }
        ]
    });

    var VIEW_SITE_NAV = Object.freeze({
        id: 'view_site',
        title: 'admin.VIEW_SITE',
        icon: 'desktop',
        href: '/',
        access: SecurityService.ACCESS_WRITER
    });

    var LOGOUT_NAV = Object.freeze({
        id: 'logout',
        title: 'generic.LOGOUT',
        icon: 'power-off',
        href: '/actions/logout',
        access: SecurityService.ACCESS_WRITER
    });

    function buildSettingsNavigation(site) {
        var settingsNav = {
            id: 'settings',
            title: 'admin.SETTINGS',
            icon: 'cogs',
            href: '#',
            access: SecurityService.ACCESS_ADMINISTRATOR,
            children: [
                {
                    id: 'site_settings',
                    title: 'admin.SITE_SETTINGS',
                    icon: 'cog',
                    href: '/admin/site_settings',
                    access: SecurityService.ACCESS_ADMINISTRATOR
                },
                {
                    id: 'content_settings',
                    title: 'admin.CONTENT',
                    icon: 'quote-right',
                    href: '/admin/site_settings/content',
                    access: SecurityService.ACCESS_ADMINISTRATOR
                },
                {
                    id: 'email_settings',
                    title: 'users.EMAIL',
                    icon: 'envelope',
                    href: '/admin/site_settings/email',
                    access: SecurityService.ACCESS_ADMINISTRATOR
                }
            ]
        };

        if (pb.SiteService.isGlobal(site)) {
            settingsNav.children.push({
                id: 'library_settings',
                title: 'site_settings.LIBRARIES',
                icon: 'book',
                href: '/admin/site_settings/libraries',
                access: SecurityService.ACCESS_ADMINISTRATOR
            });
        }
        return Object.freeze(settingsNav);
    }

    function getDefaultNavigation(site) {
        return util.clone([CONTENT_NAV, PLUGINS_NAV, USERS_NAV, buildSettingsNavigation(site), VIEW_SITE_NAV, LOGOUT_NAV]);
    }

    function getMultiSiteNavigation() {
        return util.clone([MULTISITE_NAV]);
    }

    function getGlobalScopeNavigation(site) {
        return util.clone([PLUGINS_NAV, USERS_NAV, buildSettingsNavigation(site), LOGOUT_NAV]);
    }


    /**
     *
     * @private
     * @static
     * @method getAdditions
     * @return {Array}
     */
    function getAdditions(site) {
        return getAdditionsInScope(AdminNavigation.additions, site);
    }

    /**
     *
     * @private
     * @static
     * @method getChildrenAdditions
     * @return {Object}
     */
    function getChildrenAdditions(site) {
        return getAdditionsInScope(AdminNavigation.childrenAdditions, site);
    }

    /**
     * @private
     * @method getAdditionsInScope
     * @param {Object} additions
     * @param {String} site
     */
    function getAdditionsInScope(additions, site) {
        if (additions[site]) {
            return util.clone(additions[site]);
        }
        else if (additions[pb.SiteService.GLOBAL_SITE]) {
            return util.clone(additions[pb.SiteService.GLOBAL_SITE]);
        }
        return util.clone(additions);
    }

    /**
     *
     * @private
     * @static
     * @method buildNavigation
     * @return {Array}
     */
    function buildNavigation(site) {
        var i;
        var navigation = [];
        var additions = getAdditions(site);
        var childrenAdditions = getChildrenAdditions(site);

        if (pb.config.multisite.enabled) {
            var multiSiteAdditions = getMultiSiteNavigation();
            util.arrayPushAll(multiSiteAdditions, navigation);
        }

        if (pb.config.multisite.enabled && pb.SiteService.isGlobal(site)) {
            // Don't include content or view site in the nav for multitenancy global scope.
            util.arrayPushAll(getGlobalScopeNavigation(site), navigation);
        }
        else {
            var defaultNavigation = getDefaultNavigation(site);
            util.arrayPushAll(defaultNavigation, navigation);
        }

        util.arrayPushAll(additions, navigation);

        //retrieve the nav items to iterate over
        var ids = Object.keys(childrenAdditions);
        if (ids.length === 0) {
            return navigation;
        }

        //convert to hash to create quick lookup
        var lookup = util.arrayToHash(navigation, function(navigation, i) {
            return navigation[i].id;
        });

        //add additions
        Object.keys(childrenAdditions).forEach(function(id) {
            var children = childrenAdditions[id];

            //find the nav that the children should be added to
            var nav = lookup[id];
            if (!nav) {
                return;
            }


            if (!util.isArray(nav.children)) {
                nav.children = [];
            }
            util.arrayPushAll(children, nav.children);
        });

        return navigation;
    }

    /**
     * @private
     * @static
     * @method localizeNavigation
     * @param {Array} navigation
     * @param {Localization} ls
     * @return {Array}
     */
    function localizeNavigation(navigation, ls) {
        navigation.forEach(function(nav) {
            nav.title = ls.g(nav.title);
            if(util.isArray(nav.children)) {
                nav.children = localizeNavigation(nav.children, ls);
            }
        });
        return navigation;
    }

    /**
     * @private
     * @static
     * @method isDuplicate
     * @param {String} id
     * @param {Array} navigation
     * @return {boolean}
     */
    function isDuplicate(id, navigation, site) {
        if (!navigation) {
            navigation = buildNavigation(site);
        }

        for (var i = 0; i < navigation.length; i++) {
            var node = navigation[i];

            if (node.id === id) {
                return true;
            }
            if (node.children && isDuplicate(id, node.children, site)) {
                return true;
            }
        }
        return false;
    }

    function exists(id, site) {
        var isGlobal = pb.SiteService.isGlobal(site);
        var nav = buildNavigation(site);
        return isDuplicate(id, nav) ||
          (!isGlobal && isDuplicate(id, buildNavigation(pb.SiteService.GLOBAL_SITE)));
    }

    /**
     * @private
     * @static
     * @method isDefaultNode
     * @param {String} id
     * @return {Boolean}
     */
    function isDefaultNode(id, site) {
        return isDuplicate(id, getDefaultNavigation(site));
    }

    /**
     * Retrive the admin navigation hierarchy
     * @static
     * @method get
     * @param {object} session
     * @param {array} activeMenuItems Array of nav item names that are active
     * @param {Object} ls Localization service
     * @return {object} Admin navigation
     */
    AdminNavigation.get = function (session, activeMenuItems, ls, site) {
        var navigation = AdminNavigation.removeUnauthorized(
            session,
            buildNavigation(site),
            activeMenuItems
        );

        return localizeNavigation(navigation, ls);
    };

    AdminNavigation.addChild = function(parentId, node) {
        AdminNavigation.addChildToSite(parentId, node, pb.SiteService.GLOBAL_SITE);
    };

    /**
     * Adds a new child node to an existing top level node
     * @static
     * @method addChildToSite
     * @param {String} parentId
     * @param {Object} node
     * @param {String} site - site unique id
     * @return {Boolean}
     */
    AdminNavigation.addChildToSite = function (parentId, node, site) {
        if (util.isNullOrUndefined(site)) {
            site = GLOBAL_SITE;
        }
        if (exists(node.id, site)) {
            return false;
        }

        var additionsMap;
        if (!(site in AdminNavigation.childrenAdditions)) {
            additionsMap = AdminNavigation.childrenAdditions[site] = {};
        } else {
            additionsMap = AdminNavigation.childrenAdditions[site];
        }

        if (!additionsMap[parentId]) {
            additionsMap[parentId] = [];
        }

        additionsMap[parentId].push(node);
        return true;
    };

    /**
     * Adds a new top level node
     * @static
     * @method add
     * @param {Object} node
     * @param {String} [site='global']
     * @return {Boolean}
     */
    AdminNavigation.add = function(node, site) {
        if (util.isNullOrUndefined(site)) {
            site = GLOBAL_SITE;
        }
        if (exists(node.id, site)) {
            return false;
        }

        if (!(site in AdminNavigation.additions)) {
            AdminNavigation.additions[site] = [];
        }
        AdminNavigation.additions[site].push(node);
        return true;
    };

    /**
     * Adds a new top level node
     * @static
     * @method addToSite
     * @param {Object} node
     * @param {String} site
     * @return {Boolean}
     */
    AdminNavigation.addToSite = function (node, site) {
        return AdminNavigation.add(node, site);
    };

    /**
     * Remove a navigation node
     * @static
     * @method remove
     * @param id
     * @param {String} [site='global']
     * @return {boolean}
     */
    AdminNavigation.remove = function(id, site) {
        if (util.isNullOrUndefined(site)) {
            site = GLOBAL_SITE;
        }
        if (!isDuplicate(id, buildNavigation(site))) {
            return false;
        }

        if (isDefaultNode(id)) {
            pb.log.warn("Admin Navigation: Attempting to remove default Node %s", id);
            return false;
        }

        function removeNode(id, navigation) {
            for (var i = 0; i < navigation.length; i++) {
                if (navigation[i].id === id) {
                    navigation.splice(i, 1);
                    return navigation;
                }

                if (navigation[i].children) {
                    navigation[i].children = removeNode(id, navigation[i].children);
                }
            }

            return navigation;
        }

        AdminNavigation.additions[site] = removeNode(id, AdminNavigation.additions[site]);

        var childAdditionsMap = AdminNavigation.childrenAdditions[site];
        util.forEach(childAdditionsMap, function(value, key) {
            if(key === id){
                delete childAdditionsMap[key];
            }else {
                childAdditionsMap[key] = removeNode(id, value);
            }
        });

        return true;
    };

    /**
     * Remove a navigation node
     * @static
     * @method removeFromSite
     * @param id
     * @param {String} site
     * @return {boolean}
     */
    AdminNavigation.removeFromSite = function (id, site) {
        return AdminNavigation.remove(id, site);
    };

    /**
     * @static
     * @method removeUnauthorized
     * @param {Object} session
     * @param {Array} adminNavigation
     * @param {Array} activeItems
     * @return {Array}
     */
    AdminNavigation.removeUnauthorized = function (session, adminNavigation, activeItems) {
        for (var i = 0; i < adminNavigation.length; i++) {
            if (typeof adminNavigation[i].access !== 'undefined') {

                if (!pb.security.isAuthorized(session, {admin_level: adminNavigation[i].access})) {
                    adminNavigation.splice(i, 1);
                    i--;
                    continue;
                }
            }

            for (var o = 0; o < activeItems.length; o++) {
                if (activeItems[o] === adminNavigation[i].id) {
                    adminNavigation[i].active = 'active';
                    break;
                }
            }

            if (typeof adminNavigation[i].children !== 'undefined') {
                if (adminNavigation[i].children.length > 0) {
                    adminNavigation[i].dropdown = 'dropdown';

                    for (var j = 0; j < adminNavigation[i].children.length; j++) {

                        if (typeof adminNavigation[i].children[j].access !== 'undefined') {

                            if (!pb.security.isAuthorized(session, {admin_level: adminNavigation[i].children[j].access})) {
                                adminNavigation[i].children.splice(j, 1);
                                j--;
                                continue;
                            }
                        }

                        for (var p = 0; p < activeItems.length; p++) {
                            if (activeItems[p] === adminNavigation[i].children[j].id) {
                                adminNavigation[i].children[j].active = 'active';
                                break;
                            }
                        }
                    }
                }
            }
        }

        return adminNavigation;
    };

    //exports
    return AdminNavigation;
};