<?php
namespace nur;

/**
 * Class session: gestion des sessions
 */
class session {
  /** durée de session par défaut: 24 minutes */
  const DURATION = 1440;

  /** @var int */
  protected static $duration;

  /** obtenir la durée d'une session en secondes. */
  static final function get_duration(): int {
    $duration = self::$duration;
    if ($duration === null) $duration = static::DURATION;
    return $duration;
  }

  /**
   * spécifier la durée d'une session en secondes.
   * cette méthode doit être appelée avant start()
   */
  static final function set_duration(int $duration): void {
    self::$duration = $duration;
  }

  /** vérifier si la session a été démarrée. */
  static final function started(): bool {
    return session_status() == PHP_SESSION_ACTIVE;
  }

  private static $started_once = false;

  /** vérifier si la session a été démarrée au moins une fois */
  static final function started_once(): bool {
    return self::$started_once;
  }

  /** nom de la variable de session qui indique quand elle a été créée. */
  const SESSION_CREATION_TIME = "nur\\session_creation_time";

  /**
   * démarrer la session si nécessaire. étendre la durée du cookie de session
   * de la valeur get_duration()
   *
   * la page est automatiquement configurée par PHP pour ne pas être mise en
   * cache, il est donc inutile d'utiliser {@link http::no_cache()}
   *
   * retourner true si la session vient d'être créée, false si elle a été
   * chargée depuis le disque
   */
  static final function start(): bool {
    if (!self::started()) {
      $duration = self::get_duration();
      $can_set_cookies = !headers_sent();
      if ($can_set_cookies) {
        # durée initiale du cookie
        session_set_cookie_params($duration);
      }
      session_start([
        "gc_maxlifetime" => $duration,
      ]);
      self::$started_once = true;

      $creation_time = self::get(self::SESSION_CREATION_TIME, false);
      if (!$creation_time) {
        # création initiale
        self::set(self::SESSION_CREATION_TIME, time());
        return true;
      } elseif ($can_set_cookies) {
        # étendre la durée du cookie
        $params = session_get_cookie_params();
        setcookie(session_name(), session_id(), time() + $duration, $params["path"], $params["domain"], $params["secure"], $params["httponly"]);
      }
    }
    return false;
  }

  /**
   * enregistrer la session, la fermer et libérer son verrou.
   *
   * cette fonction peut être appelée avant une opération longue si on n'a plus
   * besoin de la session.
   *
   * retourn true si la session a été fermée, false sinon.
   */
  static final function close(): bool {
    if (self::started()) {
      session_write_close();
      return true;
    }
    return false;
  }

  /**
   * vider la session de toutes ses variables ($unset_only==true) ou la détruire
   * ($unset_only==false). en cas de destruction de la session, supprimer aussi
   * le cookie de session
   *
   * si $unset_only==true, refaire la variable SESSION_CREATION_TIME
   */
  static final function destroy(bool $unset_only=false, bool $clear_cookie=true): void {
    self::start();
    if ($unset_only) {
      session_unset();
      self::set(self::SESSION_CREATION_TIME, time());
    } else {
      $can_set_cookies = !headers_sent();
      if ($clear_cookie && $can_set_cookies && ini_get("session.use_cookies")) {
        $params = session_get_cookie_params();
        setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]);
      }
      session_destroy();
    }
  }

  /**
   * Vider la session de toutes les clés spécifiées dans $keys qui ne sont pas
   * mentionnées dans $keeps
   *
   * Si $keys vaut null, toutes les clés sont supprimées comme avec destroy(true)
   * notamment, cela signifie que la variable SESSION_CREATION_TIME est refaite
   */
  static final function unset_keys(?array $keys, ?array $keeps=null): void {
    $update_session_creation_time = false;
    if ($keys === null) {
      $keys = array_keys($_SESSION);
      $update_session_creation_time = true;
    }
    if ($keeps !== null) $keys = array_diff($keys, $keeps);
    foreach ($keys as $key) {
      unset($_SESSION[$key]);
    }
    if ($update_session_creation_time) {
      self::set(self::SESSION_CREATION_TIME, time());
    }
  }

  /** Vider la session de toutes les clés spécifiées. */
  static final function unset_key(...$keys): void {
    self::unset_keys($keys);
  }

  /**
   * Dans la session, ne garder que les clés spécifiées.
   *
   * Refaire la variable SESSION_CREATION_TIME
   */
  static final function keep_key(...$keeps): void {
    self::unset_keys(null, $keeps);
  }

  /** vérifier si la session est démarrée et si la clé spécifiée existe. */
  static final function has($key): bool {
    if ($key === null || $key === false) return false;
    return isset($_SESSION) && array_key_exists($key, $_SESSION);
  }

  /** obtenir la valeur associée à la clé spécifiée si la session est démarrée. */
  static final function get(string $key, $default=null) {
    if (!isset($_SESSION)) return $default;
    return A::get($_SESSION, $key, $default);
  }

  /**
   * mettre à jour la valeur d'une variable de session.
   *
   * ne pas chercher à savoir si la session est démarrée ou non
   */
  static final function set(string $key, $value): void {
    $_SESSION[$key] = $value;
  }

  /**
   * comme {@link set()} mais rouvrir automatiquement la session si nécessaire,
   * à condition qu'elle aie déjà été ouverte une fois
   */
  static final function setx(string $key, $value): void {
    $close = !self::started() && self::started_once();
    if ($close) self::start();
    self::set($key, $value);
    if ($close) self::close();
  }

  /**
   * supprimer une variable de session.
   *
   * ne pas chercher à savoir si la session est démarrée ou non
   */
  static final function del(string $key): void {
    unset($_SESSION[$key]);
  }

  /**
   * comme {@link del()} mais rouvrir automatiquement la session si nécessaire,
   * à condition qu'elle aie déjà été ouverte une fois
   */
  static final function delx(string $key): void {
    $close = !self::started() && self::started_once();
    if ($close) self::start();
    self::del($key);
    if ($close) self::close();
  }

  /** vérifier si chemin de clé spécifié existe dans la session. */
  static final function phas($pkey): bool {
    return isset($_SESSION) && A::phas($_SESSION, $pkey);
  }

  /** obtenir la valeur associée au chemin de clé spécifié si la session est démarrée. */
  static final function pget($pkey, $default=null) {
    return isset($_SESSION) && A::pget($_SESSION, $pkey, $default);
  }

  /**
   * mettre à jour la valeur correspondant au chemin de clé spécifié.
   *
   * ne pas chercher à savoir si la session est démarrée ou non
   */
  static final function pset($pkey, $value): void {
    A::pset($_SESSION, $pkey, $value);
  }

  /**
   * comme {@link pset()} mais rouvrir automatiquement la session si nécessaire,
   * à condition qu'elle aie déjà été ouverte une fois
   */
  static final function psetx(string $key, $value): void {
    $close = !self::started() && self::started_once();
    if ($close) self::start();
    self::pset($key, $value);
    if ($close) self::close();
  }

  /**
   * supprimer la variable au chemin de clé spécifié.
   *
   * ne pas chercher à savoir si la session est démarrée ou non
   */
  static final function pdel($pkey): void {
    A::pdel($_SESSION, $pkey);
  }

  /**
   * comme {@link pdel()} mais rouvrir automatiquement la session si nécessaire,
   * à condition qu'elle aie déjà été ouverte une fois
   */
  static final function pdelx(string $key): void {
    $close = !self::started() && self::started_once();
    if ($close) self::start();
    self::pdel($key);
    if ($close) self::close();
  }
}
