GemsTracker/gemstracker-library

View on GitHub
classes/Gems/Menu/MenuAbstract.php

Summary

Maintainability
F
3 days
Test Coverage
A
90%
<?php

/**
 *
 * @package    Gems
 * @subpackage Menu
 * @author     Matijs de Jong <mjong@magnafacta.nl>
 * @copyright  Copyright (c) 2011 Erasmus MC
 * @license    New BSD License
 */

use MUtil\Translate\TranslateableTrait;

/**
 * Base class for building a menu / button structure where the display of items is dependent
 * on both privileges and the availability of parameter information,
 * e.g. data to fill an 'id' parameter.
 *
 * @package    Gems
 * @subpackage Menu
 * @copyright  Copyright (c) 2011 Erasmus MC
 * @license    New BSD License
 * @since      Class available since version 1.0
 */
abstract class Gems_Menu_MenuAbstract extends \Gems_Loader_TargetLoaderAbstract
{
    use TranslateableTrait;

    /**
     *
     * @var \Gems_Menu_SubMenuItem[]
     */
    protected $_subItems = array();

    /**
     *
     * @var \MUtil_Acl
     */
    public $acl;

    /**
     *
     * @var \Gems_User_User
     */
    protected $currentUser;

    /**
     * @var \GemsEscort
     */
    public $escort;

    /**
     *
     * @var \Gems_Loader
     *
     */
    public $loader;

    /**
     *
     * @var \Gems_Project_ProjectSettings
     */
    public $project;

    public function __construct()
    {
        $this->escort = \GemsEscort::getInstance();
    }

    /**
     * Adds privileges that are used in this menu item to the array
     *
     * @param array $privileges
     */
    protected function _addUsedPrivileges(array &$privileges, $label)
    {
        foreach ($this->_subItems as $item) {
            // Skip autofilter action, but include all others
            if ($item->get('action') == 'autofilter') {
                continue;
            }
            $_itemlabel = $label . ($item->get('label') ?: $item->get('privilege'));
            $_privilege = $item->get('privilege');

            // True is used to make a privilege always accessible, e.g. for ask/return and ask/forward
            if ($_privilege && (true !== $_privilege)) {
                if (isset($privileges[$_privilege])) {
                    $add = "<br/>&nbsp; + " . $_itemlabel;
                    if (! \MUtil_String::contains($privileges[$_privilege], $add)) {
                        $privileges[$_privilege] .= $add;
                    }
                } else {
                    $privileges[$_privilege] = $_itemlabel;
                }
            }
            $item->_addUsedPrivileges($privileges, $_itemlabel . '-&gt;');
        }
    }

    /**
     * Get tge request to use for menu building
     *
     * @return \Zend_Controller_Request_Abstract
     */
    protected function _getOriginalRequest()
    {
        $request = \MUtil\Controller\Front::getRequest();
        $handler = $request->getParam('error_handler');

        if ($handler) {
            return $handler->request;
        }

        return $request;
    }

    /**
     * Returns a \Zend_Navigation creation array for this menu item, with
     * sub menu items in 'pages'
     *
     * @param \Gems_Menu_ParameterCollector $source
     * @return array
     */
    protected function _toNavigationArray(\Gems_Menu_ParameterCollector $source)
    {
        $this->sortByOrder();
        $lastParams = null;
        $pageIdx    = 0;
        $pages      = array();
        foreach ($this->_subItems as $item) {
            // Skip button_only items
            if ($item->get('button_only')) {
                continue;
            }
            $page = $item->_toNavigationArray($source);

            if (($this instanceof \Gems_Menu_SubMenuItem) &&
                    (!$this->notSet('controller', 'action')) &&
                    isset($page['params'])) {

                $params = $page['params'];
                unset($params['reset']); // Ignore this setting

                $class = [];
                if (isset($page['class'])) {
                    $class[] = $page['class'];
                }
                if (empty($params)) {
                    $class[] = 'noParameters';
                }
                if ((null !== $lastParams) && ($lastParams !== $params)) {
                    $class[] = 'breakBefore';
                }

                if (!empty($class)) {
                    $page['class'] = join(' ', $class);
                }
                $lastParams = $params;
            }
            $pages[$pageIdx] = $page;
            $pageIdx++;
        }

        return $pages;
    }

    /**
     * Add a sub item to this item.
     *
     * The argumenets can be any of those used for \Zend_Navigation_Page as well as some Gems specials.<ul>
     * <li>'action' The name of the action.</li>
     * <li>'allowed' Is the user allowed to access this menu item. Is checked against ACL using 'privilige'.</li>
     * <li>'button_only' Never in the menu, only shown as a button by the program.</li>
     * <li>'class' Display class for the menu link.</li>
     * <li>'controller' What controller to use.</li>
     * <li>'icon' Icon to display with the label.</li>
     * <li>'label' The label to display for the menu item.</li>
     * <li>'privilege' The privilege needed to choose the item.</li>
     * <li>'target' Optional target attribute for the link.</li>
     * <li>'type' Optional content type for the link</li>
     * <li>'visible' Is the item visible. Is checked against ACL using 'privilige'.</li>
     * </ul>
     *
     * @see \Zend_Navigation_Page
     *
     * @param array $argsArray \MUtil_Ra::args array with defaults 'visible' and 'allowed' true.
     * @return \Gems_Menu_SubMenuItem
     */
    protected function add($argsArray)
    {
        // Process parameters.
        $args = \MUtil_Ra::args(func_get_args(), 0,
            array('visible' => true,    // All menu items are initally visible unless stated otherwise
                'allowed' => true,      // Same as with visible, need this for _toNavigationArray()
                ));

        if (! isset($args['label'])) {
            $args['visible'] = false;
        }

        if (! isset($args['order'])) {
            $args['order'] = 10 * (count($this->_subItems) + 1);
        }

        $page = new \Gems_Menu_SubMenuItem($this, $args);

        $this->_subItems[] = $page;

        return $page;
    }

    /**
     * Add a agenda setup menu tree to the menu
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addAgendaSetupMenu($label)
    {
        $setup = $this->addContainer($label);

        $setup->addAgendaSetupPage($this->_('Activities'),          'pr.agenda-activity',  'agenda-activity');
        $setup->addAgendaSetupPage($this->_('Procedures'),          'pr.agenda-procedure', 'agenda-procedure');
        $setup->addAgendaSetupPage($this->_('Diagnoses'),           'pr.agenda-diagnosis', 'agenda-diagnosis');
        $setup->addAgendaSetupPage($this->_('Locations'),           'pr.locations',        'location');
        $setup->addAgendaSetupPage($this->_('Healthcare staff'),    'pr.agenda-staff',     'agenda-staff');
        $setup->addBrowsePage(     $this->_('Appointment filters'), 'pr.agenda-filters',   'agenda-filter');

        return $setup;
    }

    /**
     * Add a browse / create / edit / show / / sleanup etc.. menu item
     *
     * @param string $label
     * @param string $privilege
     * @param string $controller
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addAgendaSetupPage($label, $privilege, $controller, array $other = array())
    {
        $page = $this->addPage($label, $privilege, $controller, 'index', $other);
        $page->addAutofilterAction();
        $page->addCreateAction();
        $page->addExportAction();
        $page->addImportAction();
        $show = $page->addShowAction();
        $show->addEditAction();
        $show->addDeleteAction();

        $show->addAction($this->_('Clean up'), $privilege . '.cleanup', 'cleanup')
                ->setModelParameters(1);

        return $page;
    }

    /**
     * Add a browse / ceate / edit / show / etc.. menu item
     *
     * @param string $label
     * @param string $privilege
     * @param string $controller
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addBrowsePage($label, $privilege, $controller, array $other = array())
    {
        $page = $this->addPage($label, $privilege, $controller, 'index', $other);
        $page->addAutofilterAction();
        $page->addCreateAction();
        $page->addExportAction();
        $page->addImportAction();
        $show = $page->addShowAction();
        $show->addEditAction();
        $show->addDeleteAction();

        return $page;
    }

    /**
     * Add a menu item that is never added to the navigation tree and only shows up as a button.
     *
     * @param string $label
     * @param string $privilege
     * @param string $controller
     * @param string $action
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addButtonOnly($label, $privilege, $controller, $action = 'index', array $other = array())
    {
        $other['button_only'] = true;

        return $this->addPage($label, $privilege, $controller, $action, $other);
    }

    /**
     * Add a calendar page to the menu
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addCalendarPage($label)
    {
        $page = $this->addPage($label, 'pr.calendar', 'calendar');
        $page->addAutofilterAction();
        $page->addExportAction();
        $page->addImportAction();
        $page->addAction(null, 'pr.calendar.simple-api', 'simple-api');

        return $page;
    }

    /**
     * Add a Mail menu tree to the menu
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addCommSetupMenu($label)
    {
        $setup = $this->addContainer($label);

        // AUTOMATIC COMMUNICATION CONTROLLER
        $page = $setup->addBrowsePage($this->_('Automatic messaging'), 'pr.comm.job', 'comm-job');
        $page->addButtonOnly($this->_('Turn Automatic Messaging Jobs OFF'), 'pr.comm.job', 'cron', 'cron-lock');
        $page->addButtonOnly($this->_('Monitor'), 'pr.comm.job', 'comm-job', 'monitor');

        $page->addPage($this->_('Run all'), 'pr.cron.job', 'comm-job', 'execute-all');
        $show = $page->findItem(array('controller' => 'comm-job', 'action' => 'show'));
        $show->addPage($this->_('Preview'), 'pr.cron.job', 'comm-job', 'preview')
                ->setModelParameters(1);
        $show->addPage($this->_('Run'), 'pr.cron.job', 'comm-job', 'execute')
                ->setModelParameters(1);

        $ajaxPage = $this->addPage($this->_('Round Selection'), 'pr.comm.job', 'comm-job', 'roundselect', array('visible' => false));
        $ajaxPage = $this->addPage($this->_('Sort jobs'), 'pr.comm.job.edit', 'comm-job', 'sort', array('visible' => false));

        // MESSENGERS
        $setup->addBrowsePage($this->_('Messengers'), 'pr.comm.messenger', 'comm-messengers');

        // COMMUNICATION TEMPLATE CONTROLLER
        $setup->addBrowsePage($this->_('Templates'), 'pr.comm.template', 'comm-template');

        // MAIL SERVER CONTROLLER
        $setup->addBrowsePage($this->_('Mail servers'), 'pr.mail.server', 'mail-server');

        // COMMUNICATION ACTIVITY CONTROLLER
        //$setup->addBrowsePage();
        $page = $setup->addPage($this->_('Communication log'), 'pr.mail.log', 'mail-log');
        $page->addAutofilterAction();
        $page->addExportAction();
        $showPage = $page->addShowAction();
        $showPage->addButtonOnly($this->_('Resend mail'), 'pr.mail.log.resend', 'mail-log', 'resend')
            ->setModelParameters(1);

        return $setup;
    }

    /**
     *
     * @param string $label
     * @param string $privilege
     * @param array $other
     * @return \Gems_Menu_ContainerItem
     */
    public function addContainer($label, $privilege = null, array $other = array())
    {
        $other['label'] = $label;

        if ($privilege) {
            $other['privilege'] = $privilege;
        }

        // Process parameters.
        $defaults = array(
            'visible' => (boolean) $label,  // All menu containers are initally visible unless stated otherwise or specified without label
            'allowed' => true,              // Same as with visible, need this for t_oNavigationArray()
            'order'   => 10 * (count($this->_subItems) + 1),
            );

        foreach ($defaults as $key => $value) {
            if (! isset($other[$key])) {
                $other[$key] = $value;
            }
        }

        $page = new \Gems_Menu_ContainerItem($this, $other);

        $this->_subItems[] = $page;

        return $page;
    }

    /**
     * Shortcut function to create the export container.
     *
     * @param string $label Label for the container
     * @return \Gems_Menu_MenuAbstract The new contact page
     */
    public function addExportContainer($label)
    {
        $export = $this->addContainer($label);

        // EXPORT
        $surveyExport = $export->addPage($this->_('Single survey answers'), 'pr.export', 'export-survey', 'index');
        $surveyExport->addAutofilterAction();

        $surveyExport->addExportAction();

        //$export->addButtonOnly('', 'pr.export', 'export', 'handle-export');
        //$export->addButtonOnly('', 'pr.export', 'export', 'download');


        $batchExport = $export->addPage(
                $this->_('Multiple surveys answers'),
                'pr.export',
                'export-multi-surveys',
                'index'
                );

        $batchExport->addAutofilterAction();

        // EXPORT TO HTML
        $export->addPage($this->_('Respondent archives'), 'pr.export-html', 'respondent-export', 'index');

        $export->addPage($this->_('Survey codebooks'), 'pr.export.code-book-export', 'survey-code-book-multi-export', 'index');


        return $export;
    }

    /**
     * Add a file upload/download page to the menu
     *
     * @param string $label         The label to display for the menu item, null for access without display
     * @param string $privilege     The privilege for the item, null is always, 'pr.islogin' must be logged in, 'pr.nologin' only when not logged in.
     * @param string $controller    What controller to use
     * @param string $action        The name of the action
     * @param array  $other         Array of extra options for this item, e.g. 'visible', 'allowed', 'class', 'icon', 'target', 'type', 'button_only'
     * @return \Gems_Menu_SubMenuItem
     */
    public function addFilePage($label, $privilege, $controller, array $other = array())
    {
        $page = $this->addPage($label, $privilege, $controller, 'index', $other);
        $page->addAutofilterAction();
        $page->addAction($this->_('Create folder'), $privilege . '.createdir', 'create-folder');
        $page->addAction($this->_('Upload'), $privilege . '.upload', 'upload');
        // $page->addCreateAction();
        $page = $page->addShowAction();
        $page->addEditAction();

        $onDelete = new \MUtil_Html_OnClickArrayAttribute();
        $onDelete->addConfirm($this->_("Are you sure you want to delete this file?"));
        $page->addButtonOnly($this->_('Delete'), $privilege . '.delete', $controller, 'delete', array(
            'onclick' => $onDelete,
            ))->setModelParameters(1);

        $page->addButtonOnly($this->_('Download'), $privilege . '.download', $controller, 'download')
                ->setModelParameters(1);

        return $page;
    }

