<?php
// vim: ts=2 sw=2

include_once "db.php";
include_once "migration.php";
include_once "settings.php";
include_once "feature_control.php";
include_once "lang.php";
include_once "utils.php";

include_once "config.php";

class BaseController {

  public $flash = NULL;

  function __construct() {}

  private function isHttps() {
    return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'
    || $_SERVER['SERVER_PORT'] == 443);
  }

  protected function makeUrl($url) {
    if (startsWith($url, 'http://') || startsWith($url, 'https://'))
        return $url;
    $pageURL = 'http';
    if ($this->isHttps()) {$pageURL .= "s";}
    $pageURL .= "://" . $_SERVER["HTTP_HOST"];
    if (preg_match("/:[0-9]+$/", $pageURL)==0 && $_SERVER["SERVER_PORT"] != "80") {
      $port_str = ":".$_SERVER["SERVER_PORT"];
      if (!endsWith($pageURL, $port_str))
        $pageURL .=  $port_str;
    }
    if (startsWith($url, '/'))
      $pageURL .= $url;
    else
      $pageURL .= $this->cptBaseUrl() . '/' . $url;
    return $pageURL;
  }

  protected function cptBaseUrl() {
    $rootPath = $_SERVER['DOCUMENT_ROOT'];
    $curPath = substr(__FILE__, strlen($rootPath));
    $parts = explode(DIRECTORY_SEPARATOR, $curPath);
    array_pop($parts);
    array_pop($parts);

    //NOTE: since we are dealing with URL, so only '/' is valid, even on 
    //windows platform. no need to use DIRECTORY_SEPARATOR 
    $result = implode('/', $parts);
    if (!startsWith($result, '/'))
        $result = '/' . $result;
    return $result;
  }

  protected function isAjax() {
    return (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
  }

  protected function signinRequired() {
    return false;
  }

  protected function adminRequired() {
    return false;
  }
  
  protected function authKeyRequired() {
    return false;
  }

  protected function sameHostAuthFree() {
    return false;
  }

  protected function isSessionExpired() {
    $s = Settings::instance();
    $ttl_enabled = $s->session_ttl_enabled();
    if (!$ttl_enabled) 
      return false;

    $ttl_value = $s->session_ttl_val();
    return isset($_SESSION['last_visit_time']) && $_SESSION['last_visit_time'] + $ttl_value*60 < time();
  }

  protected function isSignedin() {
    return isset($_SESSION['user_id']) && !$this->isSessionExpired();
  }
  
  protected function curUserId() {
    if (isset($_SESSION['user_id']))
      return $_SESSION['user_id'];
    else
      return null;
  }

  public function curUser() {
    if (!isset($_SESSION['user_name']))
      return NULL;

    $user_name = $_SESSION['user_name'];
    if (!isset($user_name))
      return NULL;

    $u = new User();
    if (!$u->find($user_name))
      return NULL;
    return $u;
  }

  protected function redirect($url, $permanent = false) {
    if ($this->isAjax())
    {
        $url = addcslashes($url,"\\\"\n\r");
        die('{"redirect": "' . $url . '"}');
    }
    else
    {
        header('Location: ' . $url, true, $permanent ? 301 : 302)
        die();
    }
  }
  protected function userHomePage() {
    $u = $this->curUser();
    if (FeatureControl::instance()->isEnabled("Dashboard")
      && isset($u) && $u->isDashboardEnabled() && $u->isDashboardLanding())
      return $this->akeUrl("dashboard/index.php");
    else
      return $this->akeUrl("app/graphic.php");
  }

  protected function handleCORS() {
    //NOTE: to make CORS working, must put following config into appweb.conf file: 
    // CrossOrigin origin=* credentials=yes
    if (!isset($_SERVER['HTTP_ORIGIN'])) 
      return false;

    header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}")
    header("Access-Control-Allow-Credentials: true");
    header("Access-Control-Allow-Methods: POST,GET,OPTIONS");
    header("Access-Control-Allow-Headers: Origin,X-Requested-With,Keep-Alive,User-Agent,Content-Type,Content-Range,Range,Accept,If-Modified-Since,Cache-Control");
    header("Access-Control-Max-Age: 86400");

    $method = $_SERVER['REQUEST_METHOD'];
    if ($method == "OPTIONS") 
      return true;
    else
      return false;
  }
  
  protected function handleAuthKey() {
    if (!$this->authKeyRequired())
      return false;

    if (isset($_REQUEST['auth_key'])) {
      $auth_key = $_REQUEST['auth_key'];
      if (AuthKeys::isValid($auth_key)) {
        unset($_REQUEST['auth_key']);

        //TODO: associate auth_key with user. 
        //      now all auth_key represent 'admin' user
        $_SESSION['user_name'] = 'admin';
        $_SESSION['user_id'] = 1;

        // $u = $this->curUser();
        // if ($this->adminRequired())
        //   $this->checkAdmin();
    
        $this->handleRequest();
      }
      else
        die(L("auth_key is not correct or has expired"));
    }
    else
      die(L("auth_key is missing"));
    return true;
  }
  
  protected function handleRequest() {
    if ($this->isAjax()) {
      $this->ajaxRun();
    } else {
      $this->normalRun();
    }
  }

  public function run() {
    $this->sanitizeData();

    $this->applyMigration();

    //GOTCHA: if there are multiple php application deployed, then by default
    //they all use the cookie 'PHPSESSID', that will cause conflicts issue
    //among them, rename the cookie to fix it. 
    session_start(Array('name' => 'CPTSESSID'));
    
    if ($this->handleCORS())
      return;

    if ($this->sameHostAuthFree() && $this->isLocalRequest()) {
      $this->handleRequest();
      return;
    }
      
    if ($this->signinRequired()) {
      if (!$this->isSignedin()) {
        if ($this->handleAuthKey())
          return;

        if (!$this->isAjax())
          $_SESSION['url_required'] = $_SERVER['REQUEST_URI'];

        unset($_SESSION['user_id']);
        unset($_SESSION['user_name']); 
        unset($_SESSION['last_visit_time']); 

        $this->redirect($this->akeUrl('app/signin.php'))
      }
    }
    else {
      if ($this->handleAuthKey())
        return;
    }

    $_SESSION['last_visit_time'] = time();

    $u = $this->curUser();
    if ($this->adminRequired())
      $this->checkAdmin();
    
    $this->handleRequest();
  }

  public function flash() {
    if (!is_null($this->flash))
      return $this->flash;

    if(isset($_SESSION['flash'])) {
      $this->flash = $_SESSION['flash'];
      unset($_SESSION['flash']);
      return $this->flash;
    } else
      return NULL;
  }

  protected function normalRun() {
    if (!$this->isPlatformSupported())
      die("this controller platform is not supported.");

    $method = $_SERVER['REQUEST_METHOD'];
    if ($method == "POST") {
      $this->doPost();
    } else if ($method == "GET") {
      $this->doGet();
    } else {
        error_log("request method '" . $method . "' is not supported");
    }
  }

  protected function ajaxRun() {
    $response = array();
    if (!$this->isPlatformSupported()) {
      $this->renderAjaxError($response, "this controller platform is not supported");
      return;
    }

    $method = $_SERVER['REQUEST_METHOD'];
    if ($method == "POST") {
      $this->doAjaxPost();
    } else if ($method == "GET") {
      $this->doAjaxGet();
    } else {
      $error = sprintf(L("%s is not supported"), $method);
      $response['error'] = array('text' => $error);
      echo <<<EOS
{"error": {"text": "{$response['error']['text']}"}}
EOS;
    }
  }

  private function permCheck($isRead=true) {
    $u = $this->curUser();
    if (is_null($u))
      return false;

    if ($u->isAdmin())
      return true;

    if (!isset($_SESSION['curGrPath']))
    {
      //if curGrPath is missing, then check dev permission 
      return $isRead ? $u->can('read') : $u->can('write');
    }

    $grPath = $_SESSION['curGrPath'];
    if ($isRead)
      return $u->can('read', $grPath);
    else
      return $u->can('write', $grPath);
  }

  protected function isReadable() {
    return $this->permCheck(true);
  }

  protected function isWritable() {
    return $this->permCheck(false);
  }

  protected function isUtilityEnabled() {
    return $this->curUser()->isUtilityEnabled();
  }

  protected function isSystemEnabled() {
    return $this->curUser()->isSystemEnabled();
  }

  protected function renderAjaxError($response, $error) {
    $response['error'] = array('text' => addcslashes($error, "\\\"\n\r"));
    die(<<<EOS
{"error": {"text": "{$response['error']['text']}"}}
EOS
    );
  }

  protected function renderAjaxSuccess($response, $escapeStr=true) {
    $json = array();
    foreach($response as $k => $v) {
      if ($escapeStr && is_string($v))
          $v = addcslashes($v, "\\\"\n\r");
          // $v = addslashes($v); // should not escape single quote for json string
      $json[] = "\"$k\": \"$v\"";
    }
    die('{' . implode(',', $json) . '}');
  }
  protected function renderAjaxSuccess2($response) {
    if (empty($response))
      die("{}");
    else
      die(map2json($response));
  }

  protected function renderErrorResponse($error) {
    if ($this->isAjax()) {
      $response = array();
      $this->renderAjaxError($response, $error);
    } else {
      die($error);
    }
  }

  protected function escapeJsonChars($val) {
    return addcslashes($val, "\\\"\n\r");
  }

  protected function genDownloadResp($content, $file_name) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="'. $file_name)
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . strlen($content))

    print $content;
  }
  
  protected function signinPhpLiteAdmin($user_checksum) {
    //allow phpLiteAdmin to auto login
    //GOTCHA: when upgrade phpLiteAdmin, the cookie's name will need to be updated 
    $phpliteadmin_cookie_name = 'pla3412_1_9_7_1' . 'password';
    $_SESSION[$phpliteadmin_cookie_name] = $user_checksum; 
  }
  
  protected function signoutPhpLiteAdmin() {
    //allow phpLiteAdmin to auto login
    //GOTCHA: when upgrade phpLiteAdmin, the cookie's name will need to be updated 
    $phpliteadmin_cookie_name = 'pla3412_1_9_7_1' . 'password';
    unset($_SESSION[$phpliteadmin_cookie_name]);
  }

  protected function checkAdmin() {
    $u = $this->curUser();
    if (!$u->isAdmin()) {
      $_SESSION['flash'] = L("Permission denied");
      $this->redirect($this->akeUrl('app/landing_page.php?permission_error=1'))
    }
  }

  protected function sanitizeData() {
    if (function_exists('filter_input_array')) {
      $_GET   = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
      $_POST  = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
      // $_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST;
    }
  }
  protected function sanitizeParam($param) {
    if (function_exists('filter_var'))
      return filter_var($param, FILTER_SANITIZE_STRING);
    else
      return $param;
  }
  protected function sanitizeURL($url) {
    if (function_exists('filter_var'))
        return filter_var($url, FILTER_SANITIZE_URL);
    else
        return $url;
  }
  protected function getInputData($name, $is_get_param=true) {
    if (function_exists('filter_input'))
      return filter_input($is_get_param ? INPUT_GET : INPUT_POST, $name);
    else 
      return $is_get_param ? $_GET[$name] : $_POST[$name];
  }

  protected function applyMigration() {
      Migration::instance()->run();
  }

  protected function isLocalRequest() {
    return $_SERVER['SERVER_ADDR'] == $_SERVER['REMOTE_ADDR']; 
  }

  protected function enableCache() {
    header("Cache-Control: public"); // HTTP 1.1.
    header("Expires: 0"); // Proxies.
  }
  protected function disableCache() {
    # refers: http://stackoverflow.com/questions/49547/how-to-control-web-page-caching-across-all-browsers
    header("Cache-Control: no-cache, no-store, must-revalidate"); // HTTP 1.1.
    header("Pragma: no-cache"); // HTTP 1.0.
    header("Expires: 0"); // Proxies.
  }

  //the return value is always a map with keys: white_list and/or black_list.
  //for every list, all supported/unsupported platform is included.
  protected function platformControl() {
    return array();
  }
  protected function isPlatformSupported() {
    $control = $this->platformControl();
    if (!is_array($control))
      return false;

    $name = platformName();
    if (isset($control['black_list']) && in_array($name, $control['black_list']))
      return false;
    
    if (isset($control['white_list'])) {
      return in_array($name, $control['white_list']);
    }

    return true;
  }

  protected function doGet() { }
  protected function doPost() { }

  protected function doAjaxGet() { }
  protected function doAjaxPost() { }
}

?>