<?php
/**
 * SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * @author Sascha Nitsch (grumpydeveloper)
 **/

 namespace Federator;

/**
 * main API class
 */
class Api extends Main
{
    /**
     * called path
     *
     * @var string $path
     */
    private $path;

    /**
     * path elements for the API call
     *
     * @var array<string> $paths
     * */
    private $paths;

    /**
     * current user
     *
     * @var Data\User|false $user
     * */
    private $user;

    /**
     * cache time default to 0
     *
     * @var int $cacheTime
     */
    private $cacheTime = 0;

    /**
     * constructor
     */
    public function __construct()
    {
        $this->smarty = null;
        $this->contentType = "application/json";
        Main::__construct();
    }

    /**
     * set path
     *
     * @param string $call
     *          path of called function
     * @return void
     */
    public function setPath($call)
    {
        $this->path = $call;
        while ($this->path[0] === '/') {
            $this->path = substr($this->path, 1);
        }
        $this->paths = explode("/", $this->path);
    }

    /**
     * main API function
     */
    public function run() : void
    {
        $this->setPath((string)$_REQUEST['_call']);
        $this->openDatabase();
        $this->loadPlugins();
        $retval = "";
        $handler = null;
        if (!array_key_exists('HTTP_X_SESSION', $_SERVER) || !array_key_exists('HTTP_X_PROFILE', $_SERVER)) {
            http_response_code(403);
            return;
        }
        if ($this->connector === null) {
            http_response_code(500);
            return;
        }
        $this->user = DIO\User::getUserBySession(
            $this->dbh,
            $_SERVER['HTTP_X_SESSION'],
            $_SERVER['HTTP_X_PROFILE'],
            $this->connector,
            $this->cache
        );
        if ($this->user === false) {
            http_response_code(403);
            return;
        }
        switch ($this->path[0]) {
            case 'v':
                if ($this->paths[0] === "v1") {
                    switch ($this->paths[1]) {
                        case 'dummy':
                            $handler = new Api\V1\Dummy($this);
                            break;
                    }
                }
                break;
        }
        $printresponse = true;
        if ($handler !== null) {
            try {
                $printresponse = $handler->exec($this->paths);
                if ($printresponse) {
                    $retval = $handler->toJson();
                }
            } catch (Exceptions\Exception $e) {
                $this->setResponseCode($e->getRetCode());
                $retval = json_encode(array(
                    "error" => $e->getMessage()
                ));
            }
        } else {
            $this->responseCode = 404;
        }
        if (sizeof($this->headers) != 0) {
            foreach ($this->headers as $name => $value) {
                header($name . ': ' . $value);
            }
        }
        if ($printresponse) {
            if ($this->redirect !== null) {
                header("Location: $this->redirect");
            }
            // @phan-suppress-next-line PhanSuspiciousValueComparison
            if ($this->responseCode != 200) {
                http_response_code($this->responseCode);
            }
            if ($this->responseCode != 404) {
                header("Content-type: " . $this->contentType);
                header("Access-Control-Allow-Origin: *");
            }
            if ($this->cacheTime == 0) {
                header("Cache-Control: no-cache, no-store, must-revalidate");
                header("Pragma: no-cache");
                header("Expires: 0");
            } else {
                $ts = gmdate("D, d M Y H:i:s", time() + $this->cacheTime) . " GMT";
                header("Expires: $ts");
                header("Pragma: cache");
                header("Cache-Control: max-age=" . $this->cacheTime);
            }
            echo $retval;
        } else {
            if (!headers_sent()) {
                header("Content-type: " . $this->contentType);
            }
        }
    }

    /**
     * check if the current user has the given permission
     *
     * @param string|string[] $permission
     *          permission(s) to check for
     * @param string $exception Exception Type
     * @param string $message optional message
     * @throws Exceptions\PermissionDenied
     */
    public function checkPermission($permission, $exception = "\Exceptions\PermissionDenied", $message = null) : void
    {
        // generic check first
        if ($this->user === false) {
            throw new Exceptions\PermissionDenied();
        }
        if ($this->user->id == 0) {
            throw new Exceptions\PermissionDenied();
        }
        if (!is_array($permission)) {
            $permission = array(
                $permission
            );
        }
        // LoggedIn is handled above
        foreach ($permission as $p) {
            if ($this->user->hasPermission($p)) {
                return;
            }
        }
        throw new $exception($message);
    }

    /**
     * remove unwanted elements from html input
     *
     * @param string $_input
     *          input to strip
     * @return string stripped input
     */
    public static function stripHTML(string $_input) : string
    {
        $out = preg_replace('/<(script[^>]*)>/i', '&lt;${1}&gt;', $_input);
        $out = preg_replace('/<\/(script)>/i', '&lt;/${1};&gt;', $out);
        return $out;
    }

    /**
     * is given parameter in POST data
     *
     * @param string $_key
     *          parameter to check
     * @return bool true if in
     */
    public static function hasPost(string $_key) : bool
    {
        return array_key_exists($_key, $_POST);
    }

    /**
     * SQL escape given POST parameter
     *
     * @param string $key
     *          key to escape
     * @param boolean $int
     *          is parameter an int
     * @return int|string
     */
    public function escapePost(string $key, $int = false)
    {
        if (! array_key_exists($key, $_POST)) {
            return $int ? 0 : "";
        }
        if ($int === true) {
            return intval($_POST[$key]);
        }
        $ret = $this->dbh->escape_string($this->stripHTML((string)$_POST[$key]));
        return $ret;
    }

    /**
     * update $data with POST info using optional alias $altName
     *
     * @param string $name
     *          parameter name
     * @param array<string, mixed> $data
     *          array to update
     * @param bool $int
     *          is data an integer
     * @param string $altName
     *          optional alternative name in POST
     * @return void
     */
    public function updateString($name, &$data, $int = false, $altName = "")
    {
        if ($this->hasPost($altName ?: $name)) {
            $content = $this->escapePost($altName ?: $name, $int);
            $data[$name] = $content;
        }
    }

    /**
     * set cache time
     *
     * @param int $time time in seconds
     * @return void
     */
    public function setCacheTime($time)
    {
        $this->cacheTime = $time;
    }
}