1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20:
21: 22: 23: 24: 25: 26: 27: 28: 29:
30: class JavascriptHelper extends AppHelper {
31:
32: 33: 34: 35: 36: 37:
38: var $useNative = false;
39:
40: 41: 42: 43: 44: 45: 46:
47: var $enabled = true;
48:
49: 50: 51: 52: 53: 54:
55: var $safe = false;
56:
57: 58: 59: 60: 61: 62:
63: var $tags = array(
64: 'javascriptstart' => '<script type="text/javascript">',
65: 'javascriptend' => '</script>',
66: 'javascriptblock' => '<script type="text/javascript">%s</script>',
67: 'javascriptlink' => '<script type="text/javascript" src="%s"></script>'
68: );
69:
70: 71: 72: 73: 74: 75: 76:
77: var $_blockOptions = array();
78:
79: 80: 81: 82: 83: 84: 85:
86: var $_cachedEvents = array();
87:
88: 89: 90: 91: 92: 93: 94: 95: 96:
97: var $_cacheEvents = false;
98:
99: 100: 101: 102: 103: 104: 105: 106:
107: var $_cacheToFile = false;
108:
109: 110: 111: 112: 113: 114: 115: 116:
117: var $_cacheAll = false;
118:
119: 120: 121: 122: 123: 124: 125: 126: 127:
128: var $_rules = array();
129:
130: 131: 132: 133:
134: var $__scriptBuffer = null;
135:
136: 137: 138: 139: 140:
141: function __construct($options = array()) {
142: if (!empty($options)) {
143: foreach ($options as $key => $val) {
144: if (is_numeric($key)) {
145: $key = $val;
146: $val = true;
147: }
148: switch ($key) {
149: case 'cache':
150:
151: break;
152: case 'safe':
153: $this->safe = $val;
154: break;
155: }
156: }
157: }
158: $this->useNative = function_exists('json_encode');
159: return parent::__construct($options);
160: }
161:
162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178:
179: function codeBlock($script = null, $options = array()) {
180: if (!empty($options) && !is_array($options)) {
181: $options = array('allowCache' => $options);
182: } elseif (empty($options)) {
183: $options = array();
184: }
185: $defaultOptions = array('allowCache' => true, 'safe' => true, 'inline' => true);
186: $options = array_merge($defaultOptions, $options);
187:
188: if (empty($script)) {
189: $this->__scriptBuffer = @ob_get_contents();
190: $this->_blockOptions = $options;
191: $this->inBlock = true;
192: @ob_end_clean();
193: ob_start();
194: return null;
195: }
196: if ($this->_cacheEvents && $this->_cacheAll && $options['allowCache']) {
197: $this->_cachedEvents[] = $script;
198: return null;
199: }
200: if ($options['safe'] || $this->safe) {
201: $script = "\n" . '//<![CDATA[' . "\n" . $script . "\n" . '//]]>' . "\n";
202: }
203: if ($options['inline']) {
204: return sprintf($this->tags['javascriptblock'], $script);
205: } else {
206: $view =& ClassRegistry::getObject('view');
207: $view->addScript(sprintf($this->tags['javascriptblock'], $script));
208: }
209: }
210:
211: 212: 213: 214: 215:
216: function blockEnd() {
217: if (!isset($this->inBlock) || !$this->inBlock) {
218: return;
219: }
220: $script = @ob_get_contents();
221: @ob_end_clean();
222: ob_start();
223: echo $this->__scriptBuffer;
224: $this->__scriptBuffer = null;
225: $options = $this->_blockOptions;
226: $this->_blockOptions = array();
227: $this->inBlock = false;
228:
229: if (empty($script)) {
230: return null;
231: }
232:
233: return $this->codeBlock($script, $options);
234: }
235:
236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246:
247: function link($url, $inline = true) {
248: if (is_array($url)) {
249: $out = '';
250: foreach ($url as $i) {
251: $out .= "\n\t" . $this->link($i, $inline);
252: }
253: if ($inline) {
254: return $out . "\n";
255: }
256: return;
257: }
258:
259: if (strpos($url, '://') === false) {
260: if ($url[0] !== '/') {
261: $url = JS_URL . $url;
262: }
263: if (strpos($url, '?') === false) {
264: if (substr($url, -3) !== '.js') {
265: $url .= '.js';
266: }
267: }
268: $url = $this->assetTimestamp($this->webroot($url));
269:
270: if (Configure::read('Asset.filter.js')) {
271: $pos = strpos($url, JS_URL);
272: if ($pos !== false) {
273: $url = substr($url, 0, $pos) . 'cjs/' . substr($url, $pos + strlen(JS_URL));
274: }
275: }
276: }
277: $out = sprintf($this->tags['javascriptlink'], $url);
278:
279: if ($inline) {
280: return $out;
281: } else {
282: $view =& ClassRegistry::getObject('view');
283: $view->addScript($out);
284: }
285: }
286:
287: 288: 289: 290: 291: 292:
293: function escapeScript($script) {
294: $script = str_replace(array("\r\n", "\n", "\r"), '\n', $script);
295: $script = str_replace(array('"', "'"), array('\"', "\\'"), $script);
296: return $script;
297: }
298:
299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311:
312: function escapeString($string) {
313: App::import('Core', 'Multibyte');
314: $escape = array("\r\n" => "\n", "\r" => "\n");
315: $string = str_replace(array_keys($escape), array_values($escape), $string);
316: return $this->_utf8ToHex($string);
317: }
318:
319: 320: 321: 322: 323:
324: function _utf8ToHex($string) {
325: $length = strlen($string);
326: $return = '';
327: for ($i = 0; $i < $length; ++$i) {
328: $ord = ord($string{$i});
329: switch (true) {
330: case $ord == 0x08:
331: $return .= '\b';
332: break;
333: case $ord == 0x09:
334: $return .= '\t';
335: break;
336: case $ord == 0x0A:
337: $return .= '\n';
338: break;
339: case $ord == 0x0C:
340: $return .= '\f';
341: break;
342: case $ord == 0x0D:
343: $return .= '\r';
344: break;
345: case $ord == 0x22:
346: case $ord == 0x2F:
347: case $ord == 0x5C:
348: case $ord == 0x27:
349: $return .= '\\' . $string{$i};
350: break;
351: case (($ord >= 0x20) && ($ord <= 0x7F)):
352: $return .= $string{$i};
353: break;
354: case (($ord & 0xE0) == 0xC0):
355: if ($i + 1 >= $length) {
356: $i += 1;
357: $return .= '?';
358: break;
359: }
360: $charbits = $string{$i} . $string{$i + 1};
361: $char = Multibyte::utf8($charbits);
362: $return .= sprintf('\u%04s', dechex($char[0]));
363: $i += 1;
364: break;
365: case (($ord & 0xF0) == 0xE0):
366: if ($i + 2 >= $length) {
367: $i += 2;
368: $return .= '?';
369: break;
370: }
371: $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2};
372: $char = Multibyte::utf8($charbits);
373: $return .= sprintf('\u%04s', dechex($char[0]));
374: $i += 2;
375: break;
376: case (($ord & 0xF8) == 0xF0):
377: if ($i + 3 >= $length) {
378: $i += 3;
379: $return .= '?';
380: break;
381: }
382: $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3};
383: $char = Multibyte::utf8($charbits);
384: $return .= sprintf('\u%04s', dechex($char[0]));
385: $i += 3;
386: break;
387: case (($ord & 0xFC) == 0xF8):
388: if ($i + 4 >= $length) {
389: $i += 4;
390: $return .= '?';
391: break;
392: }
393: $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3} . $string{$i + 4};
394: $char = Multibyte::utf8($charbits);
395: $return .= sprintf('\u%04s', dechex($char[0]));
396: $i += 4;
397: break;
398: case (($ord & 0xFE) == 0xFC):
399: if ($i + 5 >= $length) {
400: $i += 5;
401: $return .= '?';
402: break;
403: }
404: $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3} . $string{$i + 4} . $string{$i + 5};
405: $char = Multibyte::utf8($charbits);
406: $return .= sprintf('\u%04s', dechex($char[0]));
407: $i += 5;
408: break;
409: }
410: }
411: return $return;
412: }
413:
414: 415: 416: 417: 418: 419: 420: 421: 422:
423: function event($object, $event, $observer = null, $options = array()) {
424: if (!empty($options) && !is_array($options)) {
425: $options = array('useCapture' => $options);
426: } else if (empty($options)) {
427: $options = array();
428: }
429:
430: $defaultOptions = array('useCapture' => false);
431: $options = array_merge($defaultOptions, $options);
432:
433: if ($options['useCapture'] == true) {
434: $options['useCapture'] = 'true';
435: } else {
436: $options['useCapture'] = 'false';
437: }
438: $isObject = (
439: strpos($object, 'window') !== false || strpos($object, 'document') !== false ||
440: strpos($object, '$(') !== false || strpos($object, '"') !== false ||
441: strpos($object, '\'') !== false
442: );
443:
444: if ($isObject) {
445: $b = "Event.observe({$object}, '{$event}', function(event) { {$observer} }, ";
446: $b .= "{$options['useCapture']});";
447: } elseif ($object[0] == '\'') {
448: $b = "Event.observe(" . substr($object, 1) . ", '{$event}', function(event) { ";
449: $b .= "{$observer} }, {$options['useCapture']});";
450: } else {
451: $chars = array('#', ' ', ', ', '.', ':');
452: $found = false;
453: foreach ($chars as $char) {
454: if (strpos($object, $char) !== false) {
455: $found = true;
456: break;
457: }
458: }
459: if ($found) {
460: $this->_rules[$object] = $event;
461: } else {
462: $b = "Event.observe(\$('{$object}'), '{$event}', function(event) { ";
463: $b .= "{$observer} }, {$options['useCapture']});";
464: }
465: }
466:
467: if (isset($b) && !empty($b)) {
468: if ($this->_cacheEvents === true) {
469: $this->_cachedEvents[] = $b;
470: return;
471: } else {
472: return $this->codeBlock($b, array_diff_key($options, $defaultOptions));
473: }
474: }
475: }
476:
477: 478: 479: 480: 481: 482: 483:
484: function cacheEvents($file = false, $all = false) {
485: $this->_cacheEvents = true;
486: $this->_cacheToFile = $file;
487: $this->_cacheAll = $all;
488: }
489:
490: 491: 492: 493: 494: 495:
496: function getCache($clear = true) {
497: $out = '';
498: $rules = array();
499:
500: if (!empty($this->_rules)) {
501: foreach ($this->_rules as $sel => $event) {
502: $rules[] = "\t'{$sel}': function(element, event) {\n\t\t{$event}\n\t}";
503: }
504: }
505: $data = implode("\n", $this->_cachedEvents);
506:
507: if (!empty($rules)) {
508: $data .= "\nvar Rules = {\n" . implode(",\n\n", $rules) . "\n}";
509: $data .= "\nEventSelectors.start(Rules);\n";
510: }
511: if ($clear) {
512: $this->_rules = array();
513: $this->_cacheEvents = false;
514: $this->_cachedEvents = array();
515: }
516: return $data;
517: }
518:
519: 520: 521: 522: 523: 524: 525: 526:
527: function writeEvents($inline = true, $options = array()) {
528: $out = '';
529: $rules = array();
530:
531: if (!$this->_cacheEvents) {
532: return;
533: }
534: $data = $this->getCache();
535:
536: if (empty($data)) {
537: return;
538: }
539:
540: if ($this->_cacheToFile) {
541: $filename = md5($data);
542: if (!file_exists(JS . $filename . '.js')) {
543: cache(str_replace(WWW_ROOT, '', JS) . $filename . '.js', $data, '+999 days', 'public');
544: }
545: $out = $this->link($filename);
546: } else {
547: $out = $this->codeBlock("\n" . $data . "\n", $options);
548: }
549:
550: if ($inline) {
551: return $out;
552: } else {
553: $view =& ClassRegistry::getObject('view');
554: $view->addScript($out);
555: }
556: }
557:
558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569:
570: function includeScript($script = "", $options = array()) {
571: if ($script == "") {
572: $files = scandir(JS);
573: $javascript = '';
574:
575: foreach ($files as $file) {
576: if (substr($file, -3) == '.js') {
577: $javascript .= file_get_contents(JS . "{$file}") . "\n\n";
578: }
579: }
580: } else {
581: $javascript = file_get_contents(JS . "$script.js") . "\n\n";
582: }
583: return $this->codeBlock("\n\n" . $javascript, $options);
584: }
585:
586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602:
603: function object($data = array(), $options = array()) {
604: if (!empty($options) && !is_array($options)) {
605: $options = array('block' => $options);
606: } else if (empty($options)) {
607: $options = array();
608: }
609:
610: $defaultOptions = array(
611: 'block' => false, 'prefix' => '', 'postfix' => '',
612: 'stringKeys' => array(), 'quoteKeys' => true, 'q' => '"'
613: );
614: $options = array_merge($defaultOptions, $options, array_filter(compact(array_keys($defaultOptions))));
615:
616: if (is_object($data)) {
617: $data = get_object_vars($data);
618: }
619:
620: $out = $keys = array();
621: $numeric = true;
622:
623: if ($this->useNative) {
624: $rt = json_encode($data);
625: } else {
626: if (is_null($data)) {
627: return 'null';
628: }
629: if (is_bool($data)) {
630: return $data ? 'true' : 'false';
631: }
632:
633: if (is_array($data)) {
634: $keys = array_keys($data);
635: }
636:
637: if (!empty($keys)) {
638: $numeric = (array_values($keys) === array_keys(array_values($keys)));
639: }
640:
641: foreach ($data as $key => $val) {
642: if (is_array($val) || is_object($val)) {
643: $val = $this->object(
644: $val,
645: array_merge($options, array('block' => false, 'prefix' => '', 'postfix' => ''))
646: );
647: } else {
648: $quoteStrings = (
649: !count($options['stringKeys']) ||
650: ($options['quoteKeys'] && in_array($key, $options['stringKeys'], true)) ||
651: (!$options['quoteKeys'] && !in_array($key, $options['stringKeys'], true))
652: );
653: $val = $this->value($val, $quoteStrings);
654: }
655: if (!$numeric) {
656: $val = $options['q'] . $this->value($key, false) . $options['q'] . ':' . $val;
657: }
658: $out[] = $val;
659: }
660:
661: if (!$numeric) {
662: $rt = '{' . implode(',', $out) . '}';
663: } else {
664: $rt = '[' . implode(',', $out) . ']';
665: }
666: }
667: $rt = $options['prefix'] . $rt . $options['postfix'];
668:
669: if ($options['block']) {
670: $rt = $this->codeBlock($rt, array_diff_key($options, $defaultOptions));
671: }
672:
673: return $rt;
674: }
675:
676: 677: 678: 679: 680: 681: 682:
683: function value($val, $quoteStrings = true) {
684: switch (true) {
685: case (is_array($val) || is_object($val)):
686: $val = $this->object($val);
687: break;
688: case ($val === null):
689: $val = 'null';
690: break;
691: case (is_bool($val)):
692: $val = !empty($val) ? 'true' : 'false';
693: break;
694: case (is_int($val)):
695: $val = $val;
696: break;
697: case (is_float($val)):
698: $val = sprintf("%.11f", $val);
699: break;
700: default:
701: $val = $this->escapeString($val);
702: if ($quoteStrings) {
703: $val = '"' . $val . '"';
704: }
705: break;
706: }
707: return $val;
708: }
709:
710: 711: 712: 713: 714:
715: function afterRender() {
716: if (!$this->enabled) {
717: return;
718: }
719: echo $this->writeEvents(true);
720: }
721: }
722: