GemsTracker/gemstracker-library

View on GitHub
classes/Gems/Menu.php

Summary

Maintainability
F
4 days
Test Coverage
C
76%
<?php

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

/**
 * This is the generic Menu class to be extended by the project
 *
 * It loads the menu in two stages:
 *
 * 1. $this->loadDefaultMenu()
 * Normally you should not touch this to make upgrading easier
 *
 * 2. $this->loadProjectMenu()
 * This is where you can reorder, add or disable menu items, specific to your projects needs. Be aware that just using
 * different rights in the <project>Roles.php can also do the trick of hiding menu options.
 *
 * @package    Gems
 * @subpackage Menu
 * @copyright  Copyright (c) 2011 Erasmus MC
 * @license    New BSD License
 * @since      Class available since version 1.0
 */
class Gems_Menu extends \Gems_Menu_MenuAbstract implements \MUtil_Html_HtmlInterface
{
    /**
     *
     * @var \Gems_Menu_SubMenuItem
     */
    private $_currentMenuItem;

    private $_hiddenPrivileges = array();

    protected $_menuUlClass = 'navigation nav nav-stacked';

    private $_onlyActiveBranchVisible = false;

    /**
     *
     * @var \Gems_Menu_ParameterSource
     */
    private $_menuParameters;

    private $_visible = true;

    /**
     * @var \Gems\Event\EventDispatcher
     */
    public $event;

    /**
     * Set output echo on for debugging
     *
     * @var boolean
     */
    static public $verbose = false;

    public function  __construct()
    {
        parent::__construct();
    }

    public function afterRegistry()
    {
        parent::afterRegistry();

        //This loads the default menu
        $this->loadDefaultMenu();

        // This is where module menu changes get loaded
        $event = new \Gems\Event\Application\MenuAdd($this);
        $event->setTranslatorAdapter($this->translateAdapter);
        $this->event->dispatch($event, $event::NAME);

        //This is where you plugin your project menu settings
        $this->loadProjectMenu();
        $this->setOnlyActiveBranchVisible();
        $this->applyAcl($this->acl, $this->currentUser->getRole());
    }

    private function _findPath($request)
    {
        $find = $this->request2find($request);

        return $this->findItemPath($find);
    }

    /**
     * Shortcut function to create a ask menu, with hidden options.
     *
     * This function is in \Gems_Menu instead of AbstractMenu because
     * you should ALWAYS put this menu in the root menu.
     *
     * @param string $label Label for the whole menu
     */
    public function addAskPage($label)
    {
        $page = $this->addPage($label, 'pr.respondent.ask', 'ask');
        $page->addAction($this->_('Token lost?'), 'pr.respondent.lost', 'lost');

        // Routes for token controller return
        $this->addPage(null, true, 'ask', 'forward');
        $this->addPage(null, true, 'ask', 'resume-later');
        $this->addPage(null, true, 'ask', 'return');
        $this->addPage(null, true, 'ask', 'to-survey')->setModelParameters(1);
        $this->addPage(null, true, 'ask', 'token');

        return $page;
    }

    /**
     * Shortcut function to create a contact container.
     *
     * This function is in \Gems_Menu instead of AbstractMenu because
     * you should ALWAYS put this menu in the root menu.
     *
     * @param string $label Label for the whole menu
     * @return \Gems_Menu_MenuAbstract The new contact page
     */
    public function addContactPage($label)
    {
        $project = $this->project;

        $page = $this->addPage($label, 'pr.contact', 'contact');

        $page->addAction(sprintf($this->_('About %s'), $project->getName()), 'pr.contact', 'about');
        $page->addAction(sprintf($this->_('About %s'), $this->_('GemsTracker')), 'pr.contact.gems', 'gems');

        $page->addAction($this->_('Reporting bugs'), 'pr.contact.bugs', 'bugs');

        $page->addAction($this->_('Support'), 'pr.contact.support', 'support');

        return $page;
    }

    /**
     * Shortcut function to create a setup container.
     *
     * This function is in \Gems_Menu instead of AbstractMenu because
     * you should ALWAYS put this menu in the root menu.
     *
     * @param string $label Label for the whole menu
     * @return \Gems_Menu_ContainerItem
     */
    public function addGemsSetupContainer($label)
    {
        $setup = $this->addContainer($label);

        // PROJECT LEVEL
        $cont = $setup->addProjectInfoPage($this->_('Project setup'));

        // DATABASE CONTROLLER
        $page = $setup->addPage($this->_('Database'), 'pr.database', 'database');
        $page->addAutofilterAction();
        // Creation not possible
        $showPage = $page->addShowAction('pr.database');
        $showPage->addAction($this->_('Content'), 'pr.database', 'view')
            ->addParameters(\MUtil_Model::REQUEST_ID)
            ->setParameterFilter('exists', true);
        $showPage->addAction($this->_('Execute'), 'pr.database.create', 'run')
            ->addParameters(\MUtil_Model::REQUEST_ID)
            ->setParameterFilter('script', true);
        $showPage->addDeleteAction('pr.database.delete')
            ->setParameterFilter('exists', true);
        $page->addAction($this->_('Execute new'), 'pr.database.create', 'run-all');
        $page->addAction($this->_('Patches'), 'pr.database.patches', 'patch');
        $page->addAction($this->_('Show structure changes'), 'pr.database', 'show-changes');
        if (isset($this->project->databaseTranslations)) {
            $page->addAction($this->_('Refresh translateables'), 'pr.database', 'refresh-translations');
        }
        $page->addAction($this->_('Run SQL'), 'pr.database.execute', 'run-sql');

        $databaseBackup = $page->addPage($this->_('Database backup'), 'pr.database.backup', 'database-backup', 'index');
        $databaseBackup->addActionButton($this->_('Backup'), 'pr.database.backup', 'backup');
        $databaseBackup->addAutofilterAction();

        // CODE LEVEL
        $cont = $setup->addContainer($this->_('Codes'));
        $cont->addBrowsePage($this->_('Reception codes'), 'pr.reception', 'reception');
        $cont->addBrowsePage($this->_('Consents'), 'pr.consent', 'consent');
        $cont->addBrowsePage($this->_('Mail codes'), 'pr.mailcode', 'mail-code');

        // ACCESS LEVEL
        $cont = $setup->addContainer($this->_('Access'));
        // ROLES CONTROLLER
        $page = $cont->addBrowsePage($this->_('Roles'), 'pr.role', 'role');

        // ASSIGNED CONTROLLER
        $assiPage = $page->addPage($this->_('Assigned'), 'pr.role', 'role-overview', 'index');
        $assiPage->addAutofilterAction();
        $assiPage->addExportAction();

        // PRIVILEGES CONTROLLER
        $privPage = $page->addPage($this->_('Privileges'), 'pr.role', 'privileges', 'index');
        $privPage->addAutofilterAction();
        $privPage->addExportAction();

        // GROUPS CONTROLLER
        $cont->addGroupsPage($this->_('Groups'));
        // ORGANIZATIONS CONTROLLER
        $orgsPage = $cont->addBrowsePage($this->_('Organizations'), 'pr.organization', 'organization');
        $orgsPage->addAction($this->_('Check all respondents'), 'pr.organization.check-all', 'check-all');
        $orgPage = $this->findController('organization', 'show');
        $orgPage->addAction($this->_('Check respondents'), 'pr.organization.check-org', 'check-org')
                ->setModelParameters(1);

        // STAFF CONTROLLER
        $cont->addStaffPage($this->_('Staff'));

        // SYSTEM USER CONTROLLER
        $cont->addSystemUserPage($this->_('System users'));

        // SITE MAINTENANCE CONTROLLER
        $sitesPage = $cont->addBrowsePage($this->_('Sites'), 'pr.site-maint', 'site-maintenance');
        $sitesPage->addButtonOnly($this->_('BLOCK new sites'), 'pr.site-maint.lock', 'site-maintenance', 'lock');
        
        // AGENDA CONTAINER
        $setup->addAgendaSetupMenu($this->_('Agenda'));

        // MAIL CONTAINER
        $setup->addCommSetupMenu($this->_('Communication'));

        // LOG CONTROLLER
        $setup->addLogControllers();

        // OpenRosa
        $this->addOpenRosaContainer($this->_('OpenRosa'), $setup);

        return $setup;
    }

    /**
     * Use this to add a privilege that is not associated with a menu item.
     *
     * @param string $privilege
     * @param string $label
     * @return \Gems_Menu
     */
    public function addHiddenPrivilege($privilege, $label = null)
    {
        if (null === $label) {
            $label = $this->_('Stand-alone privilege: %s');
        }
        $this->_hiddenPrivileges[$privilege] = sprintf($label, $privilege);

        return $this;
    }

    /**
     * Use this to add all and not-displayed privileges
     *
     * @return \void
     */
    public function addHiddenPrivileges()
    {
        // Privileges not associated with menu item
        $this->addHiddenPrivilege('pr.organization-switch', $this->_(
                'Grant access to all organization.'
                ));
        $this->addHiddenPrivilege('pr.plan.mail-as-application', $this->_(
                'Grant right to impersonate the site when mailing.'
                ));
        $this->addHiddenPrivilege('pr.respondent.multiorg', $this->_(
                'Display multiple organizations in respondent overview.'
                ));
        $this->addHiddenPrivilege('pr.episodes.rawdata', $this->_(
                'Display raw data in Episodes of Care.'
                ));
        $this->addHiddenPrivilege('pr.respondent.result', $this->_(
                'Display results in token overviews.'
                ));
        $this->addHiddenPrivilege('pr.respondent.select-on-track', $this->_(
                'Grant checkboxes to select respondents on track status in respondent overview.'
                ));
        $this->addHiddenPrivilege('pr.respondent.show-deleted', $this->_(
                'Grant checkbox to view deleted respondents in respondent overview.'
                ));
        $this->addHiddenPrivilege('pr.respondent.who', $this->_(
                'Display staff member name in token overviews.'
                ));
        $this->addHiddenPrivilege('pr.staff.edit.all', $this->_(
                'Grant right to edit staff members from all organizations.'
                ));
        $this->addHiddenPrivilege('pr.export.add-resp-nr', $this->_(
                'Grant right to export respondent numbers with survey answers.'
                ));
        $this->addHiddenPrivilege('pr.export.gender-age', $this->_(
                'Grant right to export gender and age information with survey answers.'
                ));
        $this->addHiddenPrivilege('pr.staff.see.all', $this->_(
                'Display all organizations in staff overview.'
                ));
        $this->addHiddenPrivilege('pr.group.switch', $this->_(
                'Grant right to switch groups.'
                ));
        $this->addHiddenPrivilege('pr.token.mail.freetext', $this->_(
                'Grant right to send free text (i.e. non-template) email messages.'
                ));
        $this->addHiddenPrivilege('pr.systemuser.seepwd', $this->_(
                'Grant right to see password of system users (without editing right).'
                ));


        $this->addPage(null, 'pr.cron.job', 'cron', 'index');
        $this->addPage(null, 'pr.cron.job', 'cron', 'monitor');
        $this->addPage(null, 'pr.cron.job', 'cron', 'test');

        $this->addPage(null, 'pr.embed.login', 'embed', 'login');
        $this->addHiddenPrivilege('pr.embed.login', $this->_(
                'Grant right for access to embedded login page.'
                ));

        $this->addPage(null, null, 'email', 'index');
    }

    public function addLogonOffToken()
    {
        $this->addPage($this->_('Login'), 'pr.nologin', 'index', 'login')
             ->addAction($this->_('Lost password'), 'pr.nologin', 'resetpassword', ['allowed-alternative' => 'option/change-password']);

        $optionPage = $this->addPage($this->_('Your account'), 'pr.option.edit', 'option', 'edit');
        $logPage = $optionPage->addAction($this->_('Activity overview'), 'pr.option.edit', 'overview');
        $logPage->addAction($this->_('Show'), 'pr.option.edit', 'show-log')
                ->setNamedParameters(\Gems_Model::LOG_ITEM_ID, 'gla_id');
        $optionPage->addAction($this->_('Change password'), 'pr.option.password', 'change-password');
        $optionPage->addAction($this->_('Two factor setup'), 'pr.option.2factor', 'two-factor');

        $this->addParticipatePage($this->_('Participate'));
        $this->addAskPage($this->_('Token'));
        $this->addPage($this->_('Logoff'), 'pr.islogin', 'index', 'logoff');

        if (count($this->currentUser->getAllowedStaffGroups(false)) > 1) {
            // ALLOW GROUP SWITCH
            $this->addPage(null, null, 'group', 'change-ui');
        }

        if ($this->project->multiLocale) {
            // ALLOW LANGUAGE CHANGE
            $this->addPage(null, null, 'language', 'change-ui');
        }

        if (count($this->currentUser->getAllowedOrganizations()) > 1) {
            // ALLOW ORGANIZATION CHANGE
            $this->addPage(null, null, 'organization', 'change-ui');
        }
    }

    /**
     * Shortcut function to add all items needed for OpenRosa
     *
     * Should be enabled in application.ini by using useOpenRosa = 1
     *
     * @param string $label Label for the container
     */
    public function addOpenRosaContainer($label, $parent = null)
    {
        if ($this->escort instanceof \GemsEscort && $this->escort->getOption('useOpenRosa')) {
            if (is_null($parent)) {
                $parent = $this;
            }
            $page = $parent->addBrowsePage($label, 'pr.openrosa','openrosa');
            $page->addButtonOnly($this->_('Scan Responses'), 'pr.openrosa.scan', 'openrosa', 'scanresponses');
            $this->addPage(null, null, 'openrosa', 'submission');
            $this->addPage(null, null, 'openrosa', 'formList'); //mind the capital L here
            $this->addPage(null, null, 'openrosa', 'download');
            $this->addPage(null, null, 'openrosa', 'barcode'); // For barcode rendering
            $this->addPage(null, 'pr.islogin', 'openrosa', 'image'); // For image rendering

            $this->addPage(null, null, 'open-rosa-form', 'edit');
        }
    }

    /**
     * Shortcut function to create a participate page
     *
     * This function is in \Gems_Menu instead of AbstractMenu because
     * you should ALWAYS put this menu in the root menu.
     *
     * @param string $label Label for the whole menu
     */
    public function addParticipatePage($label)
    {
        $participate = $this->addContainer($label);
        $subscr = $participate->addPage($this->_('Subscribe'), 'pr.participate.subscribe', 'participate', 'subscribe');
        $subscr->addPage(null, 'pr.participate.subscribe', 'participate', 'subscribe-thanks');
        $unsub = $participate->addPage($this->_('Unsubscribe'), 'pr.participate.unsubscribe', 'participate', 'unsubscribe');
        $unsub->addPage(null, 'pr.participate.unsubscribe', 'participate', 'unsubscribe-thanks');

        return $participate;
    }

    /**
     * Shortcut function to create the respondent page.
     *
     * @param string $label Label for the container
     * @return \Gems_Menu_MenuAbstract The new respondent page
     */
    public function addRespondentPage($label)
    {
        $orgId = $this->currentUser->getCurrentOrganizationId();

        $params = array(\MUtil_Model::REQUEST_ID1  => 'gr2o_patient_nr', \MUtil_Model::REQUEST_ID2 => 'gr2o_id_organization');

        // MAIN RESPONDENTS ITEM
        $page = $this->addPage($label, 'pr.respondent', 'respondent');
        $page->addAutofilterAction();
        $page->addCreateAction('pr.respondent.create')->setParameterFilter('can_add_respondents', true);
        $page->addExportAction();
        $page->addImportAction();
        $page->addAction(null, 'pr.respondent.simple-api', 'simple-api');

        $rPage = $page->addShowAction()
                ->setNamedParameters($params)
                ->setHiddenOrgId($orgId);

        $rPage->addEditAction('pr.respondent.edit')
                ->setNamedParameters($params)
                ->setHiddenOrgId($orgId);
        $rPage->addAction($this->_('Consent'), 'pr.respondent.change-consent', 'change-consent')
                ->setNamedParameters($params)
                ->setHiddenOrgId($orgId);
        $rPage->addAction($this->_('Change organization'), 'pr.respondent.change-org', 'change-organization')
                ->setNamedParameters($params)
                ->setHiddenOrgId($orgId);

        $rPage->addAction(null, 'pr.token.answers', 'overview')
                ->setNamedParameters($params)
                ->setHiddenOrgId($orgId);

        $rPage->addPage($this->_('View survey'), 'pr.track.insert', 'track', 'view-survey', array('button_only' => true))
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::SURVEY_ID, 'gsu_id_survey')
                    ->setHiddenOrgId($orgId);

        // Add "episodes of care"
        $epiParams = array(\Gems_Model::EPISODE_ID => 'gec_episode_of_care_id'); // + $params;
        $epage = $rPage->addPage($this->_('Episodes'), 'pr.episodes', 'care-episode');
        $epage->setNamedParameters($params)
                ->setHiddenOrgId($orgId);
        $epage->addAutofilterAction();
        $epage->addCreateAction()->setNamedParameters($params)->setHiddenOrgId($orgId);
        $epage = $epage->addShowAction()->setNamedParameters($epiParams);
        $epage->addEditAction()->setNamedParameters($epiParams);
        $epage->addDeleteAction()->setNamedParameters($epiParams);
        // $epage->addAction($this->_('Check'), 'pr.episodes.check', 'check')->setNamedParameters($epiParams);

        // Add "appointments"
        $appParams = array(\Gems_Model::APPOINTMENT_ID => 'gap_id_appointment'); // + $params;
        $apage = $rPage->addPage($this->_('Appointments'), 'pr.appointments', 'appointment');
        $apage->setNamedParameters($params)
                ->setHiddenOrgId($orgId);
        $apage->addAutofilterAction();
        $apage->addCreateAction()->setNamedParameters($params)->setHiddenOrgId($orgId);
        $apage = $apage->addShowAction()->setNamedParameters($appParams);
        $apage->addEditAction()->setNamedParameters($appParams);
        $apage->addDeleteAction()->setNamedParameters($appParams);
        $apage->addAction($this->_('Filter Check'), 'pr.appointments.check', 'check')->setNamedParameters($appParams);

        if ($this->escort instanceof \Gems_Project_Tracks_SingleTrackInterface) {

            $subPage = $rPage->addPage($this->_('Track'), 'pr.track', 'track', 'show-track')
                    ->setNamedParameters($params)
                    ->setHiddenOrgId($orgId)
                    ->addHiddenParameter(\Gems_Model::TRACK_ID, $this->escort->getTrackId());

            $subPage->addAction($this->_('Add'), 'pr.track.create', 'create')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::TRACK_ID, 'gtr_id_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('track_can_be_created', 1)
                    ->addHiddenParameter('track_can_be_created', 1);
            $subPage->addAction($this->_('Preview'), 'pr.track', 'view')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::TRACK_ID, 'gtr_id_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('track_can_be_created', 1)
                    ->addHiddenParameter('track_can_be_created', 1);
            $subPage->addAction($this->_('Edit'), 'pr.track.edit', 'edit-track')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::TRACK_ID, 'gtr_id_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('track_can_be_created', 0)
                    ->addHiddenParameter('track_can_be_created', 0);

            $tkPage = $subPage->addAction($this->_('Token'), 'pr.token', 'show')
                    ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                    ->setParameterFilter(\Gems_Model::ID_TYPE, 'token');

            $subPage->addAction($this->_('Insert'), 'pr.track.insert', 'insert')
                    ->setNamedParameters($params)
                    ->addOptionalParameters(\Gems_Model::SURVEY_ID, 'gsu_id_survey')
                    ->setHiddenOrgId($orgId);

            $subPage->addAction($this->_('Check answers'), 'pr.track.answers', 'check-track-answers')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::TRACK_ID, 'gtr_id_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('track_can_be_created', 0)
                    ->addHiddenParameter('track_can_be_created', 0);
            $subPage->addAction($this->_('Check rounds'), 'pr.track.check', 'check-track')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::TRACK_ID, 'gtr_id_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('track_can_be_created', 0, 'gtr_active', 1, 'gr2t_active', 1)
                    ->addHiddenParameter('track_can_be_created', 0);
            $subPage->addAction($this->_('Recalculate fields'), 'pr.track.check', 'recalc-fields')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::TRACK_ID, 'gtr_id_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('track_can_be_created', 0, 'gtr_active', 1, 'gr2t_active', 1)
                    ->addHiddenParameter('track_can_be_created', 0);

        } else {

            $trPage = $rPage->addPage($this->_('Tracks'), 'pr.track', 'track');
            $trPage->setNamedParameters($params)
                    ->setHiddenOrgId($orgId)
                    ->addAutofilterAction();

            $trPage->addAction($this->_('Add'), 'pr.track.create', 'create')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::TRACK_ID, 'gtr_id_track')
                    ->setHiddenOrgId($orgId);

            $trPage->addAction($this->_('Assignments'), 'pr.track', 'view')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::TRACK_ID, 'gtr_id_track')
                    ->setHiddenOrgId($orgId);

            $trPage->addAction($this->_('Insert'), 'pr.track.insert', 'insert')
                    ->setNamedParameters($params)
                    ->addOptionalParameters(\Gems_Model::SURVEY_ID, 'gsu_id_survey')
                    ->setHiddenOrgId($orgId);

            $itemPage = $trPage->addAction($this->_('Show track'), 'pr.track', 'show-track')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::RESPONDENT_TRACK, 'gr2t_id_respondent_track')
                    ->setHiddenOrgId($orgId);

            $itemPage->addAction($this->_('Edit'), 'pr.track.edit', 'edit-track')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::RESPONDENT_TRACK, 'gr2t_id_respondent_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('can_edit', 1);

            $itemPage->addAction($this->_('Delete'), 'pr.track.delete', 'delete-track')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::RESPONDENT_TRACK, 'gr2t_id_respondent_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('can_edit', 1);

            $itemPage->addAction($this->_('Undelete!'), 'pr.track.undelete', 'undelete-track')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::RESPONDENT_TRACK, 'gr2t_id_respondent_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('can_edit', 0);

            $itemPage->addAction($this->_('Check answers'), 'pr.track.answers', 'check-track-answers')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::RESPONDENT_TRACK, 'gr2t_id_respondent_track')
                    ->setHiddenOrgId($orgId);
            $itemPage->addAction($this->_('Check rounds'), 'pr.track.check', 'check-track')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::RESPONDENT_TRACK, 'gr2t_id_respondent_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('can_edit', 1, 'gtr_active', 1, 'gr2t_active', 1);
            $itemPage->addAction($this->_('Recalculate fields'), 'pr.track.check', 'recalc-fields')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::RESPONDENT_TRACK, 'gr2t_id_respondent_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('can_edit', 1, 'gtr_active', 1, 'gr2t_active', 1);

            $itemPage->addAction($this->_('Export track'), 'pr.track', 'export-track')
                    ->setNamedParameters($params)
                    ->addNamedParameters(\Gems_Model::RESPONDENT_TRACK, 'gr2t_id_respondent_track')
                    ->setHiddenOrgId($orgId)
                    ->setParameterFilter('can_edit', 1);

            $tkPage = $itemPage->addAction($this->_('Token'), 'pr.token', 'show')
                    ->setNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                    ->setParameterFilter(\Gems_Model::ID_TYPE, 'token');

            $trPage->addAction($this->_('Check all answers'), 'pr.track.answers', 'check-all-answers')
                    ->setNamedParameters($params)
                    ->setHiddenOrgId($orgId);
            $trPage->addAction($this->_('Check all rounds'), 'pr.track.check', 'check-all-tracks')
                    ->setNamedParameters($params)
                    ->setHiddenOrgId($orgId);
            $trPage->addAction($this->_('Recalculate all fields'), 'pr.track.check', 'recalc-all-fields')
                    ->setNamedParameters($params)
                    ->setHiddenOrgId($orgId);

            $trPage = $rPage->addPage($this->_('Surveys'), 'pr.survey', 'token');

            // Surveys overview
            $trPage->setNamedParameters($params)
                    ->setHiddenOrgId($orgId)
                    ->addAutofilterAction();
        }

        $tkPage->addEditAction('pr.token.edit')
                ->setNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('grc_success', 1, \Gems_Model::ID_TYPE, 'token');

        $tkPage->addAction($this->_('Correct answers'), 'pr.token.correct', 'correct')
                ->addNamedParameters(MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('is_completed', 1, 'grc_success', 1, Gems_Model::ID_TYPE, 'token', 'show_answers', 1);
        $tkPage->addDeleteAction('pr.token.delete')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('grc_success', 1, \Gems_Model::ID_TYPE, 'token');
        $tkPage->addAction($this->_('Undelete!'), 'pr.token.undelete', 'undelete')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('grc_success', 0, \Gems_Model::ID_TYPE, 'token');

        $tkPage->addButtonOnly($this->_('Fill in'), 'pr.ask', 'ask', 'take')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('can_be_taken', 1, \Gems_Model::ID_TYPE, 'token');
        $tkPage->addPdfButton($this->_('Print PDF'), 'pr.token.print')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('gsu_has_pdf', 1, \Gems_Model::ID_TYPE, 'token');
        $tkPage->addAction($this->_('E-Mail now!'), 'pr.token.mail', 'email')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('can_be_taken', 1, 'can_email', 1, \Gems_Model::ID_TYPE, 'token');
        $tkPage->addAction($this->_('Preview'), 'pr.project.questions', 'questions')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter(\Gems_Model::ID_TYPE, 'token');
        $tkPage->addActionButton($this->_('Answers'), 'pr.token.answers', 'answer', ['class' => $this->currentUser->isSessionFramed() ? 'inline-answers' : ''])
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('gto_in_source', 1, \Gems_Model::ID_TYPE, 'token', 'show_answers', 1)
                ->set('target', \MUtil_Model::REQUEST_ID);
        $tkPage->addAction($this->_('Token check'), 'pr.token.check', 'check-token')
               ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
               ->setParameterFilter(\Gems_Model::ID_TYPE, 'token');
        $tkPage->addAction($this->_('(Re)check answers'), 'pr.token.answers', 'check-token-answers')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter(\Gems_Model::ID_TYPE, 'token');
        $tkPage->addActionButton($this->_('Export answers'), 'pr.token.answers', 'answer-export')
                ->addNamedParameters(\MUtil_Model::REQUEST_ID, 'gto_id_token')
                ->setParameterFilter('is_completed', 1, \Gems_Model::ID_TYPE, 'token');


        $rPage->addAction($this->_('Export archive'), 'pr.respondent.export-html', 'export-archive')
                ->setNamedParameters($params)
                ->setHiddenOrgId($orgId);

        $mailLogPage = $rPage->addPage($this->_('Communication log'), 'pr.respondent-commlog', 'respondent-mail-log', 'index')
                ->setNamedParameters($params)
                ->setHiddenOrgId($orgId);
        $mailLogPage->addAutofilterAction();
        $showPage = $mailLogPage->addShowAction();
        $showPage->addButtonOnly($this->_('Resend mail'), 'pr.respondent-commlog.resend', 'respondent-mail-log', 'resend')
                 ->setModelParameters(1);

        // LOG CONTROLLER
        $logPage = $rPage->addPage($this->_('Activity log'), 'pr.respondent-log', 'respondent-log', 'index');
        $logPage->setNamedParameters($params)
                ->setHiddenOrgId($orgId);
        $logPage->addAutofilterAction();
        $logParams = $params + array(\Gems_Model::LOG_ITEM_ID => 'gla_id');
        $logPage->addShowAction()
                ->setNamedParameters($logParams)
                ->setHiddenOrgId($orgId);

        $rPage->addDeleteAction('pr.respondent.delete')
                ->setNamedParameters($params)
                ->setParameterFilter('resp_deleted', 0)
                ->setHiddenOrgId($orgId);
        $rPage->addAction($this->_('Undelete!'), 'pr.respondent.undelete', 'undelete')
                ->setNamedParameters($params)
                ->setParameterFilter('resp_deleted', 1)
                ->setHiddenOrgId($orgId);

        /* Add respondent relations */
        $relParams = $params + array('rid' => 'grr_id');
        $relationsPage = $rPage->addPage($this->_('Relations'), 'pr.respondent.relation', 'respondent-relation', 'index')
                ->setNamedParameters($params)
                ->setHiddenOrgId($orgId);
        $relationsPage->addAutofilterAction();
        $relationsPage->addCreateAction();
        $relationsPage->addEditAction();
        $relationsPage->addDeleteAction();
        foreach ($relationsPage->getChildren() as $relation)
        {
            if (!in_array($relation->get('action'), array('autofilter', 'create'))) {
                $relation->setNamedParameters($relParams)
                         ->setHiddenOrgId($orgId);
            } else {
                $relation->setNamedParameters($params)
                         ->setHiddenOrgId($orgId);
            }
        }

        return $rPage;
    }

    /**
     *
     * @param \Zend_Controller_Request_Abstract|array $request
     * @return \Gems_Menu_SubMenuItem|null
     */
    public function find($request)
    {
        $find = $this->request2find($request);

        return $this->findItem($find, true);
    }

    /**
     * Find a menu item through specifying the controller and action
     *
     * @param string $controller
     * @param string $action
     * @return \Gems_SubMenuItem
     */
    public function findAllowedController($controller, $action = 'index')
    {
        return $this->findItem(array('controller' => $controller, 'action' => $action, 'allowed' => true), false);
    }

    public function findAll($request)
    {
        $find = $this->request2find($request);

        $results = array();

        $this->findItems($find, $results);

        if ($results) {
            if (count($results) == 1) {
                return reset($results);
            }

            return new \MUtil_Html_MultiWrapper($results);
        }
    }

    /**
     * Find a menu item through specifying the controller and action
     *
     * @param string $controller
     * @param string $action
     * @return \Gems_SubMenuItem
     */
    public function findController($controller, $action = 'index')
    {
        return $this->findItem(array('controller' => $controller, 'action' => $action), true);
    }

    public function findFirst($request)
    {
        $find = $this->request2find($request);

        return $this->findItem($find, false);
    }

    public function getActivePath(\Zend_Controller_Request_Abstract $request)
    {
        $activePath = $this->_findPath($request);

        return array_reverse($activePath);
    }

    /**
     *
     * @return \Gems_Menu_SubMenuItem
     */
    public function getCurrent()
    {
        return $this->_currentMenuItem;
    }

    public function getCurrentChildren()
    {
        if ($current = $this->getCurrent()) {
            return $current->getChildren();
        } else {
            return array();
        }
    }

    /**
     * Menulist populated with current items
     *
     * @param \Zend_Controller_Request_Abstract $request
     * @param $parentLabel
     * @return \Gems_Menu_MenuList
     */
    public function getCurrentMenuList(\Zend_Controller_Request_Abstract $request, $parentLabel = null)
    {
        $controller = $request->getControllerName();
        $action     = $request->getActionName();

        $menuList = $this->getMenuList();

        if ($controller !== 'index') {
            $menuList->addByController($controller, 'index', $parentLabel);
        }

        foreach ($this->getCurrent()->getChildren() as $child) {
            if ($child instanceof \Gems_Menu_SubMenuItem) {
                $chAction = $child->get('action');
                $chContr  = $child->get('controller');
                if (! ($controller == $chContr && $action == $chAction)) {
                    $menuList->addByController($chContr, $chAction);
                }
            }
        }
        return $menuList;
    }


    /**
     *
     * @return \Gems_Menu_SubMenuItem
     */
    public function getCurrentParent()
    {
        if ($current = $this->getCurrent()) {
            return $current->getParent();
        }
    }

    /**
     *
     * @return \Gems_Menu_MenuList
     */
    public function getMenuList()
    {
        return new \Gems_Menu_MenuList($this);
    }

    /**
     * Use to set parameters that will be used when
     * drawing the navigation menu.
     *
     * @return \Gems_Menu_ParameterSource
     */
    public function getParameterSource()
    {
        if (! $this->_menuParameters) {
            $this->_menuParameters = new \Gems_Menu_ParameterSource();
        }

        return $this->_menuParameters;
    }

    /**
     * Returns a (unique) list of privileges that are used in the menu
     *
     * @return array
     */
    public function getUsedPrivileges()
    {
        $privileges = $this->_hiddenPrivileges;

        $this->_addUsedPrivileges($privileges, '');

        return $privileges;
    }

    public function isTopLevel()
    {
        return true;
    }

    public function isVisible()
    {
        return $this->_visible;
    }

    /**
     * This is where we load the default menu, be very careful to overload this function
     * as it makes upgrading a lot more difficult
     */
    public function loadDefaultMenu()
    {
        // MAIN RESPONDENTS ITEM
        $this->addRespondentPage($this->_('Respondents'));

        // MAIN CALENDAR ITEM
        $this->addCalendarPage($this->_('Calendar'));

        // MAIN PLANNING ITEM
        $this->addPlanPage($this->_('Overview'));

        // PROJECT INFO
        $this->addProjectPage($this->_('Project'));

        // SETUP CONTAINER
        $this->addGemsSetupContainer($this->_('Setup'));

        // TRACK BUILDER
        $this->addTrackBuilderMenu($this->_('Track Builder'));

        // EXPORT
        $this->addExportContainer($this->_('Export'));

        // IMPORT
        $this->addImportContainer($this->_('Import'));

        // OTHER ITEMS
        $this->addLogonOffToken();

        // CONTACT MENU
        $this->addContactPage($this->_('Contact'));

        // HIDDEN PRIVILEGES
        $this->addHiddenPrivileges();
    }

    /**
     * Plug your project menu into this function call
     */
    public function loadProjectMenu()
    {
    }

    /**
     * Renders the element into a html string
     *
     * The $view is used to correctly encode and escape the output
     *
     * @param \Zend_View_Abstract $view
     * @return string Correctly encoded and escaped html output
     */
    public function render(\Zend_View_Abstract $view)
    {
        $request = $this->_getOriginalRequest();

        if ($this->_onlyActiveBranchVisible) {
            $activePath = $this->_findPath($request);

            // \MUtil_Echo::r($activePath);

            $this->setBranchVisible($activePath);
        }

        $parameterSources[] = $request;

        if ($this->_menuParameters) {
            $parameterSources[] = $this->_menuParameters;
        }

        $source = new \Gems_Menu_ParameterCollector($parameterSources);
        // self::$verbose = true;

        $nav = $this->_toNavigationArray($source);

        // \MUtil_Echo::track($nav);

        $ul = $this->renderFirst();

        $this->renderItems($ul, $nav, true);

        return $ul->render($view);
    }

    /**
     * Helper function to create main menu html element.
     *
     * Allows overloading by sub classes.
     *
     * @return \MUtil_Html_ListElement
     */
    protected function renderFirst()
    {
        return \MUtil_Html::create()->ul(array('class' => $this->_menuUlClass));
    }

    /**
     * Helper function to load the menu items to the html element.
     *
     * Allows overloading by sub classes.
     *
     * @param \MUtil_Html_ListElement $ul
     * @param array $items
     * @param boolean $cascade render nested items
     */
    protected function renderItems(\MUtil_Html_ListElement $ul, array $items, $cascade)
    {
        foreach ($items as $item) {
            if (isset($item['visible'], $item['label']) && $item['visible'] && $item['label']) {
                $url = $item['params'];
                $url['controller'] = $item['controller'];
                $url['action']     = $item['action'];
                $url['RouteReset'] = true;
                $li = $ul->li();
                if (isset($item['active']) && $item['active']) {
                    $li->class = 'active';
                }
                if (isset($item['liClass']) && $item['liClass']) {
                    $li->appendAttrib('class', $item['liClass']);
                }

                $a = $li->a($url, $item['label']);
                if (isset($item['class'])) {
                    $a->class = $item['class'];
                }
                if (isset($item['target'])) {
                    $a->target = $item['target'];
                }

                if ($cascade && isset($item['pages']) && is_array($item['pages'])) {
                    $this->renderItems($li->ul(array('class' => 'subnav '. $this->_menuUlClass)), $item['pages'], true);
                }
            }
        }
    }

    protected function request2find($request)
    {
        if (is_array($request)) {
            return $request;
        }

        if ($request instanceof \Zend_Controller_Request_Abstract) {
            $find['action']     = $request->getActionName();
            $find['controller'] = $request->getControllerName();
        } else {
            throw new \Gems_Exception_Coding('Not a valid menu search request!');
        }
        // \MUtil_Echo::r($find);

        return $find;
    }

    public function setCurrent(\Gems_Menu_SubMenuItem $item)
    {
        if ('autofilter' == $item->get('action')) {
            $this->_currentMenuItem = $item->getParent();
        } else {
            $this->_currentMenuItem = $item;
        }
    }

    public function setOnlyActiveBranchVisible($value = true)
    {
        $this->_onlyActiveBranchVisible = $value;

        return $this;
    }

    public function setVisible($value = true)
    {
        $this->_visible = $value;

        return $this;
    }

    /**
     * Renders the top level menu items into a html element
     *
     * @return \MUtil_Html_HtmlElement
     */
    public function toActiveBranchElement()
    {
        $request    = $this->_getOriginalRequest();
        $activePath = $this->_findPath($request);

        if (! $activePath) {
            return null;
        }

        $this->setBranchVisible($activePath);

        $activeItem = array_pop($activePath);

        if (! $activeItem instanceof \Gems_Menu_SubMenuItem) {
            return null;
        }
        // \MUtil_Echo::track($activeItem->get('label'));

        $parameterSources[] = $request;

        if ($this->_menuParameters) {
            $parameterSources[] = $this->_menuParameters;
        }

        $source = new \Gems_Menu_ParameterCollector($parameterSources);
        // self::$verbose = true;

        $nav = $this->_toNavigationArray($source);

        $ul = $this->renderFirst();

        foreach ($nav as $item => $sub) {
            if ($sub['label'] === $activeItem->get('label')) {

                if (isset($sub['pages'])) {
                    $this->renderItems($ul, $sub['pages'], true);
                }

                return $ul;
            }
        }

        return null;
    }

    /**
     * Renders the top level menu items into a html element
     *
     * @return \MUtil_Html_HtmlElement
     */
    public function toTopLevelElement()
    {
        $request     = $this->_getOriginalRequest();
        $activeItems = $this->_findPath($request);
        $this->setBranchVisible($activeItems);
        foreach ($activeItems as $activeItem) {
            if ($activeItem instanceof \Gems_Menu_SubMenuItem) {
                $activeItem->set('class', 'active');
            }
        }

        $parameterSources[] = $request;

        if ($this->_menuParameters) {
            $parameterSources[] = $this->_menuParameters;
        }

        $source = new \Gems_Menu_ParameterCollector($parameterSources);

        $nav = $this->_toNavigationArray($source);

        // \MUtil_Echo::track($nav);

        $ul = $this->renderFirst();

        $this->renderItems($ul, $nav, false);

        return $ul;
    }

    /**
     * Generates a \Zend_Navigation object from the current menu
     *
     * @param \Zend_Controller_Request_Abstract $request
     * @param mixed $actionController
     * @return \Zend_Navigation
     */
    public function toZendNavigation(\Zend_Controller_Request_Abstract $request, $actionController = null)
    {
        if ($this->_onlyActiveBranchVisible) {
            $activePath = $this->_findPath($request);

            // \MUtil_Echo::r($activePath);
            $this->setBranchVisible($activePath);
        }

        $parameterSources = func_get_args();

        if ($this->_menuParameters) {
            $parameterSources[] = $this->_menuParameters;
        }

        $source = new \Gems_Menu_ParameterCollector($parameterSources);
        // self::$verbose = true;

        $nav = new \Zend_Navigation($this->_toNavigationArray($source));

        // \MUtil_Echo::track($this->_toNavigationArray($source), $nav);

        return $nav;
    }
}