Helper.php 12 KB

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