<?php

/**
 * This file is the dispatcher of the whole application, every request for data enters
 * here. JSON is received and send to the client.
 */

// Bootstrap the script
require_once 'server/includes/bootstrap.grommunio.php';

// Callback function for unserialize
// Notifier objects of the previous request are stored in the session. With this
// function they are restored to PHP objects.
ini_set("unserialize_callback_func", "sessionNotifierLoader");

// Try to authenticate the user
WebAppAuthentication::authenticate();

// Globals suck, but we use it still in many files, so we will
// store the mapisession as global
$GLOBALS["mapisession"] = WebAppAuthentication::getMAPISession();

// Get the language from the session
// before we close the session.
if (isset($_SESSION["lang"])) {
	$session_lang = $_SESSION["lang"];
}
else {
	$session_lang = LANG;
}

// Set headers for JSON
header("Content-Type: application/json; charset=utf-8");
header("Expires: " . gmdate("D, d M Y H:i:s") . "GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
if (WebAppAuthentication::isAuthenticated()) {
	header("X-grommunio: " . trim(file_get_contents(BASE_PATH . 'version')));
}

// If a service request was sent (a REST call), the service controller will handle it.
if (isset($_GET['service'])) {
	require_once BASE_PATH . 'server/includes/controllers/service.php';

	exit;
}

// Close the session now, so we're not blocking other requests
session_write_close();

// If a ping request was sent, we the ping controller will handle it.
if (isset($_GET['ping'])) {
	require_once BASE_PATH . 'server/includes/controllers/ping.php';

	exit;
}

if (!WebAppAuthentication::isAuthenticated()) {
	if (WebAppAuthentication::getErrorCode() === MAPI_E_NETWORK_ERROR) {
		// The user is not logged in because the Gromox server could not be reached.
		// Return a HTTP 503 error so the client can act upon this event correctly.
		header('HTTP/1.1 503 Service unavailable');
		header("X-grommunio-Hresult: " . get_mapi_error_name(WebAppAuthentication::getErrorCode()));
	}
	else {
		// The session expired, or the user is otherwise not logged on.
		// Return a HTTP 401 error so the client can act upon this event correctly.
		header('HTTP/1.1 401 Unauthorized');
		header("X-grommunio-Hresult: " . get_mapi_error_name(WebAppAuthentication::getErrorCode()));
	}

	exit;
}

// Instantiate Plugin Manager
$GLOBALS['PluginManager'] = new PluginManager(ENABLE_PLUGINS);
$GLOBALS['PluginManager']->detectPlugins(DISABLED_PLUGINS_LIST);

// Initialize plugins and prevent any output which might be written as
// plugins might be uncleanly output white-space and other stuff. We must
// not allow this here as it can destroy the response data.
ob_start();
$GLOBALS['PluginManager']->initPlugins(DEBUG_LOADER);
ob_end_clean();

// Create global dispatcher object
$GLOBALS["dispatcher"] = new Dispatcher();

// Create global operations object
$GLOBALS["operations"] = new Operations();

// Create global language object
$Language = new Language();

// Create global settings object
$GLOBALS["settings"] = new Settings($Language);

// Set the correct language
$Language->setLanguage($session_lang);

// Eagerly initialize settings before the state lock.  Init() loads
// settings from the MAPI store (network I/O to Gromox) and does not
// depend on the per-subsystem bus/properties, so running it here
// keeps those MAPI round-trips out of the critical lock section.
$GLOBALS["settings"]->Init();

// Create new request object and read input data before acquiring the
// state lock — neither depends on state and this keeps the lock duration
// as short as possible.
$request = new JSONRequest();
$json = readData();

if (DEBUG_JSONOUT) {
	dump_json($json, "in"); // debugging
}

// Get the state information for this subsystem
$subsystem = sanitizeGetValue('subsystem', 'anonymous', ID_REGEX);

$state = new State($subsystem);

// Lock the state of this subsystem
$state->open();

// Get the bus object for this subsystem
$bus = $state->read("bus");

if (!$bus) {
	// Create global bus object
	$bus = new Bus();
}

// Make bus global
$GLOBALS["bus"] = $bus;

// Reset any spurious information in the bus state
$GLOBALS["bus"]->reset();

// Create global properties object
$properties = $state->read("properties");

if (!$properties) {
	$properties = new Properties();
}
$GLOBALS["properties"] = $properties;

// Reset any spurious information in the properties state
$GLOBALS["properties"]->reset();

// Execute the request
try {
	$json = $request->execute($json);
}
catch (Exception $e) {
	// invalid requestdata exception
	dump($e);
}

// Save bus and properties back to state, flush to disk, and release the
// lock before doing any I/O (gzip + echo).  This lets the next request
// from the same subsystem proceed while we compress and transmit.
$GLOBALS["bus"]->reset();
$state->write("bus", $GLOBALS["bus"], false);

$GLOBALS["properties"]->reset();
$state->write("properties", $GLOBALS["properties"], false);

$state->flush();
$state->close();

if (DEBUG_JSONOUT) {
	dump_json($json, "out"); // debugging
}

// Check if we can use gzip compression
if (ENABLE_RESPONSE_COMPRESSION && function_exists("gzencode") && isset($_SERVER["HTTP_ACCEPT_ENCODING"]) && str_contains((string) $_SERVER["HTTP_ACCEPT_ENCODING"], "gzip")) {
	// Set the correct header and compress the response
	header("Content-Encoding: gzip");
	echo gzencode($json);
}
else {
	echo $json;
}
