<?php

//Définition de la classe client
class client {

    const publicKey = 'DK_LOG_KEY';
    const access_log = 'logs/access.log'; //'../../logs_agent_client/access.log';
    const errors_log = 'logs/access.log'; //'../../logs_agent_client/errors.log';

    private $privateKey;
    private $sigb;
    private $ip;

    public $idclient;
    public $classPath;
    public $isoexportCall;
    public $isoexportPath;
    public $allowedExportTime;
    public $activateExport;
    public $methodSendExport;
    public $paramsSendExport;
    public $dirSendExport;
    public $compressExport;
    public $databaseName;
    public $hostData;
    public $portData;
    public $userData;
    public $passData;
    public $hostApi;
    public $portApi;
    public $keyApi;
    public $institutionId;
    public $wsPath;
    public $logActivate;

    function __construct($idclient, $sigb) {
        date_default_timezone_set('Europe/Paris');
        
        if (!file_exists('logs')) mkdir('logs');
        $this->idclient = $idclient;
        $this->ip = $_SERVER['REMOTE_ADDR'];
        $this->sigb = $sigb;
        $this->displayInfos();
        if (!empty($this->idclient)) $this->readConfig($this->idclient);
    }

    //Récupération de la clé privé
    private function readConfig($idClient) {
        $ini_path = '';
        if (file_exists('config/'.$idClient.'/config.ini')) $ini_path = 'config/'.$idClient.'/config.ini';
        elseif (file_exists('config/config.ini')) $ini_path = 'config/config.ini';
        elseif (file_exists('config/config_orig.ini')) $ini_path = 'config/config_orig.ini';
        if (!empty($ini_path)) {
            try {
                $aTabConfig = @parse_ini_file($ini_path, true);
                if (empty($aTabConfig)) throw new Exception();
                $this->privateKey = $aTabConfig['config']['key'];
                $this->classPath = $aTabConfig['config']['classPath'];
                $this->methodSendExport = $aTabConfig['config']['methodSendExport'];
                $this->activateExport = $aTabConfig['config']['activateExport'];
                $this->paramsSendExport = $aTabConfig[$this->methodSendExport];
                $this->dirSendExport = $aTabConfig['config']['dirSendExport'];
                $this->compressExport = $aTabConfig['config']['compressExport'];
                $this->logActivate = (array_key_exists('logActivate', $aTabConfig['config']) && $aTabConfig['config']['logActivate'] == 'T') ? true : false;
                if (!empty($this->sigb) && isset($aTabConfig[$this->sigb])) {
                    $this->databaseName = $aTabConfig[$this->sigb]['databaseName'];
                    $this->databaseID = $aTabConfig[$this->sigb]['databaseID'];
                    $this->hostData = $aTabConfig[$this->sigb]['hostData'];
                    $this->portData = $aTabConfig[$this->sigb]['portData'];
                    $this->userData = $aTabConfig[$this->sigb]['userData'];
                    $this->passData = $aTabConfig[$this->sigb]['passData'];
                    $this->isoexportCall = $aTabConfig[$this->sigb]['isoexportCall'];
                    $this->isoexportPath = $aTabConfig[$this->sigb]['isoexportPath'];
                    $this->allowedExportTime = $aTabConfig[$this->sigb]['allowedExportTime'];
					$this->hostApi = array_key_exists('hostApi', $aTabConfig[$this->sigb]) ? $aTabConfig[$this->sigb]['hostApi'] : null;
					$this->portApi = array_key_exists('portApi', $aTabConfig[$this->sigb]) ? $aTabConfig[$this->sigb]['portApi'] : null;
					$this->keyApi = array_key_exists('keyApi', $aTabConfig[$this->sigb]) ? $aTabConfig[$this->sigb]['keyApi'] : null;
                    $this->institutionId = array_key_exists('institutionId', $aTabConfig[$this->sigb]) ? $aTabConfig[$this->sigb]['institutionId'] : null;
                    $this->wsPath = array_key_exists('wsPath', $aTabConfig[$this->sigb]) ? $aTabConfig[$this->sigb]['wsPath'] : "";
                }
                $this->returnLog('Clé privée : '.$this->privateKey);
                $this->returnLog('Chemin config  : '.$ini_path);
            }
            catch (Exception $e) {
                $this->returnError('Impossible de lire le fichier de configuration client', '530', true);
            }
        }
        else $this->returnError('Impossible de lire le fichier de configuration client', '531', true);
    }

    //Vérification de la clé de connexion
    public function checkKey($sKey) {
        return $this->checkMD5($sKey, self::publicKey.$this->privateKey);
    }

    //Vérification du MD5 sur chaine ou un fichier (si bFile = true, vérification sur un fichier)
    private function checkMD5($sChaine, $sRef, $bFile = false) {
        $fonctionMD5 = '';
        $date = '';
        if ($bFile) {
            $fonctionMD5 = md5_file;
        }
        else {
            $fonctionMD5 = md5;
            $date = date('Ymd');
        }
        if ($sChaine == $fonctionMD5($sRef.$date)) {
            return true;
        }
        else {
            return false;
        }
    }

