1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 2.1.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\View;
16:
17: use Cake\Core\Configure;
18:
19: /**
20: * A view class that is used for JSON responses.
21: *
22: * It allows you to omit templates if you just need to emit JSON string as response.
23: *
24: * In your controller, you could do the following:
25: *
26: * ```
27: * $this->set(['posts' => $posts]);
28: * $this->set('_serialize', true);
29: * ```
30: *
31: * When the view is rendered, the `$posts` view variable will be serialized
32: * into JSON.
33: *
34: * You can also set multiple view variables for serialization. This will create
35: * a top level object containing all the named view variables:
36: *
37: * ```
38: * $this->set(compact('posts', 'users', 'stuff'));
39: * $this->set('_serialize', true);
40: * ```
41: *
42: * The above would generate a JSON object that looks like:
43: *
44: * `{"posts": [...], "users": [...]}`
45: *
46: * You can also set `'_serialize'` to a string or array to serialize only the
47: * specified view variables.
48: *
49: * If you don't use the `_serialize`, you will need a view template. You can use
50: * extended views to provide layout-like functionality.
51: *
52: * You can also enable JSONP support by setting parameter `_jsonp` to true or a
53: * string to specify custom query string parameter name which will contain the
54: * callback function name.
55: */
56: class JsonView extends SerializedView
57: {
58: /**
59: * JSON layouts are located in the json sub directory of `Layouts/`
60: *
61: * @var string
62: */
63: protected $layoutPath = 'json';
64:
65: /**
66: * JSON views are located in the 'json' sub directory for controllers' views.
67: *
68: * @var string
69: */
70: protected $subDir = 'json';
71:
72: /**
73: * Response type.
74: *
75: * @var string
76: */
77: protected $_responseType = 'json';
78:
79: /**
80: * List of special view vars.
81: *
82: * @var array
83: */
84: protected $_specialVars = ['_serialize', '_jsonOptions', '_jsonp'];
85:
86: /**
87: * Render a JSON view.
88: *
89: * ### Special parameters
90: * `_serialize` To convert a set of view variables into a JSON response.
91: * Its value can be a string for single variable name or array for multiple
92: * names. If true all view variables will be serialized. It unset normal
93: * view template will be rendered.
94: * `_jsonp` Enables JSONP support and wraps response in callback function
95: * provided in query string.
96: * - Setting it to true enables the default query string parameter "callback".
97: * - Setting it to a string value, uses the provided query string parameter
98: * for finding the JSONP callback name.
99: *
100: * @param string|null $view The view being rendered.
101: * @param string|null $layout The layout being rendered.
102: * @return string|null The rendered view.
103: */
104: public function render($view = null, $layout = null)
105: {
106: $return = parent::render($view, $layout);
107:
108: if (!empty($this->viewVars['_jsonp'])) {
109: $jsonpParam = $this->viewVars['_jsonp'];
110: if ($this->viewVars['_jsonp'] === true) {
111: $jsonpParam = 'callback';
112: }
113: if ($this->request->getQuery($jsonpParam)) {
114: $return = sprintf('%s(%s)', h($this->request->getQuery($jsonpParam)), $return);
115: $this->response = $this->response->withType('js');
116: }
117: }
118:
119: return $return;
120: }
121:
122: /**
123: * Serialize view vars
124: *
125: * ### Special parameters
126: * `_jsonOptions` You can set custom options for json_encode() this way,
127: * e.g. `JSON_HEX_TAG | JSON_HEX_APOS`.
128: *
129: * @param array|string|bool $serialize The name(s) of the view variable(s)
130: * that need(s) to be serialized. If true all available view variables.
131: * @return string|false The serialized data, or boolean false if not serializable.
132: */
133: protected function _serialize($serialize)
134: {
135: $data = $this->_dataToSerialize($serialize);
136:
137: $jsonOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT |
138: JSON_PARTIAL_OUTPUT_ON_ERROR;
139:
140: if (isset($this->viewVars['_jsonOptions'])) {
141: if ($this->viewVars['_jsonOptions'] === false) {
142: $jsonOptions = 0;
143: } else {
144: $jsonOptions = $this->viewVars['_jsonOptions'];
145: }
146: }
147:
148: if (Configure::read('debug')) {
149: $jsonOptions |= JSON_PRETTY_PRINT;
150: }
151:
152: return json_encode($data, $jsonOptions);
153: }
154:
155: /**
156: * Returns data to be serialized.
157: *
158: * @param array|string|bool $serialize The name(s) of the view variable(s) that
159: * need(s) to be serialized. If true all available view variables will be used.
160: * @return mixed The data to serialize.
161: */
162: protected function _dataToSerialize($serialize = true)
163: {
164: if ($serialize === true) {
165: $data = array_diff_key(
166: $this->viewVars,
167: array_flip($this->_specialVars)
168: );
169:
170: if (empty($data)) {
171: return null;
172: }
173:
174: return $data;
175: }
176:
177: if (is_array($serialize)) {
178: $data = [];
179: foreach ($serialize as $alias => $key) {
180: if (is_numeric($alias)) {
181: $alias = $key;
182: }
183: if (array_key_exists($key, $this->viewVars)) {
184: $data[$alias] = $this->viewVars[$key];
185: }
186: }
187:
188: return !empty($data) ? $data : null;
189: }
190:
191: return isset($this->viewVars[$serialize]) ? $this->viewVars[$serialize] : null;
192: }
193: }
194: