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 0.2.9
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\View;
16:
17: use Cake\Core\InstanceConfigTrait;
18: use Cake\Event\EventListenerInterface;
19:
20: /**
21: * Abstract base class for all other Helpers in CakePHP.
22: * Provides common methods and features.
23: *
24: * ### Callback methods
25: *
26: * Helpers support a number of callback methods. These callbacks allow you to hook into
27: * the various view lifecycle events and either modify existing view content or perform
28: * other application specific logic. The events are not implemented by this base class, as
29: * implementing a callback method subscribes a helper to the related event. The callback methods
30: * are as follows:
31: *
32: * - `beforeRender(Event $event, $viewFile)` - beforeRender is called before the view file is rendered.
33: * - `afterRender(Event $event, $viewFile)` - afterRender is called after the view file is rendered
34: * but before the layout has been rendered.
35: * - beforeLayout(Event $event, $layoutFile)` - beforeLayout is called before the layout is rendered.
36: * - `afterLayout(Event $event, $layoutFile)` - afterLayout is called after the layout has rendered.
37: * - `beforeRenderFile(Event $event, $viewFile)` - Called before any view fragment is rendered.
38: * - `afterRenderFile(Event $event, $viewFile, $content)` - Called after any view fragment is rendered.
39: * If a listener returns a non-null value, the output of the rendered file will be set to that.
40: */
41: class Helper implements EventListenerInterface
42: {
43: use InstanceConfigTrait;
44:
45: /**
46: * List of helpers used by this helper
47: *
48: * @var array
49: */
50: protected $helpers = [];
51:
52: /**
53: * Default config for this helper.
54: *
55: * @var array
56: */
57: protected $_defaultConfig = [];
58:
59: /**
60: * A helper lookup table used to lazy load helper objects.
61: *
62: * @var array
63: */
64: protected $_helperMap = [];
65:
66: /**
67: * Unused.
68: *
69: * @var array
70: * @deprecated 3.7.0 This property is unused and will be removed in 4.0.0.
71: */
72: public $fieldset = [];
73:
74: /**
75: * Unused.
76: *
77: * @var array
78: * @deprecated 3.7.0 This property is unused and will be removed in 4.0.0.
79: */
80: public $tags = [];
81:
82: /**
83: * The View instance this helper is attached to
84: *
85: * @var \Cake\View\View
86: */
87: protected $_View;
88:
89: /**
90: * Default Constructor
91: *
92: * @param \Cake\View\View $View The View this helper is being attached to.
93: * @param array $config Configuration settings for the helper.
94: */
95: public function __construct(View $View, array $config = [])
96: {
97: $this->_View = $View;
98: $this->setConfig($config);
99:
100: if (!empty($this->helpers)) {
101: $this->_helperMap = $View->helpers()->normalizeArray($this->helpers);
102: }
103:
104: $this->initialize($config);
105: }
106:
107: /**
108: * Provide non fatal errors on missing method calls.
109: *
110: * @param string $method Method to invoke
111: * @param array $params Array of params for the method.
112: * @return mixed|void
113: */
114: public function __call($method, $params)
115: {
116: trigger_error(sprintf('Method %1$s::%2$s does not exist', get_class($this), $method), E_USER_WARNING);
117: }
118:
119: /**
120: * Lazy loads helpers.
121: *
122: * @param string $name Name of the property being accessed.
123: * @return \Cake\View\Helper|null Helper instance if helper with provided name exists
124: */
125: public function __get($name)
126: {
127: if (isset($this->_helperMap[$name]) && !isset($this->{$name})) {
128: $config = ['enabled' => false] + (array)$this->_helperMap[$name]['config'];
129: $this->{$name} = $this->_View->loadHelper($this->_helperMap[$name]['class'], $config);
130:
131: return $this->{$name};
132: }
133:
134: $removed = [
135: 'theme' => 'getTheme',
136: 'plugin' => 'getPlugin',
137: ];
138: if (isset($removed[$name])) {
139: $method = $removed[$name];
140: deprecationWarning(sprintf(
141: 'Helper::$%s is deprecated. Use $view->%s() instead.',
142: $name,
143: $method
144: ));
145:
146: return $this->_View->{$method}();
147: }
148:
149: if ($name === 'request') {
150: deprecationWarning(
151: 'Helper::$request is deprecated. Use $helper->getView()->getRequest() instead.'
152: );
153:
154: return $this->_View->getRequest();
155: }
156:
157: if ($name === 'helpers') {
158: deprecationWarning(
159: 'Helper::$helpers is now protected and should not be accessed from outside a helper class.'
160: );
161:
162: return $this->helpers;
163: }
164: }
165:
166: /**
167: * Magic setter for removed properties.
168: *
169: * @param string $name Property name.
170: * @param mixed $value Value to set.
171: * @return void
172: */
173: public function __set($name, $value)
174: {
175: $removed = [
176: 'theme' => 'setTheme',
177: 'plugin' => 'setPlugin',
178: ];
179: if (isset($removed[$name])) {
180: $method = $removed[$name];
181: deprecationWarning(sprintf(
182: 'Helper::$%s is deprecated. Use $view->%s() instead.',
183: $name,
184: $method
185: ));
186: $this->_View->{$method}($value);
187:
188: return;
189: }
190:
191: if ($name === 'request') {
192: deprecationWarning(
193: 'Helper::$request is deprecated. Use $helper->getView()->setRequest() instead.'
194: );
195:
196: $this->_View->setRequest($value);
197:
198: return;
199: }
200:
201: if ($name === 'helpers') {
202: deprecationWarning(
203: 'Helper::$helpers is now protected and should not be accessed from outside a helper class.'
204: );
205: }
206:
207: $this->{$name} = $value;
208: }
209:
210: /**
211: * Get the view instance this helper is bound to.
212: *
213: * @return \Cake\View\View The bound view instance.
214: */
215: public function getView()
216: {
217: return $this->_View;
218: }
219:
220: /**
221: * Returns a string to be used as onclick handler for confirm dialogs.
222: *
223: * @param string $message Message to be displayed
224: * @param string $okCode Code to be executed after user chose 'OK'
225: * @param string $cancelCode Code to be executed after user chose 'Cancel'
226: * @param array $options Array of options
227: * @return string onclick JS code
228: */
229: protected function _confirm($message, $okCode, $cancelCode = '', $options = [])
230: {
231: $message = $this->_cleanConfirmMessage($message);
232: $confirm = "if (confirm({$message})) { {$okCode} } {$cancelCode}";
233: // We cannot change the key here in 3.x, but the behavior is inverted in this case
234: $escape = isset($options['escape']) && $options['escape'] === false;
235: if ($escape) {
236: /** @var string $confirm */
237: $confirm = h($confirm);
238: }
239:
240: return $confirm;
241: }
242:
243: /**
244: * Returns a string read to be used in confirm()
245: *
246: * @param string $message The message to clean
247: * @return mixed
248: */
249: protected function _cleanConfirmMessage($message)
250: {
251: return str_replace('\\\n', '\n', json_encode($message));
252: }
253:
254: /**
255: * Adds the given class to the element options
256: *
257: * @param array $options Array options/attributes to add a class to
258: * @param string|null $class The class name being added.
259: * @param string $key the key to use for class. Defaults to `'class'`.
260: * @return array Array of options with $key set.
261: */
262: public function addClass(array $options = [], $class = null, $key = 'class')
263: {
264: if (isset($options[$key]) && is_array($options[$key])) {
265: $options[$key][] = $class;
266: } elseif (isset($options[$key]) && trim($options[$key])) {
267: $options[$key] .= ' ' . $class;
268: } else {
269: $options[$key] = $class;
270: }
271:
272: return $options;
273: }
274:
275: /**
276: * Get the View callbacks this helper is interested in.
277: *
278: * By defining one of the callback methods a helper is assumed
279: * to be interested in the related event.
280: *
281: * Override this method if you need to add non-conventional event listeners.
282: * Or if you want helpers to listen to non-standard events.
283: *
284: * @return array
285: */
286: public function implementedEvents()
287: {
288: $eventMap = [
289: 'View.beforeRenderFile' => 'beforeRenderFile',
290: 'View.afterRenderFile' => 'afterRenderFile',
291: 'View.beforeRender' => 'beforeRender',
292: 'View.afterRender' => 'afterRender',
293: 'View.beforeLayout' => 'beforeLayout',
294: 'View.afterLayout' => 'afterLayout'
295: ];
296: $events = [];
297: foreach ($eventMap as $event => $method) {
298: if (method_exists($this, $method)) {
299: $events[$event] = $method;
300: }
301: }
302:
303: return $events;
304: }
305:
306: /**
307: * Constructor hook method.
308: *
309: * Implement this method to avoid having to overwrite the constructor and call parent.
310: *
311: * @param array $config The configuration settings provided to this helper.
312: * @return void
313: */
314: public function initialize(array $config)
315: {
316: }
317:
318: /**
319: * Returns an array that can be used to describe the internal state of this
320: * object.
321: *
322: * @return array
323: */
324: public function __debugInfo()
325: {
326: return [
327: 'helpers' => $this->helpers,
328: 'implementedEvents' => $this->implementedEvents(),
329: '_config' => $this->getConfig(),
330: ];
331: }
332: }
333: