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: /**
60: * JSON layouts are located in the json sub directory of `Layouts/`
61: *
62: * @var string
63: */
64: protected $layoutPath = 'json';
65:
66: /**
67: * JSON views are located in the 'json' sub directory for controllers' views.
68: *
69: * @var string
70: */
71: protected $subDir = 'json';
72:
73: /**
74: * Response type.
75: *
76: * @var string
77: */
78: protected $_responseType = 'json';
79:
80: /**
81: * List of special view vars.
82: *
83: * @var array
84: */
85: protected $_specialVars = ['_serialize', '_jsonOptions', '_jsonp'];
86:
87: /**
88: * Render a JSON view.
89: *
90: * ### Special parameters
91: * `_serialize` To convert a set of view variables into a JSON response.
92: * Its value can be a string for single variable name or array for multiple
93: * names. If true all view variables will be serialized. It unset normal
94: * view template will be rendered.
95: * `_jsonp` Enables JSONP support and wraps response in callback function
96: * provided in query string.
97: * - Setting it to true enables the default query string parameter "callback".
98: * - Setting it to a string value, uses the provided query string parameter
99: * for finding the JSONP callback name.
100: *
101: * @param string|null $view The view being rendered.
102: * @param string|null $layout The layout being rendered.
103: * @return string|null The rendered view.
104: */
105: public function render($view = null, $layout = null)
106: {
107: $return = parent::render($view, $layout);
108:
109: if (!empty($this->viewVars['_jsonp'])) {
110: $jsonpParam = $this->viewVars['_jsonp'];
111: if ($this->viewVars['_jsonp'] === true) {
112: $jsonpParam = 'callback';
113: }
114: if ($this->request->getQuery($jsonpParam)) {
115: $return = sprintf('%s(%s)', h($this->request->getQuery($jsonpParam)), $return);
116: $this->response = $this->response->withType('js');
117: }
118: }
119:
120: return $return;
121: }
122:
123: /**
124: * Serialize view vars
125: *
126: * ### Special parameters
127: * `_jsonOptions` You can set custom options for json_encode() this way,
128: * e.g. `JSON_HEX_TAG | JSON_HEX_APOS`.
129: *
130: * @param array|string|bool $serialize The name(s) of the view variable(s)
131: * that need(s) to be serialized. If true all available view variables.
132: * @return string|false The serialized data, or boolean false if not serializable.
133: */
134: protected function _serialize($serialize)
135: {
136: $data = $this->_dataToSerialize($serialize);
137:
138: $jsonOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT |
139: JSON_PARTIAL_OUTPUT_ON_ERROR;
140:
141: if (isset($this->viewVars['_jsonOptions'])) {
142: if ($this->viewVars['_jsonOptions'] === false) {
143: $jsonOptions = 0;
144: } else {
145: $jsonOptions = $this->viewVars['_jsonOptions'];
146: }
147: }
148:
149: if (Configure::read('debug')) {
150: $jsonOptions |= JSON_PRETTY_PRINT;
151: }
152:
153: return json_encode($data, $jsonOptions);
154: }
155:
156: /**
157: * Returns data to be serialized.
158: *
159: * @param array|string|bool $serialize The name(s) of the view variable(s) that
160: * need(s) to be serialized. If true all available view variables will be used.
161: * @return mixed The data to serialize.
162: */
163: protected function _dataToSerialize($serialize = true)
164: {
165: if ($serialize === true) {
166: $data = array_diff_key(
167: $this->viewVars,
168: array_flip($this->_specialVars)
169: );
170:
171: if (empty($data)) {
172: return null;
173: }
174:
175: return $data;
176: }
177:
178: if (is_array($serialize)) {
179: $data = [];
180: foreach ($serialize as $alias => $key) {
181: if (is_numeric($alias)) {
182: $alias = $key;
183: }
184: if (array_key_exists($key, $this->viewVars)) {
185: $data[$alias] = $this->viewVars[$key];
186: }
187: }
188:
189: return !empty($data) ? $data : null;
190: }
191:
192: return isset($this->viewVars[$serialize]) ? $this->viewVars[$serialize] : null;
193: }
194: }
195: