GemsTracker/gemstracker-library

View on GitHub
classes/GemsEscort.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

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

use MUtil\Controller\Router\Rewrite;
use MUtil\Javascript;

/**
 * Project Application Core code
 *
 * @package    Gems
 * @subpackage Project
 * @copyright  Copyright (c) 2011 Erasmus MC
 * @license    New BSD License
 * @since      Class available since version 1.0
 */
class GemsEscort extends \MUtil_Application_Escort
{
    /**
     * Default reception code value
     */
    const RECEPTION_OK = 'OK';

    /**
     * Static instance
     *
     * @var self
     */
    private static $_instanceOfSelf;

    /**
     * Targets for _updateVariable
     *
     * @var array
     */
    private $_copyDestinations;

    /**
     * The prefix / directory paths where the Gems Loaders should look
     *
     * @var array prefix => path
     */
    private $_loaderDirs;

    /**
     * The project loader
     *
     * @var \MUtil_Loader_PluginLoader
     */
    private $_projectLoader;

    /**
     * Is firebird logging on (set by constructor from application.ini)
     *
     * @var boolean
     */
    private $_startFirebird;

    /**
     * Set jQuery version number
     * @var string
     */
    public $jqueryVersionNr = '3.6.0';

    /**
     * Set jQuery UI version number
     * @var string
     */
    public $jqueryUiVersionNr = '1.12.1';

    /**
     * A nested array containing the pages accessible to everyone in maintenance mode
     *
     * @var array Nested controllername => [actions]
     */
    protected $maintenanceAccessiblePages = [
        'index' => ['index', 'login', 'logoff', 'resetpassword'],
        'ask' => ['index', 'forward', 'return', 'token'],
        'contact'=> ['index', 'about', 'gems', 'bugs', 'support'],
    ];

    /**
     * List of Registered Gemstracker modules
     * key: Module name
     * value: classname of The settingsFile of the module, extending \Gems\Module\ModuleSettingsAbstract
     * e.g. SampleModule => \SampleModule\ModuleSettings::class
     *
     * @var array
     */
    public static $modules;

    /**
     * @var array List of Registered Gemstracker modules that are also installed
     * key: Module name
     * value: classname of The settingsFile of the module, extending \Gems\Module\ModuleSettingsAbstract
     * e.g. SampleModule => \SampleModule\ModuleSettings::class
     */
    protected $moduleSettings;

    /**
     * Set to true for bootstrap projects. Needs html5 set to true as well
     * @var boolean
     */
    public $useBootstrap = false;

    /**
     * Set to true for html 5 projects
     *
     * @var boolean
     */
    public $useHtml5 = false;

    /**
     * Constructor
     *
     * @param  \Zend_Application|\Zend_Application_Bootstrap_Bootstrapper $application
     * @return void
     */
    public function __construct($application)
    {
        parent::__construct($application);

        self::$_instanceOfSelf = $this;

        // DIRECTORIES USED BY LOADER
        $dirs = $this->getOption('loaderDirs');
        if ($dirs) {
            $newDirs = array();
            foreach ($dirs as $key => $path) {
                if (defined($key)) {
                    $newDirs[constant($key)] = $path;
                } else {
                    $newDirs[$key] = $path;
                }
            }
            $dirs = $newDirs;
        } else {
            global $GEMS_DIRS;

            // Use $GEMS_DIRS if defined
            if (isset($GEMS_DIRS)) {
                $dirs = array();

                foreach ($GEMS_DIRS as $prefix => $dir) {
                    $dirs[$prefix] = $dir . '/' . strtr($prefix, '_', '/');
                }
            } else {
                // Default setting
                $dirs = array(
                        GEMS_PROJECT_NAME_UC => APPLICATION_PATH . '/classes/' .GEMS_PROJECT_NAME_UC,
                        'Gems' =>               GEMS_LIBRARY_DIR . '/classes/Gems'
                );
            }
        }

        $moduleSettings = $this->getModules();
        if (is_array($moduleSettings)) {
            $moduleDirs = [];
            foreach($moduleSettings as $name=>$settings) {
                if (method_exists($settings, 'getLoaderDir')) {
                    $loaderDir = $settings::getLoaderDir();
                    if ($loaderDir) {
                        $moduleDirs[$name] = $loaderDir;
                    }
                }
            }

            $dirs = array_slice($dirs, 0,1, true) + $moduleDirs + array_slice($dirs, 1, null, true);
            //array_splice($dirs, 1,0, $moduleDirs);
        }

        //\MUtil_Echo::track($dirs);
        $this->_loaderDirs = array_reverse($dirs);

        foreach ($this->_loaderDirs as $prefix => $path) {
            if ($prefix) {
                \MUtil_Model::addNameSpace($prefix);
            }
        }

        // PROJECT LOADER
        $this->_projectLoader = new \MUtil_Loader_PluginLoader($this->_loaderDirs);

        // FIRE BUG
        $firebug = $application->getOption('firebug');
        $this->_startFirebird = $firebug['log'];

        // START SESSIE
        $sessionOptions['name']            = GEMS_PROJECT_NAME_UC . '_' . md5(APPLICATION_PATH) . '_SESSID';
        $sessionOptions['cookie_path']     = strtr(dirname($_SERVER['SCRIPT_NAME']), '\\', '/');
        $sessionOptions['cookie_httponly'] = true;
        $sessionOptions['cookie_secure']   = (APPLICATION_ENV == 'production') || (APPLICATION_ENV === 'acceptance');
        \Zend_Session::start($sessionOptions);
    }

    /**
     * Copy from \Zend_Translate_Adapter
     *
     * Translates the given string
     * returns the translation
     *
     * @param  string             $text   Translation string
     * @param  string|\Zend_Locale $locale (optional) Locale/Language to use, identical with locale
     *                                    identifier, @see \Zend_Locale for more information
     * @return string
     */
    public function _($text, $locale = null)
    {
        if (! isset($this->request)) {
            // Locale is fixed by request.
            $this->setException(new \Gems_Exception_Coding('Requested translation before request was made available.'));
        }
        return $this->translateAdapter->_($text, $locale);
    }

    /**
     * Check all parameters for security violations
     *
     * All variables are checked for name contents: the <>=%&"' characters should not occur in the name.
     *
     * The non-post variables are checked for value contents as well: after a <>&% there should not occur
     * a frame, script or img stext.
     *
     * @param array $params
     * @param array $posts
     */
    protected function _checkParameters(array $params, array $posts)
    {
        foreach ($params as $key => $value) {
            $rest = strpbrk($key, '<>=%&"\'`');
            if (false !== $rest) {
//                $msg = sprintf("Illegal character %s for parameter %s using url %s from ip address %s.", $rest[0], $key, $_SERVER['REQUEST_URI'], $this->request->getServer('REMOTE_ADDR'));
//                $this->logger->log($msg, \Zend_Log::ERR);
                $this->setError(
                        $this->_('Illegal request parameter'),
                        422,
                        sprintf($this->_('Illegal character %s in parameter name.'), $rest[0]),
                        true
                );
            }
            if ($value && (!is_object($value)) && (!array_key_exists($key, $posts))) {
                foreach ((array) $value as $val) {
                    // Quickfix
                    // If $val is an array preg_match fails. This is true for export options
                    // If the elements of the array should be checked too, feel free to do so
                    if (is_array($val)) {
                        continue;
                    }
                    // Find not allowed words after <>%&
                    $checks  = ['iframe', 'img', 'script'];
                    $pattern = '/([<>%&])(' . join('|', $checks) . ')/i';
                    if (preg_match($pattern, $val, $matches)) {
                        $this->setError(
                                $this->_('Illegal request parameter'),
                                422,
                                sprintf($this->_('Illegal parameter value containing the text "%s" after a %s character.'), $matches[2], $matches[1]),
                                true
                        );
                    }
                }
            }
        }
    }

    /**
     * Function to maintain uniformity of access to variables from the bootstrap object.
     * Copies all variables to the target object.
     *
     * @param Object $object An object who gets all variables from this object.
     * @return void
     */
    protected function _copyVariables($object)
    {
        // Store for _updateVariable
        $this->_copyDestinations[] = $object;

        // Extra object
        $object->escort = $this;

        foreach ($this->getContainer() as $key => $value) {
            // Prevent self referencing
            if ($value !== $object) {
                // \MUtil_Echo::r(get_class($value), $key);
                // echo get_class($value) . ' => ' . $key . '<br/>';
                $object->$key = $value;
            }
        }

        // viewRenderer is not in the container so has to be copied separately
        foreach (get_object_vars($this) as $name => $value) {
            if ('_' != $name[0]) {
                // \MUtil_Echo::r(get_class($value), $key);
                $object->$name = $value;
            }
        }
    }

    /**
     * Initialize the GEMS project component.
     *
     * The project component contains information about this project that are not Zend specific.
     * For example:
     * -- the super administrator,
     * -- the project name, version and description,
     * -- locales used,
     * -- css and image directories used.
     *
     * This is the place for you to store any project specific data that should not be in the code.
     * I.e. if you make a controllor that needs a setting to work, then put the setting in this
     * settings file.
     *
     * Use $this->project to access afterwards
     *
     * @return \Gems_Project_ProjectSettings
     */
    protected function _initProject()
    {
        $projectArray = $this->includeFile(APPLICATION_PATH . '/configs/project');

        if ($projectArray instanceof \Gems_Project_ProjectSettings) {
            $project = $projectArray;
        } else {
            $project = $this->createProjectClass('Project_ProjectSettings', $projectArray);
        }

        return $project;
    }

    /**
     * Initialize the logger
     *
     * @return \Gems_Log
     */
    protected function _initLogger()
    {
        $this->bootstrap('project');    // Make sure the project object is available
        $logger = \Gems_Log::getLogger();

        $logPath = GEMS_ROOT_DIR . '/var/logs';

        try {
            $writer = new \Zend_Log_Writer_Stream($logPath . '/errors.log');
        } catch (Exception $exc) {
            try {
                // Try to solve the problem, otherwise fail heroically
                \MUtil_File::ensureDir($logPath);
                $writer = new \Zend_Log_Writer_Stream($logPath . '/errors.log');
            } catch (Exception $exc) {
                $this->bootstrap(array('locale', 'translate'));
                die(str_replace(GEMS_ROOT_DIR . '/', '', sprintf(
                        $this->translateAdapter->_('Path %s not writable') . "\n%s\n",
                        $logPath,
                        $exc->getMessage()
                        )));
            }
        }

        $filter = new \Zend_Log_Filter_Priority($this->project->getLogLevel());
        $writer->addFilter($filter);
        $logger->addWriter($writer);

        // OPTIONAL STARTY OF FIREBUG LOGGING.
        if ($this->_startFirebird) {
            $logger->addWriter(new \Zend_Log_Writer_Firebug());
            //We do not add the logLevel here, as the firebug window is intended for use by
            //developers only and it is only written to the active users' own screen.
        }

        \Zend_Registry::set('logger', $logger);

        return $logger;
    }

    /**
     * Create a default file cache for the Translate and DB adapters to speed up execution
     *
     * @return \Zend_Cache_Core
     */
    protected function _initCache()
    {
        $this->bootstrap('project');

        $useCache = $this->getResource('project')->getCache();

        $cache       = null;
        $exists      = false;
        $cachePrefix = GEMS_PROJECT_NAME . '_';

        $defaultLifetime = null;
        // Check if APC extension is loaded and enabled
        if (\MUtil_Console::isConsole() && !ini_get('apc.enable_cli') && $useCache === 'apc') {
            // To keep the rest readable, we just fall back to File when apc is disabled on cli
            $useCache = "file";
        }


        switch ($useCache) {
            case 'newFile':
                if (!class_exists('\Symfony\Component\Cache\Adapter\FilesystemAdapter')) {
                    error_log("Symfony filesystem cache not available!");
                    break;
                }
                $namespace = '';
                $cacheDir = GEMS_ROOT_DIR . '/var/cache';
                //$cache = new Symfony\Component\Cache\Simple\FilesystemCache($namespace, $defaultLifetime, $directory);
                $cache = new \Symfony\Component\Cache\Adapter\TagAwareAdapter(
                    new \Symfony\Component\Cache\Adapter\FilesystemAdapter($namespace, $defaultLifetime, $cacheDir)
                );
                $cacheBackend = new \Gems\Cache\Backend\Psr6Cache($cache);
                $cacheBackendOptions = [];
                if (!file_exists($cacheDir)) {
                    if (@mkdir($cacheDir, 0777, true)) {
                        $exists = true;
                    }
                } else {
                    $exists = true;
                }
                break;
            case 'newZendFile':
                if (!class_exists('\Laminas\Cache\StorageFactory')) {
                    error_log("Laminas\Cache Filesystem cache not available!");
                    break;
                }
                $cacheDir = GEMS_ROOT_DIR . "/var/cache/";
                $cacheBackendOptions = array('cache_dir' => $cacheDir, 'cache_file_perm' => 0660);

                $storage = \Laminas\Cache\StorageFactory::factory([
                    'adapter' => [
                        'name' => 'filesystem',
                        'options' => [
                            'cache_dir' => $cacheDir,
                            'file_permission' => 0660,
                        ],
                    ],
                    'plugins' => array(
                        // Don't throw exceptions on cache errors
                        /*'exception_handler' => array(
                            'throw_exceptions' => false
                        ),*/
                        // We store database rows on filesystem so we need to serialize them
                        'Serializer'
                    )
                ]);

                $cacheBackend = new \Gems\Cache\Backend\ZendCache($storage);

                if (!file_exists($cacheDir)) {
                    if (@mkdir($cacheDir, 0777, true)) {
                        $exists = true;
                    }
                } else {
                    $exists = true;
                }
                break;
            case 'redis':

                $redisDsn = $this->getResource('project')->getRedisDsn();

                if ($redisDsn !== false) {
                    $redisClient = \Symfony\Component\Cache\Adapter\RedisAdapter::createConnection(
                        $redisDsn
                    );

                    $namespace = 'gems';

                    $cache = new \Symfony\Component\Cache\Adapter\TagAwareAdapter(
                        new \Symfony\Component\Cache\Adapter\RedisAdapter($redisClient, $namespace)
                    );
                    $cacheBackend = new \Gems\Cache\Backend\Psr6Cache($cache);
                    $cacheBackendOptions = [];
                    $exists = true;
                }

                break;
            case 'newApc':

                if (!class_exists('\Symfony\Component\Cache\Adapter\ApcuAdapter')) {
                    error_log("Symfony APCU cache not available!");
                    break;
                }
                    if (extension_loaded('apc') && ini_get('apc.enabled')) {
                    //Add path to the prefix as APC is a SHARED cache
                    $cachePrefix .= md5(APPLICATION_PATH);
                    $cacheBackendOptions = array('cache_id_prefix' => $cachePrefix);

                    $cache = new \Symfony\Component\Cache\Adapter\TagAwareAdapter(
                        new \Symfony\Component\Cache\Adapter\ApcuAdapter($cachePrefix, $defaultLifetime)
                    );
                    $cacheBackend = new \Gems\Cache\Backend\Psr6Cache($cache);
                    $exists = true;
                    break;
                }
                // Intentional fall through;

            case 'apc':
            case 'oldApc':
                if (extension_loaded('apc') && ini_get('apc.enabled')) {
                    //Add path to the prefix as APC is a SHARED cache
                    $cachePrefix .= md5(APPLICATION_PATH);
                    $cacheBackendOptions = array('cache_id_prefix' => $cachePrefix);
                    $cacheBackend = new \Gems\Cache\Backend\Apc($cacheBackendOptions);
                    $exists = true;
                    break;
                } else {
                    error_log("APC cache extension not available! defaulting to file.");
                }
            // Intentional fall through;
            case 'file':
            case 'oldFile':
            default:
                $cacheBackend = 'File';
                $cacheDir = GEMS_ROOT_DIR . "/var/cache/";
                $cacheBackendOptions = array('cache_dir' => $cacheDir);
                if (!file_exists($cacheDir)) {
                    if (@mkdir($cacheDir, 0777, true)) {
                        $exists = true;
                    }
                } else {
                    $exists = true;
                }
        }

        if ($exists && $useCache <> 'none') {
            /**
             * automatic_cleaning_factor disables automatic cleaning of the cache and should get rid of
             *                           random delays on heavy traffic sites with File cache. Apc does
             *                           not support automatic cleaning.
             */
            $cacheFrontendOptions = array('automatic_serialization' => true,
                'cache_id_prefix' => $cachePrefix,
                'automatic_cleaning_factor' => 0);

            $cache = \Zend_Cache::factory('Core', $cacheBackend, $cacheFrontendOptions, $cacheBackendOptions);
        } else {
            $cache = \Zend_Cache::factory('Core', 'Static', array('caching' => false), array('disable_caching' => true));
        }

        \Zend_Db_Table_Abstract::setDefaultMetadataCache($cache);
        \Zend_Translate::setCache($cache);
        \Zend_Locale::setCache($cache);

        return $cache;
    }

    /**
     * Initialize the database.
     *
     * Use $this->db to access afterwards
     *
     * @return \Zend_Db
     */
    protected function _initDb()
    {
        // DATABASE CONNECTION
        $resource = $this->getPluginResource('db');
        if (! $resource) {
            // Do not throw error here. Error is throw in frontDispatchLoopStartup()
            // and no, you should not try to access the database in any earlier
            // code anyway.
            //
            // You are free to change this, but then you get an ugly error message on
            // the screen, while this way the ErrorController is used.
            return null;
        }
        $db = $resource->getDbAdapter();

        // Firebug
        if ($this->_startFirebird) {
            $profiler = new \Zend_Db_Profiler_Firebug(GEMS_PROJECT_NAME);
            $profiler->setEnabled(true);
            $db->setProfiler($profiler);
        }
        \Zend_Db_Table::setDefaultAdapter($db);

        \Zend_Registry::set('db', $db);

        return $db;
    }

    /**
     * @return \Gems\Event\EventDispatcher
     */
    protected function _initEvent()
    {
        $dispatcher = new \Gems\Event\EventDispatcher();

        // Add Gems general Event subscriber.
        // Projects should use their own Event subscriber (or add themselves as module).
        $dispatcher->addSubscriber(new \Gems\EventSubscriber());

        // Add Module Event subscribers.
        $moduleSettings = $this->getModules();
        if ($moduleSettings) {
            foreach ($moduleSettings as $name => $settings) {
                if (method_exists($settings, 'getEventSubscriber')) {
                    $subscriberClass = $settings::getEventSubscriber();
                    if ($subscriberClass) {
                        $subscriber = new $subscriberClass;
                        $dispatcher->addSubscriber($subscriber);
                    }
                }
            }
        }

        return $dispatcher;
    }