    //Dezip des classes
    public function unzip($sFile, $sMD5) {
        //Vérification de l'intégrité de l'archive
        $sFile = $this->classPath.$sFile;
        if ($this->checkMD5($sMD5, $sFile, true)) {
            try {
                if (!touch("./lock")) throw new Exception('Erreur création lock'); //Activer sémaphore
                $zip = new ZipArchive();
                $res = $zip->open($sFile);
                if ($res !== TRUE) throw new Exception('Impossible d\'ouvrir l\'archive, fichier introuvable');
                $res = $zip->extractTo("./".$this->classPath);
                if ($res == FALSE) throw new Exception('Impossible d\'extraire l\'archive');
                if (!$zip->close()) throw new Exception('Impossible de fermer l\'archive');
                if (!unlink($sFile)) throw new Exception('Impossible de supprimer l\'archive');
                $this->returnLog('MD5 Ok, archive décompressée, classe installée');
            }
            catch (Exception $e) {
                $this->returnError('Erreur dezip : '.$e->getMessage(), '560', true);
                if (file_exists("./lock")) unlink("./lock"); //Terminer sémaphore
                return false;
            }
        }
        else {
            $this->returnError('Fichier ZIP corrompu (mauvais hash md5)', '500', true);
            return false;
        }
        if (file_exists("./lock")) unlink("./lock"); //Terminer sémaphore
        return true;
    }

    //Création de l'archive pour l'export
    public function zip($sDirname, $sName) {
        try {
            $extFinale = '';
            $tabFiles = array();
            $tabFilesUnlink = array();
            $zip = new ZipArchive();
            $filename = $sDirname.$sName.".zip";
            $res = $zip->open($filename, ZIPARCHIVE::CREATE);
            if ($res != TRUE) throw new Exception('open : '.$res);
            $tabFiles = $this->listDir($sDirname, array('iso', 'xml'));
            foreach ($tabFiles as $file) {
                $ext = substr(strrchr($file, '.'), 1);
                $tmpPath = str_replace('\\', '/', $file);
                if (strrchr($tmpPath, '/')) $tmpName = substr(strrchr($tmpPath, '/'), 1, -(strlen($ext) + 1));
                else $tmpName = substr($file, 0, -(strlen($ext) + 1));
                //Ne garde que les fichiers du même nom que filename (sName)
                if ($ext == 'iso' && $tmpName == $sName) $extFinale = 'data';
                if ($ext == 'xml' && $tmpName == $sName) $extFinale = 'info_0';
                if (!empty($extFinale)) {
                    if (!$zip->addFile($sDirname.$file, substr($file, 0, -4).'.'.$extFinale)) throw new Exception('Impossible d\'ajouter '.$file);
                    $tabFilesUnlink[] = $file;
                }
                $extFinale = '';
            }
            if (!$zip->close()) throw new Exception('Impossible de fermer l\'archive');
            foreach ($tabFilesUnlink as $file) unlink($sDirname.$file);
            $this->returnLog('Archive de l\'export "'.$sName.'" créée avec les fichiers : '.implode(", ", $tabFilesUnlink));
            //Calcul MD5 du fichier
            $md5file = md5_file($filename);
            return $sName.".zip|".$md5file;
        }
        catch (Exception $e) {
            $this->returnError('Erreur ZIP : '.$e->getMessage(), '510', true);
        }
    }

    //Lister le contenu d'un répertoire
    private function listDir($sDirname, $aExtensions = array()) {
        try {
            $tabFiles = array();
            $handle = @opendir($sDirname);
            if (!$handle) throw new Exception('Ouverture de fichier');
            while ($file = @readdir($handle)) {
                if (substr($file, 0, 1) != ".") {
                    if (!empty($aExtensions) && in_array(substr(strrchr($file, "."), 1), $aExtensions)) $tabFiles[] = $file;
                    elseif (empty($aExtensions)) $tabFiles[] = $file;
                }
            }
            closedir($handle);
            return $tabFiles;
        }
        catch (Exception $e) {
            $this->returnError('Erreur listDir : '.$e->getMessage(), '510', true);
        }
    }

    //Fonction de décryptage des paramètres
    public function decryptParams($sIdClient, $encrypted) {
        $tabParams = array();
        $tabParamsFinal = array();
        $decrypted = $this->decrypt($encrypted, (strlen($sIdClient) < 11 ? $sIdClient : substr($sIdClient, -11)));

        //Découpage des paramètres décryptés
        $tabParams = explode("&", substr($decrypted, 1));
        foreach ($tabParams as $param) {
            list($paramName, $paramValue) = explode("=", $param, 2);
            $tabParamsFinal[$paramName] = $paramValue;
        }
        return $tabParamsFinal;
    }

    //Fonction décryptant le fichier de config reçu
    public function decryptConfig($sFilePath, $sSubIpClient) {
        try {
            if (!$contenu = file_get_contents($sFilePath)) throw new Exception('Impossible de lire le fichier de configuration reçu');
            $contenuDecrypt = $this->decrypt($contenu, $sSubIpClient);
            if (!file_put_contents($sFilePath, $contenuDecrypt)) throw new Exception('Impossible de réécrire le fichier de configuration décrypté');
        }
        catch (Exception $e) {
            $this->returnError('Erreur : '.$e->getMessage(), '500', true);
        }
    }

    //Fonction de décryptage générale
    private function decrypt($sCryptedText, $sPartKey) {
        $key = $sPartKey.date('Ymd').'OPAC2';
        $iv = '12345678';

        $cipher = mcrypt_module_open(MCRYPT_3DES,'','cbc','');

        mcrypt_generic_init($cipher, $key, $iv);
        $temp = base64_decode($sCryptedText);
        $decrypted = trim(mdecrypt_generic($cipher,$temp));
        mcrypt_generic_deinit($cipher);
        return $decrypted;
    }

    public function sendExport($sEtablissement, $sMode, $sLocalPath, $sDestPath, $aExtensions) {
        $pathComplet = $sDestPath.$sEtablissement;
        $tabExport = $this->listDir($sLocalPath, $aExtensions);
        switch($sMode) {
            case 'direct' :
                try {
                    if (!file_exists($pathComplet)) {
                        $mkdir = @mkdir($pathComplet);
                        if (!$mkdir) throw new Exception('Impossible de créer le dossier '.$pathComplet);
                    }
                    foreach ($tabExport as $file) {
                        if (!@rename($sLocalPath.$file, $pathComplet.'/'.$file)) throw new Exception('Impossible de déplacer le fichier '.$file);
                    }
                }
                catch (Exception $e) {
                    $this->returnError('Erreur : '.$e->getMessage(), '510', true);
                }
                break;

            case 'ftp' :
                try {
                    $this->returnLog('Connexion au FTP '.$this->paramsSendExport['hostFTP'].'...');
                    if (!$ressource_ftp = @ftp_connect($this->paramsSendExport['hostFTP'], (!empty($this->paramsSendExport['portFTP']) ? $this->paramsSendExport['portFTP'] : 21))) throw new Exception('Impossible de se connecter au FTP '.$this->paramsSendExport['hostFTP']);
                    if (!@ftp_login($ressource_ftp, $this->paramsSendExport['userFTP'], $this->paramsSendExport['passFTP'])) throw new Exception('Impossible s\'authentifier sur le FTP');
                    $this->returnLog('Connecté au FTP');
                    if (!@ftp_pasv($ressource_ftp, true)) throw new Exception('Impossible de passer en mode passif sur le FTP');
                    $dirCible = @ftp_nlist($ressource_ftp, $sDestPath); //Création du dossier établissement si inexistant
                    if (!in_array($pathComplet, $dirCible)) {
                        if (!@ftp_mkdir($ressource_ftp, $pathComplet)) throw new Exception('Impossible de créer le dossier '.$pathComplet);
                    }
                    foreach ($tabExport as $file) {
                        if (!@ftp_put($ressource_ftp, $pathComplet.'/'.$file.'_0', $sLocalPath.$file, FTP_BINARY)) throw new Exception('Impossible d\'envoyer le fichier '.$file);
                        //Renomme le zip avec la bonne extension pour déverouiller
                        if (!@ftp_rename($ressource_ftp, $pathComplet.'/'.$file.'_0', $pathComplet.'/'.$file)) throw new Exception ('Impossible de renommer le fichier en '.$file);
                        unlink($sLocalPath.$file);
                    }
                    $this->returnLog('Fichiers envoyés sur le FTP');
                }
                catch (Exception $e) {
                    $this->returnError('Erreur : '.$e->getMessage(), '510', true);
                }
                break;
                
            case 'sftp' :
                require("class/sftp.class.php");
                $sftp = new SFTP($sEtablissement);
                if ($sftp->connect()) $sftp->sendDir($sLocalPath, $aExtensions); //envoi de toutes les archives si il en reste
                break;

            default :
                break;
        }
    }

    public function returnError($sError, $sErrorCode, $bExit = false) {
        file_put_contents(self::errors_log, date("Ymd-H:i:s")." >> Client : Erreur : ".$sError." (code : ".$sErrorCode.")"."\r\n", FILE_APPEND);
        if ($bExit) exit();
    }

    public function returnLog($sLog) {
        if ($this->logActivate == true) {
            file_put_contents(self::access_log, date("Ymd-H:i:s") . " >> Client : " . $sLog . "\r\n", FILE_APPEND);
        }
    }

    //Affichage des infos de la classe
    private function displayInfos() {
        $this->returnLog('Création classe cliente -- IP : '.$this->ip);
    }
	
}

?>