3 require_once
'Dataface/CSSTool.php';
13 private static $instance=null;
20 private $includePath = array();
28 private $scripts = array();
34 private $included = array();
45 private $minify =
true;
50 private $useCache =
true;
55 private $dependencies = array();
61 private $dependencyContents = array();
64 private $cssIncludes = array();
74 private $ignoreScripts = array();
78 $this->ignoreScripts[$script] = 1;
79 if ( isset($this->scripts[$script]) ) unset($this->scripts[$script]);
83 return @$this->ignoreScripts[$script];
87 $this->css->ignore($stylesheet);
97 if ( isset(self::$instance) ){
98 $type = get_class(self::$instance);
100 $type =
'Dataface_JavascriptTool';
105 if ( !isset(self::$instance) or get_class(self::$instance) != $type ){
106 self::$instance =
new $type;
108 return self::$instance;
124 $this->minify = $minify;
133 return $this->minify;
140 $this->useCache = $cache;
147 return $this->useCache;
157 foreach ($this->css->getPaths() as $k=>$v){
158 $this->css->removePath($k);
160 foreach ($css->getPaths() as $k=>$v){
161 $this->css->addPath($k, $v);
172 return $this->scripts;
176 foreach ($this->scripts as $k=>$v){
177 unset($this->scripts[$k]);
182 foreach ($this->includePath as $key=>$val){
186 foreach ($this->scripts as $key=>$val){
201 $this->includePath[
$path] = $url;
210 unset($this->includePath[
$path]);
219 return $this->includePath;
223 $this->includePath = array();
231 if ( @$this->ignoreScripts[
$path] )
return;
232 $this->scripts[
$path] = 1;
238 if ( is_readable(
$path.DIRECTORY_SEPARATOR.$script) ){
239 $out[] =
$path.DIRECTORY_SEPARATOR.$script;
248 if ( is_readable(
$path.DIRECTORY_SEPARATOR.$script) ){
249 return $path.DIRECTORY_SEPARATOR.$script;
262 return DATAFACE_SITE_HREF.
'?-action=js&--id='.$this->generateCacheKeyForScripts(array_keys($this->scripts));
270 return file_get_contents($this->getJavascriptCachePath(array_keys($this->scripts)));
277 $clazz = get_class($this);
279 foreach ($this->dependencies as $script=>
$path){
280 $js->import($script);
281 $out[] = sprintf(
'<script src="%s"></script>', htmlspecialchars($js->getURL()));
282 $js->unimport($script);
284 $out[] = sprintf(
'<script src="%s"></script>', htmlspecialchars($this->
getURL()));
285 return implode(
"\r\n",
$out);
289 unset($this->scripts[$script]);
296 private function generateCacheKeyForScripts(){
298 $scripts = array_keys($this->scripts);
299 $base = basename($scripts[0]);
300 $base = substr($base, 0, 10);
301 return $base.
'-'.md5(implode(PATH_SEPARATOR, $scripts));
307 private function writeJavascript($contents){
308 $path = $this->getJavascriptCachePath();
309 return file_put_contents(
$path, $contents, LOCK_EX);
315 private function getJavascriptCachePath(){
316 return DATAFACE_SITE_PATH.
'/templates_c/'.$this->generateCacheKeyForScripts().
'.js';
323 private function getManifestPath(){
324 return DATAFACE_SITE_PATH.
'/templates_c/'.$this->generateCacheKeyForScripts().
'.manifest.js';
335 private function getManifestData(){
336 $path = $this->getManifestPath();
337 if ( is_readable(
$path) ){
338 return json_decode(file_get_contents(
$path),
true);
347 private function prepareManifest(){
350 'included'=> $this->included,
351 'dependencies' => $this->dependencies,
352 'dependencyContents' => $this->dependencyContents,
353 'cssIncludes' => $this->css->getIncluded()
370 private function writeManifest(){
371 $data = $this->prepareManifest();
372 $path = $this->getManifestPath();
373 return file_put_contents(
$path, json_encode($data), LOCK_EX);
383 private function isCacheDirty(){
384 $jspath = $this->getJavascriptCachePath();
385 $manifest = $this->getManifestData();
387 if ( !file_exists($jspath) )
return true;
388 if ( !$manifest )
return true;
389 if ( !$manifest[
'dependencyContents'] )
return true;
390 $mtime = filemtime($jspath);
392 $deps = $manifest[
'dependencyContents'];
393 foreach ($deps as $script=>$file){
394 $t = @filemtime($file);
395 if ( !$t or $t > $mtime ){
411 if ( !$this->useCache ) $clean =
true;
412 $scripts = array_keys($this->scripts);
415 if ( $clean or $this->isCacheDirty() ){
416 $this->included = array();
417 $this->dependencies = array();
418 $this->dependencyContents = array();
419 $this->cssIncludes = array();
422 $contents = $this->
_compile($scripts);
427 if ( $css->getStylesheets() ){
430 $contents = sprintf(
"\r\n".
'(function(){
431 var headtg = document.getElementsByTagName("head")[0];
432 if ( !headtg ) return;
433 var linktg = document.createElement("link");
434 linktg.type = "text/css";
435 linktg.rel = "stylesheet";
437 linktg.title="Styles";
438 headtg.appendChild(linktg);
439 })();', $css->getURL())
447 $contents =
"if ( typeof(window.console)=='undefined' ){window.console = {log: function(str){}};}if ( typeof(window.__xatajax_included__) != 'object' ){window.__xatajax_included__={};};"
449 if ( typeof(XataJax) != "undefined" ) XataJax.ready();
453 $res = file_put_contents($this->getJavascriptCachePath(), $contents, LOCK_EX);
454 if ( $res ===
false ){
455 throw new Exception(
"JavascriptTool failed cache the request's javascript file. Please check that your application has a templates_c directory and that it is writable.");
459 $res = $this->writeManifest();
460 if ( $res ===
false ){
461 throw new Exception(
"JavascriptTool failed cache the request's manifest file. Please check that your application has a templates_c directory and that it is writable.");
472 private function processDependency($script){
473 if ( @$this->ignoreScripts[$script] )
return;
474 $scriptPath = $this->
which($script);
475 if ( !$scriptPath )
throw new Exception(sprintf(
'Dependency "%s" could not be found in include path.', $script));
476 $this->dependencies[$script] = $scriptPath;
478 $clazz = get_class($this);
481 foreach ($this->
getPaths() as $k=>$v){
485 $js->import($script);
487 $data = $js->getManifestData();
489 $contents = $data[
'dependencyContents'];
490 foreach ($contents as $k=>$v){
491 $this->dependencyContents[$k] = $v;
493 foreach ( $js->getScripts() as $k=>$v){
494 $this->dependencies[$k] = $v;
496 foreach ($data[
'dependencies'] as $k=>$v){
497 $this->dependencies[$k] = $v;
521 protected function _compile($scripts, $passthru=
false, $onceOnly=
true){
524 if ( !is_array($scripts) ) $scripts = array($scripts);
525 $included =& $this->dependencyContents;
528 foreach ($scripts as $script){
530 if ( $onceOnly and isset($included[$script]) or @$this->ignoreScripts[$script] )
continue;
532 foreach ($this->includePath as
$path=>$url){
533 $filepath =
$path.DIRECTORY_SEPARATOR.$script;
535 if ( is_readable($filepath) ){
536 $contents = file_get_contents($filepath);
540 if ( preg_match_all(
'#//load <(.*?)>#', $contents, $matches, PREG_SET_ORDER) ){
542 foreach ($matches as $match){
544 $this->processDependency($match[1]);
548 $included[$script] = $filepath;
549 $this->included[$script] = $filepath;
554 if ( !isset($contents) ) {
555 throw new Exception(sprintf(
"Could not find script %s", $script));
560 $contents = preg_replace_callback(
'#//(require|include|require-css) <(.*)>#', array($this,
'_importCallback'), $contents);
561 $contents = preg_replace_callback(
'#@@\((.*?)\)#', array($this,
'_includeStringCallback'), $contents);
562 }
catch (Exception $ex){
564 error_log($ex->getMessage());
565 echo $ex->getMessage();
567 'Server-side Javascript directive failed in script "'.$script.
'"',
573 $contents =
"\r\n//START ".$script.
"\r\n"
574 .sprintf(
"if ( typeof(window.__xatajax_included__['%s']) == 'undefined'){window.__xatajax_included__['%s'] = true;\r\n", addslashes($script), addslashes($script))
575 .$contents.
"\r\n//END ".$script.
"\r\n"
583 return implode(
"\r\n",
$out);
587 return json_encode($this->
_compile($matches[1],
true,
false));
591 switch ($matches[1]){
593 if ( isset($this->dependencyContents[$matches[2]]) ){
596 return "\r\n".$this->_compile($matches[2]);
599 return "\r\n".$this->_compile($matches[2]);
602 $css->import($matches[2]);
606 throw new Exception(
"Handling import callback but no valid directive found");
611 $files = glob(DATAFACE_SITE_PATH.
'/templates_c/*.js');
615 $files = glob(DATAFACE_SITE_PATH.
'/templates_c/*.manifest.js');
699 $jsmin =
new JSMin($js);
700 return $jsmin->min();
708 $this->input = str_replace(
"\r\n",
"\n",
$input);
709 $this->inputLength = strlen($this->input);
717 if ($this->output !==
'') {
720 $this->
action(self::ACTION_DELETE_A_B);
722 while ($this->a !== null) {
725 if ($this->a ===
' ') {
729 } elseif ($this->a ===
"\n") {
730 if ($this->b ===
' ') {
732 } elseif (
false === strpos(
'{[(+-', $this->b)
738 || ($this->b ===
"\n"
739 && (
false === strpos(
'}])+-"\'', $this->a)))) {
745 $this->output = trim($this->output);
757 case self::ACTION_KEEP_A:
760 case self::ACTION_DELETE_A:
762 if ($this->a ===
"'" || $this->a ===
'"') {
766 $this->a = $this->
get();
767 if ($this->a === $this->b) {
772 'Unterminated String: ' . var_export($str,
true));
775 if ($this->a ===
'\\') {
777 $this->a = $this->
get();
783 case self::ACTION_DELETE_A_B:
784 $this->b = $this->
next();
786 $this->output .= $this->a .
$this->b;
789 $this->a = $this->
get();
791 if ($this->a ===
'/') {
793 } elseif ($this->a ===
'\\') {
795 $this->a = $this->
get();
797 } elseif (ord($this->a) <= self::ORD_LF) {
799 'Unterminated RegExp: '. var_export($pattern,
true));
803 $this->b = $this->
next();
811 if (
false !== strpos(
"\n{;(,=:[!&|?", $this->a)) {
814 if (
' ' === $this->a) {
815 $length = strlen($this->output);
820 if (preg_match(
'/(?:case|else|in|return|typeof)$/', $this->output, $m)) {
821 if ($this->output === $m[0]) {
825 $charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1);
826 if (! $this->
isAlphaNum($charBeforeKeyword)) {
837 protected function get()
840 $this->lookAhead = null;
842 if ($this->inputIndex < $this->inputLength) {
844 $this->inputIndex += 1;
849 if ($c ===
"\r" || $c ===
"\n") {
863 $this->lookAhead = $this->
get();
872 return (preg_match(
'/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126);
883 if (preg_match(
'/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
884 return "/{$comment}";
898 if ($this->
peek() ===
'/') {
901 if (0 === strpos($comment,
'!')) {
902 return "\n/*" . substr($comment, 1) .
"*/\n";
905 if (preg_match(
'/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
906 return "/*{$comment}*/";
910 } elseif ($get === null) {
927 switch ($this->
peek()) {
930 default:
return $get;