<?php

/**
 * Goweb Sitemanager 5 Framework
 *
 * @version    5.0.0
 * @category   Goweb
 * @package    Goweb_Updater
 * @author     I&D <dev@goweb.pt>, Pedro Pinto <ppinto@goweb.pt>
 * @copyright  2009 (c) Goweb (http://www.goweb.pt)
 */

/**
 *
 * @version    5.0.11
 * @category   Goweb
 * @package    Goweb_Updater
 * @author     I&D <dev@goweb.pt>, Pedro Pinto <ppinto@goweb.pt>
 * @copyright  2009 (c) Goweb (http://www.goweb.pt)
 */
class Goweb_Updater {

    /**
     * URL onde ir buscar os updates
     *
     */
    static private  $update_url = 'http://goweb.go/update/';
    
    protected $_tmpDir;
    
    public function __construct($tmpDir = null){
        if (is_null($tmpDir)) $tmpDir = CACHE_PATH . '/update/tmp/';
        $this->_tmpDir = $tmpDir;
        $this->_delTree(dirname($this->_tmpDir), true);
        if (Goweb::isOnline()){
            self::$update_url = 'http://www.goweb.pt/update/';
        }
    }


    public function isToUpdate(){
        goweb::loadClass('Goweb_Version');
        return Goweb_Version::compareVersion($this->getLatestVersion()) > 0 ? true : false;
    }

    public function getLatestVersion($filename = null){
        if (is_null($filename)){
            $filename = self::$update_url . 'version.txt';
        }
        $fd= fopen($filename, "r");
        if (!$fd){
            throw new Goweb_Exception('N&atilde;o foi poss&iacute;vel abrir o site da "Goweb" '.self::$update_url.'.');
        }
        $content= fread($fd, 1024);
        fclose($fd);
        if (empty ($content)){
            throw new Goweb_Exception('N&atilde;o foi poss&iacute;vel obter o ficheiro de vers&otilde;es do site da "Goweb".');
        }
        return $content;
    }

    public function download($file_source = null, $file_target = null){
        if (is_null($file_source)){
            $file_source = self::$update_url . 'latest.zip';
        }
        if (is_null($file_target)){
            $file_target = CACHE_PATH . '/update/latest.zip';
        }
        $this->_checkDir(dirname($file_target));
        $rh= fopen($file_source, 'rb');
        $wh= fopen($file_target, 'wb');
        if ($rh === false || $wh === false){
            if ($rh === false){
                throw new Goweb_Exception('N&atilde;o foi poss&iacute;vel ler o ficheiro ' . $file_source . '.');
            }else{
                throw new Goweb_Exception('N&atilde;o foi poss&iacute;vel escrever para o ficheiro ' . $file_target . '.');
            }
        }
        $i= 0;
        while (!feof($rh)){
            if (fwrite($wh, fread($rh, 1024)) === FALSE){
                throw new Goweb_Exception('N&atilde;o foi poss&iacute;vel escrever para o ficheiro ' . $file_target . '.');
            }
        }
        fclose($rh);
        fclose($wh);
        return true;
    }
    
    public function listContents($filename = null){
        $filename = $this->_checkFile(null, $filename);
        /**
         * para ter a certeza que o download foi mesmo efectuado
         */
        if (file_exists($filename)){
            goweb::loadClass('Goweb_Zip');
            $zip = Goweb_Zip::getInstance($filename);
            return $zip->listContent();
        } else {
            return false;
        }
    }
    
    public function extract($filename = null){
        $filename = $this->_checkFile(null, $filename);
        /**
         * para ter a certeza que o download foi mesmo efectuado
         */
        if (file_exists($filename)){
            goweb::loadClass('Goweb_Zip');
            $zip = Goweb_Zip::getInstance($filename);
            
            $this->_checkDir($this->_tmpDir);
            return $zip->extract($this->_tmpDir);
        } else {
            return false;
        }
    }
    
    protected function _checkFile($file_source = null, $filename = null){
        if (is_null($filename)){
            $filename = CACHE_PATH . '/update/latest.zip';
        }
        if (!file_exists($filename)){
            $this->download($file_source, $filename);
        }
        return $filename;
    }
    
    protected function _checkDir($dest = null){
        if (!file_exists($dest) && str_replace(APPLICATION_PATH, '', $dest) == $dest){
            @mkdir($dest, 0777, true);
            @chmod($dest, 0777);
        }
    }

    public function update($files = array(), $tmpDir = null){
        if (is_null($tmpDir)) $tmpDir = $this->_tmpDir;
        if (is_array($files) && count($files)> 0 ){
            require_once 'Zend/ProgressBar.php';
            require_once 'Zend/ProgressBar/Adapter/JsPush.php';

            $adapter     = new Zend_ProgressBar_Adapter_JsPush(array('updateMethodName' => 'Zend_ProgressBar_Update',
                                                         'finishMethodName' => 'Zend_ProgressBar_Finish'));
            $progressBar = new Zend_ProgressBar($adapter, 0, 100);


            goweb::loadClass('Zend_Log_Writer_Stream');
            goweb::loadClass('Zend_Log');
            $updateTime = date('Y-m-d_H.i.s');
            $writerFile = new Zend_Log_Writer_Stream(CACHE_PATH . '/update/update.txt');
            $logger = new Zend_Log();
            $logger->addWriter($writerFile);
            goweb::loadClass('Zend_Version');
            if (Zend_Version::compareVersion('1.8.0') <= 0){
                goweb::loadClass('Goweb_Mail');
                goweb::loadClass('Zend_Log_Writer_Mail');
                $mail = new Goweb_Mail();
                $mail->setSupportMail();
                $config = goweb::registry('config');
                $writerMail = new Zend_Log_Writer_Mail($mail);
                // Set subject text for use; summary of number of errors is appended to the
                // subject line before sending the message.
                $writerMail->setSubjectPrependText('Actualização do projecto ' . Zend_Controller_Front::getInstance()->getRequest()->getHttpHost());
                // Only email warning level entries and higher.
                if (!$config->debug) $writerMail->addFilter(Zend_Log::WARN);
                $logger->addWriter($writerMail);
            }
            $total = count($files);
            $count = 0 ;
            foreach ($files as $file){
                $list = glob(ROOT_PATH . $file . ".bak.*");
                // remover copias antigas
                if (count($list) > 3){
                    usort($list, create_function('$a,$b', 'return filemtime($a) - filemtime($b);'));
                    /*
                     * manter somente as últimas 3 cópias (Se for possível apagar)
                     */
                    for ($i = 3; $i<count($list); $i++){
                        if (is_writable($list[$i])){
                            if (@unlink($list[$i])){
                                $logger->info('O ficheiro "' . $list[$i] . '" foi removido');
                            } else {
                                $logger->err('Ocorreu um erro ao tentar remover ficheiro "' . $list[$i] . '".');
                            }
                        } else {
                            $logger->err('Ocorreu um erro ao tentar remover ficheiro ' . $list[$i] . ', corrigir permissoes.');
                        }
                    }
                }

                // efectuar copias de segurança
                $bakFile = $file . '.bak.' . $updateTime;
                $this->_checkDir(dirname(ROOT_PATH . $file));
                if (file_exists(ROOT_PATH . $file)){
                    if (@rename(ROOT_PATH . $file, ROOT_PATH . $bakFile)){
                        $logger->info('O ficheiro "' . $file . '" foi renomeado para "' . $bakFile . '".');
                        //substituir ficheiros
                        if (@copy($tmpDir . $file, ROOT_PATH . $file)){
                            @chmod(ROOT_PATH . $file, 0777);
                            $logger->info('O ficheiro "' . $file . '" foi actualizado.');
                        } else {
                            @rename(ROOT_PATH . $bakFile, ROOT_PATH . $file);
                            $logger->err('Ocorreu um erro ao tentar substituir o ficheiro "' . $file . '" rollback com o ficheiro "' . $bakFile . '".');
                        }
                    } else {
                        $logger->err('Ocorreu um erro ao tentar renomear o ficheiro "' . $file . '" para ' . $bakFile . '".');
                    }
                } else {
                    if (is_dir(dirname(ROOT_PATH . $file))){
                        if (@copy($tmpDir . $file, ROOT_PATH . $file)){
                            @chmod(ROOT_PATH . $file, 0777);
                            $logger->info('O ficheiro "' . $file . '" foi criado.');
                        }
                    }
                }
                $count++;
                $progressBar->update(round(($count/$total)*100, 2), round(($count/$total)*100, 2) . '%');
                // Now, again, force this to be
                // immediately displayed:
                @ob_flush();
                @flush();
                @ob_end_flush();
            }
            foreach (array('Sistema_Model_Utilizador', 'Utilizadores_Model_Utilizador') as $modelName){
                try {
                    $model = Goweb::getModelInstance($modelName, 'db');
                    if ($model->colExists('password')){
                        $result = $model->fetchAll();
                        if ($result){
                            foreach ($result as $item){
                                if (strlen($item->password) != 40){
                                    if ($model->find($item->id)){
                                        $model->password = sha1($item->password);
                                        $model->save();
                                    }
                                }
                            }
                        }
                    }
                    
                } catch (Exception $e) {
                    
                }
            }
            $progressBar->finish();
            
        } else {
            return false;
        }
        
    }
    
    public function compareMd5($files = array(), $tmpDir = null){
        if (is_null($tmpDir)) $tmpDir = $this->_tmpDir;
        $differences = array();
        foreach ($files as $file => $md5){
            if(file_exists($tmpDir . $file)){
                if(file_exists(ROOT_PATH . $file)){
                     if (strcmp(md5_file(ROOT_PATH . $file), $md5) !== 0){
                         $differences[] = $file;
                     }
                } else {
                    $differences[] = $file;
                }
            }
        }
        if (count($differences)>0) return $differences;
        return true;
    }

    public function getMd5($dir = null){
        if (is_null($dir)) $dir = $this->_tmpDir;
        if (substr($dir, -1) == '/' || substr($dir, -1) == DIRECTORY_SEPARATOR) $dir = substr($dir,0 , -1);
        return $this->_md5Dir($dir, $dir);
    }
    

    protected function _md5Dir($dir, $firstDir){
        if (!is_dir($dir)){
            return false;
        }
         
        $filemd5s = array();
        $d = dir($dir);

        while (false !== ($entry = $d->read())){
            if ($entry != '.' && $entry != '..'){
                if (is_dir($dir.'/'.$entry)){
                    $filemd5s = array_merge($filemd5s, $this->_md5Dir($dir.'/'.$entry, $firstDir));
                } else {
                    $filemd5s[str_replace($firstDir, '', $dir.'/'.$entry)] = md5_file($dir.'/'.$entry);
                }
            }
        }
        $d->close();
        return $filemd5s;
    }
// to use this function to totally remove a directory, write:
// _delTree('path/to/directory/to/delete');

// to use this function to empty a directory, write:
// _delTree('path/to/full_directory',TRUE);

    protected function _delTree($directory, $empty=FALSE){
        // if the path has a slash at the end we remove it here
        if(substr($directory,-1) == '/')
        {
            $directory = substr($directory,0,-1);
        }

        // if the path is not valid or is not a directory ...
        if(!file_exists($directory) || !is_dir($directory))
        {
            // ... we return false and exit the function
            return FALSE;

            // ... if the path is not readable
        }elseif(!is_readable($directory))
        {
            // ... we return false and exit the function
            return FALSE;

            // ... else if the path is readable
        }else{

            // we open the directory
            $handle = opendir($directory);

            // and scan through the items inside
            while (FALSE !== ($item = readdir($handle)))
            {
                // if the filepointer is not the current directory
                // or the parent directory
                if($item != '.' && $item != '..')
                {
                    // we build the new path to delete
                    $path = $directory.'/'.$item;

                    // if the new path is a directory
                    if(is_dir($path))
                    {
                        // we call this function with the new path
                        $this->_delTree($path);

                        // if the new path is a file
                    }else{
                        // we remove the file
                        @unlink($path);
                    }
                }
            }
            // close the directory
            closedir($handle);

            // if the option to empty is not set to true
            if($empty == FALSE)
            {
                // try to delete the now empty directory
                if(!@rmdir($directory))
                {
                    // return false if not possible
                    return FALSE;
                }
            }
            // return success
            return TRUE;
        }
    }
}