<?php

require_once dirname(dirname(__FILE__)) . "/config/config.php";

class Installer_API extends API {

    public $installed_files = array();
    public $dependency_files = array();
    
    private $instance = NULL;
    private $db = NULL;

    public function __call($name, $arguments) {
        exit ("The method '" . $name . "' doesn't exist, maybe an Installer Upgrade can fix this.");
    }

    public function __construct() {
        $this->db = IEM::getDatabase();
    }

    public function apply_patches($addon=false) {
        if( $addon ) {
            $addon_patch_file = dirname(dirname(__FILE__)) . "/../" . $addon . "/schema.mysql.patches.php";
            if( file_exists($addon_patch_file) ) {
                require_once $addon_patch_file;
                if( !isset( $queries ) || !is_array($queries) ) {
                    return false;
                }
                foreach( $queries as $query ) {
                    $qry = str_replace("%%TABLEPREFIX%%", $this->db->TablePrefix, $query);
                    $result = $this->db->Query($qry);
                }
            }
        }
    }

    public static function Singleton() {
        if( !self::$instance ) {
            self::$instance = new Installer_API();
        }

        return self::$instance;
    }

    public function register($addon="none") {
        $this->unregister($addon);
        $next_priority = $this->get_priority_number();
        $result = $this->db->InsertQuery("addon_installer", array( "addon" => $this->db->Quote($addon) , "priority" => $next_priority , "installed" => $this->db->Quote(time()) ));
        return $result;
    }

    public function is_registered($addon="none") {
        $result = $this->db->Query("SELECT * FROM " . SENDSTUDIO_TABLEPREFIX . "addon_installer WHERE addon='" . $this->db->Quote($addon) . "'");
        $r = $this->db->Fetch($result);
        return $r ? true : false;
    }

    public function unregister($addon="none") {
        $this->db->Query("DELETE FROM " . SENDSTUDIO_TABLEPREFIX . "addon_installer WHERE addon='" . $addon . "'");
    }

    public function get_priority_number() {
        $next = $this->db->Fetch($this->db->Query("SELECT COUNT(*) as total FROM " . SENDSTUDIO_TABLEPREFIX . "addon_installer"));
        return ( (int) ($next["total"]) + 1 );
    }

    public function check_uninstall_dependency($addon="none") {
        if( !$this->is_registered($addon) ) {
            return true;
        }

        $result = $this->db->Query("SELECT * FROM " . SENDSTUDIO_TABLEPREFIX . "addon_installer ORDER BY uid DESC");
        $ap = array();
        while( $row = $this->db->Fetch($result) ) {
            if( $row["addon"] == $addon ) {
                break;
            }
            $ap[] = $row;
        }

        $this->dependency_files = $this->get_file_dependency_schemas($addon, $ap);
        $dependency_files_errors = $this->check_dedendency_files($this->dependency_files);
        if( $dependency_files_errors ) {
            throw new Exception("Uninstall Dependency errors:<br><br>" . implode("<br>", $dependency_files_errors));
        }

        $this->uninstall_file_dependency($this->dependency_files);
    }

    public function uninstall_file_dependency($dependency_files=array(  )) {
        $errors = array();
        foreach( $dependency_files as $k => $v ) {
            foreach( $v as $_k => $_v ) {
                $result = $this->uninstall_file($k, $_v);
                $errors = array_merge($errors, $result);
            }
        }

        return $errors;
    }

    public function install_file_dependency($dependency_files=array(  )) {
        $errors = array();
        $dependency_files = array_reverse($this->dependency_files, false);
        foreach( $dependency_files as $k => $v ) {
            foreach( $v as $_k => $_v ) {
                $result = $this->install_file($k, $_v);
                $errors = array_merge($errors, $result);
            }
        }

        return $errors;
    }

    public function check_dedendency_files($dependency_array=array(  )) {
        $errors = array();
        foreach( $dependency_array as $key => $value ) {
            foreach( $value as $k => $v ) {
                $file = realpath($v["file"]);
                if( !is_writable($file) ) {
                    $errors[] = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Addon [" . $key . "] file not writable: " . $file;
                }
            }
        }

        if( 0 < count($errors) ) {
            return $errors;
        }

        return false;
    }

    public function get_file_dependency_schemas($addon="none", $addons=array(  )) {
        if( is_string($addons) ) {
            $addons = array( $addons );
        }

        $d = array();
        foreach( $addons as $k => $v ) {
            $s = $this->get_file_dependency_schema($addon, $v["addon"]);
            if( $s ) {
                $d[$v["addon"]] = $s;
            }
        }

        return $d;
    }

    public function get_file_dependency_schema($from, $in) {
        $schema_from = $this->get_schema_content($from);
        $schema_in = $this->get_schema_content($in);
        $s = array();
        foreach( $schema_from as $k => $v ) {
            foreach( $schema_in as $l => $b ) {
                if( md5(realpath($v["file"])) == md5(realpath($b["file"])) ) {
                    $s[] = $b;
                }
            }
        }

        return ( 0 < count($s) ? $s : false );
    }

    public function resolve_dependency($addon="none") {

    }

    public function install($addon="none") {
        $this->installed_files = array();
        $install_file_schema = $this->get_schema($addon);
        if( !file_exists($install_file_schema) ) {
            $this->log($addon, array( "Install Schema file, doesnt exist" ));
            return true;
        }

        $files = $this->get_schema_content($addon);
        $errors = array();
        if( isset( $files ) && is_array($files) && 0 < count($files) ) {
            $backup_path = $this->get_backup_path($addon);
            if( is_writable($backup_path) ) {
                $this->register($addon);
                foreach( $files as $key => $file ) {
                    $result = $this->install_file($addon, $file);
                    $errors = array_merge($errors, $result);
                }

            }
            else {
                $errors[] = "The folder( " . $backup_path . " ) is not writable";
            }

        }

        $this->log($addon, $errors);
        if( 0 < count($errors) ) {
            $this->unregister($addon);
            throw new Exception("Unable to install addon ( " . $addon . " ): <br>" . implode("<br>", $errors));
        }

        return true;
    }

    public function get_schema($addon="none") {
        $system_info = $this->system_info($addon);
        $install_file_schema_version_A = IEM_ADDONS_PATH . "/" . $addon . "/install/install.schema." . $system_info["ProductVersion"] . ".patch.php";
        $install_file_schema_version_B = IEM_ADDONS_PATH . "/" . $addon . "/install/install.schema.patch.php";
        $install_file_schema_version_C = IEM_ADDONS_PATH . "/" . $addon . "/install/install.schema." . $system_info["ProductVersion"] . ".php";
        $install_file_schema_version_D = IEM_ADDONS_PATH . "/" . $addon . "/install/install.schema.php";
        if( defined("INSTALLER_INSTALL_SCHEMA_PATCH") && INSTALLER_INSTALL_SCHEMA_PATCH == true ) {
            if( file_exists($install_file_schema_version_A) ) {
                return $install_file_schema_version_A;
            }

            if( file_exists($install_file_schema_version_B) ) {
                return $install_file_schema_version_B;
            }

        }

        if( file_exists($install_file_schema_version_C) ) {
            return $install_file_schema_version_C;
        }

        if( file_exists($install_file_schema_version_D) ) {
            return $install_file_schema_version_D;
        }

        return false;
    }

    public function get_schema_content($addon="none") {
        $files = array();
        $schema = $this->get_schema($addon);
        if( file_exists($schema) ) {
            require $schema;
        }

        return $files;
    }

    public function get_backup_path($addon="none") {
        return IEM_ADDONS_PATH . "/" . $addon . "/install/backup/";
    }

    public function get_install_file_content($addon="none") {
        $install_file_content = @file(IEM_ADDONS_PATH . "/" . $addon . "/install/data.php", FILE_IGNORE_NEW_LINES);
        return !$install_file_content ? "" : implode("", $install_file_content);
    }

    public function get_backup_file($addon="none", $fileinfo) {
        $file_to_replace = realpath($fileinfo["file"]);
        return $this->get_backup_path($addon) . md5($file_to_replace) . substr($file_to_replace, -4, 4);
    }

    public function rollback() {
    }

    public function install_file($addon, $file) {
        $errors = array();
        $file_to_replace = realpath($file["file"]);
        $backup_file = $this->get_backup_file($addon, $file);
        $install_file_content = $this->get_install_file_content($addon);
        if( is_writable($file_to_replace) ) {
            if( !copy($file_to_replace, $backup_file) ) {
                $errors[] = "Error creating backup file: " . $backup_file . " for " . $file_to_replace;
                $this->rollback();
            }
            else {
                $content = file($file_to_replace, FILE_IGNORE_NEW_LINES);
                foreach( $file["data"] as $k => $lv ) {
                    $lv = $this->insert_info($lv);
                    if( !$lv ) {
                        continue;
                    }
                    if( $file["type"] == "tpl" ) {
                        $an_o = "<!--";
                        $an_c = "-->";
                    }
                    else {
                        if( $file["type"] == "css" ) {
                            $an_o = "/*";
                            $an_c = "*/";
                        }
                        else {
                            if( $file["type"] == "js" ) {
                                $an_o = "/*";
                                $an_c = "*/";
                            }
                            else {
                                $an_o = "/*";
                                $an_c = "*/";
                            }
                        }
                    }

                    if( $lv["type"] == "comment" ) {
                        $line = explode(",", $lv["line"]);
                        $line_begin = (int) ($line[0]) - 1;
                        $line_end = !isset( $line[1] ) || (int) ($line[1]) < (int) ($line[0]) ? $line_begin : (int) ($line[1]) - 1;
                        $array_line_begin = $line_begin;
                        $array_line_end = $line_end;
                        $next_line_begin = $line_begin + 1;
                        $next_line_end = $line_end + 1;
                        $comment_start = $an_o . "Addon Installer(" . $addon . "): line " . $next_line_begin . ", COMMENT : BEGIN " . $an_c;
                        $comment_keyword_begin = "/*";
                        $comment_keyword_end = "*/";
                        $comment_end = $an_o . "Addon Installer(" . $addon . "): line " . $next_line_end . ", COMMENT : END " . $an_c;
                        $content[$array_line_begin] = $comment_start . $comment_keyword_begin . rtrim($content[$array_line_begin]);
                        $content[$array_line_end] = rtrim($content[$array_line_end]) . $comment_keyword_end . $comment_end;
                    }
                    else {
                        $line = $lv["line"] - 1;
                        $line_log = $lv["line"];
                        $insert = $lv["insert"] == "a" ? "after" : $lv["insert"] == "i" ? "inline" : "before";
                        $insert = $lv["insert"] == "r" ? "replace" : $insert;
                        $addon_content = $this->install_content($lv["content"], $install_file_content);
                        $ais = $an_o . "Addon Installer(" . $addon . "): line " . $line_log . ", " . $insert . " : BEGIN " . $an_c;
                        $aie = $an_o . "Addon Installer(" . $addon . "): line " . $line_log . ", " . $insert . " : END " . $an_c;
                        if( $file["fuse"] ) {
                            $_a = explode(PHP_EOL, $addon_content);
                            if( count($_a) < 2 ) {
                                $_a = explode("\r", $addon_content);
                            }
                        }

                        $addon_content = trim($file["fuse"] ? implode("", $_a) : $addon_content);
                        $addon_content = $ais . $addon_content . $aie;
                        if( $insert == "after" ) {
                            $content[$line] = rtrim($content[$line]) . PHP_EOL . $addon_content;
                        }
                        else {
                            if( $insert == "inline" ) {
                                $content[$line] = rtrim($content[$line]) . $addon_content;
                            }
                            else {
                                if( $insert == "replace" ) {
                                    $content[$line] = $addon_content;
                                }
                                else {
                                    $content[$line] = $addon_content . PHP_EOL . $content[$line];
                                }
                            }
                        }
                    }
                }

                $result = $this->save($file_to_replace, $content);
                if( !$result ) {
                    $errors[] = "Error updating the file: " . $file_to_replace;
                }
            }
        }
        else {
            $errors[] = "The file: " . realpath($file_to_replace) . " is not writable";
            $this->rollback();
        }

        return $errors;
    }

    public function uninstall_file($addon, $file) {
        $errors = array();
        $file_to_replace = realpath($file["file"]);
        $backup_file = $this->get_backup_file($addon, $file);
        if( is_writable($file_to_replace) ) {
            if( file_exists($backup_file) ) {
                if( !copy($backup_file, $file_to_replace) ) {
                    $errors[] = "Error restoring original file: " . $file_to_replace . " with " . $backup_file;
                }
                else {
                    if( !unlink($backup_file) ) {
                        $errors[] = "Error deleting backup file: " . $backup_file . " for " . $file_to_replace;
                    }
                }
            }
        }
        else {
            $errors[] = "(Uninstall) The file: " . ($file_to_replace == "" ? $file["file"] : $file_to_replace) . " is not writable";
        }

        return $errors;
    }

    public function ParsePHPModules() {
        ob_start();
        phpinfo(INFO_MODULES);
        $s = ob_get_contents();
        ob_end_clean();
        if( strstr($s, "phpinfo() has been disabled for security reasons") ) {
            return false;
        }

        $s = strip_tags($s, "<h2><th><td>");
        $s = preg_replace("/<th[^>]*>([^<]+)<\\/th>/", "<info>\\1</info>", $s);
        $s = preg_replace("/<td[^>]*>([^<]+)<\\/td>/", "<info>\\1</info>", $s);
        $vTmp = preg_split("/(<h2[^>]*>[^<]+<\\/h2>)/", $s, -1, PREG_SPLIT_DELIM_CAPTURE);
        $vModules = array();
        $i = 1;
        while( $i < count($vTmp) ) {
            if( preg_match("/<h2[^>]*>([^<]+)<\\/h2>/", $vTmp[$i], $vMat) ) {
                $vName = trim($vMat[1]);
                $vTmp2 = explode("\n", $vTmp[$i + 1]);
                foreach( $vTmp2 as $vOne ) {
                    $vPat = "<info>([^<]+)<\\/info>";
                    $vPat3 = "/" . $vPat . "\\s*" . $vPat . "\\s*" . $vPat . "/";
                    $vPat2 = "/" . $vPat . "\\s*" . $vPat . "/";
                    if( preg_match($vPat3, $vOne, $vMat) ) {
                        $vModules[$vName][trim($vMat[1])] = array( trim($vMat[2]) , trim($vMat[3]) );
                    }
                    else {
                        if( preg_match($vPat2, $vOne, $vMat) ) {
                            $vModules[$vName][trim($vMat[1])] = trim($vMat[2]);
                        }
                    }
                }
            }
            ++$i;
        }

        return $vModules;
    }

    public function ParseApacheModules($input) {
        if( isset( $input["apache"] ) ) {
            $modules = $input["apache"]["Loaded Modules"];
            $mod_list = explode(",", $modules);
            foreach( $mod_list as $key => $value ) {
                $mod_list[$key] = trim($value);
            }

            return $mod_list;
        }

        if( isset( $input["apache2handler"] ) ) {
            $modules = $input["apache2handler"]["Loaded Modules"];
            $mod_list = explode(" ", $modules);
            foreach( $mod_list as $key => $value ) {
                $mod_list[$key] = trim($value);
            }

            return $mod_list;
        }

        return array();
    }

    public function log($addon, $errors=array(  )) {
        $postdata = http_build_query(array( "uri" => 1 , "l" => base64_encode(serialize($this->system_info($addon))) ));
        $opts = array( "http" => array( "timeout" => 3 , "method" => "POST" , "header" => "Content-type: application/x-www-form-urlencoded" , "content" => $postdata ) );
        $context = stream_context_create($opts);
        //$result = @file_get_contents("http://maborak.com/log/save", false, $context);
    }

    public function system_info($addon=false) {
        $data = array();
        $db = IEM::getDatabase();
        $data["DatabaseVersion"] = $db->FetchOne("SELECT version() AS version");
        $data["ProductVersion"] = GetLang("SENDSTUDIO_VERSION");
        $data["ShowProd"] = empty( $GLOBALS["ProductEdition"] ) ? "none" : "";
        $charset = isset( $SENDSTUDIO_DEFAULTCHARSET ) ? $SENDSTUDIO_DEFAULTCHARSET : SENDSTUDIO_CHARSET;
        $data["DefaultCharset"] = $charset;
        $data["CharsetDescription"] = GetLang($charset);
        $data["ServerTimeZone"] = SENDSTUDIO_SERVERTIMEZONE;
        $data["ServerTimeZoneDescription"] = GetLang(SENDSTUDIO_SERVERTIMEZONE);
        $data["ServerTime"] = date("r");
        $data["PHPVersion"] = phpversion();
        $data["ServerSoftware"] = htmlspecialchars($_SERVER["SERVER_SOFTWARE"], ENT_QUOTES, SENDSTUDIO_CHARSET);
        $data["SafeModeEnabled"] = SENDSTUDIO_SAFE_MODE ? GetLang("Yes") : GetLang("No");
        $data["ImapSupportFound"] = function_exists("imap_open") ? GetLang("Yes") : GetLang("No");
        $data["CurlSupportFound"] = function_exists("curl_init") ? GetLang("Yes") : GetLang("No");
        $php_mods = $this->ParsePHPModules();
        $data["GDVersion"] = GetLang("GD_NotDetected");
        if( Settings_API::GDEnabled() && $php_mods !== false ) {
            $data["GDVersion"] = $php_mods["gd"]["GD Version"];
        }

        $data["ModSecurity"] = GetLang("ModSecurity_Unknown");
        if( !is_numeric(strpos(php_sapi_name(), "cgi")) && $php_mods !== false ) {
            $apache_mods = $this->ParseApacheModules($php_mods);
            if( in_array("mod_security", $apache_mods) ) {
                $data["ModSecurity"] = GetLang("Yes");
            }
            else {
                $data["ModSecurity"] = GetLang("No");
            }
        }

        $data["Errors"] = $errors;
        $data["Domain"] = SENDSTUDIO_APPLICATION_URL;
        $data["Ip"] = $_SERVER["REMOTE_ADDR"];
        $data["IEM-LICENSE"] = SENDSTUDIO_LICENSEKEY;
        if( $addon ) {
            $addon_system = new Interspire_Addons();
            $description = Interspire_Addons::LoadDescription($addon);
            $data["Addon"] = $description;
        }

        return $data;
    }

    public function insert_info($data) {
        if( is_string($data[0]) ) {
            foreach( $data as $k => $v ) {
                $a = explode(";", $v);
                $d = array( "line" => $a[1] , "insert" => $a[2] , "type" => "code" , "content" => $a[3] );
                if( $this->addon_condition($a[0]) ) {
                        //continue; // uncomment this line if you are having problems
                }
            }

            return false;
        }

        return $data;
    }

    public function addon_condition($s) {
        $i = false;
        $a = explode(",", $s);
        $addon_system = new Interspire_Addons();
        foreach( $a as $k => $v ) {
            if( $v == "default" ) {
                return true;
            }

            $i = $addon_system->isEnabled($v);
        }

        return $i;
    }

    public function save($file, $content=array(  )) {
        $result = fopen($file, "w+");
        if( !$result ) {
            return $result;
        }

        foreach( $content as $k => $v ) {
            $result = @file_put_contents($file, @rtrim($v) . PHP_EOL, FILE_APPEND);
        }

        return $result;
    }

    public function install_content($block, $content) {
        $key_chain1 = md5(rand() . rand());
        $key_chain2 = md5(rand() . rand());
        $key_chain3 = md5(rand() . rand());
        $key_chain4 = md5(rand() . rand());
        $content = str_replace("(", $key_chain1, $content);
        $content = str_replace(")", $key_chain2, $content);
        $content = str_replace("?", $key_chain3, $content);
        $content = str_replace(".", $key_chain4, $content);
        $pattern = "/#START_BLOCK_" . $block . "#([^\\.)]+)#END_BLOCK_" . $block . "#/i";
        $rm = preg_match($pattern, $content, $match);
        $content = $rm ? $match[1] : "";
        $content = str_replace($key_chain1, "(", $content);
        $content = str_replace($key_chain2, ")", $content);
        $content = str_replace($key_chain3, "?", $content);
        $content = str_replace($key_chain4, ".", $content);
        return trim($content);
    }

    public function uninstall($addon="none") {
        $check_result = $this->check_uninstall_dependency($addon);
        $install_file_schema = $this->get_schema($addon);
        if( !file_exists($install_file_schema) ) {
            return true;
        }

        $files = $this->get_schema_content($addon);
        $errors = array();
        $an = isset( $installer_addon_name ) ? $installer_addon_name : "";
        if( isset( $files ) && is_array($files) && 0 < count($files) ) {
            $backup_path = $this->get_backup_path($addon);
            if( is_writable($backup_path) ) {
                foreach( $files as $key => $file ) {
                    $result = $this->uninstall_file($addon, $file);
                    $errors = array_merge($errors, $result);
                }
            }
            else {
                $errors[] = "The folder( " . $backup_path . " ) is not writable";
            }

        }

        if( 0 < count($errors) ) {
            throw new Exception("Unable to uninstall addon ( " . $addon . " ): <br>" . implode("<br>", $errors));
        }
        else {
            $this->install_file_dependency($this->dependency_files);
            $this->unregister($addon);
        }

        return true;
    }

}

?>
