<?php
/**
 * -------------------------------------------------------------------------
 * The SESSION module to work with data stored in user's session.
 *
 * When creating this module, I tried to implement support for both
 * a typical session and its more secure version, which is based on binding
 * to the visitor's IP address. Also, I wanted to implement an expiration
 * date in the session that would be arbitrarily defined for any session
 * variable.
 *
 * See these elements to understand how the module works:
 *     mimimi.core/RoutinesSystem.php
 *                                └─> mimimiServer()
 *     mimimi.core/RoutinesWeb.php
 *                             ├─> mimimiInclude()
 *                             └─> mimimiRandomId()
 *     mimimi.modules/Session/Session.php
 *                            │       ├─> run()
 *                            │       ├─> get()
 *                            │       ├─> set()
 *                            │       ├─> remove()
 *                            │       └─> getSteadyId()
 *                            └─<<<── mimimi.core/Module.php
 *
 * -------------------------------------------------------------------------
 *
 * Implemented properties below are:
 *     PROTECTED  $isRunning
 *
 * Overridden methods below are:
 *     run
 *
 * Implemented methods below are:
 *     get
 *     set
 *     remove
 *     getSteadyId
 *     PROTECTED  startSession
 *     PROTECTED  restartSession
 *     PROTECTED  replaceSession
 *
 * -------------------------------------------------------------------------
 *
 * @package    MimimiFramework
 * @subpackage Modules
 * @copyright  2022 MiMiMi Community
 *             https://mimimi.software/
 * @license    CC BY 4
 *             https://www.creativecommons.org/licenses/by/4.0
 * -------------------------------------------------------------------------
 */

mimimiInclude('Module.php');
class MimimiSession extends MimimiModule {

    /**
     * ---------------------------------------------------------------------
     * Define flag "Module is running".
     *
     * @var bool
     * @access protected
     * ---------------------------------------------------------------------
     */

    protected $isRunning = false;

    /**
     * ---------------------------------------------------------------------
     * Public method to run this module.
     *
     * Typical is a standard session with an abstract identifier that can
     * be chosen at random or even by user intervention in the ID assignment
     * process. Steady is a special session whose identifier is strictly
     * associated with the visitor's IP address.
     *
     * @public
     * @param   mixed  $params  True if typical session,
     *                          False if steady session,
     *                          Empty string if steady with some uniqueness.
     * @return  bool            True.
     * ---------------------------------------------------------------------
     */

    public function run ( $params = '' ) {
        if (! $this->isRunning) {
            if (empty($params)) {
                $id = $this->getSteadyId();
                $unique = $params === false
                          ? ''
                          : ( '-' . mimimiRandomId(6) );
                if (function_exists('session_status')) {
                    $status = session_status();
                    switch ($status) {
                        case PHP_SESSION_ACTIVE:
                            $test = session_id();
                            $this->replaceSession($test, $id, $unique);
                            break;
                        case PHP_SESSION_NONE:
                            $param = session_name();
                            $test = mimimiCookie($param);
                            is_string($test) ? $this->restartSession($test, $id, $unique)
                                             : $this->startSession($id, $unique);
                            break;
                        case PHP_SESSION_DISABLED:
                            break;
                    }
                } else {
                    $test = session_id();
                    if (is_string($test)) {
                        $test ? $this->replaceSession($test, $id, $unique)
                              : $this->startSession($id, $unique);
                    }
                }
            } else {
                $this->isRunning = true;
                session_start();
            }
        }
        return true;
    }

    /**
     * ---------------------------------------------------------------------
     * Public method to get a session parameter.
     *
     * The parameter is returned only if its retention period has not
     * expired. Otherwise, the value specified in the $def argument will
     * be returned.
     *
     * @public
     * @param   string  $param  Name of parameter.
     * @param   mixed   $def    Default value if parameter not found.
     * @return  mixed           Retrieved value.
     * ---------------------------------------------------------------------
     */

    public function get ( $param, $def = '' ) {
        return $this->isRunning
               ? ( isset($_SESSION[$param])
                   ? ( isset($_SESSION[$param]['deadline']) &&
                       isset($_SESSION[$param]['value'])
                       ? ( $_SESSION[$param]['deadline'] >= time()
                           ? $_SESSION[$param]['value']
                           : $def )
                       : $_SESSION[$param] )
                   : $def )
               : $def;
    }

    /**
     * ---------------------------------------------------------------------
     * Public method to save a session parameter.
     *
     * The default retention period for this parameter is 1 day (it is
     * 86400 seconds). You can specify a different period with the
     * $lifetime argument.
     *
     * You should be aware that a session can become unstable if the
     * retention period is long enough to exceed the garbage collector
     * timer.
     *
     * The garbage collector is an internal server mechanism that deletes
     * untouched session files after a certain time specified in the
     * server settings. Those settings take precedence over the $lifetime
     * argument.
     *
     * @public
     * @param   string  $param     Name of parameter.
     * @param   mixed   $value     Value to save.
     * @param   int     $lifetime  Retention period in seconds.
     * ---------------------------------------------------------------------
     */

    public function set ( $param, $value, $lifetime = 86400 ) {
        if ($this->isRunning) {
            $_SESSION[ $param ] = [
                'value'    => $value,
                'deadline' => time() + $lifetime
            ];
        }
    }

    /**
     * ---------------------------------------------------------------------
     * Public method to delete a session parameter.
     *
     * @public
     * @param   string  $param  Name of parameter.
     * ---------------------------------------------------------------------
     */

    public function remove ( $param ) {
        if ($this->isRunning) {
            if (isset($_SESSION[$param])) {
                unset($_SESSION[$param]);
            }
        }
    }

    /**
     * ---------------------------------------------------------------------
     * Public method to compute a steady identifier.
     *
     * @public
     * @return  string  An identifier based on visitor's IP.
     * ---------------------------------------------------------------------
     */

    public function getSteadyId () {
        $ip = mimimiServer('REMOTE_ADDR');
        $ip = mb_strtolower($ip, 'UTF-8');
        return strtolower(md5($ip));
    }

    /**
     * ---------------------------------------------------------------------
     * Starts valid session.
     *
     * @protected
     * @param  string  $id      Valid identifier.
     * @param  string  $unique  Unique detail.
     * ---------------------------------------------------------------------
     */

    protected function startSession ( $id, $unique = '' ) {
        $this->isRunning = true;
        session_id($id . $unique);
        session_start();
    }

    /**
     * ---------------------------------------------------------------------
     * Restarts valid session.
     *
     * @protected
     * @param  string  $id      A tested identifier.
     * @param  string  $valid   Valid identifier.
     * @param  string  $unique  Unique detail for valid ID.
     * ---------------------------------------------------------------------
     */

    protected function restartSession ( $id, $valid, $unique = '' ) {
        $size = strlen($id) - strlen($unique);
        $test = substr($id, 0, $size);
        if ($test == $valid) {
            $valid = $id;
            $unique = '';
        }
        $this->startSession($valid, $unique);
    }

    /**
     * ---------------------------------------------------------------------
     * Replaces session with other if it is invalid.
     *
     * @protected
     * @param  string  $id      A tested identifier.
     * @param  string  $valid   Valid identifier.
     * @param  string  $unique  Unique detail for valid ID.
     * ---------------------------------------------------------------------
     */

    protected function replaceSession ( $id, $valid, $unique = '' ) {
        $size = strlen($id) - strlen($unique);
        $id = substr($id, 0, $size);
        if ($id == $valid) {
            $this->isRunning = true;
            return;
        }
        function_exists('session_abort') ? session_abort()
                                         : session_write_close();
        $this->startSession($valid, $unique);
    }
};