    /**
     * Add a roles browse edit page to the menu,
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addGroupsPage($label, array $other = array())
    {
        $page  = $this->addBrowsePage($label, 'pr.group', 'group', $other);
//        $groups = array();
//
//        if ($this->user instanceof \Gems_User_User) {
//            if ($this->user->hasPrivilege('pr.group')) {
//                $groups = $this->user->getAllowedStaffGroups();
//                $rolesObj = \Gems_Roles::getInstance();
//                $rolesRaw = $this->user->getAllowedRoles();
//                $roleIds  = $rolesObj->translateToRoleIds($rolesRaw);
//                $roles    = array_combine($roleIds, $roleIds) + $rolesRaw;
//            }
//        }
//        \MUtil_Echo::track($roles, array_flip($rolesObj->translateToRoleIds($rolesRaw)), $rolesRaw);
//        \Gems_Menu::$verbose = true;
//        // Now limit changes to allowed roles
//        foreach ($page->getChildren() as $showpage) {
//            if ($showpage instanceof \Gems_Menu_SubMenuItem) {
//                if ('show' === $showpage->get('action')) {
//                    foreach ($showpage->getChildren() as $subpage) {
//                        $subpage->addParameterFilter('ggp_role', $groups);
//                    }
//                    break;
//                }
//            }
//        }
//
        return $page;
    }

    /**
     * Shortcut function to create the import container.
     *
     * @param string $label Label for the container
     * @return \Gems_Menu_MenuAbstract The new contact page
     */
    public function addImportContainer($label)
    {
        $import = $this->addContainer($label);

        $page = $import->addPage($this->_('Answers'), 'pr.survey-maintenance.answer-import', 'file-import', 'answers-import');
        $uplPage = $import->addFilePage($this->_('Importable'), 'pr.file-import', 'file-import');
        // $page->addButtonOnly($this->_('Auto import'), 'pr.file-import.auto', 'file-import', 'auto');
        $uplPage->addImportAction('pr.file-import.import', array('label' => $this->_('Import file')))
                ->setModelParameters(1);

        $impPage = $import->addFilePage($this->_('Imported files'), 'pr.file-import', 'imported-files');
        $impPage->addImportAction('pr.file-import.import', array('label' => $this->_('Reimport file')))
                ->setModelParameters(1);

        $errPage = $import->addFilePage($this->_('Imported failures'), 'pr.file-import', 'imported-failures');
        $errPage->addImportAction('pr.file-import.import', array('label' => $this->_('Retry import')))
                ->setModelParameters(1);

        return $import;
    }

    /**
     * Add the log menu items
     */
    public function addLogControllers()
    {
        // LOG SETUP CONTROLLER
        $this->addBrowsePage($this->_('Log Setup'), 'pr.log.maintenance', 'log-maintenance');

        // LOG CONTROLLER
        $page = $this->addPage($this->_('Log'), 'pr.log', 'log', 'index');
        $page->addAutofilterAction();
        $page->addExportAction();
        $page->addShowAction()
                ->setNamedParameters(\Gems_Model::LOG_ITEM_ID, 'gla_id');

        // LOG FILES CONTROLLER
        $this->addFilePage($this->_('Log files'), 'pr.log.files', 'log-file');
    }

    /**
     * Add a page to the menu
     *
     * @param string $label         The label to display for the menu item, null for access without display
     * @param string $privilege     The privilege for the item, null is always, 'pr.islogin' must be logged in, 'pr.nologin' only when not logged in.
     * @param string $controller    What controller to use
     * @param string $action        The name of the action
     * @param array  $other         Array of extra options for this item, e.g. 'visible', 'allowed', 'class', 'icon', 'target', 'type', 'button_only'
     * @return \Gems_Menu_SubMenuItem
     */
    public function addPage($label, $privilege, $controller, $action = 'index', array $other = array())
    {
        $other['label'] = $label;
        $other['controller'] = $controller;
        $other['action'] = $action;

        if ($privilege) {
            $other['privilege'] = $privilege;
        }

        return $this->add($other);
    }

    /**
     * Add a list of report pages
     *
     * @param string $label         The label to display for the menu item, null for access without display
     * @return \Gems_Menu_SubMenuItem
     */
    public function addPlanPage($label)
    {
        $infoPage = $this->addContainer($label);

        $page = $infoPage->addPage($this->_('Track Summary'), 'pr.plan.summary', 'summary', 'index');
        $page->addAutofilterAction();
        $page->addExportAction();

        $page = $infoPage->addPage($this->_('Track Compliance'), 'pr.plan.compliance', 'compliance', 'index');
        $page->addAutofilterAction();
        $page->addExportAction();

        $page = $infoPage->addPage($this->_('Track Field Utilization'), 'pr.plan.fields', 'field-report', 'index');
        $page->addAutofilterAction();
        $page->addExportAction();

        $page = $infoPage->addPage($this->_('Track Field Content'), 'pr.plan.fields', 'field-overview', 'index');
        $page->addAutofilterAction();
        $page->addExportAction();

        $plans[] = $infoPage->addPage($this->_('By period'), 'pr.plan.overview', 'overview-plan', 'index');
        $plans[] = $infoPage->addPage($this->_('By token'), 'pr.plan.token', 'token-plan', 'index');
        $plans[] = $infoPage->addPage($this->_('By respondent'), 'pr.plan.respondent', 'respondent-plan', 'index');

        foreach ($plans as $plan) {
            $plan->addAutofilterAction();
            $plan->addAction($this->_('Bulk mail'), 'pr.token.bulkmail', 'email', array('routeReset' => false));
            $plan->addExportAction();
        }

        $page = $infoPage->addPage($this->_('Respondent status'), 'pr.plan.consent', 'consent-plan', 'index');
        $page->addShowAction();
        $page->addExportAction();

        return $infoPage;
    }

    /**
     * Add pages that show the user technical information about the installation
     * in the project.
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addProjectInfoPage($label)
    {
        $page = $this->addPage($label, 'pr.project-information', 'project-information');
        $page->addAction($this->_('Errors'),     null, 'errors');
        $page->addAction($this->_('PHP'),        null, 'php');
        $page->addAction($this->_('PHP Errors'), null, 'php-errors');
        $page->addAction($this->_('Project'),    null, 'project');
        $page->addAction($this->_('Session'),    null, 'session');
        $page->addButtonOnly($this->_('Maintenance mode'), 'pr.maintenance.maintenance-mode', 'project-information', 'maintenance');
        $page->addButtonOnly($this->_('Monitor'), 'pr.maintenance.maintenance-mode', 'project-information', 'monitor');
        $page->addButtonOnly($this->_('Clean cache'), 'pr.maintenance.clean-cache', 'project-information', 'cacheclean');

        // TEMPLATES CONTROLLER
        $templates = $page->addPage($this->_('Templates'), 'pr.templates', 'template');
        $templates->addAutofilterAction();
        $edit  = $templates->addEditAction();
        $reset = $edit->addAction($this->_('Reset to default values'), 'pr.templates.reset', 'reset');
        $reset->setModelParameters(1);

        // UPGRADES CONTROLLER
        $upage = $page->addPage($this->_('Upgrade'), 'pr.upgrade', 'upgrade', 'index');

        $show = $upage->addAction($this->_('Show'), null, 'show')
                ->setNamedParameters('id','context');
        $upage->addAction($this->_('Execute all'), 'pr.upgrade.all', 'execute-all')
                ->setModelParameters(1);
        $show->addActionButton($this->_('Execute this'), 'pr.upgrade.one', 'execute-one')
                ->setModelParameters(1)
                ->addNamedParameters('from','from','to','to');
        $show->addActionButton($this->_('Execute from here'), 'pr.upgrade.from', 'execute-from')
                ->setModelParameters(1)
                ->addNamedParameters('from','from');
        $show->addActionButton($this->_('Execute to here'), 'pr.upgrade.to', 'execute-to')
                ->setModelParameters(1)
                ->addNamedParameters('to','to');
        $show->addAction(null, 'pr.upgrade.to', 'execute-last');

        $upage->addAction(
                $this->_('Code compatibility report'),
                'pr.upgrade',
                'compatibility-report'
                );
        $upage->addPage(
                sprintf($this->_('Changelog %s'), 'GemsTracker'),
                'pr.upgrade',
                'project-information',
                'changelog-gems'
                );
        $upage->addPage(
                sprintf($this->_('Changelog %s'), $this->project->getName()),
                'pr.upgrade',
                'project-information',
                'changelog'
                );

        return $page;
    }

    /**
     * Add pages that show the user an overview of the tracks / surveys used
     * in the project.
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addProjectPage($label)
    {
        if ($this->escort instanceof \Gems_Project_Tracks_SingleTrackInterface) {
            if ($trackId = $this->escort->getTrackId()) {
                $infoPage = $this->addPage($label, 'pr.project', 'project-tracks', 'show')
                    ->addHiddenParameter(\MUtil_Model::REQUEST_ID, $trackId);
                $trackSurveys = $infoPage;
            } else {
                $infoPage = $this->addPage($label, 'pr.project', 'project-tracks');
                $trackSurveys = $infoPage->addShowAction('pr.project');
            }
            $trackSurveys->addAction($this->_('Preview'), 'pr.project.questions', 'questions')
                    ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gro_id_track', \Gems_Model::SURVEY_ID, 'gsu_id_survey');

            $infoPage->addAutofilterAction();

            // \MUtil_Echo::track($infoPage->_toNavigationArray(array($this->_getOriginalRequest())));
        } else {
            $infoPage = $this->addContainer($label);
            $tracksPage = $infoPage->addPage($this->_('Tracks'), 'pr.project', 'project-tracks');
            $tracksPage->addAutofilterAction();

            $trackSurveys = $tracksPage->addShowAction('pr.project');
            $trackSurveys->addAction($this->_('Preview'), 'pr.project.questions', 'questions')
                    ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gro_id_track', \Gems_Model::SURVEY_ID, 'gsu_id_survey');

            $surveysPage = $infoPage->addPage($this->_('Surveys'), 'pr.project', 'project-surveys');
            $surveysPage->addAutofilterAction();
            $surveysPage->addShowAction('pr.project');
        }

        return $infoPage;
    }

    /**
     * Add a staff browse edit page to the menu,
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addStaffPage($label, array $other = array())
    {
        if ($this->currentUser->hasPrivilege('pr.staff.edit.all')) {
            $filter = array_keys($this->loader->getUtil()->getDbLookup()->getOrganizations());
        } else {
            $filter = array_keys($this->currentUser->getAllowedOrganizations());
        }

        $page = $this->addPage($label, 'pr.staff', 'staff', 'index', $other);
        $page->addAutofilterAction();
        $createPage = $page->addCreateAction();
        $showPage = $page->addShowAction();

        $pages[] = $showPage->addEditAction();
        $pages[] = $showPage->addAction($this->_('Reset password'), 'pr.staff.edit', 'reset')
                ->setModelParameters(1)
                ->addParameterFilter('gsf_active', 1);
        $pages[] = $showPage->addAction($this->_('Reset 2FA'), 'pr.staff.edit', 'reset2fa')
                ->setModelParameters(1)
                ->addParameterFilter('gsf_active', 1, 'has_2factor', 2);
        $showPage->addAction($this->_('Send Mail'), 'pr.staff.edit', 'mail')
                ->setModelParameters(1)
                ->addParameterFilter('can_mail', 1, 'gsf_active', 1, 'gsf_id_organization', $filter);

        $pages = $pages + $showPage->addDeReactivateAction('gsf_active', 1, 0);
        $pages[] = $showPage->addAction($this->_('Make system user'), 'pr.staff.switch-user', 'switch-user')
                ->setModelParameters(1)
                ->addParameterFilter('gsf_active', 1, 'gsf_id_organization', $filter);

        // LOG CONTROLLER
        $logPage = $showPage->addPage($this->_('Activity overview'), 'pr.staff-log', 'staff-log', 'index')
                ->setModelParameters(1)
                ->addParameterFilter('gsf_id_organization', $filter);
        $logPage->addAutofilterAction();
        $logPage->addShowAction()->setModelParameters(1)->addNamedParameters('log', 'gla_id');

        $page->addExportAction();
        $page->addImportAction();

        if (! $this->currentUser->hasPrivilege('pr.staff.edit.all')) {
            foreach ($pages as $subPage) {
                $subPage->addParameterFilter('gsf_id_organization', $filter, 'accessible_role', 1);
            }
        }

        return $page;
    }

    /**
     * Add a staff browse edit page to the menu,
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addSystemUserPage($label, array $other = array())
    {
        if ($this->currentUser->hasPrivilege('pr.staff.edit.all')) {
            $filter = array_keys($this->loader->getUtil()->getDbLookup()->getOrganizations());
        } else {
            $filter = array_keys($this->currentUser->getAllowedOrganizations());
        }

        $page = $this->addPage($label, 'pr.systemuser', 'system-user', 'index', $other);
        $page->addAutofilterAction();
        $createPage = $page->addCreateAction();
        $showPage = $page->addShowAction();

        $pages[] = $showPage->addEditAction();

        $pages = $pages + $showPage->addDeReactivateAction('gsf_active', 1, 0);
        $pages[] = $showPage->addAction($this->_('Make staff'), 'pr.staff.switch-user', 'switch-user')
                ->setModelParameters(1)
                ->addParameterFilter('gsf_active', 1, 'gsf_id_organization', $filter);

        // LOG CONTROLLER
//        $logPage = $showPage->addPage($this->_('Activity overview'), 'pr.staff-log', 'staff-log', 'index')
//                ->setModelParameters(1);
//        $logPage->addAutofilterAction();
//        $logPage->addShowAction()->setModelParameters(1)->addNamedParameters('log', 'gla_id');

        $page->addExportAction();
        $page->addImportAction();

        return $page;
    }

    /**
     * Add a Trackbuilder menu tree to the menu
     *
     * @param string $label
     * @param array $other
     * @return \Gems_Menu_SubMenuItem
     */
    public function addTrackBuilderMenu($label, array $other = array())
    {
        $setup = $this->addContainer($label);

        // SURVEY SOURCES CONTROLLER
        $page = $setup->addPage($this->_('Survey Sources'), 'pr.source', 'source');
        $page->addAutofilterAction();
        $page->addCreateAction();
        $page->addExportAction();
        $page->addImportAction();
        $show = $page->addShowAction();
        $show->addEditAction();
        $show->addDeleteAction();

        $show->addAction($this->_('Check status'), null, 'ping')
                ->addParameters(\MUtil_Model::REQUEST_ID);
        $show->addAction($this->_('Synchronize surveys'), 'pr.source.synchronize', 'synchronize')
                ->addParameters(\MUtil_Model::REQUEST_ID);
        $show->addAction($this->_('Check is answered'), 'pr.source.check-answers', 'check')
                ->addParameters(\MUtil_Model::REQUEST_ID);
        $show->addAction($this->_('Check attributes'), 'pr.source.check-attributes', 'attributes')
                ->addParameters(\MUtil_Model::REQUEST_ID);

        $page->addAction($this->_('Synchronize all surveys'), 'pr.source.synchronize-all', 'synchronize-all');
        $page->addAction($this->_('Check all is answered'), 'pr.source.check-answers-all', 'check-all');
        $page->addAction($this->_('Check all attributes'), 'pr.source.check-attributes-all', 'attributes-all');

        // ADD CHART SETUP CONTROLLER
        $setup->addBrowsePage($this->_('Charts setup'), 'pr.chartsetup', 'chartconfig');

        // ADD CONDITIONS CONTROLLER - do not include import/export as this is handled by track import/export
        $conditions = $setup->addPage($this->_('Conditions'), 'pr.conditions', 'condition');
        $conditions->addAutofilterAction();
        $conditions->addCreateAction();
        $show = $conditions->addShowAction();
        $show->addEditAction();
        $show->addDeleteAction();

        // SURVEY MAINTENANCE CONTROLLER
        $page = $setup->addPage($this->_('Surveys'), 'pr.survey-maintenance', 'survey-maintenance');
        $page->addAutofilterAction();
        $page->addExportAction();
        $showPage = $page->addShowAction();
        $showPage->addEditAction();
        $showPage->addAction($this->_('Check is answered'), 'pr.survey-maintenance.check', 'check')
                ->addParameters(\MUtil_Model::REQUEST_ID)
                ->setParameterFilter('gsu_active', 1);
        $showPage->addAction($this->_('Import answers'), 'pr.survey-maintenance.answer-import', 'answer-import')
                ->addParameters(\MUtil_Model::REQUEST_ID)
                ->setParameterFilter('gsu_active', 1);
        $showPage->addPdfButton($this->_('PDF'), 'pr.survey-maintenance')
                ->addParameters(\MUtil_Model::REQUEST_ID)
                ->setParameterFilter('gsu_has_pdf', 1);

        $codePage = $showPage->addPage($this->_('Export codebook'), 'pr.survey-maintenance.code-book-export', 'survey-code-book-export', 'export')
            ->addParameters(\MUtil_Model::REQUEST_ID)
            ->setParameterFilter('gsu_active', 1);

        $this->addHiddenPrivilege('pr.survey-maintenance.answer-groups', $this->_(
            'Grant right to set answer access to surveys.'
        ));

        // Multi survey
        $page->addAction($this->_('Check all is answered'), 'pr.survey-maintenance.check-all', 'check-all');
        $page->addAction($this->_('Import answers'), 'pr.survey-maintenance.answer-import', 'answer-imports');

        $page->addPage($this->_('Update to new survey'), 'pr.track-maintenance.edit', 'update-survey', 'run');

        // TRACK MAINTENANCE CONTROLLER
        $page = $setup->addPage($this->_('Tracks'), 'pr.track-maintenance', 'track-maintenance', 'index');
        $page->addAutofilterAction();
        $page->addCreateAction();
        //$page->addExportAction();
        $page->addImportAction();
        $showPage = $page->addShowAction();
        $showPage->addEditAction();
        $showPage->addDeleteAction();
        /*$showPage->addButtonOnly($this->_('Copy'),  'pr.track-maintenance.copy', 'track-maintenance', 'copy')
                ->setModelParameters(1);*/

        $showPage->addAction($this->_('Export'), 'pr.track-maintenance.export', 'export')
                 ->addParameters(\MUtil_Model::REQUEST_ID);
        $showPage->addAction($this->_('Check rounds'), 'pr.track-maintenance.check', 'check-track')
                ->addParameters(\MUtil_Model::REQUEST_ID)
                ->setParameterFilter('gtr_active', 1);
        $showPage->addAction($this->_('Recalculate fields'), 'pr.track-maintenance.check', 'recalc-fields')
                ->addParameters(\MUtil_Model::REQUEST_ID)
                ->setParameterFilter('gtr_active', 1);

        // Fields
        $fpage = $showPage->addPage($this->_('Fields'), 'pr.track-maintenance', 'track-fields')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gtf_id_track');
        $fpage->addAutofilterAction();
        $fpage->addCreateAction('pr.track-maintenance.create')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gtf_id_track');
        $fpage = $fpage->addShowAction()
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gtf_id_track', \Gems_Model::FIELD_ID, 'gtf_id_field', 'sub', 'sub');
        $fpage->addEditAction('pr.track-maintenance.edit')
                ->addNamedParameters(\Gems_Model::FIELD_ID, 'gtf_id_field', \MUtil_Model::REQUEST_ID, 'gtf_id_track', 'sub', 'sub');
        $fpage->addDeleteAction('pr.track-maintenance.delete')
                ->addNamedParameters(\Gems_Model::FIELD_ID, 'gtf_id_field', \MUtil_Model::REQUEST_ID, 'gtf_id_track', 'sub', 'sub');

        // Rounds
        $rpage = $showPage->addPage($this->_('Rounds'), 'pr.track-maintenance', 'track-rounds')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gro_id_track');
        $rpage->addAutofilterAction();
        $rpage->addCreateAction('pr.track-maintenance.create')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gro_id_track');
        $spage = $rpage->addShowAction()
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gro_id_track', \Gems_Model::ROUND_ID, 'gro_id_round');
        $spage->addEditAction('pr.track-maintenance.edit')
                ->addNamedParameters(\Gems_Model::ROUND_ID, 'gro_id_round', \MUtil_Model::REQUEST_ID, 'gro_id_track');
        $spage->addDeleteAction('pr.track-maintenance.delete')
                ->addNamedParameters(\Gems_Model::ROUND_ID, 'gro_id_round', \MUtil_Model::REQUEST_ID, 'gro_id_track');

        $ajaxPage = $this->addPage($this->_('Sort rounds'), 'pr.track-maintenance.edit', 'track-rounds', 'sort', array('visible' => false));

        $overviewPage = $page->addPage($this->_('Tracks per org'), 'pr.track-maintenance.trackperorg', 'track-overview', 'index');
        $overviewPage->addExportAction();
        $overviewPage->addAutofilterAction();

        $page->addAction($this->_('Check all rounds'), 'pr.track-maintenance.check-all', 'check-all');
        $page->addAction($this->_('Recalculate all fields'), 'pr.track-maintenance.check-all', 'recalc-all-fields');

        return $setup;
    }

    public function afterRegistry()
    {
        $this->initTranslateable();
        parent::afterRegistry();
    }

    /**
     * Set the visibility of the menu item and any sub items in accordance
     * with the specified user role.
     *
     * @param \Zend_Acl $acl
     * @param string $userRole
     * @return \Gems_Menu_MenuAbstract (continuation pattern)
     */
    protected function applyAcl(\MUtil_Acl $acl, $userRole)
    {
        foreach ($this->_subItems as $item) {

            $allowed = $item->get('allowed', true);

            if ($allowed && ($privilege = $item->get('privilege'))) {
                if (true !== $privilege) {
                    $allowed = $acl->isAllowed($userRole, null, $privilege);
                }
            }

            if ($allowed) {
                $item->applyAcl($acl, $userRole);
            } else {
                // As an item can be invisible but allowed,
                // but not disallowed but visible we need to
                // set both.
                $item->set('allowed', false);
                $item->set('visible', false);
                $item->setForChildren('allowed', false);
                $item->setForChildren('visible', false);
            }
        }

        return $this;
    }

    /**
     *
     * @param <type> $options
     * @param <type> $findDeep
     * @return \Gems_Menu_SubMenuItem|null
     */
    protected function findItem($options, $findDeep = true)
    {
        foreach ($this->_subItems as $item) {
            if ($result = $item->findItem($options, $findDeep)) {
                return $result;
            }
        }

        return null;
    }

    protected function findItemPath($options)
    {
        foreach ($this->_subItems as $item) {
            if ($path = $item->findItemPath($options)) {
                return $path;
            }
        }

        return array();
    }

    protected function findItems($options, array &$results)
    {
        foreach ($this->_subItems as $item) {
            $item->findItems($options, $results);
        }
    }

    /**
     *
     * @return \Gems_Menu_SubMenuItem[]
     */
    public function getChildren()
    {
        $this->sortByOrder();
        return $this->_subItems;
    }

    public function hasChildren()
    {
        return (boolean) $this->_subItems;
    }

    abstract public function isTopLevel();

    abstract public function isVisible();

    /**
     * Make sure only the active branch is visible
     *
     * @param array $activeBranch Of \Gems_Menu_Menu Abstract items
     * @return \Gems_Menu_MenuAbstract (continuation pattern)
     */
    protected function setBranchVisible(array $activeBranch)
    {
        $current = array_pop($activeBranch);

        foreach ($this->_subItems as $item) {
            if ($item->isVisible()) {
                if ($item === $current) {
                    $item->set('active', true);
                    $item->setBranchVisible($activeBranch);
                } else {
                    $item->setForChildren('visible', false);
                }
            }
        }

        return $this;
    }

    protected function setForChildren($key, $value)
    {
        foreach ($this->_subItems as $item) {
            $item->set($key, $value);
            if ($item->_subItems) {
                $item->setForChildren($key, $value);
            }
        }

        return $this;
    }

    /**
     * Sorts the childeren on their order attribute (instead of the order the were added)
     *
     * @return \Gems_Menu_MenuAbstract (continuation pattern)
     */
    public function sortByOrder()
    {
        uasort($this->_subItems, array(__CLASS__, 'sortOrder'));

        return $this;
    }

    /**
     * uasort() function for sortByOrder()
     *
     * @see sortByOrder();
     *
     * @param self $aItem
     * @param self $bItem
     * @return int
     */
    public static function sortOrder($aItem, $bItem)
    {
        $a = $aItem->get('order');
        $b = $bItem->get('order');

        if ($a == $b) {
            return 0;
        }

        return $a > $b ? 1 : -1;
    }
 }