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 3.0.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\View;
16:
17: use BadMethodCallException;
18: use Cake\Cache\Cache;
19: use Cake\Datasource\ModelAwareTrait;
20: use Cake\Event\EventDispatcherTrait;
21: use Cake\Event\EventManager;
22: use Cake\Http\Response;
23: use Cake\Http\ServerRequest;
24: use Cake\ORM\Locator\LocatorAwareTrait;
25: use Cake\Utility\Inflector;
26: use Cake\View\Exception\MissingCellViewException;
27: use Cake\View\Exception\MissingTemplateException;
28: use Error;
29: use Exception;
30: use ReflectionException;
31: use ReflectionMethod;
32:
33: /**
34: * Cell base.
35: */
36: abstract class Cell
37: {
38:
39: use EventDispatcherTrait;
40: use LocatorAwareTrait;
41: use ModelAwareTrait;
42: use ViewVarsTrait;
43:
44: /**
45: * Instance of the View created during rendering. Won't be set until after
46: * Cell::__toString() is called.
47: *
48: * @var \Cake\View\View
49: * @deprecated 3.1.0 Use createView() instead.
50: */
51: public $View;
52:
53: /**
54: * Name of the template that will be rendered.
55: * This property is inflected from the action name that was invoked.
56: *
57: * @var string
58: */
59: public $template;
60:
61: /**
62: * Automatically set to the name of a plugin.
63: *
64: * @var string
65: */
66: public $plugin;
67:
68: /**
69: * An instance of a Cake\Http\ServerRequest object that contains information about the current request.
70: * This object contains all the information about a request and several methods for reading
71: * additional information about the request.
72: *
73: * @var \Cake\Http\ServerRequest
74: */
75: public $request;
76:
77: /**
78: * An instance of a Response object that contains information about the impending response
79: *
80: * @var \Cake\Http\Response
81: */
82: public $response;
83:
84: /**
85: * The helpers this cell uses.
86: *
87: * This property is copied automatically when using the CellTrait
88: *
89: * @var array
90: */
91: public $helpers = [];
92:
93: /**
94: * The cell's action to invoke.
95: *
96: * @var string
97: */
98: public $action;
99:
100: /**
101: * Arguments to pass to cell's action.
102: *
103: * @var array
104: */
105: public $args = [];
106:
107: /**
108: * These properties can be set directly on Cell and passed to the View as options.
109: *
110: * @var array
111: * @see \Cake\View\View
112: */
113: protected $_validViewOptions = [
114: 'viewPath'
115: ];
116:
117: /**
118: * List of valid options (constructor's fourth arguments)
119: * Override this property in subclasses to whitelist
120: * which options you want set as properties in your Cell.
121: *
122: * @var array
123: */
124: protected $_validCellOptions = [];
125:
126: /**
127: * Caching setup.
128: *
129: * @var array|bool
130: */
131: protected $_cache = false;
132:
133: /**
134: * Constructor.
135: *
136: * @param \Cake\Http\ServerRequest|null $request The request to use in the cell.
137: * @param \Cake\Http\Response|null $response The response to use in the cell.
138: * @param \Cake\Event\EventManager|null $eventManager The eventManager to bind events to.
139: * @param array $cellOptions Cell options to apply.
140: */
141: public function __construct(
142: ServerRequest $request = null,
143: Response $response = null,
144: EventManager $eventManager = null,
145: array $cellOptions = []
146: ) {
147: if ($eventManager !== null) {
148: $this->setEventManager($eventManager);
149: }
150: $this->request = $request;
151: $this->response = $response;
152: $this->modelFactory('Table', [$this->getTableLocator(), 'get']);
153:
154: $this->_validCellOptions = array_merge(['action', 'args'], $this->_validCellOptions);
155: foreach ($this->_validCellOptions as $var) {
156: if (isset($cellOptions[$var])) {
157: $this->{$var} = $cellOptions[$var];
158: }
159: }
160: if (!empty($cellOptions['cache'])) {
161: $this->_cache = $cellOptions['cache'];
162: }
163:
164: $this->initialize();
165: }
166:
167: /**
168: * Initialization hook method.
169: *
170: * Implement this method to avoid having to overwrite
171: * the constructor and calling parent::__construct().
172: *
173: * @return void
174: */
175: public function initialize()
176: {
177: }
178:
179: /**
180: * Render the cell.
181: *
182: * @param string|null $template Custom template name to render. If not provided (null), the last
183: * value will be used. This value is automatically set by `CellTrait::cell()`.
184: * @return string The rendered cell.
185: * @throws \Cake\View\Exception\MissingCellViewException When a MissingTemplateException is raised during rendering.
186: */
187: public function render($template = null)
188: {
189: $cache = [];
190: if ($this->_cache) {
191: $cache = $this->_cacheConfig($this->action, $template);
192: }
193:
194: $render = function () use ($template) {
195: try {
196: $reflect = new ReflectionMethod($this, $this->action);
197: $reflect->invokeArgs($this, $this->args);
198: } catch (ReflectionException $e) {
199: throw new BadMethodCallException(sprintf(
200: 'Class %s does not have a "%s" method.',
201: get_class($this),
202: $this->action
203: ));
204: }
205:
206: $builder = $this->viewBuilder();
207:
208: if ($template !== null &&
209: strpos($template, '/') === false &&
210: strpos($template, '.') === false
211: ) {
212: $template = Inflector::underscore($template);
213: }
214: if ($template === null) {
215: $template = $builder->getTemplate() ?: $this->template;
216: }
217: $builder->setLayout(false)
218: ->setTemplate($template);
219:
220: $className = get_class($this);
221: $namePrefix = '\View\Cell\\';
222: $name = substr($className, strpos($className, $namePrefix) + strlen($namePrefix));
223: $name = substr($name, 0, -4);
224: if (!$builder->getTemplatePath()) {
225: $builder->setTemplatePath('Cell' . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $name));
226: }
227:
228: $this->View = $this->createView();
229: try {
230: return $this->View->render($template);
231: } catch (MissingTemplateException $e) {
232: throw new MissingCellViewException(['file' => $template, 'name' => $name], null, $e);
233: }
234: };
235:
236: if ($cache) {
237: return Cache::remember($cache['key'], $render, $cache['config']);
238: }
239:
240: return $render();
241: }
242:
243: /**
244: * Generate the cache key to use for this cell.
245: *
246: * If the key is undefined, the cell class and action name will be used.
247: *
248: * @param string $action The action invoked.
249: * @param string|null $template The name of the template to be rendered.
250: * @return array The cache configuration.
251: */
252: protected function _cacheConfig($action, $template = null)
253: {
254: if (empty($this->_cache)) {
255: return [];
256: }
257: $template = $template ?: 'default';
258: $key = 'cell_' . Inflector::underscore(get_class($this)) . '_' . $action . '_' . $template;
259: $key = str_replace('\\', '_', $key);
260: $default = [
261: 'config' => 'default',
262: 'key' => $key
263: ];
264: if ($this->_cache === true) {
265: return $default;
266: }
267:
268: return $this->_cache + $default;
269: }
270:
271: /**
272: * Magic method.
273: *
274: * Starts the rendering process when Cell is echoed.
275: *
276: * *Note* This method will trigger an error when view rendering has a problem.
277: * This is because PHP will not allow a __toString() method to throw an exception.
278: *
279: * @return string Rendered cell
280: * @throws \Error Include error details for PHP 7 fatal errors.
281: */
282: public function __toString()
283: {
284: try {
285: return $this->render();
286: } catch (Exception $e) {
287: trigger_error(sprintf('Could not render cell - %s [%s, line %d]', $e->getMessage(), $e->getFile(), $e->getLine()), E_USER_WARNING);
288:
289: return '';
290: } catch (Error $e) {
291: throw new Error(sprintf('Could not render cell - %s [%s, line %d]', $e->getMessage(), $e->getFile(), $e->getLine()));
292: }
293: }
294:
295: /**
296: * Debug info.
297: *
298: * @return array
299: */
300: public function __debugInfo()
301: {
302: return [
303: 'plugin' => $this->plugin,
304: 'action' => $this->action,
305: 'args' => $this->args,
306: 'template' => $this->template,
307: 'request' => $this->request,
308: 'response' => $this->response,
309: ];
310: }
311: }
312: