<?php
/**
 * La classe 'geocimetiere' est une classe d'abstraction, spécifique à openCimetière,
 * permettant de gérer les requêtes vers divers webservices SIG et ainsi
 * proposer aux utilisateurs des informations géographiques.
 * Cette classe est instanciée et utilisée par d'autres scripts pour
 * gérer notamment l'accès à l'emprise de l'emplacement et ce peu importe le SIG utilisé.
 * Son objectif est d'instancier les classes spécifiques aux SIG aussi appelées
 * connecteurs correspondant au paramétrage de la collectivité.
 *
 * Ces connecteurs héritent de la classe 'geocimetiere_base' qui leur sert de modèle.
 *
 * Enfin la classe 'geocimetiere_exception' permet de gérer les erreurs.
 * Plusieurs classes en héritent afin de spécifier le type d'exception.
 *
 * @package opencimetiere
 * @version SVN : $Id$
 */
if (defined('PATH_OPENMAIRIE') !== true) {
    define('PATH_OPENMAIRIE', '../core/');
}
require_once PATH_OPENMAIRIE."om_logger.class.php";

/**
 * Abstracteur de la géolocalisation spécifique à openCimetière
 */
class geocimetiere {

    /**
     * Cet attribut permet de stocker l'instance du connecteur SIG utilisé.
     * Sa valeur doit être remplie en fonction du paramétrage de la collectivité.
     *
     * @var object  instance du connecteur SIG
     */
    var $sig = null;

    /**
     * Paramètres contenant les méthodes requises
     *
     * @var array
     */
    var $required_methods = array();

    /**
     * Le constructeur instancie la classe du SIG envoyée par le paramétrage
     * de la collectivité.
     *
     * @param  array  $collectivite  Identifiant de la collectivité.
     * @param  array  $extra_params  Tableau de paramètres en extra
     *
     * @return geocimetiere Instance geocimetiere
     */
    public function __construct(array $collectivite, array $extra_params = array()) {
        // Récupération de la conf sig de la collectivité
        $this->collectivite = $collectivite;
        if(!isset($this->collectivite['sig'])) {
            throw new geocimetiere_configuration_exception("Pas de SIG configuré pour la collectivité");
        }
        // instanciation du connecteur
        $path = "";
        if(isset($this->collectivite['sig']['path'])) {
            $path = $this->collectivite['sig']['path'];
        }
        $connecteur = 'geocimetiere_'.$this->collectivite['sig']['connector'];
        require_once $path.$connecteur.'.class.php';
        $this->sig = new $connecteur($this->collectivite, $extra_params);
        if (! empty($this->sig->sig_parameters['required_methods'])) {
            $this->required_methods = $this->sig->sig_parameters['required_methods'];
        }
        $this->validate_required_methods();
    }

    /**
     * Le destructeur permet de détruire la ressource instanciée
     * dans le constructeur
     */
    public function __destruct() {
        if ($this->sig != null) {
            unset($this->sig);
        }
    }

    /**
     * Permet de vérifier que les méthodes requises
     * sont bien implémentées
     *
     * @return void
     */
    protected function validate_required_methods() {
        $missing_methods = array();

        foreach ($this->required_methods as $method) {
            if (!$this->methodIsImplemented($method)) {
                $missing_methods[] = $method;
            }
        }
        if (!empty($missing_methods)) {
            throw new Exception(
                "Méthodes requises non implémentées dans " . get_class($this) . ": " .
                implode(', ', $missing_methods)
            );
        }
    }

     /**
     * Vérifie si le connecteur implémente une certaine méthode (publique).
     *
     * @param  string  $name  Nom de la méthode
     * @param    bool  $self  'true' pour que la méthode ne soit pas héritée
     *
     * @return bool  'true' si la méthode est implémentée par le connecteur
     */
    public function methodIsImplemented(string $method, bool $self = true) {
        if (! empty($this->sig)) {
            $class = new ReflectionClass($this->sig);
            $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
            $classname = get_class($this->sig);
            $methodsFiltered = array_filter(
                $methods,
                function ($m) use ($method, $classname, $self) {
                    return $m->name == $method && (! $self || $m->class == $classname);
                });
            return ! empty($methodsFiltered);
        }
        return false;
    }


    /**
     * Redirection vers le SIG dans le contexte de visualisation du dossier.
     * Si les deux arguments sont nuls, c'est l'url par défaut du sig qui doit
     * être retourné.
     *
     * @param string $contexte Détermine dans quel contexte on souhaite accéder au SIG
     *
     * @return string L'url du SIG
     */
    public function redirection_web(string $contexte = null) {
        return $this->sig->redirection_web($contexte);
    }

}

/**
 * Classe parente de tous les connecteurs SIG
 */
class geocimetiere_base extends geocimetiere {

    /**
     * Paramètres de connexion au sig
     *
     * @var array
     */
    var $sig_parameters = array();

    /**
     * Paramètres de la collectivite
     *
     * @var array
     */
    var $collectivite_parameters = array();

    /**
     * Tableau de paramètres en extra
     *
     * @var array
     */
    protected $extra_params = array();


    /**
     * Redirection vers le SIG dans le contexte de visualisation du dossier.
     * Si les deux arguments sont nuls, c'est l'url par défaut du sig qui doit
     * être retourné.
     *
     * @param string $contexte Détermine dans quel contexte on souhaite accéder au SIG
     *
     * @return string L'url du SIG
     */
    public function __construct(array $collectivite, array $extra_params = array()) {
        // Liste des paramètres extra
        $this->extra_params = $extra_params;
        // Config du connecteur SIG de la collectivité en attribut
        $this->set_sig_config($collectivite['sig']);
    }

    public function redirection_web(string $contexte = null) {
        throw new geocimetiere_connector_method_not_implemented_exception();
    }

    /**
     * Défini l'attribut contenant le tableau de configuration du SIG.
     *
     * @param array $conf Tableau de config.
     */
    public function set_sig_config(array $conf) {
        $this->sig_parameters = $conf;
    }

    /**
     * Permet de récupérer un élement de configuration.
     *
     * @param string $key Nom de la clé de l'élément.
     *
     * @return string Valeur de la config.
     */
    public function get_sig_config($key) {
        if(isset($this->sig_parameters[$key]) === false) {
            throw new geocimetiere_configuration_exception("Élément de configuration '$key' non trouvé");
        }
        return $this->sig_parameters[$key];
    }

    /**
     * Permet de récupérer un élement de paramétrage de la collectivité.
     *
     * @param string $key Nom de la clé de l'élément.
     *
     * @return string Valeur de la config.
     */
    public function get_collectivite_parameter($key) {
        if(isset($this->collectivite_parameters[$key]) === false) {
            throw new geocimetiere_configuration_exception("Élément de paramétrage '$key' non trouvé");

        }
        return $this->collectivite_parameters[$key];
    }

    /**
     * Permet de lancer une requête sql dans la base de données de l'application.
     * La requête sql doit être un SELECT.
     *
     * @param  string  $query_sql  Requête sql à exécuter.
     *
     * @return array|boolean Résultat de la requête sql ou false.
     */
    protected function query_db($query_sql, $force_return = true) {
        if (isset($this->extra_params['inst_framework']) === false) {
            throw new geocimetiere_exception(__("Il est nécessaire de passer l'instanciation du framework en paramètre pour réaliser des requête en base de données depuis un connecteur SIG."));
        }
        $inst_framework = $this->extra_params['inst_framework'];
        if (stripos($query_sql, 'SELECT') === false) {
            throw new geocimetiere_exception('Seules les requêtes SQL SELECT sont autorisées.');
        }
        return $inst_framework->get_all_results_from_db_query(
            $query_sql,
            array(
                "origin" => __METHOD__,
                "force_return" => $force_return,
            )
        );
    }

}

/**
 * Classe gérant les erreurs
 */
class geocimetiere_exception extends Exception {

    public function __construct($message = "", $code = 0, Exception $previous = null) {
        parent::__construct($message, $code, $previous);
        logger::instance()->writeErrorLogToFile();
        logger::instance()->cleanLog();
    }

    protected function add_to_log($code, $message) {
        logger::instance()->log(sprintf(
            "Geocimetiere Connector - Error code : %s -> %s",
            $code,
            $message
        ));
    }
}

class geocimetiere_configuration_exception extends geocimetiere_exception {

    public function __construct($message = null) {
        $ret = trim(sprintf(
            '%s %s %s',
            __('Erreur de configuration du SIG.'),
            __('Veuillez contacter votre administrateur.'),
            $message
        ));
        parent::__construct($ret);
    }
}

class geocimetiere_parameter_exception extends geocimetiere_exception {

    public function __construct($message = null) {
        $ret = trim(sprintf(
            '%s %s %s',
            __("Les paramètres d'appel au SIG ne sont pas valides."),
            __('Veuillez contacter votre administrateur.'),
            $message
        ));
        parent::__construct($ret);
    }
}

class geocimetiere_connector_exception extends geocimetiere_exception {

    public function __construct($message = null, $code = null) {
        // Log de l'erreur technique
        $this->add_to_log($code, $message);
        // Log utilisateur
        $ret = trim(sprintf(
            '%s %s %s',
            __('Erreur du SIG.'),
            __('Veuillez contacter votre administrateur.'),
            $message
        ));
        parent::__construct($ret);
    }
}

class geocimetiere_connector_method_not_implemented_exception extends geocimetiere_exception {

    public function __construct($message = null) {
        $ret = trim(sprintf(
            '%s %s %s',
            __("La méthode n'est pas implémentée dans le connecteur."),
            __('Veuillez contacter votre administrateur.'),
            $message
        ));
        parent::__construct($ret);
    }
}