    /**
     * Initialize the Project or Gems loader.
     *
     * Use $this->loader to access afterwards
     *
     * @return \Gems_Loader
     */
    protected function _initLoader()
    {
        $this->bootstrap(array('event'));

        $loader = $this->createProjectClass('Loader', $this->getContainer(), $this->_loaderDirs);

        if (class_exists('\\Zalt\\Loader\\ProjectOverloader')) {

            $config['abstract_factories'][] = new Zalt\Loader\ServiceManager\Factory\ZendRegistryFactory($this->getContainer());

            $loaderDirs = array_reverse($this->_loaderDirs);

            $overLoader = new \Zalt\Loader\ProjectOverloader(array_keys($loaderDirs), true);
            $overLoader->legacyClasses = true;
            $overLoader->createServiceManager([], $config);
            $overLoader->setSource($loader);
            // \Zalt\Loader\ProjectOverloader::$verbose = true;

            $this->getContainer()->overLoader = $overLoader;

            // Store the loader directories in container
            $this->getContainer()->loaderDirs = $this->_loaderDirs;
        }

        // Add other languages through Event (e.g. Modules)
        $event = new \Gems\Event\Application\LoaderInitEvent($loader, $this->getContainer());
        $this->event->dispatch($event, $event::NAME);

        \MUtil_Model::setSource($loader, true);

        return $loader;
    }

    /**
     * Initialize the access log.
     *
     * Use $this->accesslog to access afterwards
     *
     * @return \Gems_AccessLog
     */
    protected function _initAccesslog()
    {
        $this->bootstrap(array('cache', 'db', 'loader'));

        // Use container as some test will otherwise not find the db
        $container = $this->getContainer();
        return $this->createProjectClass('AccessLog', $container->cache, $container->db, $container->loader);
    }

    /**
     * Initialize the database.
     *
     * Use $this->acl to access afterwards
     *
     * @return \MUtil_Acl
     */
    protected function _initAcl()
    {
        $this->bootstrap(array('db', 'loader', 'logger'));

        $acl = $this->getLoader()->getRoles($this);

        return $acl->getAcl();
    }

    /**
     * Does nothing but add's the Gems Actionhelper path
     */
    protected function _initActionHelpers()
    {
        \Zend_Controller_Action_HelperBroker::addPrefix('Gems_Controller_Action_Helper');
    }

    /**
     * Initialize the basepath string holde object.
     *
     * Use $this->basepath to access afterwards
     *
     * @return \Gems_Util_BasePath
     */
    protected function _initBasepath()
    {
        return $this->createProjectClass('Util_BasePath');
    }

    /**
     * Initialize the Gems session.
     *
     * The session contains information on the registered user from @see $this->loadLoginInfo($username)
     * This includes:
     * -- user_id
     * -- user_login
     * -- user_name
     * -- user_role
     * -- user_locale
     * -- user_organization_id
     *
     * Use $this->session to access afterwards
     *
     * @deprecated since 1.5
     * @return \Zend_Session_Namespace
     */
    protected function _initSession()
    {
        $this->bootstrap('project'); // Make sure the project is available
        $session = new \Zend_Session_Namespace('gems.' . GEMS_PROJECT_NAME . '.session');

        $idleTimeout = $this->project->getSessionTimeOut();

        $session->setExpirationSeconds($idleTimeout);

        if (! isset($session->user_role)) {
            $session->user_role = 'nologin';
        }

        // Since userloading can clear the session, we put stuff that should remain (like redirect info)
        // in a different namespace that we call a 'static session', use getStaticSession to access.
        $this->staticSession = new \Zend_Session_Namespace('gems.' . GEMS_PROJECT_NAME . '.sessionStatic');

        return $session;
    }

    /**
     * Initialize the locale.
     *
     * We use this function instead of the standard application.ini setting to
     * simplify overruling the settings.
     *
     * Also Firefox tends to overrule the locale settings.
     *
     * You can overrule this function to specify your own project translation method / file.
     *
     * Use $this->locale to access afterwards
     *
     * @return \Zend_Locale
     */
    protected function _initLocale()
    {
        $this->bootstrap(array('project', 'session'));

        // Get the choosen language
        if (isset($this->session->user_locale)) {
            $localeId = $this->session->user_locale;
            // \MUtil_Echo::r('sess: ' . $localeId);

        } else {
            if (isset($this->project->locale, $this->project->locale['default'])) {
                // As set in project
                $localeId = $this->project->locale['default'];
                // \MUtil_Echo::r('def: ' . $localeId);

            } elseif (isset($this->project->locales)) {
                // First of the locales array.
                $localeId = reset($this->project->locales);
                // \MUtil_Echo::r('locales: ' . $localeId);


            } else {
                // Default.
                $localeId = 'en';
            }

            $this->session->user_locale = $localeId;
        }

        $locale = new \Zend_Locale($localeId);

        \Zend_Registry::set('Zend_Locale', $locale);

        return $locale;
    }

    /**
     * Set the default mailtransport to our own sendmail version
     *
     * This is needed to make sure a correct sender is set when using sendmail.
     * Feel free to set a different default transport method in your project
     * when needed.
     */
    public function _initMailTransport()
    {
        $transport = new \Gems_Mail_Transport_SendMail();
        \Zend_Mail::setDefaultTransport($transport);
    }

    /**
     * Initialize the OpenRosa survey source
     */
    protected function _initOpenRosa()
    {
        $this->bootstrap(array('loader', 'translate'));

        if ($this->getOption('useOpenRosa')) {
            // First handle dependencies
            $this->bootstrap(array('db', 'loader', 'util'));

            $this->getLoader()->addPrefixPath('OpenRosa', GEMS_LIBRARY_DIR . '/classes/OpenRosa', true);

            /**
             * Add Source for OpenRosa
             */
            $tracker = $this->loader->getTracker();
            $tracker->addSourceClasses(array('OpenRosa'=>'OpenRosa form'));
        }
    }

    /**
     * Initialize the translate component.
     *
     * Scans the application and project dirs for available translations
     *
     * Use $this->translate to access afterwards
     * Also sets $this->translateAdapter to access afterwards
     *
     * @return \Zend_Translate
     */
    protected function _initTranslate()
    {
        $this->bootstrap(['event', 'locale']);

        $language = $this->locale->getLanguage();

        /*
         * Scan for files with -<languagecode> and disable notices when the requested
         * language is not found
         */
        $options = [
            'adapter'         => 'gettext',
            'content'         => GEMS_LIBRARY_DIR . '/languages/',
            'disableNotices'  => true,
            'locale'          => $language,
            'scan'            => \Zend_Translate::LOCALE_FILENAME
        ];

        $translate = new \Zend_Translate($options);

        // If we don't find the needed language, use a fake translator to disable notices
        if (! $translate->isAvailable($language)) {
            $translate = \MUtil_Translate_Adapter_Potemkin::create();
        }

        // Add other languages through Event (e.g. Modules)
        $event = new \Gems\Event\Application\ZendTranslateEvent($translate, $language, $options);
        $this->event->dispatch($event, $event::NAME);

        //Now if we have a project specific language file, add it to the event
        $projectLanguageDir = APPLICATION_PATH . '/languages/';
        if (file_exists($projectLanguageDir)) {
            $event->addTranslationByDirectory($projectLanguageDir);
        }
        $translate = $event->getTranslate();

        $translate->setLocale($language);
        \Zend_Registry::set('Zend_Translate', $translate);

        // Fix for _init resource being case insensitive
        $container = $this->getContainer();
        $adapter   = $translate->getAdapter();
        $container->translateAdapter = $adapter;
        $this->translateAdapter      = $adapter;

        return $translate;
    }

    /**
     * Initialize the util component.
     *
     * You can overrule this function to specify your own project translation method / file.
     *
     * Use $this->util to access afterwards
     *
     * @return \Gems_Util
     */
    protected function _initUtil()
    {
        $this->bootstrap(array('basepath', 'loader', 'project'));

        return $this->getLoader()->getUtil();
    }

    /**
     * Initialize the view component and sets some project specific values.
     *
     * Actions taken here can take advantage that the full framework has
     * been activated by now, including session data, etc.
     *
     * Use $this->view to access afterwards
     *
     * @return \Zend_View
     */
    protected function _initView()
    {
        $this->bootstrap('project');

        // Initialize view
        $view = new \Zend_View();
        
        $view->addHelperPath('MUtil/View/Helper', 'MUtil_View_Helper');
        $view->addHelperPath('MUtil/Less/View/Helper', 'MUtil_Less_View_Helper');
        $view->addHelperPath('Gems/View/Helper', 'Gems_View_Helper');
        $view->addScriptPath(GEMS_LIBRARY_DIR . '/views/scripts');
        $view->headTitle($this->project->getName());
        $view->setEncoding('UTF-8');

        $metas    = $this->project->getMetaHeaders();
        $headMeta = $view->headMeta();
        foreach ($metas as $httpEquiv => $content) {
            $headMeta->appendHttpEquiv($httpEquiv, $content);
        }

        if ($this->useHtml5) {
            $view->doctype(\Zend_View_Helper_Doctype::HTML5);
        } else {
            $view->doctype(\Zend_View_Helper_Doctype::XHTML1_STRICT);
        }

        // Generate a nonce for script tags
        if ($this->project->hasNonce()) {
            Javascript::generateNonce();
        }
        // Make sure the variable is set (if Javascript::generateNonce() this will return an empty string)
        $view->nonceString = Javascript::getNonceAttributeString();

        // Add it to the ViewRenderer
        $viewRenderer = \Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
        $viewRenderer->setView($view);

        // Return it, so that it can be stored by the bootstrap
        return $view;
    }

    /**
     * Initialize the currentUser component.
     *
     * You can overrule this function to specify your own project translation method / file.
     *
     * Use $this->currentUser to access afterwards
     *
     * @return \Gems_User_User
     */
    protected function _initCurrentUser()
    {
        $this->bootstrap(array('acl', 'basepath', 'cache', 'db', 'loader', 'project', 'session', 'translate', 'util'));

        // Fix for _init resourcea being case insensitive
        $container = $this->getContainer();
        $user      = $this->loader->getCurrentUser();
        $container->currentUser = $user;

        return $user;
    }

    /**
     * Initialize the loader as source component.
     *
     * You can overrule this function to specify your own project translation method / file.
     *
     * Use $this->source to access afterwards
     *
     * @return \Gems_Loader
     */
    protected function _initSource()
    {
        $this->bootstrap(array('loader'));

        return $this->getLoader();
    }

    /**
     * Add ZFDebug info to the page output.
     *
     * @return void
     **/
    protected function _initZFDebug()
    {
        if ((APPLICATION_ENV === 'production') || (APPLICATION_ENV === 'acceptance') || Zend_Session::$_unitTestEnabled ) {
            // Never on on production systems
            return;
        }

        $debug = $this->getOption('zfdebug');
        if (! isset($debug['activate']) || ('1' !== $debug['activate'])) {
            // Only turn on when activated
            return;
        }

        # Instantiate the database adapter and cache
        $this->bootstrap('db');
        $db = $this->getPluginResource('db');

        $this->bootstrap('cache');
        $cache = $this->cache;

        $options = array(
            'plugins' => array('Variables',
                'Database' => array('adapter' => $db->getDbAdapter()),
                'File'     => array('basePath' => GEMS_ROOT_DIR),
                'Cache'    => array('backend' => $cache->getBackend()),
                'Exception')
        );

        $debugPlugin = new \ZFDebug_Controller_Plugin_Debug($options);

        $this->bootstrap('frontController');
        $frontController = $this->getResource('frontController');
        $frontController->registerPlugin($debugPlugin);
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutContact(array $args = null)
    {
        if ($this->menu instanceof \Gems_Menu) {
            $menuItem = $this->menu->find(array('controller' => 'contact', 'action' => 'index'));

            if ($menuItem) {
                $contactDiv = \MUtil_Html::create()->div(
                    $args,
                    array('id' => 'contact')
                );  // tooltip
                $contactDiv->a($menuItem->toHRefAttribute(), $menuItem->get('label'));

                // List may be empty
                if ($ul = $menuItem->toUl()) {
                    $ul->class = 'dropdownContent tooltip';
                    $contactDiv->append($ul);
                }

                return $contactDiv;
            }
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutCrumbs(array $args = null)
    {
        // Must be called after _layoutNavigation()

        if ($this->menu && $this->menu->isVisible()) {
            $path = $this->menu->getActivePath($this->request);
            $last = array_pop($path);

            if ($path && (isset($args['hideTop']) && $args['hideTop'])) {
                array_shift($path);
            }
            // Only display when there is a path of more than one step or always is on
            if ($path || (isset($args['always']) && $args['always'])) {
                // Never needed from now on
                unset($args['always'], $args['hideTop']);

                if (isset($args['tag'])) {
                    $tag = $args['tag'];
                    unset($args['tag']);
                } else {
                    $tag = 'div';
                }

                $source = array($this->menu->getParameterSource(), $this->request);

                if ($this->useBootstrap && !isset($args['tag'])) {
                    $div = \MUtil_Html::create('ol', $args + array('id' => 'crumbs', 'class' => 'breadcrumb'));

                    foreach ($path as $menuItem) {
                        $div->li()->a($menuItem->toHRefAttribute($source), $menuItem->get('label'));
                    }

                    if ($last) {
                        $div->li(array('class' => 'active'))->append($last->get('label'));
                    }

                } else {
                    $div = \MUtil_Html::create($tag, $args + array('id' => 'crumbs'));

                    $content = $div->seq();
                    $content->setGlue(\MUtil_Html::raw($this->_(' > ')));
                    // Add request to existing menu parameter sources

                    foreach ($path as $menuItem) {
                        $content->a($menuItem->toHRefAttribute($source), $menuItem->get('label'));
                    }

                    if ($last) {
                        $content->append($last->get('label'));
                    }
                }

                return $div;
            }
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutCss()
    {
        // Set CSS stylescheet(s)
        $projectCss = isset($this->project->css) ? (array) $this->project->css : [];
        $projectCss = array_reverse($projectCss);

        foreach ($projectCss as $css) {
            if (is_array($css)) {
                $media = $css['media'];
                $url = $css['url'];
            } else {
                $url = $css;
                $media = 'all';
            }
            // When exporting to pdf, we need full urls
            if (substr($url,0,4) == 'http') {
                $this->view->headLink()->prependStylesheet($url, $media);
            } else {
                $this->view->headLink()->prependStylesheet($this->view->serverUrl() . $this->basepath->getBasePath() . '/' . $url, $media);
            }
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutEnvironment(array $args = null)
    {
        $env = $this->getEnvironmentTitle();
        if (! $env) {
            return null;
        }

        if (isset($args['tagName'])) {
            $tagName = $args['tagName'];
            unset($args['tagName']);
        } else {
            $tagName = 'h1';
        }

        return \MUtil_Html::create($tagName, $env, $args);
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutFavicon()
    {
        // FAVICON
        $icon = isset($this->project->favicon) ? $this->project->favicon : 'favicon.ico';
        if (file_exists(GEMS_WEB_DIR . '/' . $icon)) {
            $this->view->headLink(
                array(
                    'rel' => 'shortcut icon',
                    'href' =>  $this->basepath->getBasePath() . '/' . $icon,
                    'type' => 'image/x-icon'
                    ),
                \Zend_View_Helper_Placeholder_Container_Abstract::PREPEND
                );
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutGroupSwitcher(array $args = null)
    {
        if ($this->currentUser->isActive() && $this->currentUser->hasPrivilege('pr.group.switch', false)) {
            $groups = $this->currentUser->getAllowedStaffGroups(false);

            if (count($groups) > 1) {
                // Group switcher
                return $this->getUiSwitcher($groups, $this->currentUser->getGroupId(), 'groups', 'group', 'group', $args);
            }
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutJQuery()
    {
        // JQUERY
        if (\MUtil_JQuery::usesJQuery($this->view)) {
            $jquery = $this->view->jQuery();
            $jquery->uiEnable(); // enable user interface

            $jqueryCss = isset($this->project->jquerycss) ? (array) $this->project->jquerycss : [];

            foreach ($jqueryCss as $css) {
                $jquery->addStylesheet($this->basepath->getBasePath() . '/' . $css);
            }

            return true;
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutLocaleSet(array $args = null)
    {
        // LOCALE
        $currentUri = base64_encode($this->view->url());
        $localeDiv = \MUtil_Html::create('div', $args, array('id' => 'languages'));

        // There will always be a localeDiv, but it can be empty
        if (isset($this->project->locales)) {
            foreach ($this->project->locales as $locale) {
                if ($locale == $this->view->locale) {
                    $localeDiv->span(strtoupper($locale), array('class' => 'language ' . $locale));
                } else {
                    $localeDiv->a(
                        array(
                            'controller' => 'language',
                            'action' => 'change-ui',
                            'language' => urlencode($locale),
                            'current_uri' => $currentUri,
                            'class' => ''
                        ),
                        strtoupper($locale),
                        array(
                            'class' => 'language ' . $locale,
                            'rel' => 'nofollow'
                        )
                    );
                }
                $localeDiv[] = ' ';
            }
        }
        return $localeDiv;
    }

    /**
     * Display either a link to the login screen or displays the name of the current user
     * and a logoff link.
     *
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutLogin(array $args = null)
    {
        // During error reporting the user or menu are not always known.
        if ($this->currentUser && $this->menu) {
            $div = \MUtil_Html::create('div', array('id' => 'login'), $args);

            $p = $div->p();
            if ($this->currentUser->isActive()) {
                $p->append(sprintf($this->_('You are logged in as %s'), $this->currentUser->getFullName()));
                $item = $this->menu->findController('index', 'logoff');
                $p->a($item->toHRefAttribute(), $this->_('Logoff'), array('class' => 'logout'));
                $item->set('visible', false);
            } else {
                $item = $this->menu->findController('index', 'login');
                $p->a($item->toHRefAttribute(), $this->_('You are not logged in'), array('class' => 'logout'));
            }

            return $div;
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutMenuActiveBranch()
    {
        // ACL && Menu
        if ($this->menu && $this->menu->isVisible()) {
            return $this->menu->toActiveBranchElement();
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutMenuHtml()
    {
        // ACL && Menu
        if ($this->menu && $this->menu->isVisible()) {

            // Make sure the actual $request and $controller in use at the end
            // of the dispatchloop is used and make \Zend_Navigation object
            return $this->menu->render($this->view);
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutMenuTopLevel()
    {
        // ACL && Menu
        if ($this->menu && $this->menu->isVisible()) {
            return $this->menu->toTopLevelElement();
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutMessages()
    {
        // Do not trust $messenger being set in the view,
        // after a reroute we have to reinitiate te $messenger.
        $messenger = $this->getMessenger();
        return $messenger->showMessages();
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutNavigation()
    {
        // ACL && Menu
        if ($this->menu && $this->menu->isVisible()) {

            // Make sure the actual $request and $controller in use at the end
            // of the dispatchloop is used and make \Zend_Navigation object
            $nav = $this->menu->toZendNavigation($this->request, $this->controller);

            // Set the navigation object
            \Zend_Registry::set('Zend_Navigation', $nav);

            $zendNav = $this->view->navigation();
            // $zendNav->setAcl($this->acl);  // Not needed with \Gems_Menu
            // $zendNav->setRole($this->session->user_role); // is set to nologin when no user
            $zendNav->setUseTranslator(false);

            // Other options
            // $zendNav->breadcrumbs()->setLinkLast(true);
            // $zendNav->breadcrumbs()->setMaxDepth(1);
            // $zendNav->menu()->setOnlyActiveBranch(true);

            return true;
        }

        return false;
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutOrganizationName(array $args = null)
    {
        if (isset($args['tagName'])) {
            $tagName = $args['tagName'];
            unset($args['tagName']);
        } else {
            $tagName = 'h1';
        }
        $org = $this->currentUser->getCurrentOrganization();

        if (! $org->getId()) {
            $org = $this->loader->getOrganization();
        }
        if ($org->getId()) {
            $name = $org->getName();
        } else {
            $name = $this->project->getDescription();
        }
        if (isset($args['addEnv']) && $args['addEnv']) {
            $env = $this->getEnvironmentTitle();
            if ($env) {
                $name .= sprintf(' [%s]', $env);
            }
        }
        unset($args['addEnv']);
        return \MUtil_Html::create($tagName, $name, $args);
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutOrganizationSwitcher(array $args = null)
    {
        if ($this->currentUser->isActive() && ($orgs = $this->currentUser->getAllowedOrganizations())) {
            if (count($orgs) > 1) {
                // Organization switcher
                return $this->getUiSwitcher($orgs, $this->currentUser->getCurrentOrganizationId(), 'organizations', 'org', 'organization', $args);
            }
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutProjectName(array $args = null)
    {
        if (isset($args['tagName'])) {
            $tagName = $args['tagName'];
            unset($args['tagName']);
        } else {
            $tagName = 'h1';
        }
        $name = $this->project->name;
        if (isset($args['addEnv']) && $args['addEnv']) {
            $env = $this->getEnvironmentTitle();
            if ($env) {
                $name .= sprintf(' [%s]', $env);
            }
        }
        unset($args['addEnv']);
        return \MUtil_Html::create($tagName, $name, $args);
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutTime(array $args = null)
    {
        return \MUtil_Html::create()->div(date('d-m-Y H:i:s'), $args, array('id' => 'time'));
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutTitle(array $args = null)
    {
        if (is_array($args) && array_key_exists('separator', $args)) {
            $separator = $args['separator'];
        } else {
            $separator = ' - ';
        }

        if ($this->controller instanceof \MUtil_Controller_Action) {
            if ($title = $this->controller->getTitle($separator)) {
                $this->view->headTitle($separator . $title);
            }
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutUser(array $args = null)
    {
        if ($this->currentUser->isActive()) {
            return \MUtil_Html::create()->div(
                sprintf($this->_('User: %s'), $this->currentUser->getFullName()),
                $args,
                array('id' => 'username')
                );
        }
    }

    /**
     * Function called if specified in the Project.ini layoutPrepare section before
     * the layout is drawn, but after the rest of the program has run it's course.
     *
     * @return mixed If null nothing is set, otherwise the name of
     * the function is used as \Zend_View variable name.
     */
    protected function _layoutVersion(array $args = null)
    {
        $div = \MUtil_Html::create()->div($args, array('id' => 'version'));
        if ($this->currentUser->isActive()) {
            $version = $this->loader->getVersions()->getVersion();
        } else {
            $version = $this->loader->getVersions()->getMainVersion();
        }
        if (($this->menu instanceof \Gems_Menu) &&
                ($item = $this->menu->findController('project-information', 'changelog')->toHRefAttribute())) {
            $link = \MUtil_Html::create()->a($version, $item);
        } else {
            $link = $version;
        }

        $this->view->currentVersion = $this->loader->getVersions()->getProjectVersion();

        $div->spaced($this->project->description, $this->translateAdapter->_('version'), $link);

        return $div;
    }


    /**
     * Function to maintain uniformity of access to variables from the bootstrap object.
     * Updates selected variable(s) to the objects targeted in _copyVariables.
     *
     * Do this when an object is created or when a non-object variable has changed.
     * You do not need to call this method for changes to objects .
     *
     * @param String|Array $name A property name or array of property names to copy from this
     * object to the previous copy targets.
     * @return void
     */
    protected function _updateVariable($name)
    {
        if (!$this->_copyDestinations) {
            return;
        }
        $names = (array) $name;

        foreach ($this->_copyDestinations as $object) {
            foreach ($names as $key) {
                $object->$key = $this->_container->$key;
            }
        }
    }

    /**
     * Adds one or more messages to the session based message store.
     *
     * @param mixed ...$messages Can be an array or multiple argements. Each sub element is a single message string
     * @return \MUtil_Controller_Action
     */
    public function addMessage()
    {
        $messages  = \MUtil_Ra::flatten(func_get_args());
        $messenger = $this->getMessenger();

        foreach ($messages as $message) {
            $messenger->addMessage($message);
        }

        return $this;
    }

    /**
     * Hook 2: Called in $this->run().
     *
     * This->init() has ran and the constructor has finisched so all _init{name} and application.ini
     * resources have been loaded. The code between the constructor and the call to $this->run() has
     * been executed in $this->run() has hooked $this as both a \Zend_Controller_Plugin and a
     * \Zend_Controller_Action_Helper.
     *
     * Not initialized are the $request, $response and $controller objects.
     *
     * Previous hook: init()
     * Actions since: $this->_inti{Name}; resources from configuration initialized
     * Actions after: $this->request object created
     * Next hook: requestChanged()
     *
     * @return void
     */
    public function beforeRun()
    {
        $this->_copyVariables($this->view);
    }


    /**
     * Hook 10: Called before the $controller->preDispatch() and $controller->{name}Action
     * methods have been called.
     *
     * Here you can change or check all values set in $controller->init(). All output echoed
     * here is captured for the output.
     *
     * Previous hook: controllerInit()
     * Actions since: $controller->init(); ob_start(); $controller->dispatch()
     * Actions after: $controller->preDispatch(); $controller->{name}Action(); $controller->postDispatch()
     * Next hook: controllerAfterAction()
     *
     * @param \Zend_Controller_Action $actionController
     * @return void
     */
    public function controllerBeforeAction(\Zend_Controller_Action $actionController = null)
    {
        // Test for Zend_Session::$_unitTestEnabled: db is not yet loaded in tests
        if (method_exists($actionController, 'getRespondent') && (!\Zend_Session::$_unitTestEnabled)) {
            $this->accesslog->logRequest($this->request, array(), null, $actionController->getRespondent());
        } else {
            $this->accesslog->logRequest($this->request, array());
        }
    }

    /**
     * Hook 9: During action controller initialization.
     *
     * This hook is called in the constructor of the controller. Nothing is done and
     * $controller->init has not been called, so this is a good moment to change settings
     * that should influence $controller->init().
     *
     * Previous hook: preDispatch()
     * Actions since: $dispatcher->dispatch(); $controller->__construct()
     * Actions after: $controller->init(); ob_start(); $controller->dispatch()
     * Next hook: controllerBeforeAction()
     *
     * @param \Zend_Controller_Action $actionController
     * @return void
     */
    public function controllerInit(\Zend_Controller_Action $actionController = null)
    {
        $this->_copyVariables($actionController ? $actionController : $this->controllerAfterAction);

        $this->prepareController();

        $imgUrl = $this->getUtil()->getImageUri('datepicker.png');
        $jstUrl = $this->basepath->getBasePath() . '/gems/js';

        // Now set some defaults
        $dateFormOptions['dateFormat']   = 'dd-MM-yyyy';
        $dateFormOptions['description']  = $this->_('dd-mm-yyyy');
        $dateFormOptions['size']         =  10;

        if ($this->useBootstrap == true) {
            // Do not use a buttonImage, since we will use bootstrap add-on
            $basicOptions = array();
        } else {
            $basicOptions = array(
                'buttonImage' => $imgUrl,
                'showOn'      => 'button'
            );
        }

        $dateFormOptions['jQueryParams'] = $basicOptions + array(
            'changeMonth' => true,
            'changeYear'  => true,
            'duration'    => 'fast',
        );
        $datetimeFormOptions['dateFormat']   = 'dd-MM-yyyy HH:mm';
        $datetimeFormOptions['description']  = $this->_('dd-mm-yyyy hh:mm');
        $datetimeFormOptions['size']         = 16;
        $datetimeFormOptions['jQueryParams'] = $basicOptions + array(
            'changeMonth' => true,
            'changeYear'  => true,
            'duration'    => 'fast',
            'stepMinute'  => 5,
            'size'        => 8,
            'timeJsUrl'   => $jstUrl,
        );

        $timeFormOptions['dateFormat']   = 'HH:mm';
        $timeFormOptions['description']  = $this->_('hh:mm');
        $timeFormOptions['jQueryParams'] = $basicOptions + array(
            'duration'    => 'fast',
            'stepMinute'  => 5,
            'size'        => 8,
            'timeJsUrl'   => $jstUrl,
        );

        \MUtil_Model_Bridge_FormBridge::setFixedOptions(array(
            'date'     => $dateFormOptions,
            'datetime' => $datetimeFormOptions,
            'time'     => $timeFormOptions,
            ));
    }

    /**
     * Creates an object of the specified className seareching the loader dirs path
     *
     * @param string $className
     * @param mixed ...$arguments Optional parameters
     * @return object
     */
    protected function createProjectClass($className)
    {
        $arguments = func_get_args();
        array_shift($arguments);

        return $this->_projectLoader->createClass($className, $arguments);
    }

    /**
     * Hook 7: Called before \Zend_Controller_Front enters its dispatch loop.
     *
     * This events enables you to adjust the request after the routing has been done.
     *
     * This is the final hook before the dispatchLoop starts. All the hooks in the dispatchLoop
     * can be executed more then once.
     *
     * Not yet initialized is the $controller object - as the $controller can change during
     * the dispatchLoop.
     *
     * Previous hook: routeShutdown()
     * Actions since: nothing, but the route consisting of controller, action and module should now be fixed
     * Actions after: dispatch loop started
     * Next hook: preDispatch()
     *
     * @param  \Zend_Controller_Request_Abstract $request
     * @return void
     */
    public function dispatchLoopStartup(\Zend_Controller_Request_Abstract $request)
    {
        // Check the installation
        if (! isset($this->db)) {
            $this->setException(new \Gems_Exception_Coding(
                    'No database registered in ' . GEMS_PROJECT_NAME . 'Application.ini for key resources.db.')
                    );
        }

        if (($request instanceof \Zend_Controller_Request_Http) && $request->isPost()) {
            $posts = $request->getPost();
        } else {
            $posts = [];
        }

        $this->_checkParameters($request->getParams(), $posts);

        // Empty params are filtered from request, only saved when using special router
        $router = Zend_Controller_Front::getInstance()->getRouter();
        if ($router instanceof Rewrite) {
            $this->_checkParameters($router->getAllParams(), $posts);
        }
    }

    private function findExtension($fullFileName, array $extensions)
    {
        foreach ($extensions as $extension) {
            if (file_exists($fullFileName . '.' . $extension)) {
                return $extension;
            }
        }
    }

    /**
     * Return the directories where the Database Administrator Model (DbaModel)
     * should look for sql creation files.
     *
     * @return array Of index => array('path' =>, 'name' =>, 'db' =>,)
     */
    public function getDatabasePaths()
    {
        $paths = [];

        $path = APPLICATION_PATH . '/configs/db';
        if (file_exists($path)) {
            $paths[] = array(
                'path' => $path,
                'name' => GEMS_PROJECT_NAME,
                'db'   => $this->db,
                );
        }

        $event = new \Gems\Event\Application\GetDatabasePaths($this->db, $paths);
        $this->event->dispatch($event, $event::NAME);
        $paths = $event->getPaths();

        $path = GEMS_LIBRARY_DIR . '/configs/db';
        if (file_exists($path)) {
            $paths[] = array(
                'path' => $path,
                'name' => 'gems',
                'db'   => $this->db,
                );
        }

        if ($this->project->hasResponseDatabase()) {
            $path = GEMS_LIBRARY_DIR . '/configs/db_response_data';
            if (file_exists($path)) {
                $paths[] = array(
                    'path' => $path,
                    'name' => 'gemsdata',
                    'db'   => $this->project->getResponseDatabase(),
                    );
            }
        }

        return $paths;
    }

    /**
     * Get the title for the environment
     *
     * @return string
     */
    public function getEnvironmentTitle()
    {
        switch (APPLICATION_ENV) {
            case 'production':
                return null;

            case 'acceptance':
                return 'ACC';

            case 'testing':
                return 'TEST';

            case 'demo':
                return 'DEMO';

            case 'development':
                return 'DEV';

            default:
                return strtoupper(substr(APPLICATION_ENV, 0 , 4));

        }
    }

    /**
     * Retrieve the GemsEscort object
     *
     * @return GemsEscort
     */
    public static function getInstance()
    {
        return self::$_instanceOfSelf;
    }

    /**
     * Type access to $this->loader
     *
     * @return \Gems_Loader Or a subclassed version when specified in the project code
     */
    public function getLoader()
    {
        return $this->loader;
    }

    /**
     * The prefix / directory paths where the Gems Loaders should look
     *
     * @return array
     */
    public function getLoaderDirs()
    {
        return $this->_loaderDirs;
    }

    /**
     * Retrieves / sets the messenger
     *
     * @return \Zend_Controller_Action_Helper_FlashMessenger
     */
    public function getMessenger()
    {
        if (! isset($this->view->messenger)) {
            $this->view->messenger = $this->loader->getMessenger();
        }
        return $this->view->messenger;
    }

    public function getModules()
    {
        if (!$this->moduleSettings && static::$modules) {
            $settings = [];
            foreach(static::$modules as $name=>$settingsClass) {
                if (class_exists($settingsClass)) {
                    $settings[$name] = $settingsClass;
                }
            }
            $this->moduleSettings = $settings;
        }
        return $this->moduleSettings;
    }

    /**
     *
     * @param array $items Array of id=>item
     * @param string $currentId Id of currently selected item
     * @param string $elementId Id to set on the returned element
     * @param string $elementName The name of the select element
     * @param string $controller The controller to redirect to
     * @param array $args Optional list of arguments
     * @param string $action Optional action
     * @return \MUTil_Html
     */
    protected function getUiSwitcher($items, $currentId, $elementId, $elementName, $controller, array $args = null, $action = 'change-ui')
    {
        $uiSwitch  = \MUtil_Html::create('div', $args, array('id' => $elementId));
        $params    = $this->request->getparams();
        unset($params['error_handler']);    // If present, this is an object and causes a warning
        unset($params[\MUtil_Model::AUTOSEARCH_RESET]);
        if ($this->request instanceof \Zend_Controller_Request_Http) {
            // Use only get params, not post as it is an url
            $params = array_diff_key($params, $this->request->getPost());
        }

        $currentUri = $this->view->url($params, null, true);
        $url        = $this->view->url(array('controller' => $controller, 'action' => $action), null, false);

        $formDiv = $uiSwitch->form(array('method' => 'get', 'action' => $url))->div();
        $formDiv->input([
            'type'  => "hidden",
            'name'  => "current_uri",
            'value' => base64_encode($currentUri)
        ]);

        $select = $formDiv->select([
            'class'    => 'form-control',
            'name'     => $elementName,
            'onchange' => "javascript:this.form.submit();",
        ]);
        foreach ($items as $elementId => $name) {
            $selected = '';
            if ($elementId == $currentId) {
                $selected = array('selected' => "selected");
            }
            $select->option(array('value' => $elementId), $name, $selected);
        }

        return $uiSwitch;
    }

    /**
     * Type access to $this->util
     *
     * @return \Gems_Util Or a subclassed version when specified in the project code
     */
    public function getUtil()
    {
        return $this->util;
    }

    /**
     * Searches and loads ini, xml, php or inc file
     *
     * When no extension is specified the system looks for a file with the right extension,
     * in the order: .ini, .php, .xml, .inc.
     *
     * .php and .inc files run within the context of this object and thus can access all
     * $this-> variables and functions.
     *
     * @param string $fileName A filename in the include path
     * @return mixed false if nothing was returned
     */
    protected function includeFile($fileName)
    {
        $extension = pathinfo($fileName, PATHINFO_EXTENSION);

        if (! $extension) {
            $extension = $this->findExtension($fileName, array('inc', 'ini', 'php', 'xml'));
            $fileName .= '.' . $extension;
        }

        if (file_exists($fileName)) {
            switch ($extension) {
                case 'ini':
                    $config = new \Zend_Config_Ini($fileName, APPLICATION_ENV);
                    break;

                case 'xml':
                    $config = new \Zend_Config_Xml($fileName, APPLICATION_ENV);
                    break;

                case 'php':
                case 'inc':
                    // Exclude all variables not needed
                    unset($extension);

                    // All variables from this Escort file can be changed in the include file.
                    return include($fileName);
                    break;

                default:
                    throw new \Zend_Application_Exception(
                            'Invalid configuration file provided; unknown config type ' . $extension
                            );

            }

            return $config->toArray();
        }

        // If the file does not exists it is up to the calling function to do something about it.
        return false;
    }

    /**
     * Is the host name one allowed by the system
     *
     * @param string $fullHost
     * @return boolean
     * @deprecated since version 1.9.1 replaced by SiteUrl->isRequestFromAllowedHost
     */
    public function isAllowedHost($fullHost)
    {
        return true;
        
//        $host = \MUtil_String::stripToHost($fullHost);
//        $request = $this->request;
//        if ($request instanceof \Zend_Controller_Request_Http) {
//            if ($host == \MUtil_String::stripToHost($request->getServer('HTTP_HOST'))) {
//                return true;
//            }
//        }
//        if (isset($this->project)) {
//            foreach ($this->project->getAllowedHosts() as $allowedHost) {
//                if ($host == \MUtil_String::stripToHost($allowedHost)) {
//                    return true;
//                }
//            }
//        }
//        $loader = $this->getLoader();
//        foreach ($loader->getUserLoader()->getOrganizationUrls() as $url => $orgId) {
//            if ($host == \MUtil_String::stripToHost($url)) {
//                return true;
//            }
//        }
//
//        return false;
    }

    /**
     * Generate random password
     * @return string
     */
    public function getRandomPassword()
    {
        $salt = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ0123456789";
        $pass = "";

        srand((double)microtime()*1000000);

        $i = 0;

        while ($i <= 7)
        {
            $num = rand() % strlen($salt);
            $tmp = substr($salt, $num, 1);
            $pass = $pass . $tmp;
            $i++;
        }

        return $pass;
    }

    /**
     * Returns a static session, that will not be affected by loading or unloading a user
     *
     * @return \Zend_Session_Namespace
     */
    public function getStaticSession()
    {
        return $this->staticSession;
    }

    /**
     * Hook 12: Called after an action is dispatched by \Zend_Controller_Dispatcher.
     *
     * This callback allows for proxy or filter behavior. By altering the
     * request and resetting its dispatched flag (via {@link
     * \Zend_Controller_Request_Abstract::setDispatched() setDispatched(false)}),
     * a new action may be specified for dispatching.
     *
     * \Zend_Layout_Controller_Plugin_Layout uses this event to change the output
     * of the $response with the rendering of the layout. As the Layout plugin
     * has a priority of 99, this Escort event will take place before the layout
     * is rendered, unless $this->run() was called with a stackIndex lower than zero.
     *
     * Previous hook: controllerAfterAction()
     * Actions since: ob_get_clean(); $response->appendBody()
     * Actions after: while (! Request->isDispatched()) or back to Hook 8 preDispatch()
     * Next hook: dispatchLoopShutdown()
     *
     * @param  \Zend_Controller_Request_Abstract $request
     * @return void
     */
    public function postDispatch(\Zend_Controller_Request_Abstract $request)
    {
        if ($request->isDispatched()) {
            $headers = $this->project->getResponseHeaders();
            foreach ($headers as $name => $value) {
                // check if nonce should be added to the CSP header
                if ($name == 'Content-Security-Policy' && strpos($value, '$scriptNonce') !== false) {
                    if (Javascript::$scriptNonce) {
                        $value = str_replace('$scriptNonce', Javascript::$scriptNonce, $value);
                    } else {
                        throw new Gems_Exception_Security('script nonce set in headers but no nonce generated');
                    }
                } // */
                $this->response->setHeader($name, $value, true);
            }

            // Only when we need to render the layout, we run the layout prepare
            if (\Zend_Controller_Action_HelperBroker::hasHelper('layout') &&
                    \Zend_Controller_Action_HelperBroker::getExistingHelper('layout')->isEnabled()) {

                // Per project layout preparation
                $layoutFuncs = isset($this->project->layoutPrepare) ? $this->project->layoutPrepare : [];
                $layoutArgs  = isset($this->project->layoutPrepareArgs) ? $this->project->layoutPrepareArgs : [];
                $this->prepareLayout($layoutFuncs, $layoutArgs);
            }

            // For AJAX calls we sometimes need to add JQuery onload scripts since otherwise they won't get rendered:
            // We expect JQuery to be loaded in the master page, since the call is probably made using JQuery
            if ($request instanceof \Zend_Controller_Request_Http && $request->isXmlHttpRequest()) {
                \MUtil_JQuery::enableView($this->view);
                $scripts = $this->view->jQuery()->getOnLoadActions();
                $content = '';
                $nonceString = Javascript::getNonceAttributeString();
                foreach($scripts as $script) {
                    $content .= "<script type='text/javascript' $nonceString>$script</script>\n";
                }
                $content .= $this->view->inlineScript();

                // Now cleanup the rendered content (just to make sure)
                $this->view->jQuery()->clearOnLoadActions();
                $this->view->inlineScript()->exchangeArray(array());
                if (!empty($content)) {
                    $this->response->appendBody($content);
                }
            }
        }
    }

    /**
     * Copy from \Zend_Translate_Adapter
     *
     * Translates the given string using plural notations
     * Returns the translated string
     *
     * @see \Zend_Locale
     * @param  string             $singular Singular translation string
     * @param  string             $plural   Plural translation string
     * @param  integer            $number   Number for detecting the correct plural
     * @param  string|\Zend_Locale $locale   (Optional) Locale/Language to use, identical with
     *                                      locale identifier, @see \Zend_Locale for more information
     * @return string
     */
    public function plural($singular, $plural, $number, $locale = null)
    {
        $args = func_get_args();
        return call_user_func_array(array($this->translateAdapter, 'plural'), $args);
    }

    /**
     * Hook 8: Start of dispatchLoop. Called before an action is dispatched
     * by \Zend_Controller_Dispatcher.
     *
     * This callback allows for proxy or filter behavior. By altering the request
     * and resetting its dispatched flag (via {@link \Zend_Controller_Request_Abstract::setDispatched()
     * setDispatched(false)}), the current action may be skipped.
     *
     * Not yet initialized is the $controller object - as the $controller can change during
     * the dispatchLoop.
     *
     * Previous hook: dispatchLoopStartup() or new loop
     * Actions since: dispatch loop started
     * Actions after: $dispatcher->dispatch(); $controller->__construct()
     * Next hook: controllerInit()
     *
     * @param  \Zend_Controller_Request_Abstract $request
     * @return void
     */
    public function preDispatch(\Zend_Controller_Request_Abstract $request)
    {
        if ($request instanceof \Zend_Controller_Request_Http && $request->isXmlHttpRequest()) {
            $mvc = \Zend_Layout::getMvcInstance();

            if ($mvc instanceof \Zend_Layout) {
                $mvc->disableLayout();
            }
        }
        $staticSession = $this->getStaticSession();
        if ($this->session->user_id && $previousRequestParameters = $staticSession->previousRequestParameters) {
            unset($previousRequestParameters['save_button']);
            unset($previousRequestParameters['userlogin']);
            unset($previousRequestParameters['password']);

            // fake POST
            if ($staticSession->previousRequestMode == 'POST') {
                if (isset($staticSession->previousRequestMessage)) {
                    $this->addMessage($staticSession->previousRequestMessage);
                } else {
                    $this->addMessage($this->_(
                        'Take note: your session has expired, your inputs were not saved. Please check the input data and try again'
                    ));
                }
                $_POST = $previousRequestParameters;
                $_SERVER['REQUEST_METHOD'] = $staticSession->previousRequestMode;
                $staticSession->previousRequestMode = null;
            }

            $staticSession->previousRequestParameters = null;
        }

        $this->setControllerDirectory($request);
    }

    /**
     * Hook function called during controllerInit
     *
     * return @void
     */
    public function prepareController()
    {
        // Do the layout switching here, when view is set the layout can still be changed, but
        // Bootstrap can no longer be switched on/off
        $this->currentUser->applyLayoutSettings($this);

        if ($this->useBootstrap) {
            $bootstrap = \MUtil_Bootstrap::bootstrap(array('fontawesome' => true));
            \MUtil_Bootstrap::enableView($this->view);
        }

        if (\MUtil_Console::isConsole()) {
            /* @var $layout \Zend_Layout */
            $layout = $this->view->layout();

            $layout->setLayoutPath(GEMS_LIBRARY_DIR . "/layouts/scripts");
            $layout->setLayout('cli');
        }
    }

    /**
     *
     * @param array $layoutFuncs
     * @param array $layoutArgs
     */
    protected function prepareLayout($layoutFuncs, $layoutArgs)
    {
        foreach ($layoutFuncs as $prepare => $type) {
            if (!$type) {
                continue;
            }
            $function = '_layout' . ucfirst($prepare);
            $args     = isset($layoutArgs[$prepare]) ? $layoutArgs[$prepare] : [];
            $result   = $this->$function($args);

            // When a result is returned, add it to the view,
            // according to the type method
            if (is_null($result)) {
                continue;
            }
            if (is_numeric($type)) {
                $this->view->$prepare = $result;
            } else {
                if (!isset($this->view->$type)) {
                    $this->view->$type = new \MUtil_Html_Sequence();
                }
                $sequence           = $this->view->$type;
                $sequence[$prepare] = $result;
            }
        }
    }

    /**
     * Hook 3: Called in $this->setRequest.
     *
     * All resources have been loaded and the $request object is created.
     * Theoretically this event can be triggered multiple times, but this does
     * not happen in a standard Zend application.
     *
     * Not initialized are the $response and $controller objects.
     *
     * Previous hook: beforeRun()
     * Actions since: $this->request object created
     * Actions after: $this->response object created
     * Next hook: responseChanged()
     *
     * @param \Zend_Controller_Request_Abstract $request
     * @return void
     */
    public function requestChanged(\Zend_Controller_Request_Abstract $request)
    {
        if ($this->project->isMultiLocale()) {
            // Get the choosen language
            $localeId = \Gems_Cookies::getLocale($request);
            if (! $localeId) {
                $site = $this->getUtil()->getSites()->getSiteForCurrentUrl();
                if ($site) {
                    $localeId = $site->getLocale();
                }
            }

            // Change when $localeId exists and is different from session
            if ($localeId && ($this->locale->getLanguage() !== $localeId)) {
                // \MUtil_Echo::r('On cookie ' . $localeId . ' <> ' . $this->locale->getLanguage());

                // Does the locale exist?
                if (isset($this->project->locales[$localeId])) {

                    // Add and implement the choosen locale
                    $this->session->user_locale = $localeId;
                    $this->locale->setLocale($localeId);
                    if (! $this->translate->isAvailable($localeId)) {
                        $languageFilename = APPLICATION_PATH . '/languages/default-' . $localeId . '.mo';
                        if (file_exists($languageFilename)) {
                            $this->translate->addTranslation($languageFilename, $localeId);
                        }
                    }
                    $this->translate->setLocale($localeId);
                    $this->translateAdapter = $this->translate->getAdapter();
                }
            }
        }

        // Set the base path, the route is now fixed
        $this->basepath->setBasePath($request->getBasePath());

        // Set the jQuery version and other information needed
        // by classes using jQuery
        $jquery = \MUtil_JQuery::jQuery();

        $jquery->setVersion($this->jqueryVersionNr);
        $jquery->setUiVersion($this->jqueryUiVersionNr);

        if ($this->project->isJQueryLocal()) {
            $jqueryDir = $request->getBasePath() . $this->project->getJQueryLocal();

            $jquery->setLocalPath($jqueryDir . 'jquery-' . $this->jqueryVersionNr . '.js');
            $jquery->setUiLocalPath($jqueryDir . 'jquery-ui-' . $this->jqueryUiVersionNr . '.js');

        } else {
            if (\MUtil_Https::on()) {
                $jquery->setCdnSsl(true);
            }
        }
        if (\MUtil_Bootstrap::enabled() && $this->project->isBootstrapLocal()) {
            $bootstrap = \MUtil_Bootstrap::bootstrap();
            $basePath = $request->getBasePath();
            $bootstrap->setBootstrapScriptPath($basePath.'/bootstrap/js/bootstrap.min.js');
            $bootstrap->setBootstrapStylePath($basePath.'/bootstrap/css/bootstrap.min.css');
            $bootstrap->setFontAwesomeStylePath($basePath.'/bootstrap/css/font-awesome.min.css');
        }
    }

    /**
     * Hook 4: Called in $this->setResponse.
     *
     * All resources have been loaded and the $request and $response object have been created.
     * Theoretically this event can be triggered multiple times, but this does
     * not happen in a standard Zend application.
     *
     * Not initialized is the $controller object and the routing has not yet been executed.
     *
     * Previous hook: requestChanged()
     * Initialized since: the $this->response object
     * Next hook: routeStartup()
     *
     * @return void
     */
    public function responseChanged(\Zend_Controller_Response_Abstract $response)
    {
        $response->setHeader('Expires', '', true);
    }

    /**
     * Hook 6: Called after \Zend_Controller_Router has determined the route set by the request.
     *
     * This events enables you to adjust the route after the routing has run it's course.
     *
     * Not initialized is the $controller object.
     *
     * Previous hook: routeStartup()
     * Actions since: $router->route()
     * Actions after: nothing, but the route consisting of controller, action and module should now be fixed
     * Next hook: dispatchLoopStartup()
     *
     * Also sets $this->currentOrganization and $this->menu to access afterwards
     *
     * @param  \Zend_Controller_Request_Abstract $request
     * @return void
     */
    public function routeShutdown(\Zend_Controller_Request_Abstract $request)
    {
        $loader = $this->getLoader();

        // Load the menu. As building the menu can depend on all resources and the request, we do it here.
        //
        // PS: The REQUEST is needed because otherwise the locale for translate is not certain.
        $menu   = $loader->createMenu($this);
        $source = $menu->getParameterSource();
        $user   = $this->_container->currentUser;
        $user->setRequest($request);

        $action       = $request->getActionName();
        $controller   = $request->getControllerName();
        $organization = $user->getCurrentOrganization();
        $organization->applyToMenuSource($source);

        $this->_container->currentOrganization = $organization;
        $this->_container->menu = $menu;
        $this->_updateVariable(array('currentOrganization', 'menu'));

        // Now is a good time to check for required values
        // Moved down here to prevent unit test from failing on missing salt
        $this->project->checkRequiredValues();

        /**
         * Check if we are in maintenance mode or not. This is triggeren by a file in the var/settings
         * directory with the name lock.txt
         */
        if ($this->getUtil()->getMaintenanceLock()->isLocked()) {
            if ($user->hasPrivilege('pr.maintenance.maintenance-mode', false)) {
                \MUtil_Echo::r($this->_('System is in maintenance mode'));

            } elseif (! (isset($this->maintenanceAccessiblePages[$controller]) &&
                    is_array($this->maintenanceAccessiblePages[$controller]) &&
                    in_array($action, $this->maintenanceAccessiblePages[$controller]))) {

                $this->addMessage($this->_('The page you requested is currently inaccessible.'));
                $this->setError($this->_('Please check back later.'), 401);

                $user->unsetAsCurrentUser();
            }
            $this->addMessage($this->_('System is in maintenance mode'));
        }

        // Gems does not use index/index
        if (('index' == $controller) &&
                (('index' == $action) || ($user->isActive() && ('login' == $action)))) {
            // Instead Gems routes to the first available menu item when this is the request target
            if (! $user->gotoStartPage($menu, $request)) {
                $this->setError(
                        $this->_('No access to site.'),
                        401,
                        $this->_('You have no access to this site.'),
                        true);
                return;
            }

        } else {
            //find first allowed item in the menu
            $menuItem = $menu->find(['action' => $action, 'controller' => $controller]);

            // Display error when not having the right priviliges
            if (! ($menuItem && $menuItem->get('allowed'))) {
                if ($menuItem) {
                    $alternative = $menuItem->get('allowed-alternative');
                    if ($alternative && \MUtil_String::contains($alternative, '/')) {
                        list($controller, $action) = explode('/', $alternative);
                        $altItem = $menu->findAllowedController($controller, $action);
                        if ($altItem) {
                            $request->setControllerName($controller);
                            $request->setActionName($action);
                            $redirector = \Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
                            $redirector->gotoRoute($altItem->toRouteUrl($request), null, true);
                        }
                    }
                }

                // When logged in
                if ($user->getUserId()) {
                    $this->setError(
                            $this->_('No access to page'),
                            403,
                            sprintf($this->_('Access to the %s/%s page is not allowed for your current group: %s.'),
                                    $controller,
                                    $action,
                                    $user->getGroup()->getName()),
                            true);

                } else { // No longer logged in

                    if (\MUtil_Console::isConsole()) {
                        $this->setError(
                                'No access to page.',
                                401,
                                sprintf('Controller "%s" action "%s" is not accessible.', $controller, $action),
                                true);
                        return;
                    }

                    if ($action == 'autofilter') {
                        // Throw an exception + HTTP 401 when an autofilter is called
                        throw new \Gems_Exception("Session expired", 401);
                    }
                    $menuItem = $menu->findFirst(['allowed' => true, 'visible' => true]);
                    if ($menuItem) {
                        // Do not store previous request & show message when the intended action is logoff
                        if (! ($controller == 'index' && $action == 'logoff')) {
                            $this->addMessage($this->_('You are no longer logged in.'));
                            $this->addMessage($this->_('You must login to access this page.'));

                            if (! \MUtil_String::contains($controller . $action, '.')) {
                                // save original request, we will redirect back once the user succesfully logs in
                                $staticSession = $this->getStaticSession();
                                $staticSession->previousRequestParameters = $request->getParams();
                                $staticSession->previousRequestMode = ($request->isPost() ? "POST" : "GET");
                            }
                        }

                        $redirector = \Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
                        $redirector->gotoRoute($menuItem->toRouteUrl($request));

                    } else {
                        $this->setError(
                                $this->_('You are no longer logged in.'),
                                401,
                                $this->_('You have no access to this site.'),
                                true);
                        return;
                    }
                }
            }
        }

        if (isset($menuItem)) {
            $menuItem->applyHiddenParameters($request, $source);
            $menu->setCurrent($menuItem);
        }
        if (($request instanceof \Zend_Controller_Request_Http)) {
            $sites       = $this->util->getSites();
            $invalidHost = $sites->isRequestFromAllowedHost($request);
            if ($invalidHost) {
                throw new \Gems_Exception(
                    sprintf("Invalid source host, possible CSRF attack. Used host: %s!", $invalidHost),
                    403
                );
            }
        }
    }

    public function setControllerDirectory(\Zend_Controller_Request_Abstract $request)
    {
        // Set Controller directory within dispatch loop to handle forwards and exceptions
        $module              = $request->getModuleName();
        $front               = $this->frontController;
        $controllerFileName  = $front->getDispatcher()->getControllerClass($request) . '.php';

        // \MUtil_Echo::r(APPLICATION_PATH . '/controllers/' . $controllerFileName);

        // Add the project controller path  as Highest priority
        $applicationPath = APPLICATION_PATH . DIRECTORY_SEPARATOR . 'controllers';
        $this->event->addListener(\Gems\Event\Application\SetFrontControllerDirectory::NAME,
            function(\Symfony\Component\EventDispatcher\Event $event) use ($applicationPath) {
                $event->setControllerDirIfControllerExists($applicationPath);
            },
            1000
        );

        $event = new \Gems\Event\Application\SetFrontControllerDirectory($front, $controllerFileName, $module);
        $this->event->dispatch($event, \Gems\Event\Application\SetFrontControllerDirectory::NAME);

        // If the event wasn't able to load the controller, fall back to the gemstracker one
        if (!$event->isPropagationStopped()) {
            $front->setControllerDirectory(GEMS_LIBRARY_DIR . '/controllers', $module);
        }
    }

    /**
     * Create an exception for the error, depending on processing position we either
     * set the response exception or throw the exception if the response is
     *
     * @param string $message
     * @param int $code
     * @param string $info
     * @param boolean $isSecurity
     * @throws exception
     */
    public function setError($message, $code = 200, $info = null, $isSecurity = false)
    {
        if ($isSecurity) {
            $e = new \Gems_Exception_Security($message, $code, null, $info);
        } else {
            $e = new \Gems_Exception($message, $code, null, $info);
        }
        $this->setException($e);
    }

    /**
     * Handle the exception depending on processing position we either
     * set the response exception or throw the exception if the response is
     *
     * @param exception $e
     * @throws exception
     */
    public function setException(exception $e)
    {
        if (isset($this->response)) {
            $this->response->setException($e);
        } else {
            throw $e;
        }
    }
}