YetiForceCompany/YetiForceCRM

View on GitHub
app/Request.php

Summary

Maintainability
F
3 days
Test Coverage
C
72%
<?php
/**
 * Request basic class.
 *
 * @package App
 *
 * @copyright YetiForce S.A.
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
 * @author    Mariusz Krzaczkowski <m.krzaczkowski@yetiforce.com>
 * @author    Radosław Skrzypczak <r.skrzypczak@yetiforce.com>
 */

namespace App;

/**
 * Request basic class.
 */
class Request
{
    /**
     * Raw request data.
     *
     * @var array
     */
    protected $rawValues = [];

    /**
     * Headers request.
     *
     * @var array
     */
    protected $headers;

    /**
     * Self instance.
     *
     * @var Request
     */
    protected static $request;

    /**
     * Purified request values for get.
     *
     * @var array
     */
    protected $purifiedValuesByGet = [];

    /**
     * Purified request values for type.
     *
     * @var array
     */
    protected $purifiedValuesByType = [];

    /**
     * Purified request values for integer.
     *
     * @var array
     */
    protected $purifiedValuesByInteger = [];

    /**
     * Purified request values for array.
     *
     * @var array
     */
    protected $purifiedValuesByArray = [];

    /**
     * Purified request values for exploded.
     *
     * @var array
     */
    protected $purifiedValuesByExploded = [];

    /**
     * Purified request values for multi dimension array.
     *
     * @var array
     */
    protected $purifiedValuesByMultiDimension = [];

    /**
     * Purified request values for date range.
     *
     * @var array
     */
    protected $purifiedValuesByDateRange = [];

    /**
     * Purified request values for date html.
     *
     * @var array
     */
    protected $purifiedValuesByHtml = [];
    /**
     * List of headings and sanitization methods.
     *
     * @var array
     */
    public $headersPurifierMap = [
    ];

    /**
     * Constructor.
     *
     * @param array $rawValues
     * @param bool  $overwrite
     */
    public function __construct($rawValues, $overwrite = true)
    {
        $this->rawValues = $rawValues;
        if ($overwrite) {
            static::$request = $this;
        }
    }

    /**
     * Function to get the value for a given key.
     *
     * @param string $key
     * @param mixed  $value Default value
     *
     * @return mixed
     */
    public function get($key, $value = '')
    {
        if (isset($this->purifiedValuesByGet[$key])) {
            return $this->purifiedValuesByGet[$key];
        }
        if (isset($this->rawValues[$key])) {
            $value = $this->rawValues[$key];
        } else {
            return $value;
        }
        if (\is_string($value) && (0 === strpos($value, '[') || 0 === strpos($value, '{'))) {
            $decodeValue = Json::decode($value);
            if (isset($decodeValue)) {
                $value = $decodeValue;
            }
        }
        if ($value) {
            $value = Purifier::purify($value);
        }

        return $this->purifiedValuesByGet[$key] = $value;
    }

    /**
     * Purify by data type.
     *
     * Type list:
     * Standard - only words
     * 1 - only words
     * Alnum - word and int
     * 2 - word and int
     *
     * @param string     $key     Key name
     * @param int|string $type    Data type that is only acceptable, default only words 'Standard'
     * @param mixed      $convert
     *
     * @return bool|mixed
     */
    public function getByType($key, $type = 'Standard', $convert = false)
    {
        if (isset($this->purifiedValuesByType[$key][$type])) {
            return $this->purifiedValuesByType[$key][$type];
        }
        if (isset($this->rawValues[$key])) {
            return $this->purifiedValuesByType[$key][$type] = Purifier::purifyByType($this->rawValues[$key], $type, $convert);
        }
        return false;
    }

    /**
     * Function to get the boolean value for a given key.
     *
     * @param string $key
     * @param bool   $defaultValue Default value
     *
     * @return bool
     */
    public function getBoolean(string $key, bool $defaultValue = null)
    {
        $value = $this->get($key, $defaultValue);
        if (\is_bool($value)) {
            return $value;
        }
        return 0 === strcasecmp('true', (string) $value) || '1' === (string) $value;
    }

    /**
     * Function to get the integer value for a given key.
     *
     * @param string $key
     * @param int    $value
     *
     * @return int
     */
    public function getInteger($key, $value = 0)
    {
        if (isset($this->purifiedValuesByInteger[$key])) {
            return $this->purifiedValuesByInteger[$key];
        }
        if (!isset($this->rawValues[$key])) {
            return $value;
        }
        if (false !== ($value = filter_var($this->rawValues[$key], FILTER_VALIDATE_INT))) {
            return $this->purifiedValuesByInteger[$key] = $value;
        }

        throw new \App\Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||$key||{$this->rawValues[$key]}", 406);
    }

    /**
     * Function to get the array values for a given key.
     *
     * @param string      $key
     * @param mixed       $type
     * @param array       $value
     * @param string|null $keyType
     *
     * @return array
     */
    public function getArray($key, $type = false, $value = [], ?string $keyType = null)
    {
        if (isset($this->purifiedValuesByArray[$key])) {
            return $this->purifiedValuesByArray[$key];
        }
        if (isset($this->rawValues[$key])) {
            $value = $this->rawValues[$key];
            if (!$value) {
                return [];
            }
            if (\is_string($value) && (0 === strpos($value, '[') || 0 === strpos($value, '{'))) {
                $decodeValue = Json::decode($value);
                if (isset($decodeValue)) {
                    $value = $decodeValue;
                } else {
                    \App\Log::warning('Invalid data format, problem encountered while decoding JSON. Data should be in JSON format. Data: ' . $value);
                }
            }
            if ($value) {
                if (\is_array($value)) {
                    $input = [];
                    foreach ($value as $k => $v) {
                        if (!\is_int($k)) {
                            $k = $keyType ? Purifier::purifyByType($k, $keyType) : Purifier::purify($k);
                        }
                        $input[$k] = $type ? Purifier::purifyByType($v, $type) : Purifier::purify($v);
                    }
                    $value = $input;
                } else {
                    $value = $type ? Purifier::purifyByType($value, $type) : Purifier::purify($value);
                }
            }

            return $this->purifiedValuesByArray[$key] = (array) $value;
        }
        return $value;
    }

    /**
     * Function to get the exploded values for a given key.
     *
     * @param string      $key
     * @param string      $delimiter
     * @param bool|string $type
     *
     * @return array
     */
    public function getExploded($key, $delimiter = ',', $type = false)
    {
        if (isset($this->purifiedValuesByExploded[$key])) {
            return $this->purifiedValuesByExploded[$key];
        }
        $value = [];
        if (isset($this->rawValues[$key])) {
            if ('' === $this->rawValues[$key]) {
                return $value;
            }
            $value = explode($delimiter, $this->rawValues[$key]);
            if ($value) {
                $value = $type ? Purifier::purifyByType($value, $type) : Purifier::purify($value);
            }

            return $this->purifiedValuesByExploded[$key] = $value;
        }

        return $value;
    }

    /**
     * Purify multi dimension array.
     *
     * @param mixed        $values
     * @param array|string $template
     *
     * @throws \App\Exceptions\IllegalValue
     *
     * @return mixed
     */
    private function purifyMultiDimensionArray($values, $template)
    {
        if (\is_array($template)) {
            foreach ($values as $firstKey => $value) {
                if (\is_array($value)) {
                    if (1 === \count($template)) {
                        $template = current($template);
                    }
                    foreach ($value as $secondKey => $val) {
                        $tempTemplate = $template;
                        if (isset($template[$firstKey])) {
                            $tempTemplate = $template[$firstKey];
                        }
                        if (1 === \count($tempTemplate)) {
                            $tempTemplate = current($tempTemplate);
                        } elseif (!isset($tempTemplate[$secondKey])) {
                            throw new Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||{$secondKey}", 406);
                        } else {
                            $tempTemplate = $tempTemplate[$secondKey];
                        }
                        $values[$firstKey][$secondKey] = $this->purifyMultiDimensionArray($val, $tempTemplate);
                    }
                } else {
                    if (\is_array($template) && 1 === \count($template)) {
                        $values[$firstKey] = $this->purifyMultiDimensionArray($value, current($template));
                    } elseif (isset($template[$firstKey])) {
                        $values[$firstKey] = $this->purifyMultiDimensionArray($value, $template[$firstKey]);
                    } else {
                        throw new Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||{$firstKey}||" . print_r($template, true), 406);
                    }
                }
            }
        } else {
            $values = empty($values) ? $values : ($template ? Purifier::purifyByType($values, $template) : Purifier::purify($values));
        }
        return $values;
    }

    /**
     * Function to get multi dimension array.
     *
     * @param string $key
     * @param array  $template
     *
     * @return array
     */
    public function getMultiDimensionArray(string $key, array $template): array
    {
        $return = [];
        if (isset($this->purifiedValuesByMultiDimension[$key])) {
            $return = $this->purifiedValuesByMultiDimension[$key];
        } elseif (isset($this->rawValues[$key]) && ($value = $this->rawValues[$key])) {
            if (\is_string($value) && (0 === strpos($value, '[') || 0 === strpos($value, '{'))) {
                $decodeValue = Json::decode($value);
                if (null !== $decodeValue) {
                    $value = $decodeValue;
                } else {
                    Log::warning('Invalid data format, problem encountered while decoding JSON. Data should be in JSON format. Data: ' . $value);
                }
            }
            $value = (array) $this->purifyMultiDimensionArray($value, $template);
            $return = $this->purifiedValuesByMultiDimension[$key] = $value;
        }

        return $return;
    }

