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