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