<?php
/**
 * ConLite Session Management
 * 
 * @package Core
 * @subpackage cSystemClasses
 * @version $Rev$
 * @author Ortwin Pinke <conlite@ortwinpinke.de>
 * @copyright (c) 2014, ConLite Team <www.conlite.org>
 * @link http://conlite.org ConLite Portal
 * 
 * $Id$
 */

/**
 * @package    ContenidoBackendArea
 * @version    1.1.1.2
 * @author     Boris Erdmann, Kristian Koehntopp
 * @copyright  four for business AG <www.4fb.de>
 * @license    http://www.contenido.org/license/LIZENZ.txt
 * @link       http://www.4fb.de
 * @link       http://www.contenido.org
 */

if(!defined('CON_FRAMEWORK')) {die('Illegal call');}

/**
 * old session class, now only extends new cSession
 * 
 * @deprecated since version 2.0
 */
class Session extends cSession {
    
}


class cSession {

    var $classname = "Session";         ## Needed for object serialization.
    ## Define the parameters of your session by either overwriting
    ## these values or by subclassing session (recommended).
    var $magic = "";                    ## Some string you should change.
    var $mode = "cookie";               ## We propagate session IDs with cookies
    var $fallback_mode;                 ## If this doesn't work, fall back...
    var $lifetime = 0;                  ## 0 = do session cookies, else minutes
    var $cookie_domain = "";            ## If set, the domain for which the
    ## session cookie is set.
    var $gc_time = 1440;               ## Purge all session data older than 1440 minutes.
    var $gc_probability = 5;            ## Garbage collect probability in percent
    var $auto_init = "";                ## Name of the autoinit-File, if any.
    var $secure_auto_init = 1;          ## Set to 0 only, if all pages call
    ## page_close() guaranteed.
    var $allowcache = "no";             ## "passive", "no", "private" or "public"
    var $allowcache_expire = 1440;      ## If you allowcache, data expires in this
    ## many minutes.
    var $that_class = "";               ## Name of data storage container
    ##
    ## End of parameters.
    ##
    var $name;                          ## Session name
    var $id;                            ## Unique Session ID
    var $that;
    var $pt = array();                  ## This Array contains the registered things
    var $in = 0;                        ## Marker: Did we already include the autoinit file?
    var $expires;                          ## Expire date
    var $_expires;

    ## register($things):
    ##
    ## call this function to register the things that should become persistent

    function register($things) {
        $arr_things = explode(",", $things);
        reset($arr_things);
        //while (list(, $thing) = each($arr_things)) {
        foreach($arr_things as $thing) {
            $str_thing = trim($thing);
            if ($str_thing) {
                $this->pt[$str_thing] = true;
            }
        }
    }

    function setExpires($time) {
        $this->_expires = $time;
    }

    function is_registered($name) {
        if (isset($this->pt[$name]) && $this->pt[$name] == true)
            return true;
        return false;
    }

    function unregister($things) {
        $things = explode(",", $things);
        reset($things);
        foreach ($things as  $thing) {
            $thing = trim($thing);
            if ($thing) {
                unset($this->pt[$thing]);
            }
        }
    }

    ## get_id():
    ##
    ## Propagate the session id according to mode and lifetime.
    ## Will create a new id if necessary. To take over abandoned sessions,
    ## one may provide the new session id as a parameter (not recommended).

