<?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() { }
}
?>