<?php

/**
 * @file classes/oai/OAIUtils.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2000-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class OAI
 *
 * @ingroup oai
 *
 * @see OAIDAO
 *
 * @brief Utility functions used by OAI related classes.
 */

namespace PKP\oai;

use Transliterator;

class OAIUtils
{
    private static $TO_ASCII;
    private static $NOT_IN_URI_NON_RESERVED_CHARS = '/[^A-Za-z0-9\-_\.!~*\'()]/';

    public static function init()
    {
        self::$TO_ASCII = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
    }

    /**
     * Return a UTC-formatted datestamp from the specified UNIX timestamp.
     *
     * @param int $timestamp *nix timestamp (if not used, the current time is used)
     * @param bool $includeTime include both the time and date
     *
     * @return string UTC datestamp
     */
    public static function UTCDate($timestamp = 0, $includeTime = true)
    {
        $format = 'Y-m-d';
        if ($includeTime) {
            $format .= "\TH:i:s\Z";
        }

        if ($timestamp == 0) {
            return gmdate($format);
        } else {
            return gmdate($format, $timestamp);
        }
    }

    /**
     * Returns a UNIX timestamp from a UTC-formatted datestamp.
     * Returns the string "invalid" if datestamp is invalid,
     * or "invalid_granularity" if unsupported granularity.
     *
     * @param string $date UTC datestamp
     * @param string $requiredGranularity Datestamp granularity to require (default: not checked)
     *
     * @return int|string timestamp
     */
    public static function UTCtoTimestamp($date, $requiredGranularity = null)
    {
        // FIXME Has limited range (see http://php.net/strtotime)
        if (preg_match("/^\d\d\d\d\-\d\d\-\d\d$/", $date)) {
            // Match date
            $time = strtotime("{$date} UTC");
            return ($time != -1) ? $time : 'invalid';
        } elseif (preg_match("/^(\d\d\d\d\-\d\d\-\d\d)T(\d\d:\d\d:\d\d)Z$/", $date, $matches)) {
            // Match datetime
            // FIXME
            $date = "{$matches[1]} {$matches[2]}";
            if ($requiredGranularity && $requiredGranularity != 'YYYY-MM-DDThh:mm:ssZ') {
                return 'invalid_granularity';
            } else {
                $time = strtotime("{$date} UTC");
                return ($time != -1) ? $time : 'invalid';
            }
        } else {
            return 'invalid';
        }
    }


    /**
     * Clean input variables (by reference).
     *
     * @param mixed $data request parameter(s)
     */
    public static function prepInput(&$data)
    { // REFERENCE REQUIRED
        if (!is_array($data)) {
            $data = urldecode($data);
        } else {
            foreach ($data as $k => $v) {
                if (is_array($data[$k])) {
                    self::prepInput($data[$k]);
                } else {
                    $data[$k] = urldecode($v);
                }
            }
        }
        return $data;
    }

    /**
     * Prepare variables for output (by reference).
     * Data is assumed to be UTF-8 encoded (FIXME?)
     *
     * @param mixed $data output parameter(s)
     *
     * @return mixed cleaned output parameter(s)
     */
    public static function prepOutput(&$data)
    { // REFERENCE REQUIRED
        if (!is_array($data)) {
            $data = htmlspecialchars($data);
        } else {
            foreach ($data as $k => $v) {
                if (is_array($data[$k])) {
                    self::prepOutput($data[$k]);
                } else {
                    // FIXME FIXME FIXME
                    $data[$k] = htmlspecialchars($v);
                }
            }
        }
        return $data;
    }

    /**
     * Parses string $string into an associate array $array.
     * Acts like parse_str($string, $array) except duplicate
     * variable names in $string are converted to an array.
     *
     * @param array $array of parsed parameters
     */
    public static function parseStr($string, &$array)
    {
        $pairs = explode('&', $string);
        foreach ($pairs as $p) {
            $vars = explode('=', $p);
            if (!empty($vars[0]) && isset($vars[1])) {
                $key = $vars[0];
                $value = join('=', array_splice($vars, 1));

                if (!isset($array[$key])) {
                    $array[$key] = $value;
                } elseif (is_array($array[$key])) {
                    array_push($array[$key], $value);
                } else {
                    $array[$key] = [$array[$key], $value];
                }
            }
        }
    }

    /**
     * Valid characters in setSpec [1] are URI unreserved characters [2]
     * Create a valid string by
     *  - transliterating all non-Latin characters
     *  - stripping all diacritics
     *  - dropping any remaining invalid characters
     *
     * [1] https://www.openarchives.org/OAI/2.0/openarchivesprotocol.htm#Set
     * [2] http://www.ietf.org/rfc/rfc2396.txt
     *
     */
    public static function toValidSetSpec(string $string): string
    {
        $t = self::$TO_ASCII->transliterate($string);
        return preg_replace(self::$NOT_IN_URI_NON_RESERVED_CHARS, '', $t);
    }
}
OAIUtils::init();

if (!PKP_STRICT_MODE) {
    class_alias('\PKP\oai\OAIUtils', '\OAIUtils');
}
