1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Core\Http;
17
18/**
19 * Standard values for a JSON response
20 *
21 * Highly inspired by ZF zend-diactoros
22 *
23 * @internal Note that this is not public API yet.
24 */
25class JsonResponse extends Response
26{
27    /**
28     * Default flags for json_encode; value of:
29     *
30     * <code>
31     * JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES
32     * </code>
33     *
34     * @var int
35     */
36    const DEFAULT_JSON_FLAGS = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES;
37
38    /**
39     * Create a JSON response with the given data.
40     *
41     * Default JSON encoding is performed with the following options, which
42     * produces RFC4627-compliant JSON, capable of embedding into HTML.
43     *
44     * - JSON_HEX_TAG
45     * - JSON_HEX_APOS
46     * - JSON_HEX_AMP
47     * - JSON_HEX_QUOT
48     * - JSON_UNESCAPED_SLASHES
49     *
50     * @param mixed $data Data to convert to JSON.
51     * @param int $status Integer status code for the response; 200 by default.
52     * @param array $headers Array of headers to use at initialization.
53     * @param int $encodingOptions JSON encoding options to use.
54     */
55    public function __construct(
56        $data = [],
57        $status = 200,
58        array $headers = [],
59        $encodingOptions = self::DEFAULT_JSON_FLAGS
60    ) {
61        $body = new Stream('php://temp', 'wb+');
62        parent::__construct($body, $status, $headers);
63
64        if (!empty($data)) {
65            $this->setPayload($data, $encodingOptions);
66        }
67
68        // Ensure that application/json header is set, if Content-Type was not set before
69        if (!$this->hasHeader('Content-Type')) {
70            $this->headers['Content-Type'][] = 'application/json; charset=utf-8';
71            $this->lowercasedHeaderNames['content-type'] = 'Content-Type';
72        }
73    }
74
75    /**
76     * Overrides the exiting content, takes an array as input
77     *
78     * @param array $data
79     * @param int $encodingOptions
80     * @return $this
81     */
82    public function setPayload(array $data = [], $encodingOptions = self::DEFAULT_JSON_FLAGS): JsonResponse
83    {
84        $this->body->write($this->jsonEncode($data, $encodingOptions));
85        $this->body->rewind();
86        return $this;
87    }
88
89    /**
90     * Encode the provided data to JSON.
91     *
92     * @param mixed $data
93     * @param int $encodingOptions
94     * @return string
95     * @throws \InvalidArgumentException if unable to encode the $data to JSON.
96     */
97    private function jsonEncode($data, $encodingOptions)
98    {
99        if (is_resource($data)) {
100            throw new \InvalidArgumentException('Cannot JSON encode resources', 1504972433);
101        }
102        // Clear json_last_error()
103        json_encode(null);
104        $json = json_encode($data, $encodingOptions);
105        if (JSON_ERROR_NONE !== json_last_error()) {
106            throw new \InvalidArgumentException(sprintf(
107                'Unable to encode data to JSON in %s: %s',
108                __CLASS__,
109                json_last_error_msg()
110            ), 1504972434);
111        }
112        return $json;
113    }
114}
115