Helper.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 0.2.9
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\View;
  16. use Cake\Core\App;
  17. use Cake\Core\Configure;
  18. use Cake\Core\InstanceConfigTrait;
  19. use Cake\Core\Plugin;
  20. use Cake\Event\EventListener;
  21. use Cake\Routing\Router;
  22. use Cake\Utility\Hash;
  23. use Cake\Utility\Inflector;
  24. /**
  25. * Abstract base class for all other Helpers in CakePHP.
  26. * Provides common methods and features.
  27. *
  28. * ## Callback methods
  29. *
  30. * Helpers support a number of callback methods. These callbacks allow you to hook into
  31. * the various view lifecycle events and either modify existing view content or perform
  32. * other application specific logic. The events are not implemented by this base class, as
  33. * implementing a callback method subscribes a helper to the related event. The callback methods
  34. * are as follows:
  35. *
  36. * - `beforeRender(Event $event, $viewFile)` - beforeRender is called before the view file is rendered.
  37. * - `afterRender(Event $event, $viewFile)` - afterRender is called after the view file is rendered
  38. * but before the layout has been rendered.
  39. * - beforeLayout(Event $event, $layoutFile)` - beforeLayout is called before the layout is rendered.
  40. * - `afterLayout(Event $event, $layoutFile)` - afterLayout is called after the layout has rendered.
  41. * - `beforeRenderFile(Event $event, $viewFile)` - Called before any view fragment is rendered.
  42. * - `afterRenderFile(Event $event, $viewFile, $content)` - Called after any view fragment is rendered.
  43. * If a listener returns a non-null value, the output of the rendered file will be set to that.
  44. *
  45. */
  46. class Helper implements EventListener {
  47. use InstanceConfigTrait;
  48. /**
  49. * List of helpers used by this helper
  50. *
  51. * @var array
  52. */
  53. public $helpers = array();
  54. /**
  55. * Default config for this helper.
  56. *
  57. * @var array
  58. */
  59. protected $_defaultConfig = [];
  60. /**
  61. * A helper lookup table used to lazy load helper objects.
  62. *
  63. * @var array
  64. */
  65. protected $_helperMap = array();
  66. /**
  67. * The current theme name if any.
  68. *
  69. * @var string
  70. */
  71. public $theme = null;
  72. /**
  73. * Request object
  74. *
  75. * @var \Cake\Network\Request
  76. */
  77. public $request = null;
  78. /**
  79. * Plugin path
  80. *
  81. * @var string
  82. */
  83. public $plugin = null;
  84. /**
  85. * Holds the fields array('field_name' => array('type' => 'string', 'length' => 100),
  86. * primaryKey and validates array('field_name')
  87. *
  88. * @var array
  89. */
  90. public $fieldset = array();
  91. /**
  92. * Holds tag templates.
  93. *
  94. * @var array
  95. */
  96. public $tags = array();
  97. /**
  98. * The View instance this helper is attached to
  99. *
  100. * @var View
  101. */
  102. protected $_View;
  103. /**
  104. * Minimized attributes
  105. *
  106. * @var array
  107. */
  108. protected $_minimizedAttributes = array(
  109. 'compact', 'checked', 'declare', 'readonly', 'disabled', 'selected',
  110. 'defer', 'ismap', 'nohref', 'noshade', 'nowrap', 'multiple', 'noresize',
  111. 'autoplay', 'controls', 'loop', 'muted', 'required', 'novalidate', 'formnovalidate'
  112. );
  113. /**
  114. * Format to attribute
  115. *
  116. * @var string
  117. */
  118. protected $_attributeFormat = '%s="%s"';
  119. /**
  120. * Format to attribute
  121. *
  122. * @var string
  123. */
  124. protected $_minimizedAttributeFormat = '%s="%s"';
  125. /**
  126. * Default Constructor
  127. *
  128. * @param View $View The View this helper is being attached to.
  129. * @param array $config Configuration settings for the helper.
  130. */
  131. public function __construct(View $View, array $config = array()) {
  132. $this->_View = $View;
  133. $this->request = $View->request;
  134. $this->config($config);
  135. if (!empty($this->helpers)) {
  136. $this->_helperMap = $View->helpers()->normalizeArray($this->helpers);
  137. }
  138. }
  139. /**
  140. * Provide non fatal errors on missing method calls.
  141. *
  142. * @param string $method Method to invoke
  143. * @param array $params Array of params for the method.
  144. * @return void
  145. */
  146. public function __call($method, $params) {
  147. trigger_error(sprintf('Method %1$s::%2$s does not exist', get_class($this), $method), E_USER_WARNING);
  148. }
  149. /**
  150. * Lazy loads helpers.
  151. *
  152. * @param string $name Name of the property being accessed.
  153. * @return mixed Helper or property found at $name
  154. * @deprecated Accessing request properties through this method is deprecated and will be removed in 3.0.
  155. */
  156. public function __get($name) {
  157. if (isset($this->_helperMap[$name]) && !isset($this->{$name})) {
  158. $this->{$name} = $this->_View->addHelper($this->_helperMap[$name]['class'], $this->_config);
  159. }
  160. if (isset($this->{$name})) {
  161. return $this->{$name};
  162. }
  163. }
  164. /**
  165. * Finds URL for specified action.
  166. *
  167. * Returns a URL pointing at the provided parameters.
  168. *
  169. * @param string|array $url Either a relative string url like `/products/view/23` or
  170. * an array of URL parameters. Using an array for URLs will allow you to leverage
  171. * the reverse routing features of CakePHP.
  172. * @param bool $full If true, the full base URL will be prepended to the result
  173. * @return string Full translated URL with base path.
  174. * @link http://book.cakephp.org/2.0/en/views/helpers.html
  175. */
  176. public function url($url = null, $full = false) {
  177. return h(Router::url($url, $full));
  178. }
  179. /**
  180. * Checks if a file exists when theme is used, if no file is found default location is returned
  181. *
  182. * @param string $file The file to create a webroot path to.
  183. * @return string Web accessible path to file.
  184. */
  185. public function webroot($file) {
  186. $asset = explode('?', $file);
  187. $asset[1] = isset($asset[1]) ? '?' . $asset[1] : null;
  188. $webPath = $this->request->webroot . $asset[0];
  189. $file = $asset[0];
  190. if (!empty($this->theme)) {
  191. $file = trim($file, '/');
  192. $theme = Inflector::underscore($this->theme) . '/';
  193. if (DS === '\\') {
  194. $file = str_replace('/', '\\', $file);
  195. }
  196. if (file_exists(Configure::read('App.www_root') . $theme . $file)) {
  197. $webPath = $this->request->webroot . $theme . $asset[0];
  198. } else {
  199. $themePath = Plugin::path($this->theme);
  200. $path = $themePath . 'webroot/' . $file;
  201. if (file_exists($path)) {
  202. $webPath = $this->request->webroot . $theme . $asset[0];
  203. }
  204. }
  205. }
  206. if (strpos($webPath, '//') !== false) {
  207. return str_replace('//', '/', $webPath . $asset[1]);
  208. }
  209. return $webPath . $asset[1];
  210. }
  211. /**
  212. * Generate URL for given asset file. Depending on options passed provides full URL with domain name.
  213. * Also calls Helper::assetTimestamp() to add timestamp to local files
  214. *
  215. * @param string|array Path string or URL array
  216. * @param array $options Options array. Possible keys:
  217. * `fullBase` Return full URL with domain name
  218. * `pathPrefix` Path prefix for relative URLs
  219. * `ext` Asset extension to append
  220. * `plugin` False value will prevent parsing path as a plugin
  221. * @return string Generated URL
  222. */
  223. public function assetUrl($path, array $options = array()) {
  224. if (is_array($path)) {
  225. return $this->url($path, !empty($options['fullBase']));
  226. }
  227. if (strpos($path, '://') !== false) {
  228. return $path;
  229. }
  230. if (!array_key_exists('plugin', $options) || $options['plugin'] !== false) {
  231. list($plugin, $path) = $this->_View->pluginSplit($path, false);
  232. }
  233. if (!empty($options['pathPrefix']) && $path[0] !== '/') {
  234. $path = $options['pathPrefix'] . $path;
  235. }
  236. if (
  237. !empty($options['ext']) &&
  238. strpos($path, '?') === false &&
  239. substr($path, -strlen($options['ext'])) !== $options['ext']
  240. ) {
  241. $path .= $options['ext'];
  242. }
  243. if (preg_match('|^([a-z0-9]+:)?//|', $path)) {
  244. return $path;
  245. }
  246. if (isset($plugin)) {
  247. $path = Inflector::underscore($plugin) . '/' . $path;
  248. }
  249. $path = $this->_encodeUrl($this->assetTimestamp($this->webroot($path)));
  250. if (!empty($options['fullBase'])) {
  251. $path = rtrim(Router::fullBaseUrl(), '/') . '/' . ltrim($path, '/');
  252. }
  253. return $path;
  254. }
  255. /**
  256. * Encodes a URL for use in HTML attributes.
  257. *
  258. * @param string $url The URL to encode.
  259. * @return string The URL encoded for both URL & HTML contexts.
  260. */
  261. protected function _encodeUrl($url) {
  262. $path = parse_url($url, PHP_URL_PATH);
  263. $parts = array_map('rawurldecode', explode('/', $path));
  264. $parts = array_map('rawurlencode', $parts);
  265. $encoded = implode('/', $parts);
  266. return h(str_replace($path, $encoded, $url));
  267. }
  268. /**
  269. * Adds a timestamp to a file based resource based on the value of `Asset.timestamp` in
  270. * Configure. If Asset.timestamp is true and debug is true, or Asset.timestamp === 'force'
  271. * a timestamp will be added.
  272. *
  273. * @param string $path The file path to timestamp, the path must be inside WWW_ROOT
  274. * @return string Path with a timestamp added, or not.
  275. */
  276. public function assetTimestamp($path) {
  277. $stamp = Configure::read('Asset.timestamp');
  278. $timestampEnabled = $stamp === 'force' || ($stamp === true && Configure::read('debug'));
  279. if ($timestampEnabled && strpos($path, '?') === false) {
  280. $filepath = preg_replace(
  281. '/^' . preg_quote($this->request->webroot, '/') . '/',
  282. '',
  283. urldecode($path)
  284. );
  285. $webrootPath = WWW_ROOT . str_replace('/', DS, $filepath);
  286. if (file_exists($webrootPath)) {
  287. //@codingStandardsIgnoreStart
  288. return $path . '?' . @filemtime($webrootPath);
  289. //@codingStandardsIgnoreEnd
  290. }
  291. $segments = explode('/', ltrim($filepath, '/'));
  292. $plugin = Inflector::camelize($segments[0]);
  293. if (Plugin::loaded($plugin)) {
  294. unset($segments[0]);
  295. $pluginPath = Plugin::path($plugin) . 'webroot' . DS . implode(DS, $segments);
  296. //@codingStandardsIgnoreStart
  297. return $path . '?' . @filemtime($pluginPath);
  298. //@codingStandardsIgnoreEnd
  299. }
  300. }
  301. return $path;
  302. }
  303. /**
  304. * Returns a string to be used as onclick handler for confirm dialogs.
  305. *
  306. * @param string $message Message to be displayed
  307. * @param string $okCode Code to be executed after user chose 'OK'
  308. * @param string $cancelCode Code to be executed after user chose 'Cancel'
  309. * @param array $options Array of options
  310. * @return string onclick JS code
  311. */
  312. protected function _confirm($message, $okCode, $cancelCode = '', $options = array()) {
  313. $message = json_encode($message);
  314. $confirm = "if (confirm({$message})) { {$okCode} } {$cancelCode}";
  315. if (isset($options['escape']) && $options['escape'] === false) {
  316. $confirm = h($confirm);
  317. }
  318. return $confirm;
  319. }
  320. /**
  321. * Adds the given class to the element options
  322. *
  323. * @param array $options Array options/attributes to add a class to
  324. * @param string $class The class name being added.
  325. * @param string $key the key to use for class.
  326. * @return array Array of options with $key set.
  327. */
  328. public function addClass(array $options = array(), $class = null, $key = 'class') {
  329. if (isset($options[$key]) && trim($options[$key])) {
  330. $options[$key] .= ' ' . $class;
  331. } else {
  332. $options[$key] = $class;
  333. }
  334. return $options;
  335. }
  336. /**
  337. * Get the View callbacks this helper is interested in.
  338. *
  339. * By defining one of the callback methods a helper is assumed
  340. * to be interested in the related event.
  341. *
  342. * Override this method if you need to add non-conventional event listeners.
  343. * Or if you want helpers to listen to non-standard events.
  344. *
  345. * @return array
  346. */
  347. public function implementedEvents() {
  348. $eventMap = [
  349. 'View.beforeRenderFile' => 'beforeRenderFile',
  350. 'View.afterRenderFile' => 'afterRenderFile',
  351. 'View.beforeRender' => 'beforeRender',
  352. 'View.afterRender' => 'afterRender',
  353. 'View.beforeLayout' => 'beforeLayout',
  354. 'View.afterLayout' => 'afterLayout'
  355. ];
  356. $events = [];
  357. foreach ($eventMap as $event => $method) {
  358. if (method_exists($this, $method)) {
  359. $events[$event] = $method;
  360. }
  361. }
  362. return $events;
  363. }
  364. }