diff options
| author | wei <> | 2007-04-05 01:57:22 +0000 | 
|---|---|---|
| committer | wei <> | 2007-04-05 01:57:22 +0000 | 
| commit | 5d3f9a7de5ee0b0d8976398a490ed46b7107056f (patch) | |
| tree | 2507dd2df914ec5fd20902c526ea6fe5919d6b04 | |
| parent | 51db38c9423a1d3bf476bb30054cf3288ee16c88 (diff) | |
add clientscripts for publishing user javascript libraries
| -rw-r--r-- | .gitattributes | 2 | ||||
| -rw-r--r-- | framework/Exceptions/messages.txt | 1 | ||||
| -rw-r--r-- | framework/Web/Javascripts/clientscripts.php | 981 | ||||
| -rw-r--r-- | framework/Web/Javascripts/packages.php | 55 | ||||
| -rw-r--r-- | framework/Web/UI/WebControls/TClientScript.php | 80 | 
5 files changed, 1117 insertions, 2 deletions
| diff --git a/.gitattributes b/.gitattributes index 4282f8b7..c93a83ae 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1955,6 +1955,7 @@ framework/Util/TSimpleDateFormatter.php -text  framework/Util/TVarDumper.php -text  framework/Web/Javascripts/TJSON.php -text  framework/Web/Javascripts/TJavaScript.php -text +framework/Web/Javascripts/clientscripts.php -text  framework/Web/Javascripts/js/compressed/ajax.js -text  framework/Web/Javascripts/js/compressed/clientscripts.php -text  framework/Web/Javascripts/js/compressed/colorpicker.js -text @@ -1973,6 +1974,7 @@ framework/Web/Javascripts/js/debug/effects.js -text  framework/Web/Javascripts/js/debug/logger.js -text  framework/Web/Javascripts/js/debug/prado.js -text  framework/Web/Javascripts/js/debug/validator.js -text +framework/Web/Javascripts/packages.php -text  framework/Web/Javascripts/prado/activecontrols/activecontrols3.js -text  framework/Web/Javascripts/prado/activecontrols/ajax3.js -text  framework/Web/Javascripts/prado/activecontrols/inlineeditor.js -text diff --git a/framework/Exceptions/messages.txt b/framework/Exceptions/messages.txt index f5b06e85..3ca019d4 100644 --- a/framework/Exceptions/messages.txt +++ b/framework/Exceptions/messages.txt @@ -330,6 +330,7 @@ databoundcontrol_datasourceid_invalid	= databoundcontrol_datasourceid_invalid  databoundcontrol_datamember_invalid		= databoundcontrol_datamember_invalid
  clientscript_invalid_file_position		= Invalid file position '{1}' for TClientScript control '{0}', must be 'Head', 'Here' or 'Begin'.
 +clientscript_invalid_package_path		= Invalid PackagePath '{0}' for TClientScript control '{1}'.
  tdatepicker_autopostback_unsupported	= '{0}' does not support AutoPostBack.
  globalization_cache_path_failed			= Unable to create translation message cache path '{0}'. Make sure the parent directory exists and is writable by the Web process.
 diff --git a/framework/Web/Javascripts/clientscripts.php b/framework/Web/Javascripts/clientscripts.php new file mode 100644 index 00000000..21da56bf --- /dev/null +++ b/framework/Web/Javascripts/clientscripts.php @@ -0,0 +1,981 @@ +<?php
 +
 +/**
 + * Combines multiple javascript files and serve up as gzip if possible.
 + * Allowable scripts and script dependencies can be specified in a "packages.php" file
 + * with the following format. This "packages.php" is optional, if absent the filenames
 + * without ".js" extension are used.
 + *
 + * <code>
 + * <?php
 + *  $packages = array(
 + *     'package1' => array('file1.js', 'file2.js'),
 + *     'package2' => array('file3.js', 'file4.js'));
 + *
 + *  $dependencies = array(
 + *     'package1' => array('package1'),
 + *     'package2' => array('package1', 'package2')); //package2 requires package1 first.
 + * </code>
 + *
 + * To serve up 'package1', specify the url, a maxium of 25 packages separated with commas is allows.
 + *
 + * clientscripts.php?js=package1
 + *
 + * for 'package2' (automatically resolves 'package1') dependency
 + *
 + * clientscripts.php?js=package2
 + *
 + * The scripts comments are removed and whitespaces removed appropriately. The
 + * scripts may be served as zipped if the browser and php server allows it. Cache
 + * headers are also sent to inform the browser and proxies to cache the file.
 + * Moreover, the post-processed (comments removed and zipped) are saved in this
 + * current directory for the next requests.
 + *
 + * If the url contains the parameter "mode=debug", the comments are not removed
 + * and headers invalidating the cache are sent. In debug mode, the script can still
 + * be zipped if the browser and server supports it.
 + *
 + * E.g. clientscripts.php?js=package2&mode=debug
 + *
 + * @link http://www.pradosoft.com/
 + * @copyright Copyright © 2007 PradoSoft
 + * @license http://www.pradosoft.com/license/
 + * @author Wei Zhuo<weizhuo[at]gmail[dot]com>
 + * @version $Id$
 + * @package System.Web.Javascripts
 + * @since 3.1
 + */
 +
 +@error_reporting(E_ERROR | E_WARNING | E_PARSE);
 +
 +function get_client_script_files()
 +{
 +	$package_file = dirname(__FILE__).'/packages.php';
 +	if(is_file($package_file))
 +		return get_package_files($package_file, get_script_requests());
 +	else
 +		return get_javascript_files(get_script_requests());
 +}
 +
 +/**
 + * @param array list of requested libraries
 + */
 +function get_script_requests($max=25)
 +{
 +	$param = isset($_GET['js']) ? $_GET['js'] : '';
 +	if(preg_match('/([a-zA-z0-9\-_])+(,[a-zA-z0-9\-_]+)*/', $param))
 +		return array_unique(explode(',', $param, $max));
 +	return array();
 +}
 +
 +/**
 + * @param string packages.php filename
 + * @param array packages requests
 + */
 +function get_package_files($package_file, $request)
 +{
 +	list($packages, $dependencies) = @include($package_file);
 +	if(!(is_array($packages) && is_array($dependencies)))
 +	{
 +		error_log('Prado client script: invalid package file '.$package_file);
 +		return array();
 +	}
 +	$result = array();
 +	$found = array();
 +	foreach($request as $library)
 +	{
 +		if(isset($dependencies[$library]))
 +		{
 +			foreach($dependencies[$library] as $dep)
 +			{
 +				if(isset($found[$dep]))
 +					continue;
 +				$result = array_merge($result, (array)$packages[$dep]);
 +				$found[$dep]=true;
 +			}
 +		}
 +		else
 +			error_log('Prado client script: no such javascript library "'.$library.'"');
 +	}
 +	return $result;
 +}
 +
 +/**
 + * @param string requested javascript files
 + * @array array list of javascript files.
 + */
 +function get_javascript_files($request)
 +{
 +	$result = array();
 +	foreach($request as $file)
 +		$result[] = $file.'.js';
 +	return $result;
 +}
 +
 +/**
 + * @param array list of javascript files.
 + * @return string combined the available javascript files.
 + */
 +function combine_javascript($files)
 +{
 +	$content = '';
 +	$base = dirname(__FILE__);
 +	foreach($files as $file)
 +	{
 +		$filename = $base.'/'.$file;
 +		if(is_file($filename)) //relies on get_client_script_files() for security
 +			$content .= file_get_contents($filename);
 +		else
 +			error_log('Prado client script: missing file '.$filename);
 +	}
 +	return $content;
 +}
 +
 +/**
 + * @param string javascript code
 + * @param array files names
 + * @return string javascript code without comments.
 + */
 +function minify_javascript($content, $files)
 +{
 +	$jsMin = new JSMin($content, false);
 +	try
 +	{
 +		return $jsMin->minify();
 +	}
 +	catch (Exception $e)
 +	{
 +		error_log('Prado client script: unable to strip javascript comments in one or more files in "'.implode(', ', $files).'"');
 +		return $content;
 +	}
 +}
 +
 +/**
 + * @param boolean true if in debug mode.
 + */
 +function is_debug_mode()
 +{
 +	return isset($_GET['mode']) && $_GET['mode']==='debug';
 +}
 +
 +/**
 + * @param string javascript code
 + * @param string gzip code
 + */
 +function gzip_content($content)
 +{
 +	return gzencode($content, 9, FORCE_GZIP);
 +}
 +
 +/**
 + * @param string javascript code.
 + * @param string filename
 + */
 +function save_javascript($content, $filename)
 +{
 +	file_put_contents($filename, $content);
 +	if(supports_gzip_encoding())
 +		file_put_contents($filename.'.gz', gzip_content($content));
 +}
 +
 +/**
 + * @param string comprssed javascript file to be read
 + * @param string javascript code, null if file is not found.
 + */
 +function get_saved_javascript($filename)
 +{
 +	if(supports_gzip_encoding())
 +		$filename .= '.gz';
 +	if(is_file($filename))
 +		return file_get_contents($filename);
 +	else
 +		error_log('Prado client script: no such file '.$filename);
 +}
 +
 +/**
 + * @return string compressed javascript file name.
 + */
 +function compressed_js_filename()
 +{
 +	$files = get_client_script_files();
 +	if(count($files) > 0)
 +	{
 +		$filename = sprintf('%x',crc32(implode(',',($files))));
 +		return dirname(__FILE__).'/clientscript_'.$filename.'.js';
 +	}
 +}
 +
 +/**
 + * @param boolean true to strip comments from javascript code
 + * @return string javascript code
 + */
 +function get_javascript_code($minify=false)
 +{
 +	$files = get_client_script_files();
 +	if(count($files) > 0)
 +	{
 +		$content = combine_javascript($files);
 +		if($minify)
 +			return minify_javascript($content, $files);
 +		else
 +			return $content;
 +	}
 +}
 +
 +/**
 + * Prints headers to serve javascript
 + */
 +function print_headers()
 +{
 +	$expiresOffset = is_debug_mode() ? -10000 : 3600 * 24 * 10; //no cache
 +	header("Content-type: text/javascript");
 +	header("Vary: Accept-Encoding");  // Handle proxies
 +	header("Expires: " . @gmdate("D, d M Y H:i:s", @time() + $expiresOffset) . " GMT");
 +	if(($enc = supports_gzip_encoding()) !== null)
 +		header("Content-Encoding: " . $enc);
 +}
 +
 +/**
 + * @return string 'x-gzip' or 'gzip' if php supports gzip and browser supports gzip response, null otherwise.
 + */
 +function supports_gzip_encoding()
 +{
 +	if (isset($_SERVER['HTTP_ACCEPT_ENCODING']))
 +	{
 +		$encodings = explode(',', strtolower(preg_replace("/\s+/", "", $_SERVER['HTTP_ACCEPT_ENCODING'])));
 +		$allowsZipEncoding = in_array('gzip', $encodings) || in_array('x-gzip', $encodings) || isset($_SERVER['---------------']);
 +		$hasGzip = function_exists('ob_gzhandler');
 +		$noZipBuffer = !ini_get('zlib.output_compression');
 +		$noZipBufferHandler = ini_get('output_handler') != 'ob_gzhandler';
 +
 +		if ( $allowsZipEncoding && $hasGzip && $noZipBuffer && $noZipBufferHandler)
 +			$enc = in_array('x-gzip', $encodings) ? "x-gzip" : "gzip";
 +		return $enc;
 +	}
 +}
 +
 +define('JSMIN_AS_LIB', true);
 +
 +/**
 +* JSMin_lib.php (for PHP 4, 5)
 +*
 +* PHP adaptation of JSMin, published by Douglas Crockford as jsmin.c, also based
 +* on its Java translation by John Reilly.
 +*
 +* Permission is hereby granted to use the PHP version under the same conditions
 +* as jsmin.c, which has the following notice :
 +*
 +* ----------------------------------------------------------------------------
 +*
 +* Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
 +*
 +* Permission is hereby granted, free of charge, to any person obtaining a copy of
 +* this software and associated documentation files (the "Software"), to deal in
 +* the Software without restriction, including without limitation the rights to
 +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 +* of the Software, and to permit persons to whom the Software is furnished to do
 +* so, subject to the following conditions:
 +*
 +* The above copyright notice and this permission notice shall be included in all
 +* copies or substantial portions of the Software.
 +*
 +* The Software shall be used for Good, not Evil.
 +*
 +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 +* SOFTWARE.
 +*/
 +
 +/**
 +* Version of this PHP translation.
 +*/
 +
 +define('JSMIN_VERSION', '0.2');
 +
 +/**
 +* How fgetc() reports an End Of File.
 +* N.B. : use === and not == to test the result of fgetc() ! (see manual)
 +*/
 +
 +define('EOF', FALSE);
 +
 +/**
 +* Some ASCII character ordinals.
 +* N.B. : PHP identifiers are case-insensitive !
 +*/
 +
 +define('ORD_NL', ord("\n"));
 +define('ORD_space', ord(' '));
 +define('ORD_cA', ord('A'));
 +define('ORD_cZ', ord('Z'));
 +define('ORD_a', ord('a'));
 +define('ORD_z', ord('z'));
 +define('ORD_0', ord('0'));
 +define('ORD_9', ord('9'));
 +
 +/**
 +* Generic exception class related to JSMin.
 +*/
 +class JSMinException extends Exception {
 +}
 +
 +/**
 +* A JSMin exception indicating that a file provided for input or output could not be properly opened.
 +*/
 +
 +class FileOpenFailedJSMinException extends JSMinException {
 +}
 +
 +/**
 +* A JSMin exception indicating that an unterminated comment was encountered in input.
 +*/
 +
 +class UnterminatedCommentJSMinException extends JSMinException {
 +}
 +
 +/**
 +* A JSMin exception indicatig that an unterminated string literal was encountered in input.
 +*/
 +
 +class UnterminatedStringLiteralJSMinException extends JSMinException {
 +}
 +
 +/**
 +* A JSMin exception indicatig that an unterminated regular expression lieteral was encountered in input.
 +*/
 +
 +class UnterminatedRegExpLiteralJSMinException extends JSMinException {
 +}
 +
 +/**
 + * Constant describing an {@link action()} : Output A. Copy B to A. Get the next B.
 + */
 +
 +define ('JSMIN_ACT_FULL', 1);
 +
 +/**
 + * Constant describing an {@link action()} : Copy B to A. Get the next B. (Delete A).
 + */
 +
 +define ('JSMIN_ACT_BUF', 2);
 +
 +/**
 + * Constant describing an {@link action()} : Get the next B. (Delete B).
 + */
 +
 +define ('JSMIN_ACT_IMM', 3);
 +
 +/**
 +* Main JSMin application class.
 +*
 +* Example of use :
 +*
 +* $jsMin = new JSMin(...input..., ...output...);
 +* $jsMin->minify();
 +*
 +* Do not specify input and/or output (or default to '-') to use stdin and/or stdout.
 +*/
 +
 +class JSMin {
 +
 +    /**
 +     * The input stream, from which to read a JS file to minimize. Obtained by fopen().
 +     * NB: might be a string instead of a stream
 +     * @var SplFileObject | string
 +     */
 +    var $in;
 +
 +    /**
 +     * The output stream, in which to write the minimized JS file. Obtained by fopen().
 +     * NB: might be a string instead of a stream
 +     * @var SplFileObject | string
 +     */
 +    var $out;
 +
 +    /**
 +     * Temporary I/O character (A).
 +     * @var string
 +     */
 +    var $theA;
 +
 +    /**
 +     * Temporary I/O character (B).
 +     * @var string
 +     */
 +    var $theB;
 +
 +	/** variables used for string-based parsing **/
 +	var $inLength = 0;
 +	var $inPos = 0;
 +	var $isString = false;
 +
 +    /**
 +     * Indicates whether a character is alphanumeric or _, $, \ or non-ASCII.
 +     *
 +     * @param   string      $c  The single character to test.
 +     * @return  boolean     Whether the char is a letter, digit, underscore, dollar, backslash, or non-ASCII.
 +     */
 +    function isAlphaNum($c) {
 +
 +        // Get ASCII value of character for C-like comparisons
 +
 +        $a = ord($c);
 +
 +        // Compare using defined character ordinals, or between PHP strings
 +        // Note : === is micro-faster than == when types are known to be the same
 +
 +        return
 +            ($a >= ORD_a && $a <= ORD_z) ||
 +            ($a >= ORD_0 && $a <= ORD_9) ||
 +            ($a >= ORD_cA && $a <= ORD_cZ) ||
 +            $c === '_' || $c === '$' || $c === '\\' || $a > 126
 +        ;
 +    }
 +
 +    /**
 +     * Get the next character from the input stream.
 +     *
 +     * If said character is a control character, translate it to a space or linefeed.
 +     *
 +     * @return  string      The next character from the specified input stream.
 +     * @see     $in
 +     * @see     peek()
 +     */
 +    function get() {
 +
 +        // Get next input character and advance position in file
 +		if ($this->isString) {
 +			if ($this->inPos < $this->inLength) {
 +				$c = $this->in[$this->inPos];
 +				++$this->inPos;
 +			}
 +			else {
 +				return EOF;
 +			}
 +		}
 +		else
 +	        $c = $this->in->fgetc();
 +
 +        // Test for non-problematic characters
 +
 +        if ($c === "\n" || $c === EOF || ord($c) >= ORD_space) {
 +            return $c;
 +        }
 +
 +        // else
 +        // Make linefeeds into newlines
 +
 +        if ($c === "\r") {
 +            return "\n";
 +        }
 +
 +        // else
 +        // Consider space
 +
 +        return ' ';
 +    }
 +
 +    /**
 +     * Get the next character from the input stream, without gettng it.
 +     *
 +     * @return  string      The next character from the specified input stream, without advancing the position
 +     *                      in the underlying file.
 +     * @see     $in
 +     * @see     get()
 +     */
 +    function peek() {
 +
 +		if ($this->isString) {
 +			if ($this->inPos < $this->inLength) {
 +				$c = $this->in[$this->inPos];
 +			}
 +			else {
 +				return EOF;
 +			}
 +		}
 +		else {
 +	        // Get next input character
 +
 +	        $c = $this->in->fgetc();
 +
 +	        // Regress position in file
 +
 +	        $this->in->fseek(-1, SEEK_CUR);
 +
 +	        // Return character obtained
 +	    }
 +
 +        return $c;
 +    }
 +
 +	/**
 +	 * Adds a char to the output steram / string
 +	 * @see $out
 +	 */
 +	function put($c)
 +	{
 +		if ($this->isString) {
 +			$this->out .= $c;
 +		}
 +		else {
 +			$this->out->fwrite($c);
 +		}
 +	}
 +
 +    /**
 +     * Get the next character from the input stream, excluding comments.
 +     *
 +     * {@link peek()} is used to see if a '/' is followed by a '*' or '/'.
 +     * Multiline comments are actually returned as a single space.
 +     *
 +     * @return  string  The next character from the specified input stream, skipping comments.
 +     * @see     $in
 +     */
 +    function next() {
 +
 +        // Get next char from input, translated if necessary
 +
 +        $c = $this->get();
 +
 +        // Check comment possibility
 +
 +        if ($c == '/') {
 +
 +            // Look ahead : a comment is two slashes or slashes followed by asterisk (to be closed)
 +
 +            switch ($this->peek()) {
 +
 +                case '/' :
 +
 +                    // Comment is up to the end of the line
 +                    // TOTEST : simple $this->in->fgets()
 +
 +                    while (true) {
 +
 +                        $c = $this->get();
 +
 +                        if (ord($c) <= ORD_NL) {
 +                            return $c;
 +                        }
 +                    }
 +
 +                case '*' :
 +
 +                    // Comment is up to comment close.
 +                    // Might not be terminated, if we hit the end of file.
 +
 +                    while (true) {
 +
 +                        // N.B. not using switch() because of having to test EOF with ===
 +
 +                        $c = $this->get();
 +
 +                        if ($c == '*') {
 +
 +                            // Comment termination if the char ahead is a slash
 +
 +                            if ($this->peek() == '/') {
 +
 +                                // Advance again and make into a single space
 +
 +                                $this->get();
 +                                return ' ';
 +                            }
 +                        }
 +                        else if ($c === EOF) {
 +
 +                            // Whoopsie
 +                            throw new UnterminatedCommentJSMinException();
 +                        }
 +                    }
 +
 +                default :
 +
 +                    // Not a comment after all
 +
 +                    return $c;
 +            }
 +        }
 +
 +        // No risk of a comment
 +
 +        return $c;
 +    }
 +
 +    /**
 +     * Do something !
 +     *
 +     * The action to perform is determined by the argument :
 +     *
 +     * JSMin::ACT_FULL : Output A. Copy B to A. Get the next B.
 +     * JSMin::ACT_BUF  : Copy B to A. Get the next B. (Delete A).
 +     * JSMin::ACT_IMM  : Get the next B. (Delete B).
 +     *
 +     * A string is treated as a single character. Also, regular expressions are recognized if preceded
 +     * by '(', ',' or '='.
 +     *
 +     * @param   int     $action     The action to perform : one of the JSMin::ACT_* constants.
 +     */
 +    function action($action) {
 +
 +        // Choice of possible actions
 +        // Note the frequent fallthroughs : the actions are decrementally "long"
 +
 +        switch ($action) {
 +
 +            case JSMIN_ACT_FULL :
 +
 +                // Write A to output, then fall through
 +
 +                $this->put($this->theA);
 +
 +            case JSMIN_ACT_BUF : // N.B. possible fallthrough from above
 +
 +                // Copy B to A
 +
 +                $tmpA = $this->theA = $this->theB;
 +
 +                // Treating a string as a single char : outputting it whole
 +                // Note that the string-opening char (" or ') is memorized in B
 +
 +                if ($tmpA == '\'' || $tmpA == '"') {
 +
 +                    while (true) {
 +
 +                        // Output string contents
 +
 +                        $this->put($tmpA);
 +
 +                        // Get next character, watching out for termination of the current string,
 +                        // new line & co (then the string is not terminated !), or a backslash
 +                        // (upon which the following char is directly output to serve the escape mechanism)
 +
 +                        $tmpA = $this->theA = $this->get();
 +
 +                        if ($tmpA == $this->theB) {
 +
 +                            // String terminated
 +
 +                            break; // from while(true)
 +                        }
 +
 +                        // else
 +
 +                        if (ord($tmpA) <= ORD_NL) {
 +
 +                            // Whoopsie
 +
 +                            throw new UnterminatedStringLiteralJSMinException();
 +                        }
 +
 +                        // else
 +
 +                        if ($tmpA == '\\') {
 +
 +                            // Escape next char immediately
 +
 +                            $this->put($tmpA);
 +                            $tmpA = $this->theA = $this->get();
 +                        }
 +                    }
 +                }
 +
 +            case JSMIN_ACT_IMM : // N.B. possible fallthrough from above
 +
 +                // Get the next B
 +
 +                $this->theB = $this->next();
 +
 +                // Special case of recognising regular expressions (beginning with /) that are
 +                // preceded by '(', ',' or '='
 +
 +                $tmpA = $this->theA;
 +
 +                if ($this->theB == '/' && ($tmpA == '(' || $tmpA == ',' || $tmpA == '=')) {
 +
 +                    // Output the two successive chars
 +
 +                    $this->put($tmpA);
 +                    $this->put($this->theB);
 +
 +                    // Look for the end of the RE literal, watching out for escaped chars or a control /
 +                    // end of line char (the RE literal then being unterminated !)
 +
 +                    while (true) {
 +
 +                        $tmpA = $this->theA = $this->get();
 +
 +                        if ($tmpA == '/') {
 +
 +                            // RE literal terminated
 +
 +                            break; // from while(true)
 +                        }
 +
 +                        // else
 +
 +                        if ($tmpA == '\\') {
 +
 +                            // Escape next char immediately
 +
 +                            $this->put($tmpA);
 +                            $tmpA = $this->theA = $this->get();
 +                        }
 +                        else if (ord($tmpA) <= ORD_NL) {
 +
 +                            // Whoopsie
 +
 +                            throw new UnterminatedRegExpLiteralJSMinException();
 +                        }
 +
 +                        // Output RE characters
 +
 +                        $this->put($tmpA);
 +                    }
 +
 +                    // Move forward after the RE literal
 +
 +                    $this->theB = $this->next();
 +                }
 +
 +            break;
 +            default :
 +				throw new JSMinException('Expected a JSMin::ACT_* constant in action().');
 +        }
 +    }
 +
 +    /**
 +     * Run the JSMin application : minify some JS code.
 +     *
 +     * The code is read from the input stream, and its minified version is written to the output one.
 +     * In case input is a string, minified vesrions is also returned by this function as string.
 +     * That is : characters which are insignificant to JavaScript are removed, as well as comments ;
 +     * tabs are replaced with spaces ; carriage returns are replaced with linefeeds, and finally most
 +     * spaces and linefeeds are deleted.
 +     *
 +     * Note : name was changed from jsmin() because PHP identifiers are case-insensitive, and it is already
 +     * the name of this class.
 +     *
 +     * @see     JSMin()
 +     * @return null | string
 +     */
 +    function minify() {
 +
 +        // Initialize A and run the first (minimal) action
 +
 +        $this->theA = "\n";
 +        $this->action(JSMIN_ACT_IMM);
 +
 +        // Proceed all the way to the end of the input file
 +
 +        while ($this->theA !== EOF) {
 +
 +            switch ($this->theA) {
 +
 +                case ' ' :
 +
 +                    if (JSMin::isAlphaNum($this->theB)) {
 +                        $this->action(JSMIN_ACT_FULL);
 +                    }
 +                    else {
 +                        $this->action(JSMIN_ACT_BUF);
 +                    }
 +
 +                break;
 +                case "\n" :
 +
 +                    switch ($this->theB) {
 +
 +                        case '{' : case '[' : case '(' :
 +                        case '+' : case '-' :
 +
 +                            $this->action(JSMIN_ACT_FULL);
 +
 +                        break;
 +                        case ' ' :
 +
 +                            $this->action(JSMIN_ACT_IMM);
 +
 +                        break;
 +                        default :
 +
 +                            if (JSMin::isAlphaNum($this->theB)) {
 +                                $this->action(JSMIN_ACT_FULL);
 +                            }
 +                            else {
 +                                $this->action(JSMIN_ACT_BUF);
 +                            }
 +
 +                        break;
 +                    }
 +
 +                break;
 +                default :
 +
 +                    switch ($this->theB) {
 +
 +                        case ' ' :
 +
 +                            if (JSMin::isAlphaNum($this->theA)) {
 +
 +                                $this->action(JSMIN_ACT_FULL);
 +                                break;
 +                            }
 +
 +                            // else
 +
 +                            $this->action(JSMIN_ACT_IMM);
 +
 +                        break;
 +                        case "\n" :
 +
 +                            switch ($this->theA) {
 +
 +                                case '}' : case ']' : case ')' : case '+' :
 +                                case '-' : case '"' : case '\'' :
 +
 +                                    $this->action(JSMIN_ACT_FULL);
 +
 +                                break;
 +                                default :
 +
 +                                    if (JSMin::isAlphaNum($this->theA)) {
 +                                        $this->action(JSMIN_ACT_FULL);
 +                                    }
 +                                    else {
 +                                        $this->action(JSMIN_ACT_IMM);
 +                                    }
 +
 +                                break;
 +                            }
 +
 +                        break;
 +                        default :
 +
 +                            $this->action(JSMIN_ACT_FULL);
 +
 +                        break;
 +                    }
 +
 +                break;
 +            }
 +        }
 +
 +	    if ($this->isString) {
 +		    return $this->out;
 +
 +	    }
 +    }
 +
 +    /**
 +     * Prepare a new JSMin application.
 +     *
 +     * The next step is to {@link minify()} the input into the output.
 +     *
 +     * @param   string  $inFileName     The pathname of the input (unminified JS) file. STDIN if '-' or absent.
 +     * @param   string  $outFileName    The pathname of the output (minified JS) file. STDOUT if '-' or absent.
 +     *                                  If outFileName === FALSE, we assume that inFileName is in fact the string to be minified!!!
 +     * @param   array   $comments       Optional lines to present as comments at the beginning of the output.
 +     */
 +	function JSMin($inFileName = '-', $outFileName = '-', $comments = NULL) {
 +		if ($outFileName === FALSE) {
 +			$this->JSMin_String($inFileName, $comments);
 +		}
 +		else {
 +			$this->JSMin_File($inFileName, $outFileName, $comments);
 +		}
 +	}
 +
 +    function JSMin_File($inFileName = '-', $outFileName = '-', $comments = NULL) {
 +
 +        // Recuperate input and output streams.
 +        // Use STDIN and STDOUT by default, if they are defined (CLI mode) and no file names are provided
 +
 +            if ($inFileName == '-')  $inFileName  = 'php://stdin';
 +            if ($outFileName == '-') $outFileName = 'php://stdout';
 +
 +            try {
 +
 +                $this->in = new SplFileObject($inFileName, 'rb', TRUE);
 +            }
 +            catch (Exception $e) {
 +
 +                throw new FileOpenFailedJSMinException(
 +                    'Failed to open "'.$inFileName.'" for reading only.'
 +                );
 +            }
 +
 +            try {
 +
 +                $this->out = new SplFileObject($outFileName, 'wb', TRUE);
 +            }
 +            catch (Exception $e) {
 +
 +                throw new FileOpenFailedJSMinException(
 +                    'Failed to open "'.$outFileName.'" for writing only.'
 +                );
 +            }
 +
 +			/*$this->in = fopen($inFileName, 'rb');
 +			if (!$this->in) {
 +				trigger_error('Failed to open "'.$inFileName, E_USER_ERROR);
 +			}
 +			$this->out = fopen($outFileName, 'wb');
 +			if (!$this->out) {
 +				trigger_error('Failed to open "'.$outFileName, E_USER_ERROR);
 +			}*/
 +
 +            // Present possible initial comments
 +
 +            if ($comments !== NULL) {
 +                foreach ($comments as $comm) {
 +                    $this->out->fwrite('// '.str_replace("\n", " ", $comm)."\n");
 +                }
 +            }
 +
 +    }
 +
 +    function JSMin_String($inString, $comments = NULL) {
 +        $this->in = $inString;
 +        $this->out = '';
 +		$this->inLength = strlen($inString);
 +		$this->inPos = 0;
 +		$this->isString = true;
 +
 +        if ($comments !== NULL) {
 +            foreach ($comments as $comm) {
 +                $this->out .= '// '.str_replace("\n", " ", $comm)."\n";
 +            }
 +        }
 +	}
 +}
 +
 +/************** OUTPUT *****************/
 +
 +if(count(get_script_requests()) > 0)
 +{
 +	if(is_debug_mode())
 +	{
 +		if(($code = get_javascript_code()) !== null)
 +		{
 +			print_headers();
 +			echo supports_gzip_encoding() ? gzip_content($code) : $code;
 +		}
 +	}
 +	else
 +	{
 +		if(($filename = compressed_js_filename()) !== null)
 +		{
 +			if(!is_file($filename))
 +				save_javascript(get_javascript_code(true), $filename);
 +			print_headers();
 +			echo get_saved_javascript($filename);
 +		}
 +	}
 +}
 +
 +?>
\ No newline at end of file diff --git a/framework/Web/Javascripts/packages.php b/framework/Web/Javascripts/packages.php new file mode 100644 index 00000000..c6b6cf67 --- /dev/null +++ b/framework/Web/Javascripts/packages.php @@ -0,0 +1,55 @@ +<?php
 +
 +$packages = array(
 +	'prado' => array(
 +		'prototype/prototype.js',
 +		'scriptaculous/builder.js',
 +		'prado/prado.js',
 +		'prado/scriptaculous-adapter.js',
 +		'prado/controls/controls.js',
 +		'prado/ratings/ratings.js',
 +	),
 +
 +	'effects' => array(
 +		'scriptaculous/effects.js'
 +	),
 +
 +	'logger' => array(
 +		'prado/logger/logger.js',
 +	),
 +
 +	'validator' => array(
 +		'prado/validator/validation3.js'
 +	),
 +
 +	'datepicker' => array(
 +		'prado/datepicker/datepicker.js'
 +	),
 +
 +	'colorpicker' => array(
 +		'prado/colorpicker/colorpicker.js'
 +	),
 +
 +	'ajax' => array(
 +		'scriptaculous/controls.js',
 +		'prado/activecontrols/json.js',
 +		'prado/activecontrols/ajax3.js',
 +		'prado/activecontrols/activecontrols3.js',
 +		'prado/activecontrols/inlineeditor.js',
 +		'prado/activeratings/ratings.js'
 +	)
 +);
 +
 +$dependencies = array(
 +		'prado'			=> array('prado'),
 +		'effects'		=> array('prado', 'effects'),
 +		'validator'		=> array('prado', 'validator'),
 +		'logger'		=> array('prado', 'logger'),
 +		'datepicker'	=> array('prado', 'datepicker'),
 +		'colorpicker'	=> array('prado', 'colorpicker'),
 +		'ajax'			=> array('prado', 'effects', 'ajax')
 +);
 +
 +return array($packages, $dependencies);
 +
 +?>
\ No newline at end of file diff --git a/framework/Web/UI/WebControls/TClientScript.php b/framework/Web/UI/WebControls/TClientScript.php index 43817c44..8b80eb69 100644 --- a/framework/Web/UI/WebControls/TClientScript.php +++ b/framework/Web/UI/WebControls/TClientScript.php @@ -38,6 +38,8 @@   */
  class TClientScript extends TControl
  {
 +	const SCRIPT_LOADER = 'Web/Javascripts/clientscripts.php';
 +
  	/**
  	 * @return string comma delimited list of javascript libraries to included
  	 * on the page.
 @@ -50,8 +52,7 @@ class TClientScript extends TControl  	/**
  	 * Include javascript library to the current page. The current supported
  	 * libraries are: "prado", "effects", "ajax", "validator", "logger",
 -	 * "datepicker", "rico", "colorpicker". Library dependencies are
 -	 * automatically resolved.
 +	 * "datepicker", "colorpicker". Library dependencies are automatically resolved.
  	 *
  	 * @param string comma delimited list of javascript libraries to include.
  	 */
 @@ -77,6 +78,38 @@ class TClientScript extends TControl  	}
  	/**
 +	 * @param string custom javascript library directory.
 +	 */
 +	public function setPackagePath($value)
 +	{
 +		$this->setViewState('PackagePath', $value);
 +	}
 +
 +	/**
 +	 * @return string custom javascript library directory.
 +	 */
 +	public function getPackagePath()
 +	{
 +		return $this->getViewState('PackagePath');
 +	}
 +
 +	/**
 +	 * @param string load specific packages from the javascript library in the PackagePath, comma delimited package names
 +	 */
 +	public function setPackageScripts($value)
 +	{
 +		$this->setViewState('PackageScripts', $value,'');
 +	}
 +
 +	/**
 +	 * @return string comma delimited list of javascript library packages to load.
 +	 */
 +	public function getPackageScripts()
 +	{
 +		return $this->getViewState('PackageScripts','');
 +	}
 +
 +	/**
  	 * Calls the client script manager to add each of the requested client
  	 * script libraries.
  	 * @param mixed event parameter
 @@ -102,6 +135,7 @@ class TClientScript extends TControl  	public function render($writer)
  	{
  		$this->renderCustomScriptFile($writer);
 +		$this->renderPackageScriptFile($writer);
  		$this->renderCustomScript($writer);
  	}
 @@ -128,6 +162,48 @@ class TClientScript extends TControl  			$writer->write("\n/*]]>*/\n</script>\n");
  		}
  	}
 +
 +	protected function renderPackageScriptFile($writer)
 +	{
 +		$baseUrl = $this->publishScriptLoader();
 +		$scripts = split('/\s*[, ]+\s*/', $this->getPackageScripts());
 +		$url = $baseUrl . '?js=' . implode(',', $scripts);
 +		if($this->getApplication()->getMode()===TApplicationMode::Debug)
 +			$url.='&mode=debug';
 +		$writer->write("<script type=\"text/javascript\" src=\"{$url}\"></script>\n");
 +	}
 +
 +	protected function publishScriptLoader()
 +	{
 +		list($path, $url) = $this->getPublishedPackagePath();
 +		if(is_dir($path))
 +		{
 +			$scriptLoader = Prado::getFrameworkPath().'/'.self::SCRIPT_LOADER;
 +			$scriptLoaderFile = basename($scriptLoader);
 +			$dest = $path.'/'.$scriptLoaderFile;
 +			if(!is_file($dest))
 +				copy($scriptLoader,$dest);
 +			return $url.'/'.$scriptLoaderFile;
 +		}
 +		else
 +			throw new TConfigurationException('clientscript_invalid_package_path',
 +				$this->getPackagePath(), $this->getUniqueID());
 +	}
 +
 +	protected function getPublishedPackagePath()
 +	{
 +		$assets = $this->getApplication()->getAssetManager();
 +		//assumes dot path first
 +		$dir = Prado::getPathOfNameSpace($packageDir);
 +		if(!is_null($dir))
 +		{
 +			$url = $assets->publishFilePath($dir); //show throw an excemption if invalid
 +			return array($dir, $url);
 +		}
 +		$url = $this->getPackagePath();
 +		$packageDir = str_replace($assets->getBaseUrl(), '', $url);
 +		return array($assets->getBasePath().$packageDir,$url);
 +	}
  }
  ?>
\ No newline at end of file | 