    function get_id($id = "") {
        global $_COOKIE, $_GET, $_POST, $QUERY_STRING;
        $newid = true;

        $this->name = $this->cookiename == "" ? $this->classname : $this->cookiename;

        if ("" == $id) {
            $newid = false;
            switch ($this->mode) {
                case "get":
                    $id = isset($_GET[$this->name]) ?
                            $_GET[$this->name] :
                            ( isset($_POST[$this->name]) ?
                                    $_POST[$this->name] :
                                    "");
                    break;
                case "cookie":
                    $id = isset($_COOKIE[$this->name]) ?
                            $_COOKIE[$this->name] : "";
                    break;
                default:
                    die("This has not been coded yet.");
                    break;
            }
        }

        if ("" == $id) {
            $newid = true;
            $id = $this->that->ac_newid(md5(uniqid($this->magic)), $this->name);
        }

        switch ($this->mode) {
            case "cookie":
                if ($newid && ( 0 == $this->lifetime )) {
                    SetCookie($this->name, $id, 0, "/", $this->cookie_domain);
                }
                if (0 < $this->lifetime) {
                    SetCookie($this->name, $id, time() + $this->lifetime * 60, "/", $this->cookie_domain);
                }

                // Remove session ID info from QUERY String - it is in cookie
                if (isset($QUERY_STRING) && ("" != $QUERY_STRING)) {
                    $QUERY_STRING = preg_replace(
                            "/(^|&)" . quotemeta(urlencode($this->name)) . "=" . $id . "(&|$)/", "\\1", $QUERY_STRING);
                }
                break;
            case "get":
                if (isset($QUERY_STRING) && ("" != $QUERY_STRING)) {
                    $QUERY_STRING = preg_replace(
                            "/(^|&)" . quotemeta(urlencode($this->name)) . "=" . $id . "(&|$)/", "\\1", $QUERY_STRING);
                }
                break;
            default:
                ;
                break;
        }
        $this->id = $id;
    }

    ## put_id():
    ##
    ## Stop using the current session id (unset cookie, ...) and
    ## abandon a session.

    function put_id() {
        global $_COOKIE;

        switch ($this->mode) {
            case "cookie":
                $this->name = $this->cookiename == "" ? $this->classname : $this->cookiename;
                SetCookie($this->name, "", 0, "/", $this->cookie_domain);
                $_COOKIE[$this->name] = "";
                break;

            default:
                // do nothing. We don't need to die for modes other than cookie here.
                break;
        }
    }

    ## delete():
    ##
    ## Delete the current session record and put the session id.

    function delete() {
        $this->that->ac_delete($this->id, $this->name);
        $this->put_id();
    }

    ## url($url):
    ##
    ## Helper function: returns $url concatenated with the current
    ## session $id.

    function url($url) {
        // Remove existing session info from url
        $url = preg_replace(
                "/([&?])" . quotemeta(urlencode($this->name)) . "=" . $this->id . "(&|$)/", "\\1", $url);

        // Remove trailing ?/& if needed
        $url = preg_replace("/[&?]+$/", "", $url);

        switch ($this->mode) {
            case "get":
                $url .= ( strpos($url, "?") != false ? "&" : "?" ) .
                        urlencode($this->name) . "=" . $this->id;
                break;
            default:
                ;
                break;
        }

        // Encode naughty characters in the URL
        $url = str_replace(array("<", ">", " ", "\"", "'"), array("%3C", "%3E", "+", "%22", "%27"), $url);
        return $url;
    }

    function purl($url) {
        print $this->url($url);
    }

    /**
     * 
     * @param array $aParam
     * @return string
     */
    function self_url($aParam = array()) {
        $sURI = $_SERVER["PHP_SELF"];
        parse_str($_SERVER["QUERY_STRING"], $aQuery);
        $aQuery = array_merge($aQuery, $aParam);
        $sQuery = http_build_query($aQuery,'','&');

        return $this->url($_SERVER["PHP_SELF"] . "?" . $sQuery);
    }

    function pself_url() {
        print $this->self_url();
    }

    function hidden_session($mode = 0) {
        if ($mode) {
            return sprintf("<input type=\"hidden\" name=\"%s\" value=\"%s\">\n", $this->name, $this->id);
        } else {
            printf("<input type=\"hidden\" name=\"%s\" value=\"%s\">\n", $this->name, $this->id);
        }
    }

    function add_query($qarray) {
        global $PHP_SELF;
        global $QUERY_STRING;

        if ((isset($QUERY_STRING) && ("" != $QUERY_STRING)) || ($this->mode == "get")) {
            $sep_char = "&";
        } else {
            $sep_char = "?";
        }

        $qstring = "";
        while (list($k, $v) = each($qarray)) {
            $qstring .= $sep_char . urlencode($k) . "=" . urlencode($v);
            $sep_char = "&";
        }

        return $qstring;
    }

    function padd_query($qarray) {
        print $this->add_query($qarray);
    }

    ## serialize($var,&$str):
    ##
    ## appends a serialized representation of $$var
    ## at the end of $str.
    ##
    ## To be able to serialize an object, the object must implement
    ## a variable $classname (containing the name of the class as string)
    ## and a variable $persistent_slots (containing the names of the slots
    ## to be saved as an array of strings).

    function serialize($var, &$str) {
        static $t, $l, $k;

        ## Determine the type of $$var
        eval("\$t = gettype(\$$var);");
        switch ($t) {

            case "array":
                /*
                ## $$var is an array. Enumerate the elements and serialize them.
                eval("reset(\$$var); \$l = gettype(list(\$k)=each(\$$var));");
                $str .= "\$$var = array(); ";
                while ("array" == $l) {
                    ## Structural recursion
                    $this->serialize($var . "['" . preg_replace("/([\\'])/", "\\\\1", $k) . "']", $str);
                    eval("\$l = gettype(list(\$k)=each(\$$var));");
                }
                
                */
                // $$var is an array. Enumerate the elements and serialize them.
                $str .= "\$$var = array(); ";
                eval("\$l = array(); foreach(\$$var as \$k => \$v) {\$l[] = array(\$k,gettype(\$k),\$v);}");
                foreach ($l as $item) {
                    // Structural recursion
                    $this->serialize($var . "['" . preg_replace("/([\\'])/", "\\\\1", $item[0]) . "']", $str);
                }

                break;
            case "object":
                ## $$var is an object. Enumerate the slots and serialize them.
                eval("\$k = \${$var}->classname; \$l = reset(\${$var}->persistent_slots);");
                $str.="\$$var = new $k; ";
                while ($l) {
                    ## Structural recursion.
                    $this->serialize($var . "->" . $l, $str);
                    eval("\$l = next(\${$var}->persistent_slots);");
                }

                break;
            default:
                ## $$var is an atom. Extract it to $l, then generate code.
                eval("\$l = \$$var;");
                $str.="\$$var = '" . preg_replace("/([\\'])/", "\\\\1", $l) . "'; ";
                break;
        }
    }

    function get_lock() {
        $this->that->ac_get_lock();
    }

    function release_lock() {
        $this->that->ac_release_lock();
    }

    ## freeze():
    ##
    ## freezes all registered things ( scalar variables, arrays, objects ) into
    ## a database table

    function freeze() {
        $str = "";
//print "DBG: <pre>"; var_dump($this->pt); print "</pre>\n";
        $this->serialize("this->in", $str);
        $this->serialize("this->pt", $str);

        reset($this->pt);
        $int_cnt_pt = count($this->pt);
        
        for($i=0;$i<$int_cnt_pt;$i++) {
            $thing = trim(key($this->pt));
            if ($thing && isset($GLOBALS[$thing])) {
                $this->serialize("GLOBALS['" . $thing . "']", $str);
            }
            next($this->pt);
        }
        /*
        reset($this->pt); echo "\n";
        while (list($thing) = each($this->pt)) {
        //foreach($this->pt as $thing) {
            echo "\n".$thing = trim($thing);
            if ($thing && isset($GLOBALS[$thing])) {
                $this->serialize("GLOBALS['" . $thing . "']", $str);
            }
        }
         * 
         */
        
//return;
        $r = $this->that->ac_store($this->id, $this->name, $str);
        $this->release_lock();

        if (!$r)
            $this->that->ac_halt("Session: freeze() failed.");
    }

    ## thaw:
    ##
    ## Reload frozen variables from the database and microwave them.

    function thaw() {
        $this->get_lock();

        $vals = $this->that->ac_get_value($this->id, $this->name);
        eval(sprintf(";%s", $vals));
    }

    ##
    ## Variable precedence functions
    ##

    function reimport_get_vars() {
        $this->reimport_any_vars("_GET");
    }

    function reimport_post_vars() {
        $this->reimport_any_vars("_POST");
    }

    function reimport_cookie_vars() {
        $this->reimport_any_vars("HTTP_COOKIE_VARS");
    }

    function reimport_any_vars($arrayname) {
        global $$arrayname;

        if (!is_array($$arrayname))
            return;

        reset($$arrayname);
        while (list($key, $val) = each($$arrayname)) {
            $GLOBALS[$key] = $val;
        }
    }

    ##
    ## All this is support infrastructure for the start() method
    ##

    function set_container() {
        $name = $this->that_class;
        $this->that = new $name;

        $this->that->ac_start();
    }

    function set_tokenname() {
        $this->name = $this->cookiename == "" ? $this->classname : $this->cookiename;
    }

    function release_token($sid = "") {
        global $_COOKIE, $_POST, $_GET,
        $HTTP_HOST, $HTTPS;

        if (isset($this->fallback_mode) && ("get" == $this->fallback_mode) && ("cookie" == $this->mode) && (!isset($_COOKIE[$this->name]))) {

            // Looks like no cookie here - check GET/POST params
            if (isset($_GET[$this->name]) || isset($_POST[$this->name])) {
                // Session info passed via GET/POST - go to fallback_mode
                $this->mode = $this->fallback_mode;
            } else {
                // It seems to be the first load of this page -
                // no cookie and no GET/POST params
                // Generate session ID and setup cookie.
                $this->get_id($sid);

                // Next line is to generate correct self_url() later
                $this->mode = $this->fallback_mode;

                if (isset($HTTPS) && $HTTPS == 'on') {
                    ## You will need to fix suexec as well, if you
                    ## use Apache and CGI PHP
                    $PROTOCOL = 'https';
                } else {
                    $PROTOCOL = 'http';
                }
                header("Status: 302 Moved Temporarily");
                header("Location: " . $PROTOCOL . "://" . $HTTP_HOST . $this->self_url());
                exit;
            }
        }
    }

    function put_headers() {
        # Allowing a limited amount of caching, as suggested by
        # Padraic Renaghan on phplib@lists.netuse.de.
        #
    # Note that in HTTP/1.1 the Cache-Control headers override the Expires
        # headers and HTTP/1.0 ignores headers it does not recognize (e.g,
        # Cache-Control). Mulitple Cache-Control directives are split into
        # mulitple headers to better support MSIE 4.x.
        #
    # Added pre- and post-check for MSIE 5.x as suggested by R.C.Winters,
        # see http://msdn.microsoft.com/workshop/author/perf/perftips.asp#Use%20Cache-Control%20Extensions
        # for details
        switch ($this->allowcache) {

            case "passive":
                $mod_gmt = gmdate("D, d M Y H:i:s", getlastmod()) . " GMT";
                header("Last-Modified: " . $mod_gmt);
                # possibly ie5 needs the pre-check line. This needs testing.
                header("Cache-Control: post-check=0, pre-check=0");
                break;

            case "public":
                $exp_gmt = gmdate("D, d M Y H:i:s", time() + $this->allowcache_expire * 60) . " GMT";
                $mod_gmt = gmdate("D, d M Y H:i:s", getlastmod()) . " GMT";
                header("Expires: " . $exp_gmt);
                header("Last-Modified: " . $mod_gmt);
                header("Cache-Control: public");
                header("Cache-Control: max-age=" . $this->allowcache_expire * 60);
                break;

            case "private":
                $mod_gmt = gmdate("D, d M Y H:i:s", getlastmod()) . " GMT";
                header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
                header("Last-Modified: " . $mod_gmt);
                header("Cache-Control: private");
                header("Cache-Control: max-age=" . $this->allowcache_expire * 60);
                header("Cache-Control: pre-check=" . $this->allowcache_expire * 60);
                break;

            default:
                $rand = md5(mt_rand());
                $mod_gmt = gmdate("D, d M Y H:i:s", time() - 3600) . " GMT";

                if ($this->_expires > 0) {
                    header("Expires: " . gmdate("D, d M Y H:i:s", $this->_expires) . " GMT");
                } else {
                    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
                }

                header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
                header("Cache-Control:no-store, no-cache, must-revalidate");
                header("Cache-Control: post-check=0, pre-check=0", false);
                header("Cache-control: private, no-cache");
                header("Pragma: no-cache");
                header("ETag: $rand");

                break;
        }
    }

    ##
    ## Garbage collection
    ##
    ## Destroy all session data older than this
    ##

    function gc() {
        srand(time());
        if ((rand() % 100) < $this->gc_probability) {
            $this->that->ac_gc($this->gc_time, $this->name);
        }
    }

    ##
    ## Initialization
    ##

    function start($sid = "") {
        $this->set_container();
        $this->set_tokenname();
        $this->put_headers();
        $this->release_token($sid);
        $this->get_id($sid);
        $this->thaw();
        $this->gc();
        $this->setExpires(0);
    }

}