    /**
     * Function to get the date range values for a given key.
     *
     * @param string $key request param like 'createdtime'
     *
     * @return array
     */
    public function getDateRange($key)
    {
        return $this->getByType($key, 'DateRangeUserFormat');
    }

    /**
     * Function to get html the value for a given key.
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return mixed
     */
    public function getForHtml($key, $value = '')
    {
        if (isset($this->purifiedValuesByHtml[$key])) {
            return $this->purifiedValuesByHtml[$key];
        }
        if (isset($this->rawValues[$key])) {
            $value = $this->rawValues[$key];
        }
        if ($value) {
            $value = \App\Purifier::purifyHtml($value);
        }

        return $this->purifiedValuesByHtml[$key] = $value;
    }

    /**
     * Function to get the value if its safe to use for SQL Query (column).
     *
     * @param string $key
     * @param bool   $skipEmtpy
     *
     * @return string
     */
    public function getForSql($key, $skipEmtpy = true)
    {
        return Purifier::purifySql($this->get($key), $skipEmtpy);
    }

    /**
     * Function to get the request mode.
     *
     * @return string
     */
    public function getMode()
    {
        return '' !== $this->getRaw('mode') ? $this->getByType('mode', 'Alnum') : '';
    }

    /**
     * Get all data.
     *
     * @return array
     */
    public function getAll()
    {
        foreach ($this->rawValues as $key => $value) {
            $this->get($key);
        }

        return $this->purifiedValuesByGet;
    }

    /**
     * Get all raw data.
     *
     * @return array
     */
    public function getAllRaw()
    {
        return $this->rawValues;
    }

    /**
     * Get raw value.
     *
     * @param string $key
     * @param mixed  $defaultValue
     *
     * @return mixed
     */
    public function getRaw($key, $defaultValue = '')
    {
        if (isset($this->rawValues[$key])) {
            return $this->rawValues[$key];
        }

        return $defaultValue;
    }

    /**
     * Get all headers.
     *
     * @return string[]
     */
    public function getHeaders()
    {
        if (isset($this->headers)) {
            return $this->headers;
        }
        $data = array_change_key_case(getallheaders(), CASE_LOWER);
        foreach ($data as $key => &$value) {
            if ('' !== $value) {
                $value = isset($this->headersPurifierMap[$key]) ? Purifier::purifyByType($value, $this->headersPurifierMap[$key]) : Purifier::purify($value);
            }
        }
        return $this->headers = $data;
    }

    /**
     * Get header for a given key.
     *
     * @param string $key
     *
     * @return string
     */
    public function getHeader($key)
    {
        if (!isset($this->headers)) {
            $this->getHeaders();
        }
        return $this->headers[$key] ?? null;
    }

    /**
     * Get request method.
     *
     * @throws \App\Exceptions\AppException
     *
     * @return string
     */
    public static function getRequestMethod()
    {
        $method = $_SERVER['REQUEST_METHOD'];
        if ('POST' === $method && isset($_SERVER['HTTP_X_HTTP_METHOD'])) {
            if ('DELETE' === $_SERVER['HTTP_X_HTTP_METHOD']) {
                $method = 'DELETE';
            } elseif ('PUT' === $_SERVER['HTTP_X_HTTP_METHOD']) {
                $method = 'PUT';
            } else {
                throw new \App\Exceptions\AppException('Unexpected Header');
            }
        }
        return strtoupper($method);
    }

    /**
     * Get server and execution environment information.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return bool
     */
    public function getServer($key, $default = false)
    {
        if (!isset($_SERVER[$key])) {
            return $default;
        }
        return Purifier::purifyByType($_SERVER[$key], 'Text');
    }

    /**
     * Get module name.
     *
     * @param bool $raw
     *
     * @return string
     */
    public function getModule($raw = true)
    {
        $moduleName = $this->getByType('module', \App\Purifier::ALNUM);
        if (!$raw && !$this->isEmpty('parent', true) && 'Settings' === ($parentModule = $this->getByType('parent', \App\Purifier::ALNUM))) {
            $moduleName = "$parentModule:$moduleName";
        }
        return $moduleName;
    }

    /**
     * Check for existence of key.
     *
     * @param string $key
     *
     * @return bool
     */
    public function has($key)
    {
        return isset($this->rawValues[$key]);
    }

    /**
     * Function to check if the key is empty.
     *
     * @param string $key
     * @param bool   $emptyFunction
     *
     * @return bool
     */
    public function isEmpty($key, $emptyFunction = false)
    {
        if ($emptyFunction) {
            return empty($this->rawValues[$key]);
        }
        return !isset($this->rawValues[$key]) || '' === $this->rawValues[$key];
    }

    /**
     * Function to set the value for a given key.
     *
     * @param string $key
     * @param mixed  $value
     * @param bool   $onlyRaw
     *
     * @return $this
     */
    public function set($key, $value, bool $onlyRaw = false): self
    {
        if ($onlyRaw) {
            $this->rawValues[$key] = $value;
        } else {
            $this->rawValues[$key] = $this->purifiedValuesByGet[$key] = $this->purifiedValuesByInteger[$key] = $this->purifiedValuesByHtml[$key] = $value;
            $this->purifiedValuesByType[$key] = [];
        }
        return $this;
    }

    /**
     * Function to remove the value for a given key.
     *
     * @param string $key
     */
    public function delete($key)
    {
        if (isset($this->purifiedValuesByGet[$key])) {
            unset($this->purifiedValuesByGet[$key]);
        }
        if (isset($this->purifiedValuesByInteger[$key])) {
            unset($this->purifiedValuesByInteger[$key]);
        }
        if (isset($this->purifiedValuesByType[$key])) {
            unset($this->purifiedValuesByType[$key]);
        }
        if (isset($this->purifiedValuesByHtml[$key])) {
            unset($this->purifiedValuesByHtml[$key]);
        }
        if (isset($this->purifiedValuesByArray[$key])) {
            unset($this->purifiedValuesByArray[$key]);
        }
        if (isset($this->purifiedValuesByDateRange[$key])) {
            unset($this->purifiedValuesByDateRange[$key]);
        }
        if (isset($this->purifiedValuesByExploded[$key])) {
            unset($this->purifiedValuesByExploded[$key]);
        }
        if (isset($this->purifiedValuesByMultiDimension[$key])) {
            unset($this->purifiedValuesByMultiDimension[$key]);
        }
        if (isset($this->rawValues[$key])) {
            unset($this->rawValues[$key]);
        }
    }

    /**
     * Get all request keys.
     *
     * @return array
     */
    public function getKeys()
    {
        return array_keys($this->rawValues);
    }

    /**
     * Function to check if the ajax request.
     *
     * @return bool
     */
    public function isAjax()
    {
        if (!empty($_SERVER['HTTP_X_PJAX']) && true === $_SERVER['HTTP_X_PJAX']) {
            return true;
        }
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) {
            return true;
        }
        return false;
    }

    /**
     * Is json.
     *
     * @return bool
     */
    public function isJSON()
    {
        return false !== strpos($this->getHeader('accept'), 'application/json');
    }

    /**
     * Validating read access request.
     *
     * @throws \App\Exceptions\Csrf
     */
    public function validateReadAccess()
    {
        // Referer check if present - to over come && Check for user post authentication.
        if (\Config\Security::$verifyRefererHeader && isset($_SERVER['HTTP_REFERER']) && \App\User::getCurrentUserId() && 'Install' !== $this->get('module')) {
            $allowed = array_merge(\Config\Security::$allowedFrameDomains, \Config\Security::$allowedFormDomains);
            $allowed[] = \App\Config::main('site_URL');
            $throw = true;
            foreach ($allowed as $value) {
                if (0 === stripos($_SERVER['HTTP_REFERER'], $value)) {
                    $throw = false;
                }
            }
            if ($throw) {
                throw new \App\Exceptions\Csrf('Illegal request');
            }
        }
    }

    /**
     * Validating write access request.
     *
     * @param bool $skipRequestTypeCheck
     *
     * @throws \App\Exceptions\Csrf
     */
    public function validateWriteAccess($skipRequestTypeCheck = false)
    {
        if (!$skipRequestTypeCheck && 'POST' !== $_SERVER['REQUEST_METHOD']) {
            throw new \App\Exceptions\Csrf('Invalid request - validate Write Access', 403);
        }
        $this->validateReadAccess();
        if (\App\Config::security('csrfActive')) {
            \CsrfMagic\Csrf::check();
        }
    }

    /**
     * Static instance initialization.
     *
     * @param array|bool $request
     *
     * @return Request
     */
    public static function init($request = false)
    {
        if (!static::$request) {
            static::$request = new self($request ?: $_REQUEST);
        }
        return static::$request;
    }

    /**
     * Support static methods, all functions must start with "_".
     *
     * @param string     $name
     * @param array|null $arguments
     *
     * @throws \App\Exceptions\AppException
     *
     * @return mixed
     */
    public static function __callStatic($name, $arguments = null)
    {
        if (!static::$request) {
            static::init();
        }
        $function = ltrim($name, '_');
        if (!method_exists(static::$request, $function)) {
            throw new \App\Exceptions\AppException('Method not found');
        }
        if (empty($arguments)) {
            return static::$request->{$function}();
        }
        $first = array_shift($arguments);
        if (empty($arguments)) {
            return static::$request->{$function}($first);
        }
        return static::$request->{$function}($first, $arguments[0]);
    }
}