<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.2.9
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\View;
use Cake\Core\InstanceConfigTrait;
use Cake\Event\EventListenerInterface;
/**
* Abstract base class for all other Helpers in CakePHP.
* Provides common methods and features.
*
* ### Callback methods
*
* Helpers support a number of callback methods. These callbacks allow you to hook into
* the various view lifecycle events and either modify existing view content or perform
* other application specific logic. The events are not implemented by this base class, as
* implementing a callback method subscribes a helper to the related event. The callback methods
* are as follows:
*
* - `beforeRender(Event $event, $viewFile)` - beforeRender is called before the view file is rendered.
* - `afterRender(Event $event, $viewFile)` - afterRender is called after the view file is rendered
* but before the layout has been rendered.
* - beforeLayout(Event $event, $layoutFile)` - beforeLayout is called before the layout is rendered.
* - `afterLayout(Event $event, $layoutFile)` - afterLayout is called after the layout has rendered.
* - `beforeRenderFile(Event $event, $viewFile)` - Called before any view fragment is rendered.
* - `afterRenderFile(Event $event, $viewFile, $content)` - Called after any view fragment is rendered.
* If a listener returns a non-null value, the output of the rendered file will be set to that.
*/
class Helper implements EventListenerInterface
{
use InstanceConfigTrait;
/**
* List of helpers used by this helper
*
* @var array
*/
protected $helpers = [];
/**
* Default config for this helper.
*
* @var array
*/
protected $_defaultConfig = [];
/**
* A helper lookup table used to lazy load helper objects.
*
* @var array
*/
protected $_helperMap = [];
/**
* Unused.
*
* @var array
* @deprecated 3.7.0 This property is unused and will be removed in 4.0.0.
*/
public $fieldset = [];
/**
* Unused.
*
* @var array
* @deprecated 3.7.0 This property is unused and will be removed in 4.0.0.
*/
public $tags = [];
/**
* The View instance this helper is attached to
*
* @var \Cake\View\View
*/
protected $_View;
/**
* Default Constructor
*
* @param \Cake\View\View $View The View this helper is being attached to.
* @param array $config Configuration settings for the helper.
*/
public function __construct(View $View, array $config = [])
{
$this->_View = $View;
$this->setConfig($config);
if (!empty($this->helpers)) {
$this->_helperMap = $View->helpers()->normalizeArray($this->helpers);
}
$this->initialize($config);
}
/**
* Provide non fatal errors on missing method calls.
*
* @param string $method Method to invoke
* @param array $params Array of params for the method.
* @return mixed|void
*/
public function __call($method, $params)
{
trigger_error(sprintf('Method %1$s::%2$s does not exist', get_class($this), $method), E_USER_WARNING);
}
/**
* Lazy loads helpers.
*
* @param string $name Name of the property being accessed.
* @return \Cake\View\Helper|null Helper instance if helper with provided name exists
*/
public function __get($name)
{
if (isset($this->_helperMap[$name]) && !isset($this->{$name})) {
$config = ['enabled' => false] + (array)$this->_helperMap[$name]['config'];
$this->{$name} = $this->_View->loadHelper($this->_helperMap[$name]['class'], $config);
return $this->{$name};
}
$removed = [
'theme' => 'getTheme',
'plugin' => 'getPlugin',
];
if (isset($removed[$name])) {
$method = $removed[$name];
deprecationWarning(sprintf(
'Helper::$%s is deprecated. Use $view->%s() instead.',
$name,
$method
));
return $this->_View->{$method}();
}
if ($name === 'request') {
deprecationWarning(
'Helper::$request is deprecated. Use $helper->getView()->getRequest() instead.'
);
return $this->_View->getRequest();
}
if ($name === 'helpers') {
deprecationWarning(
'Helper::$helpers is now protected and should not be accessed from outside a helper class.'
);
return $this->helpers;
}
}
/**
* Magic setter for removed properties.
*
* @param string $name Property name.
* @param mixed $value Value to set.
* @return void
*/
public function __set($name, $value)
{
$removed = [
'theme' => 'setTheme',
'plugin' => 'setPlugin',
];
if (isset($removed[$name])) {
$method = $removed[$name];
deprecationWarning(sprintf(
'Helper::$%s is deprecated. Use $view->%s() instead.',
$name,
$method
));
$this->_View->{$method}($value);
return;
}
if ($name === 'request') {
deprecationWarning(
'Helper::$request is deprecated. Use $helper->getView()->setRequest() instead.'
);
$this->_View->setRequest($value);
return;
}
if ($name === 'helpers') {
deprecationWarning(
'Helper::$helpers is now protected and should not be accessed from outside a helper class.'
);
}
$this->{$name} = $value;
}
/**
* Get the view instance this helper is bound to.
*
* @return \Cake\View\View The bound view instance.
*/
public function getView()
{
return $this->_View;
}
/**
* Returns a string to be used as onclick handler for confirm dialogs.
*
* @param string $message Message to be displayed
* @param string $okCode Code to be executed after user chose 'OK'
* @param string $cancelCode Code to be executed after user chose 'Cancel'
* @param array $options Array of options
* @return string onclick JS code
*/
protected function _confirm($message, $okCode, $cancelCode = '', $options = [])
{
$message = $this->_cleanConfirmMessage($message);
$confirm = "if (confirm({$message})) { {$okCode} } {$cancelCode}";
// We cannot change the key here in 3.x, but the behavior is inverted in this case
$escape = isset($options['escape']) && $options['escape'] === false;
if ($escape) {
/** @var string $confirm */
$confirm = h($confirm);
}
return $confirm;
}
/**
* Returns a string read to be used in confirm()
*
* @param string $message The message to clean
* @return mixed
*/
protected function _cleanConfirmMessage($message)
{
return str_replace('\\\n', '\n', json_encode($message));
}
/**
* Adds the given class to the element options
*
* @param array $options Array options/attributes to add a class to
* @param string|null $class The class name being added.
* @param string $key the key to use for class. Defaults to `'class'`.
* @return array Array of options with $key set.
*/
public function addClass(array $options = [], $class = null, $key = 'class')
{
if (isset($options[$key]) && is_array($options[$key])) {
$options[$key][] = $class;
} elseif (isset($options[$key]) && trim($options[$key])) {
$options[$key] .= ' ' . $class;
} else {
$options[$key] = $class;
}
return $options;
}
/**
* Get the View callbacks this helper is interested in.
*
* By defining one of the callback methods a helper is assumed
* to be interested in the related event.
*
* Override this method if you need to add non-conventional event listeners.
* Or if you want helpers to listen to non-standard events.
*
* @return array
*/
public function implementedEvents()
{
$eventMap = [
'View.beforeRenderFile' => 'beforeRenderFile',
'View.afterRenderFile' => 'afterRenderFile',
'View.beforeRender' => 'beforeRender',
'View.afterRender' => 'afterRender',
'View.beforeLayout' => 'beforeLayout',
'View.afterLayout' => 'afterLayout',
];
$events = [];
foreach ($eventMap as $event => $method) {
if (method_exists($this, $method)) {
$events[$event] = $method;
}
}
return $events;
}
/**
* Constructor hook method.
*
* Implement this method to avoid having to overwrite the constructor and call parent.
*
* @param array $config The configuration settings provided to this helper.
* @return void
*/
public function initialize(array $config)
{
}
/**
* Returns an array that can be used to describe the internal state of this
* object.
*
* @return array
*/
public function __debugInfo()
{
return [
'helpers' => $this->helpers,
'implementedEvents' => $this->implementedEvents(),
'_config' => $this->getConfig(),
];
}
}