<?php
require_once("../config/database.php");
require_once('resource_base.php');
abstract class service_base
{

    /** Label representing an LTI 2 message type */
    const LTI_VERSION2P0 = 'LTI-2p0';
    /** Service enabled */
    const SERVICE_ENABLED = 1;

    /** @var string ID for the service. */
    protected $id;
    /** @var string Human readable name for the service. */
    protected $name;
    /** @var boolean <code>true</code> if requests for this service do not need to be signed. */
    protected $unsigned;
    /** @var stdClass Tool proxy object for the current service request. */
    private $toolproxy;
    /** @var stdClass LTI type object for the current service request. */
    private $type;
    /** @var array LTI type config array for the current service request. */
    private $typeconfig;
    /** @var array Instances of the resources associated with this service. */
    protected $resources;


    /**
     * Class constructor.
     */
    public function __construct()
    {

        $this->id = null;
        $this->name = null;
        $this->unsigned = false;
        $this->toolproxy = null;
        $this->type = null;
        $this->typeconfig = null;
        $this->resources = null;

    }

    /**
     * Get the service ID.
     *
     * @return string
     */
    public function get_id()
    {

        return $this->id;

    }

    /**
     * Get the service compoent ID.
     *
     * @return string
     */
    public function get_component_id()
    {

        return 'ltiservice_' . $this->id;

    }

    /**
     * Get the service name.
     *
     * @return string
     */
    public function get_name()
    {

        return $this->name;

    }

    /**
     * Get whether the service requests need to be signed.
     *
     * @return boolean
     */
    public function is_unsigned()
    {

        return $this->unsigned;

    }

    /**
     * Get the tool proxy object.
     *
     * @return stdClass
     */
    public function get_tool_proxy()
    {

        return $this->toolproxy;

    }

    /**
     * Set the tool proxy object.
     *
     * @param object $toolproxy The tool proxy for this service request
     *
     * @var stdClass
     */
    public function set_tool_proxy($toolproxy)
    {

        $this->toolproxy = $toolproxy;

    }

    /**
     * Get the type object.
     *
     * @return stdClass
     */
    public function get_type()
    {

        return $this->type;

    }

    /**
     * Set the LTI type object.
     *
     * @param object $type The LTI type for this service request
     *
     * @var stdClass
     */
    public function set_type($type)
    {

        $this->type = $type;

    }

    /**
     * Get the type config array.
     *
     * @return array|null
     */
    public function get_typeconfig()
    {

        return $this->typeconfig;

    }

    /**
     * Set the LTI type config object.
     *
     * @param array $typeconfig The LTI type config for this service request
     *
     * @var array
     */
    public function set_typeconfig($typeconfig)
    {

        $this->typeconfig = $typeconfig;

    }

    /**
     * Get the resources for this service.
     *
     * @return resource_base[]
     */
    abstract public function get_resources();

    /**
     * Get the scope(s) permitted for this service in the context of a particular tool type.
     *
     * A null value indicates that no scopes are required to access the service.
     *
     * @return array|null
     */
    public function get_permitted_scopes()
    {
        return null;
    }

    /**
     * Get the scope(s) permitted for this service.
     *
     * A null value indicates that no scopes are required to access the service.
     *
     * @return array|null
     */
    public function get_scopes()
    {
        return null;
    }

    /**
     * Called when a new LTI Instance is added.
     *
     * @param object $lti LTI Instance.
     */
    public function instance_added(object $lti): void
    {

    }

    /**
     * Called when a new LTI Instance is updated.
     *
     * @param object $lti LTI Instance.
     */
    public function instance_updated(object $lti): void
    {

    }

    /**
     * Get the path for service requests.
     *
     * @return string
     */
    public static function get_service_path()
    {
        $url = CLIENT_HTTP_PATH . 'tool/services.php';
        return $url;
    }

    /**
     * Parse a string for custom substitution parameter variables supported by this service's resources.
     *
     * @param string $value  Value to be parsed
     *
     * @return string
     */
    public function parse_value($value, $offering_id, $contentdtl_id)
    {

        if (empty($this->resources)) {
            $this->resources = $this->get_resources();
        }
        if (!empty($this->resources)) {
            foreach ($this->resources as $resource) {
                $value = $resource->parse_value($value, $offering_id, $contentdtl_id);
            }
        }

        return $value;

    }

    /**
     * Check that the request has been properly signed and is permitted.
     *
     * @param string $typeid    LTI type ID
     * @param string $body      Request body (null if none)
     * @param string[] $scopes  Array of required scope(s) for incoming request
     *
     * @return boolean
     */
    public function check_tool($typeid, $body = null, $scopes = null, $date_time)
    {
        $lti = new LTI();
        $ok = true;
        $toolproxy = null;
        $consumerkey = get_oauth_key_from_headers($typeid, $scopes, $date_time);
        if ($consumerkey === false) {
            $ok = $this->is_unsigned();
        } else {
            if (empty($typeid) && is_int($consumerkey)) {
                $typeid = $consumerkey;
            }
            if (!empty($typeid)) {
                $tool = new stdClass();
                $stmt = $lti->get_lti_types_from_clientId($typeid);
                if ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    $tool->lti_typename = $result['tool_name'];
                    $tool->typeid = $result['lti_types_id'];
                    $tool->lti_toolurl = $result['tool_url'];
                    $tool->lti_ltiversion = $result['lti_version'];
                    $tool->lti_clientid = $result['client_id'];
                    $tool->toolproxyid = $result['tool_proxy_id'];
                    $tool->lti_parameters = $result['parameter'];
                }
                $this->type = $tool;

                $typeconfig = array();
                $stmt = $lti->get_lti_types_config($tool->typeid);
                while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    $typeconfig[$result['name']] = $result['value'];
                }
                $this->typeconfig = $typeconfig;

                $ok = !empty($this->type->id);
                if ($ok && !empty($this->type->toolproxyid)) {
                    //Toolproxy not implemented in this version
                    //Tip: Take care in next version
                }
            } else {
                //Toolproxy not implemented in this version
                //Tip: Take care in next version
            }
        }
        if ($ok && is_string($consumerkey)) {
            if (!empty($this->toolproxy)) {
                //Toolproxy not implemented in this version
                //Tip: Take care in next version
            } else {
                $key = $this->typeconfig['resourcekey'];
                $secret = $this->typeconfig['password'];
            }
            if (!$this->is_unsigned() && ($key == $consumerkey)) {
                //OAuthSignature not implemented
                //Tip: Take care in next version
                //$ok = $this->check_signature($key, $secret, $body);
                //$ok = true;
            } else {
                $ok = $this->is_unsigned();
            }
        }
        return $ok;
    }
}

function get_oauth_key_from_headers($typeid = null, $scopes = null, $date_time)
{
    $lti = new LTI();
    $now = $date_time;
    $requestheaders = OAuthUtil::get_headers();

    if (isset($requestheaders['Authorization'])) {
        if (substr($requestheaders['Authorization'], 0, 6) == "OAuth ") {
            $headerparameters = OAuthUtil::split_header($requestheaders['Authorization']);
            return $headerparameters['oauth_consumer_key'];
        } else if (empty($scopes)) {
            return true;
        } else if (substr($requestheaders['Authorization'], 0, 7) == 'Bearer ') {
            $tokenvalue = trim(substr($requestheaders['Authorization'], 7));

            $stmt = $lti->token_record_exists($tokenvalue);
            if ($stmt->rowCount() > 0) {
                if ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    // Log token access.
                    $updatetoken = new stdClass();
                    $updatetoken->last_access = $now;
                    $updatetoken->lti_access_tokens_id = $result['lti_access_tokens_id'];
                    $lti->update_token_last_access($updatetoken);

                    $permittedscopes = json_decode($result['scope']);
                    if ((intval($result['valid_until']) > $now) && !empty(array_intersect($scopes, $permittedscopes))) {
                        return intval($result['lti_types_id']);
                    }
                }
            }
        }
    }
    return false;
}

class OAuthUtil
{
    // helper to try to sort out headers for people who aren't running apache
    public static function get_headers()
    {
        if (function_exists('apache_request_headers')) {
            // we need this to get the actual Authorization: header
            // because apache tends to tell us it doesn't exist
            $in = apache_request_headers();
            $out = array();
            foreach ($in as $key => $value) {
                $key = str_replace(" ", "-", ucwords(strtolower(str_replace("-", " ", $key))));
                $out[$key] = $value;
            }
            return $out;
        }
        // otherwise we don't have apache and are just going to have to hope
        // that $_SERVER actually contains what we need
        $out = array();
        foreach ($_SERVER as $key => $value) {
            if (substr($key, 0, 5) == "HTTP_") {
                // this is chaos, basically it is just there to capitalize the first
                // letter of every word that is not an initial HTTP and strip HTTP
                // code from przemek
                $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
                $out[$key] = $value;
            }
        }
        return $out;
    }

    // This decode function isn't taking into consideration the above
    // modifications to the encoding process. However, this method doesn't
    // seem to be used anywhere so leaving it as is.
    public static function urldecode_rfc3986($string)
    {
        return urldecode($string);
    }

    // Utility function for turning the Authorization: header into
    // parameters, has to do some unescaping
    // Can filter out any non-oauth parameters if needed (default behaviour)
    public static function split_header($header, $only_allow_oauth_parameters = true)
    {
        $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
        $offset = 0;
        $params = array();
        while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
            $match = $matches[0];
            $header_name = $matches[2][0];
            $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
            if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
                $params[$header_name] = self::urldecode_rfc3986($header_content);
            }
            $offset = $match[1] + strlen($match[0]);
        }
        if (isset($params['realm'])) {
            unset($params['realm']);
        }
        return $params;
    }
}