PageRenderTime 54ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/code/ryzom/tools/server/www/webtt/cake/libs/view/helpers/javascript.php

https://bitbucket.org/mattraykowski/ryzomcore_demoshard
PHP | 721 lines | 456 code | 50 blank | 215 comment | 83 complexity | 693dfcf039df3b9f0edac271e30e0f4e MD5 | raw file
Possible License(s): AGPL-3.0, GPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Javascript Helper class file.
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake
  16. * @subpackage cake.cake.libs.view.helpers
  17. * @since CakePHP(tm) v 0.10.0.1076
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. /**
  21. * Javascript Helper class for easy use of JavaScript.
  22. *
  23. * JavascriptHelper encloses all methods needed while working with JavaScript.
  24. *
  25. * @package cake
  26. * @subpackage cake.cake.libs.view.helpers
  27. * @link http://book.cakephp.org/view/1450/Javascript
  28. */
  29. class JavascriptHelper extends AppHelper {
  30. /**
  31. * Determines whether native JSON extension is used for encoding. Set by object constructor.
  32. *
  33. * @var boolean
  34. * @access public
  35. */
  36. var $useNative = false;
  37. /**
  38. * If true, automatically writes events to the end of a script or to an external JavaScript file
  39. * at the end of page execution
  40. *
  41. * @var boolean
  42. * @access public
  43. */
  44. var $enabled = true;
  45. /**
  46. * Indicates whether <script /> blocks should be written 'safely,' i.e. wrapped in CDATA blocks
  47. *
  48. * @var boolean
  49. * @access public
  50. */
  51. var $safe = false;
  52. /**
  53. * HTML tags used by this helper.
  54. *
  55. * @var array
  56. * @access public
  57. */
  58. var $tags = array(
  59. 'javascriptstart' => '<script type="text/javascript">',
  60. 'javascriptend' => '</script>',
  61. 'javascriptblock' => '<script type="text/javascript">%s</script>',
  62. 'javascriptlink' => '<script type="text/javascript" src="%s"></script>'
  63. );
  64. /**
  65. * Holds options passed to codeBlock(), saved for when block is dumped to output
  66. *
  67. * @var array
  68. * @access protected
  69. * @see JavascriptHelper::codeBlock()
  70. */
  71. var $_blockOptions = array();
  72. /**
  73. * Caches events written by event() for output at the end of page execution
  74. *
  75. * @var array
  76. * @access protected
  77. * @see JavascriptHelper::event()
  78. */
  79. var $_cachedEvents = array();
  80. /**
  81. * Indicates whether generated events should be cached for later output (can be written at the
  82. * end of the page, in the <head />, or to an external file).
  83. *
  84. * @var boolean
  85. * @access protected
  86. * @see JavascriptHelper::event()
  87. * @see JavascriptHelper::writeEvents()
  88. */
  89. var $_cacheEvents = false;
  90. /**
  91. * Indicates whether cached events should be written to an external file
  92. *
  93. * @var boolean
  94. * @access protected
  95. * @see JavascriptHelper::event()
  96. * @see JavascriptHelper::writeEvents()
  97. */
  98. var $_cacheToFile = false;
  99. /**
  100. * Indicates whether *all* generated JavaScript should be cached for later output
  101. *
  102. * @var boolean
  103. * @access protected
  104. * @see JavascriptHelper::codeBlock()
  105. * @see JavascriptHelper::blockEnd()
  106. */
  107. var $_cacheAll = false;
  108. /**
  109. * Contains event rules attached with CSS selectors. Used with the event:Selectors JavaScript
  110. * library.
  111. *
  112. * @var array
  113. * @access protected
  114. * @see JavascriptHelper::event()
  115. * @link http://alternateidea.com/event-selectors/
  116. */
  117. var $_rules = array();
  118. /**
  119. * @var string
  120. * @access private
  121. */
  122. var $__scriptBuffer = null;
  123. /**
  124. * Constructor. Checks for presence of native PHP JSON extension to use for object encoding
  125. *
  126. * @access public
  127. */
  128. function __construct($options = array()) {
  129. if (!empty($options)) {
  130. foreach ($options as $key => $val) {
  131. if (is_numeric($key)) {
  132. $key = $val;
  133. $val = true;
  134. }
  135. switch ($key) {
  136. case 'cache':
  137. break;
  138. case 'safe':
  139. $this->safe = $val;
  140. break;
  141. }
  142. }
  143. }
  144. $this->useNative = function_exists('json_encode');
  145. return parent::__construct($options);
  146. }
  147. /**
  148. * Returns a JavaScript script tag.
  149. *
  150. * Options:
  151. *
  152. * - allowCache: boolean, designates whether this block is cacheable using the
  153. * current cache settings.
  154. * - safe: boolean, whether this block should be wrapped in CDATA tags. Defaults
  155. * to helper's object configuration.
  156. * - inline: whether the block should be printed inline, or written
  157. * to cached for later output (i.e. $scripts_for_layout).
  158. *
  159. * @param string $script The JavaScript to be wrapped in SCRIPT tags.
  160. * @param array $options Set of options:
  161. * @return string The full SCRIPT element, with the JavaScript inside it, or null,
  162. * if 'inline' is set to false.
  163. */
  164. function codeBlock($script = null, $options = array()) {
  165. if (!empty($options) && !is_array($options)) {
  166. $options = array('allowCache' => $options);
  167. } elseif (empty($options)) {
  168. $options = array();
  169. }
  170. $defaultOptions = array('allowCache' => true, 'safe' => true, 'inline' => true);
  171. $options = array_merge($defaultOptions, $options);
  172. if (empty($script)) {
  173. $this->__scriptBuffer = @ob_get_contents();
  174. $this->_blockOptions = $options;
  175. $this->inBlock = true;
  176. @ob_end_clean();
  177. ob_start();
  178. return null;
  179. }
  180. if ($this->_cacheEvents && $this->_cacheAll && $options['allowCache']) {
  181. $this->_cachedEvents[] = $script;
  182. return null;
  183. }
  184. if ($options['safe'] || $this->safe) {
  185. $script = "\n" . '//<![CDATA[' . "\n" . $script . "\n" . '//]]>' . "\n";
  186. }
  187. if ($options['inline']) {
  188. return sprintf($this->tags['javascriptblock'], $script);
  189. } else {
  190. $view =& ClassRegistry::getObject('view');
  191. $view->addScript(sprintf($this->tags['javascriptblock'], $script));
  192. }
  193. }
  194. /**
  195. * Ends a block of cached JavaScript code
  196. *
  197. * @return mixed
  198. */
  199. function blockEnd() {
  200. if (!isset($this->inBlock) || !$this->inBlock) {
  201. return;
  202. }
  203. $script = @ob_get_contents();
  204. @ob_end_clean();
  205. ob_start();
  206. echo $this->__scriptBuffer;
  207. $this->__scriptBuffer = null;
  208. $options = $this->_blockOptions;
  209. $this->_blockOptions = array();
  210. $this->inBlock = false;
  211. if (empty($script)) {
  212. return null;
  213. }
  214. return $this->codeBlock($script, $options);
  215. }
  216. /**
  217. * Returns a JavaScript include tag (SCRIPT element). If the filename is prefixed with "/",
  218. * the path will be relative to the base path of your application. Otherwise, the path will
  219. * be relative to your JavaScript path, usually webroot/js.
  220. *
  221. * @param mixed $url String URL to JavaScript file, or an array of URLs.
  222. * @param boolean $inline If true, the <script /> tag will be printed inline,
  223. * otherwise it will be printed in the <head />, using $scripts_for_layout
  224. * @see JS_URL
  225. * @return string
  226. */
  227. function link($url, $inline = true) {
  228. if (is_array($url)) {
  229. $out = '';
  230. foreach ($url as $i) {
  231. $out .= "\n\t" . $this->link($i, $inline);
  232. }
  233. if ($inline) {
  234. return $out . "\n";
  235. }
  236. return;
  237. }
  238. if (strpos($url, '://') === false) {
  239. if ($url[0] !== '/') {
  240. $url = JS_URL . $url;
  241. }
  242. if (strpos($url, '?') === false) {
  243. if (substr($url, -3) !== '.js') {
  244. $url .= '.js';
  245. }
  246. }
  247. $url = $this->assetTimestamp($this->webroot($url));
  248. if (Configure::read('Asset.filter.js')) {
  249. $pos = strpos($url, JS_URL);
  250. if ($pos !== false) {
  251. $url = substr($url, 0, $pos) . 'cjs/' . substr($url, $pos + strlen(JS_URL));
  252. }
  253. }
  254. }
  255. $out = sprintf($this->tags['javascriptlink'], $url);
  256. if ($inline) {
  257. return $out;
  258. } else {
  259. $view =& ClassRegistry::getObject('view');
  260. $view->addScript($out);
  261. }
  262. }
  263. /**
  264. * Escape carriage returns and single and double quotes for JavaScript segments.
  265. *
  266. * @param string $script string that might have javascript elements
  267. * @return string escaped string
  268. */
  269. function escapeScript($script) {
  270. $script = str_replace(array("\r\n", "\n", "\r"), '\n', $script);
  271. $script = str_replace(array('"', "'"), array('\"', "\\'"), $script);
  272. return $script;
  273. }
  274. /**
  275. * Escape a string to be JavaScript friendly.
  276. *
  277. * List of escaped ellements:
  278. * + "\r\n" => '\n'
  279. * + "\r" => '\n'
  280. * + "\n" => '\n'
  281. * + '"' => '\"'
  282. * + "'" => "\\'"
  283. *
  284. * @param string $script String that needs to get escaped.
  285. * @return string Escaped string.
  286. */
  287. function escapeString($string) {
  288. App::import('Core', 'Multibyte');
  289. $escape = array("\r\n" => "\n", "\r" => "\n");
  290. $string = str_replace(array_keys($escape), array_values($escape), $string);
  291. return $this->_utf8ToHex($string);
  292. }
  293. /**
  294. * Encode a string into JSON. Converts and escapes necessary characters.
  295. *
  296. * @return void
  297. */
  298. function _utf8ToHex($string) {
  299. $length = strlen($string);
  300. $return = '';
  301. for ($i = 0; $i < $length; ++$i) {
  302. $ord = ord($string{$i});
  303. switch (true) {
  304. case $ord == 0x08:
  305. $return .= '\b';
  306. break;
  307. case $ord == 0x09:
  308. $return .= '\t';
  309. break;
  310. case $ord == 0x0A:
  311. $return .= '\n';
  312. break;
  313. case $ord == 0x0C:
  314. $return .= '\f';
  315. break;
  316. case $ord == 0x0D:
  317. $return .= '\r';
  318. break;
  319. case $ord == 0x22:
  320. case $ord == 0x2F:
  321. case $ord == 0x5C:
  322. case $ord == 0x27:
  323. $return .= '\\' . $string{$i};
  324. break;
  325. case (($ord >= 0x20) && ($ord <= 0x7F)):
  326. $return .= $string{$i};
  327. break;
  328. case (($ord & 0xE0) == 0xC0):
  329. if ($i + 1 >= $length) {
  330. $i += 1;
  331. $return .= '?';
  332. break;
  333. }
  334. $charbits = $string{$i} . $string{$i + 1};
  335. $char = Multibyte::utf8($charbits);
  336. $return .= sprintf('\u%04s', dechex($char[0]));
  337. $i += 1;
  338. break;
  339. case (($ord & 0xF0) == 0xE0):
  340. if ($i + 2 >= $length) {
  341. $i += 2;
  342. $return .= '?';
  343. break;
  344. }
  345. $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2};
  346. $char = Multibyte::utf8($charbits);
  347. $return .= sprintf('\u%04s', dechex($char[0]));
  348. $i += 2;
  349. break;
  350. case (($ord & 0xF8) == 0xF0):
  351. if ($i + 3 >= $length) {
  352. $i += 3;
  353. $return .= '?';
  354. break;
  355. }
  356. $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3};
  357. $char = Multibyte::utf8($charbits);
  358. $return .= sprintf('\u%04s', dechex($char[0]));
  359. $i += 3;
  360. break;
  361. case (($ord & 0xFC) == 0xF8):
  362. if ($i + 4 >= $length) {
  363. $i += 4;
  364. $return .= '?';
  365. break;
  366. }
  367. $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3} . $string{$i + 4};
  368. $char = Multibyte::utf8($charbits);
  369. $return .= sprintf('\u%04s', dechex($char[0]));
  370. $i += 4;
  371. break;
  372. case (($ord & 0xFE) == 0xFC):
  373. if ($i + 5 >= $length) {
  374. $i += 5;
  375. $return .= '?';
  376. break;
  377. }
  378. $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3} . $string{$i + 4} . $string{$i + 5};
  379. $char = Multibyte::utf8($charbits);
  380. $return .= sprintf('\u%04s', dechex($char[0]));
  381. $i += 5;
  382. break;
  383. }
  384. }
  385. return $return;
  386. }
  387. /**
  388. * Attach an event to an element. Used with the Prototype library.
  389. *
  390. * @param string $object Object to be observed
  391. * @param string $event event to observe
  392. * @param string $observer function to call
  393. * @param array $options Set options: useCapture, allowCache, safe
  394. * @return boolean true on success
  395. */
  396. function event($object, $event, $observer = null, $options = array()) {
  397. if (!empty($options) && !is_array($options)) {
  398. $options = array('useCapture' => $options);
  399. } else if (empty($options)) {
  400. $options = array();
  401. }
  402. $defaultOptions = array('useCapture' => false);
  403. $options = array_merge($defaultOptions, $options);
  404. if ($options['useCapture'] == true) {
  405. $options['useCapture'] = 'true';
  406. } else {
  407. $options['useCapture'] = 'false';
  408. }
  409. $isObject = (
  410. strpos($object, 'window') !== false || strpos($object, 'document') !== false ||
  411. strpos($object, '$(') !== false || strpos($object, '"') !== false ||
  412. strpos($object, '\'') !== false
  413. );
  414. if ($isObject) {
  415. $b = "Event.observe({$object}, '{$event}', function(event) { {$observer} }, ";
  416. $b .= "{$options['useCapture']});";
  417. } elseif ($object[0] == '\'') {
  418. $b = "Event.observe(" . substr($object, 1) . ", '{$event}', function(event) { ";
  419. $b .= "{$observer} }, {$options['useCapture']});";
  420. } else {
  421. $chars = array('#', ' ', ', ', '.', ':');
  422. $found = false;
  423. foreach ($chars as $char) {
  424. if (strpos($object, $char) !== false) {
  425. $found = true;
  426. break;
  427. }
  428. }
  429. if ($found) {
  430. $this->_rules[$object] = $event;
  431. } else {
  432. $b = "Event.observe(\$('{$object}'), '{$event}', function(event) { ";
  433. $b .= "{$observer} }, {$options['useCapture']});";
  434. }
  435. }
  436. if (isset($b) && !empty($b)) {
  437. if ($this->_cacheEvents === true) {
  438. $this->_cachedEvents[] = $b;
  439. return;
  440. } else {
  441. return $this->codeBlock($b, array_diff_key($options, $defaultOptions));
  442. }
  443. }
  444. }
  445. /**
  446. * Cache JavaScript events created with event()
  447. *
  448. * @param boolean $file If true, code will be written to a file
  449. * @param boolean $all If true, all code written with JavascriptHelper will be sent to a file
  450. * @return null
  451. */
  452. function cacheEvents($file = false, $all = false) {
  453. $this->_cacheEvents = true;
  454. $this->_cacheToFile = $file;
  455. $this->_cacheAll = $all;
  456. }
  457. /**
  458. * Gets (and clears) the current JavaScript event cache
  459. *
  460. * @param boolean $clear
  461. * @return string
  462. */
  463. function getCache($clear = true) {
  464. $out = '';
  465. $rules = array();
  466. if (!empty($this->_rules)) {
  467. foreach ($this->_rules as $sel => $event) {
  468. $rules[] = "\t'{$sel}': function(element, event) {\n\t\t{$event}\n\t}";
  469. }
  470. }
  471. $data = implode("\n", $this->_cachedEvents);
  472. if (!empty($rules)) {
  473. $data .= "\nvar Rules = {\n" . implode(",\n\n", $rules) . "\n}";
  474. $data .= "\nEventSelectors.start(Rules);\n";
  475. }
  476. if ($clear) {
  477. $this->_rules = array();
  478. $this->_cacheEvents = false;
  479. $this->_cachedEvents = array();
  480. }
  481. return $data;
  482. }
  483. /**
  484. * Write cached JavaScript events
  485. *
  486. * @param boolean $inline If true, returns JavaScript event code. Otherwise it is added to the
  487. * output of $scripts_for_layout in the layout.
  488. * @param array $options Set options for codeBlock
  489. * @return string
  490. */
  491. function writeEvents($inline = true, $options = array()) {
  492. $out = '';
  493. $rules = array();
  494. if (!$this->_cacheEvents) {
  495. return;
  496. }
  497. $data = $this->getCache();
  498. if (empty($data)) {
  499. return;
  500. }
  501. if ($this->_cacheToFile) {
  502. $filename = md5($data);
  503. if (!file_exists(JS . $filename . '.js')) {
  504. cache(str_replace(WWW_ROOT, '', JS) . $filename . '.js', $data, '+999 days', 'public');
  505. }
  506. $out = $this->link($filename);
  507. } else {
  508. $out = $this->codeBlock("\n" . $data . "\n", $options);
  509. }
  510. if ($inline) {
  511. return $out;
  512. } else {
  513. $view =& ClassRegistry::getObject('view');
  514. $view->addScript($out);
  515. }
  516. }
  517. /**
  518. * Includes the Prototype Javascript library (and anything else) inside a single script tag.
  519. *
  520. * Note: The recommended approach is to copy the contents of
  521. * javascripts into your application's
  522. * public/javascripts/ directory, and use @see javascriptIncludeTag() to
  523. * create remote script links.
  524. *
  525. * @param string $script Script file to include
  526. * @param array $options Set options for codeBlock
  527. * @return string script with all javascript in/javascripts folder
  528. */
  529. function includeScript($script = "", $options = array()) {
  530. if ($script == "") {
  531. $files = scandir(JS);
  532. $javascript = '';
  533. foreach ($files as $file) {
  534. if (substr($file, -3) == '.js') {
  535. $javascript .= file_get_contents(JS . "{$file}") . "\n\n";
  536. }
  537. }
  538. } else {
  539. $javascript = file_get_contents(JS . "$script.js") . "\n\n";
  540. }
  541. return $this->codeBlock("\n\n" . $javascript, $options);
  542. }
  543. /**
  544. * Generates a JavaScript object in JavaScript Object Notation (JSON)
  545. * from an array
  546. *
  547. * ### Options
  548. *
  549. * - block - Wraps the return value in a script tag if true. Default is false
  550. * - prefix - Prepends the string to the returned data. Default is ''
  551. * - postfix - Appends the string to the returned data. Default is ''
  552. * - stringKeys - A list of array keys to be treated as a string.
  553. * - quoteKeys - If false treats $stringKeys as a list of keys **not** to be quoted. Default is true.
  554. * - q - The type of quote to use. Default is '"'. This option only affects the keys, not the values.
  555. *
  556. * @param array $data Data to be converted
  557. * @param array $options Set of options: block, prefix, postfix, stringKeys, quoteKeys, q
  558. * @return string A JSON code block
  559. */
  560. function object($data = array(), $options = array()) {
  561. if (!empty($options) && !is_array($options)) {
  562. $options = array('block' => $options);
  563. } else if (empty($options)) {
  564. $options = array();
  565. }
  566. $defaultOptions = array(
  567. 'block' => false, 'prefix' => '', 'postfix' => '',
  568. 'stringKeys' => array(), 'quoteKeys' => true, 'q' => '"'
  569. );
  570. $options = array_merge($defaultOptions, $options, array_filter(compact(array_keys($defaultOptions))));
  571. if (is_object($data)) {
  572. $data = get_object_vars($data);
  573. }
  574. $out = $keys = array();
  575. $numeric = true;
  576. if ($this->useNative) {
  577. $rt = json_encode($data);
  578. } else {
  579. if (is_null($data)) {
  580. return 'null';
  581. }
  582. if (is_bool($data)) {
  583. return $data ? 'true' : 'false';
  584. }
  585. if (is_array($data)) {
  586. $keys = array_keys($data);
  587. }
  588. if (!empty($keys)) {
  589. $numeric = (array_values($keys) === array_keys(array_values($keys)));
  590. }
  591. foreach ($data as $key => $val) {
  592. if (is_array($val) || is_object($val)) {
  593. $val = $this->object(
  594. $val,
  595. array_merge($options, array('block' => false, 'prefix' => '', 'postfix' => ''))
  596. );
  597. } else {
  598. $quoteStrings = (
  599. !count($options['stringKeys']) ||
  600. ($options['quoteKeys'] && in_array($key, $options['stringKeys'], true)) ||
  601. (!$options['quoteKeys'] && !in_array($key, $options['stringKeys'], true))
  602. );
  603. $val = $this->value($val, $quoteStrings);
  604. }
  605. if (!$numeric) {
  606. $val = $options['q'] . $this->value($key, false) . $options['q'] . ':' . $val;
  607. }
  608. $out[] = $val;
  609. }
  610. if (!$numeric) {
  611. $rt = '{' . implode(',', $out) . '}';
  612. } else {
  613. $rt = '[' . implode(',', $out) . ']';
  614. }
  615. }
  616. $rt = $options['prefix'] . $rt . $options['postfix'];
  617. if ($options['block']) {
  618. $rt = $this->codeBlock($rt, array_diff_key($options, $defaultOptions));
  619. }
  620. return $rt;
  621. }
  622. /**
  623. * Converts a PHP-native variable of any type to a JSON-equivalent representation
  624. *
  625. * @param mixed $val A PHP variable to be converted to JSON
  626. * @param boolean $quoteStrings If false, leaves string values unquoted
  627. * @return string a JavaScript-safe/JSON representation of $val
  628. */
  629. function value($val, $quoteStrings = true) {
  630. switch (true) {
  631. case (is_array($val) || is_object($val)):
  632. $val = $this->object($val);
  633. break;
  634. case ($val === null):
  635. $val = 'null';
  636. break;
  637. case (is_bool($val)):
  638. $val = !empty($val) ? 'true' : 'false';
  639. break;
  640. case (is_int($val)):
  641. $val = $val;
  642. break;
  643. case (is_float($val)):
  644. $val = sprintf("%.11f", $val);
  645. break;
  646. default:
  647. $val = $this->escapeString($val);
  648. if ($quoteStrings) {
  649. $val = '"' . $val . '"';
  650. }
  651. break;
  652. }
  653. return $val;
  654. }
  655. /**
  656. * AfterRender callback. Writes any cached events to the view, or to a temp file.
  657. *
  658. * @return null
  659. */
  660. function afterRender() {
  661. if (!$this->enabled) {
  662. return;
  663. }
  664. echo $this->writeEvents(true);
  665. }
  666. }