<?php
/**
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link          http://cakephp.org CakePHP(tm) Project
 * @since         3.3.0
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Http;

use Cake\Network\Request as CakeRequest;
use Cake\Utility\Hash;
use Psr\Http\Message\ServerRequestInterface as PsrRequest;

/**
 * Translate and transform from PSR7 requests into CakePHP requests.
 *
 * This is an important step for maintaining backwards compatibility
 * with existing CakePHP applications, which depend on the CakePHP request object.
 *
 * There is no reverse transform as the 'application' cannot return a mutated
 * request object.
 *
 * @internal
 */
class RequestTransformer
{
    /**
     * Transform a PSR7 request into a CakePHP one.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request The PSR7 request.
     * @return \Cake\Network\Request The transformed request.
     */
    public static function toCake(PsrRequest $request)
    {
        $post = $request->getParsedBody();
        $server = $request->getServerParams();

        $files = static::getFiles($request);
        if (!empty($files)) {
            $post = Hash::merge($post, $files);
        }

        $input = $request->getBody()->getContents();
        $input = $input === '' ? null : $input;

        return new CakeRequest([
            'query' => $request->getQueryParams(),
            'post' => $post,
            'cookies' => $request->getCookieParams(),
            'environment' => $server,
            'params' => static::getParams($request),
            'url' => $request->getUri()->getPath(),
            'base' => $request->getAttribute('base', ''),
            'webroot' => $request->getAttribute('webroot', '/'),
            'session' => $request->getAttribute('session', null),
            'input' => $input,
        ]);
    }

    /**
     * Extract the routing parameters out of the request object.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request The request to extract params from.
     * @return array The routing parameters.
     */
    protected static function getParams(PsrRequest $request)
    {
        $params = (array)$request->getAttribute('params', []);
        $params += [
            'plugin' => null,
            'controller' => null,
            'action' => null,
            '_ext' => null,
            'pass' => []
        ];

        return $params;
    }

    /**
     * Extract the uploaded files out of the request object.
     *
     * CakePHP expects to get arrays of file information and
     * not the parsed objects that PSR7 requests contain. Downsample the data here.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request The request to extract files from.
     * @return array The routing parameters.
     */
    protected static function getFiles($request)
    {
        return static::convertFiles([], $request->getUploadedFiles());
    }

    /**
     * Convert a nested array of files to arrays.
     *
     * @param array $data The data to add files to.
     * @param array $files The file objects to convert.
     * @param string $path The current array path.
     * @return array Converted file data
     */
    protected static function convertFiles($data, $files, $path = '')
    {
        foreach ($files as $key => $file) {
            $newPath = $path;
            if ($newPath === '') {
                $newPath = $key;
            }
            if ($newPath !== $key) {
                $newPath .= '.' . $key;
            }

            if (is_array($file)) {
                $data = static::convertFiles($data, $file, $newPath);
            } else {
                $data = Hash::insert($data, $newPath, static::convertFile($file));
            }
        }

        return $data;
    }

    /**
     * Convert a single file back into an array.
     *
     * @param \Psr\Http\Message\UploadedFileInterface $file The file to convert.
     * @return array
     */
    protected static function convertFile($file)
    {
        $error = $file->getError();
        $tmpName = '';
        if ($error === UPLOAD_ERR_OK) {
            $tmpName = $file->getStream()->getMetadata('uri');
        }

        return [
            'name' => $file->getClientFilename(),
            'type' => $file->getClientMediaType(),
            'tmp_name' => $tmpName,
            'error' => $error,
            'size' => $file->getSize(),
        ];
    }
}
