<?php
/*
 * Copyright 2007-2013 Charles du Jeu - Abstrium SAS <team (at) pyd.io>
 * This file is part of Pydio.
 *
 * Pydio is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Pydio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Pydio.  If not, see <http://www.gnu.org/licenses/>.
 *
 * The latest code can be found at <http://pyd.io/>.
 */
defined('AJXP_EXEC') or die( 'Access not allowed');

/**
 * Configuration holder. Singleton class accessed statically, encapsulates the confDriver implementation.
 * @package Pydio
 * @subpackage Core
 */
class ConfService
{
    private static $instance;
    private $errors = array();
    private $configs = array();

    /**
     * @param AJXP_PluginsService $ajxpPluginService
     * @return AbstractConfDriver
     */
    public function confPluginSoftLoad($ajxpPluginService)
    {
        $booter = $ajxpPluginService->softLoad("boot.conf", array());
        $coreConfigs = $booter->loadPluginConfig("core", "conf");
        $corePlug = $ajxpPluginService->softLoad("core.conf", array());
        $corePlug->loadConfigs($coreConfigs);
        return $corePlug->getConfImpl();

    }

    /**
     * @return AbstractConfDriver
     */
    public static function getBootConfStorageImpl()
    {
        $inst = AJXP_PluginsService::getInstance()->findPluginById("boot.conf");
        if (empty($inst)) {
            $inst = AJXP_PluginsService::getInstance()->softLoad("boot.conf", array());
        }
        return $inst;
    }

    /**
     * Initialize singleton
     * @static
     * @return void
     */
    public static function init()
    {
        $inst = self::getInstance();
        $inst->initInst();
    }
    /**
     * Load the boostrap_* files and their configs
     * @return void
     */
    public function initInst()
    {
        // INIT AS GLOBAL
        $this->configs["AVAILABLE_LANG"] = self::listAvailableLanguages();
        if (isSet($_SERVER["HTTPS"]) && strtolower($_SERVER["HTTPS"]) == "on") {
            $this->configs["USE_HTTPS"] = true;
        }
        if (isSet($this->configs["USE_HTTPS"])) {
            AJXP_Utils::safeIniSet("session.cookie_secure", true);
        }
        $this->configs["JS_DEBUG"] = AJXP_CLIENT_DEBUG;
        $this->configs["SERVER_DEBUG"] = AJXP_SERVER_DEBUG;


        if (is_file(AJXP_CONF_PATH."/bootstrap_repositories.php")) {
            include(AJXP_CONF_PATH."/bootstrap_repositories.php");
            $this->configs["DEFAULT_REPOSITORIES"] = $REPOSITORIES;
        } else {
            $this->configs["DEFAULT_REPOSITORIES"] = array();
        }
    }
    /**
     * Start the singleton
     * @static
     * @return void
     */
    public static function start()
    {
        $inst = self::getInstance();
        $inst->startInst();
    }
    /**
     * Init CONF, AUTH drivers
     * Init Repositories
     * @return void
     */
    public function startInst()
    {
        AJXP_PluginsService::getInstance()->setPluginUniqueActiveForType("conf", self::getConfStorageImpl()->getName());
    }
    /**
     * Get errors generated by the boot sequence (init/start)
     * @static
     * @return array
     */
    public static function getErrors()
    {
        return self::getInstance()->errors;
    }

    /**
     * @static
     * @param $globalsArray
     * @param string $interfaceCheck
     * @internal param string $bootstrapConfigKey
     * @internal param string $bootstrapConfigType
     * @return AJXP_Plugin|null
     */
    public static function instanciatePluginFromGlobalParams($globalsArray, $interfaceCheck = "")
    {
        $plugin = false;

        if (is_string($globalsArray)) {
            $globalsArray = array("instance_name" => $globalsArray);
        }

        if (isSet($globalsArray["instance_name"])) {
            $pName = $globalsArray["instance_name"];
            unset($globalsArray["instance_name"]);
            $plugin = AJXP_PluginsService::getInstance()->softLoad($pName, $globalsArray);
            $plugin->performChecks();
        }

        if ($plugin != false && !empty($interfaceCheck)) {
            if (!is_a($plugin, $interfaceCheck)) {
                $plugin = false;
            }
        }
        if ($plugin !== false) {
            AJXP_PluginsService::getInstance()->setPluginActive($plugin->getType(), $plugin->getName(), true, $plugin);
        }
        return $plugin;

    }
    /**
     * Check if the STDIN constant is defined
     * @static
     * @return bool
     */
    public static function currentContextIsCommandLine()
    {
        return php_sapi_name() === "cli";
    }
    /**
     * Check the presence of mcrypt and option CMDLINE_ACTIVE
     * @static
     * @return bool
     */
    public static function backgroundActionsSupported()
    {
        return function_exists("mcrypt_create_iv") && ConfService::getCoreConf("CMDLINE_ACTIVE");
    }

    /**
     * @var AbstractConfDriver
     */
    private static $tmpConfStorageImpl;
    /**
     * @var AbstractAuthDriver
     */
    private static $tmpAuthStorageImpl;

    /**
     * @param $confStorage AbstractConfDriver
     * @param $authStorage AbstractAuthDriver
     */
    public static function setTmpStorageImplementations($confStorage, $authStorage)
    {
        self::$tmpConfStorageImpl = $confStorage;
        self::$tmpAuthStorageImpl = $authStorage;
    }

    /**
     * Get conf driver implementation
     *
     * @return AbstractConfDriver
     */
    public static function getConfStorageImpl()
    {
        if(isSet(self::$tmpConfStorageImpl)) return self::$tmpConfStorageImpl;
        return AJXP_PluginsService::getInstance()->getPluginById("core.conf")->getConfImpl();
    }

    /**
     * Get auth driver implementation
     *
     * @return AbstractAuthDriver
     */
    public static function getAuthDriverImpl()
    {
        if(isSet(self::$tmpAuthStorageImpl)) return self::$tmpAuthStorageImpl;
        return AJXP_PluginsService::getInstance()->getPluginById("core.auth")->getAuthImpl();
    }

    public static function switchUserToActiveRepository($loggedUser, $parameterId = -1)
    {
        if (isSet($_SESSION["PENDING_REPOSITORY_ID"]) && isSet($_SESSION["PENDING_FOLDER"])) {
            $loggedUser->setArrayPref("history", "last_repository", $_SESSION["PENDING_REPOSITORY_ID"]);
            $loggedUser->setPref("pending_folder", $_SESSION["PENDING_FOLDER"]);
            $loggedUser->save("user");
            AuthService::updateUser($loggedUser);
            unset($_SESSION["PENDING_REPOSITORY_ID"]);
            unset($_SESSION["PENDING_FOLDER"]);
        }
        $currentRepoId = ConfService::getCurrentRepositoryId();
        $lastRepoId  = $loggedUser->getArrayPref("history", "last_repository");
        $defaultRepoId = AuthService::getDefaultRootId();
        if ($defaultRepoId == -1) {
            return false;
        } else {
            if ($lastRepoId !== "" && $lastRepoId!==$currentRepoId && $parameterId == -1 && $loggedUser->canSwitchTo($lastRepoId)) {
                ConfService::switchRootDir($lastRepoId);
            } else if ($parameterId != -1 && $loggedUser->canSwitchTo($parameterId)) {
                ConfService::switchRootDir($parameterId);
            } else if (!$loggedUser->canSwitchTo($currentRepoId)) {
                ConfService::switchRootDir($defaultRepoId);
            }
        }
        return true;
    }


    /**
     * See instance method
     * @static
     * @param $rootDirIndex
     * @param bool $temporary
     * @return void
     */
    public static function switchRootDir($rootDirIndex = -1, $temporary = false)
    {
        self::getInstance()->switchRootDirInst($rootDirIndex, $temporary);
    }
    /**
     * Switch the current repository
     * @param $rootDirIndex
     * @param bool $temporary
     * @return void
     */
    public function switchRootDirInst($rootDirIndex=-1, $temporary=false)
    {
        if ($rootDirIndex == -1) {
            $currentRepos = $this->getLoadedRepositories();
            if (isSet($_SESSION['REPO_ID']) && array_key_exists($_SESSION['REPO_ID'], $currentRepos)) {
                $this->configs["REPOSITORY"] = $currentRepos[$_SESSION['REPO_ID']];
            } else {
                $keys = array_keys($currentRepos);
                $this->configs["REPOSITORY"] = $currentRepos[$keys[0]];
                $_SESSION['REPO_ID'] = $keys[0];
            }
        } else {
            /*
            if (isSet($this->configs["REPOSITORY"]) && $this->configs["REPOSITORY"] == $rootDirIndex) {
                return;
            }
            */
            if ($temporary && isSet($_SESSION['REPO_ID'])) {
                $crtId = $_SESSION['REPO_ID'];
                if ($crtId != $rootDirIndex && !isSet($_SESSION['SWITCH_BACK_REPO_ID'])) {
                    $_SESSION['SWITCH_BACK_REPO_ID'] = $crtId;
                    //AJXP_Logger::debug("switching to $rootDirIndex, registering $crtId");
                }
            } else {
                $crtId = $_SESSION['REPO_ID'];
                $_SESSION['PREVIOUS_REPO_ID'] = $crtId;
                //AJXP_Logger::debug("switching back to $rootDirIndex");
            }
            if (isSet($this->configs["REPOSITORIES"]) && isSet($this->configs["REPOSITORIES"][$rootDirIndex])) {
                $this->configs["REPOSITORY"] = $this->configs["REPOSITORIES"][$rootDirIndex];
            } else {
                $this->configs["REPOSITORY"] = ConfService::getRepositoryById($rootDirIndex);
            }
            $_SESSION['REPO_ID'] = $rootDirIndex;
            if(isSet($this->configs["ACCESS_DRIVER"])) unset($this->configs["ACCESS_DRIVER"]);
        }

        if (isSet($this->configs["REPOSITORY"]) && $this->configs["REPOSITORY"]->getOption("CHARSET")!="") {
            $_SESSION["AJXP_CHARSET"] = $this->configs["REPOSITORY"]->getOption("CHARSET");
        } else {
            if (isSet($_SESSION["AJXP_CHARSET"])) {
                unset($_SESSION["AJXP_CHARSET"]);
            }
        }


        if ($rootDirIndex!=-1 && AuthService::usersEnabled() && AuthService::getLoggedUser()!=null) {
            $loggedUser = AuthService::getLoggedUser();
            $loggedUser->setArrayPref("history", "last_repository", $rootDirIndex);
            $loggedUser->save("user");
        }

    }

    /**
     * See instance method
     * @static
     * @param String $scope "user" or "all"
     * @return Repository[]
     */
    public static function getRepositoriesList($scope = "user")
    {
        if ($scope == "user") {
            return self::getInstance()->getLoadedRepositories();
        } else {
            return self::getInstance()->initRepositoriesListInst("all");
        }
    }

    /**
     * @return Repository[]
     */
    private function getLoadedRepositories()
    {
        if (isSet($this->configs["REPOSITORIES"])) {
            return $this->configs["REPOSITORIES"];
        }
        $this->configs["REPOSITORIES"] = $this->initRepositoriesListInst();
        return $this->configs["REPOSITORIES"];
    }

    private function invalidateLoadedRepositories()
    {
        $this->configs["REPOSITORIES"] = null;
    }

    /**
     * @static
     * @param AbstractAjxpUser $userObject
     * @param bool $details
     * @param bool $labelOnly
     * @param bool $skipShared
     * @return Repository[]
     */
    public static function getAccessibleRepositories($userObject=null, $details=false, $labelOnly = false, $skipShared = false)
    {
        $result = array();
        $allReps = ConfService::getRepositoriesList("all");
        foreach ($allReps as $repositoryId => $repositoryObject) {
            if (!ConfService::repositoryIsAccessible($repositoryId, $repositoryObject, $userObject, $details, $skipShared)) {
                continue;
            }

            if ($labelOnly) {
                $result[$repositoryId] = $repositoryObject->getDisplay();
            } else {
                $result[$repositoryId] = $repositoryObject;
            }

        }
        return $result;
    }

    /**
     * @param String $repositoryId
     * @param Repository $repositoryObject
     * @param AbstractAjxpUser $userObject
     * @param bool $details
     * @param bool $skipShared
     *
     * @return bool
     */
    public static function repositoryIsAccessible($repositoryId, $repositoryObject, $userObject = null, $details=false, $skipShared=false)
    {
        if($userObject == null) $userObject = AuthService::getLoggedUser();
        if ($userObject == null && AuthService::usersEnabled()) {
            return false;
        }
        if (!AuthService::canAssign($repositoryObject, $userObject)) {
            return false;
        }
        if ($repositoryObject->isTemplate) {
            return false;
        }
        if ($repositoryObject->getAccessType()=="ajxp_conf" && $userObject != null) {
            if (AuthService::usersEnabled() && !$userObject->isAdmin()) {
                return false;
            }
        }
        if ($repositoryObject->getAccessType() == "ajxp_shared" && !AuthService::usersEnabled()) {
            return false;
        }
        if ($repositoryObject->getUniqueUser() && (!AuthService::usersEnabled() || $userObject == null  || $userObject->getId() == "shared" || $userObject->getId() != $repositoryObject->getUniqueUser() )) {
            return false;
        }
        if ( $userObject != null && !($userObject->canRead($repositoryId) || $userObject->canWrite($repositoryId)) && !$details) {
            return false;
        }
        if ($userObject == null || $userObject->canRead($repositoryId) || $userObject->canWrite($repositoryId) || $details) {
            // Do not display standard repositories even in details mode for "sub"users
            if ($userObject != null && $userObject->hasParent() && !($userObject->canRead($repositoryId) || $userObject->canWrite($repositoryId) )) {
                return false;
            }
            // Do not display shared repositories otherwise.
            if ($repositoryObject->hasOwner() && $skipShared) {
                return false;
            }
            if ($userObject != null && $repositoryObject->hasOwner() && !$userObject->hasParent()) {
                // Display the repositories if allow_crossusers is ok
                if(ConfService::getCoreConf("ALLOW_CROSSUSERS_SHARING", "conf") === false
                || ConfService::getCoreConf("ALLOW_CROSSUSERS_SHARING", "conf") === 0) {
                    return false;
                }
                // But still do not display its own shared repositories!
                if ($repositoryObject->getOwner() == $userObject->getId()) {
                    return false;
                }
            }
            if ($repositoryObject->hasOwner() && $userObject != null &&  $details && !($userObject->canRead($repositoryId) || $userObject->canWrite($repositoryId) ) ) {
                return false;
            }
        }
        return true;
    }

    /**
     * Return the full list of repositories, as id => objects
     * @return array
     */
    public function getRepositoriesListInst()
    {
        return $this->getLoadedRepositories();
    }

    /**
     * See instance method
     * @static
     * @return string
     */
    public static function getCurrentRepositoryId()
    {
        return self::getInstance()->getCurrentRepositoryIdInst();
    }
    /**
     * Get the current repository ID;
     * @return string
     */
    public function getCurrentRepositoryIdInst()
    {
        $currentRepos = $this->getLoadedRepositories();
        if (isSet($_SESSION['REPO_ID']) &&  isSet($currentRepos[$_SESSION['REPO_ID']])) {
            return $_SESSION['REPO_ID'];
        }
        return array_shift(array_keys($currentRepos));
    }
    /**
     * Get the current repo label
     * @static
     * @return string
     */
    public static function getCurrentRootDirDisplay()
    {
        return self::getInstance()->getCurrentRootDirDisplayInst();
    }
    /**
     * @return string
     */
    public function getCurrentRootDirDisplayInst()
    {
        $currentRepos = $this->getLoadedRepositories();
        if (isSet($currentRepos[$_SESSION['REPO_ID']])) {
            $repo = $currentRepos[$_SESSION['REPO_ID']];
            return $repo->getDisplay();
        }
        return "";
    }

    /**
     * @param $scope String "user", "all"
     * @return array
     */
    protected function initRepositoriesListInst($scope = "user")
    {
        // APPEND CONF FILE REPOSITORIES

        $objList = array();
        foreach ($this->configs["DEFAULT_REPOSITORIES"] as $index=>$repository) {
            $repo = self::createRepositoryFromArray($index, $repository);
            $repo->setWriteable(false);
            $objList[$repo->getId()] = $repo;
        }
        // LOAD FROM DRIVER
        $confDriver = self::getConfStorageImpl();
        $drvList = $confDriver->listRepositories($scope == "user" ? AuthService::getLoggedUser() : null);
        if (is_array($drvList)) {
            foreach ($drvList as $repoId=>$repoObject) {
                $driver = AJXP_PluginsService::getInstance()->getPluginByTypeName("access", $repoObject->getAccessType());
                if (!is_object($driver) || !$driver->isEnabled()) {
                    unset($drvList[$repoId]);
                } else {
                    $repoObject->setId($repoId);
                    $drvList[$repoId] = $repoObject;
                }
            }
            $objList = array_merge($objList, $drvList);
        }
        return $objList;
    }
    /**
     * See instance method
     * @static
     * @param bool $register
     * @return array
     */
    public static function detectRepositoryStreams($register = false)
    {
        return self::getInstance()->detectRepositoryStreamsInst($register);
    }
    /**
     * Call the detectStreamWrapper method
     * @param bool $register
     * @return array
     */
    public function detectRepositoryStreamsInst($register = false)
    {
        $streams = array();
        $currentRepos = $this->getLoadedRepositories();
        foreach ($currentRepos as $repository) {
            $repository->detectStreamWrapper($register, $streams);
        }
        return $streams;
    }

    /**
     * Create a repository object from a config options array
     *
     * @param integer $index
     * @param Array $repository
     * @return Repository
     */
    public static function createRepositoryFromArray($index, $repository)
    {
        return self::getInstance()->createRepositoryFromArrayInst($index, $repository);
    }
    /**
     * See static method
     * @param string $index
     * @param array $repository
     * @return Repository
     */
    public function createRepositoryFromArrayInst($index, $repository)
    {
        $repo = new Repository($index, $repository["DISPLAY"], $repository["DRIVER"]);
        if (isSet($repository["DISPLAY_ID"])) {
            $repo->setDisplayStringId($repository["DISPLAY_ID"]);
        }
        if (isSet($repository["DESCRIPTION_ID"])) {
            $repo->setDescription($repository["DESCRIPTION_ID"]);
        }
        if (isSet($repository["AJXP_SLUG"])) {
            $repo->setSlug($repository["AJXP_SLUG"]);
        }
        if (isSet($repository["IS_TEMPLATE"]) && $repository["IS_TEMPLATE"]) {
            $repo->isTemplate = true;
            $repo->uuid = $index;
        }
        if (array_key_exists("DRIVER_OPTIONS", $repository) && is_array($repository["DRIVER_OPTIONS"])) {
            foreach ($repository["DRIVER_OPTIONS"] as $oName=>$oValue) {
                $repo->addOption($oName, $oValue);
            }
        }
        // BACKWARD COMPATIBILITY!
        if (array_key_exists("PATH", $repository)) {
            $repo->addOption("PATH", $repository["PATH"]);
            $repo->addOption("CREATE", intval($repository["CREATE"]));
            $repo->addOption("RECYCLE_BIN", $repository["RECYCLE_BIN"]);
        }
        return $repo;
    }

    /**
     * Add dynamically created repository
     *
     * @param Repository $oRepository
     * @return void|-1 if error
     */
    public static function addRepository($oRepository)
    {
        return self::getInstance()->addRepositoryInst($oRepository);
    }
    /**
     * @param $oRepository
     * @return void|-1 on error
     */
    public function addRepositoryInst($oRepository)
    {
        $confStorage = self::getConfStorageImpl();
        $res = $confStorage->saveRepository($oRepository);
        if ($res == -1) {
            return $res;
        }
        AJXP_Logger::info(__CLASS__,"Create Repository", array("repo_name"=>$oRepository->getDisplay()));
        $this->invalidateLoadedRepositories();
    }

    public static function findRepositoryByIdOrAlias($idOrAlias)
    {
        $repository = ConfService::getRepositoryById($idOrAlias);
        if($repository != null ) return $repository;
        $repository = ConfService::getRepositoryByAlias($idOrAlias);
        if($repository != null) return $repository;
        return null;
    }

    /**
     * Retrieve a repository object
     *
     * @param String $repoId
     * @return Repository
     */
    public static function getRepositoryById($repoId)
    {
        return self::getInstance()->getRepositoryByIdInst($repoId);
    }
    /**
     * See static method
     * @param $repoId
     * @return Repository|null
     */
    public function getRepositoryByIdInst($repoId)
    {
        /*
        $currentRepos = $this->getLoadedRepositories();
        if (isSet($currentRepos[$repoId])) {
            return $currentRepos[$repoId];
        }
        */
        if (isSet($this->configs["REPOSITORIES"]) && isSet($this->configs["REPOSITORIES"][$repoId])) {
            return $this->configs["REPOSITORIES"][$repoId];
        }
        if (iSset($this->configs["REPOSITORY"]) && $this->configs["REPOSITORY"]->getId()."" == $repoId) {
            return $this->configs["REPOSITORY"];
        }
        $test =  $this->getConfStorageImpl()->getRepositoryById($repoId);
        if($test != null) {
            return $test;
        }
        // Finally try to search in default repositories
        if (isSet($this->configs["DEFAULT_REPOSITORIES"]) && isSet($this->configs["DEFAULT_REPOSITORIES"][$repoId])) {
            $repo = self::createRepositoryFromArray($repoId, $this->configs["DEFAULT_REPOSITORIES"][$repoId]);
            $repo->setWriteable(false);
            return $repo;
        }
    }

    /**
     * Retrieve a repository object by its slug
     *
     * @param String $repoAlias
     * @return Repository
     */
    public static function getRepositoryByAlias($repoAlias)
    {
        $repo = self::getConfStorageImpl()->getRepositoryByAlias($repoAlias);
        if($repo !== null) return $repo;
        // check default repositories
        return self::getInstance()->getRepositoryByAliasInstDefaults($repoAlias);
    }
    /**
     * See static method
     * @param $repoAlias
     * @return Repository|null
     */
    public function getRepositoryByAliasInstDefaults($repoAlias)
    {
        $conf = $this->configs["DEFAULT_REPOSITORIES"];
        foreach ($conf as $repoId => $repoDef) {
            if ($repoDef["AJXP_SLUG"] == $repoAlias) {
                $repo = self::createRepositoryFromArray($repoId, $repoDef);
                $repo->setWriteable(false);
                return $repo;
            }
        }
        return null;
    }


    /**
     * Replace a repository by an update one.
     *
     * @param String $oldId
     * @param Repository $oRepositoryObject
     * @return mixed
     */
    public static function replaceRepository($oldId, $oRepositoryObject)
    {
        return self::getInstance()->replaceRepositoryInst($oldId, $oRepositoryObject);
    }
    /**
     * See static method
     * @param $oldId
     * @param $oRepositoryObject
     * @return void
     */
    public function replaceRepositoryInst($oldId, $oRepositoryObject)
    {
        $confStorage = self::getConfStorageImpl();
        $res = $confStorage->saveRepository($oRepositoryObject, true);
        if ($res == -1) {
            return $res;
        }
        AJXP_Logger::info(__CLASS__,"Edit Repository", array("repo_name"=>$oRepositoryObject->getDisplay()));
        $this->invalidateLoadedRepositories();
    }
    /**
     * Set a temp repository id but not in the session
     * @static
     * @param $repositoryObject
     * @return void
     */
    public static function tmpReplaceRepository($repositoryObject)
    {
        $inst = self::getInstance();
        if (isSet($inst->configs["REPOSITORIES"][$repositoryObject->getUniqueId()])) {
            $inst->configs["REPOSITORIES"][$repositoryObject->getUniqueId()] = $repositoryObject;
        }
    }
    /**
     * Remove a repository using the conf driver implementation
     * @static
     * @param $repoId
     * @return int
     */
    public static function deleteRepository($repoId)
    {
        return self::getInstance()->deleteRepositoryInst($repoId);
    }
    /**
     * See static method
     * @param $repoId
     * @return int
     */
    public function deleteRepositoryInst($repoId)
    {
        $confStorage = self::getConfStorageImpl();
        $res = $confStorage->deleteRepository($repoId);
        if ($res == -1) {
            return $res;
        }
        AJXP_Logger::info(__CLASS__,"Delete Repository", array("repo_id"=>$repoId));
        $this->invalidateLoadedRepositories();
    }

    /**
     * Check if the gzopen function exists
     * @static
     * @return bool
     */
    public static function zipEnabled()
    {
        if(ConfService::getCoreConf("DISABLE_ZIP_BROWSING") === true) return false;
        return (function_exists("gzopen")?true:false);
    }
    /**
     * Get the list of all "conf" messages
     * @static
     * @param bool $forceRefresh Refresh the list
     * @return
     */
    public static function getMessagesConf($forceRefresh = false)
    {
        return self::getInstance()->getMessagesInstConf($forceRefresh);
    }
    /**
     * See static method
     * @param bool $forceRefresh
     * @return
     */
    public function getMessagesInstConf($forceRefresh = false)
    {
        // make sure they are loaded
        $mess = $this->getMessagesInst($forceRefresh);
        return $this->configs["CONF_MESSAGES"];
    }

    /**
     * Get all i18n message
     * @static
     * @param bool $forceRefresh
     * @return
     */
    public static function getMessages($forceRefresh = false)
    {
        return self::getInstance()->getMessagesInst($forceRefresh);
    }
    /**
     * Get i18n messages
     * @param bool $forceRefresh
     * @return
     */
    public function getMessagesInst($forceRefresh = false)
    {
        $crtLang = self::getLanguage();
        $messageCacheDir = dirname(AJXP_PLUGINS_MESSAGES_FILE)."/i18n";
        $messageFile = $messageCacheDir."/".$crtLang."_".basename(AJXP_PLUGINS_MESSAGES_FILE);
        if (isSet($this->configs["MESSAGES"]) && !$forceRefresh) {
            return $this->configs["MESSAGES"];
        }
        if (!isset($this->configs["MESSAGES"]) && is_file($messageFile)) {
            include($messageFile);
            if (isSet($MESSAGES)) {
                $this->configs["MESSAGES"] = $MESSAGES;
            }
            if (isSet($CONF_MESSAGES)) {
                $this->configs["CONF_MESSAGES"] = $CONF_MESSAGES;
            }
        } else {
            $this->configs["MESSAGES"] = array();
            $this->configs["CONF_MESSAGES"] = array();
            $nodes = AJXP_PluginsService::getInstance()->searchAllManifests("//i18n", "nodes");
            foreach ($nodes as $node) {
                $nameSpace = $node->getAttribute("namespace");
                $path = $node->getAttribute("path");
                $lang = $crtLang;
                if (!is_file($path."/".$crtLang.".php")) {
                    $lang = "en"; // Default language, minimum required.
                }
                if (is_file($path."/".$lang.".php")) {
                    require($path."/".$lang.".php");
                    if (isSet($mess)) {
                        foreach ($mess as $key => $message) {
                            $this->configs["MESSAGES"][(empty($nameSpace)?"":$nameSpace.".").$key] = $message;
                        }
                    }
                }
                $lang = $crtLang;
                if (!is_file($path."/conf/".$crtLang.".php")) {
                    $lang = "en";
                }
                if (is_file($path."/conf/".$lang.".php")) {
                    require($path."/conf/".$lang.".php");
                    $this->configs["CONF_MESSAGES"] = array_merge($this->configs["CONF_MESSAGES"], $mess);
                }
            }
            if(!is_dir($messageCacheDir)) mkdir($messageCacheDir);
            @file_put_contents($messageFile, "<?php \$MESSAGES = ".var_export($this->configs["MESSAGES"], true) ." ; \$CONF_MESSAGES = ".var_export($this->configs["CONF_MESSAGES"], true) ." ; ");
        }

        return $this->configs["MESSAGES"];
    }

    /**
     * Get all registered extensions, from both the conf/extensions.conf.php and from the plugins
     * @static
     * @return
     */
    public static function getRegisteredExtensions()
    {
        return self::getInstance()->getRegisteredExtensionsInst();
    }
    /**
     * See static method
     * @return
     */
    public function getRegisteredExtensionsInst()
    {
        if (!isSet($this->configs["EXTENSIONS"])) {
            $EXTENSIONS = array();
            $RESERVED_EXTENSIONS = array();
            include_once(AJXP_CONF_PATH."/extensions.conf.php");
            $EXTENSIONS = array_merge($RESERVED_EXTENSIONS, $EXTENSIONS);
            foreach ($EXTENSIONS as $key => $value) {
                unset($EXTENSIONS[$key]);
                $EXTENSIONS[$value[0]] = $value;
            }
            $nodes = AJXP_PluginsService::getInstance()->searchAllManifests("//extensions/extension", "nodes", true);
            $res = array();
            foreach ($nodes as $node) {
                $res[$node->getAttribute("mime")] = array($node->getAttribute("mime"), $node->getAttribute("icon"), $node->getAttribute("messageId"));
            }
            if (count($res)) {
                $EXTENSIONS = array_merge($EXTENSIONS, $res);
            }
            $this->configs["EXTENSIONS"] = $EXTENSIONS;
        }
        return $this->configs["EXTENSIONS"];
    }
    /**
     * Get the actions that declare to skip the secure token in the plugins
     * @static
     * @return array
     */
    public static function getDeclaredUnsecureActions()
    {
        $test = AJXP_PluginsService::getInstance()->loadFromPluginQueriesCache("//action[@skipSecureToken]");
        if (!empty($test) && is_array($test)) {
            return $test;
        } else {
            $nodes = AJXP_PluginsService::getInstance()->searchAllManifests("//action[@skipSecureToken]", "nodes", false, false, true);
            $res = array();
            foreach ($nodes as $node) {
                $res[] = $node->getAttribute("name");
            }
            AJXP_PluginsService::getInstance()->storeToPluginQueriesCache("//action[@skipSecureToken]", $res);
            return $res;
        }

    }
    /**
     * Detect available languages from the core i18n library
     * @static
     * @return array
     */
    public static function listAvailableLanguages()
    {
        // Cache in session!
        if (isSet($_SESSION["AJXP_LANGUAGES"]) && !isSet($_GET["refresh_langs"])) {
            return $_SESSION["AJXP_LANGUAGES"];
        }
        $langDir = AJXP_COREI18N_FOLDER;
        $languages = array();
        if (($dh = opendir($langDir))!==FALSE) {
            while (($file = readdir($dh)) !== false) {
                $matches = array();
                if (preg_match("/(.*)\.php/", $file, $matches) == 1) {
                    $fRadical = $matches[1];
                    include($langDir."/".$fRadical.".php");
                    $langName = isSet($mess["languageLabel"])?$mess["languageLabel"]:"Not Found";
                    $languages[$fRadical] = $langName;
                }
            }
            closedir($dh);
        }
        if (count($languages)) {
            $_SESSION["AJXP_LANGUAGES"] = $languages;
        }
        return $languages;
    }

    /**
     * Get a config by its name
     * @static
     * @param string $varName
     * @return mixed
     */
    public static function getConf($varName)
    {
        return self::getInstance()->getConfInst($varName);
    }
    /**
     * Set a config by its name
     * @static
     * @param string $varName
     * @param mixed $varValue
     * @return void
     */
    public static function setConf($varName, $varValue)
    {
        return self::getInstance()->setConfInst($varName, $varValue);
    }
    /**
     * See static method
     * @param $varName
     * @return mixed
     */
    public function getConfInst($varName)
    {
        if (isSet($this->configs[$varName])) {
            return $this->configs[$varName];
        }
        if (defined("AJXP_".$varName)) {
            return eval("return AJXP_".$varName.";");
        }
        return null;
    }
    /**
     * See static method
     * @param $varName
     * @param $varValue
     * @return void
     */
    public function setConfInst($varName, $varValue)
    {
        $this->configs[$varName] = $varValue;
    }
    /**
     * Get config from the core.$coreType plugin
     * @static
     * @param string $varName
     * @param string $coreType
     * @return mixed|null|string
     */
    public static function getCoreConf($varName, $coreType = "ajaxplorer")
    {
        $coreP = AJXP_PluginsService::getInstance()->findPlugin("core", $coreType);
        if($coreP === false) return null;
        $confs = $coreP->getConfigs();
        $confs = AuthService::filterPluginParameters("core.".$coreType, $confs);
        return (isSet($confs[$varName]) ? AJXP_VarsFilter::filter($confs[$varName]) : null);
    }

    /**
     * Set the language in the session
     * @static
     * @param string $lang
     * @return void
     */
    public static function setLanguage($lang)
    {
        return self::getInstance()->setLanguageInst($lang);
    }
    /**
     * See static method
     * @param string $lang
     * @return void
     */
    public function setLanguageInst($lang)
    {
        if (array_key_exists($lang, $this->configs["AVAILABLE_LANG"])) {
            $this->configs["LANGUE"] = $lang;
        }
    }
    /**
     * Get the language from the session
     * @static
     * @return string
     */
    public static function getLanguage()
    {
        $lang = self::getInstance()->getConfInst("LANGUE");
        if ($lang == null) {
            $lang = self::getInstance()->getCoreConf("DEFAULT_LANGUAGE");
        }
        if(empty($lang)) return "en";
        return $lang;
    }

    /**
     * The current repository
     * @return Repository
     */
    public static function getRepository()
    {
        return self::getInstance()->getRepositoryInst();
    }
    /**
     * See static method
     * @return Repository
     */
    public function getRepositoryInst()
    {
        if (isSet($_SESSION['REPO_ID']) && isSet($this->configs["REPOSITORIES"])  &&  isSet($this->configs["REPOSITORIES"][$_SESSION['REPO_ID']])) {
            return $this->configs["REPOSITORIES"][$_SESSION['REPO_ID']];
        }
        return $this->configs["REPOSITORY"];
    }

    /**
     * Returns the repository access driver
     * @return AJXP_Plugin
     */
    public static function loadRepositoryDriver()
    {
        return self::getInstance()->loadRepositoryDriverInst();
    }
    /**
     * See static method
     * @throws Exception
     * @return AJXP_Plugin
     */
    public function loadRepositoryDriverInst()
    {
        if (isSet($this->configs["ACCESS_DRIVER"]) && is_a($this->configs["ACCESS_DRIVER"], "AbstractAccessDriver")) {
            return $this->configs["ACCESS_DRIVER"];
        }
        $this->switchRootDirInst();
        $crtRepository = $this->getRepositoryInst();
        $accessType = $crtRepository->getAccessType();
        $pServ = AJXP_PluginsService::getInstance();
        $plugInstance = $pServ->getPluginByTypeName("access", $accessType);

        // TRIGGER BEFORE INIT META
        $metaSources = $crtRepository->getOption("META_SOURCES");
        if (isSet($metaSources) && is_array($metaSources) && count($metaSources)) {
            $keys = array_keys($metaSources);
            foreach ($keys as $plugId) {
                if($plugId == "") continue;
                $split = explode(".", $plugId);
                $instance = $pServ->getPluginById($plugId);
                if (!is_object($instance)) {
                    continue;
                }
                if (!method_exists($instance, "beforeInitMeta")) {
                    continue;
                }
                try {
                    $instance->init(AuthService::filterPluginParameters($plugId, $metaSources[$plugId], $crtRepository->getId()));
                    $instance->beforeInitMeta($plugInstance);
                } catch (Exception $e) {
                    AJXP_Logger::error(__CLASS__, 'Meta plugin', 'Cannot instanciate Meta plugin, reason : '.$e->getMessage());
                    $this->errors[] = $e->getMessage();
                }
            }
        }

        // INIT MAIN DRIVER
        $plugInstance->init($crtRepository);
        try {
            $plugInstance->initRepository();
            $crtRepository->driverInstance = $plugInstance;
        } catch (Exception $e) {
            // Remove repositories from the lists
            unset($this->configs["REPOSITORIES"][$crtRepository->getId()]);
            if (isSet($_SESSION["PREVIOUS_REPO_ID"]) && $_SESSION["PREVIOUS_REPO_ID"] !=$crtRepository->getId()) {
                $this->switchRootDir($_SESSION["PREVIOUS_REPO_ID"]);
            } else {
                $this->switchRootDir();
            }
            throw $e;
        }
        $pServ->setPluginUniqueActiveForType("access", $accessType);

        // TRIGGER INIT META
        $metaSources = $crtRepository->getOption("META_SOURCES");
        if (isSet($metaSources) && is_array($metaSources) && count($metaSources)) {
            $keys = array_keys($metaSources);
            foreach ($keys as $plugId) {
                if($plugId == "") continue;
                $split = explode(".", $plugId);
                $instance = $pServ->getPluginById($plugId);
                if (!is_object($instance)) {
                    continue;
                }
                try {
                    $instance->init(AuthService::filterPluginParameters($plugId, $metaSources[$plugId], $crtRepository->getId()));
                    if (!method_exists($instance, "initMeta")) {
                        throw new Exception("Meta Source $plugId does not implement the initMeta method.");
                    }
                    $instance->initMeta($plugInstance);
                } catch (Exception $e) {
                    AJXP_Logger::error(__CLASS__, 'Meta plugin', 'Cannot instanciate Meta plugin, reason : '.$e->getMessage());
                    $this->errors[] = $e->getMessage();
                }
                $pServ->setPluginActive($split[0], $split[1]);
            }
        }
        if (count($this->errors)>0) {
            $e = new AJXP_Exception("Error while loading repository feature : ".implode(",",$this->errors));
            // Remove repositories from the lists
            unset($this->configs["REPOSITORIES"][$crtRepository->getId()]);
            if (isSet($_SESSION["PREVIOUS_REPO_ID"]) && $_SESSION["PREVIOUS_REPO_ID"] !=$crtRepository->getId()) {
                $this->switchRootDir($_SESSION["PREVIOUS_REPO_ID"]);
            } else {
                $this->switchRootDir();
            }
            throw $e;
        }

        $this->configs["ACCESS_DRIVER"] = $plugInstance;
        return $this->configs["ACCESS_DRIVER"];
    }

    /**
     * @static
     * @param Repository $repository
     * @return AbstractAccessDriver
     */
    public static function loadDriverForRepository(&$repository)
    {
        return self::getInstance()->loadRepositoryDriverREST($repository);
    }

    /**
     * See static method
     * @param Repository $repository
     * @throws AJXP_Exception|Exception
     * @return AbstractAccessDriver
     */
    public function loadRepositoryDriverREST(&$repository)
    {
        if (isset($repository->driverInstance)) {
            return $repository->driverInstance;
        }
        $accessType = $repository->getAccessType();
        $pServ = AJXP_PluginsService::getInstance();
        $plugInstance = $pServ->getPluginByTypeName("access", $accessType);

        // TRIGGER BEFORE INIT META
        $metaSources = $repository->getOption("META_SOURCES");
        if (isSet($metaSources) && is_array($metaSources) && count($metaSources)) {
            $keys = array_keys($metaSources);
            foreach ($keys as $plugId) {
                if($plugId == "") continue;
                $instance = $pServ->getPluginById($plugId);
                if (!is_object($instance)) {
                    continue;
                }
                if (!method_exists($instance, "beforeInitMeta")) {
                    continue;
                }
                try {
                    $instance->init(AuthService::filterPluginParameters($plugId, $metaSources[$plugId], $repository->getId()));
                    $instance->beforeInitMeta($plugInstance);
                } catch (Exception $e) {
                    AJXP_Logger::error(__CLASS__, 'Meta plugin', 'Cannot instanciate Meta plugin, reason : '.$e->getMessage());
                    $this->errors[] = $e->getMessage();
                }
            }
        }

        // INIT MAIN DRIVER
        $plugInstance->init($repository);
        try {
            $plugInstance->initRepository();
        } catch (Exception $e) {
            throw $e;
        }
        $pServ->setPluginUniqueActiveForType("access", $accessType);

        // TRIGGER INIT META
        $metaSources = $repository->getOption("META_SOURCES");
        if (isSet($metaSources) && is_array($metaSources) && count($metaSources)) {
            $keys = array_keys($metaSources);
            foreach ($keys as $plugId) {
                if($plugId == "") continue;
                $split = explode(".", $plugId);
                $instance = $pServ->getPluginById($plugId);
                if (!is_object($instance)) {
                    continue;
                }
                try {
                    $instance->init(AuthService::filterPluginParameters($plugId, $metaSources[$plugId], $repository->getId()));
                    if(!method_exists($instance, "initMeta")) throw new Exception("Meta Source $plugId does not implement the initMeta method.");
                    $instance->initMeta($plugInstance);
                } catch (Exception $e) {
                    AJXP_Logger::error(__CLASS__, 'Meta plugin', 'Cannot instanciate Meta plugin, reason : '.$e->getMessage());
                    $this->errors[] = $e->getMessage();
                }
                $pServ->setPluginActive($split[0], $split[1]);
            }
        }
        if (count($this->errors)>0) {
            $e = new AJXP_Exception("Error while loading repository feature : ".implode(",",$this->errors));
            throw $e;
        }

        $repository->driverInstance = $plugInstance;
        if (isSet($_SESSION["REPO_ID"]) && $_SESSION["REPO_ID"] == $repository->getId()) {
            $this->configs["REPOSITORY"] = $repository;
            $this->configs["REPOSITORIES"][$_SESSION['REPO_ID']] = $repository;
        }
        return $plugInstance;
    }

    /**
     * Search the manifests declaring ajxpdriver as their root node. Remove ajxp_conf & ajxp_shared
     * @static
     * @param string $filterByTagName
     * @param string $filterByDriverName
     * @param bool $limitToEnabledPlugins
     * @return string
     */
    public static function availableDriversToXML($filterByTagName = "", $filterByDriverName="", $limitToEnabledPlugins = false)
    {
        $nodeList = AJXP_PluginsService::searchAllManifests("//ajxpdriver", "node", false, $limitToEnabledPlugins);
        $xmlBuffer = "";
        foreach ($nodeList as $node) {
            $dName = $node->getAttribute("name");
            if($filterByDriverName != "" && $dName != $filterByDriverName) continue;
            if($dName == "ajxp_conf" || $dName == "ajxp_shared") continue;
            if ($filterByTagName == "") {
                $xmlBuffer .= $node->ownerDocument->saveXML($node);
                continue;
            }
            $q = new DOMXPath($node->ownerDocument);
            $cNodes = $q->query("//".$filterByTagName, $node);
            $nodeAttr = $node->attributes;
            $xmlBuffer .= "<ajxpdriver ";
            foreach($node->attributes as $attr) $xmlBuffer.= " $attr->name=\"$attr->value\" ";
            $xmlBuffer .=">";
            foreach ($cNodes as $child) {
                $xmlBuffer .= $child->ownerDocument->saveXML($child);
            }
            $xmlBuffer .= "</ajxpdriver>";
        }
        return $xmlBuffer;
    }

     /**
      * Singleton method
      *
      * @return ConfService the service instance
      */
     public static function getInstance()
     {
         if (!isSet(self::$instance)) {
             $c = __CLASS__;
             self::$instance = new $c;
         }
         return self::$instance;
     }
     private function __construct(){}
    public function __clone()
    {
        trigger_error("Cannot clone me, i'm a singleton!", E_USER_ERROR);
    }

}
